<commit_before>package dir

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"strings"

	"rsprd.com/spread/pkg/deploy"
	"rsprd.com/spread/pkg/entity"

	"github.com/ghodss/yaml"
	kube "k8s.io/kubernetes/pkg/api"
	kubectl "k8s.io/kubernetes/pkg/kubectl/cmd/util"
	"k8s.io/kubernetes/pkg/kubectl/resource"
)

const (
	// ContainerExtension is the file extension checked for Containers.
	ContainerExtension = ".ctr"

	// ObjectsDir is the directory checked for arbitrary Kubernetes objects.
	ObjectsDir = "rs"
)

var (
	// RCExtension is the partial extension checked for RCs
	RCExtension = "rc"

	// PodExtension is the partial extension checked for Pods.
	PodExtension = "pod"

	// ObjectExtensions are the file extensions that objects should be checked for in
	ObjectExtensions = []string{"json", "yml", "yaml"}
)

// FileSource provides access to Entities stored according to the Redspread file convention.
// Under this convention RC's are looked for in RCFile, Pods are looked for in PodFile, and anything with the extension
// ContainerExtension is considered a container.
type FileSource string

// Entities returns the entities of the requested type from the source. Errors if any invalid entities.
func (fs FileSource) Entities(t entity.Type, objects ...deploy.KubeObject) ([]entity.Entity, error) {
	switch t {
	case entity.EntityReplicationController:
		return fs.rcs(objects)
	case entity.EntityPod:
		return fs.pods(objects)
	case entity.EntityContainer:
		return fs.containers(objects)
	case entity.EntityImage:
		// getting images not implemented
		return []entity.Entity{}, nil
	}

	// if unknown type, return error
	return []entity.Entity{}, ErrInvalidType
}

// Objects returns the Kubernetes objects available from the source. Errors if any invalid objects.
func (fs FileSource) Objects() (objects []deploy.KubeObject, err error) {
	dirPath := path.Join(string(fs), ObjectsDir)

	err = walkPathForObjects(dirPath, func(info *resource.Info, walkErr error) error {
		if walkErr != nil {
			return walkErr
		}

		obj := info.Object.(deploy.KubeObject)

		objects = append(objects, obj)
		return nil
	})

	// don't throw error if simply didn't find anything
	if err != nil && !checkErrNoResources(err) && !checkErrPathDoesNotExist(err) {
		return nil, err
	}
	return objects, nil
}

// rcs returns entities for the rcs in the RCFile
func (fs FileSource) rcs(objects []deploy.KubeObject) (rcs []entity.Entity, err error) {
	pattern := fmt.Sprintf("%s/*.%s", fs, RCExtension)

	patternExts := withExtensions(pattern)
	paths, err := multiGlob(patternExts...)
	if err != nil {
		return rcs, err
	}

	for _, filePath := range paths {
		err = walkPathForObjects(filePath, func(info *resource.Info, err error) error {
			kubeRC, ok := info.Object.(*kube.ReplicationController)
			if !ok {
				return fmt.Errorf("expected type `ReplicationController` but found `%s`", info.Object.GetObjectKind().GroupVersionKind().Kind)
			}

			rc, err := entity.NewReplicationController(kubeRC, kube.ObjectMeta{}, info.Source, objects...)
			if err != nil {
				return err
			}

			rcs = append(rcs, rc)
			return nil
		})

		if checkErrPathDoesNotExist(err) {
			// it's okay if directory doesn't exit
			err = nil
		}

		if err != nil {
			return
		}
	}
	return
}

// pods returns Pods for the rcs in the PodFile
func (fs FileSource) pods(objects []deploy.KubeObject) (pods []entity.Entity, err error) {
	pattern := fmt.Sprintf("%s/*.%s", fs, PodExtension)

	patternExts := withExtensions(pattern)
	paths, err := multiGlob(patternExts...)
	if err != nil {
		return pods, err
	}

	for _, filePath := range paths {
		err = walkPathForObjects(filePath, func(info *resource.Info, err error) error {
			kubePod, ok := info.Object.(*kube.Pod)
			if !ok {
				return fmt.Errorf("expected type `Pod` but found `%s`", info.Object.GetObjectKind().GroupVersionKind().Kind)
			}

			pod, err := entity.NewPod(kubePod, kube.ObjectMeta{}, filePath, objects...)
			if err != nil {
				return err
			}

			pods = append(pods, pod)
			return nil
		})

		if checkErrPathDoesNotExist(err) {
			// it's okay if directory doesn't exit
			err = nil
		}

		if err != nil {
			return
		}
	}
	return
}

// containers creates entities from files with the ContainerExtension
func (fs FileSource) containers(objects []deploy.KubeObject) (containers []entity.Entity, err error) {
	info, err := os.Stat(string(fs))
	if err != nil {
		return
	}

	// check if file
	if !info.IsDir() {
		kubeCtr, err := unmarshalContainer(string(fs))
		if err != nil {
			return nil, err
		}

		ctr, err := entity.NewContainer(kubeCtr, kube.ObjectMeta{}, string(fs), objects...)
		if err != nil {
			return nil, err
		}

		return []entity.Entity{ctr}, nil
	}

	dir, err := os.Open(string(fs))
	if err != nil {
		return
	}
	defer dir.Close()

	files, err := dir.Readdir(-1)
	if err != nil {
		return
	}

	for _, file := range files {
		if file.Mode().IsRegular() {
			if filepath.Ext(file.Name()) == ContainerExtension {
				filename := path.Join(string(fs), file.Name())

				kubeCtr, err := unmarshalContainer(filename)
				if err != nil {
					return nil, err
				}

				ctr, err := entity.NewContainer(kubeCtr, kube.ObjectMeta{}, filename)
				if err != nil {
					return nil, err
				}

				containers = append(containers, ctr)
			}
		}
	}
	return containers, nil
}

func walkPathForObjects(path string, fn resource.VisitorFunc) error {
	f := kubectl.NewFactory(nil)

	// todo: does "cacheDir" need to be parameterizable?
	schema, err := f.Validator(false, "")
	if err != nil {
		return err
	}

	mapper, typer := f.Object()
	result := resource.NewBuilder(mapper, typer, resource.DisabledClientForMapping{}, f.Decoder(true)).
		ContinueOnError().
		Schema(schema).
		FilenameParam(false, path).
		Flatten().
		Do()

	err = result.Err()
	if err != nil && !checkErrNoResources(err) {
		return err
	}

	err = result.Visit(fn)
	if err != nil {
		return err
	}
	return nil
}

// withExtensions returns the inputted string with the supported file extensions appended to it
func withExtensions(begin string) []string {
	if len(ObjectExtensions) == 0 {
		return []string{begin}
	}
	var paths []string
	for _, ext := range ObjectExtensions {
		path := fmt.Sprintf("%s.%s", begin, ext)
		paths = append(paths, path)
	}
	return paths
}

// multiGlob returns the result of multiple Globs into a single slice.
func multiGlob(patterns ...string) (results []string, err error) {
	for _, pattern := range patterns {
		r, err := filepath.Glob(pattern)
		if err != nil {
			return results, err
		}
		results = append(results, r...)
	}
	return results, err
}

func unmarshalContainer(path string) (ctr kube.Container, err error) {
	data, err := ioutil.ReadFile(path)
	if err != nil {
		return
	}

	err = yaml.Unmarshal(data, &ctr)
	return
}

func checkErrNoResources(err error) bool {
	if err == nil {
		return false
	}
	return strings.HasPrefix(err.Error(), "you must provide one or more resources")
}

func checkErrPathDoesNotExist(err error) bool {
	if err == nil {
		return false
	}
	return strings.HasSuffix(err.Error(), " does not exist")
}

var (
	// ErrInvalidType is returned when the entity.Type is unknown
	ErrInvalidType = errors.New("passed invalid type")
)
<commit_msg>support pod.yml/json<commit_after>package dir

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"strings"

	"rsprd.com/spread/pkg/deploy"
	"rsprd.com/spread/pkg/entity"

	"github.com/ghodss/yaml"
	kube "k8s.io/kubernetes/pkg/api"
	kubectl "k8s.io/kubernetes/pkg/kubectl/cmd/util"
	"k8s.io/kubernetes/pkg/kubectl/resource"
)

const (
	// ContainerExtension is the file extension checked for Containers.
	ContainerExtension = ".ctr"

	// ObjectsDir is the directory checked for arbitrary Kubernetes objects.
	ObjectsDir = "rs"
)

var (
	// RCExtension is the partial extension checked for RCs
	RCExtension = "rc"

	// PodExtension is the partial extension checked for Pods.
	PodExtension = "pod"

	// ObjectExtensions are the file extensions that objects should be checked for in
	ObjectExtensions = []string{"json", "yml", "yaml"}
)

// FileSource provides access to Entities stored according to the Redspread file convention.
// Under this convention RC's are looked for in RCFile, Pods are looked for in PodFile, and anything with the extension
// ContainerExtension is considered a container.
type FileSource string

// Entities returns the entities of the requested type from the source. Errors if any invalid entities.
func (fs FileSource) Entities(t entity.Type, objects ...deploy.KubeObject) ([]entity.Entity, error) {
	switch t {
	case entity.EntityReplicationController:
		return fs.rcs(objects)
	case entity.EntityPod:
		return fs.pods(objects)
	case entity.EntityContainer:
		return fs.containers(objects)
	case entity.EntityImage:
		// getting images not implemented
		return []entity.Entity{}, nil
	}

	// if unknown type, return error
	return []entity.Entity{}, ErrInvalidType
}

// Objects returns the Kubernetes objects available from the source. Errors if any invalid objects.
func (fs FileSource) Objects() (objects []deploy.KubeObject, err error) {
	dirPath := path.Join(string(fs), ObjectsDir)

	err = walkPathForObjects(dirPath, func(info *resource.Info, walkErr error) error {
		if walkErr != nil {
			return walkErr
		}

		obj := info.Object.(deploy.KubeObject)

		objects = append(objects, obj)
		return nil
	})

	// don't throw error if simply didn't find anything
	if err != nil && !checkErrNoResources(err) && !checkErrPathDoesNotExist(err) {
		return nil, err
	}
	return objects, nil
}

// rcs returns entities for the rcs in the RCFile
func (fs FileSource) rcs(objects []deploy.KubeObject) (rcs []entity.Entity, err error) {
	pattern := fmt.Sprintf("%s/*%s", fs, RCExtension)

	patternExts := withExtensions(pattern)
	paths, err := multiGlob(patternExts...)
	if err != nil {
		return rcs, err
	}

	for _, filePath := range paths {
		err = walkPathForObjects(filePath, func(info *resource.Info, err error) error {
			kubeRC, ok := info.Object.(*kube.ReplicationController)
			if !ok {
				return fmt.Errorf("expected type `ReplicationController` but found `%s`", info.Object.GetObjectKind().GroupVersionKind().Kind)
			}

			rc, err := entity.NewReplicationController(kubeRC, kube.ObjectMeta{}, info.Source, objects...)
			if err != nil {
				return err
			}

			rcs = append(rcs, rc)
			return nil
		})

		if checkErrPathDoesNotExist(err) {
			// it's okay if directory doesn't exit
			err = nil
		}

		if err != nil {
			return
		}
	}
	return
}

// pods returns Pods for the rcs in the PodFile
func (fs FileSource) pods(objects []deploy.KubeObject) (pods []entity.Entity, err error) {
	pattern := fmt.Sprintf("%s/*%s", fs, PodExtension)

	patternExts := withExtensions(pattern)
	paths, err := multiGlob(patternExts...)
	if err != nil {
		return pods, err
	}

	for _, filePath := range paths {
		err = walkPathForObjects(filePath, func(info *resource.Info, err error) error {
			kubePod, ok := info.Object.(*kube.Pod)
			if !ok {
				return fmt.Errorf("expected type `Pod` but found `%s`", info.Object.GetObjectKind().GroupVersionKind().Kind)
			}

			pod, err := entity.NewPod(kubePod, kube.ObjectMeta{}, filePath, objects...)
			if err != nil {
				return err
			}

			pods = append(pods, pod)
			return nil
		})

		if checkErrPathDoesNotExist(err) {
			// it's okay if directory doesn't exist
			err = nil
		}

		if err != nil {
			return
		}
	}
	return
}

// containers creates entities from files with the ContainerExtension
func (fs FileSource) containers(objects []deploy.KubeObject) (containers []entity.Entity, err error) {
	info, err := os.Stat(string(fs))
	if err != nil {
		return
	}

	// check if file
	if !info.IsDir() {
		kubeCtr, err := unmarshalContainer(string(fs))
		if err != nil {
			return nil, err
		}

		ctr, err := entity.NewContainer(kubeCtr, kube.ObjectMeta{}, string(fs), objects...)
		if err != nil {
			return nil, err
		}

		return []entity.Entity{ctr}, nil
	}

	dir, err := os.Open(string(fs))
	if err != nil {
		return
	}
	defer dir.Close()

	files, err := dir.Readdir(-1)
	if err != nil {
		return
	}

	for _, file := range files {
		if file.Mode().IsRegular() {
			if filepath.Ext(file.Name()) == ContainerExtension {
				filename := path.Join(string(fs), file.Name())

				kubeCtr, err := unmarshalContainer(filename)
				if err != nil {
					return nil, err
				}

				ctr, err := entity.NewContainer(kubeCtr, kube.ObjectMeta{}, filename)
				if err != nil {
					return nil, err
				}

				containers = append(containers, ctr)
			}
		}
	}
	return containers, nil
}

func walkPathForObjects(path string, fn resource.VisitorFunc) error {
	f := kubectl.NewFactory(nil)

	// todo: does "cacheDir" need to be parameterizable?
	schema, err := f.Validator(false, "")
	if err != nil {
		return err
	}

	mapper, typer := f.Object()
	result := resource.NewBuilder(mapper, typer, resource.DisabledClientForMapping{}, f.Decoder(true)).
		ContinueOnError().
		Schema(schema).
		FilenameParam(false, path).
		Flatten().
		Do()

	err = result.Err()
	if err != nil && !checkErrNoResources(err) {
		return err
	}

	err = result.Visit(fn)
	if err != nil {
		return err
	}
	return nil
}

// withExtensions returns the inputted string with the supported file extensions appended to it
func withExtensions(begin string) []string {
	if len(ObjectExtensions) == 0 {
		return []string{begin}
	}
	var paths []string
	for _, ext := range ObjectExtensions {
		path := fmt.Sprintf("%s.%s", begin, ext)
		paths = append(paths, path)
	}
	return paths
}

// multiGlob returns the result of multiple Globs into a single slice.
func multiGlob(patterns ...string) (results []string, err error) {
	for _, pattern := range patterns {
		r, err := filepath.Glob(pattern)
		if err != nil {
			return results, err
		}
		results = append(results, r...)
	}
	return results, err
}

func unmarshalContainer(path string) (ctr kube.Container, err error) {
	data, err := ioutil.ReadFile(path)
	if err != nil {
		return
	}

	err = yaml.Unmarshal(data, &ctr)
	return
}

func checkErrNoResources(err error) bool {
	if err == nil {
		return false
	}
	return strings.HasPrefix(err.Error(), "you must provide one or more resources")
}

func checkErrPathDoesNotExist(err error) bool {
	if err == nil {
		return false
	}
	return strings.HasSuffix(err.Error(), " does not exist")
}

var (
	// ErrInvalidType is returned when the entity.Type is unknown
	ErrInvalidType = errors.New("passed invalid type")
)
<|endoftext|>


<commit_before>package scripts

import (
	"io"
	"net/http"
	"net/url"
	"os"
	"path/filepath"

	utilglog "github.com/openshift/source-to-image/pkg/util/glog"

	"github.com/openshift/source-to-image/pkg/api"
	s2ierr "github.com/openshift/source-to-image/pkg/errors"
)

var glog = utilglog.StderrLog

// Downloader downloads the specified URL to the target file location
type Downloader interface {
	Download(url *url.URL, target string) (*api.SourceInfo, error)
}

// schemeReader creates an io.Reader from the given url.
type schemeReader interface {
	Read(*url.URL) (io.ReadCloser, error)
}

type downloader struct {
	schemeReaders map[string]schemeReader
}

// NewDownloader creates an instance of the default Downloader implementation
func NewDownloader(proxyConfig *api.ProxyConfig) Downloader {
	httpReader := NewHTTPURLReader(proxyConfig)
	return &downloader{
		schemeReaders: map[string]schemeReader{
			"http":  httpReader,
			"https": httpReader,
			"file":  &FileURLReader{},
			"image": &ImageReader{},
		},
	}
}

// Download downloads the file pointed to by URL into local targetFile
// Returns information a boolean flag informing whether any download/copy operation
// happened and an error if there was a problem during that operation
func (d *downloader) Download(url *url.URL, targetFile string) (*api.SourceInfo, error) {
	r := d.schemeReaders[url.Scheme]
	info := &api.SourceInfo{}
	if r == nil {
		glog.Errorf("No URL handler found for %s", url.String())
		return nil, s2ierr.NewURLHandlerError(url.String())
	}

	reader, err := r.Read(url)
	if err != nil {
		return nil, err
	}
	defer reader.Close()

	out, err := os.Create(targetFile)
	defer out.Close()

	if err != nil {
		glog.Errorf("Unable to create target file %s (%s)", targetFile, err)
		return nil, err
	}

	if _, err = io.Copy(out, reader); err != nil {
		os.Remove(targetFile)
		glog.Warningf("Skipping file %s due to error copying from source: %s", targetFile, err)
		return nil, err
	}

	glog.V(2).Infof("Downloaded '%s'", url.String())
	info.Location = url.String()
	return info, nil
}

// HTTPURLReader retrieves a response from a given HTTP(S) URL.
type HTTPURLReader struct {
	Get func(url string) (*http.Response, error)
}

// NewHTTPURLReader returns a new HTTPURLReader.
func NewHTTPURLReader(proxyConfig *api.ProxyConfig) *HTTPURLReader {
	getFunc := http.Get
	if proxyConfig != nil {
		transport := &http.Transport{
			Proxy: func(req *http.Request) (*url.URL, error) {
				if proxyConfig.HTTPSProxy != nil && req.URL.Scheme == "https" {
					return proxyConfig.HTTPSProxy, nil
				}
				return proxyConfig.HTTPProxy, nil
			},
		}
		client := &http.Client{
			Transport: transport,
		}
		getFunc = client.Get
	}
	return &HTTPURLReader{Get: getFunc}
}

// Read produces an io.Reader from an http(s) URL.
func (h *HTTPURLReader) Read(url *url.URL) (io.ReadCloser, error) {
	resp, err := h.Get(url.String())
	if err != nil {
		if resp != nil {
			defer resp.Body.Close()
		}
		return nil, err
	}
	if resp.StatusCode == 200 || resp.StatusCode == 201 {
		return resp.Body, nil
	}
	return nil, s2ierr.NewDownloadError(url.String(), resp.StatusCode)
}

// FileURLReader opens a specified file and returns its stream
type FileURLReader struct{}

// Read produces an io.Reader from a file URL
func (*FileURLReader) Read(url *url.URL) (io.ReadCloser, error) {
	// for some reason url.Host may contain information about the ./ or ../ when
	// specifying relative path, thus using that value as well
	return os.Open(filepath.Join(url.Host, url.Path))
}

// ImageReader just returns information the URL is from inside the image
type ImageReader struct{}

// Read throws Not implemented error
func (*ImageReader) Read(url *url.URL) (io.ReadCloser, error) {
	return nil, s2ierr.NewScriptsInsideImageError(url.String())
}
<commit_msg>modify log format<commit_after>package scripts

import (
	"io"
	"net/http"
	"net/url"
	"os"
	"path/filepath"

	utilglog "github.com/openshift/source-to-image/pkg/util/glog"

	"github.com/openshift/source-to-image/pkg/api"
	s2ierr "github.com/openshift/source-to-image/pkg/errors"
)

var glog = utilglog.StderrLog

// Downloader downloads the specified URL to the target file location
type Downloader interface {
	Download(url *url.URL, target string) (*api.SourceInfo, error)
}

// schemeReader creates an io.Reader from the given url.
type schemeReader interface {
	Read(*url.URL) (io.ReadCloser, error)
}

type downloader struct {
	schemeReaders map[string]schemeReader
}

// NewDownloader creates an instance of the default Downloader implementation
func NewDownloader(proxyConfig *api.ProxyConfig) Downloader {
	httpReader := NewHTTPURLReader(proxyConfig)
	return &downloader{
		schemeReaders: map[string]schemeReader{
			"http":  httpReader,
			"https": httpReader,
			"file":  &FileURLReader{},
			"image": &ImageReader{},
		},
	}
}

// Download downloads the file pointed to by URL into local targetFile
// Returns information a boolean flag informing whether any download/copy operation
// happened and an error if there was a problem during that operation
func (d *downloader) Download(url *url.URL, targetFile string) (*api.SourceInfo, error) {
	r := d.schemeReaders[url.Scheme]
	info := &api.SourceInfo{}
	if r == nil {
		glog.Errorf("No URL handler found for %s", url.String())
		return nil, s2ierr.NewURLHandlerError(url.String())
	}

	reader, err := r.Read(url)
	if err != nil {
		return nil, err
	}
	defer reader.Close()

	out, err := os.Create(targetFile)
	defer out.Close()

	if err != nil {
		glog.Errorf("Unable to create target file %s (%v)", targetFile, err)
		return nil, err
	}

	if _, err = io.Copy(out, reader); err != nil {
		os.Remove(targetFile)
		glog.Warningf("Skipping file %s due to error copying from source: %v", targetFile, err)
		return nil, err
	}

	glog.V(2).Infof("Downloaded '%s'", url.String())
	info.Location = url.String()
	return info, nil
}

// HTTPURLReader retrieves a response from a given HTTP(S) URL.
type HTTPURLReader struct {
	Get func(url string) (*http.Response, error)
}

// NewHTTPURLReader returns a new HTTPURLReader.
func NewHTTPURLReader(proxyConfig *api.ProxyConfig) *HTTPURLReader {
	getFunc := http.Get
	if proxyConfig != nil {
		transport := &http.Transport{
			Proxy: func(req *http.Request) (*url.URL, error) {
				if proxyConfig.HTTPSProxy != nil && req.URL.Scheme == "https" {
					return proxyConfig.HTTPSProxy, nil
				}
				return proxyConfig.HTTPProxy, nil
			},
		}
		client := &http.Client{
			Transport: transport,
		}
		getFunc = client.Get
	}
	return &HTTPURLReader{Get: getFunc}
}

// Read produces an io.Reader from an http(s) URL.
func (h *HTTPURLReader) Read(url *url.URL) (io.ReadCloser, error) {
	resp, err := h.Get(url.String())
	if err != nil {
		if resp != nil {
			defer resp.Body.Close()
		}
		return nil, err
	}
	if resp.StatusCode == 200 || resp.StatusCode == 201 {
		return resp.Body, nil
	}
	return nil, s2ierr.NewDownloadError(url.String(), resp.StatusCode)
}

// FileURLReader opens a specified file and returns its stream
type FileURLReader struct{}

// Read produces an io.Reader from a file URL
func (*FileURLReader) Read(url *url.URL) (io.ReadCloser, error) {
	// for some reason url.Host may contain information about the ./ or ../ when
	// specifying relative path, thus using that value as well
	return os.Open(filepath.Join(url.Host, url.Path))
}

// ImageReader just returns information the URL is from inside the image
type ImageReader struct{}

// Read throws Not implemented error
func (*ImageReader) Read(url *url.URL) (io.ReadCloser, error) {
	return nil, s2ierr.NewScriptsInsideImageError(url.String())
}
<|endoftext|>


<commit_before>package settings

import (
	"encoding/json"
	"fmt"
	"os"
	"regexp"
	"strconv"
	"strings"

	v32 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3"
	authsettings "github.com/rancher/rancher/pkg/auth/settings"
	"github.com/sirupsen/logrus"
)

var (
	releasePattern = regexp.MustCompile("^v[0-9]")
	settings       = map[string]Setting{}
	provider       Provider
	InjectDefaults string

	AgentImage                        = NewSetting("agent-image", "rancher/rancher-agent:master-head")
	AgentRolloutTimeout               = NewSetting("agent-rollout-timeout", "300s")
	AgentRolloutWait                  = NewSetting("agent-rollout-wait", "true")
	AuthImage                         = NewSetting("auth-image", v32.ToolsSystemImages.AuthSystemImages.KubeAPIAuth)
	AuthTokenMaxTTLMinutes            = NewSetting("auth-token-max-ttl-minutes", "0") // never expire
	AuthorizationCacheTTLSeconds      = NewSetting("authorization-cache-ttl-seconds", "10")
	AuthorizationDenyCacheTTLSeconds  = NewSetting("authorization-deny-cache-ttl-seconds", "10")
	AzureGroupCacheSize               = NewSetting("azure-group-cache-size", "10000")
	CACerts                           = NewSetting("cacerts", "")
	CLIURLDarwin                      = NewSetting("cli-url-darwin", "https://releases.rancher.com/cli/v1.0.0-alpha8/rancher-darwin-amd64-v1.0.0-alpha8.tar.gz")
	CLIURLLinux                       = NewSetting("cli-url-linux", "https://releases.rancher.com/cli/v1.0.0-alpha8/rancher-linux-amd64-v1.0.0-alpha8.tar.gz")
	CLIURLWindows                     = NewSetting("cli-url-windows", "https://releases.rancher.com/cli/v1.0.0-alpha8/rancher-windows-386-v1.0.0-alpha8.zip")
	ClusterControllerStartCount       = NewSetting("cluster-controller-start-count", "50")
	EngineInstallURL                  = NewSetting("engine-install-url", "https://releases.rancher.com/install-docker/20.10.sh")
	EngineISOURL                      = NewSetting("engine-iso-url", "https://releases.rancher.com/os/latest/rancheros-vmware.iso")
	EngineNewestVersion               = NewSetting("engine-newest-version", "v17.12.0")
	EngineSupportedRange              = NewSetting("engine-supported-range", "~v1.11.2 || ~v1.12.0 || ~v1.13.0 || ~v17.03.0 || ~v17.06.0 || ~v17.09.0 || ~v18.06.0 || ~v18.09.0 || ~v19.03.0 || ~v20.10.0 ")
	FirstLogin                        = NewSetting("first-login", "true")
	GlobalRegistryEnabled             = NewSetting("global-registry-enabled", "false")
	GithubProxyAPIURL                 = NewSetting("github-proxy-api-url", "https://api.github.com")
	HelmVersion                       = NewSetting("helm-version", "dev")
	HelmMaxHistory                    = NewSetting("helm-max-history", "10")
	IngressIPDomain                   = NewSetting("ingress-ip-domain", "xip.io")
	InstallUUID                       = NewSetting("install-uuid", "")
	InternalServerURL                 = NewSetting("internal-server-url", "")
	InternalCACerts                   = NewSetting("internal-cacerts", "")
	JailerTimeout                     = NewSetting("jailer-timeout", "60")
	KubeconfigGenerateToken           = NewSetting("kubeconfig-generate-token", "true")
	KubeconfigTokenTTLMinutes         = NewSetting("kubeconfig-token-ttl-minutes", "960") // 16 hours
	KubernetesVersion                 = NewSetting("k8s-version", "")
	KubernetesVersionToServiceOptions = NewSetting("k8s-version-to-service-options", "")
	KubernetesVersionToSystemImages   = NewSetting("k8s-version-to-images", "")
	KubernetesVersionsCurrent         = NewSetting("k8s-versions-current", "")
	KubernetesVersionsDeprecated      = NewSetting("k8s-versions-deprecated", "")
	KDMBranch                         = NewSetting("kdm-branch", "dev-v2.6")
	MachineVersion                    = NewSetting("machine-version", "dev")
	Namespace                         = NewSetting("namespace", os.Getenv("CATTLE_NAMESPACE"))
	PeerServices                      = NewSetting("peer-service", os.Getenv("CATTLE_PEER_SERVICE"))
	RDNSServerBaseURL                 = NewSetting("rdns-base-url", "https://api.lb.rancher.cloud/v1")
	RkeVersion                        = NewSetting("rke-version", "")
	RkeMetadataConfig                 = NewSetting("rke-metadata-config", getMetadataConfig())
	ServerImage                       = NewSetting("server-image", "rancher/rancher")
	ServerURL                         = NewSetting("server-url", "")
	ServerVersion                     = NewSetting("server-version", "dev")
	SystemAgentVersion                = NewSetting("system-agent-version", "")
	SystemAgentInstallScript          = NewSetting("system-agent-install-script", "https://raw.githubusercontent.com/rancher/system-agent/main/install.sh")
	WindowsRke2InstallScript          = NewSetting("windows-rke2-install-script", "https://raw.githubusercontent.com/rancher/rke2/master/windows/rke2-install.ps1")
	SystemAgentInstallerImage         = NewSetting("system-agent-installer-image", "docker.io/rancher/system-agent-installer-")
	SystemAgentUpgradeImage           = NewSetting("system-agent-upgrade-image", "")
	SystemDefaultRegistry             = NewSetting("system-default-registry", "")
	SystemNamespaces                  = NewSetting("system-namespaces", "kube-system,kube-public,cattle-system,cattle-alerting,cattle-logging,cattle-pipeline,cattle-prometheus,ingress-nginx,cattle-global-data,cattle-istio,kube-node-lease,cert-manager,cattle-global-nt,security-scan,cattle-fleet-system,calico-system,tigera-operator")
	TelemetryOpt                      = NewSetting("telemetry-opt", "")
	TokenHashing                      = NewSetting("token-hashing", "true")
	TLSMinVersion                     = NewSetting("tls-min-version", "1.2")
	TLSCiphers                        = NewSetting("tls-ciphers", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305")
	UIBanners                         = NewSetting("ui-banners", "{}")
	UIBrand                           = NewSetting("ui-brand", "")
	UIDefaultLanding                  = NewSetting("ui-default-landing", "vue")
	UIFeedBackForm                    = NewSetting("ui-feedback-form", "")
	UIIndex                           = NewSetting("ui-index", "https://releases.rancher.com/ui/latest2/index.html")
	UIPath                            = NewSetting("ui-path", "/usr/share/rancher/ui")
	UIDashboardIndex                  = NewSetting("ui-dashboard-index", "https://releases.rancher.com/dashboard/release-2.6/index.html")
	UIDashboardPath                   = NewSetting("ui-dashboard-path", "/usr/share/rancher/ui-dashboard")
	UIPreferred                       = NewSetting("ui-preferred", "vue")
	UIOfflinePreferred                = NewSetting("ui-offline-preferred", "dynamic")
	UIIssues                          = NewSetting("ui-issues", "")
	UIPL                              = NewSetting("ui-pl", "rancher")
	UICommunityLinks                  = NewSetting("ui-community-links", "true")
	UIKubernetesSupportedVersions     = NewSetting("ui-k8s-supported-versions-range", ">= 1.11.0 <=1.14.x")
	UIKubernetesDefaultVersion        = NewSetting("ui-k8s-default-version-range", "<=1.14.x")
	WhitelistDomain                   = NewSetting("whitelist-domain", "forums.rancher.com")
	WhitelistEnvironmentVars          = NewSetting("whitelist-envvars", "HTTP_PROXY,HTTPS_PROXY,NO_PROXY")
	AuthUserInfoResyncCron            = NewSetting("auth-user-info-resync-cron", "0 0 * * *")
	AuthUserSessionTTLMinutes         = NewSetting("auth-user-session-ttl-minutes", "960")   // 16 hours
	AuthUserInfoMaxAgeSeconds         = NewSetting("auth-user-info-max-age-seconds", "3600") // 1 hour
	APIUIVersion                      = NewSetting("api-ui-version", "1.1.6")                // Please update the CATTLE_API_UI_VERSION in package/Dockerfile when updating the version here.
	RotateCertsIfExpiringInDays       = NewSetting("rotate-certs-if-expiring-in-days", "7")  // 7 days
	ClusterTemplateEnforcement        = NewSetting("cluster-template-enforcement", "false")
	InitialDockerRootDir              = NewSetting("initial-docker-root-dir", "/var/lib/docker")
	SystemCatalog                     = NewSetting("system-catalog", "external") // Options are 'external' or 'bundled'
	ChartDefaultBranch                = NewSetting("chart-default-branch", "dev-v2.6")
	PartnerChartDefaultBranch         = NewSetting("partner-chart-default-branch", "main")
	RKE2ChartDefaultBranch            = NewSetting("rke2-chart-default-branch", "main")
	FleetDefaultWorkspaceName         = NewSetting("fleet-default-workspace-name", "fleet-default") // fleetWorkspaceName to assign to clusters with none
	ShellImage                        = NewSetting("shell-image", "rancher/shell:v0.1.8")
	IgnoreNodeName                    = NewSetting("ignore-node-name", "") // nodes to ignore when syncing v1.node to v3.node
	NoDefaultAdmin                    = NewSetting("no-default-admin", "")
	RestrictedDefaultAdmin            = NewSetting("restricted-default-admin", "false") // When bootstrapping the admin for the first time, give them the global role restricted-admin
	AKSUpstreamRefresh                = NewSetting("aks-refresh", "300")
	EKSUpstreamRefreshCron            = NewSetting("eks-refresh-cron", "*/5 * * * *") // EKSUpstreamRefreshCron is deprecated and will be replaced by EKSUpstreamRefresh
	EKSUpstreamRefresh                = NewSetting("eks-refresh", "300")
	GKEUpstreamRefresh                = NewSetting("gke-refresh", "300")
	HideLocalCluster                  = NewSetting("hide-local-cluster", "false")
	MachineProvisionImage             = NewSetting("machine-provision-image", "rancher/machine:v0.15.0-rancher65")

	FleetMinVersion          = NewSetting("fleet-min-version", "")
	RancherWebhookMinVersion = NewSetting("rancher-webhook-min-version", "")
)

func FullShellImage() string {
	return PrefixPrivateRegistry(ShellImage.Get())
}

func PrefixPrivateRegistry(image string) string {
	private := SystemDefaultRegistry.Get()
	if private == "" {
		return image
	}
	return private + "/" + image
}

func IsRelease() bool {
	return !strings.Contains(ServerVersion.Get(), "head") && releasePattern.MatchString(ServerVersion.Get())
}

func init() {
	// setup auth setting
	authsettings.AuthUserInfoResyncCron = AuthUserInfoResyncCron
	authsettings.AuthUserSessionTTLMinutes = AuthUserSessionTTLMinutes
	authsettings.AuthUserInfoMaxAgeSeconds = AuthUserInfoMaxAgeSeconds
	authsettings.FirstLogin = FirstLogin

	if InjectDefaults == "" {
		return
	}
	defaults := map[string]string{}
	if err := json.Unmarshal([]byte(InjectDefaults), &defaults); err != nil {
		return
	}
	for name, defaultValue := range defaults {
		value, ok := settings[name]
		if !ok {
			continue
		}
		value.Default = defaultValue
		settings[name] = value
	}
}

type Provider interface {
	Get(name string) string
	Set(name, value string) error
	SetIfUnset(name, value string) error
	SetAll(settings map[string]Setting) error
}

type Setting struct {
	Name     string
	Default  string
	ReadOnly bool
}

func (s Setting) SetIfUnset(value string) error {
	if provider == nil {
		return s.Set(value)
	}
	return provider.SetIfUnset(s.Name, value)
}

func (s Setting) Set(value string) error {
	if provider == nil {
		s, ok := settings[s.Name]
		if ok {
			s.Default = value
			settings[s.Name] = s
		}
	} else {
		return provider.Set(s.Name, value)
	}
	return nil
}

func (s Setting) Get() string {
	if provider == nil {
		s := settings[s.Name]
		return s.Default
	}
	return provider.Get(s.Name)
}

func (s Setting) GetInt() int {
	v := s.Get()
	i, err := strconv.Atoi(v)
	if err == nil {
		return i
	}
	logrus.Errorf("failed to parse setting %s=%s as int: %v", s.Name, v, err)
	i, err = strconv.Atoi(s.Default)
	if err != nil {
		return 0
	}
	return i
}

func SetProvider(p Provider) error {
	if err := p.SetAll(settings); err != nil {
		return err
	}
	provider = p
	return nil
}

func NewSetting(name, def string) Setting {
	s := Setting{
		Name:    name,
		Default: def,
	}
	settings[s.Name] = s
	return s
}

func GetEnvKey(key string) string {
	return "CATTLE_" + strings.ToUpper(strings.Replace(key, "-", "_", -1))
}

func getMetadataConfig() string {
	branch := KDMBranch.Get()
	data := map[string]interface{}{
		"url":                      fmt.Sprintf("https://releases.rancher.com/kontainer-driver-metadata/%s/data.json", branch),
		"refresh-interval-minutes": "1440",
	}
	ans, err := json.Marshal(data)
	if err != nil {
		logrus.Errorf("error getting metadata config %v", err)
		return ""
	}
	return string(ans)
}

// GetSettingByID returns a setting that is stored with the given id
func GetSettingByID(id string) string {
	if provider == nil {
		s := settings[id]
		return s.Default
	}
	return provider.Get(id)
}
<commit_msg>Update rancher/shell image<commit_after>package settings

import (
	"encoding/json"
	"fmt"
	"os"
	"regexp"
	"strconv"
	"strings"

	v32 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3"
	authsettings "github.com/rancher/rancher/pkg/auth/settings"
	"github.com/sirupsen/logrus"
)

var (
	releasePattern = regexp.MustCompile("^v[0-9]")
	settings       = map[string]Setting{}
	provider       Provider
	InjectDefaults string

	AgentImage                        = NewSetting("agent-image", "rancher/rancher-agent:master-head")
	AgentRolloutTimeout               = NewSetting("agent-rollout-timeout", "300s")
	AgentRolloutWait                  = NewSetting("agent-rollout-wait", "true")
	AuthImage                         = NewSetting("auth-image", v32.ToolsSystemImages.AuthSystemImages.KubeAPIAuth)
	AuthTokenMaxTTLMinutes            = NewSetting("auth-token-max-ttl-minutes", "0") // never expire
	AuthorizationCacheTTLSeconds      = NewSetting("authorization-cache-ttl-seconds", "10")
	AuthorizationDenyCacheTTLSeconds  = NewSetting("authorization-deny-cache-ttl-seconds", "10")
	AzureGroupCacheSize               = NewSetting("azure-group-cache-size", "10000")
	CACerts                           = NewSetting("cacerts", "")
	CLIURLDarwin                      = NewSetting("cli-url-darwin", "https://releases.rancher.com/cli/v1.0.0-alpha8/rancher-darwin-amd64-v1.0.0-alpha8.tar.gz")
	CLIURLLinux                       = NewSetting("cli-url-linux", "https://releases.rancher.com/cli/v1.0.0-alpha8/rancher-linux-amd64-v1.0.0-alpha8.tar.gz")
	CLIURLWindows                     = NewSetting("cli-url-windows", "https://releases.rancher.com/cli/v1.0.0-alpha8/rancher-windows-386-v1.0.0-alpha8.zip")
	ClusterControllerStartCount       = NewSetting("cluster-controller-start-count", "50")
	EngineInstallURL                  = NewSetting("engine-install-url", "https://releases.rancher.com/install-docker/20.10.sh")
	EngineISOURL                      = NewSetting("engine-iso-url", "https://releases.rancher.com/os/latest/rancheros-vmware.iso")
	EngineNewestVersion               = NewSetting("engine-newest-version", "v17.12.0")
	EngineSupportedRange              = NewSetting("engine-supported-range", "~v1.11.2 || ~v1.12.0 || ~v1.13.0 || ~v17.03.0 || ~v17.06.0 || ~v17.09.0 || ~v18.06.0 || ~v18.09.0 || ~v19.03.0 || ~v20.10.0 ")
	FirstLogin                        = NewSetting("first-login", "true")
	GlobalRegistryEnabled             = NewSetting("global-registry-enabled", "false")
	GithubProxyAPIURL                 = NewSetting("github-proxy-api-url", "https://api.github.com")
	HelmVersion                       = NewSetting("helm-version", "dev")
	HelmMaxHistory                    = NewSetting("helm-max-history", "10")
	IngressIPDomain                   = NewSetting("ingress-ip-domain", "xip.io")
	InstallUUID                       = NewSetting("install-uuid", "")
	InternalServerURL                 = NewSetting("internal-server-url", "")
	InternalCACerts                   = NewSetting("internal-cacerts", "")
	JailerTimeout                     = NewSetting("jailer-timeout", "60")
	KubeconfigGenerateToken           = NewSetting("kubeconfig-generate-token", "true")
	KubeconfigTokenTTLMinutes         = NewSetting("kubeconfig-token-ttl-minutes", "960") // 16 hours
	KubernetesVersion                 = NewSetting("k8s-version", "")
	KubernetesVersionToServiceOptions = NewSetting("k8s-version-to-service-options", "")
	KubernetesVersionToSystemImages   = NewSetting("k8s-version-to-images", "")
	KubernetesVersionsCurrent         = NewSetting("k8s-versions-current", "")
	KubernetesVersionsDeprecated      = NewSetting("k8s-versions-deprecated", "")
	KDMBranch                         = NewSetting("kdm-branch", "dev-v2.6")
	MachineVersion                    = NewSetting("machine-version", "dev")
	Namespace                         = NewSetting("namespace", os.Getenv("CATTLE_NAMESPACE"))
	PeerServices                      = NewSetting("peer-service", os.Getenv("CATTLE_PEER_SERVICE"))
	RDNSServerBaseURL                 = NewSetting("rdns-base-url", "https://api.lb.rancher.cloud/v1")
	RkeVersion                        = NewSetting("rke-version", "")
	RkeMetadataConfig                 = NewSetting("rke-metadata-config", getMetadataConfig())
	ServerImage                       = NewSetting("server-image", "rancher/rancher")
	ServerURL                         = NewSetting("server-url", "")
	ServerVersion                     = NewSetting("server-version", "dev")
	SystemAgentVersion                = NewSetting("system-agent-version", "")
	SystemAgentInstallScript          = NewSetting("system-agent-install-script", "https://raw.githubusercontent.com/rancher/system-agent/main/install.sh")
	WindowsRke2InstallScript          = NewSetting("windows-rke2-install-script", "https://raw.githubusercontent.com/rancher/rke2/master/windows/rke2-install.ps1")
	SystemAgentInstallerImage         = NewSetting("system-agent-installer-image", "docker.io/rancher/system-agent-installer-")
	SystemAgentUpgradeImage           = NewSetting("system-agent-upgrade-image", "")
	SystemDefaultRegistry             = NewSetting("system-default-registry", "")
	SystemNamespaces                  = NewSetting("system-namespaces", "kube-system,kube-public,cattle-system,cattle-alerting,cattle-logging,cattle-pipeline,cattle-prometheus,ingress-nginx,cattle-global-data,cattle-istio,kube-node-lease,cert-manager,cattle-global-nt,security-scan,cattle-fleet-system,calico-system,tigera-operator")
	TelemetryOpt                      = NewSetting("telemetry-opt", "")
	TokenHashing                      = NewSetting("token-hashing", "true")
	TLSMinVersion                     = NewSetting("tls-min-version", "1.2")
	TLSCiphers                        = NewSetting("tls-ciphers", "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305")
	UIBanners                         = NewSetting("ui-banners", "{}")
	UIBrand                           = NewSetting("ui-brand", "")
	UIDefaultLanding                  = NewSetting("ui-default-landing", "vue")
	UIFeedBackForm                    = NewSetting("ui-feedback-form", "")
	UIIndex                           = NewSetting("ui-index", "https://releases.rancher.com/ui/latest2/index.html")
	UIPath                            = NewSetting("ui-path", "/usr/share/rancher/ui")
	UIDashboardIndex                  = NewSetting("ui-dashboard-index", "https://releases.rancher.com/dashboard/release-2.6/index.html")
	UIDashboardPath                   = NewSetting("ui-dashboard-path", "/usr/share/rancher/ui-dashboard")
	UIPreferred                       = NewSetting("ui-preferred", "vue")
	UIOfflinePreferred                = NewSetting("ui-offline-preferred", "dynamic")
	UIIssues                          = NewSetting("ui-issues", "")
	UIPL                              = NewSetting("ui-pl", "rancher")
	UICommunityLinks                  = NewSetting("ui-community-links", "true")
	UIKubernetesSupportedVersions     = NewSetting("ui-k8s-supported-versions-range", ">= 1.11.0 <=1.14.x")
	UIKubernetesDefaultVersion        = NewSetting("ui-k8s-default-version-range", "<=1.14.x")
	WhitelistDomain                   = NewSetting("whitelist-domain", "forums.rancher.com")
	WhitelistEnvironmentVars          = NewSetting("whitelist-envvars", "HTTP_PROXY,HTTPS_PROXY,NO_PROXY")
	AuthUserInfoResyncCron            = NewSetting("auth-user-info-resync-cron", "0 0 * * *")
	AuthUserSessionTTLMinutes         = NewSetting("auth-user-session-ttl-minutes", "960")   // 16 hours
	AuthUserInfoMaxAgeSeconds         = NewSetting("auth-user-info-max-age-seconds", "3600") // 1 hour
	APIUIVersion                      = NewSetting("api-ui-version", "1.1.6")                // Please update the CATTLE_API_UI_VERSION in package/Dockerfile when updating the version here.
	RotateCertsIfExpiringInDays       = NewSetting("rotate-certs-if-expiring-in-days", "7")  // 7 days
	ClusterTemplateEnforcement        = NewSetting("cluster-template-enforcement", "false")
	InitialDockerRootDir              = NewSetting("initial-docker-root-dir", "/var/lib/docker")
	SystemCatalog                     = NewSetting("system-catalog", "external") // Options are 'external' or 'bundled'
	ChartDefaultBranch                = NewSetting("chart-default-branch", "dev-v2.6")
	PartnerChartDefaultBranch         = NewSetting("partner-chart-default-branch", "main")
	RKE2ChartDefaultBranch            = NewSetting("rke2-chart-default-branch", "main")
	FleetDefaultWorkspaceName         = NewSetting("fleet-default-workspace-name", "fleet-default") // fleetWorkspaceName to assign to clusters with none
	ShellImage                        = NewSetting("shell-image", "rancher/shell:v0.1.9")
	IgnoreNodeName                    = NewSetting("ignore-node-name", "") // nodes to ignore when syncing v1.node to v3.node
	NoDefaultAdmin                    = NewSetting("no-default-admin", "")
	RestrictedDefaultAdmin            = NewSetting("restricted-default-admin", "false") // When bootstrapping the admin for the first time, give them the global role restricted-admin
	AKSUpstreamRefresh                = NewSetting("aks-refresh", "300")
	EKSUpstreamRefreshCron            = NewSetting("eks-refresh-cron", "*/5 * * * *") // EKSUpstreamRefreshCron is deprecated and will be replaced by EKSUpstreamRefresh
	EKSUpstreamRefresh                = NewSetting("eks-refresh", "300")
	GKEUpstreamRefresh                = NewSetting("gke-refresh", "300")
	HideLocalCluster                  = NewSetting("hide-local-cluster", "false")
	MachineProvisionImage             = NewSetting("machine-provision-image", "rancher/machine:v0.15.0-rancher65")

	FleetMinVersion          = NewSetting("fleet-min-version", "")
	RancherWebhookMinVersion = NewSetting("rancher-webhook-min-version", "")
)

func FullShellImage() string {
	return PrefixPrivateRegistry(ShellImage.Get())
}

func PrefixPrivateRegistry(image string) string {
	private := SystemDefaultRegistry.Get()
	if private == "" {
		return image
	}
	return private + "/" + image
}

func IsRelease() bool {
	return !strings.Contains(ServerVersion.Get(), "head") && releasePattern.MatchString(ServerVersion.Get())
}

func init() {
	// setup auth setting
	authsettings.AuthUserInfoResyncCron = AuthUserInfoResyncCron
	authsettings.AuthUserSessionTTLMinutes = AuthUserSessionTTLMinutes
	authsettings.AuthUserInfoMaxAgeSeconds = AuthUserInfoMaxAgeSeconds
	authsettings.FirstLogin = FirstLogin

	if InjectDefaults == "" {
		return
	}
	defaults := map[string]string{}
	if err := json.Unmarshal([]byte(InjectDefaults), &defaults); err != nil {
		return
	}
	for name, defaultValue := range defaults {
		value, ok := settings[name]
		if !ok {
			continue
		}
		value.Default = defaultValue
		settings[name] = value
	}
}

type Provider interface {
	Get(name string) string
	Set(name, value string) error
	SetIfUnset(name, value string) error
	SetAll(settings map[string]Setting) error
}

type Setting struct {
	Name     string
	Default  string
	ReadOnly bool
}

func (s Setting) SetIfUnset(value string) error {
	if provider == nil {
		return s.Set(value)
	}
	return provider.SetIfUnset(s.Name, value)
}

func (s Setting) Set(value string) error {
	if provider == nil {
		s, ok := settings[s.Name]
		if ok {
			s.Default = value
			settings[s.Name] = s
		}
	} else {
		return provider.Set(s.Name, value)
	}
	return nil
}

func (s Setting) Get() string {
	if provider == nil {
		s := settings[s.Name]
		return s.Default
	}
	return provider.Get(s.Name)
}

func (s Setting) GetInt() int {
	v := s.Get()
	i, err := strconv.Atoi(v)
	if err == nil {
		return i
	}
	logrus.Errorf("failed to parse setting %s=%s as int: %v", s.Name, v, err)
	i, err = strconv.Atoi(s.Default)
	if err != nil {
		return 0
	}
	return i
}

func SetProvider(p Provider) error {
	if err := p.SetAll(settings); err != nil {
		return err
	}
	provider = p
	return nil
}

func NewSetting(name, def string) Setting {
	s := Setting{
		Name:    name,
		Default: def,
	}
	settings[s.Name] = s
	return s
}

func GetEnvKey(key string) string {
	return "CATTLE_" + strings.ToUpper(strings.Replace(key, "-", "_", -1))
}

func getMetadataConfig() string {
	branch := KDMBranch.Get()
	data := map[string]interface{}{
		"url":                      fmt.Sprintf("https://releases.rancher.com/kontainer-driver-metadata/%s/data.json", branch),
		"refresh-interval-minutes": "1440",
	}
	ans, err := json.Marshal(data)
	if err != nil {
		logrus.Errorf("error getting metadata config %v", err)
		return ""
	}
	return string(ans)
}

// GetSettingByID returns a setting that is stored with the given id
func GetSettingByID(id string) string {
	if provider == nil {
		s := settings[id]
		return s.Default
	}
	return provider.Get(id)
}
<|endoftext|>


<commit_before>package setup

import (
	"fmt"
	"github.com/skatteetaten/aoc/pkg/cmdoptions"
	"io/ioutil"
	"strings"
	"testing"
	"unicode"
)

func TestExecuteSetup(t *testing.T) {
	//var expected int = OPERATION_OKs
	var args []string = make([]string, 1)
	var overrideFiles []string
	var err error
	var out string
	var persistentOptions cmdoptions.CommonCommandOptions

	args[0] = "testfiles/utv/about.json"
	persistentOptions.DryRun = true
	out, err = ExecuteSetup(args, overrideFiles, &persistentOptions)
	if err != nil {
		t.Errorf("Failed simple dryrun: %v", err.Error())
	} else {
		fileJson, err := ioutil.ReadFile("testresult.json")
		if err != nil {
			t.Errorf("Unable to read testresult.json")
		}
		fileJsonStr := stripSpaces(string(fileJson))
		fmt.Println("Testresult:")
		fmt.Println(fileJsonStr)
		out := stripSpaces(out)
		fmt.Println("Out:")
		fmt.Println(out)
		if strings.Compare(out, fileJsonStr) != 0 {
			t.Errorf("Dryrun result different than expected")
		}
	}

	args = make([]string, 2)
	args[0] = "testfiles/utv/about.json"
	args[1] = "{\"Game\": \"Thrones\"}"
	_, err = ExecuteSetup(args, overrideFiles, &persistentOptions)
	if err == nil {
		t.Errorf("Did not detect missing file reference")
	}

	overrideFiles = make([]string, 2)
	overrideFiles[0] = "File1"
	overrideFiles[1] = "File2"

	_, err = ExecuteSetup(args, overrideFiles, &persistentOptions)
	if err == nil {
		t.Errorf("Did not detect missing configuration")
	}
}

func stripSpaces(str string) string {
	return strings.Map(func(r rune) rune {
		if unicode.IsSpace(r) {
			// if the character is a space, drop it
			return -1
		}
		// else keep it in the string
		return r
	}, str)
}
<commit_msg>Setup refactored to a struct-based "class"<commit_after>package setup

import (
	"fmt"
	"github.com/skatteetaten/aoc/pkg/cmdoptions"
	"io/ioutil"
	"strings"
	"testing"
	"unicode"
)

func TestExecuteSetup(t *testing.T) {
	//var expected int = OPERATION_OKs
	var args []string = make([]string, 1)
	var overrideFiles []string
	var err error
	var out string
	var persistentOptions cmdoptions.CommonCommandOptions
	var setupClass SetupClass

	args[0] = "testfiles/utv/about.json"
	persistentOptions.DryRun = true
	out, err = setupClass.ExecuteSetup(args, overrideFiles, &persistentOptions)
	if err != nil {
		t.Errorf("Failed simple dryrun: %v", err.Error())
	} else {
		fileJson, err := ioutil.ReadFile("testresult.json")
		if err != nil {
			t.Errorf("Unable to read testresult.json")
		}
		fileJsonStr := stripSpaces(string(fileJson))
		fmt.Println("Testresult:")
		fmt.Println(fileJsonStr)
		out := stripSpaces(out)
		fmt.Println("Out:")
		fmt.Println(out)
		if strings.Compare(out, fileJsonStr) != 0 {
			t.Errorf("Dryrun result different than expected")
		}
	}

	args = make([]string, 2)
	args[0] = "testfiles/utv/about.json"
	args[1] = "{\"Game\": \"Thrones\"}"
	_, err = setupClass.ExecuteSetup(args, overrideFiles, &persistentOptions)
	if err == nil {
		t.Errorf("Did not detect missing file reference")
	}

	overrideFiles = make([]string, 2)
	overrideFiles[0] = "File1"
	overrideFiles[1] = "File2"

	_, err = setupClass.ExecuteSetup(args, overrideFiles, &persistentOptions)
	if err == nil {
		t.Errorf("Did not detect missing configuration")
	}
}

func stripSpaces(str string) string {
	return strings.Map(func(r rune) rune {
		if unicode.IsSpace(r) {
			// if the character is a space, drop it
			return -1
		}
		// else keep it in the string
		return r
	}, str)
}
<|endoftext|>


<commit_before>package util

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/rmohr/go-model"
)

var _ = Describe("Mapper", func() {

	type E struct {
		X string
	}

	type F struct {
		X string
	}

	type A struct {
		X E
	}

	type B struct {
		X F
	}

	type C struct {
		X *E
	}

	type D struct {
		X *F
	}

	BeforeSuite(func() {
		AddConversion(&E{}, &F{})
		AddPtrConversion((**E)(nil), (**F)(nil))
	})

	Describe("converting objects", func() {
		Context("with concrete types", func() {
			It("should succeed from srt to dest", func() {

				a := &A{X: E{X: "test"}}
				b := &B{}
				errs := model.Copy(b, a)
				Expect(errs).To(BeEmpty())
				Expect(b.X.X).To(Equal("test"))
			})
			It("should succeed from dest to src", func() {

				b := &B{X: F{X: "test"}}
				a := &A{}
				errs := model.Copy(a, b)
				Expect(errs).To(BeEmpty())
				Expect(a.X.X).To(Equal("test"))
			})
		})
		Context("with pointer types", func() {
			It("should succeed from src to dest", func() {

				c := &C{X: &E{X: "test"}}
				d := &D{}
				errs := model.Copy(d, c)
				Expect(errs).To(BeEmpty())
				Expect(d.X.X).To(Equal("test"))
			})
			It("should succeed from dest to src", func() {

				d := &D{X: &F{X: "test"}}
				c := &C{}
				errs := model.Copy(c, d)
				Expect(errs).To(BeEmpty())
				Expect(c.X.X).To(Equal("test"))
			})
		})
	})

	AfterSuite(func() {

	})
})
<commit_msg>Fix wrong import in mapper tests<commit_after>package util

import (
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"github.com/jeevatkm/go-model"
)

var _ = Describe("Mapper", func() {

	type E struct {
		X string
	}

	type F struct {
		X string
	}

	type A struct {
		X E
	}

	type B struct {
		X F
	}

	type C struct {
		X *E
	}

	type D struct {
		X *F
	}

	BeforeSuite(func() {
		AddConversion(&E{}, &F{})
		AddPtrConversion((**E)(nil), (**F)(nil))
	})

	Describe("converting objects", func() {
		Context("with concrete types", func() {
			It("should succeed from srt to dest", func() {

				a := &A{X: E{X: "test"}}
				b := &B{}
				errs := model.Copy(b, a)
				Expect(errs).To(BeEmpty())
				Expect(b.X.X).To(Equal("test"))
			})
			It("should succeed from dest to src", func() {

				b := &B{X: F{X: "test"}}
				a := &A{}
				errs := model.Copy(a, b)
				Expect(errs).To(BeEmpty())
				Expect(a.X.X).To(Equal("test"))
			})
		})
		Context("with pointer types", func() {
			It("should succeed from src to dest", func() {

				c := &C{X: &E{X: "test"}}
				d := &D{}
				errs := model.Copy(d, c)
				Expect(errs).To(BeEmpty())
				Expect(d.X.X).To(Equal("test"))
			})
			It("should succeed from dest to src", func() {

				d := &D{X: &F{X: "test"}}
				c := &C{}
				errs := model.Copy(c, d)
				Expect(errs).To(BeEmpty())
				Expect(c.X.X).To(Equal("test"))
			})
		})
	})

	AfterSuite(func() {

	})
})
<|endoftext|>


<commit_before>// Copyright 2012 The Plotinum Authors. All rights reserved.
// Use of this source code is governed by an MIT-style license
// that can be found in the LICENSE file.

package plotter

import (
	"code.google.com/p/plotinum/plot"
	"code.google.com/p/plotinum/vg"
	"fmt"
	"image/color"
	"math"
	"math/rand"
	"testing"
)

func TestDrawPng(t *testing.T) {
	if err := Example_bubbles().Save(4, 4, "test.png"); err != nil {
		t.Error(err)
	}
}

func TestDrawEps(t *testing.T) {
	if err := Example_points().Save(4, 4, "test.eps"); err != nil {
		t.Error(err)
	}
}

func TestDrawSvg(t *testing.T) {
	if err := Example_points().Save(4, 4, "test.svg"); err != nil {
		t.Error(err)
	}
}

func TestDrawTiff(t *testing.T) {
	if err := Example_points().Save(4, 4, "test.tiff"); err != nil {
		t.Error(err)
	}
}

func TestDrawJpg(t *testing.T) {
	if err := Example_points().Save(4, 4, "test.jpg"); err != nil {
		t.Error(err)
	}
}

func TestDrawPdf(t *testing.T) {
	if err := Example_points().Save(4, 4, "test.pdf"); err != nil {
		t.Error(err)
	}
}

// Example_functions draws some functions.
func Example_functions() *plot.Plot {
	p, err := plot.New()
	if err != nil {
		panic(err)
	}
	p.Title.Text = "Functions"
	p.X.Label.Text = "X"
	p.Y.Label.Text = "Y"

	quad := NewFunction(func(x float64) float64 { return x * x })
	quad.Color = color.RGBA{B: 255, A: 255}

	exp := NewFunction(func(x float64) float64 { return math.Pow(2, x) })
	exp.Dashes = []vg.Length{vg.Points(2), vg.Points(2)}
	exp.Width = vg.Points(2)
	exp.Color = color.RGBA{G: 255, A: 255}

	sin := NewFunction(func(x float64) float64 { return 10*math.Sin(x) + 50 })
	sin.Dashes = []vg.Length{vg.Points(4), vg.Points(5)}
	sin.Width = vg.Points(4)
	sin.Color = color.RGBA{R: 255, A: 255}

	p.Add(quad, exp, sin)
	p.Legend.Add("x^2", quad)
	p.Legend.Add("2^x", exp)
	p.Legend.Add("10*sin(x)+50", sin)
	p.Legend.ThumbnailWidth = vg.Inches(0.5)

	p.X.Min = 0
	p.X.Max = 10
	p.Y.Min = 0
	p.Y.Max = 100
	return p
}

// Example_boxPlots draws vertical boxplots.
func Example_boxPlots() *plot.Plot {
	rand.Seed(int64(0))
	n := 100
	uniform := make(Values, n)
	normal := make(Values, n)
	expon := make(Values, n)
	for i := 0; i < n; i++ {
		uniform[i] = rand.Float64()
		normal[i] = rand.NormFloat64()
		expon[i] = rand.ExpFloat64()
	}

	p, err := plot.New()
	if err != nil {
		panic(err)
	}
	p.Title.Text = "Box Plot"
	p.Y.Label.Text = "Values"

	// Make boxes for our data and add them to the plot.
	p.Add(NewBoxPlot(vg.Points(20), 0, uniform),
		NewBoxPlot(vg.Points(20), 1, normal),
		NewBoxPlot(vg.Points(20), 2, expon))

	// Set the X axis of the plot to nominal with
	// the given names for x=0, x=1 and x=2.
	p.NominalX("Uniform\nDistribution", "Normal\nDistribution",
		"Exponential\nDistribution")
	return p
}

// Example_verticalBoxPlots draws vertical boxplots
// with some labels on their points.
func Example_verticalBoxPlots() *plot.Plot {
	rand.Seed(int64(0))
	n := 100
	uniform := make(ValueLabels, n)
	normal := make(ValueLabels, n)
	expon := make(ValueLabels, n)
	for i := 0; i < n; i++ {
		uniform[i].Value = rand.Float64()
		uniform[i].Label = fmt.Sprintf("%4.4f", uniform[i].Value)
		normal[i].Value = rand.NormFloat64()
		normal[i].Label = fmt.Sprintf("%4.4f", normal[i].Value)
		expon[i].Value = rand.ExpFloat64()
		expon[i].Label = fmt.Sprintf("%4.4f", expon[i].Value)
	}

	p, err := plot.New()
	if err != nil {
		panic(err)
	}
	p.Title.Text = "Box Plot"
	p.Y.Label.Text = "Values"

	// Make boxes for our data and add them to the plot.
	uniBox := NewBoxPlot(vg.Points(20), 0, uniform)
	uniLabels, err := uniBox.OutsideLabels(uniform)
	if err != nil {
		panic(err)
	}

	normBox := NewBoxPlot(vg.Points(20), 1, normal)
	normLabels, err := normBox.OutsideLabels(normal)
	if err != nil {
		panic(err)
	}

	expBox := NewBoxPlot(vg.Points(20), 2, expon)
	expLabels, err := expBox.OutsideLabels(expon)
	if err != nil {
		panic(err)
	}

	p.Add(uniBox, uniLabels, normBox, normLabels, expBox, expLabels)

	// Set the X axis of the plot to nominal with
	// the given names for x=0, x=1 and x=2.
	p.NominalX("Uniform\nDistribution", "Normal\nDistribution",
		"Exponential\nDistribution")
	return p
}

// Example_horizontalBoxPlots draws horizontal boxplots
// with some labels on their points.
func Example_horizontalBoxPlots() *plot.Plot {
	rand.Seed(int64(0))
	n := 100
	uniform := make(ValueLabels, n)
	normal := make(ValueLabels, n)
	expon := make(ValueLabels, n)
	for i := 0; i < n; i++ {
		uniform[i].Value = rand.Float64()
		uniform[i].Label = fmt.Sprintf("%4.4f", uniform[i].Value)
		normal[i].Value = rand.NormFloat64()
		normal[i].Label = fmt.Sprintf("%4.4f", normal[i].Value)
		expon[i].Value = rand.ExpFloat64()
		expon[i].Label = fmt.Sprintf("%4.4f", expon[i].Value)
	}

	p, err := plot.New()
	if err != nil {
		panic(err)
	}
	p.Title.Text = "Horizontal Box Plot"
	p.X.Label.Text = "Values"

	// Make boxes for our data and add them to the plot.
	uniBox := HorizBoxPlot{NewBoxPlot(vg.Points(20), 0, uniform)}
	uniLabels, err := uniBox.OutsideLabels(uniform)
	if err != nil {
		panic(err)
	}

	normBox := HorizBoxPlot{NewBoxPlot(vg.Points(20), 1, normal)}
	normLabels, err := normBox.OutsideLabels(normal)
	if err != nil {
		panic(err)
	}

	expBox := HorizBoxPlot{NewBoxPlot(vg.Points(20), 2, expon)}
	expLabels, err := expBox.OutsideLabels(expon)
	if err != nil {
		panic(err)
	}
	p.Add(uniBox, uniLabels, normBox, normLabels, expBox, expLabels)

	// Add a GlyphBox plotter for debugging.
	p.Add(NewGlyphBoxes())

	// Set the Y axis of the plot to nominal with
	// the given names for y=0, y=1 and y=2.
	p.NominalY("Uniform\nDistribution", "Normal\nDistribution",
		"Exponential\nDistribution")
	return p
}

// Example_points draws some scatter points, a line,
// and a line with points.
func Example_points() *plot.Plot {
	rand.Seed(int64(0))

	n := 15
	scatterData := randomPoints(n)
	lineData := randomPoints(n)
	linePointsData := randomPoints(n)

	p, err := plot.New()
	if err != nil {
		panic(err)
	}
	p.Title.Text = "Points Example"
	p.X.Label.Text = "X"
	p.Y.Label.Text = "Y"
	p.Add(NewGrid())

	s := NewScatter(scatterData)
	s.GlyphStyle.Color = color.RGBA{R: 255, B: 128, A: 255}
	s.GlyphStyle.Radius = vg.Points(3)

	l := NewLine(lineData)
	l.LineStyle.Width = vg.Points(1)
	l.LineStyle.Dashes = []vg.Length{vg.Points(5), vg.Points(5)}
	l.LineStyle.Color = color.RGBA{B: 255, A: 255}

	lp := NewLinePoints(linePointsData)
	lp.LineStyle.Color = color.RGBA{G: 255, A: 255}
	lp.GlyphStyle.Shape = plot.CircleGlyph{}
	lp.GlyphStyle.Color = color.RGBA{R: 255, A: 255}

	p.Add(s, l, lp)
	p.Legend.Add("scatter", s)
	p.Legend.Add("line", l)
	p.Legend.Add("line points", lp)

	return p
}

// randomPoints returns some random x, y points
// with some interesting kind of trend.
func randomPoints(n int) XYs {
	pts := make(XYs, n)
	for i := range pts {
		if i == 0 {
			pts[i].X = rand.Float64()
		} else {
			pts[i].X = pts[i-1].X + rand.Float64()
		}
		pts[i].Y = pts[i].X + 10*rand.Float64()
	}
	return pts
}

func Example_bubbles() *plot.Plot{
	rand.Seed(int64(0))
	n := 10
	bubbleData := randomTriples(n)

	p, err := plot.New()
	if err != nil {
		panic(err)
	}
	p.Title.Text = "Points Bubbles"
	p.X.Label.Text = "X"
	p.Y.Label.Text = "Y"

	bs := NewBubbles(bubbleData, vg.Points(1), vg.Points(20))
	bs.Color = color.RGBA{R:196, B:128, A: 255}
	p.Add(bs)

	return p
}

// randomTriples returns some random x, y, z triples
// with some interesting kind of trend.
func randomTriples(n int) XYZs {
	data := make(XYZs, n)
	for i := range data {
		if i == 0 {
			data[i].X = rand.Float64()
		} else {
			data[i].X = data[i-1].X + 2*rand.Float64()
		}
		data[i].Y = data[i].X + 10*rand.Float64()
		data[i].Z = data[i].X
	}
	return data
}

// An example of making a histogram.
func Example_histogram() *plot.Plot {
	rand.Seed(int64(0))
	n := 10000
	vals := make(XYs, n)
	for i := 0; i < n; i++ {
		vals[i].X = rand.NormFloat64()
		vals[i].Y = 1
	}

	p, err := plot.New()
	if err != nil {
		panic(err)
	}
	p.Title.Text = "Histogram"
	h := NewHistogram(vals, 16)
	h.Normalize(1)
	p.Add(h)

	// The normal distribution function
	norm := NewFunction(stdNorm)
	norm.Color = color.RGBA{R: 255, A: 255}
	norm.Width = vg.Points(2)
	p.Add(norm)

	return p
}

// stdNorm returns the probability of drawing a
// value from a standard normal distribution.
func stdNorm(x float64) float64 {
	const sigma = 1.0
	const mu = 0.0
	const root2π = 2.50662827459517818309
	return 1.0 / (sigma * root2π) * math.Exp(-((x-mu)*(x-mu))/(2*sigma*sigma))
}

func TestEmpty(t *testing.T) {
	p, err := plot.New()
	if err != nil {
		t.Error(err)
	}

	if err := p.Save(4, 4, "empty.svg"); err != nil {
		t.Error(err)
	}
}
<commit_msg>Change the title of the example bubble plot.<commit_after>// Copyright 2012 The Plotinum Authors. All rights reserved.
// Use of this source code is governed by an MIT-style license
// that can be found in the LICENSE file.

package plotter

import (
	"code.google.com/p/plotinum/plot"
	"code.google.com/p/plotinum/vg"
	"fmt"
	"image/color"
	"math"
	"math/rand"
	"testing"
)

func TestDrawPng(t *testing.T) {
	if err := Example_bubbles().Save(4, 4, "test.png"); err != nil {
		t.Error(err)
	}
}

func TestDrawEps(t *testing.T) {
	if err := Example_points().Save(4, 4, "test.eps"); err != nil {
		t.Error(err)
	}
}

func TestDrawSvg(t *testing.T) {
	if err := Example_points().Save(4, 4, "test.svg"); err != nil {
		t.Error(err)
	}
}

func TestDrawTiff(t *testing.T) {
	if err := Example_points().Save(4, 4, "test.tiff"); err != nil {
		t.Error(err)
	}
}

func TestDrawJpg(t *testing.T) {
	if err := Example_points().Save(4, 4, "test.jpg"); err != nil {
		t.Error(err)
	}
}

func TestDrawPdf(t *testing.T) {
	if err := Example_points().Save(4, 4, "test.pdf"); err != nil {
		t.Error(err)
	}
}

// Example_functions draws some functions.
func Example_functions() *plot.Plot {
	p, err := plot.New()
	if err != nil {
		panic(err)
	}
	p.Title.Text = "Functions"
	p.X.Label.Text = "X"
	p.Y.Label.Text = "Y"

	quad := NewFunction(func(x float64) float64 { return x * x })
	quad.Color = color.RGBA{B: 255, A: 255}

	exp := NewFunction(func(x float64) float64 { return math.Pow(2, x) })
	exp.Dashes = []vg.Length{vg.Points(2), vg.Points(2)}
	exp.Width = vg.Points(2)
	exp.Color = color.RGBA{G: 255, A: 255}

	sin := NewFunction(func(x float64) float64 { return 10*math.Sin(x) + 50 })
	sin.Dashes = []vg.Length{vg.Points(4), vg.Points(5)}
	sin.Width = vg.Points(4)
	sin.Color = color.RGBA{R: 255, A: 255}

	p.Add(quad, exp, sin)
	p.Legend.Add("x^2", quad)
	p.Legend.Add("2^x", exp)
	p.Legend.Add("10*sin(x)+50", sin)
	p.Legend.ThumbnailWidth = vg.Inches(0.5)

	p.X.Min = 0
	p.X.Max = 10
	p.Y.Min = 0
	p.Y.Max = 100
	return p
}

// Example_boxPlots draws vertical boxplots.
func Example_boxPlots() *plot.Plot {
	rand.Seed(int64(0))
	n := 100
	uniform := make(Values, n)
	normal := make(Values, n)
	expon := make(Values, n)
	for i := 0; i < n; i++ {
		uniform[i] = rand.Float64()
		normal[i] = rand.NormFloat64()
		expon[i] = rand.ExpFloat64()
	}

	p, err := plot.New()
	if err != nil {
		panic(err)
	}
	p.Title.Text = "Box Plot"
	p.Y.Label.Text = "Values"

	// Make boxes for our data and add them to the plot.
	p.Add(NewBoxPlot(vg.Points(20), 0, uniform),
		NewBoxPlot(vg.Points(20), 1, normal),
		NewBoxPlot(vg.Points(20), 2, expon))

	// Set the X axis of the plot to nominal with
	// the given names for x=0, x=1 and x=2.
	p.NominalX("Uniform\nDistribution", "Normal\nDistribution",
		"Exponential\nDistribution")
	return p
}

// Example_verticalBoxPlots draws vertical boxplots
// with some labels on their points.
func Example_verticalBoxPlots() *plot.Plot {
	rand.Seed(int64(0))
	n := 100
	uniform := make(ValueLabels, n)
	normal := make(ValueLabels, n)
	expon := make(ValueLabels, n)
	for i := 0; i < n; i++ {
		uniform[i].Value = rand.Float64()
		uniform[i].Label = fmt.Sprintf("%4.4f", uniform[i].Value)
		normal[i].Value = rand.NormFloat64()
		normal[i].Label = fmt.Sprintf("%4.4f", normal[i].Value)
		expon[i].Value = rand.ExpFloat64()
		expon[i].Label = fmt.Sprintf("%4.4f", expon[i].Value)
	}

	p, err := plot.New()
	if err != nil {
		panic(err)
	}
	p.Title.Text = "Box Plot"
	p.Y.Label.Text = "Values"

	// Make boxes for our data and add them to the plot.
	uniBox := NewBoxPlot(vg.Points(20), 0, uniform)
	uniLabels, err := uniBox.OutsideLabels(uniform)
	if err != nil {
		panic(err)
	}

	normBox := NewBoxPlot(vg.Points(20), 1, normal)
	normLabels, err := normBox.OutsideLabels(normal)
	if err != nil {
		panic(err)
	}

	expBox := NewBoxPlot(vg.Points(20), 2, expon)
	expLabels, err := expBox.OutsideLabels(expon)
	if err != nil {
		panic(err)
	}

	p.Add(uniBox, uniLabels, normBox, normLabels, expBox, expLabels)

	// Set the X axis of the plot to nominal with
	// the given names for x=0, x=1 and x=2.
	p.NominalX("Uniform\nDistribution", "Normal\nDistribution",
		"Exponential\nDistribution")
	return p
}

// Example_horizontalBoxPlots draws horizontal boxplots
// with some labels on their points.
func Example_horizontalBoxPlots() *plot.Plot {
	rand.Seed(int64(0))
	n := 100
	uniform := make(ValueLabels, n)
	normal := make(ValueLabels, n)
	expon := make(ValueLabels, n)
	for i := 0; i < n; i++ {
		uniform[i].Value = rand.Float64()
		uniform[i].Label = fmt.Sprintf("%4.4f", uniform[i].Value)
		normal[i].Value = rand.NormFloat64()
		normal[i].Label = fmt.Sprintf("%4.4f", normal[i].Value)
		expon[i].Value = rand.ExpFloat64()
		expon[i].Label = fmt.Sprintf("%4.4f", expon[i].Value)
	}

	p, err := plot.New()
	if err != nil {
		panic(err)
	}
	p.Title.Text = "Horizontal Box Plot"
	p.X.Label.Text = "Values"

	// Make boxes for our data and add them to the plot.
	uniBox := HorizBoxPlot{NewBoxPlot(vg.Points(20), 0, uniform)}
	uniLabels, err := uniBox.OutsideLabels(uniform)
	if err != nil {
		panic(err)
	}

	normBox := HorizBoxPlot{NewBoxPlot(vg.Points(20), 1, normal)}
	normLabels, err := normBox.OutsideLabels(normal)
	if err != nil {
		panic(err)
	}

	expBox := HorizBoxPlot{NewBoxPlot(vg.Points(20), 2, expon)}
	expLabels, err := expBox.OutsideLabels(expon)
	if err != nil {
		panic(err)
	}
	p.Add(uniBox, uniLabels, normBox, normLabels, expBox, expLabels)

	// Add a GlyphBox plotter for debugging.
	p.Add(NewGlyphBoxes())

	// Set the Y axis of the plot to nominal with
	// the given names for y=0, y=1 and y=2.
	p.NominalY("Uniform\nDistribution", "Normal\nDistribution",
		"Exponential\nDistribution")
	return p
}

// Example_points draws some scatter points, a line,
// and a line with points.
func Example_points() *plot.Plot {
	rand.Seed(int64(0))

	n := 15
	scatterData := randomPoints(n)
	lineData := randomPoints(n)
	linePointsData := randomPoints(n)

	p, err := plot.New()
	if err != nil {
		panic(err)
	}
	p.Title.Text = "Points Example"
	p.X.Label.Text = "X"
	p.Y.Label.Text = "Y"
	p.Add(NewGrid())

	s := NewScatter(scatterData)
	s.GlyphStyle.Color = color.RGBA{R: 255, B: 128, A: 255}
	s.GlyphStyle.Radius = vg.Points(3)

	l := NewLine(lineData)
	l.LineStyle.Width = vg.Points(1)
	l.LineStyle.Dashes = []vg.Length{vg.Points(5), vg.Points(5)}
	l.LineStyle.Color = color.RGBA{B: 255, A: 255}

	lp := NewLinePoints(linePointsData)
	lp.LineStyle.Color = color.RGBA{G: 255, A: 255}
	lp.GlyphStyle.Shape = plot.CircleGlyph{}
	lp.GlyphStyle.Color = color.RGBA{R: 255, A: 255}

	p.Add(s, l, lp)
	p.Legend.Add("scatter", s)
	p.Legend.Add("line", l)
	p.Legend.Add("line points", lp)

	return p
}

// randomPoints returns some random x, y points
// with some interesting kind of trend.
func randomPoints(n int) XYs {
	pts := make(XYs, n)
	for i := range pts {
		if i == 0 {
			pts[i].X = rand.Float64()
		} else {
			pts[i].X = pts[i-1].X + rand.Float64()
		}
		pts[i].Y = pts[i].X + 10*rand.Float64()
	}
	return pts
}

func Example_bubbles() *plot.Plot{
	rand.Seed(int64(0))
	n := 10
	bubbleData := randomTriples(n)

	p, err := plot.New()
	if err != nil {
		panic(err)
	}
	p.Title.Text = "Bubbles"
	p.X.Label.Text = "X"
	p.Y.Label.Text = "Y"

	bs := NewBubbles(bubbleData, vg.Points(1), vg.Points(20))
	bs.Color = color.RGBA{R:196, B:128, A: 255}
	p.Add(bs)

	return p
}

// randomTriples returns some random x, y, z triples
// with some interesting kind of trend.
func randomTriples(n int) XYZs {
	data := make(XYZs, n)
	for i := range data {
		if i == 0 {
			data[i].X = rand.Float64()
		} else {
			data[i].X = data[i-1].X + 2*rand.Float64()
		}
		data[i].Y = data[i].X + 10*rand.Float64()
		data[i].Z = data[i].X
	}
	return data
}

// An example of making a histogram.
func Example_histogram() *plot.Plot {
	rand.Seed(int64(0))
	n := 10000
	vals := make(XYs, n)
	for i := 0; i < n; i++ {
		vals[i].X = rand.NormFloat64()
		vals[i].Y = 1
	}

	p, err := plot.New()
	if err != nil {
		panic(err)
	}
	p.Title.Text = "Histogram"
	h := NewHistogram(vals, 16)
	h.Normalize(1)
	p.Add(h)

	// The normal distribution function
	norm := NewFunction(stdNorm)
	norm.Color = color.RGBA{R: 255, A: 255}
	norm.Width = vg.Points(2)
	p.Add(norm)

	return p
}

// stdNorm returns the probability of drawing a
// value from a standard normal distribution.
func stdNorm(x float64) float64 {
	const sigma = 1.0
	const mu = 0.0
	const root2π = 2.50662827459517818309
	return 1.0 / (sigma * root2π) * math.Exp(-((x-mu)*(x-mu))/(2*sigma*sigma))
}

func TestEmpty(t *testing.T) {
	p, err := plot.New()
	if err != nil {
		t.Error(err)
	}

	if err := p.Save(4, 4, "empty.svg"); err != nil {
		t.Error(err)
	}
}
<|endoftext|>


<commit_before>/*
Copyright 2011 Google 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 s3 implements a generic Amazon S3 client, not specific
// to Camlistore.
package s3

import (
	"bytes"
	"encoding/base64"
	"encoding/xml"
	"errors"
	"fmt"
	"hash"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"strconv"

	"github.com/simonz05/util/httputil"
)

const maxList = 1000

// Client is an Amazon S3 client.
type Client struct {
	*Auth
	HTTPClient *http.Client // or nil for default client
}

type Bucket struct {
	Name         string
	CreationDate string // 2006-02-03T16:45:09.000Z
}

func (c *Client) httpClient() *http.Client {
	if c.HTTPClient != nil {
		return c.HTTPClient
	}
	return http.DefaultClient
}

func newReq(url_ string) *http.Request {
	req, err := http.NewRequest("GET", url_, nil)
	if err != nil {
		panic(fmt.Sprintf("s3 client; invalid URL: %v", err))
	}
	req.Header.Set("User-Agent", "go-camlistore-s3")
	return req
}

func (c *Client) Buckets() ([]*Bucket, error) {
	req := newReq("https://" + c.hostname() + "/")
	c.Auth.SignRequest(req)
	res, err := c.httpClient().Do(req)
	if err != nil {
		return nil, err
	}
	defer httputil.CloseBody(res.Body)
	if res.StatusCode != 200 {
		return nil, fmt.Errorf("s3: Unexpected status code %d fetching bucket list", res.StatusCode)
	}
	return parseListAllMyBuckets(res.Body)
}

func parseListAllMyBuckets(r io.Reader) ([]*Bucket, error) {
	type allMyBuckets struct {
		Buckets struct {
			Bucket []*Bucket
		}
	}
	var res allMyBuckets
	if err := xml.NewDecoder(r).Decode(&res); err != nil {
		return nil, err
	}
	return res.Buckets.Bucket, nil
}

// Returns 0, os.ErrNotExist if not on S3, otherwise reterr is real.
func (c *Client) Stat(name, bucket string) (size int64, reterr error) {
	req := newReq("http://" + bucket + "." + c.hostname() + "/" + name)
	req.Method = "HEAD"
	c.Auth.SignRequest(req)
	res, err := c.httpClient().Do(req)
	if err != nil {
		return 0, err
	}
	if res.Body != nil {
		defer res.Body.Close()
	}
	if res.StatusCode == http.StatusNotFound {
		return 0, os.ErrNotExist
	}
	return strconv.ParseInt(res.Header.Get("Content-Length"), 10, 64)
}

func (c *Client) PutObject(name, bucket string, md5 hash.Hash, size int64, body io.Reader) error {
	req := newReq("http://" + bucket + "." + c.hostname() + "/" + name)
	req.Method = "PUT"
	req.ContentLength = size
	if md5 != nil {
		b64 := new(bytes.Buffer)
		encoder := base64.NewEncoder(base64.StdEncoding, b64)
		encoder.Write(md5.Sum(nil))
		encoder.Close()
		req.Header.Set("Content-MD5", b64.String())
	}
	c.Auth.SignRequest(req)
	req.Body = ioutil.NopCloser(body)

	res, err := c.httpClient().Do(req)
	if res != nil && res.Body != nil {
		defer httputil.CloseBody(res.Body)
	}
	if err != nil {
		return err
	}
	if res.StatusCode != 200 {
		res.Write(os.Stderr)
		return fmt.Errorf("Got response code %d from s3", res.StatusCode)
	}
	return nil
}

type Item struct {
	Key  string
	Size int64
}

type listBucketResults struct {
	Contents    []*Item
	IsTruncated bool
}

// ListBucket returns 0 to maxKeys (inclusive) items from the provided
// bucket. Keys before startAt will be skipped. (This is the S3
// 'marker' value). If the length of the returned items is equal to
// maxKeys, there is no indication whether or not the returned list is
// truncated.
func (c *Client) ListBucket(bucket string, startAt string, maxKeys int) (items []*Item, err error) {
	if maxKeys < 0 {
		return nil, errors.New("invalid negative maxKeys")
	}
	marker := startAt
	for len(items) < maxKeys {
		fetchN := maxKeys - len(items)
		if fetchN > maxList {
			fetchN = maxList
		}
		var bres listBucketResults
		url_ := fmt.Sprintf("http://%s.%s/?marker=%s&max-keys=%d",
			bucket, c.hostname(), url.QueryEscape(marker), fetchN)
		req := newReq(url_)
		c.Auth.SignRequest(req)
		res, err := c.httpClient().Do(req)
		if err != nil {
			return nil, err
		}
		err = xml.NewDecoder(res.Body).Decode(&bres)
		httputil.CloseBody(res.Body)
		if err != nil {
			return nil, err
		}
		for _, it := range bres.Contents {
			if it.Key == marker && it.Key != startAt {
				// Skip first dup on pages 2 and higher.
				continue
			}
			if it.Key < startAt {
				return nil, fmt.Errorf("Unexpected response from Amazon: item key %q but wanted greater than %q", it.Key, startAt)
			}
			items = append(items, it)
			marker = it.Key
		}
		if !bres.IsTruncated {
			break
		}
	}
	return items, nil
}

func (c *Client) Get(bucket, key string) (body io.ReadCloser, size int64, err error) {
	url_ := fmt.Sprintf("http://%s.%s/%s", bucket, c.hostname(), key)
	req := newReq(url_)
	c.Auth.SignRequest(req)
	var res *http.Response
	res, err = c.httpClient().Do(req)
	if err != nil {
		return
	}
	if res.StatusCode != http.StatusOK && res != nil && res.Body != nil {
		defer func() {
			io.Copy(os.Stderr, res.Body)
		}()
	}
	if res.StatusCode == http.StatusNotFound {
		err = os.ErrNotExist
		return
	}
	if res.StatusCode != http.StatusOK {
		err = fmt.Errorf("Amazon HTTP error on GET: %d", res.StatusCode)
		return
	}
	return res.Body, res.ContentLength, nil
}

func (c *Client) Delete(bucket, key string) error {
	url_ := fmt.Sprintf("http://%s.%s/%s", bucket, c.hostname(), key)
	req := newReq(url_)
	req.Method = "DELETE"
	c.Auth.SignRequest(req)
	res, err := c.httpClient().Do(req)
	if err != nil {
		return err
	}
	if res != nil && res.Body != nil {
		defer res.Body.Close()
	}
	if res.StatusCode == http.StatusNotFound || res.StatusCode == http.StatusNoContent ||
		res.StatusCode == http.StatusOK {
		return nil
	}
	return fmt.Errorf("Amazon HTTP error on DELETE: %d", res.StatusCode)
}
<commit_msg>amazon: expose DEFAULT_ACL.<commit_after>/*
Copyright 2011 Google 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 s3 implements a generic Amazon S3 client, not specific
// to Camlistore.
package s3

import (
	"bytes"
	"encoding/base64"
	"encoding/xml"
	"errors"
	"fmt"
	"hash"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"strconv"

	"github.com/simonz05/util/httputil"
)

const maxList = 1000

// Client is an Amazon S3 client.
type Client struct {
	*Auth
	HTTPClient *http.Client // or nil for default client
	DefaultACL string
}

type Bucket struct {
	Name         string
	CreationDate string // 2006-02-03T16:45:09.000Z
}

func (c *Client) httpClient() *http.Client {
	if c.HTTPClient != nil {
		return c.HTTPClient
	}
	return http.DefaultClient
}

func newReq(url_ string) *http.Request {
	req, err := http.NewRequest("GET", url_, nil)
	if err != nil {
		panic(fmt.Sprintf("s3 client; invalid URL: %v", err))
	}
	req.Header.Set("User-Agent", "go-camlistore-s3")
	return req
}

func (c *Client) Buckets() ([]*Bucket, error) {
	req := newReq("https://" + c.hostname() + "/")
	c.Auth.SignRequest(req)
	res, err := c.httpClient().Do(req)
	if err != nil {
		return nil, err
	}
	defer httputil.CloseBody(res.Body)
	if res.StatusCode != 200 {
		return nil, fmt.Errorf("s3: Unexpected status code %d fetching bucket list", res.StatusCode)
	}
	return parseListAllMyBuckets(res.Body)
}

func parseListAllMyBuckets(r io.Reader) ([]*Bucket, error) {
	type allMyBuckets struct {
		Buckets struct {
			Bucket []*Bucket
		}
	}
	var res allMyBuckets
	if err := xml.NewDecoder(r).Decode(&res); err != nil {
		return nil, err
	}
	return res.Buckets.Bucket, nil
}

// Returns 0, os.ErrNotExist if not on S3, otherwise reterr is real.
func (c *Client) Stat(name, bucket string) (size int64, reterr error) {
	req := newReq("http://" + bucket + "." + c.hostname() + "/" + name)
	req.Method = "HEAD"
	c.Auth.SignRequest(req)
	res, err := c.httpClient().Do(req)
	if err != nil {
		return 0, err
	}
	if res.Body != nil {
		defer res.Body.Close()
	}
	if res.StatusCode == http.StatusNotFound {
		return 0, os.ErrNotExist
	}
	return strconv.ParseInt(res.Header.Get("Content-Length"), 10, 64)
}

func (c *Client) PutObject(name, bucket string, md5 hash.Hash, size int64, body io.Reader) error {
	req := newReq("http://" + bucket + "." + c.hostname() + "/" + name)
	req.Method = "PUT"
	req.ContentLength = size
	if md5 != nil {
		b64 := new(bytes.Buffer)
		encoder := base64.NewEncoder(base64.StdEncoding, b64)
		encoder.Write(md5.Sum(nil))
		encoder.Close()
		req.Header.Set("Content-MD5", b64.String())
	}
	if c.DefaultACL != "" {
		req.Header.Set("x-amz-acl", c.DefaultACL)
	}
	c.Auth.SignRequest(req)
	req.Body = ioutil.NopCloser(body)

	res, err := c.httpClient().Do(req)
	if res != nil && res.Body != nil {
		defer httputil.CloseBody(res.Body)
	}
	if err != nil {
		return err
	}
	if res.StatusCode != 200 {
		res.Write(os.Stderr)
		return fmt.Errorf("Got response code %d from s3", res.StatusCode)
	}
	return nil
}

type Item struct {
	Key  string
	Size int64
}

type listBucketResults struct {
	Contents    []*Item
	IsTruncated bool
}

// ListBucket returns 0 to maxKeys (inclusive) items from the provided
// bucket. Keys before startAt will be skipped. (This is the S3
// 'marker' value). If the length of the returned items is equal to
// maxKeys, there is no indication whether or not the returned list is
// truncated.
func (c *Client) ListBucket(bucket string, startAt string, maxKeys int) (items []*Item, err error) {
	if maxKeys < 0 {
		return nil, errors.New("invalid negative maxKeys")
	}
	marker := startAt
	for len(items) < maxKeys {
		fetchN := maxKeys - len(items)
		if fetchN > maxList {
			fetchN = maxList
		}
		var bres listBucketResults
		url_ := fmt.Sprintf("http://%s.%s/?marker=%s&max-keys=%d",
			bucket, c.hostname(), url.QueryEscape(marker), fetchN)
		req := newReq(url_)
		c.Auth.SignRequest(req)
		res, err := c.httpClient().Do(req)
		if err != nil {
			return nil, err
		}
		err = xml.NewDecoder(res.Body).Decode(&bres)
		httputil.CloseBody(res.Body)
		if err != nil {
			return nil, err
		}
		for _, it := range bres.Contents {
			if it.Key == marker && it.Key != startAt {
				// Skip first dup on pages 2 and higher.
				continue
			}
			if it.Key < startAt {
				return nil, fmt.Errorf("Unexpected response from Amazon: item key %q but wanted greater than %q", it.Key, startAt)
			}
			items = append(items, it)
			marker = it.Key
		}
		if !bres.IsTruncated {
			break
		}
	}
	return items, nil
}

func (c *Client) Get(bucket, key string) (body io.ReadCloser, size int64, err error) {
	url_ := fmt.Sprintf("http://%s.%s/%s", bucket, c.hostname(), key)
	req := newReq(url_)
	c.Auth.SignRequest(req)
	var res *http.Response
	res, err = c.httpClient().Do(req)
	if err != nil {
		return
	}
	if res.StatusCode != http.StatusOK && res != nil && res.Body != nil {
		defer func() {
			io.Copy(os.Stderr, res.Body)
		}()
	}
	if res.StatusCode == http.StatusNotFound {
		err = os.ErrNotExist
		return
	}
	if res.StatusCode != http.StatusOK {
		err = fmt.Errorf("Amazon HTTP error on GET: %d", res.StatusCode)
		return
	}
	return res.Body, res.ContentLength, nil
}

func (c *Client) Delete(bucket, key string) error {
	url_ := fmt.Sprintf("http://%s.%s/%s", bucket, c.hostname(), key)
	req := newReq(url_)
	req.Method = "DELETE"
	c.Auth.SignRequest(req)
	res, err := c.httpClient().Do(req)
	if err != nil {
		return err
	}
	if res != nil && res.Body != nil {
		defer res.Body.Close()
	}
	if res.StatusCode == http.StatusNotFound || res.StatusCode == http.StatusNoContent ||
		res.StatusCode == http.StatusOK {
		return nil
	}
	return fmt.Errorf("Amazon HTTP error on DELETE: %d", res.StatusCode)
}
<|endoftext|>


<commit_before>/**
 * link_test.go
 *
 * (C) Steven White 2015
 */

package link

import (
	"net/http"
	"testing"
)

func TestReadSingle(t *testing.T) {
	r, _ := http.NewRequest("GET", "", nil)
	r.Header.Add("Link", "<https://api.github.com/search/code?q=addClass+user%3Amozilla&page=2>; rel=\"next\"")

	g := Parse(r)

	if len(g) != 1 {
		t.Errorf("Incorrent number of links parsed")
	}
	if g["next"] == nil {
		t.Errorf("Unable to parse next link")
	}
}

func TestReadMultiple(t *testing.T) {
	r, _ := http.NewRequest("GET", "", nil)
	r.Header.Add("Link", "<https://api.github.com/search/code?q=addClass+user%3Amozilla&page=2>; rel=\"next\""+
		", <https://api.github.com/search/code?q=addClass+user%3Amozilla&page=34>; rel=\"last\"")

	g := Parse(r)

	if len(g) != 2 {
		t.Errorf("Incorrent number of links parsed")
	}
	if g["next"] == nil {
		t.Errorf("Unable to parse next link")
	}
	if g["last"] == nil {
		t.Errorf("Unable to parse last link")
	}
}

func TestNoLink(t *testing.T) {
	r, _ := http.NewRequest("GET", "", nil)
	g := Parse(r)

	if g != nil {
		t.Error("Failed to deliver nil for empty Link header")
	}
}
<commit_msg>Add test for extra fields<commit_after>/**
 * link_test.go
 *
 * (C) Steven White 2015
 */

package link

import (
	"net/http"
	"testing"
)

func TestReadSingle(t *testing.T) {
	r, _ := http.NewRequest("GET", "", nil)
	r.Header.Add("Link", "<https://api.github.com/search/code?q=addClass+user%3Amozilla&page=2>; rel=\"next\"")

	g := Parse(r)

	if len(g) != 1 {
		t.Errorf("Incorrent number of links parsed")
	}
	if g["next"] == nil {
		t.Errorf("Unable to parse next link")
	}
}

func TestReadMultiple(t *testing.T) {
	r, _ := http.NewRequest("GET", "", nil)
	r.Header.Add("Link", "<https://api.github.com/search/code?q=addClass+user%3Amozilla&page=2>; rel=\"next\""+
		", <https://api.github.com/search/code?q=addClass+user%3Amozilla&page=34>; rel=\"last\"")

	g := Parse(r)

	if len(g) != 2 {
		t.Errorf("Incorrent number of links parsed")
	}
	if g["next"] == nil {
		t.Errorf("Unable to parse next link")
	}
	if g["last"] == nil {
		t.Errorf("Unable to parse last link")
	}
}

func TestExtra(t *testing.T) {
	r, _ := http.NewRequest("GET", "", nil)
	r.Header.Add("Link", "<https://api.github.com/search/code?q=addClass+user%3Amozilla&page=2>; rel=\"next\"; title=\"foobar\"")

	g := Parse(r)
	if len(g) != 1 {
		t.Errorf("Incorrent number of links parsed")
	}
	if g["next"] == nil {
		t.Errorf("Unable to parse next link")
	}
	if g["next"].Extra["title"] != "foobar" {
		t.Errorf("Failed to grab title extra")
	}
}

func TestNoLink(t *testing.T) {
	r, _ := http.NewRequest("GET", "", nil)
	g := Parse(r)

	if g != nil {
		t.Error("Failed to deliver nil for empty Link header")
	}
}
<|endoftext|>