<commit_before>package commands

import (
	"fmt"
	"html/template"
	"io/ioutil"
	"log"
	"math"
	"os"
	"strings"

	"gopkg.in/yaml.v2"

	"github.com/andjosh/gopod"
	"github.com/codeskyblue/go-sh"
	"github.com/satori/go.uuid"
	"github.com/spf13/cobra"

	"podgen/utils"
	"time"
)

type Item struct {
	Title       string
	Description string
	Link        string
	Pubdate     string
	Image       string
	Guid        string
}

type Channel struct {
	Title       string
	Link        string
	Description string
	Image       string
	Copyright   string
	Language    string
	Author      string
	Categories  []string
	Page        int
	Twitter     string
	Linkedin    string
	Github      string
}

type PageTemplate struct {
	Info      Channel
	Home      string
	Current   Item
	Podcasts  []Item
	Paginator template.HTML
}

var generated_guid bool

var buildCmd = &cobra.Command{
	Use:   "build",
	Short: "build the podcast site",
	Long:  `build the podcast site, generate html files against template`,
	Run: func(cmd *cobra.Command, args []string) {
		execute()
	},
}

// command implementation

func execute() {
	generatePages()
	session := sh.NewSession()
	cpFiles(session, ".", TARGET_PATH, "assets", "CNAME")
	cpFiles(session, TEMPLATE_PATH, TARGET_PATH, "css", "font-awesome", "fonts", "img", "js")
	if generated_guid {
		fmt.Println("Build finished. Please copy guid: <guid_value> to your items.yml for each episode.")
	} else {
		fmt.Println("Build finished.")
	}
}

func generatePages() {
	channel := getChannelData("channel.yml")
	items := getItemData("items.yml")

	current := items[0]
	chopped_items := chopItems(items, channel.Page)
	len_chopped_items := len(chopped_items)

	generateRss(channel, items)

	pages := make([]int, len_chopped_items)
	for i := 1; i <= len_chopped_items; i++ {
		pages[i-1] = i
	}

	data, _ := ioutil.ReadFile(fmt.Sprintf("%s/index.tmpl", TEMPLATE_PATH))
	content := string(data[:])
	funcs := template.FuncMap{"alt": alt, "trunc": truncate}
	t := template.Must(template.New("Podgen").Funcs(funcs).Parse(content))

	for i := 1; i <= len_chopped_items; i++ {
		var filename string
		var home string
		if i == 1 {
			filename = "index.html"
			home = "#current"
		} else {
			filename = fmt.Sprintf("page%d.html", i)
			home = "index.html"
		}
		f, err := os.Create(fmt.Sprintf("%s/%s", TARGET_PATH, filename))
		utils.CheckError(err)
		defer f.Close()

		err = t.Execute(f, PageTemplate{
			Info:      channel,
			Home:      home,
			Current:   current,
			Podcasts:  chopped_items[i-1],
			Paginator: generatePaginator(i, len_chopped_items),
		})
		utils.CheckError(err)
	}
}

func generateRss(channel Channel, items []Item) {
	imageUrl := utils.Urljoin(channel.Link, ASSETS_PATH, channel.Image)
	c := gopod.ChannelFactory(channel.Title, channel.Link, channel.Description, imageUrl)
	c.SetiTunesExplicit("No")
	c.SetCopyright(channel.Copyright)
	c.SetiTunesAuthor(channel.Author)
	c.SetiTunesSummary(channel.Description)
	c.SetCategory(strings.Join(channel.Categories, ","))
	c.SetLanguage(channel.Language)

	for _, item := range items {
		url := utils.Urljoin(c.Link, ASSETS_PATH, item.Link)
		enclosure := gopod.Enclosure{
			Url:    url,
			Length: "0",
			Type:   "audio/mpeg",
		}
		pubdate, err := time.Parse(time.RFC3339, item.Pubdate)
		utils.CheckError(err)
		var guid string
		description := truncate(item.Description)
		if item.Guid != "" {
			guid = item.Guid
		} else {
			generated_guid = true
			guid = uuid.NewV4().String()
			fmt.Printf("%s -  guid: %s\n", item.Link, guid)

		}

		c.AddItem(&gopod.Item{
			Title:         item.Title,
			Link:          url,
			Description:   description,
			PubDate:       pubdate.Format(time.RFC1123),
			Guid:          guid,
			TunesAuthor:   channel.Author,
			TunesSubtitle: description,
			TunesSummary:  description,
			Enclosure:     []*gopod.Enclosure{&enclosure},
		})
	}
	f, err := os.Create(fmt.Sprintf("%s/%s", TARGET_PATH, "rss.xml"))
	utils.CheckError(err)
	defer f.Close()
	f.Write(c.Publish())
}

func getChannelData(filename string) (channel Channel) {
	data, err := ioutil.ReadFile(filename)
	if err != nil {
		log.Fatalf("Cannot read file %s (%s)", filename, err)
	}
	err = yaml.Unmarshal(data, &channel)
	if err != nil {
		log.Fatalf("Cannot parse file %s (%s)", filename, err)
	}
	return
}

func getItemData(filename string) (items []Item) {

	data, err := ioutil.ReadFile(filename)
	if err != nil {
		log.Fatalf("Cannot read file %s (%s)", filename, err)
	}
	err = yaml.Unmarshal(data, &items)
	if err != nil {
		log.Fatalf("Cannot parse file %s (%s)", filename, err)
	}

	// ugly reverse - I'm seeking something like list.reverse() in python but not found.
	for i, j := 0, len(items)-1; i < j; i, j = i+1, j-1 {
		items[i], items[j] = items[j], items[i]
	}

	return
}

func chopItems(items []Item, page int) (chopped_items [][]Item) {

	length := len(items)
	j := 0
	for i := 1; i < length; i += page {
		chopped_items = append(chopped_items, items[i:int(math.Min(float64(i+page), float64(length)))])
		j += 1
	}
	return
}

func alt(x int) string {
	if x%2 == 0 {
		return "a"
	} else {
		return "b"
	}
}

func truncate(str string) string {
	data := []rune(str)
	if len(data) <= MAX_DESCRIPTION {
		return str
	} else {
		return string(data[:MAX_DESCRIPTION-1]) + "..."
	}
}

// I cannot bear the golang template to do such a not-that-complicated paginator,
// thus I just do string concat myself...please tell me an elegant way to do so!!
func generatePaginator(curPage int, maxPage int) template.HTML {
	var data []string
	var pageName string
	var css_class string
	if curPage == 1 {
		data = append(data, "
«
")
	} else {
		if curPage-1 == 1 {
			pageName = "index.html"
		} else {
			pageName = fmt.Sprintf("page%d.html", (curPage - 1))
		}
		data = append(data, fmt.Sprintf("
«
", pageName))
	}
	for i := 1; i <= maxPage; i++ {
		if i == curPage {
			css_class = "active"
		} else {
			css_class = ""
		}

		if i == 1 {
			pageName = "index.html"
		} else {
			pageName = fmt.Sprintf("page%d.html", i)
		}
		data = append(data, fmt.Sprintf("
%d
", css_class, pageName, i))
	}

	if curPage == maxPage {
		data = append(data, "
»
")
	} else {
		data = append(data, fmt.Sprintf("
»
", (curPage+1)))
	}

	return template.HTML(strings.Join(data, "\n"))
}
<commit_msg>fix #2. Origin andjosh/gopod doesn't allow you to do so. Thus I forked and hacked it to tyrchen/gopod. Now it works<commit_after>package commands

import (
	"fmt"
	"html/template"
	"io/ioutil"
	"log"
	"math"
	"os"
	"strings"

	"gopkg.in/yaml.v2"

	"github.com/codeskyblue/go-sh"
	"github.com/satori/go.uuid"
	"github.com/spf13/cobra"
	"github.com/tyrchen/gopod"

	"podgen/utils"
	"time"
)

type Item struct {
	Title       string
	Description string
	Link        string
	Pubdate     string
	Image       string
	Guid        string
}

type Channel struct {
	Title       string
	Link        string
	Description string
	Image       string
	Copyright   string
	Language    string
	Author      string
	Categories  []string
	Page        int
	Twitter     string
	Linkedin    string
	Github      string
}

type PageTemplate struct {
	Info      Channel
	Home      string
	Current   Item
	Podcasts  []Item
	Paginator template.HTML
}

var generated_guid bool

var buildCmd = &cobra.Command{
	Use:   "build",
	Short: "build the podcast site",
	Long:  `build the podcast site, generate html files against template`,
	Run: func(cmd *cobra.Command, args []string) {
		execute()
	},
}

// command implementation

func execute() {
	generatePages()
	session := sh.NewSession()
	cpFiles(session, ".", TARGET_PATH, "assets", "CNAME")
	cpFiles(session, TEMPLATE_PATH, TARGET_PATH, "css", "font-awesome", "fonts", "img", "js")
	if generated_guid {
		fmt.Println("Build finished. Please copy guid: <guid_value> to your items.yml for each episode.")
	} else {
		fmt.Println("Build finished.")
	}
}

func generatePages() {
	channel := getChannelData("channel.yml")
	items := getItemData("items.yml")

	current := items[0]
	chopped_items := chopItems(items, channel.Page)
	len_chopped_items := len(chopped_items)

	generateRss(channel, items)

	pages := make([]int, len_chopped_items)
	for i := 1; i <= len_chopped_items; i++ {
		pages[i-1] = i
	}

	data, _ := ioutil.ReadFile(fmt.Sprintf("%s/index.tmpl", TEMPLATE_PATH))
	content := string(data[:])
	funcs := template.FuncMap{"alt": alt, "trunc": truncate}
	t := template.Must(template.New("Podgen").Funcs(funcs).Parse(content))

	for i := 1; i <= len_chopped_items; i++ {
		var filename string
		var home string
		if i == 1 {
			filename = "index.html"
			home = "#current"
		} else {
			filename = fmt.Sprintf("page%d.html", i)
			home = "index.html"
		}
		f, err := os.Create(fmt.Sprintf("%s/%s", TARGET_PATH, filename))
		utils.CheckError(err)
		defer f.Close()

		err = t.Execute(f, PageTemplate{
			Info:      channel,
			Home:      home,
			Current:   current,
			Podcasts:  chopped_items[i-1],
			Paginator: generatePaginator(i, len_chopped_items),
		})
		utils.CheckError(err)
	}
}

func generateRss(channel Channel, items []Item) {
	imageUrl := utils.Urljoin(channel.Link, ASSETS_PATH, channel.Image)
	c := gopod.ChannelFactory(channel.Title, channel.Link, channel.Description, imageUrl)
	c.SetiTunesExplicit("No")
	c.SetCopyright(channel.Copyright)
	c.SetiTunesAuthor(channel.Author)
	c.SetiTunesSummary(channel.Description)
	c.SetCategory(strings.Join(channel.Categories, ","))
	c.SetLanguage(channel.Language)

	for _, item := range items {
		url := utils.Urljoin(c.Link, ASSETS_PATH, item.Link)
		enclosure := gopod.Enclosure{
			Url:    url,
			Length: "0",
			Type:   "audio/mpeg",
		}
		pubdate, err := time.Parse(time.RFC3339, item.Pubdate)
		utils.CheckError(err)
		var guid string
		description := truncate(item.Description)
		if item.Guid != "" {
			guid = item.Guid
		} else {
			generated_guid = true
			guid = uuid.NewV4().String()
			fmt.Printf("%s -  guid: %s\n", item.Link, guid)

		}

		episode := gopod.Item{
			Title:         item.Title,
			Link:          url,
			Description:   description,
			PubDate:       pubdate.Format(time.RFC1123),
			Guid:          guid,
			TunesAuthor:   channel.Author,
			TunesSubtitle: description,
			TunesSummary:  description,
			Enclosure:     []*gopod.Enclosure{&enclosure},
		}

		episode.SetTunesImage(utils.Urljoin(channel.Link, ASSETS_PATH, item.Image))

		c.AddItem(&episode)
	}
	f, err := os.Create(fmt.Sprintf("%s/%s", TARGET_PATH, "rss.xml"))
	utils.CheckError(err)
	defer f.Close()
	f.Write(c.Publish())
}

func getChannelData(filename string) (channel Channel) {
	data, err := ioutil.ReadFile(filename)
	if err != nil {
		log.Fatalf("Cannot read file %s (%s)", filename, err)
	}
	err = yaml.Unmarshal(data, &channel)
	if err != nil {
		log.Fatalf("Cannot parse file %s (%s)", filename, err)
	}
	return
}

func getItemData(filename string) (items []Item) {

	data, err := ioutil.ReadFile(filename)
	if err != nil {
		log.Fatalf("Cannot read file %s (%s)", filename, err)
	}
	err = yaml.Unmarshal(data, &items)
	if err != nil {
		log.Fatalf("Cannot parse file %s (%s)", filename, err)
	}

	// ugly reverse - I'm seeking something like list.reverse() in python but not found.
	for i, j := 0, len(items)-1; i < j; i, j = i+1, j-1 {
		items[i], items[j] = items[j], items[i]
	}

	return
}

func chopItems(items []Item, page int) (chopped_items [][]Item) {

	length := len(items)
	j := 0
	for i := 1; i < length; i += page {
		chopped_items = append(chopped_items, items[i:int(math.Min(float64(i+page), float64(length)))])
		j += 1
	}
	return
}

func alt(x int) string {
	if x%2 == 0 {
		return "a"
	} else {
		return "b"
	}
}

func truncate(str string) string {
	data := []rune(str)
	if len(data) <= MAX_DESCRIPTION {
		return str
	} else {
		return string(data[:MAX_DESCRIPTION-1]) + "..."
	}
}

// I cannot bear the golang template to do such a not-that-complicated paginator,
// thus I just do string concat myself...please tell me an elegant way to do so!!
func generatePaginator(curPage int, maxPage int) template.HTML {
	var data []string
	var pageName string
	var css_class string
	if curPage == 1 {
		data = append(data, "
«
")
	} else {
		if curPage-1 == 1 {
			pageName = "index.html"
		} else {
			pageName = fmt.Sprintf("page%d.html", (curPage - 1))
		}
		data = append(data, fmt.Sprintf("
«
", pageName))
	}
	for i := 1; i <= maxPage; i++ {
		if i == curPage {
			css_class = "active"
		} else {
			css_class = ""
		}

		if i == 1 {
			pageName = "index.html"
		} else {
			pageName = fmt.Sprintf("page%d.html", i)
		}
		data = append(data, fmt.Sprintf("
%d
", css_class, pageName, i))
	}

	if curPage == maxPage {
		data = append(data, "
»
")
	} else {
		data = append(data, fmt.Sprintf("
»
", (curPage+1)))
	}

	return template.HTML(strings.Join(data, "\n"))
}
<|endoftext|>


<commit_before>package trap

import (
	"bufio"
	"compress/gzip"
	"errors"
	"fmt"
	"go/ast"
	"go/types"
	"io/ioutil"
	"os"
	"path/filepath"
	"unicode/utf8"

	"github.com/github/codeql-go/extractor/srcarchive"
	"golang.org/x/tools/go/packages"
)

// A Writer provides methods for writing data to a TRAP file
type Writer struct {
	zip             *gzip.Writer
	w               *bufio.Writer
	file            *os.File
	Labeler         *Labeler
	path            string
	trapFilePath    string
	Package         *packages.Package
	TypesOverride   map[ast.Expr]types.Type
	ObjectsOverride map[types.Object]types.Object
}

func FileFor(path string) (string, error) {
	trapFolder, err := trapFolder()
	if err != nil {
		return "", err
	}

	return filepath.Join(trapFolder, srcarchive.AppendablePath(path)+".trap.gz"), nil
}

// NewWriter creates a TRAP file for the given path and returns a writer for
// writing to it
func NewWriter(path string, pkg *packages.Package) (*Writer, error) {
	trapFilePath, err := FileFor(path)
	if err != nil {
		return nil, err
	}
	trapFileDir := filepath.Dir(trapFilePath)
	err = os.MkdirAll(trapFileDir, 0755)
	if err != nil {
		return nil, err
	}
	tmpFile, err := ioutil.TempFile(trapFileDir, filepath.Base(trapFilePath))
	if err != nil {
		return nil, err
	}
	bufioWriter := bufio.NewWriter(tmpFile)
	zipWriter := gzip.NewWriter(bufioWriter)
	tw := &Writer{
		zipWriter,
		bufioWriter,
		tmpFile,
		nil,
		path,
		trapFilePath,
		pkg,
		make(map[ast.Expr]types.Type),
		make(map[types.Object]types.Object),
	}
	tw.Labeler = newLabeler(tw)
	return tw, nil
}

func trapFolder() (string, error) {
	trapFolder := os.Getenv("CODEQL_EXTRACTOR_GO_TRAP_DIR")
	if trapFolder == "" {
		trapFolder = os.Getenv("TRAP_FOLDER")
	}
	if trapFolder == "" {
		return "", errors.New("environment variable CODEQL_EXTRACTOR_GO_TRAP_DIR not set")
	}
	err := os.MkdirAll(trapFolder, 0755)
	if err != nil {
		return "", err
	}
	return trapFolder, nil
}

// Close the underlying file writer
func (tw *Writer) Close() error {
	err := tw.zip.Close()
	if err != nil {
		// return zip-close error, but ignore file-close error
		tw.file.Close()
		return err
	}
	err = tw.w.Flush()
	if err != nil {
		// throw away close error because write errors are likely to be more important
		tw.file.Close()
		return err
	}
	err = tw.file.Close()
	if err != nil {
		return err
	}
	return os.Rename(tw.file.Name(), tw.trapFilePath)
}

// ForEachObject iterates over all objects labeled by this labeler, and invokes
// the provided callback with a writer for the trap file, the object, and its
// label. It returns true if any extra objects were labeled and false otherwise.
func (tw *Writer) ForEachObject(cb func(*Writer, types.Object, Label)) bool {
	// copy the objects into an array so that our behaviour is deterministic even
	// if `cb` adds any new objects
	i := 0
	objects := make([]types.Object, len(tw.Labeler.objectLabels))
	for k := range tw.Labeler.objectLabels {
		objects[i] = k
		i++
	}

	for _, object := range objects {
		cb(tw, object, tw.Labeler.objectLabels[object])
	}

	return len(tw.Labeler.objectLabels) != len(objects)
}

const max_strlen = 1024 * 1024

func capStringLength(s string) string {
	// if the UTF8-encoded string is longer than 1MiB, we truncate it
	if len(s) > max_strlen {
		// to ensure that the truncated string is valid UTF-8, we find the last byte at or
		// before index max_strlen that starts a UTF-8 encoded character, and then cut off
		// right before that byte
		end := max_strlen
		for ; !utf8.RuneStart(s[end]); end-- {
		}
		return s[0:end]
	}
	return s
}

// Emit writes out a tuple of values for the given `table`
func (tw *Writer) Emit(table string, values []interface{}) error {
	fmt.Fprintf(tw.zip, "%s(", table)
	for i, value := range values {
		if i > 0 {
			fmt.Fprint(tw.zip, ", ")
		}
		switch value := value.(type) {
		case Label:
			fmt.Fprint(tw.zip, value.id)
		case string:
			fmt.Fprintf(tw.zip, "\"%s\"", escapeString(capStringLength(value)))
		case int:
			fmt.Fprintf(tw.zip, "%d", value)
		case float64:
			fmt.Fprintf(tw.zip, "%e", value)
		default:
			return errors.New("Cannot emit value")
		}
	}
	fmt.Fprintf(tw.zip, ")\n")
	return nil
}
<commit_msg>Go: Optimize trap.Writer by buffering gzip writes<commit_after>package trap

import (
	"bufio"
	"compress/gzip"
	"errors"
	"fmt"
	"go/ast"
	"go/types"
	"io/ioutil"
	"os"
	"path/filepath"
	"unicode/utf8"

	"github.com/github/codeql-go/extractor/srcarchive"
	"golang.org/x/tools/go/packages"
)

// A Writer provides methods for writing data to a TRAP file
type Writer struct {
	zip             *gzip.Writer
	wzip            *bufio.Writer
	wfile           *bufio.Writer
	file            *os.File
	Labeler         *Labeler
	path            string
	trapFilePath    string
	Package         *packages.Package
	TypesOverride   map[ast.Expr]types.Type
	ObjectsOverride map[types.Object]types.Object
}

func FileFor(path string) (string, error) {
	trapFolder, err := trapFolder()
	if err != nil {
		return "", err
	}

	return filepath.Join(trapFolder, srcarchive.AppendablePath(path)+".trap.gz"), nil
}

// NewWriter creates a TRAP file for the given path and returns a writer for
// writing to it
func NewWriter(path string, pkg *packages.Package) (*Writer, error) {
	trapFilePath, err := FileFor(path)
	if err != nil {
		return nil, err
	}
	trapFileDir := filepath.Dir(trapFilePath)
	err = os.MkdirAll(trapFileDir, 0755)
	if err != nil {
		return nil, err
	}
	tmpFile, err := ioutil.TempFile(trapFileDir, filepath.Base(trapFilePath))
	if err != nil {
		return nil, err
	}
	bufioFileWriter := bufio.NewWriter(tmpFile)
	zipWriter := gzip.NewWriter(bufioFileWriter)
	bufioZipWriter := bufio.NewWriter(zipWriter)
	tw := &Writer{
		zipWriter,
		bufioZipWriter,
		bufioFileWriter,
		tmpFile,
		nil,
		path,
		trapFilePath,
		pkg,
		make(map[ast.Expr]types.Type),
		make(map[types.Object]types.Object),
	}
	tw.Labeler = newLabeler(tw)
	return tw, nil
}

func trapFolder() (string, error) {
	trapFolder := os.Getenv("CODEQL_EXTRACTOR_GO_TRAP_DIR")
	if trapFolder == "" {
		trapFolder = os.Getenv("TRAP_FOLDER")
	}
	if trapFolder == "" {
		return "", errors.New("environment variable CODEQL_EXTRACTOR_GO_TRAP_DIR not set")
	}
	err := os.MkdirAll(trapFolder, 0755)
	if err != nil {
		return "", err
	}
	return trapFolder, nil
}

// Close the underlying file writer
func (tw *Writer) Close() error {
	err := tw.wzip.Flush()
	if err != nil {
		// throw away file close error
		tw.file.Close()
	}
	err = tw.zip.Close()
	if err != nil {
		// return zip-close error, but ignore file-close error
		tw.file.Close()
		return err
	}
	err = tw.wfile.Flush()
	if err != nil {
		// throw away close error because write errors are likely to be more important
		tw.file.Close()
		return err
	}
	err = tw.file.Close()
	if err != nil {
		return err
	}
	return os.Rename(tw.file.Name(), tw.trapFilePath)
}

// ForEachObject iterates over all objects labeled by this labeler, and invokes
// the provided callback with a writer for the trap file, the object, and its
// label. It returns true if any extra objects were labeled and false otherwise.
func (tw *Writer) ForEachObject(cb func(*Writer, types.Object, Label)) bool {
	// copy the objects into an array so that our behaviour is deterministic even
	// if `cb` adds any new objects
	i := 0
	objects := make([]types.Object, len(tw.Labeler.objectLabels))
	for k := range tw.Labeler.objectLabels {
		objects[i] = k
		i++
	}

	for _, object := range objects {
		cb(tw, object, tw.Labeler.objectLabels[object])
	}

	return len(tw.Labeler.objectLabels) != len(objects)
}

const max_strlen = 1024 * 1024

func capStringLength(s string) string {
	// if the UTF8-encoded string is longer than 1MiB, we truncate it
	if len(s) > max_strlen {
		// to ensure that the truncated string is valid UTF-8, we find the last byte at or
		// before index max_strlen that starts a UTF-8 encoded character, and then cut off
		// right before that byte
		end := max_strlen
		for ; !utf8.RuneStart(s[end]); end-- {
		}
		return s[0:end]
	}
	return s
}

// Emit writes out a tuple of values for the given `table`
func (tw *Writer) Emit(table string, values []interface{}) error {
	fmt.Fprintf(tw.wzip, "%s(", table)
	for i, value := range values {
		if i > 0 {
			fmt.Fprint(tw.wzip, ", ")
		}
		switch value := value.(type) {
		case Label:
			fmt.Fprint(tw.wzip, value.id)
		case string:
			fmt.Fprintf(tw.wzip, "\"%s\"", escapeString(capStringLength(value)))
		case int:
			fmt.Fprintf(tw.wzip, "%d", value)
		case float64:
			fmt.Fprintf(tw.wzip, "%e", value)
		default:
			return errors.New("Cannot emit value")
		}
	}
	fmt.Fprintf(tw.wzip, ")\n")
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build go1.7
// +build gc

package gcexportdata_test

import (
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"go/types"
	"log"
	"os"
	"path/filepath"

	"golang.org/x/tools/go/gcexportdata"
)

// ExampleRead uses gcexportdata.Read to load type information for the
// "fmt" package from the fmt.a file produced by the gc compiler.
func ExampleRead() {
	// Find the export data file.
	filename, path := gcexportdata.Find("fmt", "")
	if filename == "" {
		log.Fatalf("can't find export data for fmt")
	}
	fmt.Printf("Package path:       %s\n", path)
	fmt.Printf("Export data:        %s\n", filepath.Base(filename))

	// Open and read the file.
	f, err := os.Open(filename)
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()
	r, err := gcexportdata.NewReader(f)
	if err != nil {
		log.Fatalf("reading export data %s: %v", filename, err)
	}

	// Decode the export data.
	fset := token.NewFileSet()
	imports := make(map[string]*types.Package)
	pkg, err := gcexportdata.Read(r, fset, imports, path)
	if err != nil {
		log.Fatal(err)
	}

	// Print package information.
	fmt.Printf("Package members:    %s...\n", pkg.Scope().Names()[:5])
	println := pkg.Scope().Lookup("Println")
	posn := fset.Position(println.Pos())
	posn.Line = 123 // make example deterministic
	fmt.Printf("Println type:       %s\n", println.Type())
	fmt.Printf("Println location:   %s\n", slashify(posn))

	// Output:
	//
	// Package path:       fmt
	// Export data:        fmt.a
	// Package members:    [Errorf Formatter Fprint Fprintf Fprintln]...
	// Println type:       func(a ...interface{}) (n int, err error)
	// Println location:   $GOROOT/src/fmt/print.go:123:1
}

// ExampleNewImporter demonstrates usage of NewImporter to provide type
// information for dependencies when type-checking Go source code.
func ExampleNewImporter() {
	const src = `package myscanner

// choosing a package that is unlikely to change across releases
import "text/scanner"

const eof = scanner.EOF
`
	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, "myscanner.go", src, 0)
	if err != nil {
		log.Fatal(err)
	}

	packages := make(map[string]*types.Package)
	imp := gcexportdata.NewImporter(fset, packages)
	conf := types.Config{Importer: imp}
	pkg, err := conf.Check("myscanner", fset, []*ast.File{f}, nil)
	if err != nil {
		log.Fatal(err)
	}

	// object from imported package
	pi := packages["text/scanner"].Scope().Lookup("EOF")
	fmt.Printf("const %s.%s %s = %s // %s\n",
		pi.Pkg().Path(),
		pi.Name(),
		pi.Type(),
		pi.(*types.Const).Val(),
		slashify(fset.Position(pi.Pos())),
	)

	// object in source package
	twopi := pkg.Scope().Lookup("eof")
	fmt.Printf("const %s %s = %s // %s\n",
		twopi.Name(),
		twopi.Type(),
		twopi.(*types.Const).Val(),
		slashify(fset.Position(twopi.Pos())),
	)

	// Output:
	//
	// const text/scanner.EOF untyped int = -1 // $GOROOT/src/text/scanner/scanner.go:75:1
	// const eof untyped int = -1 // myscanner.go:6:7
}

func slashify(posn token.Position) token.Position {
	posn.Filename = filepath.ToSlash(posn.Filename) // for MS Windows portability
	return posn
}
<commit_msg>go/gcexportdata: switch constant used in tests<commit_after>// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build go1.7
// +build gc

package gcexportdata_test

import (
	"fmt"
	"go/ast"
	"go/parser"
	"go/token"
	"go/types"
	"log"
	"os"
	"path/filepath"

	"golang.org/x/tools/go/gcexportdata"
)

// ExampleRead uses gcexportdata.Read to load type information for the
// "fmt" package from the fmt.a file produced by the gc compiler.
func ExampleRead() {
	// Find the export data file.
	filename, path := gcexportdata.Find("fmt", "")
	if filename == "" {
		log.Fatalf("can't find export data for fmt")
	}
	fmt.Printf("Package path:       %s\n", path)
	fmt.Printf("Export data:        %s\n", filepath.Base(filename))

	// Open and read the file.
	f, err := os.Open(filename)
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()
	r, err := gcexportdata.NewReader(f)
	if err != nil {
		log.Fatalf("reading export data %s: %v", filename, err)
	}

	// Decode the export data.
	fset := token.NewFileSet()
	imports := make(map[string]*types.Package)
	pkg, err := gcexportdata.Read(r, fset, imports, path)
	if err != nil {
		log.Fatal(err)
	}

	// Print package information.
	fmt.Printf("Package members:    %s...\n", pkg.Scope().Names()[:5])
	println := pkg.Scope().Lookup("Println")
	posn := fset.Position(println.Pos())
	posn.Line = 123 // make example deterministic
	fmt.Printf("Println type:       %s\n", println.Type())
	fmt.Printf("Println location:   %s\n", slashify(posn))

	// Output:
	//
	// Package path:       fmt
	// Export data:        fmt.a
	// Package members:    [Errorf Formatter Fprint Fprintf Fprintln]...
	// Println type:       func(a ...interface{}) (n int, err error)
	// Println location:   $GOROOT/src/fmt/print.go:123:1
}

// ExampleNewImporter demonstrates usage of NewImporter to provide type
// information for dependencies when type-checking Go source code.
func ExampleNewImporter() {
	const src = `package myscanner

// choosing a package that is unlikely to change across releases
import "text/scanner"

const scanIdents = scanner.ScanIdents
`
	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, "myscanner.go", src, 0)
	if err != nil {
		log.Fatal(err)
	}

	packages := make(map[string]*types.Package)
	imp := gcexportdata.NewImporter(fset, packages)
	conf := types.Config{Importer: imp}
	pkg, err := conf.Check("myscanner", fset, []*ast.File{f}, nil)
	if err != nil {
		log.Fatal(err)
	}

	// object from imported package
	pi := packages["text/scanner"].Scope().Lookup("ScanIdents")
	fmt.Printf("const %s.%s %s = %s // %s\n",
		pi.Pkg().Path(),
		pi.Name(),
		pi.Type(),
		pi.(*types.Const).Val(),
		slashify(fset.Position(pi.Pos())),
	)

	// object in source package
	twopi := pkg.Scope().Lookup("scanIdents")
	fmt.Printf("const %s %s = %s // %s\n",
		twopi.Name(),
		twopi.Type(),
		twopi.(*types.Const).Val(),
		slashify(fset.Position(twopi.Pos())),
	)

	// Output:
	//
	// const text/scanner.ScanIdents untyped int = 4 // $GOROOT/src/text/scanner/scanner.go:62:1
	// const scanIdents untyped int = 4 // myscanner.go:6:7
}

func slashify(posn token.Position) token.Position {
	posn.Filename = filepath.ToSlash(posn.Filename) // for MS Windows portability
	return posn
}
<|endoftext|>


<commit_before>package hadoopconf

import (
	"archive/zip"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
)

type HadoopConf struct {
	multiSourceConf
	CoreSite *ConfWithDefault
	HdfsSite *ConfWithDefault
	MapredSite *ConfWithDefault
	YarnSite *ConfWithDefault
}

type HadoopDefaultConf struct {
	CoreSite ConfSourcer
	HdfsSite ConfSourcer
	MapredSite ConfSourcer
	YarnSite ConfSourcer
}

func (c *HadoopConf) Save() error {
	for _, conf := range []*ConfWithDefault{c.CoreSite, c.HdfsSite, c.MapredSite, c.YarnSite} {
		if err := conf.Conf.(*FileConfiguration).Save(); err != nil {
			return err
		}
	}
	return nil
}

func FromConf(coreSite *ConfWithDefault, hdfsSite *ConfWithDefault,
	mapredSite *ConfWithDefault, yarnSite *ConfWithDefault) *HadoopConf {
	confs := []ConfSourcer{coreSite, hdfsSite}
	if yarnSite != nil {
		confs = append(confs, yarnSite)
	}
	if mapredSite != nil {
		confs = append(confs, mapredSite)
	}
	return &HadoopConf{confs, coreSite, hdfsSite, mapredSite, yarnSite}
}

func anyRegexpMatch(s string, res []*regexp.Regexp) bool {
	for _, re := range res {
		if re.MatchString(s) {
			return true
		}
	}
	return false
}

func ConfsFromJar(jar string, files ...string) ([]ConfSourcer, error) {
	rv := make([]ConfSourcer, len(files))
	r, err := zip.OpenReader(jar)
	if err != nil {
		return nil, err
	}
	defer r.Close()
	m := make(map[string]int)
	for i, name := range files {
		m[name] = i
	}
	for _, f := range r.File {
		if ix, ok := m[f.Name]; ok {
			r, err := f.Open()
			if err != nil {
				return nil, err
			}
			b, err := ioutil.ReadAll(r)
			if err != nil {
				return nil, err
			}
			if rv[ix], err = NewGeneratedConfFromBytes(Source{filepath.Join(jar, f.Name), FileFromJar}, b); err != nil {
				return nil, err
			}
		}
	}
	return rv, nil
}

func ConfFromJar(jar string, file string) (ConfSourcer, error) {
	confs, err := ConfsFromJar(jar, file)
	if err != nil {
		return nil, err
	}
	if confs[0] == nil {
		return nil, errors.New("Cannot find " + file + " in " + jar)
	}
	return confs[0], nil
}

func globRegexp(basedirs []string, res []*regexp.Regexp) (string, error) {
	for _, glob := range basedirs {
		dirs, err := filepath.Glob(glob)
		if err != nil {
			return "", err
		}
		for _, dir := range dirs {
			jars, err := ioutil.ReadDir(dir)
			if err != nil {
				return "", err
			}
			for _, jar := range jars {
				if anyRegexpMatch(jar.Name(), res) {
					return filepath.Join(dir, jar.Name()), nil
				}
			}
		}
	}
	return "", errors.New(fmt.Sprintln("in", basedirs, "jar was not found", res))
}

func getDefault(filename string, res []*regexp.Regexp, basedirs ...string) (ConfSourcer, error) {
	jar, err := globRegexp(basedirs, res)
	if err != nil {
		return nil, err
	}
	return ConfFromJar(jar, filename)
}

func getCoreDefault(basedirs ...string) (ConfSourcer, error) {
	hadoopCommonRegexp := []*regexp.Regexp{
		regexp.MustCompile(`hadoop-(common|core)-[0-9.]+-?(beta|alpha|rc)?\.jar`),
	}
	jar, err := globRegexp(basedirs, hadoopCommonRegexp)
	if err != nil {
		return nil, err
	}
	return ConfFromJar(jar, "core-default.xml")
}

func getConf(confName string, globs ...string) (*FileConfiguration, error) {
	for _, glob := range globs {
		dirs, err := filepath.Glob(glob)
		if err != nil {
			return nil, err
		}
		for _, dir := range dirs {
			conf, err := NewFileConfiguration(filepath.Join(dir, confName))
			if os.IsNotExist(err) {
				continue
			}
			if err != nil {
				return nil, err
			}
			return conf, nil
		}
	}
	return nil, errors.New(fmt.Sprintln("cannot find file", confName, "in any of", globs))
}

type returnPanic struct {
	err error
}

func oneRe(pat string) []*regexp.Regexp {
	return []*regexp.Regexp{regexp.MustCompile(pat)}
}

func Jars(basedir string) (*HadoopDefaultConf, error) {
	coreDefault, err := getDefault("core-default.xml", oneRe(`hadoop-(common|core)-[0-9.]+-?(beta|alpha|rc)?\.jar`), basedir,
		filepath.Join(basedir, "share/hadoop/common"), "/share/hadoop/common")
	if err != nil {
		return nil, err
	}
	hdfsDefault, err := getDefault("hdfs-default.xml", oneRe(`hadoop-(hdfs|core)-[0-9.]+-?(beta|alpha|rc)?\.jar`), basedir,
		filepath.Join(basedir, "share/hadoop/hdfs"), "/share/hadoop/hdfs")
	if err != nil {
		return nil, err
	}
	mapredDefault, err := getDefault("mapred-default.xml", oneRe(`hadoop-(mapreduce-client-)?core-[0-9.]+-?(beta|alpha|rc)?\.jar`), basedir,
		filepath.Join(basedir, "share/hadoop/mapreduce"), "/share/hadoop/mapreduce")
	if mapredDefault == nil {
		fmt.Println("got", err)
	}
	yarnDefault, _ := getDefault("yarn-default.xml", oneRe(`hadoop-yarn-common-[0-9.]+-?(beta|alpha|rc)?\.jar`), basedir,
		filepath.Join(basedir, "share/hadoop/yarn"), "/share/hadoop/yarn")
	return &HadoopDefaultConf{
		CoreSite: coreDefault,
		HdfsSite: hdfsDefault,
		MapredSite: mapredDefault,
		YarnSite: yarnDefault,
	}, nil
}

func New(basedir string, defaultConf *HadoopDefaultConf) (conf *HadoopConf, err error) {
	j := func(s string) string {
		return filepath.Join(basedir, s)
	}
	coreSite, err := getConf("core-site.xml", j("etc/hadoop"), j("conf"), basedir)
	if err != nil {
		return nil, err
	}
	if _, err := os.Stat(coreSite.Path); err != nil {
		return nil, err
	}
	hdfsSite, err := getConf("hdfs-site.xml", j("etc/hadoop"), j("conf"), basedir)
	if err != nil {
		return nil, err
	}
	mapredSite, _ := getConf("mapred-site.xml", j("etc/hadoop"), j("conf"), basedir)
	yarnSite, _ := getConf("yarn-site.xml", j("etc/hadoop"), j("conf"), basedir)
	return FromConf(&ConfWithDefault{Default: defaultConf.CoreSite, Conf: coreSite},
		&ConfWithDefault{Default: defaultConf.HdfsSite, Conf: hdfsSite},
		&ConfWithDefault{Default: defaultConf.MapredSite, Conf: mapredSite},
		&ConfWithDefault{Default: defaultConf.YarnSite, Conf: yarnSite},
		), nil
}

<commit_msg>support cloudera CDH packaging<commit_after>package hadoopconf

import (
	"archive/zip"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
)

type HadoopConf struct {
	multiSourceConf
	CoreSite *ConfWithDefault
	HdfsSite *ConfWithDefault
	MapredSite *ConfWithDefault
	YarnSite *ConfWithDefault
}

type HadoopDefaultConf struct {
	CoreSite ConfSourcer
	HdfsSite ConfSourcer
	MapredSite ConfSourcer
	YarnSite ConfSourcer
}

func (c *HadoopConf) Save() error {
	for _, conf := range []*ConfWithDefault{c.CoreSite, c.HdfsSite, c.MapredSite, c.YarnSite} {
		if err := conf.Conf.(*FileConfiguration).Save(); err != nil {
			return err
		}
	}
	return nil
}

func FromConf(coreSite *ConfWithDefault, hdfsSite *ConfWithDefault,
	mapredSite *ConfWithDefault, yarnSite *ConfWithDefault) *HadoopConf {
	confs := []ConfSourcer{coreSite, hdfsSite}
	if yarnSite != nil {
		confs = append(confs, yarnSite)
	}
	if mapredSite != nil {
		confs = append(confs, mapredSite)
	}
	return &HadoopConf{confs, coreSite, hdfsSite, mapredSite, yarnSite}
}

func anyRegexpMatch(s string, res []*regexp.Regexp) bool {
	for _, re := range res {
		if re.MatchString(s) {
			return true
		}
	}
	return false
}

func ConfsFromJar(jar string, files ...string) ([]ConfSourcer, error) {
	rv := make([]ConfSourcer, len(files))
	r, err := zip.OpenReader(jar)
	if err != nil {
		return nil, err
	}
	defer r.Close()
	m := make(map[string]int)
	for i, name := range files {
		m[name] = i
	}
	for _, f := range r.File {
		if ix, ok := m[f.Name]; ok {
			r, err := f.Open()
			if err != nil {
				return nil, err
			}
			b, err := ioutil.ReadAll(r)
			if err != nil {
				return nil, err
			}
			if rv[ix], err = NewGeneratedConfFromBytes(Source{filepath.Join(jar, f.Name), FileFromJar}, b); err != nil {
				return nil, err
			}
		}
	}
	return rv, nil
}

func ConfFromJar(jar string, file string) (ConfSourcer, error) {
	confs, err := ConfsFromJar(jar, file)
	if err != nil {
		return nil, err
	}
	if confs[0] == nil {
		return nil, errors.New("Cannot find " + file + " in " + jar)
	}
	return confs[0], nil
}

func globRegexp(basedirs []string, res []*regexp.Regexp) (string, error) {
	for _, glob := range basedirs {
		dirs, err := filepath.Glob(glob)
		if err != nil {
			return "", err
		}
		for _, dir := range dirs {
			jars, err := ioutil.ReadDir(dir)
			if err != nil {
				return "", err
			}
			for _, jar := range jars {
				if anyRegexpMatch(jar.Name(), res) {
					return filepath.Join(dir, jar.Name()), nil
				}
			}
		}
	}
	return "", errors.New(fmt.Sprintln("in", basedirs, "jar was not found", res))
}

func getDefault(filename string, res []*regexp.Regexp, basedirs ...string) (ConfSourcer, error) {
	jar, err := globRegexp(basedirs, res)
	if err != nil {
		return nil, err
	}
	return ConfFromJar(jar, filename)
}

func getCoreDefault(basedirs ...string) (ConfSourcer, error) {
	hadoopCommonRegexp := []*regexp.Regexp{
		regexp.MustCompile(`hadoop-(common|core)-[0-9.]+-?([a-zA-Z0-9._]+)?\.jar`),
	}
	jar, err := globRegexp(basedirs, hadoopCommonRegexp)
	if err != nil {
		return nil, err
	}
	return ConfFromJar(jar, "core-default.xml")
}

func getConf(confName string, globs ...string) (*FileConfiguration, error) {
	for _, glob := range globs {
		dirs, err := filepath.Glob(glob)
		if err != nil {
			return nil, err
		}
		for _, dir := range dirs {
			conf, err := NewFileConfiguration(filepath.Join(dir, confName))
			if os.IsNotExist(err) {
				continue
			}
			if err != nil {
				return nil, err
			}
			return conf, nil
		}
	}
	return nil, errors.New(fmt.Sprintln("cannot find file", confName, "in any of", globs))
}

type returnPanic struct {
	err error
}

func re(pats ...string) []*regexp.Regexp {
	res := []*regexp.Regexp{}
	for _, pat := range pats {
		res = append(res, regexp.MustCompile(pat))
	}
	return res
}

func Jars(basedir string) (*HadoopDefaultConf, error) {
	coreDefault, err := getDefault("core-default.xml", re(`hadoop-(common|core)-[0-9.]+-?([a-zA-Z0-9._]+)?\.jar`, "hadoop-common.jar"), basedir,
		filepath.Join(basedir, "hadoop-common"),
		"/usr/lib/hadoop",
		"/share/hadoop/common")
	if err != nil {
		return nil, err
	}
	hdfsDefault, err := getDefault("hdfs-default.xml", re(`hadoop-(hdfs|core)-[0-9.]+-?([a-zA-Z0-9._]+)?\.jar`), basedir,
		filepath.Join(basedir, "share/hadoop/hdfs"),
		filepath.Join(basedir, "hadoop-hdfs"),
		"/share/hadoop/hdfs",
		"/usr/lib/hadoop-hdfs")
	if err != nil {
		return nil, err
	}
	mapredDefault, err := getDefault("mapred-default.xml", re(`hadoop-(mapreduce-client-)?core-[0-9.]+-?([a-zA-Z0-9._]+)?\.jar`), basedir,
		filepath.Join(basedir, "hadoop-0.20-mapreduce"),
		filepath.Join(basedir, "hadoop-mapreduce"),
		filepath.Join(basedir, "share/hadoop/mapreduce"),
		"/share/hadoop/mapreduce",
		"/usr/lib/hadoop-0.20-mapreduce",
		"/usr/lib/hadoop-mapreduce")
	if mapredDefault == nil {
		fmt.Println("got", err)
	}
	yarnDefault, _ := getDefault("yarn-default.xml", re(`hadoop-yarn-common-[0-9.]+-?([a-zA-Z0-9._]+)?\.jar`), basedir,
		filepath.Join(basedir, "share/hadoop/yarn"),
		filepath.Join(basedir, "hadoop-yarn"),
		"/usr/lib/hadoop-yarn",
		"/share/hadoop/yarn")
	return &HadoopDefaultConf{
		CoreSite: coreDefault,
		HdfsSite: hdfsDefault,
		MapredSite: mapredDefault,
		YarnSite: yarnDefault,
	}, nil
}

func New(basedir string, defaultConf *HadoopDefaultConf) (conf *HadoopConf, err error) {
	j := func(s string) string {
		return filepath.Join(basedir, s)
	}
	coreSite, err := getConf("core-site.xml", j("etc/hadoop"), j("conf"), basedir)
	if err != nil {
		return nil, err
	}
	if _, err := os.Stat(coreSite.Path); err != nil {
		return nil, err
	}
	hdfsSite, err := getConf("hdfs-site.xml", j("etc/hadoop"), j("conf"), basedir)
	if err != nil {
		return nil, err
	}
	mapredSite, _ := getConf("mapred-site.xml", j("etc/hadoop"), j("conf"), basedir)
	yarnSite, _ := getConf("yarn-site.xml", j("etc/hadoop"), j("conf"), basedir)
	return FromConf(&ConfWithDefault{Default: defaultConf.CoreSite, Conf: coreSite},
		&ConfWithDefault{Default: defaultConf.HdfsSite, Conf: hdfsSite},
		&ConfWithDefault{Default: defaultConf.MapredSite, Conf: mapredSite},
		&ConfWithDefault{Default: defaultConf.YarnSite, Conf: yarnSite},
		), nil
}

<|endoftext|>


<commit_before>// Copyright 2016 The rkt Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// +build coreos src

package main

import (
	"fmt"
	"os"
	"testing"

	"github.com/coreos/rkt/tests/testutils"
)

// TestPathsWrite checks whether access to paths like /proc/sysrq-trigger are
// restricted
func TestPathsWrite(t *testing.T) {
	imageFile := patchTestACI("rkt-inspect-paths.aci",
		"--exec=/inspect --write-file --print-msg=testing-insecure-option")
	defer os.Remove(imageFile)

	ctx := testutils.NewRktRunCtx()
	defer ctx.Cleanup()

	tests := []struct {
		Path    string
		Content string
	}{
		{
			Path:    "/proc/sysrq-trigger",
			Content: "h", // Print help message in dmesg: not dangerous
		},
	}

	for _, tt := range tests {
		// Without --insecure-options=paths
		expectErr := fmt.Sprintf("open %s: read-only file system", tt.Path)
		for _, insecureOption := range []string{"image", "image,ondisk,capabilities", "all-fetch"} {
			// run
			cmd := fmt.Sprintf(`%s --debug --insecure-options=%s run --set-env=FILE=%s --set-env=CONTENT=%s %s`,
				ctx.Cmd(), insecureOption, tt.Path, tt.Content, imageFile)
			t.Logf("run: attempting to write on %q with --insecure-options=%s (expecting error)\n", tt.Path, insecureOption)
			runRktAndCheckOutput(t, cmd, expectErr, true)
			// run-prepared
			t.Logf("run-prepared: attempting to write on %q with --insecure-options=%s (expecting error)\n", tt.Path, insecureOption)
			cmd = fmt.Sprintf(`%s --insecure-options=%s prepare --set-env=FILE=%s --set-env=CONTENT=%s %s`,
				ctx.Cmd(), insecureOption, tt.Path, tt.Content, imageFile)
			uuid := runRktAndGetUUID(t, cmd)
			cmd = fmt.Sprintf("%s --debug --insecure-options=%s run-prepared --mds-register=false %s", ctx.Cmd(), insecureOption, uuid)
			runRktAndCheckOutput(t, cmd, expectErr, true)
		}

		// With --insecure-options=paths
		expectOk := "testing-insecure-option"
		for _, insecureOption := range []string{"image,paths", "image,paths,ondisk,capabilities", "image,all-run", "all"} {
			// run
			t.Logf("run: attempting to write on %q with --insecure-options=%s (expecting success)\n", tt.Path, insecureOption)
			cmd := fmt.Sprintf(`%s --debug --insecure-options=%s run --set-env=FILE=%s --set-env=CONTENT=%s %s`,
				ctx.Cmd(), insecureOption, tt.Path, tt.Content, imageFile)
			runRktAndCheckOutput(t, cmd, expectOk, false)
			// run-prepared
			t.Logf("run-prepared: attempting to write on %q with --insecure-options=%s (expecting success)\n", tt.Path, insecureOption)
			cmd = fmt.Sprintf(`%s --insecure-options=%s prepare --set-env=FILE=%s --set-env=CONTENT=%s %s`,
				ctx.Cmd(), insecureOption, tt.Path, tt.Content, imageFile)
			uuid := runRktAndGetUUID(t, cmd)
			cmd = fmt.Sprintf("%s --debug --insecure-options=%s run-prepared --mds-register=false %s", ctx.Cmd(), insecureOption, uuid)
			runRktAndCheckOutput(t, cmd, expectOk, false)
		}
	}
}

// TestPathsStat checks that access to inaccessible paths under
// /proc or /sys is correctly restricted:
// https://github.com/coreos/rkt/issues/2484
func TestPathsStat(t *testing.T) {
	tests := []struct {
		Path         string
		ExpectedMode string
	}{
		{
			Path:         "/sys/firmware",
			ExpectedMode: "d---------",
		},
		{
			Path:         "/proc/kcore",
			ExpectedMode: "----------",
		},
	}

	for _, tt := range tests {
		hiddenImage := patchTestACI("rkt-inspect-stat-procfs.aci", fmt.Sprintf("--exec=/inspect --stat-file --file-name %s", tt.Path))
		defer os.Remove(hiddenImage)

		hiddenCtx := testutils.NewRktRunCtx()
		defer hiddenCtx.Cleanup()

		//run
		hiddenCmd := fmt.Sprintf("%s --debug --insecure-options=image run %s", hiddenCtx.Cmd(), hiddenImage)
		hiddenExpectedLine := fmt.Sprintf("%s: mode: %s", tt.Path, tt.ExpectedMode)
		runRktAndCheckOutput(t, hiddenCmd, hiddenExpectedLine, false)
		// run-prepared
		hiddenCmd = fmt.Sprintf(`%s --insecure-options=image prepare %s`, hiddenCtx.Cmd(), hiddenImage)
		uuid := runRktAndGetUUID(t, hiddenCmd)
		hiddenCmd = fmt.Sprintf("%s --debug run-prepared --mds-register=false %s", hiddenCtx.Cmd(), uuid)
		runRktAndCheckOutput(t, hiddenCmd, hiddenExpectedLine, false)
	}
}
<commit_msg>tests: skip testing for insecure sysrq-trigger in TestPathsWrite<commit_after>// Copyright 2016 The rkt Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// +build coreos src

package main

import (
	"fmt"
	"os"
	"testing"

	"github.com/coreos/rkt/tests/testutils"
)

// TestPathsWrite checks whether access to paths like /proc/sysrq-trigger are
// restricted
func TestPathsWrite(t *testing.T) {
	imageFile := patchTestACI("rkt-inspect-paths.aci",
		"--exec=/inspect --write-file --print-msg=testing-insecure-option")
	defer os.Remove(imageFile)

	ctx := testutils.NewRktRunCtx()
	defer ctx.Cleanup()

	tests := []struct {
		Path    string
		Content string
	}{
		{
			Path:    "/proc/sysrq-trigger",
			Content: "h", // Print help message in dmesg: not dangerous
		},
	}

	for _, tt := range tests {
		// Without --insecure-options=paths
		expectErr := fmt.Sprintf("open %s: read-only file system", tt.Path)
		for _, insecureOption := range []string{"image", "image,ondisk,capabilities", "all-fetch"} {
			// run
			cmd := fmt.Sprintf(`%s --debug --insecure-options=%s run --set-env=FILE=%s --set-env=CONTENT=%s %s`,
				ctx.Cmd(), insecureOption, tt.Path, tt.Content, imageFile)
			t.Logf("run: attempting to write on %q with --insecure-options=%s (expecting error)\n", tt.Path, insecureOption)
			runRktAndCheckOutput(t, cmd, expectErr, true)
			// run-prepared
			t.Logf("run-prepared: attempting to write on %q with --insecure-options=%s (expecting error)\n", tt.Path, insecureOption)
			cmd = fmt.Sprintf(`%s --insecure-options=%s prepare --set-env=FILE=%s --set-env=CONTENT=%s %s`,
				ctx.Cmd(), insecureOption, tt.Path, tt.Content, imageFile)
			uuid := runRktAndGetUUID(t, cmd)
			cmd = fmt.Sprintf("%s --debug --insecure-options=%s run-prepared --mds-register=false %s", ctx.Cmd(), insecureOption, uuid)
			runRktAndCheckOutput(t, cmd, expectErr, true)
		}

		/* TODO(lucab): re-enable once stage1 has https://github.com/systemd/systemd/pull/4395
		 *
		 * // With --insecure-options=paths
		 * expectOk := "testing-insecure-option"
		 * for _, insecureOption := range []string{"image,paths", "image,paths,ondisk,capabilities", "image,all-run", "all"} {
		 * 	// run
		 * 	t.Logf("run: attempting to write on %q with --insecure-options=%s (expecting success)\n", tt.Path, insecureOption)
		 * 	cmd := fmt.Sprintf(`%s --debug --insecure-options=%s run --set-env=FILE=%s --set-env=CONTENT=%s %s`,
		 * 		ctx.Cmd(), insecureOption, tt.Path, tt.Content, imageFile)
		 * 	runRktAndCheckOutput(t, cmd, expectOk, false)
		 * 	// run-prepared
		 * 	t.Logf("run-prepared: attempting to write on %q with --insecure-options=%s (expecting success)\n", tt.Path, insecureOption)
		 * 	cmd = fmt.Sprintf(`%s --insecure-options=%s prepare --set-env=FILE=%s --set-env=CONTENT=%s %s`,
		 * 		ctx.Cmd(), insecureOption, tt.Path, tt.Content, imageFile)
		 * 	uuid := runRktAndGetUUID(t, cmd)
		 * 	cmd = fmt.Sprintf("%s --debug --insecure-options=%s run-prepared --mds-register=false %s", ctx.Cmd(), insecureOption, uuid)
		 * 	runRktAndCheckOutput(t, cmd, expectOk, false)
		 * }
		 */
	}
}

// TestPathsStat checks that access to inaccessible paths under
// /proc or /sys is correctly restricted:
// https://github.com/coreos/rkt/issues/2484
func TestPathsStat(t *testing.T) {
	tests := []struct {
		Path         string
		ExpectedMode string
	}{
		{
			Path:         "/sys/firmware",
			ExpectedMode: "d---------",
		},
		{
			Path:         "/proc/kcore",
			ExpectedMode: "----------",
		},
	}

	for _, tt := range tests {
		hiddenImage := patchTestACI("rkt-inspect-stat-procfs.aci", fmt.Sprintf("--exec=/inspect --stat-file --file-name %s", tt.Path))
		defer os.Remove(hiddenImage)

		hiddenCtx := testutils.NewRktRunCtx()
		defer hiddenCtx.Cleanup()

		//run
		hiddenCmd := fmt.Sprintf("%s --debug --insecure-options=image run %s", hiddenCtx.Cmd(), hiddenImage)
		hiddenExpectedLine := fmt.Sprintf("%s: mode: %s", tt.Path, tt.ExpectedMode)
		runRktAndCheckOutput(t, hiddenCmd, hiddenExpectedLine, false)
		// run-prepared
		hiddenCmd = fmt.Sprintf(`%s --insecure-options=image prepare %s`, hiddenCtx.Cmd(), hiddenImage)
		uuid := runRktAndGetUUID(t, hiddenCmd)
		hiddenCmd = fmt.Sprintf("%s --debug run-prepared --mds-register=false %s", hiddenCtx.Cmd(), uuid)
		runRktAndCheckOutput(t, hiddenCmd, hiddenExpectedLine, false)
	}
}
<|endoftext|>


<commit_before>package aws

import (
	"fmt"
	"log"
	"strings"

	"github.com/hashicorp/terraform/helper/multierror"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/service/autoscaling"
	"github.com/aws/aws-sdk-go/service/cloudwatch"
	"github.com/aws/aws-sdk-go/service/dynamodb"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/aws/aws-sdk-go/service/ecs"
	"github.com/aws/aws-sdk-go/service/elasticache"
	"github.com/aws/aws-sdk-go/service/elb"
	"github.com/aws/aws-sdk-go/service/iam"
	"github.com/aws/aws-sdk-go/service/kinesis"
	"github.com/aws/aws-sdk-go/service/lambda"
	"github.com/aws/aws-sdk-go/service/rds"
	"github.com/aws/aws-sdk-go/service/route53"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/service/sns"
	"github.com/aws/aws-sdk-go/service/sqs"
)

type Config struct {
	AccessKey  string
	SecretKey  string
	Token      string
	Region     string
	MaxRetries int

	AllowedAccountIds   []interface{}
	ForbiddenAccountIds []interface{}

	DynamoDBEndpoint string
}

type AWSClient struct {
	cloudwatchconn  *cloudwatch.CloudWatch
	dynamodbconn    *dynamodb.DynamoDB
	ec2conn         *ec2.EC2
	ecsconn         *ecs.ECS
	elbconn         *elb.ELB
	autoscalingconn *autoscaling.AutoScaling
	s3conn          *s3.S3
	sqsconn         *sqs.SQS
	snsconn         *sns.SNS
	r53conn         *route53.Route53
	region          string
	rdsconn         *rds.RDS
	iamconn         *iam.IAM
	kinesisconn     *kinesis.Kinesis
	elasticacheconn *elasticache.ElastiCache
	lambdaconn      *lambda.Lambda
}

// Client configures and returns a fully initialized AWSClient
func (c *Config) Client() (interface{}, error) {
	var client AWSClient

	// Get the auth and region. This can fail if keys/regions were not
	// specified and we're attempting to use the environment.
	var errs []error

	log.Println("[INFO] Building AWS region structure")
	err := c.ValidateRegion()
	if err != nil {
		errs = append(errs, err)
	}

	if len(errs) == 0 {
		// store AWS region in client struct, for region specific operations such as
		// bucket storage in S3
		client.region = c.Region

		log.Println("[INFO] Building AWS auth structure")
		// We fetched all credential sources in Provider. If they are
		// available, they'll already be in c. See Provider definition.
		creds := credentials.NewStaticCredentials(c.AccessKey, c.SecretKey, c.Token)
		awsConfig := &aws.Config{
			Credentials: creds,
			Region:      aws.String(c.Region),
			MaxRetries:  aws.Int(c.MaxRetries),
		}

		log.Println("[INFO] Initializing IAM Connection")
		client.iamconn = iam.New(awsConfig)

		err := c.ValidateCredentials(client.iamconn)
		if err != nil {
			errs = append(errs, err)
		}

		awsDynamoDBConfig := &aws.Config{
			Credentials: creds,
			Region:      aws.String(c.Region),
			MaxRetries:  aws.Int(c.MaxRetries),
			Endpoint:    aws.String(c.DynamoDBEndpoint),
		}

		log.Println("[INFO] Initializing DynamoDB connection")
		client.dynamodbconn = dynamodb.New(awsDynamoDBConfig)

		log.Println("[INFO] Initializing ELB connection")
		client.elbconn = elb.New(awsConfig)

		log.Println("[INFO] Initializing S3 connection")
		client.s3conn = s3.New(awsConfig)

		log.Println("[INFO] Initializing SQS connection")
		client.sqsconn = sqs.New(awsConfig)

		log.Println("[INFO] Initializing SNS connection")
		client.snsconn = sns.New(awsConfig)

		log.Println("[INFO] Initializing RDS Connection")
		client.rdsconn = rds.New(awsConfig)

		log.Println("[INFO] Initializing Kinesis Connection")
		client.kinesisconn = kinesis.New(awsConfig)

		authErr := c.ValidateAccountId(client.iamconn)
		if authErr != nil {
			errs = append(errs, authErr)
		}

		log.Println("[INFO] Initializing AutoScaling connection")
		client.autoscalingconn = autoscaling.New(awsConfig)

		log.Println("[INFO] Initializing EC2 Connection")
		client.ec2conn = ec2.New(awsConfig)

		log.Println("[INFO] Initializing ECS Connection")
		client.ecsconn = ecs.New(awsConfig)

		// aws-sdk-go uses v4 for signing requests, which requires all global
		// endpoints to use 'us-east-1'.
		// See http://docs.aws.amazon.com/general/latest/gr/sigv4_changes.html
		log.Println("[INFO] Initializing Route 53 connection")
		client.r53conn = route53.New(&aws.Config{
			Credentials: creds,
			Region:      aws.String("us-east-1"),
			MaxRetries:  aws.Int(c.MaxRetries),
		})

		log.Println("[INFO] Initializing Elasticache Connection")
		client.elasticacheconn = elasticache.New(awsConfig)

		log.Println("[INFO] Initializing Lambda Connection")
		client.lambdaconn = lambda.New(awsConfig)

		log.Println("[INFO] Initializing CloudWatch SDK connection")
		client.cloudwatchconn = cloudwatch.New(awsConfig)
	}

	if len(errs) > 0 {
		return nil, &multierror.Error{Errors: errs}
	}

	return &client, nil
}

// ValidateRegion returns an error if the configured region is not a
// valid aws region and nil otherwise.
func (c *Config) ValidateRegion() error {
	var regions = [11]string{"us-east-1", "us-west-2", "us-west-1", "eu-west-1",
		"eu-central-1", "ap-southeast-1", "ap-southeast-2", "ap-northeast-1",
		"sa-east-1", "cn-north-1", "us-gov-west-1"}

	for _, valid := range regions {
		if c.Region == valid {
			return nil
		}
	}
	return fmt.Errorf("Not a valid region: %s", c.Region)
}

// Validate credentials early and fail before we do any graph walking
func (c *Config) ValidateCredentials(iamconn *iam.IAM) error {
	_, err := iamconn.GetUser(nil)

	if awsErr, ok := err.(awserr.Error); ok {
		if awsErr.Code() == "SignatureDoesNotMatch" {
			return fmt.Errorf("Failed authenticating with AWS: please verify credentials")
		}
	}

	return err
}

// ValidateAccountId returns a context-specific error if the configured account
// id is explicitly forbidden or not authorised; and nil if it is authorised.
func (c *Config) ValidateAccountId(iamconn *iam.IAM) error {
	if c.AllowedAccountIds == nil && c.ForbiddenAccountIds == nil {
		return nil
	}

	log.Printf("[INFO] Validating account ID")

	out, err := iamconn.GetUser(nil)
	if err != nil {
		return fmt.Errorf("Failed getting account ID from IAM: %s", err)
	}

	account_id := strings.Split(*out.User.ARN, ":")[4]

	if c.ForbiddenAccountIds != nil {
		for _, id := range c.ForbiddenAccountIds {
			if id == account_id {
				return fmt.Errorf("Forbidden account ID (%s)", id)
			}
		}
	}

	if c.AllowedAccountIds != nil {
		for _, id := range c.AllowedAccountIds {
			if id == account_id {
				return nil
			}
		}
		return fmt.Errorf("Account ID not allowed (%s)", account_id)
	}

	return nil
}
<commit_msg>provider/aws: Fail silently in ValidateCredentials for IAM users<commit_after>package aws

import (
	"fmt"
	"log"
	"strings"

	"github.com/hashicorp/terraform/helper/multierror"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/awserr"
	"github.com/aws/aws-sdk-go/aws/credentials"
	"github.com/aws/aws-sdk-go/service/autoscaling"
	"github.com/aws/aws-sdk-go/service/cloudwatch"
	"github.com/aws/aws-sdk-go/service/dynamodb"
	"github.com/aws/aws-sdk-go/service/ec2"
	"github.com/aws/aws-sdk-go/service/ecs"
	"github.com/aws/aws-sdk-go/service/elasticache"
	"github.com/aws/aws-sdk-go/service/elb"
	"github.com/aws/aws-sdk-go/service/iam"
	"github.com/aws/aws-sdk-go/service/kinesis"
	"github.com/aws/aws-sdk-go/service/lambda"
	"github.com/aws/aws-sdk-go/service/rds"
	"github.com/aws/aws-sdk-go/service/route53"
	"github.com/aws/aws-sdk-go/service/s3"
	"github.com/aws/aws-sdk-go/service/sns"
	"github.com/aws/aws-sdk-go/service/sqs"
)

type Config struct {
	AccessKey  string
	SecretKey  string
	Token      string
	Region     string
	MaxRetries int

	AllowedAccountIds   []interface{}
	ForbiddenAccountIds []interface{}

	DynamoDBEndpoint string
}

type AWSClient struct {
	cloudwatchconn  *cloudwatch.CloudWatch
	dynamodbconn    *dynamodb.DynamoDB
	ec2conn         *ec2.EC2
	ecsconn         *ecs.ECS
	elbconn         *elb.ELB
	autoscalingconn *autoscaling.AutoScaling
	s3conn          *s3.S3
	sqsconn         *sqs.SQS
	snsconn         *sns.SNS
	r53conn         *route53.Route53
	region          string
	rdsconn         *rds.RDS
	iamconn         *iam.IAM
	kinesisconn     *kinesis.Kinesis
	elasticacheconn *elasticache.ElastiCache
	lambdaconn      *lambda.Lambda
}

// Client configures and returns a fully initialized AWSClient
func (c *Config) Client() (interface{}, error) {
	var client AWSClient

	// Get the auth and region. This can fail if keys/regions were not
	// specified and we're attempting to use the environment.
	var errs []error

	log.Println("[INFO] Building AWS region structure")
	err := c.ValidateRegion()
	if err != nil {
		errs = append(errs, err)
	}

	if len(errs) == 0 {
		// store AWS region in client struct, for region specific operations such as
		// bucket storage in S3
		client.region = c.Region

		log.Println("[INFO] Building AWS auth structure")
		// We fetched all credential sources in Provider. If they are
		// available, they'll already be in c. See Provider definition.
		creds := credentials.NewStaticCredentials(c.AccessKey, c.SecretKey, c.Token)
		awsConfig := &aws.Config{
			Credentials: creds,
			Region:      aws.String(c.Region),
			MaxRetries:  aws.Int(c.MaxRetries),
		}

		log.Println("[INFO] Initializing IAM Connection")
		client.iamconn = iam.New(awsConfig)

		err := c.ValidateCredentials(client.iamconn)
		if err != nil {
			errs = append(errs, err)
		}

		awsDynamoDBConfig := &aws.Config{
			Credentials: creds,
			Region:      aws.String(c.Region),
			MaxRetries:  aws.Int(c.MaxRetries),
			Endpoint:    aws.String(c.DynamoDBEndpoint),
		}

		log.Println("[INFO] Initializing DynamoDB connection")
		client.dynamodbconn = dynamodb.New(awsDynamoDBConfig)

		log.Println("[INFO] Initializing ELB connection")
		client.elbconn = elb.New(awsConfig)

		log.Println("[INFO] Initializing S3 connection")
		client.s3conn = s3.New(awsConfig)

		log.Println("[INFO] Initializing SQS connection")
		client.sqsconn = sqs.New(awsConfig)

		log.Println("[INFO] Initializing SNS connection")
		client.snsconn = sns.New(awsConfig)

		log.Println("[INFO] Initializing RDS Connection")
		client.rdsconn = rds.New(awsConfig)

		log.Println("[INFO] Initializing Kinesis Connection")
		client.kinesisconn = kinesis.New(awsConfig)

		authErr := c.ValidateAccountId(client.iamconn)
		if authErr != nil {
			errs = append(errs, authErr)
		}

		log.Println("[INFO] Initializing AutoScaling connection")
		client.autoscalingconn = autoscaling.New(awsConfig)

		log.Println("[INFO] Initializing EC2 Connection")
		client.ec2conn = ec2.New(awsConfig)

		log.Println("[INFO] Initializing ECS Connection")
		client.ecsconn = ecs.New(awsConfig)

		// aws-sdk-go uses v4 for signing requests, which requires all global
		// endpoints to use 'us-east-1'.
		// See http://docs.aws.amazon.com/general/latest/gr/sigv4_changes.html
		log.Println("[INFO] Initializing Route 53 connection")
		client.r53conn = route53.New(&aws.Config{
			Credentials: creds,
			Region:      aws.String("us-east-1"),
			MaxRetries:  aws.Int(c.MaxRetries),
		})

		log.Println("[INFO] Initializing Elasticache Connection")
		client.elasticacheconn = elasticache.New(awsConfig)

		log.Println("[INFO] Initializing Lambda Connection")
		client.lambdaconn = lambda.New(awsConfig)

		log.Println("[INFO] Initializing CloudWatch SDK connection")
		client.cloudwatchconn = cloudwatch.New(awsConfig)
	}

	if len(errs) > 0 {
		return nil, &multierror.Error{Errors: errs}
	}

	return &client, nil
}

// ValidateRegion returns an error if the configured region is not a
// valid aws region and nil otherwise.
func (c *Config) ValidateRegion() error {
	var regions = [11]string{"us-east-1", "us-west-2", "us-west-1", "eu-west-1",
		"eu-central-1", "ap-southeast-1", "ap-southeast-2", "ap-northeast-1",
		"sa-east-1", "cn-north-1", "us-gov-west-1"}

	for _, valid := range regions {
		if c.Region == valid {
			return nil
		}
	}
	return fmt.Errorf("Not a valid region: %s", c.Region)
}

// Validate credentials early and fail before we do any graph walking.
// In the case of an IAM role/profile with insuffecient privileges, fail
// silently
func (c *Config) ValidateCredentials(iamconn *iam.IAM) error {
	_, err := iamconn.GetUser(nil)

	if awsErr, ok := err.(awserr.Error); ok {

		if awsErr.Code() == "AccessDenied" {
			log.Printf("[WARN] AccessDenied Error with iam.GetUser, assuming IAM profile")
			// User may be an IAM instance profile, or otherwise IAM role without the
			// GetUser permissions, so fail silently
			return nil
		}

		if awsErr.Code() == "SignatureDoesNotMatch" {
			return fmt.Errorf("Failed authenticating with AWS: please verify credentials")
		}
	}

	return err
}

// ValidateAccountId returns a context-specific error if the configured account
// id is explicitly forbidden or not authorised; and nil if it is authorised.
func (c *Config) ValidateAccountId(iamconn *iam.IAM) error {
	if c.AllowedAccountIds == nil && c.ForbiddenAccountIds == nil {
		return nil
	}

	log.Printf("[INFO] Validating account ID")

	out, err := iamconn.GetUser(nil)
	if err != nil {
		return fmt.Errorf("Failed getting account ID from IAM: %s", err)
	}

	account_id := strings.Split(*out.User.ARN, ":")[4]

	if c.ForbiddenAccountIds != nil {
		for _, id := range c.ForbiddenAccountIds {
			if id == account_id {
				return fmt.Errorf("Forbidden account ID (%s)", id)
			}
		}
	}

	if c.AllowedAccountIds != nil {
		for _, id := range c.AllowedAccountIds {
			if id == account_id {
				return nil
			}
		}
		return fmt.Errorf("Account ID not allowed (%s)", account_id)
	}

	return nil
}
<|endoftext|>


<commit_before>/*
Copyright 2014 The Kubernetes Authors All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// TODO: move everything in this file to pkg/api/rest
package meta

import (
	"fmt"
	"strings"

	"k8s.io/kubernetes/pkg/api/unversioned"
)

// Implements RESTScope interface
type restScope struct {
	name             RESTScopeName
	paramName        string
	argumentName     string
	paramDescription string
}

func (r *restScope) Name() RESTScopeName {
	return r.name
}
func (r *restScope) ParamName() string {
	return r.paramName
}
func (r *restScope) ArgumentName() string {
	return r.argumentName
}
func (r *restScope) ParamDescription() string {
	return r.paramDescription
}

var RESTScopeNamespace = &restScope{
	name:             RESTScopeNameNamespace,
	paramName:        "namespaces",
	argumentName:     "namespace",
	paramDescription: "object name and auth scope, such as for teams and projects",
}

var RESTScopeRoot = &restScope{
	name: RESTScopeNameRoot,
}

// DefaultRESTMapper exposes mappings between the types defined in a
// runtime.Scheme. It assumes that all types defined the provided scheme
// can be mapped with the provided MetadataAccessor and Codec interfaces.
//
// The resource name of a Kind is defined as the lowercase,
// English-plural version of the Kind string.
// When converting from resource to Kind, the singular version of the
// resource name is also accepted for convenience.
//
// TODO: Only accept plural for some operations for increased control?
// (`get pod bar` vs `get pods bar`)
// TODO these maps should be keyed based on GroupVersionKinds
type DefaultRESTMapper struct {
	defaultGroupVersions []unversioned.GroupVersion

	resourceToKind       map[string]unversioned.GroupVersionKind
	kindToPluralResource map[unversioned.GroupVersionKind]string
	kindToScope          map[unversioned.GroupVersionKind]RESTScope
	singularToPlural     map[string]string
	pluralToSingular     map[string]string

	interfacesFunc VersionInterfacesFunc
}

// VersionInterfacesFunc returns the appropriate codec, typer, and metadata accessor for a
// given api version, or an error if no such api version exists.
type VersionInterfacesFunc func(apiVersion string) (*VersionInterfaces, error)

// NewDefaultRESTMapper initializes a mapping between Kind and APIVersion
// to a resource name and back based on the objects in a runtime.Scheme
// and the Kubernetes API conventions. Takes a group name, a priority list of the versions
// to search when an object has no default version (set empty to return an error),
// and a function that retrieves the correct codec and metadata for a given version.
func NewDefaultRESTMapper(defaultGroupVersions []unversioned.GroupVersion, f VersionInterfacesFunc) *DefaultRESTMapper {
	resourceToKind := make(map[string]unversioned.GroupVersionKind)
	kindToPluralResource := make(map[unversioned.GroupVersionKind]string)
	kindToScope := make(map[unversioned.GroupVersionKind]RESTScope)
	singularToPlural := make(map[string]string)
	pluralToSingular := make(map[string]string)
	// TODO: verify name mappings work correctly when versions differ

	return &DefaultRESTMapper{
		resourceToKind:       resourceToKind,
		kindToPluralResource: kindToPluralResource,
		kindToScope:          kindToScope,
		defaultGroupVersions: defaultGroupVersions,
		singularToPlural:     singularToPlural,
		pluralToSingular:     pluralToSingular,
		interfacesFunc:       f,
	}
}

func (m *DefaultRESTMapper) Add(gvk unversioned.GroupVersionKind, scope RESTScope, mixedCase bool) {
	plural, singular := KindToResource(gvk.Kind, mixedCase)
	m.singularToPlural[singular] = plural
	m.pluralToSingular[plural] = singular
	_, ok1 := m.resourceToKind[plural]
	_, ok2 := m.resourceToKind[strings.ToLower(plural)]
	if !ok1 && !ok2 {
		m.resourceToKind[plural] = gvk
		m.resourceToKind[singular] = gvk
		if strings.ToLower(plural) != plural {
			m.resourceToKind[strings.ToLower(plural)] = gvk
			m.resourceToKind[strings.ToLower(singular)] = gvk
		}
	}
	m.kindToPluralResource[gvk] = plural
	m.kindToScope[gvk] = scope
}

// KindToResource converts Kind to a resource name.
func KindToResource(kind string, mixedCase bool) (plural, singular string) {
	if len(kind) == 0 {
		return
	}
	if mixedCase {
		// Legacy support for mixed case names
		singular = strings.ToLower(kind[:1]) + kind[1:]
	} else {
		singular = strings.ToLower(kind)
	}
	if strings.HasSuffix(singular, "endpoints") {
		plural = singular
	} else {
		switch string(singular[len(singular)-1]) {
		case "s":
			plural = singular + "es"
		case "y":
			plural = strings.TrimSuffix(singular, "y") + "ies"
		default:
			plural = singular + "s"
		}
	}
	return
}

// ResourceSingularizer implements RESTMapper
// It converts a resource name from plural to singular (e.g., from pods to pod)
func (m *DefaultRESTMapper) ResourceSingularizer(resource string) (singular string, err error) {
	singular, ok := m.pluralToSingular[resource]
	if !ok {
		return resource, fmt.Errorf("no singular of resource %q has been defined", resource)
	}
	return singular, nil
}

// VersionAndKindForResource implements RESTMapper
func (m *DefaultRESTMapper) VersionAndKindForResource(resource string) (gvString, kind string, err error) {
	gvk, ok := m.resourceToKind[strings.ToLower(resource)]
	if !ok {
		return "", "", fmt.Errorf("in version and kind for resource, no resource %q has been defined", resource)
	}
	return gvk.GroupVersion().String(), gvk.Kind, nil
}

func (m *DefaultRESTMapper) GroupForResource(resource string) (string, error) {
	gvk, exists := m.resourceToKind[strings.ToLower(resource)]
	if !exists {
		return "", fmt.Errorf("in group for resource, no resource %q has been defined", resource)
	}

	return gvk.Group, nil
}

// RESTMapping returns a struct representing the resource path and conversion interfaces a
// RESTClient should use to operate on the provided kind in order of versions. If a version search
// order is not provided, the search order provided to DefaultRESTMapper will be used to resolve which
// APIVersion should be used to access the named kind.
// TODO version here in this RESTMapper means just APIVersion, but the RESTMapper API is intended to handle multiple groups
// So this API is broken.  The RESTMapper test made it clear that versions here were API versions, but the code tries to use
// them with group/version tuples.
// TODO this should probably become RESTMapping(GroupKind, versions ...string)
func (m *DefaultRESTMapper) RESTMapping(kind string, versions ...string) (*RESTMapping, error) {
	// Pick an appropriate version
	var groupVersion *unversioned.GroupVersion
	hadVersion := false
	for _, v := range versions {
		if len(v) == 0 {
			continue
		}
		currGroupVersion, err := unversioned.ParseGroupVersion(v)
		if err != nil {
			return nil, err
		}

		currGVK := currGroupVersion.WithKind(kind)
		hadVersion = true
		if _, ok := m.kindToPluralResource[currGVK]; ok {
			groupVersion = &currGroupVersion
			break
		}
	}
	// Use the default preferred versions
	if !hadVersion && (groupVersion == nil) {
		for _, currGroupVersion := range m.defaultGroupVersions {
			currGVK := currGroupVersion.WithKind(kind)
			if _, ok := m.kindToPluralResource[currGVK]; ok {
				groupVersion = &currGroupVersion
				break
			}
		}
	}
	if groupVersion == nil {
		return nil, fmt.Errorf("no kind named %q is registered in versions %q", kind, versions)
	}

	gvk := groupVersion.WithKind(kind)

	// Ensure we have a REST mapping
	resource, ok := m.kindToPluralResource[gvk]
	if !ok {
		found := []unversioned.GroupVersion{}
		for _, gv := range m.defaultGroupVersions {
			if _, ok := m.kindToPluralResource[gvk]; ok {
				found = append(found, gv)
			}
		}
		if len(found) > 0 {
			return nil, fmt.Errorf("object with kind %q exists in versions %v, not %v", kind, found, *groupVersion)
		}
		return nil, fmt.Errorf("the provided version %q and kind %q cannot be mapped to a supported object", groupVersion, kind)
	}

	// Ensure we have a REST scope
	scope, ok := m.kindToScope[gvk]
	if !ok {
		return nil, fmt.Errorf("the provided version %q and kind %q cannot be mapped to a supported scope", gvk.GroupVersion().String(), gvk.Kind)
	}

	interfaces, err := m.interfacesFunc(gvk.GroupVersion().String())
	if err != nil {
		return nil, fmt.Errorf("the provided version %q has no relevant versions", gvk.GroupVersion().String())
	}

	retVal := &RESTMapping{
		Resource:         resource,
		GroupVersionKind: gvk,
		Scope:            scope,

		Codec:            interfaces.Codec,
		ObjectConvertor:  interfaces.ObjectConvertor,
		MetadataAccessor: interfaces.MetadataAccessor,
	}

	return retVal, nil
}

// aliasToResource is used for mapping aliases to resources
var aliasToResource = map[string][]string{}

// AddResourceAlias maps aliases to resources
func (m *DefaultRESTMapper) AddResourceAlias(alias string, resources ...string) {
	if len(resources) == 0 {
		return
	}
	aliasToResource[alias] = resources
}

// AliasesForResource returns whether a resource has an alias or not
func (m *DefaultRESTMapper) AliasesForResource(alias string) ([]string, bool) {
	if res, ok := aliasToResource[alias]; ok {
		return res, true
	}
	return nil, false
}

// ResourceIsValid takes a string (kind) and checks if it's a valid resource
func (m *DefaultRESTMapper) ResourceIsValid(resource string) bool {
	_, _, err := m.VersionAndKindForResource(resource)
	return err == nil
}

// MultiRESTMapper is a wrapper for multiple RESTMappers.
type MultiRESTMapper []RESTMapper

// ResourceSingularizer converts a REST resource name from plural to singular (e.g., from pods to pod)
// This implementation supports multiple REST schemas and return the first match.
func (m MultiRESTMapper) ResourceSingularizer(resource string) (singular string, err error) {
	for _, t := range m {
		singular, err = t.ResourceSingularizer(resource)
		if err == nil {
			return
		}
	}
	return
}

// VersionAndKindForResource provides the Version and Kind  mappings for the
// REST resources. This implementation supports multiple REST schemas and return
// the first match.
func (m MultiRESTMapper) VersionAndKindForResource(resource string) (defaultVersion, kind string, err error) {
	for _, t := range m {
		defaultVersion, kind, err = t.VersionAndKindForResource(resource)
		if err == nil {
			return
		}
	}
	return
}

// GroupForResource provides the Group mappings for the REST resources. This
// implementation supports multiple REST schemas and returns the first match.
func (m MultiRESTMapper) GroupForResource(resource string) (group string, err error) {
	for _, t := range m {
		group, err = t.GroupForResource(resource)
		if err == nil {
			return
		}
	}
	return
}

// RESTMapping provides the REST mapping for the resource based on the resource
// kind and version. This implementation supports multiple REST schemas and
// return the first match.
func (m MultiRESTMapper) RESTMapping(kind string, versions ...string) (mapping *RESTMapping, err error) {
	for _, t := range m {
		mapping, err = t.RESTMapping(kind, versions...)
		if err == nil {
			return
		}
	}
	return
}

// AliasesForResource finds the first alias response for the provided mappers.
func (m MultiRESTMapper) AliasesForResource(alias string) (aliases []string, ok bool) {
	for _, t := range m {
		if aliases, ok = t.AliasesForResource(alias); ok {
			return
		}
	}
	return nil, false
}

// ResourceIsValid takes a string (either group/kind or kind) and checks if it's a valid resource
func (m MultiRESTMapper) ResourceIsValid(resource string) bool {
	for _, t := range m {
		if t.ResourceIsValid(resource) {
			return true
		}
	}
	return false
}
<commit_msg>fix outputversion restmapper<commit_after>/*
Copyright 2014 The Kubernetes Authors All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

// TODO: move everything in this file to pkg/api/rest
package meta

import (
	"fmt"
	"strings"

	"k8s.io/kubernetes/pkg/api/unversioned"
)

// Implements RESTScope interface
type restScope struct {
	name             RESTScopeName
	paramName        string
	argumentName     string
	paramDescription string
}

func (r *restScope) Name() RESTScopeName {
	return r.name
}
func (r *restScope) ParamName() string {
	return r.paramName
}
func (r *restScope) ArgumentName() string {
	return r.argumentName
}
func (r *restScope) ParamDescription() string {
	return r.paramDescription
}

var RESTScopeNamespace = &restScope{
	name:             RESTScopeNameNamespace,
	paramName:        "namespaces",
	argumentName:     "namespace",
	paramDescription: "object name and auth scope, such as for teams and projects",
}

var RESTScopeRoot = &restScope{
	name: RESTScopeNameRoot,
}

// DefaultRESTMapper exposes mappings between the types defined in a
// runtime.Scheme. It assumes that all types defined the provided scheme
// can be mapped with the provided MetadataAccessor and Codec interfaces.
//
// The resource name of a Kind is defined as the lowercase,
// English-plural version of the Kind string.
// When converting from resource to Kind, the singular version of the
// resource name is also accepted for convenience.
//
// TODO: Only accept plural for some operations for increased control?
// (`get pod bar` vs `get pods bar`)
// TODO these maps should be keyed based on GroupVersionKinds
type DefaultRESTMapper struct {
	defaultGroupVersions []unversioned.GroupVersion

	resourceToKind       map[string]unversioned.GroupVersionKind
	kindToPluralResource map[unversioned.GroupVersionKind]string
	kindToScope          map[unversioned.GroupVersionKind]RESTScope
	singularToPlural     map[string]string
	pluralToSingular     map[string]string

	interfacesFunc VersionInterfacesFunc
}

// VersionInterfacesFunc returns the appropriate codec, typer, and metadata accessor for a
// given api version, or an error if no such api version exists.
type VersionInterfacesFunc func(apiVersion string) (*VersionInterfaces, error)

// NewDefaultRESTMapper initializes a mapping between Kind and APIVersion
// to a resource name and back based on the objects in a runtime.Scheme
// and the Kubernetes API conventions. Takes a group name, a priority list of the versions
// to search when an object has no default version (set empty to return an error),
// and a function that retrieves the correct codec and metadata for a given version.
func NewDefaultRESTMapper(defaultGroupVersions []unversioned.GroupVersion, f VersionInterfacesFunc) *DefaultRESTMapper {
	resourceToKind := make(map[string]unversioned.GroupVersionKind)
	kindToPluralResource := make(map[unversioned.GroupVersionKind]string)
	kindToScope := make(map[unversioned.GroupVersionKind]RESTScope)
	singularToPlural := make(map[string]string)
	pluralToSingular := make(map[string]string)
	// TODO: verify name mappings work correctly when versions differ

	return &DefaultRESTMapper{
		resourceToKind:       resourceToKind,
		kindToPluralResource: kindToPluralResource,
		kindToScope:          kindToScope,
		defaultGroupVersions: defaultGroupVersions,
		singularToPlural:     singularToPlural,
		pluralToSingular:     pluralToSingular,
		interfacesFunc:       f,
	}
}

func (m *DefaultRESTMapper) Add(gvk unversioned.GroupVersionKind, scope RESTScope, mixedCase bool) {
	plural, singular := KindToResource(gvk.Kind, mixedCase)
	m.singularToPlural[singular] = plural
	m.pluralToSingular[plural] = singular
	_, ok1 := m.resourceToKind[plural]
	_, ok2 := m.resourceToKind[strings.ToLower(plural)]
	if !ok1 && !ok2 {
		m.resourceToKind[plural] = gvk
		m.resourceToKind[singular] = gvk
		if strings.ToLower(plural) != plural {
			m.resourceToKind[strings.ToLower(plural)] = gvk
			m.resourceToKind[strings.ToLower(singular)] = gvk
		}
	}
	m.kindToPluralResource[gvk] = plural
	m.kindToScope[gvk] = scope
}

// KindToResource converts Kind to a resource name.
func KindToResource(kind string, mixedCase bool) (plural, singular string) {
	if len(kind) == 0 {
		return
	}
	if mixedCase {
		// Legacy support for mixed case names
		singular = strings.ToLower(kind[:1]) + kind[1:]
	} else {
		singular = strings.ToLower(kind)
	}
	if strings.HasSuffix(singular, "endpoints") {
		plural = singular
	} else {
		switch string(singular[len(singular)-1]) {
		case "s":
			plural = singular + "es"
		case "y":
			plural = strings.TrimSuffix(singular, "y") + "ies"
		default:
			plural = singular + "s"
		}
	}
	return
}

// ResourceSingularizer implements RESTMapper
// It converts a resource name from plural to singular (e.g., from pods to pod)
func (m *DefaultRESTMapper) ResourceSingularizer(resource string) (singular string, err error) {
	singular, ok := m.pluralToSingular[resource]
	if !ok {
		return resource, fmt.Errorf("no singular of resource %q has been defined", resource)
	}
	return singular, nil
}

// VersionAndKindForResource implements RESTMapper
func (m *DefaultRESTMapper) VersionAndKindForResource(resource string) (gvString, kind string, err error) {
	gvk, ok := m.resourceToKind[strings.ToLower(resource)]
	if !ok {
		return "", "", fmt.Errorf("in version and kind for resource, no resource %q has been defined", resource)
	}
	return gvk.GroupVersion().String(), gvk.Kind, nil
}

func (m *DefaultRESTMapper) GroupForResource(resource string) (string, error) {
	gvk, exists := m.resourceToKind[strings.ToLower(resource)]
	if !exists {
		return "", fmt.Errorf("in group for resource, no resource %q has been defined", resource)
	}

	return gvk.Group, nil
}

// RESTMapping returns a struct representing the resource path and conversion interfaces a
// RESTClient should use to operate on the provided kind in order of versions. If a version search
// order is not provided, the search order provided to DefaultRESTMapper will be used to resolve which
// APIVersion should be used to access the named kind.
// TODO version here in this RESTMapper means just APIVersion, but the RESTMapper API is intended to handle multiple groups
// So this API is broken.  The RESTMapper test made it clear that versions here were API versions, but the code tries to use
// them with group/version tuples.
// TODO this should probably become RESTMapping(GroupKind, versions ...string)
func (m *DefaultRESTMapper) RESTMapping(kind string, versions ...string) (*RESTMapping, error) {
	// TODO, this looks really strange, but once this API is update, the version detection becomes clean again
	// because you won't be able to request cross-group kinds
	hadVersion := false
	for _, gvString := range versions {
		currGroupVersion, err := unversioned.ParseGroupVersion(gvString)
		if err != nil {
			return nil, err
		}
		if len(currGroupVersion.Version) != 0 {
			hadVersion = true
		}
	}

	// Pick an appropriate version
	var groupVersion *unversioned.GroupVersion
	for _, v := range versions {
		if len(v) == 0 {
			continue
		}
		currGroupVersion, err := unversioned.ParseGroupVersion(v)
		if err != nil {
			return nil, err
		}

		currGVK := currGroupVersion.WithKind(kind)
		if _, ok := m.kindToPluralResource[currGVK]; ok {
			groupVersion = &currGroupVersion
			break
		}
	}
	// Use the default preferred versions
	if !hadVersion && (groupVersion == nil) {
		for _, currGroupVersion := range m.defaultGroupVersions {
			currGVK := currGroupVersion.WithKind(kind)
			if _, ok := m.kindToPluralResource[currGVK]; ok {
				groupVersion = &currGroupVersion
				break
			}
		}
	}
	if groupVersion == nil {
		return nil, fmt.Errorf("no kind named %q is registered in versions %q", kind, versions)
	}

	gvk := groupVersion.WithKind(kind)

	// Ensure we have a REST mapping
	resource, ok := m.kindToPluralResource[gvk]
	if !ok {
		found := []unversioned.GroupVersion{}
		for _, gv := range m.defaultGroupVersions {
			if _, ok := m.kindToPluralResource[gvk]; ok {
				found = append(found, gv)
			}
		}
		if len(found) > 0 {
			return nil, fmt.Errorf("object with kind %q exists in versions %v, not %v", kind, found, *groupVersion)
		}
		return nil, fmt.Errorf("the provided version %q and kind %q cannot be mapped to a supported object", groupVersion, kind)
	}

	// Ensure we have a REST scope
	scope, ok := m.kindToScope[gvk]
	if !ok {
		return nil, fmt.Errorf("the provided version %q and kind %q cannot be mapped to a supported scope", gvk.GroupVersion().String(), gvk.Kind)
	}

	interfaces, err := m.interfacesFunc(gvk.GroupVersion().String())
	if err != nil {
		return nil, fmt.Errorf("the provided version %q has no relevant versions", gvk.GroupVersion().String())
	}

	retVal := &RESTMapping{
		Resource:         resource,
		GroupVersionKind: gvk,
		Scope:            scope,

		Codec:            interfaces.Codec,
		ObjectConvertor:  interfaces.ObjectConvertor,
		MetadataAccessor: interfaces.MetadataAccessor,
	}

	return retVal, nil
}

// aliasToResource is used for mapping aliases to resources
var aliasToResource = map[string][]string{}

// AddResourceAlias maps aliases to resources
func (m *DefaultRESTMapper) AddResourceAlias(alias string, resources ...string) {
	if len(resources) == 0 {
		return
	}
	aliasToResource[alias] = resources
}

// AliasesForResource returns whether a resource has an alias or not
func (m *DefaultRESTMapper) AliasesForResource(alias string) ([]string, bool) {
	if res, ok := aliasToResource[alias]; ok {
		return res, true
	}
	return nil, false
}

// ResourceIsValid takes a string (kind) and checks if it's a valid resource
func (m *DefaultRESTMapper) ResourceIsValid(resource string) bool {
	_, _, err := m.VersionAndKindForResource(resource)
	return err == nil
}

// MultiRESTMapper is a wrapper for multiple RESTMappers.
type MultiRESTMapper []RESTMapper

// ResourceSingularizer converts a REST resource name from plural to singular (e.g., from pods to pod)
// This implementation supports multiple REST schemas and return the first match.
func (m MultiRESTMapper) ResourceSingularizer(resource string) (singular string, err error) {
	for _, t := range m {
		singular, err = t.ResourceSingularizer(resource)
		if err == nil {
			return
		}
	}
	return
}

// VersionAndKindForResource provides the Version and Kind  mappings for the
// REST resources. This implementation supports multiple REST schemas and return
// the first match.
func (m MultiRESTMapper) VersionAndKindForResource(resource string) (defaultVersion, kind string, err error) {
	for _, t := range m {
		defaultVersion, kind, err = t.VersionAndKindForResource(resource)
		if err == nil {
			return
		}
	}
	return
}

// GroupForResource provides the Group mappings for the REST resources. This
// implementation supports multiple REST schemas and returns the first match.
func (m MultiRESTMapper) GroupForResource(resource string) (group string, err error) {
	for _, t := range m {
		group, err = t.GroupForResource(resource)
		if err == nil {
			return
		}
	}
	return
}

// RESTMapping provides the REST mapping for the resource based on the resource
// kind and version. This implementation supports multiple REST schemas and
// return the first match.
func (m MultiRESTMapper) RESTMapping(kind string, versions ...string) (mapping *RESTMapping, err error) {
	for _, t := range m {
		mapping, err = t.RESTMapping(kind, versions...)
		if err == nil {
			return
		}
	}
	return
}

// AliasesForResource finds the first alias response for the provided mappers.
func (m MultiRESTMapper) AliasesForResource(alias string) (aliases []string, ok bool) {
	for _, t := range m {
		if aliases, ok = t.AliasesForResource(alias); ok {
			return
		}
	}
	return nil, false
}

// ResourceIsValid takes a string (either group/kind or kind) and checks if it's a valid resource
func (m MultiRESTMapper) ResourceIsValid(resource string) bool {
	for _, t := range m {
		if t.ResourceIsValid(resource) {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>/*
Copyright 2017 Toolhouse, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

	http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package deployment

import (
	"encoding/json"
	"net/http"
	"strings"

	multierror "github.com/hashicorp/go-multierror"
	"github.com/pkg/errors"
)

// Manifest defines the JSON schema for a deployment manifest file
type Manifest struct {
	Commit string `json:"commit"`
	Ref    string `json:"ref"`
}

func (m Manifest) Verify(commit, ref string) error {
	var result *multierror.Error
	if commit != "" && !strings.HasPrefix(m.Commit, commit) {
		err := errors.Errorf("Commit %s does not match (expected value: %s)", m.Commit, commit)
		result = multierror.Append(result, err)
	}

	if ref != "" && ref != m.Ref {
		err := errors.Errorf("Ref %s does not match (expected value: %s)", m.Ref, ref)
		result = multierror.Append(result, err)
	}

	return result.ErrorOrNil()
}

func FetchManifest(url string) (Manifest, error) {
	var manifest Manifest

	res, err := http.Get(url)
	if err != nil {
		return manifest, err
	}

	if res.StatusCode < 200 || res.StatusCode > 299 {
		return manifest, errors.Errorf("Received non-200 HTTP status code: %s", res.Status)
	}

	err = json.NewDecoder(res.Body).Decode(&manifest)
	res.Body.Close()
	return manifest, err
}
<commit_msg>Add timeout for HTTP request for manifests<commit_after>/*
Copyright 2017 Toolhouse, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

	http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package deployment

import (
	"encoding/json"
	"net/http"
	"strings"
	"time"

	multierror "github.com/hashicorp/go-multierror"
	"github.com/pkg/errors"
)

// Manifest defines the JSON schema for a deployment manifest file
type Manifest struct {
	Commit string `json:"commit"`
	Ref    string `json:"ref"`
}

func (m Manifest) Verify(commit, ref string) error {
	var result *multierror.Error
	if commit != "" && !strings.HasPrefix(m.Commit, commit) {
		err := errors.Errorf("Commit %s does not match (expected value: %s)", m.Commit, commit)
		result = multierror.Append(result, err)
	}

	if ref != "" && ref != m.Ref {
		err := errors.Errorf("Ref %s does not match (expected value: %s)", m.Ref, ref)
		result = multierror.Append(result, err)
	}

	return result.ErrorOrNil()
}

func FetchManifest(url string) (Manifest, error) {
	var manifest Manifest

	client := &http.Client{
		Timeout: 5 * time.Second,
	}

	req, err := http.NewRequest(http.MethodGet, url, nil)
	if err != nil {
		return manifest, err
	}

	res, err := client.Do(req)
	if err != nil {
		return manifest, err
	}

	if res.StatusCode < 200 || res.StatusCode > 299 {
		return manifest, errors.Errorf("Received non-200 HTTP status code: %s", res.Status)
	}

	err = json.NewDecoder(res.Body).Decode(&manifest)
	res.Body.Close()
	return manifest, err
}
<|endoftext|>