<commit_before>package main

import (
	"encoding/xml"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

// Structs for XML building
type Package struct {
	Xmlns   string     `xml:"xmlns,attr"`
	Types   []MetaType `xml:"types"`
	Version string     `xml:"version"`
}

type MetaType struct {
	Members []string `xml:"members"`
	Name    string   `xml:"name"`
}

func createPackage() Package {
	return Package{
		Version: strings.TrimPrefix(apiVersion, "v"),
		Xmlns:   "http://soap.sforce.com/2006/04/metadata",
	}
}

type metapath struct {
	path string
	name string
	hasFolder bool
	onlyFolder bool
}

var metapaths = []metapath{
	metapath{path: "applications", name: "CustomApplication"},
	metapath{path: "assignmentRules", name: "AssignmentRules"},
	metapath{path: "aura", name: "AuraDefinitionBundle", hasFolder: true, onlyFolder: true},
	metapath{path: "autoResponseRules", name: "AutoResponseRules"},
	metapath{path: "classes", name: "ApexClass"},
	metapath{path: "communities", name: "Community"},
	metapath{path: "components", name: "ApexComponent"},
	metapath{path: "connectedApps", name: "ConnectedApp"},
	metapath{path: "customMetadata", name: "CustomMetadata"},
	metapath{path: "customPermissions", name: "CustomPermission"},
	metapath{path: "dashboards", name: "Dashboard", hasFolder: true},
	metapath{path: "documents", name: "Document", hasFolder: true},
	metapath{path: "email", name: "EmailTemplate", hasFolder: true},
	metapath{path: "flexipages", name: "FlexiPage"},
	metapath{path: "groups", name: "Group"},
	metapath{path: "homePageLayouts", name: "HomePageLayout"},
	metapath{path: "labels", name: "CustomLabels"},
	metapath{path: "layouts", name: "Layout"},
	metapath{path: "objects", name: "CustomObject"},
	metapath{path: "objectTranslations", name: "CustomObjectTranslation"},
	metapath{path: "pages", name: "ApexPage"},
	metapath{path: "permissionsets", name: "PermissionSet"},
	metapath{path: "profiles", name: "Profile"},
	metapath{path: "queues", name: "Queue"},
	metapath{path: "quickActions", name: "QuickAction"},
	metapath{path: "remoteSiteSettings", name: "RemoteSiteSetting"},
	metapath{path: "reports", name: "Report", hasFolder: true},
	metapath{path: "reportTypes", name: "ReportType"},
	metapath{path: "roles", name: "Role"},
	metapath{path: "scontrols", name: "Scontrol"},
	metapath{path: "settings", name: "Settings"},
	metapath{path: "sharingRules", name: "SharingRules"},
	metapath{path: "staticresources", name: "StaticResource"},
	metapath{path: "tabs", name: "CustomTab"},
	metapath{path: "triggers", name: "ApexTrigger"},
	metapath{path: "workflows", name: "Workflow"},
}

type PackageBuilder struct {
	IsPush   bool
	Metadata map[string]MetaType
	Files    ForceMetadataFiles
}

func NewPushBuilder() PackageBuilder {
	pb := PackageBuilder{IsPush: true}
	pb.Metadata = make(map[string]MetaType)
	pb.Files = make(ForceMetadataFiles)

	return pb
}

func NewFetchBuilder() PackageBuilder {
	pb := PackageBuilder{IsPush: false}
	pb.Metadata = make(map[string]MetaType)
	pb.Files = make(ForceMetadataFiles)

	return pb
}

// Build and return package.xml
func (pb PackageBuilder) PackageXml() []byte {
	p := createPackage()

	for _, metaType := range pb.Metadata {
		p.Types = append(p.Types, metaType)
	}

	byteXml, _ := xml.MarshalIndent(p, "", "    ")
	byteXml = append([]byte(xml.Header), byteXml...)
	//if err := ioutil.WriteFile("mypackage.xml", byteXml, 0644); err != nil {
	//ErrorAndExit(err.Error())
	//}
	return byteXml
}

// Returns the full ForceMetadataFiles container
func (pb *PackageBuilder) ForceMetadataFiles() ForceMetadataFiles {
	pb.Files["package.xml"] = pb.PackageXml()
	return pb.Files
}

// Add a file to the builder
func (pb *PackageBuilder) AddFile(fpath string) (fname string, err error) {
	fpath, err = filepath.Abs(fpath)
	if err != nil {
		return
	}
	_, err = os.Stat(fpath)
	if err != nil {
		return
	}

	isDestructiveChanges, err := regexp.MatchString("destructiveChanges(Pre|Post)?"+regexp.QuoteMeta(".")+"xml", fpath)
	if err != nil {
		return
	}

	metaName, fname := getMetaTypeFromPath(fpath)
	if !isDestructiveChanges && !strings.HasSuffix(fname, "-meta.xml") {
		pb.AddMetaToPackage(metaName, fname)
	}

	// If it's a push, we want to actually add the files
	if pb.IsPush {
		if isDestructiveChanges {
			err = pb.addDestructiveChanges(fpath)
		} else {
			err = pb.addFileToWorkingDir(metaName, fpath)
		}
	}

	return
}

// Adds the file to a temp directory for deploy
func (pb *PackageBuilder) addFileToWorkingDir(metaName string, fpath string) (err error) {
	// Get relative dir from source
	srcDir := filepath.Dir(filepath.Dir(fpath))
	for _, mp := range metapaths {
		if metaName == mp.name && mp.hasFolder {
			srcDir = filepath.Dir(srcDir)
		}
	}
	frel, _ := filepath.Rel(srcDir, fpath)

	// Try to find meta file
	hasMeta := true
	fmeta := fpath + "-meta.xml"
	fmetarel := ""
	if _, err = os.Stat(fmeta); err != nil {
		if os.IsNotExist(err) {
			hasMeta = false
		} else {
			// Has error
			return
		}
	} else {
		// Should be present since we worked back to srcDir
		fmetarel, _ = filepath.Rel(srcDir, fmeta)
	}

	fdata, err := ioutil.ReadFile(fpath)
	if err != nil {
		return
	}

	pb.Files[frel] = fdata
	if hasMeta {
		fdata, err = ioutil.ReadFile(fmeta)
		pb.Files[fmetarel] = fdata
		return
	}

	return
}

func (pb *PackageBuilder) addDestructiveChanges(fpath string) (err error) {
	fdata, err := ioutil.ReadFile(fpath)
	if err != nil {
		return
	}

	frel, _ := filepath.Rel(filepath.Dir(fpath), fpath)
	pb.Files[frel] = fdata

	return
}

// Adds a metadata name to the pending package
func (pb *PackageBuilder) AddMetaToPackage(metaName string, name string) {
	mt := pb.Metadata[metaName]
	if mt.Name == "" {
		mt.Name = metaName
	}

	mt.Members = append(mt.Members, name)
	pb.Metadata[metaName] = mt
}

// Gets metadata type name and target name from a file path
func getMetaTypeFromPath(fpath string) (metaName string, name string) {
	fpath, err := filepath.Abs(fpath)
	if err != nil {
		ErrorAndExit("Cound not find " + fpath)
	}
	if _, err := os.Stat(fpath); err != nil {
		ErrorAndExit("Cound not open " + fpath)
	}

	// Get the metadata type and name for the file
	metaName, fileName := getMetaForPath(fpath)
	name = strings.TrimSuffix(fileName, filepath.Ext(fileName))
	return
}

// Gets partial path based on a meta type name
func getPathForMeta(metaname string) string {
	for _, mp := range metapaths {
		if strings.EqualFold(mp.name, metaname) {
			return mp.path
		}
	}

	// Unknown, so use metaname
	return metaname
}

// Gets meta type and name based on a path
func getMetaForPath(path string) (metaName string, objectName string) {
	parentDir := filepath.Dir(path)
	parentName := filepath.Base(parentDir)
	grandparentName := filepath.Base(filepath.Dir(parentDir))
	fileName := filepath.Base(path)

	for _, mp := range metapaths {
		if mp.hasFolder && grandparentName == mp.path {
			metaName = mp.name
			if mp.onlyFolder {
				objectName = parentName
			} else {
				objectName = parentName + "/" + fileName
			}
			return
		}
		if mp.path == parentName {
			metaName = mp.name
			objectName = fileName
			return
		}
	}

	// Unknown, so use path
	metaName = parentName
	objectName = fileName
	return
}
<commit_msg>Install Managed Packages<commit_after>package main

import (
	"encoding/xml"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

// Structs for XML building
type Package struct {
	Xmlns   string     `xml:"xmlns,attr"`
	Types   []MetaType `xml:"types"`
	Version string     `xml:"version"`
}

type MetaType struct {
	Members []string `xml:"members"`
	Name    string   `xml:"name"`
}

func createPackage() Package {
	return Package{
		Version: strings.TrimPrefix(apiVersion, "v"),
		Xmlns:   "http://soap.sforce.com/2006/04/metadata",
	}
}

type metapath struct {
	path string
	name string
	hasFolder bool
	onlyFolder bool
}

var metapaths = []metapath{
	metapath{path: "applications", name: "CustomApplication"},
	metapath{path: "assignmentRules", name: "AssignmentRules"},
	metapath{path: "aura", name: "AuraDefinitionBundle", hasFolder: true, onlyFolder: true},
	metapath{path: "autoResponseRules", name: "AutoResponseRules"},
	metapath{path: "classes", name: "ApexClass"},
	metapath{path: "communities", name: "Community"},
	metapath{path: "components", name: "ApexComponent"},
	metapath{path: "connectedApps", name: "ConnectedApp"},
	metapath{path: "customMetadata", name: "CustomMetadata"},
	metapath{path: "customPermissions", name: "CustomPermission"},
	metapath{path: "dashboards", name: "Dashboard", hasFolder: true},
	metapath{path: "documents", name: "Document", hasFolder: true},
	metapath{path: "email", name: "EmailTemplate", hasFolder: true},
	metapath{path: "flexipages", name: "FlexiPage"},
	metapath{path: "groups", name: "Group"},
	metapath{path: "homePageLayouts", name: "HomePageLayout"},
	metapath{path: "installedPackages", name: "InstalledPackage"},
	metapath{path: "labels", name: "CustomLabels"},
	metapath{path: "layouts", name: "Layout"},
	metapath{path: "objects", name: "CustomObject"},
	metapath{path: "objectTranslations", name: "CustomObjectTranslation"},
	metapath{path: "pages", name: "ApexPage"},
	metapath{path: "permissionsets", name: "PermissionSet"},
	metapath{path: "profiles", name: "Profile"},
	metapath{path: "queues", name: "Queue"},
	metapath{path: "quickActions", name: "QuickAction"},
	metapath{path: "remoteSiteSettings", name: "RemoteSiteSetting"},
	metapath{path: "reports", name: "Report", hasFolder: true},
	metapath{path: "reportTypes", name: "ReportType"},
	metapath{path: "roles", name: "Role"},
	metapath{path: "scontrols", name: "Scontrol"},
	metapath{path: "settings", name: "Settings"},
	metapath{path: "sharingRules", name: "SharingRules"},
	metapath{path: "staticresources", name: "StaticResource"},
	metapath{path: "tabs", name: "CustomTab"},
	metapath{path: "triggers", name: "ApexTrigger"},
	metapath{path: "workflows", name: "Workflow"},
}

type PackageBuilder struct {
	IsPush   bool
	Metadata map[string]MetaType
	Files    ForceMetadataFiles
}

func NewPushBuilder() PackageBuilder {
	pb := PackageBuilder{IsPush: true}
	pb.Metadata = make(map[string]MetaType)
	pb.Files = make(ForceMetadataFiles)

	return pb
}

func NewFetchBuilder() PackageBuilder {
	pb := PackageBuilder{IsPush: false}
	pb.Metadata = make(map[string]MetaType)
	pb.Files = make(ForceMetadataFiles)

	return pb
}

// Build and return package.xml
func (pb PackageBuilder) PackageXml() []byte {
	p := createPackage()

	for _, metaType := range pb.Metadata {
		p.Types = append(p.Types, metaType)
	}

	byteXml, _ := xml.MarshalIndent(p, "", "    ")
	byteXml = append([]byte(xml.Header), byteXml...)
	//if err := ioutil.WriteFile("mypackage.xml", byteXml, 0644); err != nil {
	//ErrorAndExit(err.Error())
	//}
	return byteXml
}

// Returns the full ForceMetadataFiles container
func (pb *PackageBuilder) ForceMetadataFiles() ForceMetadataFiles {
	pb.Files["package.xml"] = pb.PackageXml()
	return pb.Files
}

// Add a file to the builder
func (pb *PackageBuilder) AddFile(fpath string) (fname string, err error) {
	fpath, err = filepath.Abs(fpath)
	if err != nil {
		return
	}
	_, err = os.Stat(fpath)
	if err != nil {
		return
	}

	isDestructiveChanges, err := regexp.MatchString("destructiveChanges(Pre|Post)?"+regexp.QuoteMeta(".")+"xml", fpath)
	if err != nil {
		return
	}

	metaName, fname := getMetaTypeFromPath(fpath)
	if !isDestructiveChanges && !strings.HasSuffix(fname, "-meta.xml") {
		pb.AddMetaToPackage(metaName, fname)
	}

	// If it's a push, we want to actually add the files
	if pb.IsPush {
		if isDestructiveChanges {
			err = pb.addDestructiveChanges(fpath)
		} else {
			err = pb.addFileToWorkingDir(metaName, fpath)
		}
	}

	return
}

// Adds the file to a temp directory for deploy
func (pb *PackageBuilder) addFileToWorkingDir(metaName string, fpath string) (err error) {
	// Get relative dir from source
	srcDir := filepath.Dir(filepath.Dir(fpath))
	for _, mp := range metapaths {
		if metaName == mp.name && mp.hasFolder {
			srcDir = filepath.Dir(srcDir)
		}
	}
	frel, _ := filepath.Rel(srcDir, fpath)

	// Try to find meta file
	hasMeta := true
	fmeta := fpath + "-meta.xml"
	fmetarel := ""
	if _, err = os.Stat(fmeta); err != nil {
		if os.IsNotExist(err) {
			hasMeta = false
		} else {
			// Has error
			return
		}
	} else {
		// Should be present since we worked back to srcDir
		fmetarel, _ = filepath.Rel(srcDir, fmeta)
	}

	fdata, err := ioutil.ReadFile(fpath)
	if err != nil {
		return
	}

	pb.Files[frel] = fdata
	if hasMeta {
		fdata, err = ioutil.ReadFile(fmeta)
		pb.Files[fmetarel] = fdata
		return
	}

	return
}

func (pb *PackageBuilder) addDestructiveChanges(fpath string) (err error) {
	fdata, err := ioutil.ReadFile(fpath)
	if err != nil {
		return
	}

	frel, _ := filepath.Rel(filepath.Dir(fpath), fpath)
	pb.Files[frel] = fdata

	return
}

// Adds a metadata name to the pending package
func (pb *PackageBuilder) AddMetaToPackage(metaName string, name string) {
	mt := pb.Metadata[metaName]
	if mt.Name == "" {
		mt.Name = metaName
	}

	mt.Members = append(mt.Members, name)
	pb.Metadata[metaName] = mt
}

// Gets metadata type name and target name from a file path
func getMetaTypeFromPath(fpath string) (metaName string, name string) {
	fpath, err := filepath.Abs(fpath)
	if err != nil {
		ErrorAndExit("Cound not find " + fpath)
	}
	if _, err := os.Stat(fpath); err != nil {
		ErrorAndExit("Cound not open " + fpath)
	}

	// Get the metadata type and name for the file
	metaName, fileName := getMetaForPath(fpath)
	name = strings.TrimSuffix(fileName, filepath.Ext(fileName))
	return
}

// Gets partial path based on a meta type name
func getPathForMeta(metaname string) string {
	for _, mp := range metapaths {
		if strings.EqualFold(mp.name, metaname) {
			return mp.path
		}
	}

	// Unknown, so use metaname
	return metaname
}

// Gets meta type and name based on a path
func getMetaForPath(path string) (metaName string, objectName string) {
	parentDir := filepath.Dir(path)
	parentName := filepath.Base(parentDir)
	grandparentName := filepath.Base(filepath.Dir(parentDir))
	fileName := filepath.Base(path)

	for _, mp := range metapaths {
		if mp.hasFolder && grandparentName == mp.path {
			metaName = mp.name
			if mp.onlyFolder {
				objectName = parentName
			} else {
				objectName = parentName + "/" + fileName
			}
			return
		}
		if mp.path == parentName {
			metaName = mp.name
			objectName = fileName
			return
		}
	}

	// Unknown, so use path
	metaName = parentName
	objectName = fileName
	return
}
<|endoftext|>


<commit_before>// Categories functions
package fakku

import (
	"encoding/json"
	"fmt"
	"net/url"
)

const (
	CategoryManga     = "manga"
	CategoryDoujinshi = "doujinshi"
	CategoryVideos    = "videos" // Is this a legal category?
)

type categoryIndexApiFunction struct {
	Category string
}

func (c categoryIndexApiFunction) Construct() string {
	return fmt.Sprintf("%s/%s", apiHeader, c.Category)
}

func GetCategoryIndex(category string) (*CategoryIndex, error) {
	var c CategoryIndex
	url := categoryIndexApiFunction{Category: category}
	if err := apiCall(url, &c); err != nil {
		return nil, err
	} else {
		return &c, nil
	}

}

type CategoryIndex struct {
	Latest        ContentList
	Favorites     ContentList
	Popular       ContentList
	Controversial ContentList
}

func (c *CategoryIndex) UnmarshalJSON(b []byte) error {
	var err error
	var f interface{}
	json.Unmarshal(b, &f)
	m := f.(map[string]interface{})
	if _, errExists := m["error"]; errExists {
		return fmt.Errorf("CategoryIndex request yielded an error!")
	}
	c.Latest, err = populateCategory("latest", m)
	if err != nil {
		return err
	}
	c.Favorites, err = populateCategory("favorites", m)
	if err != nil {
		return err
	}

	c.Popular, err = populateCategory("popular", m)
	if err != nil {
		return err
	}
	c.Controversial, err = populateCategory("controversial", m)
	if err != nil {
		return err
	}

	return nil
}
func populateCategory(category string, container map[string]interface{}) (ContentList, error) {
	q, result := container[category].([]interface{})
	if !result {
		return nil, fmt.Errorf("Category %s does not exist!", category)
	}
	l := make(ContentList, len(q))
	for i := 0; i < len(q); i++ {
		l[i].populate(q[i].(map[string]interface{}))
	}
	return l, nil

}

type tags struct {
	Tags  []Tag `json:"tags"`
	Total uint  `json:"total"`
}

type Tag struct {
	Name           string `json:"tag_name"`
	RawUrl         string `json:"tag_url"`
	RawImageSample string `json:"tag_image_sample"`
	Description    string `json:"tag_description"`
}

func (this *Tag) Url() (*url.URL, error) {
	return url.Parse(this.RawUrl)
}
func (this *Tag) ImageSampleUrl() (*url.URL, error) {
	return url.Parse(this.RawImageSample)
}

type tagsApiFunction struct{}

func (c tagsApiFunction) Construct() string {
	return fmt.Sprintf("%s/tags", apiHeader)
}

func Tags() ([]Tag, error) {
	var c tags
	url := tagsApiFunction{}
	if err := apiCall(url, &c); err != nil {
		return nil, err
	} else {
		return c.Tags, nil
	}
}

type ContentSearchResults struct {
	Content ContentList `json:"content"`
	Total   uint        `json:"total"`
	Pages   uint        `json:"pages"`
}

type contentSearchApiFunction struct {
	Terms string
	supportsPagination
}

func (c contentSearchApiFunction) Construct() string {
	base := fmt.Sprintf("%s/search/%s", apiHeader, c.Terms)
	return paginateString(base, c.Page)
}

func ContentSearchPage(terms string, page uint) (*ContentSearchResults, error) {
	var c ContentSearchResults
	url := contentSearchApiFunction{
		Terms:              terms,
		supportsPagination: supportsPagination{Page: page},
	}
	if err := apiCall(url, c); err != nil {
		return nil, err
	} else {
		return &c, nil
	}
}

func ContentSearch(terms string) (*ContentSearchResults, error) {
	return ContentSearchPage(terms, 0)
}
<commit_msg>Added data checking to the Tags command<commit_after>// Categories functions
package fakku

import (
	"encoding/json"
	"fmt"
	"net/url"
)

const (
	CategoryManga     = "manga"
	CategoryDoujinshi = "doujinshi"
	CategoryVideos    = "videos" // Is this a legal category?
)

type categoryIndexApiFunction struct {
	Category string
}

func (c categoryIndexApiFunction) Construct() string {
	return fmt.Sprintf("%s/%s", apiHeader, c.Category)
}

func GetCategoryIndex(category string) (*CategoryIndex, error) {
	var c CategoryIndex
	url := categoryIndexApiFunction{Category: category}
	if err := apiCall(url, &c); err != nil {
		return nil, err
	} else {
		return &c, nil
	}

}

type CategoryIndex struct {
	Latest        ContentList
	Favorites     ContentList
	Popular       ContentList
	Controversial ContentList
}

func (c *CategoryIndex) UnmarshalJSON(b []byte) error {
	var err error
	var f interface{}
	json.Unmarshal(b, &f)
	m := f.(map[string]interface{})
	if _, errExists := m["error"]; errExists {
		return fmt.Errorf("CategoryIndex request yielded an error!")
	}
	c.Latest, err = populateCategory("latest", m)
	if err != nil {
		return err
	}
	c.Favorites, err = populateCategory("favorites", m)
	if err != nil {
		return err
	}

	c.Popular, err = populateCategory("popular", m)
	if err != nil {
		return err
	}
	c.Controversial, err = populateCategory("controversial", m)
	if err != nil {
		return err
	}

	return nil
}
func populateCategory(category string, container map[string]interface{}) (ContentList, error) {
	q, result := container[category].([]interface{})
	if !result {
		return nil, fmt.Errorf("Category %s does not exist!", category)
	}
	l := make(ContentList, len(q))
	for i := 0; i < len(q); i++ {
		l[i].populate(q[i].(map[string]interface{}))
	}
	return l, nil

}

type tags struct {
	Tags  []Tag `json:"tags"`
	Total uint  `json:"total"`
}

type Tag struct {
	Name           string `json:"tag_name"`
	RawUrl         string `json:"tag_url"`
	RawImageSample string `json:"tag_image_sample"`
	Description    string `json:"tag_description"`
}

func (this *Tag) Url() (*url.URL, error) {
	return url.Parse(this.RawUrl)
}
func (this *Tag) ImageSampleUrl() (*url.URL, error) {
	return url.Parse(this.RawImageSample)
}

type tagsApiFunction struct{}

func (c tagsApiFunction) Construct() string {
	return fmt.Sprintf("%s/tags", apiHeader)
}

func Tags() ([]Tag, error) {
	var c tags
	url := tagsApiFunction{}
	if err := apiCall(url, &c); err != nil {
		return nil, err
	} else {
		if c.Total != uint(len(c.Tags)) {
			return nil, fmt.Errorf("Count mismatch! Expected %d tags but got %d instead!", c.Total, len(c.Tags))
		} else {
			return c.Tags, nil
		}
	}
}

type ContentSearchResults struct {
	Content ContentList `json:"content"`
	Total   uint        `json:"total"`
	Pages   uint        `json:"pages"`
}

type contentSearchApiFunction struct {
	Terms string
	supportsPagination
}

func (c contentSearchApiFunction) Construct() string {
	base := fmt.Sprintf("%s/search/%s", apiHeader, c.Terms)
	return paginateString(base, c.Page)
}

func ContentSearchPage(terms string, page uint) (*ContentSearchResults, error) {
	var c ContentSearchResults
	url := contentSearchApiFunction{
		Terms:              terms,
		supportsPagination: supportsPagination{Page: page},
	}
	if err := apiCall(url, c); err != nil {
		return nil, err
	} else {
		return &c, nil
	}
}

func ContentSearch(terms string) (*ContentSearchResults, error) {
	return ContentSearchPage(terms, 0)
}
<|endoftext|>


<commit_before>package main

import . "github.com/fogleman/pt/pt"

// http://graphics.cs.williams.edu/data/meshes/dragon.zip

func main() {
	scene := Scene{}

	material := GlossyMaterial(HexColor(0xB7CA79), 1.5, Radians(20))
	mesh, err := LoadOBJ("examples/dragon.obj", material)
	if err != nil {
		panic(err)
	}
	mesh.FitInside(Box{Vector{-1, 0, -1}, Vector{1, 2, 1}}, Vector{0.5, 0, 0.5})
	scene.Add(mesh)

	floor := GlossyMaterial(HexColor(0xD8CAA8), 1.2, Radians(5))
	scene.Add(NewCube(Vector{-50, -50, -50}, Vector{50, 0, 50}, floor))

	light := LightMaterial(Color{1, 1, 1}, 50)
	scene.Add(NewSphere(Vector{-1, 10, 0}, 1, light))

	mouth := LightMaterial(HexColor(0xFFFAD5), 500)
	scene.Add(NewSphere(V(-0.05, 1, -0.5), 0.03, mouth))

	camera := LookAt(Vector{-3, 2, -1}, Vector{0, 0.6, -0.1}, Vector{0, 1, 0}, 35)
	camera.SetFocus(Vector{0, 1, -0.5}, 0.03)
	sampler := NewSampler(16, 8)
	IterativeRender("out%03d.png", 1000, &scene, &camera, sampler, 1920, 1080, -1)
}
<commit_msg>update example<commit_after>package main

import . "github.com/fogleman/pt/pt"

// http://graphics.cs.williams.edu/data/meshes/dragon.zip

func main() {
	scene := Scene{}

	material := GlossyMaterial(HexColor(0xB7CA79), 1.5, Radians(20))
	mesh, err := LoadOBJ("examples/dragon.obj", material)
	if err != nil {
		panic(err)
	}
	mesh.FitInside(Box{Vector{-1, 0, -1}, Vector{1, 2, 1}}, Vector{0.5, 0, 0.5})
	scene.Add(mesh)

	floor := GlossyMaterial(HexColor(0xD8CAA8), 1.2, Radians(5))
	scene.Add(NewCube(Vector{-50, -50, -50}, Vector{50, 0, 50}, floor))

	light := LightMaterial(Color{1, 1, 1}, 75)
	scene.Add(NewSphere(Vector{-1, 10, 0}, 1, light))

	mouth := LightMaterial(HexColor(0xFFFAD5), 500)
	scene.Add(NewSphere(V(-0.05, 1, -0.5), 0.03, mouth))

	camera := LookAt(Vector{-3, 2, -1}, Vector{0, 0.6, -0.1}, Vector{0, 1, 0}, 35)
	camera.SetFocus(Vector{0, 1, -0.5}, 0.03)
	sampler := NewSampler(16, 8)
	IterativeRender("out%03d.png", 1000, &scene, &camera, sampler, 1920, 1080, -1)
}
<|endoftext|>


<commit_before>package connections

import (
	"sync"
	"time"

	"github.com/gorilla/websocket"
	"github.com/pquerna/ffjson/ffjson"
	"github.com/sirupsen/logrus"

	"github.com/ivan1993spb/snake-server/game"
)

const (
	chanOutputMessageBuffer      = 128
	chanReadMessagesBuffer       = 128
	chanDecodeMessageBuffer      = 128
	chanEncodeMessageBuffer      = 128
	chanProxyInputMessageBuffer  = 64
	chanListenInputMessageBuffer = 32

	sendInputMessageTimeout = time.Millisecond * 50
)

type ConnectionWorker struct {
	conn   *websocket.Conn
	logger *logrus.Logger

	chStop      chan struct{}
	chOutput    chan OutputMessage
	chsInput    []chan InputMessage
	chsInputMux *sync.RWMutex

	flagStarted bool
}

func NewConnectionWorker(conn *websocket.Conn, logger *logrus.Logger) *ConnectionWorker {
	return &ConnectionWorker{
		conn:        conn,
		logger:      logger,
		chStop:      make(chan struct{}, 0),
		chOutput:    make(chan OutputMessage, chanOutputMessageBuffer),
		chsInput:    make([]chan InputMessage, 0),
		chsInputMux: &sync.RWMutex{},
	}
}

type ErrStartConnectionWorker string

func (e ErrStartConnectionWorker) Error() string {
	return "error start connection worker: " + string(e)
}

func (cw *ConnectionWorker) Start(game *game.Game) error {
	if cw.flagStarted {
		return ErrStartConnectionWorker("connection worker already started")
	}

	cw.flagStarted = true

	chInputBytes := cw.read()
	chInputMessages := cw.decode(chInputBytes, cw.chStop)
	cw.broadcastInputMessage(chInputMessages, cw.chStop)
	cw.listen(game.Events(cw.chStop), cw.chStop)
	chOutputBytes := cw.encode(cw.chOutput, cw.chStop)
	cw.write(chOutputBytes, cw.chStop)

	return nil
}

func (cw *ConnectionWorker) listenErrors(cherr <-chan error) <-chan struct{} {
	stop := make(chan struct{}, 0)

	go func() {
		for err := range cherr {
			if err != nil {
				close(stop)
				return
			}
		}
	}()

	return stop
}

func (cw *ConnectionWorker) read() <-chan []byte {
	chout := make(chan []byte, chanReadMessagesBuffer)

	go func() {
		defer close(chout)

		for {
			messageType, data, err := cw.conn.ReadMessage()
			if err != nil {
				// TODO: Handle error?
				return
			}

			if websocket.TextMessage != messageType {
				// TODO: Handle case - unexpected message type?
				continue
			}

			chout <- data
		}
	}()

	return chout
}

func (cw *ConnectionWorker) decode(chin <-chan []byte, stop <-chan struct{}) <-chan InputMessage {
	chout := make(chan InputMessage, chanDecodeMessageBuffer)

	go func() {
		defer close(chout)

		var decoder = ffjson.NewDecoder()

		for {
			select {
			case data := <-chin:
				var inputMessage *InputMessage
				if err := decoder.DecodeFast(data, &inputMessage); err != nil {
					// TODO: Handler error.
				} else {
					chout <- *inputMessage
				}
			case <-stop:
				return
			}
		}
	}()

	return chout
}

func (cw *ConnectionWorker) broadcastInputMessage(chin <-chan InputMessage, stop <-chan struct{}) {
	go func() {
		for {
			select {
			case inputMessage := <-chin:
				cw.chsInputMux.RLock()
				for _, ch := range cw.chsInput {
					select {
					case ch <- inputMessage:
					case <-stop:
						return
					}
				}
				cw.chsInputMux.RUnlock()
			case <-stop:
				return
			}
		}
	}()
}

func (cw *ConnectionWorker) Input(stop <-chan struct{}) <-chan InputMessage {
	chProxy := make(chan InputMessage, chanProxyInputMessageBuffer)

	cw.chsInputMux.Lock()
	cw.chsInput = append(cw.chsInput, chProxy)
	cw.chsInputMux.Unlock()

	chout := make(chan InputMessage, chanListenInputMessageBuffer)

	go func() {
		defer close(chout)
		defer func() {
			cw.chsInputMux.Lock()
			for i := range cw.chsInput {
				if cw.chsInput[i] == chProxy {
					cw.chsInput = append(cw.chsInput[:i], cw.chsInput[i+1:]...)
					close(chProxy)
					break
				}
			}
			cw.chsInputMux.Unlock()
		}()

		for {
			select {
			case <-stop:
				return
			case <-cw.chStop:
				return
			case inputMessage := <-chProxy:
				cw.sendInputMessage(chout, inputMessage, stop, sendInputMessageTimeout)
			}
		}
	}()

	return chout
}

func (cw *ConnectionWorker) sendInputMessage(ch chan InputMessage, inputMessage InputMessage, stop <-chan struct{}, timeout time.Duration) {
	var timer = time.NewTimer(timeout)
	defer timer.Stop()
	if cap(ch) == 0 {
		select {
		case ch <- inputMessage:
		case <-cw.chStop:
		case <-stop:
		case <-timer.C:
		}
	} else {
		for {
			select {
			case ch <- inputMessage:
				return
			case <-cw.chStop:
				return
			case <-stop:
				return
			case <-timer.C:
				return
			default:
				if len(ch) == cap(ch) {
					<-ch
				}
			}
		}
	}
}

func (cw *ConnectionWorker) write(chin <-chan []byte, stop <-chan struct{}) {
	go func() {
		for {
			select {
			case data := <-chin:
				if err := cw.conn.WriteMessage(websocket.TextMessage, data); err != nil {
					// TODO: Handler error.
				}
			case <-stop:
				return
			}
		}
	}()
}

func (cw *ConnectionWorker) outputMessage(outputMessage OutputMessage) {
	select {
	case cw.chOutput <- outputMessage:
	case <-cw.chStop:
	}
}

func (cw *ConnectionWorker) encode(chin <-chan OutputMessage, stop <-chan struct{}) <-chan []byte {
	chout := make(chan []byte, chanEncodeMessageBuffer)

	go func() {
		defer close(chout)

		for {
			select {
			case message := <-chin:
				if data, err := ffjson.MarshalFast(message); err != nil {
					// TODO: Handler error.
				} else {
					chout <- data
				}
			case <-stop:
				return
			}
		}
	}()

	return chout
}

func (cw *ConnectionWorker) listen(chin <-chan game.Event, stop <-chan struct{}) {
	for {
		select {
		case event := <-chin:
			// TODO: Do stuff.
			cw.logger.Info(event)

			// ...

			cw.outputMessage(OutputMessage{
				Type:    OutputMessageTypeGameEvent,
				Payload: event,
			})
		case <-stop:
			return
		}
	}
}
<commit_msg>Remove json decode/encode fast methods<commit_after>package connections

import (
	"sync"
	"time"

	"github.com/gorilla/websocket"
	"github.com/pquerna/ffjson/ffjson"
	"github.com/sirupsen/logrus"

	"github.com/ivan1993spb/snake-server/game"
)

const (
	chanOutputMessageBuffer      = 128
	chanReadMessagesBuffer       = 128
	chanDecodeMessageBuffer      = 128
	chanEncodeMessageBuffer      = 128
	chanProxyInputMessageBuffer  = 64
	chanListenInputMessageBuffer = 32

	sendInputMessageTimeout = time.Millisecond * 50
)

type ConnectionWorker struct {
	conn   *websocket.Conn
	logger *logrus.Logger

	chStop      chan struct{}
	chOutput    chan OutputMessage
	chsInput    []chan InputMessage
	chsInputMux *sync.RWMutex

	flagStarted bool
}

func NewConnectionWorker(conn *websocket.Conn, logger *logrus.Logger) *ConnectionWorker {
	return &ConnectionWorker{
		conn:        conn,
		logger:      logger,
		chStop:      make(chan struct{}, 0),
		chOutput:    make(chan OutputMessage, chanOutputMessageBuffer),
		chsInput:    make([]chan InputMessage, 0),
		chsInputMux: &sync.RWMutex{},
	}
}

type ErrStartConnectionWorker string

func (e ErrStartConnectionWorker) Error() string {
	return "error start connection worker: " + string(e)
}

func (cw *ConnectionWorker) Start(game *game.Game) error {
	if cw.flagStarted {
		return ErrStartConnectionWorker("connection worker already started")
	}

	cw.flagStarted = true

	chInputBytes := cw.read()
	chInputMessages := cw.decode(chInputBytes, cw.chStop)
	cw.broadcastInputMessage(chInputMessages, cw.chStop)
	cw.listen(game.Events(cw.chStop), cw.chStop)
	chOutputBytes := cw.encode(cw.chOutput, cw.chStop)
	cw.write(chOutputBytes, cw.chStop)

	return nil
}

func (cw *ConnectionWorker) listenErrors(cherr <-chan error) <-chan struct{} {
	stop := make(chan struct{}, 0)

	go func() {
		for err := range cherr {
			if err != nil {
				close(stop)
				return
			}
		}
	}()

	return stop
}

func (cw *ConnectionWorker) read() <-chan []byte {
	chout := make(chan []byte, chanReadMessagesBuffer)

	go func() {
		defer close(chout)

		for {
			messageType, data, err := cw.conn.ReadMessage()
			if err != nil {
				// TODO: Handle error?
				return
			}

			if websocket.TextMessage != messageType {
				// TODO: Handle case - unexpected message type?
				continue
			}

			chout <- data
		}
	}()

	return chout
}

func (cw *ConnectionWorker) decode(chin <-chan []byte, stop <-chan struct{}) <-chan InputMessage {
	chout := make(chan InputMessage, chanDecodeMessageBuffer)

	go func() {
		defer close(chout)

		var decoder = ffjson.NewDecoder()

		for {
			select {
			case data := <-chin:
				var inputMessage *InputMessage
				if err := decoder.Decode(data, &inputMessage); err != nil {
					// TODO: Handler error.
				} else {
					chout <- *inputMessage
				}
			case <-stop:
				return
			}
		}
	}()

	return chout
}

func (cw *ConnectionWorker) broadcastInputMessage(chin <-chan InputMessage, stop <-chan struct{}) {
	go func() {
		for {
			select {
			case inputMessage := <-chin:
				cw.chsInputMux.RLock()
				for _, ch := range cw.chsInput {
					select {
					case ch <- inputMessage:
					case <-stop:
						return
					}
				}
				cw.chsInputMux.RUnlock()
			case <-stop:
				return
			}
		}
	}()
}

func (cw *ConnectionWorker) Input(stop <-chan struct{}) <-chan InputMessage {
	chProxy := make(chan InputMessage, chanProxyInputMessageBuffer)

	cw.chsInputMux.Lock()
	cw.chsInput = append(cw.chsInput, chProxy)
	cw.chsInputMux.Unlock()

	chout := make(chan InputMessage, chanListenInputMessageBuffer)

	go func() {
		defer close(chout)
		defer func() {
			cw.chsInputMux.Lock()
			for i := range cw.chsInput {
				if cw.chsInput[i] == chProxy {
					cw.chsInput = append(cw.chsInput[:i], cw.chsInput[i+1:]...)
					close(chProxy)
					break
				}
			}
			cw.chsInputMux.Unlock()
		}()

		for {
			select {
			case <-stop:
				return
			case <-cw.chStop:
				return
			case inputMessage := <-chProxy:
				cw.sendInputMessage(chout, inputMessage, stop, sendInputMessageTimeout)
			}
		}
	}()

	return chout
}

func (cw *ConnectionWorker) sendInputMessage(ch chan InputMessage, inputMessage InputMessage, stop <-chan struct{}, timeout time.Duration) {
	var timer = time.NewTimer(timeout)
	defer timer.Stop()
	if cap(ch) == 0 {
		select {
		case ch <- inputMessage:
		case <-cw.chStop:
		case <-stop:
		case <-timer.C:
		}
	} else {
		for {
			select {
			case ch <- inputMessage:
				return
			case <-cw.chStop:
				return
			case <-stop:
				return
			case <-timer.C:
				return
			default:
				if len(ch) == cap(ch) {
					<-ch
				}
			}
		}
	}
}

func (cw *ConnectionWorker) write(chin <-chan []byte, stop <-chan struct{}) {
	go func() {
		for {
			select {
			case data := <-chin:
				if err := cw.conn.WriteMessage(websocket.TextMessage, data); err != nil {
					// TODO: Handler error.
				}
			case <-stop:
				return
			}
		}
	}()
}

func (cw *ConnectionWorker) outputMessage(outputMessage OutputMessage) {
	select {
	case cw.chOutput <- outputMessage:
	case <-cw.chStop:
	}
}

func (cw *ConnectionWorker) encode(chin <-chan OutputMessage, stop <-chan struct{}) <-chan []byte {
	chout := make(chan []byte, chanEncodeMessageBuffer)

	go func() {
		defer close(chout)

		for {
			select {
			case message := <-chin:
				if data, err := ffjson.Marshal(message); err != nil {
					// TODO: Handler error.
				} else {
					chout <- data
				}
			case <-stop:
				return
			}
		}
	}()

	return chout
}

func (cw *ConnectionWorker) listen(chin <-chan game.Event, stop <-chan struct{}) {
	for {
		select {
		case event := <-chin:
			// TODO: Do stuff.
			cw.logger.Info(event)

			// ...

			cw.outputMessage(OutputMessage{
				Type:    OutputMessageTypeGameEvent,
				Payload: event,
			})
		case <-stop:
			return
		}
	}
}
<|endoftext|>


<commit_before>package state

import (
	"fmt"
	"os"
	"reflect"
	"testing"

	"github.com/hashicorp/consul/consul/structs"
)

func testStateStore(t *testing.T) *StateStore {
	s, err := NewStateStore(os.Stderr)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if s == nil {
		t.Fatalf("missing state store")
	}
	return s
}

func TestStateStore_EnsureNode_GetNode(t *testing.T) {
	s := testStateStore(t)

	// Fetching a non-existent node returns nil
	if node, err := s.GetNode("node1"); node != nil || err != nil {
		t.Fatalf("expected (nil, nil), got: (%#v, %#v)", node, err)
	}

	// Create a node registration request
	in := &structs.Node{
		Node:    "node1",
		Address: "1.1.1.1",
	}

	// Ensure the node is registered in the db
	if err := s.EnsureNode(1, in); err != nil {
		t.Fatalf("err: %s", err)
	}

	// Retrieve the node again
	out, err := s.GetNode("node1")
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	// Correct node was returned
	if out.Node != "node1" || out.Address != "1.1.1.1" {
		t.Fatalf("bad node returned: %#v", out)
	}

	// Indexes are set properly
	if out.CreateIndex != 1 || out.ModifyIndex != 1 {
		t.Fatalf("bad node index: %#v", out)
	}

	// Update the node registration
	in.Address = "1.1.1.2"
	if err := s.EnsureNode(2, in); err != nil {
		t.Fatalf("err: %s", err)
	}

	// Retrieve the node
	out, err = s.GetNode("node1")
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	// Node and indexes were updated
	if out.CreateIndex != 1 || out.ModifyIndex != 2 || out.Address != "1.1.1.2" {
		t.Fatalf("bad: %#v", out)
	}

	// Node upsert is idempotent
	if err := s.EnsureNode(2, in); err != nil {
		t.Fatalf("err: %s", err)
	}
	out, err = s.GetNode("node1")
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if out.Address != "1.1.1.2" || out.CreateIndex != 1 || out.ModifyIndex != 2 {
		t.Fatalf("node was modified: %#v", out)
	}
}

func TestStateStore_GetNodes(t *testing.T) {
	s := testStateStore(t)

	// Create some nodes in the state store
	nodes := []*structs.Node{
		&structs.Node{Node: "node0", Address: "1.1.1.0"},
		&structs.Node{Node: "node1", Address: "1.1.1.1"},
		&structs.Node{Node: "node2", Address: "1.1.1.2"},
	}
	for i, node := range nodes {
		if err := s.EnsureNode(uint64(i), node); err != nil {
			t.Fatalf("err: %s", err)
		}
	}

	// Retrieve the nodes
	out, err := s.Nodes()
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	// All nodes were returned
	if n := len(out); n != 3 {
		t.Fatalf("bad node count: %d", n)
	}

	// Make sure the nodes match
	for i, node := range nodes {
		if node.CreateIndex != uint64(i) || node.ModifyIndex != uint64(i) {
			t.Fatalf("bad node index: %d, %d", node.CreateIndex, node.ModifyIndex)
		}
		name := fmt.Sprintf("node%d", i)
		addr := fmt.Sprintf("1.1.1.%d", i)
		if node.Node != name || node.Address != addr {
			t.Fatalf("bad: %#v", node)
		}
	}
}

func TestStateStore_DeleteNode(t *testing.T) {
	s := testStateStore(t)

	// Create a node
	node := &structs.Node{Node: "node1", Address: "1.1.1.1"}
	if err := s.EnsureNode(1, node); err != nil {
		t.Fatalf("err: %s", err)
	}

	// The node exists
	if n, err := s.GetNode("node1"); err != nil || n == nil {
		t.Fatalf("bad: %#v (%#v)", n, err)
	}

	// Delete the node
	if err := s.DeleteNode(2, "node1"); err != nil {
		t.Fatalf("err: %s", err)
	}

	// The node is now gone and the index was updated
	if n, err := s.GetNode("node1"); err != nil || n != nil {
		t.Fatalf("bad: %#v (err: %#v)", node, err)
	}
	if idx := s.maxIndex("nodes"); idx != 2 {
		t.Fatalf("bad index: %d", idx)
	}
}

func TestStateStore_EnsureService_NodeServices(t *testing.T) {
	s := testStateStore(t)

	// Fetching services for a node with none returns nil
	if res, err := s.NodeServices("node1"); err != nil || res != nil {
		t.Fatalf("expected (nil, nil), got: (%#v, %#v)", res, err)
	}

	// Register the nodes
	for i, nr := range []*structs.Node{
		&structs.Node{Node: "node1", Address: "1.1.1.1"},
		&structs.Node{Node: "node2", Address: "1.1.1.2"},
	} {
		if err := s.EnsureNode(uint64(i), nr); err != nil {
			t.Fatalf("err: %s", err)
		}
	}

	// Create the service registration
	ns1 := &structs.NodeService{
		ID:      "service1",
		Service: "redis",
		Tags:    []string{"prod"},
		Address: "1.1.1.1",
		Port:    1111,
	}

	// Service successfully registers into the state store
	if err := s.EnsureService(10, "node1", ns1); err != nil {
		t.Fatalf("err: %s", err)
	}

	// Register a similar service against both nodes
	ns2 := *ns1
	ns2.ID = "service2"
	for _, n := range []string{"node1", "node2"} {
		if err := s.EnsureService(20, n, &ns2); err != nil {
			t.Fatalf("err: %s", err)
		}
	}

	// Register a different service on the bad node
	ns3 := *ns1
	ns3.ID = "service3"
	if err := s.EnsureService(30, "node2", &ns3); err != nil {
		t.Fatalf("err: %s", err)
	}

	// Retrieve the services
	out, err := s.NodeServices("node1")
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	// Only the services for the requested node are returned
	if out == nil || len(out.Services) != 2 {
		t.Fatalf("bad services: %#v", out)
	}

	// Results match the inserted services and have the proper indexes set
	expect1 := *ns1
	expect1.CreateIndex, expect1.ModifyIndex = 10, 10
	if svc := out.Services["service1"]; !reflect.DeepEqual(&expect1, svc) {
		t.Fatalf("bad: %#v", svc)
	}

	expect2 := ns2
	expect2.CreateIndex, expect2.ModifyIndex = 20, 20
	if svc := out.Services["service2"]; !reflect.DeepEqual(&expect2, svc) {
		t.Fatalf("bad: %#v %#v", ns2, svc)
	}

	// Lastly, ensure that the highest index was preserved.
	if out.CreateIndex != 20 || out.ModifyIndex != 20 {
		t.Fatalf("bad index: %d, %d", out.CreateIndex, out.ModifyIndex)
	}
}

func TestStateStore_DeleteNodeService(t *testing.T) {
	s := testStateStore(t)

	// Register a node
	node := &structs.Node{
		Node:    "node1",
		Address: "1.1.1.1",
	}
	if err := s.EnsureNode(1, node); err != nil {
		t.Fatalf("err: %s", err)
	}

	// Create a service
	service := &structs.NodeService{
		ID:      "service1",
		Service: "redis",
		Tags:    []string{"prod"},
		Address: "1.1.1.1",
		Port:    1111,
	}
	if err := s.EnsureService(2, "node1", service); err != nil {
		t.Fatalf("err: %s", err)
	}

	// The service exists
	ns, err := s.NodeServices("node1")
	if err != nil || ns == nil || len(ns.Services) != 1 {
		t.Fatalf("bad: %#v (err: %#v)", ns, err)
	}

	// Delete the service
	if err := s.DeleteNodeService(3, "node1", "service1"); err != nil {
		t.Fatalf("err: %s", err)
	}

	// The service doesn't exist and the index was updated
	ns, err = s.NodeServices("node1")
	if err != nil || ns == nil || len(ns.Services) != 0 {
		t.Fatalf("bad: %#v (err: %#v)", ns, err)
	}
	if idx := s.maxIndex("services"); idx != 3 {
		t.Fatalf("bad index: %d", idx)
	}
}

func TestStateStore_EnsureCheck(t *testing.T) {
	s := testStateStore(t)

	// Create a node and insert it
	node := &structs.Node{
		Node:    "node1",
		Address: "1.1.1.1",
	}
	if err := s.EnsureNode(1, node); err != nil {
		t.Fatalf("err: %s", err)
	}

	// Create a service and insert it
	service := &structs.NodeService{
		ID:      "service1",
		Service: "redis",
		Tags:    []string{"prod"},
		Address: "1.1.1.1",
		Port:    1111,
	}
	if err := s.EnsureService(2, "node1", service); err != nil {
		t.Fatalf("err: %s", err)
	}

	// Create a check associated with the node and insert it
	check := &structs.HealthCheck{
		Node:        "node1",
		CheckID:     "check1",
		Name:        "redis check",
		Status:      structs.HealthPassing,
		Notes:       "test check",
		Output:      "aaa",
		ServiceID:   "service1",
		ServiceName: "redis",
	}
	if err := s.EnsureCheck(3, check); err != nil {
		t.Fatalf("err: %s", err)
	}

	// Retrieve the check and make sure it matches
	checks, err := s.NodeChecks("node1")
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if len(checks) != 1 {
		t.Fatalf("bad number of checks: %d", len(checks))
	}
	if !reflect.DeepEqual(checks[0], check) {
		t.Fatalf("bad: %#v", checks[0])
	}
}
<commit_msg>consul/state: negative tests<commit_after>package state

import (
	"fmt"
	"os"
	"reflect"
	"testing"

	"github.com/hashicorp/consul/consul/structs"
)

func testStateStore(t *testing.T) *StateStore {
	s, err := NewStateStore(os.Stderr)
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if s == nil {
		t.Fatalf("missing state store")
	}
	return s
}

func TestStateStore_EnsureNode_GetNode(t *testing.T) {
	s := testStateStore(t)

	// Fetching a non-existent node returns nil
	if node, err := s.GetNode("node1"); node != nil || err != nil {
		t.Fatalf("expected (nil, nil), got: (%#v, %#v)", node, err)
	}

	// Create a node registration request
	in := &structs.Node{
		Node:    "node1",
		Address: "1.1.1.1",
	}

	// Ensure the node is registered in the db
	if err := s.EnsureNode(1, in); err != nil {
		t.Fatalf("err: %s", err)
	}

	// Retrieve the node again
	out, err := s.GetNode("node1")
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	// Correct node was returned
	if out.Node != "node1" || out.Address != "1.1.1.1" {
		t.Fatalf("bad node returned: %#v", out)
	}

	// Indexes are set properly
	if out.CreateIndex != 1 || out.ModifyIndex != 1 {
		t.Fatalf("bad node index: %#v", out)
	}

	// Update the node registration
	in.Address = "1.1.1.2"
	if err := s.EnsureNode(2, in); err != nil {
		t.Fatalf("err: %s", err)
	}

	// Retrieve the node
	out, err = s.GetNode("node1")
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	// Node and indexes were updated
	if out.CreateIndex != 1 || out.ModifyIndex != 2 || out.Address != "1.1.1.2" {
		t.Fatalf("bad: %#v", out)
	}

	// Node upsert is idempotent
	if err := s.EnsureNode(2, in); err != nil {
		t.Fatalf("err: %s", err)
	}
	out, err = s.GetNode("node1")
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if out.Address != "1.1.1.2" || out.CreateIndex != 1 || out.ModifyIndex != 2 {
		t.Fatalf("node was modified: %#v", out)
	}
}

func TestStateStore_GetNodes(t *testing.T) {
	s := testStateStore(t)

	// Create some nodes in the state store
	nodes := []*structs.Node{
		&structs.Node{Node: "node0", Address: "1.1.1.0"},
		&structs.Node{Node: "node1", Address: "1.1.1.1"},
		&structs.Node{Node: "node2", Address: "1.1.1.2"},
	}
	for i, node := range nodes {
		if err := s.EnsureNode(uint64(i), node); err != nil {
			t.Fatalf("err: %s", err)
		}
	}

	// Retrieve the nodes
	out, err := s.Nodes()
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	// All nodes were returned
	if n := len(out); n != 3 {
		t.Fatalf("bad node count: %d", n)
	}

	// Make sure the nodes match
	for i, node := range nodes {
		if node.CreateIndex != uint64(i) || node.ModifyIndex != uint64(i) {
			t.Fatalf("bad node index: %d, %d", node.CreateIndex, node.ModifyIndex)
		}
		name := fmt.Sprintf("node%d", i)
		addr := fmt.Sprintf("1.1.1.%d", i)
		if node.Node != name || node.Address != addr {
			t.Fatalf("bad: %#v", node)
		}
	}
}

func TestStateStore_DeleteNode(t *testing.T) {
	s := testStateStore(t)

	// Create a node
	node := &structs.Node{Node: "node1", Address: "1.1.1.1"}
	if err := s.EnsureNode(1, node); err != nil {
		t.Fatalf("err: %s", err)
	}

	// The node exists
	if n, err := s.GetNode("node1"); err != nil || n == nil {
		t.Fatalf("bad: %#v (%#v)", n, err)
	}

	// Delete the node
	if err := s.DeleteNode(2, "node1"); err != nil {
		t.Fatalf("err: %s", err)
	}

	// The node is now gone and the index was updated
	if n, err := s.GetNode("node1"); err != nil || n != nil {
		t.Fatalf("bad: %#v (err: %#v)", node, err)
	}
	if idx := s.maxIndex("nodes"); idx != 2 {
		t.Fatalf("bad index: %d", idx)
	}
}

func TestStateStore_EnsureService_NodeServices(t *testing.T) {
	s := testStateStore(t)

	// Fetching services for a node with none returns nil
	if res, err := s.NodeServices("node1"); err != nil || res != nil {
		t.Fatalf("expected (nil, nil), got: (%#v, %#v)", res, err)
	}

	// Register the nodes
	for i, nr := range []*structs.Node{
		&structs.Node{Node: "node1", Address: "1.1.1.1"},
		&structs.Node{Node: "node2", Address: "1.1.1.2"},
	} {
		if err := s.EnsureNode(uint64(i), nr); err != nil {
			t.Fatalf("err: %s", err)
		}
	}

	// Create the service registration
	ns1 := &structs.NodeService{
		ID:      "service1",
		Service: "redis",
		Tags:    []string{"prod"},
		Address: "1.1.1.1",
		Port:    1111,
	}

	// Service successfully registers into the state store
	if err := s.EnsureService(10, "node1", ns1); err != nil {
		t.Fatalf("err: %s", err)
	}

	// Register a similar service against both nodes
	ns2 := *ns1
	ns2.ID = "service2"
	for _, n := range []string{"node1", "node2"} {
		if err := s.EnsureService(20, n, &ns2); err != nil {
			t.Fatalf("err: %s", err)
		}
	}

	// Register a different service on the bad node
	ns3 := *ns1
	ns3.ID = "service3"
	if err := s.EnsureService(30, "node2", &ns3); err != nil {
		t.Fatalf("err: %s", err)
	}

	// Retrieve the services
	out, err := s.NodeServices("node1")
	if err != nil {
		t.Fatalf("err: %s", err)
	}

	// Only the services for the requested node are returned
	if out == nil || len(out.Services) != 2 {
		t.Fatalf("bad services: %#v", out)
	}

	// Results match the inserted services and have the proper indexes set
	expect1 := *ns1
	expect1.CreateIndex, expect1.ModifyIndex = 10, 10
	if svc := out.Services["service1"]; !reflect.DeepEqual(&expect1, svc) {
		t.Fatalf("bad: %#v", svc)
	}

	expect2 := ns2
	expect2.CreateIndex, expect2.ModifyIndex = 20, 20
	if svc := out.Services["service2"]; !reflect.DeepEqual(&expect2, svc) {
		t.Fatalf("bad: %#v %#v", ns2, svc)
	}

	// Lastly, ensure that the highest index was preserved.
	if out.CreateIndex != 20 || out.ModifyIndex != 20 {
		t.Fatalf("bad index: %d, %d", out.CreateIndex, out.ModifyIndex)
	}
}

func TestStateStore_DeleteNodeService(t *testing.T) {
	s := testStateStore(t)

	// Register a node
	node := &structs.Node{
		Node:    "node1",
		Address: "1.1.1.1",
	}
	if err := s.EnsureNode(1, node); err != nil {
		t.Fatalf("err: %s", err)
	}

	// Create a service
	service := &structs.NodeService{
		ID:      "service1",
		Service: "redis",
		Tags:    []string{"prod"},
		Address: "1.1.1.1",
		Port:    1111,
	}
	if err := s.EnsureService(2, "node1", service); err != nil {
		t.Fatalf("err: %s", err)
	}

	// The service exists
	ns, err := s.NodeServices("node1")
	if err != nil || ns == nil || len(ns.Services) != 1 {
		t.Fatalf("bad: %#v (err: %#v)", ns, err)
	}

	// Delete the service
	if err := s.DeleteNodeService(3, "node1", "service1"); err != nil {
		t.Fatalf("err: %s", err)
	}

	// The service doesn't exist and the index was updated
	ns, err = s.NodeServices("node1")
	if err != nil || ns == nil || len(ns.Services) != 0 {
		t.Fatalf("bad: %#v (err: %#v)", ns, err)
	}
	if idx := s.maxIndex("services"); idx != 3 {
		t.Fatalf("bad index: %d", idx)
	}
}

func TestStateStore_EnsureCheck(t *testing.T) {
	s := testStateStore(t)

	// Create a check associated with the node
	check := &structs.HealthCheck{
		Node:        "node1",
		CheckID:     "check1",
		Name:        "redis check",
		Status:      structs.HealthPassing,
		Notes:       "test check",
		Output:      "aaa",
		ServiceID:   "service1",
		ServiceName: "redis",
	}

	// Creating a check without a node returns error
	if err := s.EnsureCheck(1, check); err != ErrMissingNode {
		t.Fatalf("expected %#v, got: %#v", ErrMissingNode, err)
	}

	// Create a node and insert it
	node := &structs.Node{
		Node:    "node1",
		Address: "1.1.1.1",
	}
	if err := s.EnsureNode(1, node); err != nil {
		t.Fatalf("err: %s", err)
	}

	// Creating a check with a bad services returns error
	if err := s.EnsureCheck(1, check); err != ErrMissingService {
		t.Fatalf("expected: %#v, got: %#v", ErrMissingService, err)
	}

	// Create a service and insert it
	service := &structs.NodeService{
		ID:      "service1",
		Service: "redis",
		Tags:    []string{"prod"},
		Address: "1.1.1.1",
		Port:    1111,
	}
	if err := s.EnsureService(2, "node1", service); err != nil {
		t.Fatalf("err: %s", err)
	}

	// Inserting the check with the prerequisites succeeds
	if err := s.EnsureCheck(3, check); err != nil {
		t.Fatalf("err: %s", err)
	}

	// Retrieve the check and make sure it matches
	checks, err := s.NodeChecks("node1")
	if err != nil {
		t.Fatalf("err: %s", err)
	}
	if len(checks) != 1 {
		t.Fatalf("bad number of checks: %d", len(checks))
	}
	if !reflect.DeepEqual(checks[0], check) {
		t.Fatalf("bad: %#v", checks[0])
	}
}
<|endoftext|>


<commit_before>// Copyright 2017 CoreOS, 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 blackbox

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"syscall"
	"testing"
	"time"

	config "github.com/coreos/ignition/config/v2_3_experimental"
	"github.com/coreos/ignition/tests/register"
	"github.com/coreos/ignition/tests/types"

	// Register the tests
	_ "github.com/coreos/ignition/tests/registry"

	// UUID generation tool
	"github.com/pborman/uuid"
)

var (
	// testTimeout controls how long a given test is allowed to run before being
	// cancelled.
	testTimeout = time.Second * 60
	// somewhat of an abuse of contexts but go's got our hands tied
	killContext = context.TODO()
)

func TestMain(m *testing.M) {
	httpServer := &HTTPServer{}
	httpServer.Start()
	tftpServer := &TFTPServer{}
	tftpServer.Start()

	interruptChan := make(chan os.Signal, 3)
	signal.Notify(interruptChan, os.Interrupt, syscall.SIGTERM)
	tmp, killCancel := context.WithCancel(context.Background())
	killContext = tmp
	go func() {
		for {
			sig := <-interruptChan
			switch sig {
			case os.Interrupt, syscall.SIGTERM:
				killCancel()
			}
		}
	}()

	os.Exit(m.Run())
}

func TestIgnitionBlackBox(t *testing.T) {
	for _, test := range register.Tests[register.PositiveTest] {
		test := test
		t.Run(test.Name, func(t *testing.T) {
			if killContext.Err() != nil {
				t.SkipNow()
			}
			t.Parallel()
			err := outer(t, test, false)
			if err != nil {
				t.Error(err)
			}
		})
	}
}

func TestIgnitionBlackBoxNegative(t *testing.T) {
	for _, test := range register.Tests[register.NegativeTest] {
		test := test
		t.Run(test.Name, func(t *testing.T) {
			if killContext.Err() != nil {
				t.SkipNow()
			}
			t.Parallel()
			err := outer(t, test, true)
			if err != nil {
				t.Error(err)
			}
		})
	}
}

func outer(t *testing.T, test types.Test, negativeTests bool) error {
	t.Log(test.Name)

	err := test.ReplaceAllUUIDVars()
	if err != nil {
		return err
	}

	ctx, cancelFunc := context.WithDeadline(killContext, time.Now().Add(testTimeout))
	defer cancelFunc()

	tmpDirectory, err := ioutil.TempDir("/var/tmp", "ignition-blackbox-")
	if err != nil {
		return fmt.Errorf("failed to create a temp dir: %v", err)
	}
	defer os.RemoveAll(tmpDirectory)
	// the tmpDirectory must be 0755 or the tests will fail as the tool will
	// not have permissions to perform some actions in the mounted folders
	err = os.Chmod(tmpDirectory, 0755)
	if err != nil {
		return fmt.Errorf("failed to change mode of temp dir: %v", err)
	}

	oemLookasideDir := filepath.Join(tmpDirectory, "oem-lookaside")
	systemConfigDir := filepath.Join(tmpDirectory, "system")
	var rootPartition *types.Partition

	// Setup
	err = createFilesFromSlice(oemLookasideDir, test.OEMLookasideFiles)
	// Defer before the error handling because the createFilesFromSlice function
	// can fail after partially-creating things
	defer os.RemoveAll(oemLookasideDir)
	if err != nil {
		return err
	}
	err = createFilesFromSlice(systemConfigDir, test.SystemDirFiles)
	defer os.RemoveAll(systemConfigDir)
	if err != nil {
		return err
	}
	for i, disk := range test.In {
		// Set image file path
		disk.ImageFile = filepath.Join(tmpDirectory, fmt.Sprintf("hd%d", i))
		test.Out[i].ImageFile = disk.ImageFile

		// There may be more partitions created by Ignition, so look at the
		// expected output instead of the input to determine image size
		imageSize := test.Out[i].CalculateImageSize()
		if inSize := disk.CalculateImageSize(); inSize > imageSize {
			imageSize = inSize
		}

		// Finish data setup
		for _, part := range disk.Partitions {
			if part.GUID == "" {
				part.GUID = uuid.New()
				if err != nil {
					return err
				}
			}
			err := updateTypeGUID(part)
			if err != nil {
				return err
			}
		}

		disk.SetOffsets()
		for _, part := range test.Out[i].Partitions {
			err := updateTypeGUID(part)
			if err != nil {
				return err
			}
		}
		test.Out[i].SetOffsets()

		if err = setupDisk(ctx, &disk, i, imageSize, tmpDirectory); err != nil {
			return err
		}

		// Creation
		// Move value into the local scope, because disk.ImageFile and device
		// will change by the time this runs
		imageFile := disk.ImageFile
		device := disk.Device
		defer func() {
			if err := os.Remove(imageFile); err != nil {
				t.Errorf("couldn't remove %s: %v", imageFile, err)
			}
		}()
		defer func() {
			if err := destroyDevice(device); err != nil {
				t.Errorf("couldn't destroy device: %v", err)
			}
		}()

		test.Out[i].Device = disk.Device

		err = createFilesForPartitions(ctx, disk.Partitions)
		if err != nil {
			return err
		}

		// Mount device name substitution
		for _, d := range test.MntDevices {
			device := pickPartition(disk.Device, disk.Partitions, d.Label)
			// The device may not be on this disk, if it's not found here let's
			// assume we'll find it on another one and keep going
			if device != "" {
				test.Config = strings.Replace(test.Config, d.Substitution, device, -1)
			}
		}

		// Replace any instance of $disk<num> with the actual loop device
		// that got assigned to it
		test.Config = strings.Replace(test.Config, fmt.Sprintf("$disk%d", i), disk.Device, -1)

		if rootPartition == nil {
			rootPartition = getRootPartition(disk.Partitions)
		}
	}
	if rootPartition == nil {
		return fmt.Errorf("ROOT filesystem not found! A partition labeled ROOT is requred")
	}

	if strings.Contains(test.Config, "passwd") {
		if err := prepareRootPartitionForPasswd(ctx, rootPartition); err != nil {
			return err
		}
	}

	// Validation and cleanup deferral
	for i, disk := range test.Out {
		// Update out structure with mount points & devices
		setExpectedPartitionsDrive(test.In[i].Partitions, disk.Partitions)
	}

	// Let's make sure that all of the devices we needed to substitute names in
	// for were found
	for _, d := range test.MntDevices {
		if strings.Contains(test.Config, d.Substitution) {
			return fmt.Errorf("Didn't find a drive with label: %s", d.Substitution)
		}
	}

	// If we're not expecting the config to be bad, make sure it passes
	// validation.
	if !test.ConfigShouldBeBad {
		_, rpt, err := config.Parse([]byte(test.Config))
		if rpt.IsFatal() {
			return fmt.Errorf("test has bad config: %s", rpt.String())
		}
		if err != nil {
			return fmt.Errorf("error parsing config: %v", err)
		}
	}

	// Ignition config
	if err := ioutil.WriteFile(filepath.Join(tmpDirectory, "config.ign"), []byte(test.Config), 0666); err != nil {
		return fmt.Errorf("error writing config: %v", err)
	}

	// Ignition
	appendEnv := []string{
		"IGNITION_OEM_DEVICE=" + test.In[0].Partitions.GetPartition("OEM").Device,
		"IGNITION_OEM_LOOKASIDE_DIR=" + oemLookasideDir,
		"IGNITION_SYSTEM_CONFIG_DIR=" + systemConfigDir,
	}
	disksErr := runIgnition(t, ctx, "disks", rootPartition.MountPath, tmpDirectory, appendEnv)
	if !negativeTests && disksErr != nil {
		return disksErr
	}

	if err := mountPartition(ctx, rootPartition); err != nil {
		return err
	}
	filesErr := runIgnition(t, ctx, "files", rootPartition.MountPath, tmpDirectory, appendEnv)
	if err := umountPartition(rootPartition); err != nil {
		return err
	}
	if !negativeTests && filesErr != nil {
		return filesErr
	}
	if negativeTests && disksErr == nil && filesErr == nil {
		return fmt.Errorf("Expected failure and ignition succeeded")
	}

	for _, disk := range test.Out {
		if !negativeTests {
			err = validateDisk(t, disk)
			if err != nil {
				return err
			}
			err = validateFilesystems(t, disk.Partitions)
			if err != nil {
				return err
			}
			validateFilesDirectoriesAndLinks(t, ctx, disk.Partitions)
		}
	}
	return nil
}
<commit_msg>tests: don't run files stage if disks failed<commit_after>// Copyright 2017 CoreOS, 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 blackbox

import (
	"context"
	"fmt"
	"io/ioutil"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"syscall"
	"testing"
	"time"

	config "github.com/coreos/ignition/config/v2_3_experimental"
	"github.com/coreos/ignition/tests/register"
	"github.com/coreos/ignition/tests/types"

	// Register the tests
	_ "github.com/coreos/ignition/tests/registry"

	// UUID generation tool
	"github.com/pborman/uuid"
)

var (
	// testTimeout controls how long a given test is allowed to run before being
	// cancelled.
	testTimeout = time.Second * 60
	// somewhat of an abuse of contexts but go's got our hands tied
	killContext = context.TODO()
)

func TestMain(m *testing.M) {
	httpServer := &HTTPServer{}
	httpServer.Start()
	tftpServer := &TFTPServer{}
	tftpServer.Start()

	interruptChan := make(chan os.Signal, 3)
	signal.Notify(interruptChan, os.Interrupt, syscall.SIGTERM)
	tmp, killCancel := context.WithCancel(context.Background())
	killContext = tmp
	go func() {
		for {
			sig := <-interruptChan
			switch sig {
			case os.Interrupt, syscall.SIGTERM:
				killCancel()
			}
		}
	}()

	os.Exit(m.Run())
}

func TestIgnitionBlackBox(t *testing.T) {
	for _, test := range register.Tests[register.PositiveTest] {
		test := test
		t.Run(test.Name, func(t *testing.T) {
			if killContext.Err() != nil {
				t.SkipNow()
			}
			t.Parallel()
			err := outer(t, test, false)
			if err != nil {
				t.Error(err)
			}
		})
	}
}

func TestIgnitionBlackBoxNegative(t *testing.T) {
	for _, test := range register.Tests[register.NegativeTest] {
		test := test
		t.Run(test.Name, func(t *testing.T) {
			if killContext.Err() != nil {
				t.SkipNow()
			}
			t.Parallel()
			err := outer(t, test, true)
			if err != nil {
				t.Error(err)
			}
		})
	}
}

func outer(t *testing.T, test types.Test, negativeTests bool) error {
	t.Log(test.Name)

	err := test.ReplaceAllUUIDVars()
	if err != nil {
		return err
	}

	ctx, cancelFunc := context.WithDeadline(killContext, time.Now().Add(testTimeout))
	defer cancelFunc()

	tmpDirectory, err := ioutil.TempDir("/var/tmp", "ignition-blackbox-")
	if err != nil {
		return fmt.Errorf("failed to create a temp dir: %v", err)
	}
	defer os.RemoveAll(tmpDirectory)
	// the tmpDirectory must be 0755 or the tests will fail as the tool will
	// not have permissions to perform some actions in the mounted folders
	err = os.Chmod(tmpDirectory, 0755)
	if err != nil {
		return fmt.Errorf("failed to change mode of temp dir: %v", err)
	}

	oemLookasideDir := filepath.Join(tmpDirectory, "oem-lookaside")
	systemConfigDir := filepath.Join(tmpDirectory, "system")
	var rootPartition *types.Partition

	// Setup
	err = createFilesFromSlice(oemLookasideDir, test.OEMLookasideFiles)
	// Defer before the error handling because the createFilesFromSlice function
	// can fail after partially-creating things
	defer os.RemoveAll(oemLookasideDir)
	if err != nil {
		return err
	}
	err = createFilesFromSlice(systemConfigDir, test.SystemDirFiles)
	defer os.RemoveAll(systemConfigDir)
	if err != nil {
		return err
	}
	for i, disk := range test.In {
		// Set image file path
		disk.ImageFile = filepath.Join(tmpDirectory, fmt.Sprintf("hd%d", i))
		test.Out[i].ImageFile = disk.ImageFile

		// There may be more partitions created by Ignition, so look at the
		// expected output instead of the input to determine image size
		imageSize := test.Out[i].CalculateImageSize()
		if inSize := disk.CalculateImageSize(); inSize > imageSize {
			imageSize = inSize
		}

		// Finish data setup
		for _, part := range disk.Partitions {
			if part.GUID == "" {
				part.GUID = uuid.New()
				if err != nil {
					return err
				}
			}
			err := updateTypeGUID(part)
			if err != nil {
				return err
			}
		}

		disk.SetOffsets()
		for _, part := range test.Out[i].Partitions {
			err := updateTypeGUID(part)
			if err != nil {
				return err
			}
		}
		test.Out[i].SetOffsets()

		if err = setupDisk(ctx, &disk, i, imageSize, tmpDirectory); err != nil {
			return err
		}

		// Creation
		// Move value into the local scope, because disk.ImageFile and device
		// will change by the time this runs
		imageFile := disk.ImageFile
		device := disk.Device
		defer func() {
			if err := os.Remove(imageFile); err != nil {
				t.Errorf("couldn't remove %s: %v", imageFile, err)
			}
		}()
		defer func() {
			if err := destroyDevice(device); err != nil {
				t.Errorf("couldn't destroy device: %v", err)
			}
		}()

		test.Out[i].Device = disk.Device

		err = createFilesForPartitions(ctx, disk.Partitions)
		if err != nil {
			return err
		}

		// Mount device name substitution
		for _, d := range test.MntDevices {
			device := pickPartition(disk.Device, disk.Partitions, d.Label)
			// The device may not be on this disk, if it's not found here let's
			// assume we'll find it on another one and keep going
			if device != "" {
				test.Config = strings.Replace(test.Config, d.Substitution, device, -1)
			}
		}

		// Replace any instance of $disk<num> with the actual loop device
		// that got assigned to it
		test.Config = strings.Replace(test.Config, fmt.Sprintf("$disk%d", i), disk.Device, -1)

		if rootPartition == nil {
			rootPartition = getRootPartition(disk.Partitions)
		}
	}
	if rootPartition == nil {
		return fmt.Errorf("ROOT filesystem not found! A partition labeled ROOT is requred")
	}

	if strings.Contains(test.Config, "passwd") {
		if err := prepareRootPartitionForPasswd(ctx, rootPartition); err != nil {
			return err
		}
	}

	// Validation and cleanup deferral
	for i, disk := range test.Out {
		// Update out structure with mount points & devices
		setExpectedPartitionsDrive(test.In[i].Partitions, disk.Partitions)
	}

	// Let's make sure that all of the devices we needed to substitute names in
	// for were found
	for _, d := range test.MntDevices {
		if strings.Contains(test.Config, d.Substitution) {
			return fmt.Errorf("Didn't find a drive with label: %s", d.Substitution)
		}
	}

	// If we're not expecting the config to be bad, make sure it passes
	// validation.
	if !test.ConfigShouldBeBad {
		_, rpt, err := config.Parse([]byte(test.Config))
		if rpt.IsFatal() {
			return fmt.Errorf("test has bad config: %s", rpt.String())
		}
		if err != nil {
			return fmt.Errorf("error parsing config: %v", err)
		}
	}

	// Ignition config
	if err := ioutil.WriteFile(filepath.Join(tmpDirectory, "config.ign"), []byte(test.Config), 0666); err != nil {
		return fmt.Errorf("error writing config: %v", err)
	}

	// Ignition
	appendEnv := []string{
		"IGNITION_OEM_DEVICE=" + test.In[0].Partitions.GetPartition("OEM").Device,
		"IGNITION_OEM_LOOKASIDE_DIR=" + oemLookasideDir,
		"IGNITION_SYSTEM_CONFIG_DIR=" + systemConfigDir,
	}
	disksErr := runIgnition(t, ctx, "disks", rootPartition.MountPath, tmpDirectory, appendEnv)
	if !negativeTests && disksErr != nil {
		return disksErr
	}

	var filesErr error
	if disksErr == nil {
		// Even if we're running negative tests, we shouldn't run the files stage if the disks stage
		// failed. This is how Ignition was designed to be used.
		if err := mountPartition(ctx, rootPartition); err != nil {
			return err
		}
		filesErr = runIgnition(t, ctx, "files", rootPartition.MountPath, tmpDirectory, appendEnv)
		if err := umountPartition(rootPartition); err != nil {
			return err
		}
	}

	if !negativeTests && filesErr != nil {
		return filesErr
	}
	if negativeTests && disksErr == nil && filesErr == nil {
		return fmt.Errorf("Expected failure and ignition succeeded")
	}

	for _, disk := range test.Out {
		if !negativeTests {
			err = validateDisk(t, disk)
			if err != nil {
				return err
			}
			err = validateFilesystems(t, disk.Partitions)
			if err != nil {
				return err
			}
			validateFilesDirectoriesAndLinks(t, ctx, disk.Partitions)
		}
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2020 Google LLC
//
// 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 android managed device attestation inegation with Android's
// SafetyNet API.
package android

import (
	"context"
	"crypto/rsa"
	"crypto/x509"
	"fmt"
	"runtime/trace"
	"time"

	"github.com/google/exposure-notifications-server/internal/authorizedapp/model"
	"github.com/google/exposure-notifications-server/internal/base64util"
	"github.com/google/exposure-notifications-server/internal/logging"

	"github.com/dgrijalva/jwt-go"
)

// The VerifyOpts determine the fields that are required for verification
type VerifyOpts struct {
	AppPkgName      string
	APKDigest       []string
	Nonce           string
	CTSProfileMatch bool
	BasicIntegrity  bool
	MinValidTime    time.Time
	MaxValidTime    time.Time
}

// ValidateAttestation validates the the SafetyNet Attestation from this device
// matches the properties that we expect based on the AuthorizedApp entry. See
// https://developer.android.com/training/safetynet/attestation#use-response-server
// for details on the format of these attestations.
func ValidateAttestation(ctx context.Context, attestation string, opts *VerifyOpts) error {
	defer trace.StartRegion(ctx, "ValidateAttestation").End()
	logger := logging.FromContext(ctx)

	claims, err := verifyAttestation(ctx, attestation)
	if err != nil {
		return fmt.Errorf("verifyAttestation: %w", err)
	}

	// Validate the nonce.
	if opts.Nonce == "" {
		return fmt.Errorf("missing nonce")
	}
	nonceClaimB64, ok := claims["nonce"].(string)
	if !ok {
		return fmt.Errorf("invalid nonce claim, not a string")
	}
	nonceClaimBytes, err := base64util.DecodeString(nonceClaimB64)
	if err != nil {
		return fmt.Errorf("unable to decode nonce claim data: %w", err)
	}
	nonceClaim := string(nonceClaimBytes)
	nonceCalculated := opts.Nonce
	if nonceCalculated != nonceClaim {
		return fmt.Errorf("nonce mismatch: expected %v got %v", nonceCalculated, nonceClaim)
	}

	// Validate time interval.
	if opts.MinValidTime.IsZero() || opts.MaxValidTime.IsZero() {
		return fmt.Errorf("missing timestamp bounds for attestation")
	}
	issMsF, ok := claims["timestampMs"].(float64)
	if !ok {
		return fmt.Errorf("timestampMs is not a readable value: %v", claims["timestampMs"])
	}
	issueTime := time.Unix(int64(issMsF/1000), 0)

	if opts.MinValidTime.Unix() > issueTime.Unix() {
		return fmt.Errorf("attestation is too old, must be newer than %v, was %v", opts.MinValidTime.Unix(), issueTime.Unix())
	}
	if opts.MaxValidTime.Unix() < issueTime.Unix() {
		return fmt.Errorf("attestation is in the future, must be older than %v, was %v", opts.MaxValidTime.Unix(), issueTime.Unix())
	}

	// The apkCertificateDigestSha256 is an array with a single entry.
	// https://developer.android.com/training/safetynet/attestation#use-response-server
	digestArr := claims["apkCertificateDigestSha256"].([]interface{})
	claimApkDigest := ""
	if len(digestArr) >= 1 {
		claimApkDigest = digestArr[0].(string)
	} else {
		logger.Warnf("attestation didn't contain apkCertificateDigestSha256")
	}

	match := false
	for _, digest := range opts.APKDigest {
		if digest != claimApkDigest {
			match = true
			break
		}
	}
	if !match {
		return fmt.Errorf("attestation apkCertificateDigestSha256 value does not match configuration, got %v", claimApkDigest)
	}

	// Integrity checks.
	if opts.CTSProfileMatch {
		ctsProfileMatch, ok := claims["ctsProfileMatch"].(bool)
		if !ok {
			return fmt.Errorf("attestation value of ctsProfileMatch is not a valid bool, %v", claims["ctsProfileMatch"])
		}
		if !ctsProfileMatch {
			return fmt.Errorf("ctsProfileMatch is false when true is required")
		}
	} else {
		logger.Warnf("Verify attestation is not checking ctsProfileMatch")
	}

	if opts.BasicIntegrity {
		basicIntegrity, ok := claims["basicIntegrity"].(bool)
		if !ok {
			return fmt.Errorf("attestation value of basicIntegrity is not a valid bool, %v", claims["basicIntegrity"])
		}
		if !basicIntegrity {
			return fmt.Errorf("basicIntegrity is false when true is required")
		}
	}

	return nil
}

// VerifyOptsFor returns the Android SafetyNet verification options to be used
// based on the AuthorizedApp configuration, request time, and nonce.
func VerifyOptsFor(c *model.AuthorizedApp, from time.Time, nonce string) *VerifyOpts {
	digests := make([]string, len(c.SafetyNetApkDigestSHA256))
	copy(digests, c.SafetyNetApkDigestSHA256)
	rtn := &VerifyOpts{
		AppPkgName:      c.AppPackageName,
		BasicIntegrity:  c.SafetyNetBasicIntegrity,
		CTSProfileMatch: c.SafetyNetCTSProfileMatch,
		APKDigest:       digests,
		Nonce:           nonce,
	}

	// Calculate the valid time window based on now + config options.
	if c.SafetyNetPastTime > 0 {
		minTime := from.Add(-c.SafetyNetPastTime)
		rtn.MinValidTime = minTime
	}
	if c.SafetyNetFutureTime > 0 {
		maxTime := from.Add(c.SafetyNetFutureTime)
		rtn.MaxValidTime = maxTime
	}

	return rtn
}

// The keyFunc is based on the Android sample code
// https://github.com/googlesamples/android-play-safetynet/blob/d7513a54e2f28c0dcd7f8d8d0fa03adb5d87b91a/server/java/src/main/java/OfflineVerify.java
func keyFunc(ctx context.Context, tok *jwt.Token) (interface{}, error) {
	x5c, ok := tok.Header["x5c"].([]interface{})
	if !ok || len(x5c) == 0 {
		return nil, fmt.Errorf("attestation is missing certificate")
	}

	// Verify the singature of the JWS and retrieve the signature and certificates.
	x509certs := make([]*x509.Certificate, len(x5c))
	for i, certStr := range x5c {
		if certStr == "" {
			return nil, fmt.Errorf("certificate is empty")
		}
		certData, err := base64util.DecodeString(certStr.(string))
		if err != nil {
			return nil, fmt.Errorf("invalid certificate encoding: %w", err)
		}
		x509certs[i], err = x509.ParseCertificate(certData)
		if err != nil {
			return nil, fmt.Errorf("invalid certificate: %w", err)
		}
	}

	pool := x509.NewCertPool()
	for _, cert := range x509certs {
		pool.AddCert(cert)
	}
	opts := x509.VerifyOptions{
		DNSName:       "attest.android.com", // required hostname for valid attestation.
		Intermediates: pool,
	}

	// Verify the first certificate, with all added as allowed intermediates.
	_, err := x509certs[0].Verify(opts)
	if err != nil {
		return nil, fmt.Errorf("invalid certificate chain: %w", err)
	}

	// extract the public key for verification.
	if rsaKey, ok := x509certs[0].PublicKey.(*rsa.PublicKey); ok {
		return rsaKey, nil
	}
	return nil, fmt.Errorf("invalid certificate, unable to extract public key")
}

// verifyAttestation extracts and verifies the signature and claims on the
// attestation. It does NOT validate the attestation, only the signature.
func verifyAttestation(ctx context.Context, signedAttestation string) (jwt.MapClaims, error) {
	defer trace.StartRegion(ctx, "verifyAttestation").End()
	// jwt.Parse also validates the signature after extracting
	// the key via the keyFunc, which validates the certificate chain.
	token, err := jwt.Parse(signedAttestation,
		func(tok *jwt.Token) (interface{}, error) {
			return keyFunc(ctx, tok)
		})

	if err != nil {
		return nil, fmt.Errorf("jwt.Parse: %w", err)
	}
	if !token.Valid {
		return nil, fmt.Errorf("invalid JWS attestation")
	}

	claims, ok := token.Claims.(jwt.MapClaims)
	if !ok {
		return nil, fmt.Errorf("claims are of wrong type")
	}
	return claims, nil
}
<commit_msg>Fix typo (#417)<commit_after>// Copyright 2020 Google LLC
//
// 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 android managed device attestation inegation with Android's
// SafetyNet API.
package android

import (
	"context"
	"crypto/rsa"
	"crypto/x509"
	"fmt"
	"runtime/trace"
	"time"

	"github.com/google/exposure-notifications-server/internal/authorizedapp/model"
	"github.com/google/exposure-notifications-server/internal/base64util"
	"github.com/google/exposure-notifications-server/internal/logging"

	"github.com/dgrijalva/jwt-go"
)

// The VerifyOpts determine the fields that are required for verification
type VerifyOpts struct {
	AppPkgName      string
	APKDigest       []string
	Nonce           string
	CTSProfileMatch bool
	BasicIntegrity  bool
	MinValidTime    time.Time
	MaxValidTime    time.Time
}

// ValidateAttestation validates the the SafetyNet Attestation from this device
// matches the properties that we expect based on the AuthorizedApp entry. See
// https://developer.android.com/training/safetynet/attestation#use-response-server
// for details on the format of these attestations.
func ValidateAttestation(ctx context.Context, attestation string, opts *VerifyOpts) error {
	defer trace.StartRegion(ctx, "ValidateAttestation").End()
	logger := logging.FromContext(ctx)

	claims, err := verifyAttestation(ctx, attestation)
	if err != nil {
		return fmt.Errorf("verifyAttestation: %w", err)
	}

	// Validate the nonce.
	if opts.Nonce == "" {
		return fmt.Errorf("missing nonce")
	}
	nonceClaimB64, ok := claims["nonce"].(string)
	if !ok {
		return fmt.Errorf("invalid nonce claim, not a string")
	}
	nonceClaimBytes, err := base64util.DecodeString(nonceClaimB64)
	if err != nil {
		return fmt.Errorf("unable to decode nonce claim data: %w", err)
	}
	nonceClaim := string(nonceClaimBytes)
	nonceCalculated := opts.Nonce
	if nonceCalculated != nonceClaim {
		return fmt.Errorf("nonce mismatch: expected %v got %v", nonceCalculated, nonceClaim)
	}

	// Validate time interval.
	if opts.MinValidTime.IsZero() || opts.MaxValidTime.IsZero() {
		return fmt.Errorf("missing timestamp bounds for attestation")
	}
	issMsF, ok := claims["timestampMs"].(float64)
	if !ok {
		return fmt.Errorf("timestampMs is not a readable value: %v", claims["timestampMs"])
	}
	issueTime := time.Unix(int64(issMsF/1000), 0)

	if opts.MinValidTime.Unix() > issueTime.Unix() {
		return fmt.Errorf("attestation is too old, must be newer than %v, was %v", opts.MinValidTime.Unix(), issueTime.Unix())
	}
	if opts.MaxValidTime.Unix() < issueTime.Unix() {
		return fmt.Errorf("attestation is in the future, must be older than %v, was %v", opts.MaxValidTime.Unix(), issueTime.Unix())
	}

	// The apkCertificateDigestSha256 is an array with a single entry.
	// https://developer.android.com/training/safetynet/attestation#use-response-server
	digestArr := claims["apkCertificateDigestSha256"].([]interface{})
	claimApkDigest := ""
	if len(digestArr) >= 1 {
		claimApkDigest = digestArr[0].(string)
	} else {
		logger.Warnf("attestation didn't contain apkCertificateDigestSha256")
	}

	match := false
	for _, digest := range opts.APKDigest {
		if digest != claimApkDigest {
			match = true
			break
		}
	}
	if !match {
		return fmt.Errorf("attestation apkCertificateDigestSha256 value does not match configuration, got %v", claimApkDigest)
	}

	// Integrity checks.
	if opts.CTSProfileMatch {
		ctsProfileMatch, ok := claims["ctsProfileMatch"].(bool)
		if !ok {
			return fmt.Errorf("attestation value of ctsProfileMatch is not a valid bool, %v", claims["ctsProfileMatch"])
		}
		if !ctsProfileMatch {
			return fmt.Errorf("ctsProfileMatch is false when true is required")
		}
	} else {
		logger.Warnf("Verify attestation is not checking ctsProfileMatch")
	}

	if opts.BasicIntegrity {
		basicIntegrity, ok := claims["basicIntegrity"].(bool)
		if !ok {
			return fmt.Errorf("attestation value of basicIntegrity is not a valid bool, %v", claims["basicIntegrity"])
		}
		if !basicIntegrity {
			return fmt.Errorf("basicIntegrity is false when true is required")
		}
	}

	return nil
}

// VerifyOptsFor returns the Android SafetyNet verification options to be used
// based on the AuthorizedApp configuration, request time, and nonce.
func VerifyOptsFor(c *model.AuthorizedApp, from time.Time, nonce string) *VerifyOpts {
	digests := make([]string, len(c.SafetyNetApkDigestSHA256))
	copy(digests, c.SafetyNetApkDigestSHA256)
	rtn := &VerifyOpts{
		AppPkgName:      c.AppPackageName,
		BasicIntegrity:  c.SafetyNetBasicIntegrity,
		CTSProfileMatch: c.SafetyNetCTSProfileMatch,
		APKDigest:       digests,
		Nonce:           nonce,
	}

	// Calculate the valid time window based on now + config options.
	if c.SafetyNetPastTime > 0 {
		minTime := from.Add(-c.SafetyNetPastTime)
		rtn.MinValidTime = minTime
	}
	if c.SafetyNetFutureTime > 0 {
		maxTime := from.Add(c.SafetyNetFutureTime)
		rtn.MaxValidTime = maxTime
	}

	return rtn
}

// The keyFunc is based on the Android sample code
// https://github.com/googlesamples/android-play-safetynet/blob/d7513a54e2f28c0dcd7f8d8d0fa03adb5d87b91a/server/java/src/main/java/OfflineVerify.java
func keyFunc(ctx context.Context, tok *jwt.Token) (interface{}, error) {
	x5c, ok := tok.Header["x5c"].([]interface{})
	if !ok || len(x5c) == 0 {
		return nil, fmt.Errorf("attestation is missing certificate")
	}

	// Verify the signature of the JWS and retrieve the signature and certificates.
	x509certs := make([]*x509.Certificate, len(x5c))
	for i, certStr := range x5c {
		if certStr == "" {
			return nil, fmt.Errorf("certificate is empty")
		}
		certData, err := base64util.DecodeString(certStr.(string))
		if err != nil {
			return nil, fmt.Errorf("invalid certificate encoding: %w", err)
		}
		x509certs[i], err = x509.ParseCertificate(certData)
		if err != nil {
			return nil, fmt.Errorf("invalid certificate: %w", err)
		}
	}

	pool := x509.NewCertPool()
	for _, cert := range x509certs {
		pool.AddCert(cert)
	}
	opts := x509.VerifyOptions{
		DNSName:       "attest.android.com", // required hostname for valid attestation.
		Intermediates: pool,
	}

	// Verify the first certificate, with all added as allowed intermediates.
	_, err := x509certs[0].Verify(opts)
	if err != nil {
		return nil, fmt.Errorf("invalid certificate chain: %w", err)
	}

	// extract the public key for verification.
	if rsaKey, ok := x509certs[0].PublicKey.(*rsa.PublicKey); ok {
		return rsaKey, nil
	}
	return nil, fmt.Errorf("invalid certificate, unable to extract public key")
}

// verifyAttestation extracts and verifies the signature and claims on the
// attestation. It does NOT validate the attestation, only the signature.
func verifyAttestation(ctx context.Context, signedAttestation string) (jwt.MapClaims, error) {
	defer trace.StartRegion(ctx, "verifyAttestation").End()
	// jwt.Parse also validates the signature after extracting
	// the key via the keyFunc, which validates the certificate chain.
	token, err := jwt.Parse(signedAttestation,
		func(tok *jwt.Token) (interface{}, error) {
			return keyFunc(ctx, tok)
		})

	if err != nil {
		return nil, fmt.Errorf("jwt.Parse: %w", err)
	}
	if !token.Valid {
		return nil, fmt.Errorf("invalid JWS attestation")
	}

	claims, ok := token.Claims.(jwt.MapClaims)
	if !ok {
		return nil, fmt.Errorf("claims are of wrong type")
	}
	return claims, nil
}
<|endoftext|>


<commit_before>package iolimits

import (
	"io"
	"io/ioutil"

	"github.com/pkg/errors"
)

// All constants below are intended to be used as limits for `ReadAtMost`. The
// immediate use-case for limiting the size of in-memory copied data is to
// protect against OOM DOS attacks as described inCVE-2020-1702. Instead of
// copying data until running out of memory, we error out after hitting the
// specified limit.
const (
	// megaByte denotes one megabyte and is intended to be used as a limit in
	// `ReadAtMost`.
	megaByte = 1 << 20
	// MaxManifestBodySize is the maximum allowed size of a manifest. The limit
	// of 4 MB aligns with the one of a Docker registry:
	// https://github.com/docker/distribution/blob/a8371794149d1d95f1e846744b05c87f2f825e5a/registry/handlers/manifests.go#L30
	MaxManifestBodySize = 4 * megaByte
	// MaxAuthTokenBodySize is the maximum allowed size of an auth token.
	// The limit of 1 MB is considered to be greatly sufficient.
	MaxAuthTokenBodySize = megaByte
	// MaxSignatureListBodySize is the maximum allowed size of a signature list.
	// The limit of 4 MB is considered to be greatly sufficient.
	MaxSignatureListBodySize = 4 * megaByte
	// MaxSignatureBodySize is the maximum allowed size of a signature.
	// The limit of 4 MB is considered to be greatly sufficient.
	MaxSignatureBodySize = 4 * megaByte
	// MaxErrorBodySize is the maximum allowed size of an error-response body.
	// The limit of 1 MB is considered to be greatly sufficient.
	MaxErrorBodySize = megaByte
	// MaxConfigBodySize is the maximum allowed size of a config blob.
	// The limit of 4 MB is considered to be greatly sufficient.
	MaxConfigBodySize = 4 * megaByte
	// MaxOpenShiftStatusBody is the maximum allowed size of an OpenShift status body.
	// The limit of 4 MB is considered to be greatly sufficient.
	MaxOpenShiftStatusBody = 4 * megaByte
)

// ReadAtMost reads from reader and errors out if the specified limit (in bytes) is exceeded.
func ReadAtMost(reader io.Reader, limit int) ([]byte, error) {
	limitedReader := io.LimitReader(reader, int64(limit+1))

	res, err := ioutil.ReadAll(limitedReader)
	if err != nil && err != io.EOF {
		return nil, err
	}

	if len(res) > limit {
		return nil, errors.Errorf("exceeded maximum allowed size of %d bytes", limit)
	}

	return res, nil
}
<commit_msg>Simplify error checking in iolimits.ReadAtMost<commit_after>package iolimits

import (
	"io"
	"io/ioutil"

	"github.com/pkg/errors"
)

// All constants below are intended to be used as limits for `ReadAtMost`. The
// immediate use-case for limiting the size of in-memory copied data is to
// protect against OOM DOS attacks as described inCVE-2020-1702. Instead of
// copying data until running out of memory, we error out after hitting the
// specified limit.
const (
	// megaByte denotes one megabyte and is intended to be used as a limit in
	// `ReadAtMost`.
	megaByte = 1 << 20
	// MaxManifestBodySize is the maximum allowed size of a manifest. The limit
	// of 4 MB aligns with the one of a Docker registry:
	// https://github.com/docker/distribution/blob/a8371794149d1d95f1e846744b05c87f2f825e5a/registry/handlers/manifests.go#L30
	MaxManifestBodySize = 4 * megaByte
	// MaxAuthTokenBodySize is the maximum allowed size of an auth token.
	// The limit of 1 MB is considered to be greatly sufficient.
	MaxAuthTokenBodySize = megaByte
	// MaxSignatureListBodySize is the maximum allowed size of a signature list.
	// The limit of 4 MB is considered to be greatly sufficient.
	MaxSignatureListBodySize = 4 * megaByte
	// MaxSignatureBodySize is the maximum allowed size of a signature.
	// The limit of 4 MB is considered to be greatly sufficient.
	MaxSignatureBodySize = 4 * megaByte
	// MaxErrorBodySize is the maximum allowed size of an error-response body.
	// The limit of 1 MB is considered to be greatly sufficient.
	MaxErrorBodySize = megaByte
	// MaxConfigBodySize is the maximum allowed size of a config blob.
	// The limit of 4 MB is considered to be greatly sufficient.
	MaxConfigBodySize = 4 * megaByte
	// MaxOpenShiftStatusBody is the maximum allowed size of an OpenShift status body.
	// The limit of 4 MB is considered to be greatly sufficient.
	MaxOpenShiftStatusBody = 4 * megaByte
)

// ReadAtMost reads from reader and errors out if the specified limit (in bytes) is exceeded.
func ReadAtMost(reader io.Reader, limit int) ([]byte, error) {
	limitedReader := io.LimitReader(reader, int64(limit+1))

	res, err := ioutil.ReadAll(limitedReader)
	if err != nil {
		return nil, err
	}

	if len(res) > limit {
		return nil, errors.Errorf("exceeded maximum allowed size of %d bytes", limit)
	}

	return res, nil
}
<|endoftext|>