<commit_before>// Package k8s is a facade over (super-terrible, very difficult to understand)
// client-go to provide a higher-level interface to Kubernetes, with support
// for simple, high-level APIs for watching resources (including from stable,
// long-running processes) and implementing basic controllers.
//
// It is intended to offer the same API for (nearly) every Kubernetes resource,
// including easy CRD access without codegen.
package k8s

import (
	"fmt"
	"strings"

	"k8s.io/apimachinery/pkg/api/meta"
	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"

	"k8s.io/client-go/discovery"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/restmapper"
	"k8s.io/client-go/tools/clientcmd"

	"github.com/google/shlex"
	"github.com/pkg/errors"
)

// KubeInfo holds the data required to talk to a cluster
type KubeInfo struct {
	Kubeconfig   string
	context      string
	namespace    string
	clientConfig clientcmd.ClientConfig
}

// NewKubeInfo returns a useable KubeInfo, handling optional
// kubeconfig, context, and namespace.
func NewKubeInfo(configfile, context, namespace string) *KubeInfo {
	// Find the correct kube config file
	configfilesearch := clientcmd.NewDefaultClientConfigLoadingRules()
	if len(configfile) != 0 {
		configfilesearch.ExplicitPath = configfile
	}

	// Possibly override context and namespace
	overrides := &clientcmd.ConfigOverrides{}
	if len(context) != 0 {
		overrides.CurrentContext = context
	}
	if len(namespace) != 0 {
		overrides.Context.Namespace = namespace
	}

	// Construct the config
	kubeconfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(configfilesearch, overrides)

	res := KubeInfo{
		configfile,
		context,
		namespace,
		kubeconfig,
	}

	return &res
}

// Context returns the context name of the KubeInfo.
func (info *KubeInfo) Context() (string, error) {
	// Extract context
	resultContext := info.context
	if len(info.context) == 0 {
		apiconfig, err := info.clientConfig.RawConfig()
		if err != nil {
			return "", err
		}
		resultContext = apiconfig.CurrentContext
	}
	return resultContext, nil
}

// Namespace returns the namespace for a KubeInfo.
func (info *KubeInfo) Namespace() (string, error) {
	// Extract namespace
	resultNamespace, _, err := info.clientConfig.Namespace()
	return resultNamespace, err
}

// GetRestConfig returns a REST config
func (info *KubeInfo) GetRestConfig() (*rest.Config, error) {
	/*
		// Do the right thing if you're running in a cluster
		if os.Getenv("KUBERNETES_SERVICE_HOST") != "" {
			inClusterConfig, err := rest.InClusterConfig()
			if err != nil {
				return nil, err
			}
			return inClusterConfig, nil
		}
	*/

	config, err := info.clientConfig.ClientConfig()
	if err != nil {
		return nil, err
	}
	return config, nil
}

// GetKubectl returns the arguments for a runnable kubectl command that talks to
// the same cluster as the associated ClientConfig.
func (info *KubeInfo) GetKubectl(args string) (string, error) {
	parts, err := shlex.Split(args)
	if err != nil {
		panic(err)
	}
	kargs, err := info.GetKubectlArray(parts...)
	if err != nil {
		return "", err
	}
	return strings.Join(kargs, " "), nil
}

// GetKubectlArray does what GetKubectl does but returns the result as a []string.
func (info *KubeInfo) GetKubectlArray(args ...string) ([]string, error) {
	res := []string{"kubectl"}
	if len(info.Kubeconfig) != 0 {
		res = append(res, "--kubeconfig", info.Kubeconfig)
	}
	context, err := info.Context()
	if err != nil {
		return nil, err
	}
	namespace, err := info.Namespace()
	if err != nil {
		return nil, err
	}
	res = append(res, "--context", context, "--namespace", namespace)
	res = append(res, args...)
	return res[1:], nil // Drop leading "kubectl" because reasons...
}

// Client is the top-level handle to the Kubernetes cluster.
type Client struct {
	config *rest.Config
	mapper meta.RESTMapper
}

// NewClient constructs a k8s.Client, optionally using a previously-constructed
// KubeInfo.
func NewClient(info *KubeInfo) (*Client, error) {
	if info == nil {
		info = NewKubeInfo("", "", "") // Empty file/ctx/ns for defaults
	}
	config, err := info.GetRestConfig()
	if err != nil {
		return nil, errors.Errorf("Failed to get REST config: %v", err)
	}

	disco, err := discovery.NewDiscoveryClientForConfig(config)
	if err != nil {
		return nil, err
	}

	resources, err := restmapper.GetAPIGroupResources(disco)
	if err != nil {
		return nil, err
	}

	return &Client{
		config: config,
		mapper: restmapper.NewShortcutExpander(restmapper.NewDiscoveryRESTMapper(resources), disco),
	}, nil
}

// ResourceType describes a Kubernetes resource type in a particular cluster.
// See ResolveResourceType() for more information.
//
// It is equivalent to a k8s.io/apimachinery/pkg/api/meta.RESTMapping
type ResourceType struct {
	Group      string
	Version    string
	Name       string // lowercase plural, called Resource in kubernetes code
	Kind       string // uppercase singular
	Namespaced bool
}

func (r ResourceType) String() string {
	return r.Name + "." + r.Version + "." + r.Group
}

// ResolveResourceType takes the name of a resource type (singular,
// plural, or an abbreviation; like you might pass to `kubectl get`)
// and returns cluster-specific canonical information about that
// resource type.
//
// For example, with Kubernetes v1.10.5:
//   "pod"        --> {Group: "",           Version: "v1",      Name: "pods",        Kind: "Pod",        Namespaced: true}
//   "deployment" --> {Group: "extensions", Version: "v1beta1", Name: "deployments", Kind: "Deployment", Namespaced: true}
//
// Newer versions of Kubernetes might instead put "pod" in the "core"
// group, or put "deployment" in apps/v1 instead of
// extensions/v1beta1.  Because of discrepancies between different
// clusters, it may be a good idea to use this even for internal
// callers, rather than treating it purely as a UI concern.
//
// This implementation is supposed to be equivalent to
// k8s.io/cli-runtime/pkg/genericclioptions/resource.Builder.mappingFor(),
// which calls
// k8s.io/apimachinery/pkg/runtime/schema.ParseResourceArg() and
// k8s.io/client-go/restmapper.shortcutExpander.expandResourceShortcut()
func (c *Client) ResolveResourceType(resource string) (ResourceType, error) {
	fullySpecifiedGVR, groupResource := schema.ParseResourceArg(resource)
	gvk := schema.GroupVersionKind{}

	if fullySpecifiedGVR != nil {
		gvk, _ = c.mapper.KindFor(*fullySpecifiedGVR)
	}
	if gvk.Empty() {
		gvk, _ = c.mapper.KindFor(groupResource.WithVersion(""))
	}
	if !gvk.Empty() {
		return wrapRESTMapping(c.mapper.RESTMapping(gvk.GroupKind(), gvk.Version))
	}

	fullySpecifiedGVK, groupKind := schema.ParseKindArg(resource)
	if fullySpecifiedGVK == nil {
		gvk := groupKind.WithVersion("")
		fullySpecifiedGVK = &gvk
	}

	if !fullySpecifiedGVK.Empty() {
		if mapping, err := c.mapper.RESTMapping(fullySpecifiedGVK.GroupKind(), fullySpecifiedGVK.Version); err == nil {
			return wrapRESTMapping(mapping, nil)
		}
	}

	mapping, err := c.mapper.RESTMapping(groupKind, gvk.Version)
	if err != nil {
		// if we error out here, it is because we could not match a resource or a kind
		// for the given argument. To maintain consistency with previous behavior,
		// announce that a resource type could not be found.
		// if the error is _not_ a *meta.NoKindMatchError, then we had trouble doing discovery,
		// so we should return the original error since it may help a user diagnose what is actually wrong
		if meta.IsNoMatchError(err) {
			return ResourceType{}, fmt.Errorf("the server doesn't have a resource type %q", groupResource.Resource)
		}
		return ResourceType{}, err
	}
	return wrapRESTMapping(mapping, err)
}

func wrapRESTMapping(m *meta.RESTMapping, err error) (ResourceType, error) {
	if err != nil || m == nil {
		return ResourceType{}, err
	}

	return ResourceType{
		Group:      m.GroupVersionKind.Group,
		Version:    m.GroupVersionKind.Version,
		Name:       m.Resource.Resource,
		Kind:       m.GroupVersionKind.Kind,
		Namespaced: m.Scope.Name() == meta.RESTScopeNameNamespace,
	}, nil
}

// List calls ListNamespace(...) with the empty string as the namespace, which
// means all namespaces if the resource is namespaced.
func (c *Client) List(resource string) ([]Resource, error) {
	return c.ListNamespace("", resource)
}

// ListNamespace returns a slice of Resources.
// If the resource is not namespaced, the namespace must be the empty string.
// If the resource is namespaced, the empty string lists across all namespaces.
func (c *Client) ListNamespace(namespace, resource string) ([]Resource, error) {
	return c.SelectiveList(namespace, resource, "", "")
}

func (c *Client) SelectiveList(namespace, resource, fieldSelector, labelSelector string) ([]Resource, error) {
	return c.ListQuery(Query{
		Kind:          resource,
		Namespace:     namespace,
		FieldSelector: fieldSelector,
		LabelSelector: labelSelector,
	})
}

// Query describes a query for a set of kubernetes resources.
//
// The Kind of a query may use any of the short names or abbreviations
// permitted by kubectl.
//
// If the Namespace field is the empty string, then all namespaces
// will be queried.
//
// The FieldSelector and LabelSelector fields contain field and label
// selectors as documented by kubectl.
type Query struct {
	Kind          string
	Namespace     string
	FieldSelector string
	LabelSelector string
	resourceType  ResourceType
}

func (q *Query) resolve(c *Client) error {
	if q.resourceType != (ResourceType{}) {
		return nil
	}

	rt, err := c.ResolveResourceType(q.Kind)
	if err != nil {
		return err
	}
	q.resourceType = rt
	return nil
}

// ListQuery returns all the kubernetes resources that satisfy the
// supplied query.
func (c *Client) ListQuery(query Query) ([]Resource, error) {
	err := query.resolve(c)
	if err != nil {
		return nil, err
	}

	ri := query.resourceType

	dyn, err := dynamic.NewForConfig(c.config)
	if err != nil {
		return nil, errors.Wrap(err, "failed to create dynamic context")
	}

	cli := dyn.Resource(schema.GroupVersionResource{
		Group:    ri.Group,
		Version:  ri.Version,
		Resource: ri.Name,
	})

	var filtered dynamic.ResourceInterface
	if query.Namespace != "" {
		filtered = cli.Namespace(query.Namespace)
	} else {
		filtered = cli
	}

	uns, err := filtered.List(v1.ListOptions{
		FieldSelector: query.FieldSelector,
		LabelSelector: query.LabelSelector,
	})
	if err != nil {
		return nil, err
	}

	result := make([]Resource, len(uns.Items))
	for idx, un := range uns.Items {
		result[idx] = un.UnstructuredContent()
	}
	return result, nil
}
<commit_msg>pkg/k8s: Reduce scope of more complexity for ResolveResourceType<commit_after>// Package k8s is a facade over (super-terrible, very difficult to understand)
// client-go to provide a higher-level interface to Kubernetes, with support
// for simple, high-level APIs for watching resources (including from stable,
// long-running processes) and implementing basic controllers.
//
// It is intended to offer the same API for (nearly) every Kubernetes resource,
// including easy CRD access without codegen.
package k8s

import (
	"fmt"
	"strings"

	"k8s.io/apimachinery/pkg/api/meta"
	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime/schema"

	"k8s.io/client-go/discovery"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/restmapper"
	"k8s.io/client-go/tools/clientcmd"

	"github.com/google/shlex"
	"github.com/pkg/errors"
)

// KubeInfo holds the data required to talk to a cluster
type KubeInfo struct {
	Kubeconfig   string
	context      string
	namespace    string
	clientConfig clientcmd.ClientConfig
}

// NewKubeInfo returns a useable KubeInfo, handling optional
// kubeconfig, context, and namespace.
func NewKubeInfo(configfile, context, namespace string) *KubeInfo {
	// Find the correct kube config file
	configfilesearch := clientcmd.NewDefaultClientConfigLoadingRules()
	if len(configfile) != 0 {
		configfilesearch.ExplicitPath = configfile
	}

	// Possibly override context and namespace
	overrides := &clientcmd.ConfigOverrides{}
	if len(context) != 0 {
		overrides.CurrentContext = context
	}
	if len(namespace) != 0 {
		overrides.Context.Namespace = namespace
	}

	// Construct the config
	kubeconfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(configfilesearch, overrides)

	res := KubeInfo{
		configfile,
		context,
		namespace,
		kubeconfig,
	}

	return &res
}

// Context returns the context name of the KubeInfo.
func (info *KubeInfo) Context() (string, error) {
	// Extract context
	resultContext := info.context
	if len(info.context) == 0 {
		apiconfig, err := info.clientConfig.RawConfig()
		if err != nil {
			return "", err
		}
		resultContext = apiconfig.CurrentContext
	}
	return resultContext, nil
}

// Namespace returns the namespace for a KubeInfo.
func (info *KubeInfo) Namespace() (string, error) {
	// Extract namespace
	resultNamespace, _, err := info.clientConfig.Namespace()
	return resultNamespace, err
}

// GetRestConfig returns a REST config
func (info *KubeInfo) GetRestConfig() (*rest.Config, error) {
	/*
		// Do the right thing if you're running in a cluster
		if os.Getenv("KUBERNETES_SERVICE_HOST") != "" {
			inClusterConfig, err := rest.InClusterConfig()
			if err != nil {
				return nil, err
			}
			return inClusterConfig, nil
		}
	*/

	config, err := info.clientConfig.ClientConfig()
	if err != nil {
		return nil, err
	}
	return config, nil
}

// GetKubectl returns the arguments for a runnable kubectl command that talks to
// the same cluster as the associated ClientConfig.
func (info *KubeInfo) GetKubectl(args string) (string, error) {
	parts, err := shlex.Split(args)
	if err != nil {
		panic(err)
	}
	kargs, err := info.GetKubectlArray(parts...)
	if err != nil {
		return "", err
	}
	return strings.Join(kargs, " "), nil
}

// GetKubectlArray does what GetKubectl does but returns the result as a []string.
func (info *KubeInfo) GetKubectlArray(args ...string) ([]string, error) {
	res := []string{"kubectl"}
	if len(info.Kubeconfig) != 0 {
		res = append(res, "--kubeconfig", info.Kubeconfig)
	}
	context, err := info.Context()
	if err != nil {
		return nil, err
	}
	namespace, err := info.Namespace()
	if err != nil {
		return nil, err
	}
	res = append(res, "--context", context, "--namespace", namespace)
	res = append(res, args...)
	return res[1:], nil // Drop leading "kubectl" because reasons...
}

// Client is the top-level handle to the Kubernetes cluster.
type Client struct {
	config *rest.Config

	restMapper      meta.RESTMapper
	discoveryClient discovery.DiscoveryInterface
}

// NewClient constructs a k8s.Client, optionally using a previously-constructed
// KubeInfo.
func NewClient(info *KubeInfo) (*Client, error) {
	if info == nil {
		info = NewKubeInfo("", "", "") // Empty file/ctx/ns for defaults
	}
	config, err := info.GetRestConfig()
	if err != nil {
		return nil, errors.Errorf("Failed to get REST config: %v", err)
	}

	// TODO(lukeshu): Optionally use a DiscoveryClient that does kubectl-like filesystem
	// caching; see k8s.io/cli-runtime/pkg/genericclioptions.ConfigFlags.ToDiscoveryClient().
	discoveryClient, err := discovery.NewDiscoveryClientForConfig(config)
	if err != nil {
		return nil, err
	}

	// TODO(lukeshu): Use a *restmapper.DeferredDiscoveryRESTMapper to lazily call
	// restmapper.GetAPIGroupResources().  This is blocked by discoveryClient implementing
	// discovery.DiscoveryInterface but not discovery.CachedDiscoveryInterface (probably
	// resolved with the above TODO).
	resources, err := restmapper.GetAPIGroupResources(discoveryClient)
	if err != nil {
		return nil, err
	}

	return &Client{
		config: config,

		restMapper:      restmapper.NewDiscoveryRESTMapper(resources),
		discoveryClient: discoveryClient,
	}, nil
}

// ResourceType describes a Kubernetes resource type in a particular cluster.
// See ResolveResourceType() for more information.
//
// It is equivalent to a k8s.io/apimachinery/pkg/api/meta.RESTMapping
type ResourceType struct {
	Group      string
	Version    string
	Name       string // lowercase plural, called Resource in kubernetes code
	Kind       string // uppercase singular
	Namespaced bool
}

func (r ResourceType) String() string {
	return r.Name + "." + r.Version + "." + r.Group
}

// ResolveResourceType takes the name of a resource type (singular,
// plural, or an abbreviation; like you might pass to `kubectl get`)
// and returns cluster-specific canonical information about that
// resource type.
//
// For example, with Kubernetes v1.10.5:
//   "pod"        --> {Group: "",           Version: "v1",      Name: "pods",        Kind: "Pod",        Namespaced: true}
//   "deployment" --> {Group: "extensions", Version: "v1beta1", Name: "deployments", Kind: "Deployment", Namespaced: true}
//
// Newer versions of Kubernetes might instead put "pod" in the "core"
// group, or put "deployment" in apps/v1 instead of
// extensions/v1beta1.  Because of discrepancies between different
// clusters, it may be a good idea to use this even for internal
// callers, rather than treating it purely as a UI concern.
func (c *Client) ResolveResourceType(resource string) (ResourceType, error) {
	shortcutExpander := restmapper.NewShortcutExpander(c.restMapper, c.discoveryClient)
	restmapping, err := mappingFor(resource, shortcutExpander)
	if err != nil {
		return ResourceType{}, err
	}
	return ResourceType{
		Group:      restmapping.GroupVersionKind.Group,
		Version:    restmapping.GroupVersionKind.Version,
		Name:       restmapping.Resource.Resource,
		Kind:       restmapping.GroupVersionKind.Kind,
		Namespaced: restmapping.Scope.Name() == meta.RESTScopeNameNamespace,
	}, nil
}

// mappingFor returns the RESTMapping for the Kind given, or the Kind referenced by the resource.
// Prefers a fully specified GroupVersionResource match. If one is not found, we match on a fully
// specified GroupVersionKind, or fallback to a match on GroupKind.
//
// This is copy/pasted from k8s.io/cli-runtime/pkg/resource.Builder.mappingFor() (which is
// unfortunately private), with modified lines marked with "// MODIFIED".
func mappingFor(resourceOrKindArg string, restMapper meta.RESTMapper) (*meta.RESTMapping, error) { // MODIFIED: args
	fullySpecifiedGVR, groupResource := schema.ParseResourceArg(resourceOrKindArg)
	gvk := schema.GroupVersionKind{}
	// MODIFIED: Don't call b.restMapperFn(), use the mapper given as an argument.

	if fullySpecifiedGVR != nil {
		gvk, _ = restMapper.KindFor(*fullySpecifiedGVR)
	}
	if gvk.Empty() {
		gvk, _ = restMapper.KindFor(groupResource.WithVersion(""))
	}
	if !gvk.Empty() {
		return restMapper.RESTMapping(gvk.GroupKind(), gvk.Version)
	}

	fullySpecifiedGVK, groupKind := schema.ParseKindArg(resourceOrKindArg)
	if fullySpecifiedGVK == nil {
		gvk := groupKind.WithVersion("")
		fullySpecifiedGVK = &gvk
	}

	if !fullySpecifiedGVK.Empty() {
		if mapping, err := restMapper.RESTMapping(fullySpecifiedGVK.GroupKind(), fullySpecifiedGVK.Version); err == nil {
			return mapping, nil
		}
	}

	mapping, err := restMapper.RESTMapping(groupKind, gvk.Version)
	if err != nil {
		// if we error out here, it is because we could not match a resource or a kind
		// for the given argument. To maintain consistency with previous behavior,
		// announce that a resource type could not be found.
		// if the error is _not_ a *meta.NoKindMatchError, then we had trouble doing discovery,
		// so we should return the original error since it may help a user diagnose what is actually wrong
		if meta.IsNoMatchError(err) {
			return nil, fmt.Errorf("the server doesn't have a resource type %q", groupResource.Resource)
		}
		return nil, err
	}

	return mapping, nil
}

// List calls ListNamespace(...) with the empty string as the namespace, which
// means all namespaces if the resource is namespaced.
func (c *Client) List(resource string) ([]Resource, error) {
	return c.ListNamespace("", resource)
}

// ListNamespace returns a slice of Resources.
// If the resource is not namespaced, the namespace must be the empty string.
// If the resource is namespaced, the empty string lists across all namespaces.
func (c *Client) ListNamespace(namespace, resource string) ([]Resource, error) {
	return c.SelectiveList(namespace, resource, "", "")
}

func (c *Client) SelectiveList(namespace, resource, fieldSelector, labelSelector string) ([]Resource, error) {
	return c.ListQuery(Query{
		Kind:          resource,
		Namespace:     namespace,
		FieldSelector: fieldSelector,
		LabelSelector: labelSelector,
	})
}

// Query describes a query for a set of kubernetes resources.
//
// The Kind of a query may use any of the short names or abbreviations
// permitted by kubectl.
//
// If the Namespace field is the empty string, then all namespaces
// will be queried.
//
// The FieldSelector and LabelSelector fields contain field and label
// selectors as documented by kubectl.
type Query struct {
	Kind          string
	Namespace     string
	FieldSelector string
	LabelSelector string
	resourceType  ResourceType
}

func (q *Query) resolve(c *Client) error {
	if q.resourceType != (ResourceType{}) {
		return nil
	}

	rt, err := c.ResolveResourceType(q.Kind)
	if err != nil {
		return err
	}
	q.resourceType = rt
	return nil
}

// ListQuery returns all the kubernetes resources that satisfy the
// supplied query.
func (c *Client) ListQuery(query Query) ([]Resource, error) {
	err := query.resolve(c)
	if err != nil {
		return nil, err
	}

	ri := query.resourceType

	dyn, err := dynamic.NewForConfig(c.config)
	if err != nil {
		return nil, errors.Wrap(err, "failed to create dynamic context")
	}

	cli := dyn.Resource(schema.GroupVersionResource{
		Group:    ri.Group,
		Version:  ri.Version,
		Resource: ri.Name,
	})

	var filtered dynamic.ResourceInterface
	if query.Namespace != "" {
		filtered = cli.Namespace(query.Namespace)
	} else {
		filtered = cli
	}

	uns, err := filtered.List(v1.ListOptions{
		FieldSelector: query.FieldSelector,
		LabelSelector: query.LabelSelector,
	})
	if err != nil {
		return nil, err
	}

	result := make([]Resource, len(uns.Items))
	for idx, un := range uns.Items {
		result[idx] = un.UnstructuredContent()
	}
	return result, nil
}
<|endoftext|>


<commit_before>package slinga

import (
	log "github.com/Sirupsen/logrus"
	"os"
)

// AptomiOject represents an aptomi entity, which gets stored in aptomi DB
type AptomiOject string

const (
	/*
		The following objects can be added to Aptomi
	*/

	// TypeCluster is k8s cluster or any other cluster
	TypeCluster AptomiOject = "cluster"

	// TypeService is service definitions
	TypeService AptomiOject = "service"

	// TypeContext is how service gets allocated
	TypeContext AptomiOject = "context"

	// TypeRules is global rules of the land
	TypeRules AptomiOject = "rules"

	// TypeDependencies is who requested what
	TypeDependencies AptomiOject = "dependencies"

	/*
		The following objects must be configured to point to external resources
	*/

	// TypeUsers is where users are stored (later in AD and LDAP)
	TypeUsers AptomiOject = "users"

	// TypeSecrets is where secret tokens are stored (later in Hashicorp Vault)
	TypeSecrets AptomiOject = "secrets"

	// TypeCharts is where binary charts/images are stored (later in external repo)
	TypeCharts AptomiOject = "charts"

	/*
		The following objects are generated by aptomi during or after dependency resolution via policy
	*/

	// TypePolicyResolution holds usage data for components/dependencies
	TypePolicyResolution AptomiOject = "resolution/usage"

	// TypeLogs contains debug logs
	TypeLogs AptomiOject = "resolution/logs"

	// TypeGraphics contains images generated by graphviz
	TypeGraphics AptomiOject = "resolution/graphics"
)

// AptomiObjectsCanBeModified contains a map of all objects which can be added to aptomi policy
//var AptomiObjectsCanBeModified = map[string]AptomiOject{
//	"cluster":      Clusters,
//	"service":      Services,
//	"context":      Contexts,
//	"rules":        Rules,
//	"dependencies": Dependencies,
//	"users":        Users,
//	"secrets":      Secrets,
//	"chart":        Charts,
//}

// Return aptomi DB directory
func getAptomiEnvVarAsDir(key string) string {
	value, ok := os.LookupEnv(key)
	if !ok {
		debug.WithFields(log.Fields{
			"var": key,
		}).Fatal("Environment variable is not present. Must point to a directory")
	}
	if stat, err := os.Stat(value); err != nil || !stat.IsDir() {
		debug.WithFields(log.Fields{
			"var":       key,
			"directory": value,
			"error":     err,
		}).Fatal("Directory doesn't exist or error encountered")
	}
	return value
}

// GetAptomiBaseDir returns base directory, i.e. the value of APTOMI_DB environment variable
func GetAptomiBaseDir() string {
	return getAptomiEnvVarAsDir("APTOMI_DB")
}

// GetAptomiObjectFilePatternYaml returns file pattern for aptomi objects (so they can be loaded from those files)
func GetAptomiObjectFilePatternYaml(baseDir string, aptomiObject AptomiOject) string {
	return baseDir + "/**/" + string(aptomiObject) + "*.yaml"
}

// GetAptomiObjectFilePatternTgz returns file pattern for tgz objects (so they can be loaded from those files)
func GetAptomiObjectFilePatternTgz(baseDir string, aptomiObject AptomiOject, chartName string) string {
	return baseDir + "/**/" + chartName + ".tgz"
}

// GetAptomiObjectWriteDir returns directory for aptomi objects (so they can be saved to this directory)
func GetAptomiObjectWriteDir(baseDir string, aptomiObject AptomiOject) string {
	return baseDir + "/" + string(aptomiObject)
}
<commit_msg>create dir if it doesn't exist<commit_after>package slinga

import (
	log "github.com/Sirupsen/logrus"
	"os"
)

// AptomiOject represents an aptomi entity, which gets stored in aptomi DB
type AptomiOject string

const (
	/*
		The following objects can be added to Aptomi
	*/

	// TypeCluster is k8s cluster or any other cluster
	TypeCluster AptomiOject = "cluster"

	// TypeService is service definitions
	TypeService AptomiOject = "service"

	// TypeContext is how service gets allocated
	TypeContext AptomiOject = "context"

	// TypeRules is global rules of the land
	TypeRules AptomiOject = "rules"

	// TypeDependencies is who requested what
	TypeDependencies AptomiOject = "dependencies"

	/*
		The following objects must be configured to point to external resources
	*/

	// TypeUsers is where users are stored (later in AD and LDAP)
	TypeUsers AptomiOject = "users"

	// TypeSecrets is where secret tokens are stored (later in Hashicorp Vault)
	TypeSecrets AptomiOject = "secrets"

	// TypeCharts is where binary charts/images are stored (later in external repo)
	TypeCharts AptomiOject = "charts"

	/*
		The following objects are generated by aptomi during or after dependency resolution via policy
	*/

	// TypePolicyResolution holds usage data for components/dependencies
	TypePolicyResolution AptomiOject = "resolution/usage"

	// TypeLogs contains debug logs
	TypeLogs AptomiOject = "resolution/logs"

	// TypeGraphics contains images generated by graphviz
	TypeGraphics AptomiOject = "resolution/graphics"
)

// AptomiObjectsCanBeModified contains a map of all objects which can be added to aptomi policy
//var AptomiObjectsCanBeModified = map[string]AptomiOject{
//	"cluster":      Clusters,
//	"service":      Services,
//	"context":      Contexts,
//	"rules":        Rules,
//	"dependencies": Dependencies,
//	"users":        Users,
//	"secrets":      Secrets,
//	"chart":        Charts,
//}

// Return aptomi DB directory
func getAptomiEnvVarAsDir(key string) string {
	value, ok := os.LookupEnv(key)
	if !ok {
		debug.WithFields(log.Fields{
			"var": key,
		}).Fatal("Environment variable is not present. Must point to a directory")
	}
	if stat, err := os.Stat(value); err != nil || !stat.IsDir() {
		debug.WithFields(log.Fields{
			"var":       key,
			"directory": value,
			"error":     err,
		}).Fatal("Directory doesn't exist or error encountered")
	}
	return value
}

// GetAptomiBaseDir returns base directory, i.e. the value of APTOMI_DB environment variable
func GetAptomiBaseDir() string {
	return getAptomiEnvVarAsDir("APTOMI_DB")
}

// GetAptomiObjectFilePatternYaml returns file pattern for aptomi objects (so they can be loaded from those files)
func GetAptomiObjectFilePatternYaml(baseDir string, aptomiObject AptomiOject) string {
	return baseDir + "/**/" + string(aptomiObject) + "*.yaml"
}

// GetAptomiObjectFilePatternTgz returns file pattern for tgz objects (so they can be loaded from those files)
func GetAptomiObjectFilePatternTgz(baseDir string, aptomiObject AptomiOject, chartName string) string {
	return baseDir + "/**/" + chartName + ".tgz"
}

// GetAptomiObjectWriteDir returns directory for aptomi objects (so they can be saved to this directory)
// It will create directory if it doesn't exist
func GetAptomiObjectWriteDir(baseDir string, aptomiObject AptomiOject) string {
	dir := baseDir + "/" + string(aptomiObject)
	if stat, err := os.Stat(dir); err != nil || !stat.IsDir() {
		_ = os.MkdirAll(dir, 0755)
	}
	if stat, err := os.Stat(dir); err != nil || !stat.IsDir() {
		debug.WithFields(log.Fields{
			"directory": dir,
			"error":     err,
		}).Fatal("Directory can't be created or error encountered")
	}
	return dir
}
<|endoftext|>


<commit_before>package store

import (
	"crypto/tls"
	"net"
	"net/http"
	"strings"
	"time"

	log "github.com/Sirupsen/logrus"
	etcd "github.com/coreos/go-etcd/etcd"
)

// Etcd embeds the client
type Etcd struct {
	client  *etcd.Client
	watches map[string]chan<- bool
}

// InitializeEtcd creates a new Etcd client given
// a list of endpoints and optional tls config
func InitializeEtcd(addrs []string, options Config) (Store, error) {
	s := &Etcd{}
	s.watches = make(map[string]chan<- bool)

	entries := createEndpoints(addrs, "http")
	s.client = etcd.NewClient(entries)

	if options.TLS != nil {
		s.setTLS(options.TLS)
	}

	if options.Timeout != 0 {
		s.setTimeout(options.Timeout)
	}

	return s, nil
}

// SetTLS sets the tls configuration given the path
// of certificate files
func (s *Etcd) setTLS(tls *tls.Config) {
	// Change to https scheme
	var addrs []string
	entries := s.client.GetCluster()
	for _, entry := range entries {
		addrs = append(addrs, strings.Replace(entry, "http", "https", -1))
	}
	s.client.SetCluster(addrs)

	// Set transport
	t := http.Transport{
		Dial: (&net.Dialer{
			Timeout:   30 * time.Second, // default timeout
			KeepAlive: 30 * time.Second,
		}).Dial,
		TLSHandshakeTimeout: 10 * time.Second,
		TLSClientConfig:     tls,
	}
	s.client.SetTransport(&t)
}

// SetTimeout sets the timeout used for connecting to the store
func (s *Etcd) setTimeout(time time.Duration) {
	s.client.SetDialTimeout(time)
}

// Create the entire path for a directory that does not exist
func (s *Etcd) createDirectory(path string) error {
	// TODO Handle TTL at key/dir creation -> use K/V struct for key infos?
	if _, err := s.client.CreateDir(format(path), 10); err != nil {
		if etcdError, ok := err.(*etcd.EtcdError); ok {
			if etcdError.ErrorCode != 105 { // Skip key already exists
				return err
			}
		} else {
			return err
		}
	}
	return nil
}

// Get the value at "key", returns the last modified index
// to use in conjunction to CAS calls
func (s *Etcd) Get(key string) (value []byte, lastIndex uint64, err error) {
	result, err := s.client.Get(format(key), false, false)
	if err != nil {
		if etcdError, ok := err.(*etcd.EtcdError); ok {
			// Not a Directory or Not a file
			if etcdError.ErrorCode == 102 || etcdError.ErrorCode == 104 {
				return nil, 0, ErrKeyNotFound
			}
		}
		return nil, 0, err
	}
	return []byte(result.Node.Value), result.Node.ModifiedIndex, nil
}

// Put a value at "key"
func (s *Etcd) Put(key string, value []byte) error {
	if _, err := s.client.Set(key, string(value), 0); err != nil {
		if etcdError, ok := err.(*etcd.EtcdError); ok {
			if etcdError.ErrorCode == 104 { // Not a directory
				// Remove the last element (the actual key) and set the prefix as a dir
				err = s.createDirectory(getDir(key))
				if _, err := s.client.Set(key, string(value), 0); err != nil {
					return err
				}
			}
		}
		return err
	}
	return nil
}

// Delete a value at "key"
func (s *Etcd) Delete(key string) error {
	if _, err := s.client.Delete(format(key), false); err != nil {
		return err
	}
	return nil
}

// Exists checks if the key exists inside the store
func (s *Etcd) Exists(key string) (bool, error) {
	value, _, err := s.Get(key)
	if err != nil {
		if err == ErrKeyNotFound || value == nil {
			return false, nil
		}
		return false, err
	}
	return true, nil
}

// Watch a single key for modifications
func (s *Etcd) Watch(key string, _ time.Duration, callback WatchCallback) error {
	key = format(key)
	watchChan := make(chan *etcd.Response)
	stopChan := make(chan bool)

	// Create new Watch entry
	s.watches[key] = stopChan

	// Start watch
	go s.client.Watch(key, 0, false, watchChan, stopChan)

	for _ = range watchChan {
		log.WithField("name", "etcd").Debug("Discovery watch triggered")
		entry, index, err := s.Get(key)
		if err != nil {
			log.Error("Cannot refresh the key: ", key, ", cancelling watch")
			s.watches[key] = nil
			return err
		}
		kvi := []KVEntry{&kviTuple{key, entry, index}}
		callback(kvi)
	}
	return nil
}

// CancelWatch cancels a watch, sends a signal to the appropriate
// stop channel
func (s *Etcd) CancelWatch(key string) error {
	key = format(key)
	if _, ok := s.watches[key]; !ok {
		log.Error("Chan does not exist for key: ", key)
		return ErrWatchDoesNotExist
	}
	// Send stop signal to event chan
	s.watches[key] <- true
	s.watches[key] = nil
	return nil
}

// AtomicPut put a value at "key" if the key has not been
// modified in the meantime, throws an error if this is the case
func (s *Etcd) AtomicPut(key string, oldValue []byte, newValue []byte, index uint64) (bool, error) {
	resp, err := s.client.CompareAndSwap(format(key), string(newValue), 5, string(oldValue), 0)
	if err != nil {
		return false, err
	}
	if !(resp.Node.Value == string(newValue) && resp.Node.Key == key && resp.Node.TTL == 5) {
		return false, ErrKeyModified
	}
	if !(resp.PrevNode.Value == string(newValue) && resp.PrevNode.Key == key && resp.PrevNode.TTL == 5) {
		return false, ErrKeyModified
	}
	return true, nil
}

// AtomicDelete deletes a value at "key" if the key has not
// been modified in the meantime, throws an error if this is the case
func (s *Etcd) AtomicDelete(key string, oldValue []byte, index uint64) (bool, error) {
	resp, err := s.client.CompareAndDelete(format(key), string(oldValue), 0)
	if err != nil {
		return false, err
	}
	if !(resp.PrevNode.Value == string(oldValue) && resp.PrevNode.Key == key && resp.PrevNode.TTL == 5) {
		return false, ErrKeyModified
	}
	return true, nil
}

// GetRange gets a range of values at "directory"
func (s *Etcd) GetRange(prefix string) ([]KVEntry, error) {
	resp, err := s.client.Get(format(prefix), true, true)
	if err != nil {
		return nil, err
	}
	kvi := make([]KVEntry, len(resp.Node.Nodes))
	for i, n := range resp.Node.Nodes {
		kvi[i] = &kviTuple{n.Key, []byte(n.Value), n.ModifiedIndex}
	}
	return kvi, nil
}

// DeleteRange deletes a range of values at "directory"
func (s *Etcd) DeleteRange(prefix string) error {
	if _, err := s.client.Delete(format(prefix), true); err != nil {
		return err
	}
	return nil
}

// WatchRange triggers a watch on a range of values at "directory"
func (s *Etcd) WatchRange(prefix string, filter string, _ time.Duration, callback WatchCallback) error {
	prefix = format(prefix)
	watchChan := make(chan *etcd.Response)
	stopChan := make(chan bool)

	// Create new Watch entry
	s.watches[prefix] = stopChan

	// Start watch
	go s.client.Watch(prefix, 0, true, watchChan, stopChan)
	for _ = range watchChan {
		log.WithField("name", "etcd").Debug("Discovery watch triggered")
		kvi, err := s.GetRange(prefix)
		if err != nil {
			log.Error("Cannot refresh the key: ", prefix, ", cancelling watch")
			s.watches[prefix] = nil
			return err
		}
		callback(kvi)
	}
	return nil
}

// CancelWatchRange stops the watch on the range of values, sends
// a signal to the appropriate stop channel
func (s *Etcd) CancelWatchRange(prefix string) error {
	return s.CancelWatch(format(prefix))
}

// Acquire the lock for "key"/"directory"
func (s *Etcd) Acquire(key string, value []byte) (string, error) {
	return "", ErrNotImplemented
}

// Release the lock for "key"/"directory"
func (s *Etcd) Release(session string) error {
	return ErrNotImplemented
}
<commit_msg>Add call to SyncCluster at etcd client initialization<commit_after>package store

import (
	"crypto/tls"
	"net"
	"net/http"
	"strings"
	"time"

	log "github.com/Sirupsen/logrus"
	etcd "github.com/coreos/go-etcd/etcd"
)

// Etcd embeds the client
type Etcd struct {
	client  *etcd.Client
	watches map[string]chan<- bool
}

// InitializeEtcd creates a new Etcd client given
// a list of endpoints and optional tls config
func InitializeEtcd(addrs []string, options Config) (Store, error) {
	s := &Etcd{}
	s.watches = make(map[string]chan<- bool)

	entries := createEndpoints(addrs, "http")
	s.client = etcd.NewClient(entries)

	if options.TLS != nil {
		s.setTLS(options.TLS)
	}

	if options.Timeout != 0 {
		s.setTimeout(options.Timeout)
	}

	// FIXME sync on each operation?
	s.client.SyncCluster()
	return s, nil
}

// SetTLS sets the tls configuration given the path
// of certificate files
func (s *Etcd) setTLS(tls *tls.Config) {
	// Change to https scheme
	var addrs []string
	entries := s.client.GetCluster()
	for _, entry := range entries {
		addrs = append(addrs, strings.Replace(entry, "http", "https", -1))
	}
	s.client.SetCluster(addrs)

	// Set transport
	t := http.Transport{
		Dial: (&net.Dialer{
			Timeout:   30 * time.Second, // default timeout
			KeepAlive: 30 * time.Second,
		}).Dial,
		TLSHandshakeTimeout: 10 * time.Second,
		TLSClientConfig:     tls,
	}
	s.client.SetTransport(&t)
}

// SetTimeout sets the timeout used for connecting to the store
func (s *Etcd) setTimeout(time time.Duration) {
	s.client.SetDialTimeout(time)
}

// Create the entire path for a directory that does not exist
func (s *Etcd) createDirectory(path string) error {
	// TODO Handle TTL at key/dir creation -> use K/V struct for key infos?
	if _, err := s.client.CreateDir(format(path), 10); err != nil {
		if etcdError, ok := err.(*etcd.EtcdError); ok {
			if etcdError.ErrorCode != 105 { // Skip key already exists
				return err
			}
		} else {
			return err
		}
	}
	return nil
}

// Get the value at "key", returns the last modified index
// to use in conjunction to CAS calls
func (s *Etcd) Get(key string) (value []byte, lastIndex uint64, err error) {
	result, err := s.client.Get(format(key), false, false)
	if err != nil {
		if etcdError, ok := err.(*etcd.EtcdError); ok {
			// Not a Directory or Not a file
			if etcdError.ErrorCode == 102 || etcdError.ErrorCode == 104 {
				return nil, 0, ErrKeyNotFound
			}
		}
		return nil, 0, err
	}
	return []byte(result.Node.Value), result.Node.ModifiedIndex, nil
}

// Put a value at "key"
func (s *Etcd) Put(key string, value []byte) error {
	if _, err := s.client.Set(key, string(value), 0); err != nil {
		if etcdError, ok := err.(*etcd.EtcdError); ok {
			if etcdError.ErrorCode == 104 { // Not a directory
				// Remove the last element (the actual key) and set the prefix as a dir
				err = s.createDirectory(getDir(key))
				if _, err := s.client.Set(key, string(value), 0); err != nil {
					return err
				}
			}
		}
		return err
	}
	return nil
}

// Delete a value at "key"
func (s *Etcd) Delete(key string) error {
	if _, err := s.client.Delete(format(key), false); err != nil {
		return err
	}
	return nil
}

// Exists checks if the key exists inside the store
func (s *Etcd) Exists(key string) (bool, error) {
	value, _, err := s.Get(key)
	if err != nil {
		if err == ErrKeyNotFound || value == nil {
			return false, nil
		}
		return false, err
	}
	return true, nil
}

// Watch a single key for modifications
func (s *Etcd) Watch(key string, _ time.Duration, callback WatchCallback) error {
	key = format(key)
	watchChan := make(chan *etcd.Response)
	stopChan := make(chan bool)

	// Create new Watch entry
	s.watches[key] = stopChan

	// Start watch
	go s.client.Watch(key, 0, false, watchChan, stopChan)

	for _ = range watchChan {
		log.WithField("name", "etcd").Debug("Discovery watch triggered")
		entry, index, err := s.Get(key)
		if err != nil {
			log.Error("Cannot refresh the key: ", key, ", cancelling watch")
			s.watches[key] = nil
			return err
		}
		kvi := []KVEntry{&kviTuple{key, entry, index}}
		callback(kvi)
	}
	return nil
}

// CancelWatch cancels a watch, sends a signal to the appropriate
// stop channel
func (s *Etcd) CancelWatch(key string) error {
	key = format(key)
	if _, ok := s.watches[key]; !ok {
		log.Error("Chan does not exist for key: ", key)
		return ErrWatchDoesNotExist
	}
	// Send stop signal to event chan
	s.watches[key] <- true
	s.watches[key] = nil
	return nil
}

// AtomicPut put a value at "key" if the key has not been
// modified in the meantime, throws an error if this is the case
func (s *Etcd) AtomicPut(key string, oldValue []byte, newValue []byte, index uint64) (bool, error) {
	resp, err := s.client.CompareAndSwap(format(key), string(newValue), 5, string(oldValue), 0)
	if err != nil {
		return false, err
	}
	if !(resp.Node.Value == string(newValue) && resp.Node.Key == key && resp.Node.TTL == 5) {
		return false, ErrKeyModified
	}
	if !(resp.PrevNode.Value == string(newValue) && resp.PrevNode.Key == key && resp.PrevNode.TTL == 5) {
		return false, ErrKeyModified
	}
	return true, nil
}

// AtomicDelete deletes a value at "key" if the key has not
// been modified in the meantime, throws an error if this is the case
func (s *Etcd) AtomicDelete(key string, oldValue []byte, index uint64) (bool, error) {
	resp, err := s.client.CompareAndDelete(format(key), string(oldValue), 0)
	if err != nil {
		return false, err
	}
	if !(resp.PrevNode.Value == string(oldValue) && resp.PrevNode.Key == key && resp.PrevNode.TTL == 5) {
		return false, ErrKeyModified
	}
	return true, nil
}

// GetRange gets a range of values at "directory"
func (s *Etcd) GetRange(prefix string) ([]KVEntry, error) {
	resp, err := s.client.Get(format(prefix), true, true)
	if err != nil {
		return nil, err
	}
	kvi := make([]KVEntry, len(resp.Node.Nodes))
	for i, n := range resp.Node.Nodes {
		kvi[i] = &kviTuple{n.Key, []byte(n.Value), n.ModifiedIndex}
	}
	return kvi, nil
}

// DeleteRange deletes a range of values at "directory"
func (s *Etcd) DeleteRange(prefix string) error {
	if _, err := s.client.Delete(format(prefix), true); err != nil {
		return err
	}
	return nil
}

// WatchRange triggers a watch on a range of values at "directory"
func (s *Etcd) WatchRange(prefix string, filter string, _ time.Duration, callback WatchCallback) error {
	prefix = format(prefix)
	watchChan := make(chan *etcd.Response)
	stopChan := make(chan bool)

	// Create new Watch entry
	s.watches[prefix] = stopChan

	// Start watch
	go s.client.Watch(prefix, 0, true, watchChan, stopChan)
	for _ = range watchChan {
		log.WithField("name", "etcd").Debug("Discovery watch triggered")
		kvi, err := s.GetRange(prefix)
		if err != nil {
			log.Error("Cannot refresh the key: ", prefix, ", cancelling watch")
			s.watches[prefix] = nil
			return err
		}
		callback(kvi)
	}
	return nil
}

// CancelWatchRange stops the watch on the range of values, sends
// a signal to the appropriate stop channel
func (s *Etcd) CancelWatchRange(prefix string) error {
	return s.CancelWatch(format(prefix))
}

// Acquire the lock for "key"/"directory"
func (s *Etcd) Acquire(key string, value []byte) (string, error) {
	return "", ErrNotImplemented
}

// Release the lock for "key"/"directory"
func (s *Etcd) Release(session string) error {
	return ErrNotImplemented
}
<|endoftext|>


<commit_before>package types

type Type interface {
	IsType()

	Clone() Type
	Equals(to Type, compareFlags bool) bool

	SetFlag(f Flag)
	UnsetFlag(f Flag)
	GetFlag(f Flag) bool
	Flags() Flag
}

type Unresolved struct{ Base }

type Void struct{ Base }

type Int struct {
	Base
	Size   int
	Signed bool
}

type Named struct {
	Base
	Name string
}

type Annotation struct {
	Base
	Type        *Named
	Annotations []Type
}

type Float struct{ Base }

type Double struct{ Base }

type Bool struct{ Base }

type Char struct{ Base }

type Array struct {
	Base
	InnerType Type
}

type Pointer struct {
	Base
	InnerType Type
}

type Alias struct {
	Base
	Name         string
	OriginalType Type
}

func (t Annotation) IsType() {}
func (t Unresolved) IsType() {}
func (t Void) IsType()       {}
func (t Int) IsType()        {}
func (t Named) IsType()      {}
func (t Float) IsType()      {}
func (t Double) IsType()     {}
func (t Bool) IsType()       {}
func (t Char) IsType()       {}
func (t Array) IsType()      {}
func (t Pointer) IsType()    {}
func (t Alias) IsType()      {}

func (t Annotation) Clone() Type { return &t }
func (t Unresolved) Clone() Type { return &t }
func (t Void) Clone() Type       { return &t }
func (t Int) Clone() Type        { return &t }
func (t Named) Clone() Type      { return &t }
func (t Float) Clone() Type      { return &t }
func (t Double) Clone() Type     { return &t }
func (t Bool) Clone() Type       { return &t }
func (t Char) Clone() Type       { return &t }
func (t Array) Clone() Type      { return &t }
func (t Pointer) Clone() Type    { return &t }
func (t Alias) Clone() Type      { return &t }

func (t Annotation) Equals(to Type, compareFlags bool) bool {
	if compareFlags && to.Flags() != t.Flags() {
		return false
	}

	otherTy, ok := getRealType(to).(*Annotation)

	if !ok || !otherTy.Type.Equals(t.Type, compareFlags) ||
		len(otherTy.Annotations) != len(t.Annotations) {
		return false
	}

	for i := range t.Annotations {
		if !t.Annotations[i].Equals(otherTy.Annotations[i], compareFlags) {
			return false
		}
	}

	return true
}

func (t Unresolved) Equals(to Type, compareFlags bool) bool {
	if compareFlags && to.Flags() != t.Flags() {
		return false
	}

	_, ok := getRealType(to).(*Unresolved)
	return ok
}

func (t Void) Equals(to Type, compareFlags bool) bool {
	if compareFlags && to.Flags() != t.Flags() {
		return false
	}

	_, ok := getRealType(to).(*Void)
	return ok
}

func (t Int) Equals(to Type, compareFlags bool) bool {
	if compareFlags && to.Flags() != t.Flags() {
		return false
	}

	other, ok := getRealType(to).(*Int)
	return ok && other.Signed == t.Signed && other.Size == t.Size
}

func (t Named) Equals(to Type, compareFlags bool) bool {
	if compareFlags && to.Flags() != t.Flags() {
		return false
	}

	otherTy, ok := getRealType(to).(*Named)
	return ok && otherTy.Name == t.Name
}

func (t Float) Equals(to Type, compareFlags bool) bool {
	if compareFlags && to.Flags() != t.Flags() {
		return false
	}

	_, ok := getRealType(to).(*Float)
	return ok
}

func (t Double) Equals(to Type, compareFlags bool) bool {
	if compareFlags && to.Flags() != t.Flags() {
		return false
	}

	_, ok := getRealType(to).(*Double)
	return ok
}

func (t Bool) Equals(to Type, compareFlags bool) bool {
	if compareFlags && to.Flags() != t.Flags() {
		return false
	}

	_, ok := getRealType(to).(*Bool)
	return ok
}

func (t Char) Equals(to Type, compareFlags bool) bool {
	if compareFlags && to.Flags() != t.Flags() {
		return false
	}

	_, ok := getRealType(to).(*Char)
	return ok
}

func (t Array) Equals(to Type, compareFlags bool) bool {
	if compareFlags && to.Flags() != t.Flags() {
		return false
	}

	otherTy, ok := getRealType(to).(*Array)
	return ok && otherTy.InnerType.Equals(t.InnerType, compareFlags)
}

func (t Pointer) Equals(to Type, compareFlags bool) bool {
	if compareFlags && to.Flags() != t.Flags() {
		return false
	}

	otherTy, ok := getRealType(to).(*Pointer)
	return ok && otherTy.InnerType.Equals(t.InnerType, compareFlags)
}

func (t Alias) Equals(to Type, compareFlags bool) bool {
	if compareFlags && to.Flags() != t.Flags() {
		return false
	}

	return getRealType(&t).Equals(getRealType(to), compareFlags)
}

// getRealType will return the original type if the type is an alias;
// otherwise just returns the source type.
func getRealType(source Type) Type {
	if ty, isAlias := source.(*Alias); isAlias {
		return getRealType(ty.OriginalType)
	}

	return source
}
<commit_msg>Added a function type (types.Function)<commit_after>package types

type Type interface {
	IsType()

	Clone() Type
	Equals(to Type, compareFlags bool) bool

	SetFlag(f Flag)
	UnsetFlag(f Flag)
	GetFlag(f Flag) bool
	Flags() Flag
}

type Unresolved struct{ Base }

type Void struct{ Base }

type Int struct {
	Base
	Size   int
	Signed bool
}

type Named struct {
	Base
	Name string
}

type Annotation struct {
	Base
	Type        *Named
	Annotations []Type
}

type Float struct{ Base }

type Double struct{ Base }

type Bool struct{ Base }

type Char struct{ Base }

type Array struct {
	Base
	InnerType Type
}

type Pointer struct {
	Base
	InnerType Type
}

type Alias struct {
	Base
	Name         string
	OriginalType Type
}

type Function struct {
	Base
	Parameters []Type
	ReturnType Type
}

func (t Annotation) IsType() {}
func (t Unresolved) IsType() {}
func (t Void) IsType()       {}
func (t Int) IsType()        {}
func (t Named) IsType()      {}
func (t Float) IsType()      {}
func (t Double) IsType()     {}
func (t Bool) IsType()       {}
func (t Char) IsType()       {}
func (t Array) IsType()      {}
func (t Pointer) IsType()    {}
func (t Alias) IsType()      {}
func (t Function) IsType()   {}

func (t Annotation) Clone() Type { return &t }
func (t Unresolved) Clone() Type { return &t }
func (t Void) Clone() Type       { return &t }
func (t Int) Clone() Type        { return &t }
func (t Named) Clone() Type      { return &t }
func (t Float) Clone() Type      { return &t }
func (t Double) Clone() Type     { return &t }
func (t Bool) Clone() Type       { return &t }
func (t Char) Clone() Type       { return &t }
func (t Array) Clone() Type      { return &t }
func (t Pointer) Clone() Type    { return &t }
func (t Alias) Clone() Type      { return &t }
func (t Function) Clone() Type   { return &t }

func (t Annotation) Equals(to Type, compareFlags bool) bool {
	if compareFlags && to.Flags() != t.Flags() {
		return false
	}

	otherTy, ok := getRealType(to).(*Annotation)

	if !ok || !otherTy.Type.Equals(t.Type, compareFlags) ||
		len(otherTy.Annotations) != len(t.Annotations) {
		return false
	}

	for i := range t.Annotations {
		if !t.Annotations[i].Equals(otherTy.Annotations[i], compareFlags) {
			return false
		}
	}

	return true
}

func (t Unresolved) Equals(to Type, compareFlags bool) bool {
	if compareFlags && to.Flags() != t.Flags() {
		return false
	}

	_, ok := getRealType(to).(*Unresolved)
	return ok
}

func (t Void) Equals(to Type, compareFlags bool) bool {
	if compareFlags && to.Flags() != t.Flags() {
		return false
	}

	_, ok := getRealType(to).(*Void)
	return ok
}

func (t Int) Equals(to Type, compareFlags bool) bool {
	if compareFlags && to.Flags() != t.Flags() {
		return false
	}

	other, ok := getRealType(to).(*Int)
	return ok && other.Signed == t.Signed && other.Size == t.Size
}

func (t Named) Equals(to Type, compareFlags bool) bool {
	if compareFlags && to.Flags() != t.Flags() {
		return false
	}

	otherTy, ok := getRealType(to).(*Named)
	return ok && otherTy.Name == t.Name
}

func (t Float) Equals(to Type, compareFlags bool) bool {
	if compareFlags && to.Flags() != t.Flags() {
		return false
	}

	_, ok := getRealType(to).(*Float)
	return ok
}

func (t Double) Equals(to Type, compareFlags bool) bool {
	if compareFlags && to.Flags() != t.Flags() {
		return false
	}

	_, ok := getRealType(to).(*Double)
	return ok
}

func (t Bool) Equals(to Type, compareFlags bool) bool {
	if compareFlags && to.Flags() != t.Flags() {
		return false
	}

	_, ok := getRealType(to).(*Bool)
	return ok
}

func (t Char) Equals(to Type, compareFlags bool) bool {
	if compareFlags && to.Flags() != t.Flags() {
		return false
	}

	_, ok := getRealType(to).(*Char)
	return ok
}

func (t Array) Equals(to Type, compareFlags bool) bool {
	if compareFlags && to.Flags() != t.Flags() {
		return false
	}

	otherTy, ok := getRealType(to).(*Array)
	return ok && otherTy.InnerType.Equals(t.InnerType, compareFlags)
}

func (t Pointer) Equals(to Type, compareFlags bool) bool {
	if compareFlags && to.Flags() != t.Flags() {
		return false
	}

	otherTy, ok := getRealType(to).(*Pointer)
	return ok && otherTy.InnerType.Equals(t.InnerType, compareFlags)
}

func (t Alias) Equals(to Type, compareFlags bool) bool {
	if compareFlags && to.Flags() != t.Flags() {
		return false
	}

	return getRealType(&t).Equals(getRealType(to), compareFlags)
}

func (t Function) Equals(to Type, compareFlags bool) bool {
	if compareFlags && to.Flags() != t.Flags() {
		return false
	}

	if otherTy, ok := getRealType(to).(*Function); !ok {
		return false
	} else if len(t.Parameters) != len(otherTy.Parameters) {
		return false
	} else if !otherTy.ReturnType.Equals(t.ReturnType, compareFlags) {
		return false
	} else {
		for i := range t.Parameters {
			ourParam := t.Parameters[i]
			theirParam := otherTy.Parameters[i]

			if !ourParam.Equals(theirParam, compareFlags) {
				return false
			}
		}
	}

	return true
}

// getRealType will return the original type if the type is an alias;
// otherwise just returns the source type.
func getRealType(source Type) Type {
	if ty, isAlias := source.(*Alias); isAlias {
		return getRealType(ty.OriginalType)
	}

	return source
}
<|endoftext|>


<commit_before>package examples

import (
  "github.com/arschles/assert"
  "testing"
  "errors"
)

func TestNil(t *testing.T) {
  assert.Nil(t, nil, "nil")
  assert.NotNil(t, "abc", "string")
}

func TestBooleans(t *testing.T) {
  assert.True(t, true, "boolean true")
  assert.False(t, false, "boolean false")
}

func TestEqual(t *testing.T) {
  s1 := struct{
    a string
    b int
  } {"testString", 1}
  s2 := struct{
    a string
    b int
  }{"testString", 1}
  assert.Equal(t, s1, s2, "anonymous struct")
}

func TestErrors(t *testing.T) {
  err1 := errors.New("this is an error")
  var err2 error = nil
  assert.Err(t, err1, errors.New("this is an error"))
  assert.NoErr(t, err2)
  assert.ExistsErr(t, err1, "valid error")
}
<commit_msg>fmt the example<commit_after>package examples

import (
	"errors"
	"github.com/arschles/assert"
	"testing"
)

func TestNil(t *testing.T) {
	assert.Nil(t, nil, "nil")
	assert.NotNil(t, "abc", "string")
}

func TestBooleans(t *testing.T) {
	assert.True(t, true, "boolean true")
	assert.False(t, false, "boolean false")
}

func TestEqual(t *testing.T) {
	s1 := struct {
		a string
		b int
	}{"testString", 1}
	s2 := struct {
		a string
		b int
	}{"testString", 1}
	assert.Equal(t, s1, s2, "anonymous struct")
}

func TestErrors(t *testing.T) {
	err1 := errors.New("this is an error")
	var err2 error = nil
	assert.Err(t, err1, errors.New("this is an error"))
	assert.NoErr(t, err2)
	assert.ExistsErr(t, err1, "valid error")
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"path/filepath"

	"github.com/masci/flickr.go/flickr"
)

func main() {
	// retrieve Flickr credentials from env vars
	apik := os.Getenv("FLICKRGO_API_KEY")
	apisec := os.Getenv("FLICKRGO_API_SECRET")
	token := os.Getenv("FLICKRGO_OAUTH_TOKEN")
	tokenSecret := os.Getenv("FLICKRGO_OAUTH_TOKEN_SECRET")

	// do not proceed if credentials were not provided
	if apik == "" || apisec == "" || token == "" || tokenSecret == "" {
		fmt.Fprintln(os.Stderr, "Please set FLICKRGO_API_KEY, FLICKRGO_API_SECRET "+
			"and FLICKRGO_OAUTH_TOKEN env vars")
		os.Exit(1)
	}

	// create an API client with credentials
	client := flickr.NewFlickrClient(apik, apisec)
	client.OAuthToken = token
	client.OAuthTokenSecret = tokenSecret

	path, _ := filepath.Abs("examples/upload/gopher.jpg")
	id, err := flickr.UploadPhoto(client, path, nil)

	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println("Photo uploaded, id:", id)
	}
}
<commit_msg>example passing upload params<commit_after>package main

import (
	"fmt"
	"os"
	"path/filepath"

	"github.com/masci/flickr.go/flickr"
)

func main() {
	// retrieve Flickr credentials from env vars
	apik := os.Getenv("FLICKRGO_API_KEY")
	apisec := os.Getenv("FLICKRGO_API_SECRET")
	token := os.Getenv("FLICKRGO_OAUTH_TOKEN")
	tokenSecret := os.Getenv("FLICKRGO_OAUTH_TOKEN_SECRET")

	// do not proceed if credentials were not provided
	if apik == "" || apisec == "" || token == "" || tokenSecret == "" {
		fmt.Fprintln(os.Stderr, "Please set FLICKRGO_API_KEY, FLICKRGO_API_SECRET "+
			"and FLICKRGO_OAUTH_TOKEN env vars")
		os.Exit(1)
	}

	// create an API client with credentials
	client := flickr.NewFlickrClient(apik, apisec)
	client.OAuthToken = token
	client.OAuthTokenSecret = tokenSecret

	path, _ := filepath.Abs("examples/upload/gopher.jpg")
	id, err := flickr.UploadPhoto(client, path, nil)

	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println("Photo uploaded, id:", id)
	}

	params := flickr.NewUploadParams()
	params.Title = "A Gopher"
	id, err = flickr.UploadPhoto(client, path, params)

	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println("Photo uploaded, id:", id)
	}
}
<|endoftext|>


<commit_before>package api

import "sort"

// Regions is used to query the regions in the cluster.
type Regions struct {
	client *Client
}

// Regions returns a handle on the allocs endpoints.
func (c *Client) Regions() *Regions {
	return &Regions{client: c}
}

// List returns a list of all of the regions.
func (r *Regions) List() ([]string, error) {
	var resp []string
	if _, err := r.client.query("/v1/regions", &resp, nil); err != nil {
		return nil, err
	}
	sort.Strings(resp)
	return resp, nil
}
<commit_msg>fix a copy/paste error on api.Regions() comment<commit_after>package api

import "sort"

// Regions is used to query the regions in the cluster.
type Regions struct {
	client *Client
}

// Regions returns a handle on the regions endpoints.
func (c *Client) Regions() *Regions {
	return &Regions{client: c}
}

// List returns a list of all of the regions.
func (r *Regions) List() ([]string, error) {
	var resp []string
	if _, err := r.client.query("/v1/regions", &resp, nil); err != nil {
		return nil, err
	}
	sort.Strings(resp)
	return resp, nil
}
<|endoftext|>


<commit_before>package api

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

	"github.com/FederationOfFathers/dashboard/bridge"
	"github.com/FederationOfFathers/dashboard/streams"
	"github.com/gorilla/mux"
	"github.com/jinzhu/gorm"
	"github.com/uber-go/zap"
)

func init() {
	Router.Path("/api/v0/streams").Methods("GET").Handler(jwtHandlerFunc(
		func(w http.ResponseWriter, r *http.Request) {
			w.Header().Set("Content-Type", "application/json")
			enc := json.NewEncoder(w)
			enc.Encode(streams.Streams)
		},
	))

	Router.Path("/api/v0/streams/{memberID}/{type}").Methods("DELETE").Handler(jwtHandlerFunc(
		func(w http.ResponseWriter, r *http.Request) {
			w.Header().Set("Content-Type", "application/json")
			id := getSlackUserID(r)
			admin, _ := bridge.Data.Slack.IsUserIDAdmin(id)
			member, err := DB.MemberBySlackID(mux.Vars(r)["memberID"])
			if err != nil {
				http.NotFound(w, r)
				return
			}
			if strings.ToLower(member.Slack) != strings.ToLower(id) && !admin {
				http.NotFound(w, r)
				return
			}
			stream, err := DB.StreamByMemberID(member.ID)
			if err != nil {
				http.NotFound(w, r)
				return
			}
			if err := streams.Remove(stream.ID, mux.Vars(r)["type"]); err != nil {
				logger.Error("Error removing stream", zap.String("uri", r.URL.RawPath), zap.Error(err))
				w.WriteHeader(http.StatusInternalServerError)
			}
		},
	))

	Router.Path("/api/v0/streams/{memberID}").Methods("GET").Handler(jwtHandlerFunc(
		func(w http.ResponseWriter, r *http.Request) {
			w.Header().Set("Content-Type", "application/json")
			member, err := DB.MemberByAny(mux.Vars(r)["memberID"])
			if err == gorm.ErrRecordNotFound {
				http.NotFound(w, r)
				return
			}
			if err != nil {
				w.WriteHeader(http.StatusInternalServerError)
				return
			}
			stream, err := DB.StreamByMemberID(member.ID)
			if err == gorm.ErrRecordNotFound {
				http.NotFound(w, r)
				return
			}
			if err != nil {
				w.WriteHeader(http.StatusInternalServerError)
				return
			}
			json.NewEncoder(w).Encode(stream)
		},
	))

	streamSetHandler := jwtHandlerFunc(
		func(w http.ResponseWriter, r *http.Request) {
			defer r.Body.Close()
			var kind string
			var id string
			var userID string
			if strings.Contains(strings.ToLower(r.Header.Get("Content-Type")), "json") {
				var form = struct {
					Kind   string `json:"kind"`
					ID     string `json:"id"`
					UserID string `json:"userID"`
				}{}
				err := json.NewDecoder(r.Body).Decode(&form)
				if err != nil {
					logger.Error("Error decoding JSON", zap.String("uri", r.URL.RawPath), zap.Error(err))
				}
				kind = form.Kind
				id = form.ID
				userID = form.UserID
			} else {
				kind = r.FormValue("kind")
				id = r.FormValue("id")
				userID = r.FormValue("userID")
			}
			w.Header().Set("Content-Type", "application/json")
			if userID == "" {
				userID = getSlackUserID(r)
			}
			err := streams.Add(kind, id, userID)
			if err != nil {
				logger.Error(
					"Error adding stream",
					zap.String("uri", r.URL.RawPath),
					zap.String("kind", kind),
					zap.String("id", id),
					zap.String("userID", userID),
					zap.Error(err))
				w.WriteHeader(http.StatusInternalServerError)
			}
		},
	)
	Router.Path("/api/v0/streams").Methods("PUT").Handler(streamSetHandler)
	Router.Path("/api/v0/streams").Methods("POST").Handler(streamSetHandler)
}
<commit_msg>Return a 404 instead of a 500 if the user has no streams listed so the UI can actually render the member page.<commit_after>package api

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

	"github.com/FederationOfFathers/dashboard/bridge"
	"github.com/FederationOfFathers/dashboard/streams"
	"github.com/gorilla/mux"
	"github.com/jinzhu/gorm"
	"github.com/uber-go/zap"
)

func init() {
	Router.Path("/api/v0/streams").Methods("GET").Handler(jwtHandlerFunc(
		func(w http.ResponseWriter, r *http.Request) {
			w.Header().Set("Content-Type", "application/json")
			enc := json.NewEncoder(w)
			enc.Encode(streams.Streams)
		},
	))

	Router.Path("/api/v0/streams/{memberID}/{type}").Methods("DELETE").Handler(jwtHandlerFunc(
		func(w http.ResponseWriter, r *http.Request) {
			w.Header().Set("Content-Type", "application/json")
			id := getSlackUserID(r)
			admin, _ := bridge.Data.Slack.IsUserIDAdmin(id)
			member, err := DB.MemberBySlackID(mux.Vars(r)["memberID"])
			if err != nil {
				http.NotFound(w, r)
				return
			}
			if strings.ToLower(member.Slack) != strings.ToLower(id) && !admin {
				http.NotFound(w, r)
				return
			}
			stream, err := DB.StreamByMemberID(member.ID)
			if err != nil {
				http.NotFound(w, r)
				return
			}
			if err := streams.Remove(stream.ID, mux.Vars(r)["type"]); err != nil {
				logger.Error("Error removing stream", zap.String("uri", r.URL.RawPath), zap.Error(err))
				w.WriteHeader(http.StatusInternalServerError)
			}
		},
	))

	Router.Path("/api/v0/streams/{memberID}").Methods("GET").Handler(jwtHandlerFunc(
		func(w http.ResponseWriter, r *http.Request) {
			w.Header().Set("Content-Type", "application/json")
			member, err := DB.MemberByAny(mux.Vars(r)["memberID"])
			if err == gorm.ErrRecordNotFound {
				http.NotFound(w, r)
				return
			}
			if err != nil {
				w.WriteHeader(http.StatusInternalServerError)
				return
			}
			stream, err := DB.StreamByMemberID(member.ID)
			if err != nil && err != gorm.ErrRecordNotFound {
				w.WriteHeader(http.StatusInternalServerError)
				return
			}
			json.NewEncoder(w).Encode(stream)
		},
	))

	streamSetHandler := jwtHandlerFunc(
		func(w http.ResponseWriter, r *http.Request) {
			defer r.Body.Close()
			var kind string
			var id string
			var userID string
			if strings.Contains(strings.ToLower(r.Header.Get("Content-Type")), "json") {
				var form = struct {
					Kind   string `json:"kind"`
					ID     string `json:"id"`
					UserID string `json:"userID"`
				}{}
				err := json.NewDecoder(r.Body).Decode(&form)
				if err != nil {
					logger.Error("Error decoding JSON", zap.String("uri", r.URL.RawPath), zap.Error(err))
				}
				kind = form.Kind
				id = form.ID
				userID = form.UserID
			} else {
				kind = r.FormValue("kind")
				id = r.FormValue("id")
				userID = r.FormValue("userID")
			}
			w.Header().Set("Content-Type", "application/json")
			if userID == "" {
				userID = getSlackUserID(r)
			}
			err := streams.Add(kind, id, userID)
			if err != nil {
				logger.Error(
					"Error adding stream",
					zap.String("uri", r.URL.RawPath),
					zap.String("kind", kind),
					zap.String("id", id),
					zap.String("userID", userID),
					zap.Error(err))
				w.WriteHeader(http.StatusInternalServerError)
			}
		},
	)
	Router.Path("/api/v0/streams").Methods("PUT").Handler(streamSetHandler)
	Router.Path("/api/v0/streams").Methods("POST").Handler(streamSetHandler)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"go/ast"
	"go/token"
	"log"
	"path"
	"strconv"
	"strings"
)

func init() {
	register(causeFix)
	register(maskFix)
	register(newFix)
}

const errgoPkgPath = "github.com/juju/errgo/errors"

var maskFix = fix{
	"errgo-mask",
	"2014-02-10",
	errgoMask,
	`wrap all returned errors; use errgo for all error creation functions
`,
}

var causeFix = fix{
	"errgo-cause",
	"2014-02-14",
	errgoCause,
	`use Cause when comparing errors
`,
}

var newFix = fix{
	"errgo-new",
	"2014-03-03",
	errgoNew,
	`use Newf instead of Errorf
`,
}

type errgoFixContext struct {
	pathToIdent  map[string]string
	gocheckIdent string
	errgoIdent   string
	importsOldErrgo bool
}

func newErrgoFixContext(f *ast.File) *errgoFixContext {
	ctxt := &errgoFixContext{
		pathToIdent: importPathToIdentMap(f),
	}
	ctxt.gocheckIdent = ctxt.pathToIdent["launchpad.net/gocheck"]

	// If we import from any */errors package path,
	// import as errgo to save name clashes.
	ctxt.errgoIdent = "errors"
	for _, imp := range f.Imports {
		path := importPath(imp)
		if strings.HasSuffix(path, "/errors") {
			ctxt.errgoIdent = "errgo"
		}
		if path == "github.com/errgo/errgo" {
			ctxt.importsOldErrgo = true
		}
	}
	return ctxt
}

func importPathToIdentMap(f *ast.File) map[string]string {
	m := make(map[string]string)
	for _, imp := range f.Imports {
		ipath := importPath(imp)
		if imp.Name != nil {
			m[ipath] = imp.Name.Name
		} else {
			_, name := path.Split(ipath)
			m[ipath] = name
		}
	}
	return m
}

func errgoNew(f *ast.File) bool {
	ctxt := newErrgoFixContext(f)

	fixed := false
	walk(f, func(n interface{}) {
		warning := func(format string, arg ...interface{}) {
			pos := fset.Position(n.(ast.Node).Pos())
			log.Printf("warning: %s: %s", pos, fmt.Sprintf(format, arg...))
		}
		switch n := n.(type) {
		case *ast.CallExpr:
			switch {
			case isPkgDot(n.Fun, "fmt", "Errorf"):
				if len(n.Args) == 0 {
					warning("Errorf with no args")
					break
				}
				lit, ok := n.Args[0].(*ast.BasicLit)
				if !ok {
					warning("Errorf with non-constant first arg")
					break
				}
				if lit.Kind != token.STRING {
					warning("Errorf with non-string literal first arg")
					break
				}
				format, err := strconv.Unquote(lit.Value)
				if err != nil {
					warning("Errorf with invalid quoted string literal: %v", err)
					break
				}
				if !strings.HasSuffix(format, ": %v") || len(n.Args) < 2 || !isName(n.Args[len(n.Args)-1], "err") {
					// fmt.Errorf("foo %s", x) ->
					// errgo.Newf("foo %s", x)
					n.Fun = &ast.SelectorExpr{
						X:   ast.NewIdent(ctxt.errgoIdent),
						Sel: ast.NewIdent("Newf"),
					}
					fixed = true
					break
				}
				// fmt.Errorf("format: %v", args..., err) ->
				// errgo.Maskf(err, "format", args...)
				newArgs := []ast.Expr{
					n.Args[len(n.Args)-1],
					&ast.BasicLit{
						Kind:  token.STRING,
						Value: fmt.Sprintf("%q", strings.TrimSuffix(format, ": %v")),
					},
				}
				newArgs = append(newArgs, n.Args[1:len(n.Args)-1]...)
				n.Args = newArgs
				n.Fun = &ast.SelectorExpr{
					X:   ast.NewIdent(ctxt.errgoIdent),
					Sel: ast.NewIdent("Notef"),
				}
				fixed = true
			case ctxt.importsOldErrgo && isPkgDot(n.Fun, "errgo", "Annotate"):
				n.Fun = &ast.SelectorExpr{
					X:   ast.NewIdent(ctxt.errgoIdent),
					Sel: ast.NewIdent("NoteMask"),
				}
				fixed = true
			case ctxt.importsOldErrgo && isPkgDot(n.Fun, "errgo", "Annotatef"):
				n.Fun = &ast.SelectorExpr{
					X:   ast.NewIdent(ctxt.errgoIdent),
					Sel: ast.NewIdent("Notef"),
				}
				fixed = true
			case ctxt.importsOldErrgo && isPkgDot(n.Fun, "errgo", "New"):
				n.Fun = &ast.SelectorExpr{
					X:   ast.NewIdent(ctxt.errgoIdent),
					Sel: ast.NewIdent("Newf"),
				}
				fixed = true
			case isPkgDot(n.Fun, ctxt.pathToIdent["errors"], "New"):
				n.Fun = &ast.SelectorExpr{
					X:   ast.NewIdent(ctxt.errgoIdent),
					Sel: ast.NewIdent("New"),
				}
				fixed = true
			}
		}
	})
	fixed = deleteImport(f, "github.com/errgo/errgo") || fixed
	fixed = rewriteImports(ctxt, f, fixed) || fixed
	return fixed
}

func errgoMask(f *ast.File) bool {
	ctxt := newErrgoFixContext(f)

	fixed := false
	walk(f, func(n interface{}) {
		switch n := n.(type) {
		case *ast.IfStmt:
			if ok := fixIfErrNotEqualNil(n, ctxt.errgoIdent); ok {
				fixed = true
				break
			}
		}
	})
	fixed = deleteImport(f, "github.com/errgo/errgo") || fixed
	fixed = rewriteImports(ctxt, f, fixed) || fixed
	return fixed
}

func errgoCause(f *ast.File) bool {
	ctxt := newErrgoFixContext(f)

	fixed := false
	walk(f, func(n interface{}) {
		switch n := n.(type) {
		case *ast.IfStmt:
			if ok := fixIfErrEqualSomething(n, ctxt.errgoIdent); ok {
				fixed = true
				break
			}
		case *ast.TypeAssertExpr:
			if isName(n.X, "err") {
				n.X = causeExpr(ctxt.errgoIdent, "err")
				fixed = true
			}
		case *ast.CallExpr:
			fixed = fixGocheck(n, ctxt.errgoIdent, ctxt.gocheckIdent) || fixed
		}
	})
	if fixed {
		rewriteImports(ctxt, f, fixed)
	}
	return fixed
}

func rewriteImports(ctxt *errgoFixContext, f *ast.File, usingErrgo bool) bool {
	// If there was already an "errors" import, then we can
	// rewrite it to use errgo
	fixed := false
	if ctxt.pathToIdent["errors"] != "" {
		// We've already imported the errors package;
		// change it to refer to errgo.
		for _, imp := range f.Imports {
			if importPath(imp) == "errors" {
				fixed = true
				imp.EndPos = imp.End()
				imp.Path.Value = strconv.Quote(errgoPkgPath)
				if ctxt.errgoIdent != "errors" {
					imp.Name = ast.NewIdent(ctxt.errgoIdent)
				}
			}
		}
	} else if usingErrgo {
		fixed = addImport(f, errgoPkgPath, ctxt.errgoIdent, false)
	}
	return fixed
}

func fixIfErrNotEqualNil(n *ast.IfStmt, errgoIdent string) bool {
	// if stmt; err != nil {
	//	return [..., ]err
	//  }
	// ->
	// if stmt; err != nil {
	// 	return [..., ]errgo.Mask(err)
	// }
	cond, ok := n.Cond.(*ast.BinaryExpr)
	if !ok {
		return false
	}
	if !isName(cond.X, "err") {
		return false
	}
	if !isName(cond.Y, "nil") {
		// comparison of errors against anything
		// other than nil - use errgo.Cause.

	}
	if cond.Op != token.NEQ {
		return false
	}
	if len(n.Body.List) != 1 {
		return false
	}
	returnStmt, ok := n.Body.List[0].(*ast.ReturnStmt)
	if !ok {
		return false
	}
	if len(returnStmt.Results) == 0 {
		return false
	}
	lastResult := &returnStmt.Results[len(returnStmt.Results)-1]
	if !isName(*lastResult, "err") {
		return false
	}
	*lastResult = &ast.CallExpr{
		Fun: &ast.SelectorExpr{
			X:   ast.NewIdent(errgoIdent),
			Sel: ast.NewIdent("Mask"),
		},
		Args: []ast.Expr{ast.NewIdent("err")},
	}
	return true
}

func fixIfErrEqualSomething(n *ast.IfStmt, errgoIdent string) bool {
	// if stmt; err == something-but-not-nil
	// ->
	// if stmt; errgo.Cause(err) == something-but-not-nil
	cond, ok := n.Cond.(*ast.BinaryExpr)
	if !ok {
		return false
	}
	if !isName(cond.X, "err") {
		return false
	}
	if cond.Op != token.EQL {
		return false
	}
	if isName(cond.Y, "nil") {
		return false
	}
	cond.X = causeExpr(errgoIdent, "err")
	return true
}

func fixGocheck(n *ast.CallExpr, errgoIdent, gocheckIdent string) bool {
	// gc.Check(err, gc.Equals, foo-not-nil)
	// ->
	// gc.Check(errgo.Cause(err), gc.Equals, foo-not-nil)

	// gc.Check(err, gc.Not(gc.Equals), foo-not-nil)
	// ->
	// gc.Check(errgo.Cause(err), gc.Not(gc.Equals), foo-not-nil)
	if gocheckIdent == "" {
		return false
	}
	sel, ok := n.Fun.(*ast.SelectorExpr)
	if !ok {
		return false
	}
	if !isName(sel.X, "c") {
		return false
	}
	if s := sel.Sel.String(); s != "Check" && s != "Assert" {
		return false
	}

	if len(n.Args) < 3 {
		return false
	}
	if !isName(n.Args[0], "err") {
		return false
	}
	if condCall, ok := n.Args[1].(*ast.CallExpr); ok {
		if !isPkgDot(condCall.Fun, gocheckIdent, "Not") {
			return false
		}
		if len(condCall.Args) != 1 {
			return false
		}
		if !isPkgDot(condCall.Args[0], gocheckIdent, "Equals") {
			return false
		}
	} else if !isPkgDot(n.Args[1], gocheckIdent, "Equals") {
		return false
	}
	if isName(n.Args[2], "nil") {
		return false
	}
	n.Args[0] = causeExpr(errgoIdent, "err")
	return true
}

func causeExpr(errgoIdent string, ident string) *ast.CallExpr {
	return &ast.CallExpr{
		Fun: &ast.SelectorExpr{
			X:   ast.NewIdent(errgoIdent),
			Sel: ast.NewIdent("Cause"),
		},
		Args: []ast.Expr{ast.NewIdent(ident)},
	}
}
<commit_msg>cmd/errfix: use errgo as identifier<commit_after>package main

import (
	"fmt"
	"go/ast"
	"go/token"
	"log"
	"path"
	"strconv"
	"strings"
)

func init() {
	register(causeFix)
	register(maskFix)
	register(newFix)
}

const errgoPkgPath = "github.com/juju/errgo"

var maskFix = fix{
	"errgo-mask",
	"2014-02-10",
	errgoMask,
	`wrap all returned errors; use errgo for all error creation functions
`,
}

var causeFix = fix{
	"errgo-cause",
	"2014-02-14",
	errgoCause,
	`use Cause when comparing errors
`,
}

var newFix = fix{
	"errgo-new",
	"2014-03-03",
	errgoNew,
	`use Newf instead of Errorf
`,
}

type errgoFixContext struct {
	pathToIdent  map[string]string
	gocheckIdent string
	importsOldErrgo bool
}

const errgoIdent = "errgo"

func newErrgoFixContext(f *ast.File) *errgoFixContext {
	ctxt := &errgoFixContext{
		pathToIdent: importPathToIdentMap(f),
	}
	ctxt.gocheckIdent = ctxt.pathToIdent["launchpad.net/gocheck"]

	// If we import from any */errors package path,
	// import as errgo to save name clashes.
	for _, imp := range f.Imports {
		if importPath(imp) == "github.com/errgo/errgo" {
			ctxt.importsOldErrgo = true
		}
	}
	return ctxt
}

func importPathToIdentMap(f *ast.File) map[string]string {
	m := make(map[string]string)
	for _, imp := range f.Imports {
		ipath := importPath(imp)
		if imp.Name != nil {
			m[ipath] = imp.Name.Name
		} else {
			_, name := path.Split(ipath)
			m[ipath] = name
		}
	}
	return m
}

func errgoNew(f *ast.File) bool {
	ctxt := newErrgoFixContext(f)

	fixed := false
	walk(f, func(n interface{}) {
		warning := func(format string, arg ...interface{}) {
			pos := fset.Position(n.(ast.Node).Pos())
			log.Printf("warning: %s: %s", pos, fmt.Sprintf(format, arg...))
		}
		switch n := n.(type) {
		case *ast.CallExpr:
			switch {
			case isPkgDot(n.Fun, "fmt", "Errorf"):
				if len(n.Args) == 0 {
					warning("Errorf with no args")
					break
				}
				lit, ok := n.Args[0].(*ast.BasicLit)
				if !ok {
					warning("Errorf with non-constant first arg")
					break
				}
				if lit.Kind != token.STRING {
					warning("Errorf with non-string literal first arg")
					break
				}
				format, err := strconv.Unquote(lit.Value)
				if err != nil {
					warning("Errorf with invalid quoted string literal: %v", err)
					break
				}
				if !strings.HasSuffix(format, ": %v") || len(n.Args) < 2 || !isName(n.Args[len(n.Args)-1], "err") {
					// fmt.Errorf("foo %s", x) ->
					// errgo.Newf("foo %s", x)
					n.Fun = &ast.SelectorExpr{
						X:   ast.NewIdent(errgoIdent),
						Sel: ast.NewIdent("Newf"),
					}
					fixed = true
					break
				}
				// fmt.Errorf("format: %v", args..., err) ->
				// errgo.Maskf(err, "format", args...)
				newArgs := []ast.Expr{
					n.Args[len(n.Args)-1],
					&ast.BasicLit{
						Kind:  token.STRING,
						Value: fmt.Sprintf("%q", strings.TrimSuffix(format, ": %v")),
					},
				}
				newArgs = append(newArgs, n.Args[1:len(n.Args)-1]...)
				n.Args = newArgs
				n.Fun = &ast.SelectorExpr{
					X:   ast.NewIdent(errgoIdent),
					Sel: ast.NewIdent("Notef"),
				}
				fixed = true
			case ctxt.importsOldErrgo && isPkgDot(n.Fun, "errgo", "Annotate"):
				n.Fun = &ast.SelectorExpr{
					X:   ast.NewIdent(errgoIdent),
					Sel: ast.NewIdent("NoteMask"),
				}
				fixed = true
			case ctxt.importsOldErrgo && isPkgDot(n.Fun, "errgo", "Annotatef"):
				n.Fun = &ast.SelectorExpr{
					X:   ast.NewIdent(errgoIdent),
					Sel: ast.NewIdent("Notef"),
				}
				fixed = true
			case ctxt.importsOldErrgo && isPkgDot(n.Fun, "errgo", "New"):
				n.Fun = &ast.SelectorExpr{
					X:   ast.NewIdent(errgoIdent),
					Sel: ast.NewIdent("Newf"),
				}
				fixed = true
			case isPkgDot(n.Fun, ctxt.pathToIdent["errors"], "New"):
				n.Fun = &ast.SelectorExpr{
					X:   ast.NewIdent(errgoIdent),
					Sel: ast.NewIdent("New"),
				}
				fixed = true
			}
		}
	})
	fixed = deleteImport(f, "github.com/errgo/errgo") || fixed
	fixed = rewriteImports(ctxt, f, fixed) || fixed
	return fixed
}

func errgoMask(f *ast.File) bool {
	ctxt := newErrgoFixContext(f)

	fixed := false
	walk(f, func(n interface{}) {
		switch n := n.(type) {
		case *ast.IfStmt:
			if ok := fixIfErrNotEqualNil(n); ok {
				fixed = true
				break
			}
		}
	})
	fixed = deleteImport(f, "github.com/errgo/errgo") || fixed
	fixed = rewriteImports(ctxt, f, fixed) || fixed
	return fixed
}

func errgoCause(f *ast.File) bool {
	ctxt := newErrgoFixContext(f)

	fixed := false
	walk(f, func(n interface{}) {
		switch n := n.(type) {
		case *ast.IfStmt:
			if ok := fixIfErrEqualSomething(n, errgoIdent); ok {
				fixed = true
				break
			}
		case *ast.TypeAssertExpr:
			if isName(n.X, "err") {
				n.X = causeExpr(errgoIdent, "err")
				fixed = true
			}
		case *ast.CallExpr:
			fixed = fixGocheck(n, errgoIdent, ctxt.gocheckIdent) || fixed
		}
	})
	if fixed {
		rewriteImports(ctxt, f, fixed)
	}
	return fixed
}

func rewriteImports(ctxt *errgoFixContext, f *ast.File, usingErrgo bool) bool {
	// If there was already an "errors" import, then we can
	// rewrite it to use errgo
	fixed := false
	if ctxt.pathToIdent["errors"] != "" {
		// We've already imported the errors package;
		// change it to refer to errgo.
		for _, imp := range f.Imports {
			if importPath(imp) == "errors" {
				fixed = true
				imp.Name = nil
				imp.EndPos = imp.End()
				imp.Path.Value = strconv.Quote(errgoPkgPath)
			}
		}
	} else if usingErrgo {
		fixed = addImport(f, errgoPkgPath, errgoIdent, false)
	}
	return fixed
}

func fixIfErrNotEqualNil(n *ast.IfStmt) bool {
	// if stmt; err != nil {
	//	return [..., ]err
	//  }
	// ->
	// if stmt; err != nil {
	// 	return [..., ]errgo.Mask(err)
	// }
	cond, ok := n.Cond.(*ast.BinaryExpr)
	if !ok {
		return false
	}
	if !isName(cond.X, "err") {
		return false
	}
	if !isName(cond.Y, "nil") {
		// comparison of errors against anything
		// other than nil - use errgo.Cause.

	}
	if cond.Op != token.NEQ {
		return false
	}
	if len(n.Body.List) != 1 {
		return false
	}
	returnStmt, ok := n.Body.List[0].(*ast.ReturnStmt)
	if !ok {
		return false
	}
	if len(returnStmt.Results) == 0 {
		return false
	}
	lastResult := &returnStmt.Results[len(returnStmt.Results)-1]
	if !isName(*lastResult, "err") {
		return false
	}
	*lastResult = &ast.CallExpr{
		Fun: &ast.SelectorExpr{
			X:   ast.NewIdent(errgoIdent),
			Sel: ast.NewIdent("Mask"),
		},
		Args: []ast.Expr{ast.NewIdent("err")},
	}
	return true
}

func fixIfErrEqualSomething(n *ast.IfStmt, errgoIdent string) bool {
	// if stmt; err == something-but-not-nil
	// ->
	// if stmt; errgo.Cause(err) == something-but-not-nil
	cond, ok := n.Cond.(*ast.BinaryExpr)
	if !ok {
		return false
	}
	if !isName(cond.X, "err") {
		return false
	}
	if cond.Op != token.EQL {
		return false
	}
	if isName(cond.Y, "nil") {
		return false
	}
	cond.X = causeExpr(errgoIdent, "err")
	return true
}

func fixGocheck(n *ast.CallExpr, errgoIdent, gocheckIdent string) bool {
	// gc.Check(err, gc.Equals, foo-not-nil)
	// ->
	// gc.Check(errgo.Cause(err), gc.Equals, foo-not-nil)

	// gc.Check(err, gc.Not(gc.Equals), foo-not-nil)
	// ->
	// gc.Check(errgo.Cause(err), gc.Not(gc.Equals), foo-not-nil)
	if gocheckIdent == "" {
		return false
	}
	sel, ok := n.Fun.(*ast.SelectorExpr)
	if !ok {
		return false
	}
	if !isName(sel.X, "c") {
		return false
	}
	if s := sel.Sel.String(); s != "Check" && s != "Assert" {
		return false
	}

	if len(n.Args) < 3 {
		return false
	}
	if !isName(n.Args[0], "err") {
		return false
	}
	if condCall, ok := n.Args[1].(*ast.CallExpr); ok {
		if !isPkgDot(condCall.Fun, gocheckIdent, "Not") {
			return false
		}
		if len(condCall.Args) != 1 {
			return false
		}
		if !isPkgDot(condCall.Args[0], gocheckIdent, "Equals") {
			return false
		}
	} else if !isPkgDot(n.Args[1], gocheckIdent, "Equals") {
		return false
	}
	if isName(n.Args[2], "nil") {
		return false
	}
	n.Args[0] = causeExpr(errgoIdent, "err")
	return true
}

func causeExpr(errgoIdent string, ident string) *ast.CallExpr {
	return &ast.CallExpr{
		Fun: &ast.SelectorExpr{
			X:   ast.NewIdent(errgoIdent),
			Sel: ast.NewIdent("Cause"),
		},
		Args: []ast.Expr{ast.NewIdent(ident)},
	}
}
<|endoftext|>


<commit_before>/*
Package lmdbscan provides a wrapper for lmdb.Cursor to simplify iteration.
This package is experimental and it's API may change.
*/
package lmdbscan

import (
	"fmt"

	"github.com/bmatsuo/lmdb-go/lmdb"
)

// Scanner is a low level construct for scanning databases inside a
// transaction.
type Scanner struct {
	dbi     lmdb.DBI
	dbflags uint
	txn     *lmdb.Txn
	cur     *lmdb.Cursor
	op      uint
	setop   *uint
	setkey  []byte
	setval  []byte
	key     []byte
	val     []byte
	err     error
}

// New allocates and intializes a Scanner for dbi within txn.
func New(txn *lmdb.Txn, dbi lmdb.DBI) *Scanner {
	s := &Scanner{
		dbi: dbi,
		txn: txn,
	}
	if dbi != 0 {
		s.dbflags, s.err = txn.Flags(dbi)
		if s.err != nil {
			return s
		}
	}
	s.op = lmdb.Next

	s.cur, s.err = txn.OpenCursor(dbi)
	return s
}

// Cursor returns the lmdb.Cursor underlying s.  Cursor returns nil if the
// scanner is closed.
func (s *Scanner) Cursor() *lmdb.Cursor {
	return s.cur
}

// Del will delete the key at the current cursor location.
//
// Del is deprecated.  Instead use s.Cursor().Del(flags).
func (s *Scanner) Del(flags uint) error {
	if s.cur == nil {
		return fmt.Errorf("scanner is closed")
	}
	return s.cur.Del(flags)
}

// Key returns the key read during the last call to Scan.
func (s *Scanner) Key() []byte {
	return s.key
}

// Val returns the value read during the last call to Scan.
func (s *Scanner) Val() []byte {
	return s.val
}

// Set marks the starting position for iteration.  On the next call to s.Scan()
// the underlying cursor will be moved as
//		c.Get(k, v, op)
func (s *Scanner) Set(k, v []byte, op uint) {
	if s.err != nil {
		return
	}
	s.setop = new(uint)
	*s.setop = op
	s.setkey = k
	s.setval = v
}

// SetNext determines the cursor behavior for subsequent calls to s.Scan().
// The immediately following call to s.Scan() behaves as if s.Set(k,v,opset)
// was called.  Subsequent calls move the cursor as
//		c.Get(nil, nil, opnext)
func (s *Scanner) SetNext(k, v []byte, opset, opnext uint) {
	s.Set(k, v, opset)
	s.op = opnext
}

// Scan gets key-value successive pairs with the underlying cursor until one
// matches the supplied filters.  If all filters return a nil error for the
// current pair, true is returned.  Scan returns false if all key-value pairs
// where exhausted.
func (s *Scanner) Scan() bool {
	if s.setop == nil {
		s.key, s.val, s.err = s.cur.Get(nil, nil, s.op)
	} else {
		s.key, s.val, s.err = s.cur.Get(s.setkey, s.setval, *s.setop)
		s.setkey = nil
		s.setval = nil
		s.setop = nil
	}
	return s.err == nil
}

// Err returns a non-nil error if and only if the previous call to s.Scan()
// resulted in an error other than lmdb.ErrNotFound.
func (s *Scanner) Err() error {
	if lmdb.IsNotFound(s.err) {
		return nil
	}
	return s.err
}

// Close closes the cursor underlying s and clears its ows internal structures.
// Close does not attempt to terminate the enclosing transaction.
//
// Scan must not be called after Close.
func (s *Scanner) Close() {
	s.txn = nil
	if s.cur != nil {
		s.cur.Close()
		s.cur = nil
	}
}
<commit_msg>Docs: godoc fixes for lmdbscan<commit_after>/*
Package lmdbscan provides a wrapper for lmdb.Cursor to simplify iteration.
This package is experimental and it's API may change.
*/
package lmdbscan

import (
	"fmt"

	"github.com/bmatsuo/lmdb-go/lmdb"
)

// Scanner is a low level construct for scanning databases inside a
// transaction.
type Scanner struct {
	dbi     lmdb.DBI
	dbflags uint
	txn     *lmdb.Txn
	cur     *lmdb.Cursor
	op      uint
	setop   *uint
	setkey  []byte
	setval  []byte
	key     []byte
	val     []byte
	err     error
}

// New allocates and intializes a Scanner for dbi within txn.  When the Scanner
// returned by New is no longer needed its Close method must be called.
func New(txn *lmdb.Txn, dbi lmdb.DBI) *Scanner {
	s := &Scanner{
		dbi: dbi,
		txn: txn,
	}
	if dbi != 0 {
		s.dbflags, s.err = txn.Flags(dbi)
		if s.err != nil {
			return s
		}
	}
	s.op = lmdb.Next

	s.cur, s.err = txn.OpenCursor(dbi)
	return s
}

// Cursor returns the lmdb.Cursor underlying s.  Cursor returns nil if the
// scanner is closed.
func (s *Scanner) Cursor() *lmdb.Cursor {
	return s.cur
}

// Del will delete the key at the current cursor location.
//
// Del is deprecated.  Instead use s.Cursor().Del(flags).
func (s *Scanner) Del(flags uint) error {
	if s.cur == nil {
		return fmt.Errorf("scanner is closed")
	}
	return s.cur.Del(flags)
}

// Key returns the key read during the last call to Scan.
func (s *Scanner) Key() []byte {
	return s.key
}

// Val returns the value read during the last call to Scan.
func (s *Scanner) Val() []byte {
	return s.val
}

// Set marks the starting position for iteration.  On the next call to s.Scan()
// the underlying cursor will be moved as c.Get(k, v, opset).
func (s *Scanner) Set(k, v []byte, opset uint) {
	if s.err != nil {
		return
	}
	s.setop = new(uint)
	*s.setop = opset
	s.setkey = k
	s.setval = v
}

// SetNext determines the cursor behavior for subsequent calls to s.Scan().
// The immediately following call to s.Scan() behaves as if s.Set(k,v,opset)
// was called.  Subsequent calls move the cursor as c.Get(nil, nil, opnext)
func (s *Scanner) SetNext(k, v []byte, opset, opnext uint) {
	s.Set(k, v, opset)
	s.op = opnext
}

// Scan gets key-value successive pairs with the underlying cursor until one
// matches the supplied filters.  If all filters return a nil error for the
// current pair, true is returned.  Scan returns false if all key-value pairs
// where exhausted.
func (s *Scanner) Scan() bool {
	if s.setop == nil {
		s.key, s.val, s.err = s.cur.Get(nil, nil, s.op)
	} else {
		s.key, s.val, s.err = s.cur.Get(s.setkey, s.setval, *s.setop)
		s.setkey = nil
		s.setval = nil
		s.setop = nil
	}
	return s.err == nil
}

// Err returns a non-nil error if and only if the previous call to s.Scan()
// resulted in an error other than lmdb.ErrNotFound.
func (s *Scanner) Err() error {
	if lmdb.IsNotFound(s.err) {
		return nil
	}
	return s.err
}

// Close closes the cursor underlying s and clears its ows internal structures.
// Close does not attempt to terminate the enclosing transaction.
//
// Scan must not be called after Close.
func (s *Scanner) Close() {
	s.txn = nil
	if s.cur != nil {
		s.cur.Close()
		s.cur = 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
//
// 	https://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 fetchmetadata

import (
	"github.com/google/go-safeweb/safehttp"
)

// RequestLogger is used for logging Fetch Metadata policy violations;
type RequestLogger interface {
	Log(ir *safehttp.IncomingRequest)
}

var (
	navigationalModes = map[string]bool{
		"navigate":        true,
		"nested-navigate": true,
	}
	navigationalDest = map[string]bool{
		"document":        true,
		"nested-document": true,
	}
	statePreservingMethods = map[string]bool{
		safehttp.MethodGet:  true,
		safehttp.MethodHead: true,
	}
)

// Plugin implements Fetch Metadata functionality.
//
// See https://www.w3.org/TR/fetch-metadata/ and
// https://web.dev/fetch-metadata/  for more details.
type Plugin struct {
	// NavIsolation indicates whether the Navigation Isolation Policy should
	// be applied to the request before the Resource Isolation Policy as an
	// additional layer of checking. This provides a way to mitigate
	// clickjacking and reflected XSS by rejecting all cross-site navigations
	// unless targeted to endpoints that are CORS-protected.
	//
	// WARNING: This is still an experimental feature and will be disabled by
	// default.
	NavIsolation bool
	// RedirectURL can optionally indicate an URL to which the user can be
	// redirected in case the Navigation Isolation policy rejects the request.
	RedirectURL   *safehttp.URL
	Logger        RequestLogger
	reportOnly    bool
	corsProtected map[string]bool
}

// NewPlugin creates a new Fetch Metadata plugin in enforce mode that will apply
// the Resource Isolation Policy by default. The user can provide a set of
// endpoints that are CORS-protected. Any request targeted to those endpoints
// will be allowed by default without the policies being applied.
func NewPlugin(endpoints ...string) *Plugin {
	m := map[string]bool{}
	for _, e := range endpoints {
		m[e] = true
	}
	return &Plugin{corsProtected: m}
}

func (p *Plugin) resourceIsolationPolicy(r *safehttp.IncomingRequest) bool {
	h := r.Header
	if h.Get("Sec-Fetch-Site") != "cross-site" {
		// The request is allowed to pass because one of the following applies:
		// - Fetch Metadata is not supported by the browser
		// - the request is same-origin, same-site or caused by the user
		// explicitly interacting with the user-agent
		return true
	}

	method := r.Method()
	mode := h.Get("Sec-Fetch-Mode")
	dest := h.Get("Sec-Fetch-Dest")
	// Allow CORS options requests if neither Mode nor Dest is set.
	// https://github.com/w3c/webappsec-fetch-metadata/issues/35
	// https://bugs.chromium.org/p/chromium/issues/detail?id=979946
	if mode == "" && dest == "" && method == safehttp.MethodOptions {
		return true
	}

	if navigationalModes[mode] && navigationalDest[dest] && statePreservingMethods[method] {
		// The request is cross-site, but a simple top-level navigation from a
		// safe destination so we  allow it to pass.
		return true
	}
	// The request is cross-site and not a simple navigation or from an unsafe
	// destination so it is rejected.
	return false
}

func (p *Plugin) navigationIsolationPolicy(r *safehttp.IncomingRequest) bool {
	if h := r.Header; p.NavIsolation && h.Get("Sec-Fetch-Site") == "cross-site" && navigationalModes[h.Get("Sec-Fetch-Mode")] {
		return false
	}
	return true
}

// SetReportOnly sets the Fetch Metadata policy mode to "report". This will
// allow requests that violate the policy to pass, but will report the violation
// using the RequestLogger. The method will panic if RequestLogger is nil.
func (p *Plugin) SetReportOnly() {
	if p.Logger == nil {
		panic("logging service required for Fetch Metadata report mode")
	}
	p.reportOnly = true
}

// SetEnforce sets the Fetch Metadata policy mode to "enforce". This will reject
// any requests that violates the policy provided by the plugin.
func (p *Plugin) SetEnforce() {
	p.reportOnly = false
}

// Before validates the safehttp.IncomingRequest using the Resource Isolation
// Policy and, if enabled, the Navigation Isolation Policy. It only allows
// requests to pass if they conform to the policy, if it's targeted to one of
// the CORS-protected endpoints, specified when creating the plugin, or if the
// mode is set to  "report", in which case the request is allowed to pass but
// the  violation is reported. If a redirectURL was provided and the Navigation
// Isolation Policy is enabled and fails, the IncomingRequest will be
// redirected to redirectURL.
func (p *Plugin) Before(w safehttp.ResponseWriter, r *safehttp.IncomingRequest) safehttp.Result {
	if p.corsProtected[r.URL.Path()] {
		// The request is targeted to an endpoint on which Fetch Metadata
		// policies are disabled because it is CORS-protected so we don't apply
		// the policies.
		return safehttp.Result{}
	}

	rejected := false
	if !p.navigationIsolationPolicy(r) {
		rejected = true
		if p.RedirectURL != nil {
			return w.Redirect(r, p.RedirectURL.String(), safehttp.StatusMovedPermanently)
		}
	}

	if rejected || !p.resourceIsolationPolicy(r) {
		if p.Logger != nil {
			p.Logger.Log(r)
		}
		if p.reportOnly {
			return safehttp.Result{}
		}
		return w.ClientError(safehttp.StatusForbidden)
	}

	return safehttp.Result{}
}
<commit_msg>Added TODO and rename functions that apply policies<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
//
// 	https://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 fetchmetadata

import (
	"github.com/google/go-safeweb/safehttp"
)

// RequestLogger is used for logging Fetch Metadata policy violations;
type RequestLogger interface {
	Log(ir *safehttp.IncomingRequest)
}

var (
	navigationalModes = map[string]bool{
		"navigate":        true,
		"nested-navigate": true,
	}
	navigationalDest = map[string]bool{
		"document":        true,
		"nested-document": true,
	}
	statePreservingMethods = map[string]bool{
		safehttp.MethodGet:  true,
		safehttp.MethodHead: true,
	}
)

// Plugin implements Fetch Metadata functionality.
//
// See https://www.w3.org/TR/fetch-metadata/ and
// https://web.dev/fetch-metadata/  for more details.
type Plugin struct {
	// NavIsolation indicates whether the Navigation Isolation Policy should
	// be applied to the request before the Resource Isolation Policy as an
	// additional layer of checking. This provides a way to mitigate
	// clickjacking and reflected XSS by rejecting all cross-site navigations
	// unless targeted to endpoints that are CORS-protected.
	//
	// WARNING: This is still an experimental feature and is disabled by
	// default.
	NavIsolation bool
	// RedirectURL can optionally indicate an URL to which the user can be
	// redirected in case the Navigation Isolation policy rejects the request.
	RedirectURL   *safehttp.URL
	Logger        RequestLogger
	reportOnly    bool
	corsProtected map[string]bool
}

// NewPlugin creates a new Fetch Metadata plugin in enforce mode that will apply
// the Resource Isolation Policy by default. The user can provide a set of
// endpoints that are CORS-protected. Any request targeted to those endpoints
// will be allowed by default without the policies being applied.
func NewPlugin(endpoints ...string) *Plugin {
	m := map[string]bool{}
	for _, e := range endpoints {
		m[e] = true
	}
	return &Plugin{corsProtected: m}
}

func (p *Plugin) checkResourceIsolationPolicy(r *safehttp.IncomingRequest) bool {
	h := r.Header
	if h.Get("Sec-Fetch-Site") != "cross-site" {
		// The request is allowed to pass because one of the following applies:
		// - Fetch Metadata is not supported by the browser
		// - the request is same-origin, same-site or caused by the user
		// explicitly interacting with the user-agent
		return true
	}

	method := r.Method()
	mode := h.Get("Sec-Fetch-Mode")
	dest := h.Get("Sec-Fetch-Dest")
	// Allow CORS options requests if neither Mode nor Dest is set.
	// https://github.com/w3c/webappsec-fetch-metadata/issues/35
	// https://bugs.chromium.org/p/chromium/issues/detail?id=979946
	if mode == "" && dest == "" && method == safehttp.MethodOptions {
		return true
	}

	if navigationalModes[mode] && navigationalDest[dest] && statePreservingMethods[method] {
		// The request is cross-site, but a simple top-level navigation from a
		// safe destination so we  allow it to pass.
		return true
	}
	// The request is cross-site and not a simple navigation or from an unsafe
	// destination so it is rejected.
	return false
}

func (p *Plugin) checkNavigationIsolationPolicy(r *safehttp.IncomingRequest) bool {
	if h := r.Header; p.NavIsolation && h.Get("Sec-Fetch-Site") == "cross-site" && navigationalModes[h.Get("Sec-Fetch-Mode")] {
		return false
	}
	return true
}

// SetReportOnly sets the Fetch Metadata policy mode to "report". This will
// allow requests that violate the policy to pass, but will report the violation
// using the RequestLogger. The method will panic if RequestLogger is nil.
func (p *Plugin) SetReportOnly() {
	if p.Logger == nil {
		panic("logging service required for Fetch Metadata report mode")
	}
	p.reportOnly = true
}

// SetEnforce sets the Fetch Metadata policy mode to "enforce". This will reject
// any requests that violates the policy provided by the plugin.
func (p *Plugin) SetEnforce() {
	p.reportOnly = false
}

// Before validates the safehttp.IncomingRequest using the Resource Isolation
// Policy and, if enabled, the Navigation Isolation Policy. It only allows
// requests to pass if they conform to the policy, if it's targeted to one of
// the CORS-protected endpoints, specified when creating the plugin, or if the
// mode is set to  "report", in which case the request is allowed to pass but
// the  violation is reported. If a redirectURL was provided and the Navigation
// Isolation Policy is enabled and fails, the IncomingRequest will be
// redirected to redirectURL.
func (p *Plugin) Before(w safehttp.ResponseWriter, r *safehttp.IncomingRequest) safehttp.Result {
	// TODO(mihalimara22): Remove and disable using configurations when those
	// have been implemented
	if p.corsProtected[r.URL.Path()] {
		// The request is targeted to an endpoint on which Fetch Metadata
		// policies are disabled because it is CORS-protected so we don't apply
		// the policies.
		return safehttp.Result{}
	}

	rejected := false
	if !p.checkNavigationIsolationPolicy(r) {
		rejected = true
		if p.RedirectURL != nil {
			return w.Redirect(r, p.RedirectURL.String(), safehttp.StatusMovedPermanently)
		}
	}

	if rejected || !p.checkResourceIsolationPolicy(r) {
		if p.Logger != nil {
			p.Logger.Log(r)
		}
		if p.reportOnly {
			return safehttp.Result{}
		}
		return w.ClientError(safehttp.StatusForbidden)
	}

	return safehttp.Result{}
}
<|endoftext|>