<commit_before>package workload

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

	"github.com/docker/distribution/reference"
	"github.com/rancher/norman/api/access"
	"github.com/rancher/norman/httperror"
	"github.com/rancher/norman/types"
	"github.com/rancher/norman/types/convert"
	"github.com/rancher/norman/types/values"
	"github.com/rancher/rancher/pkg/api/customization/workload"
	"github.com/rancher/rancher/pkg/clustermanager"
	managementschema "github.com/rancher/types/apis/management.cattle.io/v3/schema"
	"github.com/rancher/types/apis/project.cattle.io/v3/schema"
	projectschema "github.com/rancher/types/apis/project.cattle.io/v3/schema"
	managementv3 "github.com/rancher/types/client/management/v3"
	projectclient "github.com/rancher/types/client/project/v3"
	"github.com/sirupsen/logrus"
	corev1 "k8s.io/api/core/v1"
)

func NewWorkloadAggregateStore(schemas *types.Schemas, manager *clustermanager.Manager) {
	workloadSchema := schemas.Schema(&schema.Version, "workload")
	store := NewAggregateStore(schemas.Schema(&schema.Version, "deployment"),
		schemas.Schema(&schema.Version, "replicaSet"),
		schemas.Schema(&schema.Version, "replicationController"),
		schemas.Schema(&schema.Version, "daemonSet"),
		schemas.Schema(&schema.Version, "statefulSet"),
		schemas.Schema(&schema.Version, "job"),
		schemas.Schema(&schema.Version, "cronJob"))

	for _, s := range store.Schemas {
		if s.ID == "deployment" {
			s.Formatter = workload.DeploymentFormatter
		} else {
			s.Formatter = workload.Formatter
		}
	}

	workloadSchema.Store = store
	actionWrapper := workload.ActionWrapper{
		ClusterManager: manager,
	}
	workloadSchema.ActionHandler = actionWrapper.ActionHandler
	workloadSchema.LinkHandler = workload.Handler{}.LinkHandler
}

func NewCustomizeStore(store types.Store) types.Store {
	return &CustomizeStore{
		Store: NewTransformStore(store),
	}
}

type CustomizeStore struct {
	types.Store
}

func (s *CustomizeStore) Create(apiContext *types.APIContext, schema *types.Schema, data map[string]interface{}) (map[string]interface{}, error) {
	setSelector(schema.ID, data)
	setWorkloadSpecificDefaults(schema.ID, data)
	setSecrets(apiContext, data, true)
	if err := setPorts(convert.ToString(data["name"]), data); err != nil {
		return nil, err
	}
	setScheduling(apiContext, data)
	setStrategy(data)
	return s.Store.Create(apiContext, schema, data)
}

func (s *CustomizeStore) Update(apiContext *types.APIContext, schema *types.Schema, data map[string]interface{}, id string) (map[string]interface{}, error) {
	splitted := strings.Split(id, ":")
	if err := setPorts(splitted[1], data); err != nil {
		return nil, err
	}
	setScheduling(apiContext, data)
	setStrategy(data)
	if err := setSecrets(apiContext, data, false); err != nil {
		return nil, err
	}
	return s.Store.Update(apiContext, schema, data, id)
}

func (s *CustomizeStore) ByID(apiContext *types.APIContext, schema *types.Schema, id string) (map[string]interface{}, error) {
	shortID := id
	if strings.Count(id, ":") > 1 {
		_, shortID = splitTypeAndID(id)
	}
	return s.Store.ByID(apiContext, schema, shortID)
}

func setScheduling(apiContext *types.APIContext, data map[string]interface{}) {
	if nodeID := convert.ToString(values.GetValueN(data, "scheduling", "node", "nodeId")); nodeID != "" {
		nodeName := getNodeName(apiContext, nodeID)
		values.PutValue(data, nodeName, "scheduling", "node", "nodeId")
		state := getState(data)
		state[getKey(nodeName)] = nodeID
		setState(data, state)
	} else {
		values.PutValue(data, "", "nodeId")
	}
}

func setStrategy(data map[string]interface{}) {
	strategy, ok := values.GetValue(data, "deploymentConfig", "strategy")
	if ok && convert.ToString(strategy) == "Recreate" {
		values.RemoveValue(data, "deploymentConfig", "maxSurge")
		values.RemoveValue(data, "deploymentConfig", "maxUnavailable")
	}
}

func setSelector(schemaID string, data map[string]interface{}) {
	setSelector := false
	isJob := strings.EqualFold(schemaID, "job") || strings.EqualFold(schemaID, "cronJob")
	if convert.IsAPIObjectEmpty(data["selector"]) && !isJob {
		setSelector = true
	}
	if setSelector {
		workloadID := resolveWorkloadID(schemaID, data)
		// set selector
		data["selector"] = map[string]interface{}{
			"matchLabels": map[string]interface{}{
				SelectorLabel: workloadID,
			},
		}

		// set workload labels
		workloadLabels := convert.ToMapInterface(data["workloadLabels"])
		if workloadLabels == nil {
			workloadLabels = make(map[string]interface{})
		}
		workloadLabels[SelectorLabel] = workloadID
		data["workloadLabels"] = workloadLabels

		// set labels
		labels := convert.ToMapInterface(data["labels"])
		if labels == nil {
			labels = make(map[string]interface{})
		}
		labels[SelectorLabel] = workloadID
		data["labels"] = labels
	}
}

func getSecrets(apiContext *types.APIContext, data map[string]interface{}) *[]corev1.LocalObjectReference {
	imagePullSecrets, _ := data["imagePullSecrets"].([]corev1.LocalObjectReference)
	if containers, _ := values.GetSlice(data, "containers"); len(containers) > 0 {
		domainToCreds := getCreds(apiContext, convert.ToString(data["namespaceId"]))
		for _, container := range containers {
			if image := convert.ToString(container["image"]); image != "" {
				domain := getDomain(image)
				if secrets, ok := domainToCreds[domain]; ok {
					imagePullSecrets = append(imagePullSecrets, secrets...)
				}
			}
		}
	}
	return &imagePullSecrets
}

func setSecrets(apiContext *types.APIContext, data map[string]interface{}, create bool) error {
	imagePullSecrets := getSecrets(apiContext, data)
	if imagePullSecrets != nil {
		if create {
			values.PutValue(data, imagePullSecrets, "imagePullSecrets")
		} else {
			if updated, err := imageUpdated(apiContext, data); err != nil {
				return httperror.NewAPIError(httperror.ServerError, fmt.Sprintf("error accessing workload : %v", err))
			} else if updated {
				values.PutValue(data, imagePullSecrets, "imagePullSecrets")
			}
		}
	}
	return nil
}

func setWorkloadSpecificDefaults(schemaID string, data map[string]interface{}) {
	if strings.EqualFold(schemaID, "job") || strings.EqualFold(schemaID, "cronJob") {
		// job has different defaults
		if _, ok := data["restartPolicy"]; !ok {
			data["restartPolicy"] = "OnFailure"
		}
	}
}

func setPorts(workloadName string, data map[string]interface{}) error {
	containers, ok := values.GetValue(data, "containers")
	if !ok {
		return nil
	}

	for _, c := range convert.ToInterfaceSlice(containers) {
		cMap, err := convert.EncodeToMap(c)
		if err != nil {
			logrus.Warnf("Failed to transform container to map: %v", err)
			continue
		}
		v, ok := values.GetValue(cMap, "ports")

		if ok {
			ports := convert.ToInterfaceSlice(v)
			usedNames := map[string]bool{}
			for _, p := range ports {
				port, err := convert.EncodeToMap(p)
				if err != nil {
					logrus.Warnf("Failed to transform port to map %v", err)
					continue
				}

				containerPort, err := convert.ToNumber(port["containerPort"])
				if err != nil {
					return httperror.NewAPIError(httperror.InvalidOption, fmt.Sprintf("failed to transform container port [%v] "+
						"to number: %v", port["containerPort"], err))
				}

				containerPortStr := strconv.Itoa(int(containerPort))
				protocol := strings.ToLower(convert.ToString(port["protocol"]))
				sourcePort := strings.ToLower(convert.ToString(port["sourcePort"]))
				portName := convert.ToString(port["name"])
				if portName == "" || isRancherGeneratedPort(portName, containerPortStr, protocol) {
					// port name is of format containerPortProtoSourcePortKind
					// len limit is 15, therefore a) no separator b) kind is numerated
					numKind := "0"
					switch kind := convert.ToString(port["kind"]); kind {
					case "NodePort":
						numKind = "1"
					case "ClusterIP":
						numKind = "2"
					case "LoadBalancer":
						numKind = "3"
					}
					portName = fmt.Sprintf("%s%s%s%s",
						containerPortStr,
						protocol,
						sourcePort,
						numKind)
				}

				//validate port name
				if _, ok := usedNames[portName]; ok {
					return httperror.NewAPIError(httperror.InvalidOption, fmt.Sprintf("Duplicated port kind=%v,"+
						" conainerPort=%v, protcol=%v", port["kind"], port["containerPort"], port["protocol"]))
				}
				usedNames[portName] = true
				port["name"] = portName

				if generateDNSName(workloadName, convert.ToString(port["dnsName"])) {
					if port["kind"] == "ClusterIP" {
						// use workload name for clusterIP service as it will be used by dns resolution
						port["dnsName"] = strings.ToLower(convert.ToString(workloadName))
					} else {
						port["dnsName"] = fmt.Sprintf("%s-%s", strings.ToLower(convert.ToString(workloadName)),
							strings.ToLower(convert.ToString(port["kind"])))
					}
				}
			}
		}
	}
	return nil
}

func isRancherGeneratedPort(portName, containerPort, protocol string) bool {
	if strings.HasPrefix(portName, fmt.Sprintf("%s%s", containerPort, protocol)) {
		return true
	}
	return false
}
func generateDNSName(workloadName, dnsName string) bool {
	if dnsName == "" {
		return true
	}
	// regenerate the name in case port type got changed
	if strings.EqualFold(dnsName, workloadName) || strings.HasPrefix(dnsName, fmt.Sprintf("%s-", workloadName)) {
		return true
	}
	return false
}

func getCreds(apiContext *types.APIContext, namespaceID string) map[string][]corev1.LocalObjectReference {
	domainToCreds := make(map[string][]corev1.LocalObjectReference)
	var namespacedCreds []projectclient.NamespacedDockerCredential
	if err := access.List(apiContext, &projectschema.Version, "namespacedDockerCredential", &types.QueryOptions{}, &namespacedCreds); err == nil {
		for _, cred := range namespacedCreds {
			if cred.NamespaceId == namespaceID {
				store(cred.Registries, domainToCreds, cred.Name)
			}
		}
	}
	var creds []projectclient.DockerCredential
	if err := access.List(apiContext, &projectschema.Version, "dockerCredential", &types.QueryOptions{}, &creds); err == nil {
		for _, cred := range creds {
			store(cred.Registries, domainToCreds, cred.Name)
		}
	}
	return domainToCreds
}

func getNodeName(apiContext *types.APIContext, nodeID string) string {
	var node managementv3.Node
	var nodeName string
	if err := access.ByID(apiContext, &managementschema.Version, managementv3.NodeType, nodeID, &node); err == nil {
		nodeName = node.NodeName
	}
	return nodeName
}

func setState(data map[string]interface{}, stateMap map[string]string) {
	content, err := json.Marshal(stateMap)
	if err != nil {
		logrus.Errorf("failed to save state on workload: %v", data["id"])
		return
	}

	values.PutValue(data, string(content), "annotations", "workload.cattle.io/state")
}

func getState(data map[string]interface{}) map[string]string {
	state := map[string]string{}

	v, ok := values.GetValue(data, "annotations", "workload.cattle.io/state")
	if ok {
		json.Unmarshal([]byte(convert.ToString(v)), &state)
	}

	return state
}

func getDomain(image string) string {
	var repo string
	named, err := reference.ParseNormalizedNamed(image)
	if err != nil {
		logrus.Debug(err)
		return repo
	}
	domain := reference.Domain(named)
	if domain == "docker.io" {
		return "index.docker.io"
	}
	return domain
}

func imageUpdated(apiContext *types.APIContext, data map[string]interface{}) (bool, error) {
	var workload projectclient.Workload
	accessError := access.ByID(apiContext, &projectschema.Version, "workload", apiContext.ID, &workload)
	if accessError != nil {
		return false, httperror.NewAPIError(httperror.InvalidReference, "Error accessing workload")
	}

	containers, _ := values.GetSlice(data, "containers")
	if len(containers) != len(workload.Containers) {
		return true, nil
	}
	for i := range containers {
		if workload.Containers[i].Image != convert.ToString(containers[i]["image"]) {
			return true, nil
		}
	}
	return false, nil
}
<commit_msg>throw error when statefulset volumeMounts.subPath is invalid<commit_after>package workload

import (
	"encoding/json"
	"fmt"
	"path"
	"strconv"
	"strings"

	"github.com/docker/distribution/reference"
	"github.com/rancher/norman/api/access"
	"github.com/rancher/norman/httperror"
	"github.com/rancher/norman/types"
	"github.com/rancher/norman/types/convert"
	"github.com/rancher/norman/types/values"
	"github.com/rancher/rancher/pkg/api/customization/workload"
	"github.com/rancher/rancher/pkg/clustermanager"
	managementschema "github.com/rancher/types/apis/management.cattle.io/v3/schema"
	"github.com/rancher/types/apis/project.cattle.io/v3/schema"
	projectschema "github.com/rancher/types/apis/project.cattle.io/v3/schema"
	managementv3 "github.com/rancher/types/client/management/v3"
	projectclient "github.com/rancher/types/client/project/v3"
	"github.com/sirupsen/logrus"
	corev1 "k8s.io/api/core/v1"
)

func NewWorkloadAggregateStore(schemas *types.Schemas, manager *clustermanager.Manager) {
	workloadSchema := schemas.Schema(&schema.Version, "workload")
	store := NewAggregateStore(schemas.Schema(&schema.Version, "deployment"),
		schemas.Schema(&schema.Version, "replicaSet"),
		schemas.Schema(&schema.Version, "replicationController"),
		schemas.Schema(&schema.Version, "daemonSet"),
		schemas.Schema(&schema.Version, "statefulSet"),
		schemas.Schema(&schema.Version, "job"),
		schemas.Schema(&schema.Version, "cronJob"))

	for _, s := range store.Schemas {
		if s.ID == "deployment" {
			s.Formatter = workload.DeploymentFormatter
		} else {
			s.Formatter = workload.Formatter
		}
	}

	workloadSchema.Store = store
	actionWrapper := workload.ActionWrapper{
		ClusterManager: manager,
	}
	workloadSchema.ActionHandler = actionWrapper.ActionHandler
	workloadSchema.LinkHandler = workload.Handler{}.LinkHandler
}

func NewCustomizeStore(store types.Store) types.Store {
	return &CustomizeStore{
		Store: NewTransformStore(store),
	}
}

type CustomizeStore struct {
	types.Store
}

func (s *CustomizeStore) Create(apiContext *types.APIContext, schema *types.Schema, data map[string]interface{}) (map[string]interface{}, error) {
	setSelector(schema.ID, data)
	setWorkloadSpecificDefaults(schema.ID, data)
	setSecrets(apiContext, data, true)
	if err := setPorts(convert.ToString(data["name"]), data); err != nil {
		return nil, err
	}
	setScheduling(apiContext, data)
	setStrategy(data)

	err := s.validateStatefulSetVolume(schema, data)
	if err != nil {
		return nil, err
	}
	return s.Store.Create(apiContext, schema, data)
}

func (s *CustomizeStore) Update(apiContext *types.APIContext, schema *types.Schema, data map[string]interface{}, id string) (map[string]interface{}, error) {
	splitted := strings.Split(id, ":")
	if err := setPorts(splitted[1], data); err != nil {
		return nil, err
	}
	setScheduling(apiContext, data)
	setStrategy(data)
	if err := setSecrets(apiContext, data, false); err != nil {
		return nil, err
	}

	err := s.validateStatefulSetVolume(schema, data)
	if err != nil {
		return nil, err
	}
	return s.Store.Update(apiContext, schema, data, id)
}

func (s *CustomizeStore) validateStatefulSetVolume(schema *types.Schema, data map[string]interface{}) error {
	if schema.ID == "statefulSet" {
		// retrieve volumeMounts.subPath
		containers, _ := values.GetSlice(data, "containers")
		for i := range containers {
			container := containers[i]
			volumeMounts, _ := values.GetSlice(container, "volumeMounts")
			for j := range volumeMounts {
				vmnt := volumeMounts[j]
				subPath := convert.ToString(vmnt["subPath"])
				err := s.validateSubPath(subPath)
				if err != nil {
					return err
				}
			}
		}
	}
	return nil
}

// This validate will make sure subPath:
// 1. is not abs path
// 2. does not have any element which is ".."
func (s *CustomizeStore) validateSubPath(subPath string) error {
	if path.IsAbs(subPath) {
		return httperror.NewAPIError(httperror.MissingRequired, fmt.Sprintf("Invalid value: Sub Path in Volume %v must be a relative path", subPath))
	}
	parts := strings.Split(subPath, "/")
	for _, item := range parts {
		if item == ".." {
			return httperror.NewAPIError(httperror.MissingRequired, fmt.Sprintf("Invalid value: Sub Path in Volume %v must not contain '..'", subPath))
		}
	}
	return nil
}

func (s *CustomizeStore) ByID(apiContext *types.APIContext, schema *types.Schema, id string) (map[string]interface{}, error) {
	shortID := id
	if strings.Count(id, ":") > 1 {
		_, shortID = splitTypeAndID(id)
	}
	return s.Store.ByID(apiContext, schema, shortID)
}

func setScheduling(apiContext *types.APIContext, data map[string]interface{}) {
	if nodeID := convert.ToString(values.GetValueN(data, "scheduling", "node", "nodeId")); nodeID != "" {
		nodeName := getNodeName(apiContext, nodeID)
		values.PutValue(data, nodeName, "scheduling", "node", "nodeId")
		state := getState(data)
		state[getKey(nodeName)] = nodeID
		setState(data, state)
	} else {
		values.PutValue(data, "", "nodeId")
	}
}

func setStrategy(data map[string]interface{}) {
	strategy, ok := values.GetValue(data, "deploymentConfig", "strategy")
	if ok && convert.ToString(strategy) == "Recreate" {
		values.RemoveValue(data, "deploymentConfig", "maxSurge")
		values.RemoveValue(data, "deploymentConfig", "maxUnavailable")
	}
}

func setSelector(schemaID string, data map[string]interface{}) {
	setSelector := false
	isJob := strings.EqualFold(schemaID, "job") || strings.EqualFold(schemaID, "cronJob")
	if convert.IsAPIObjectEmpty(data["selector"]) && !isJob {
		setSelector = true
	}
	if setSelector {
		workloadID := resolveWorkloadID(schemaID, data)
		// set selector
		data["selector"] = map[string]interface{}{
			"matchLabels": map[string]interface{}{
				SelectorLabel: workloadID,
			},
		}

		// set workload labels
		workloadLabels := convert.ToMapInterface(data["workloadLabels"])
		if workloadLabels == nil {
			workloadLabels = make(map[string]interface{})
		}
		workloadLabels[SelectorLabel] = workloadID
		data["workloadLabels"] = workloadLabels

		// set labels
		labels := convert.ToMapInterface(data["labels"])
		if labels == nil {
			labels = make(map[string]interface{})
		}
		labels[SelectorLabel] = workloadID
		data["labels"] = labels
	}
}

func getSecrets(apiContext *types.APIContext, data map[string]interface{}) *[]corev1.LocalObjectReference {
	imagePullSecrets, _ := data["imagePullSecrets"].([]corev1.LocalObjectReference)
	if containers, _ := values.GetSlice(data, "containers"); len(containers) > 0 {
		domainToCreds := getCreds(apiContext, convert.ToString(data["namespaceId"]))
		for _, container := range containers {
			if image := convert.ToString(container["image"]); image != "" {
				domain := getDomain(image)
				if secrets, ok := domainToCreds[domain]; ok {
					imagePullSecrets = append(imagePullSecrets, secrets...)
				}
			}
		}
	}
	return &imagePullSecrets
}

func setSecrets(apiContext *types.APIContext, data map[string]interface{}, create bool) error {
	imagePullSecrets := getSecrets(apiContext, data)
	if imagePullSecrets != nil {
		if create {
			values.PutValue(data, imagePullSecrets, "imagePullSecrets")
		} else {
			if updated, err := imageUpdated(apiContext, data); err != nil {
				return httperror.NewAPIError(httperror.ServerError, fmt.Sprintf("error accessing workload : %v", err))
			} else if updated {
				values.PutValue(data, imagePullSecrets, "imagePullSecrets")
			}
		}
	}
	return nil
}

func setWorkloadSpecificDefaults(schemaID string, data map[string]interface{}) {
	if strings.EqualFold(schemaID, "job") || strings.EqualFold(schemaID, "cronJob") {
		// job has different defaults
		if _, ok := data["restartPolicy"]; !ok {
			data["restartPolicy"] = "OnFailure"
		}
	}
}

func setPorts(workloadName string, data map[string]interface{}) error {
	containers, ok := values.GetValue(data, "containers")
	if !ok {
		return nil
	}

	for _, c := range convert.ToInterfaceSlice(containers) {
		cMap, err := convert.EncodeToMap(c)
		if err != nil {
			logrus.Warnf("Failed to transform container to map: %v", err)
			continue
		}
		v, ok := values.GetValue(cMap, "ports")

		if ok {
			ports := convert.ToInterfaceSlice(v)
			usedNames := map[string]bool{}
			for _, p := range ports {
				port, err := convert.EncodeToMap(p)
				if err != nil {
					logrus.Warnf("Failed to transform port to map %v", err)
					continue
				}

				containerPort, err := convert.ToNumber(port["containerPort"])
				if err != nil {
					return httperror.NewAPIError(httperror.InvalidOption, fmt.Sprintf("failed to transform container port [%v] "+
						"to number: %v", port["containerPort"], err))
				}

				containerPortStr := strconv.Itoa(int(containerPort))
				protocol := strings.ToLower(convert.ToString(port["protocol"]))
				sourcePort := strings.ToLower(convert.ToString(port["sourcePort"]))
				portName := convert.ToString(port["name"])
				if portName == "" || isRancherGeneratedPort(portName, containerPortStr, protocol) {
					// port name is of format containerPortProtoSourcePortKind
					// len limit is 15, therefore a) no separator b) kind is numerated
					numKind := "0"
					switch kind := convert.ToString(port["kind"]); kind {
					case "NodePort":
						numKind = "1"
					case "ClusterIP":
						numKind = "2"
					case "LoadBalancer":
						numKind = "3"
					}
					portName = fmt.Sprintf("%s%s%s%s",
						containerPortStr,
						protocol,
						sourcePort,
						numKind)
				}

				//validate port name
				if _, ok := usedNames[portName]; ok {
					return httperror.NewAPIError(httperror.InvalidOption, fmt.Sprintf("Duplicated port kind=%v,"+
						" conainerPort=%v, protcol=%v", port["kind"], port["containerPort"], port["protocol"]))
				}
				usedNames[portName] = true
				port["name"] = portName

				if generateDNSName(workloadName, convert.ToString(port["dnsName"])) {
					if port["kind"] == "ClusterIP" {
						// use workload name for clusterIP service as it will be used by dns resolution
						port["dnsName"] = strings.ToLower(convert.ToString(workloadName))
					} else {
						port["dnsName"] = fmt.Sprintf("%s-%s", strings.ToLower(convert.ToString(workloadName)),
							strings.ToLower(convert.ToString(port["kind"])))
					}
				}
			}
		}
	}
	return nil
}

func isRancherGeneratedPort(portName, containerPort, protocol string) bool {
	if strings.HasPrefix(portName, fmt.Sprintf("%s%s", containerPort, protocol)) {
		return true
	}
	return false
}

func generateDNSName(workloadName, dnsName string) bool {
	if dnsName == "" {
		return true
	}
	// regenerate the name in case port type got changed
	if strings.EqualFold(dnsName, workloadName) || strings.HasPrefix(dnsName, fmt.Sprintf("%s-", workloadName)) {
		return true
	}
	return false
}

func getCreds(apiContext *types.APIContext, namespaceID string) map[string][]corev1.LocalObjectReference {
	domainToCreds := make(map[string][]corev1.LocalObjectReference)
	var namespacedCreds []projectclient.NamespacedDockerCredential
	if err := access.List(apiContext, &projectschema.Version, "namespacedDockerCredential", &types.QueryOptions{}, &namespacedCreds); err == nil {
		for _, cred := range namespacedCreds {
			if cred.NamespaceId == namespaceID {
				store(cred.Registries, domainToCreds, cred.Name)
			}
		}
	}
	var creds []projectclient.DockerCredential
	if err := access.List(apiContext, &projectschema.Version, "dockerCredential", &types.QueryOptions{}, &creds); err == nil {
		for _, cred := range creds {
			store(cred.Registries, domainToCreds, cred.Name)
		}
	}
	return domainToCreds
}

func getNodeName(apiContext *types.APIContext, nodeID string) string {
	var node managementv3.Node
	var nodeName string
	if err := access.ByID(apiContext, &managementschema.Version, managementv3.NodeType, nodeID, &node); err == nil {
		nodeName = node.NodeName
	}
	return nodeName
}

func setState(data map[string]interface{}, stateMap map[string]string) {
	content, err := json.Marshal(stateMap)
	if err != nil {
		logrus.Errorf("failed to save state on workload: %v", data["id"])
		return
	}

	values.PutValue(data, string(content), "annotations", "workload.cattle.io/state")
}

func getState(data map[string]interface{}) map[string]string {
	state := map[string]string{}

	v, ok := values.GetValue(data, "annotations", "workload.cattle.io/state")
	if ok {
		json.Unmarshal([]byte(convert.ToString(v)), &state)
	}

	return state
}

func getDomain(image string) string {
	var repo string
	named, err := reference.ParseNormalizedNamed(image)
	if err != nil {
		logrus.Debug(err)
		return repo
	}
	domain := reference.Domain(named)
	if domain == "docker.io" {
		return "index.docker.io"
	}
	return domain
}

func imageUpdated(apiContext *types.APIContext, data map[string]interface{}) (bool, error) {
	var workload projectclient.Workload
	accessError := access.ByID(apiContext, &projectschema.Version, "workload", apiContext.ID, &workload)
	if accessError != nil {
		return false, httperror.NewAPIError(httperror.InvalidReference, "Error accessing workload")
	}

	containers, _ := values.GetSlice(data, "containers")
	if len(containers) != len(workload.Containers) {
		return true, nil
	}
	for i := range containers {
		if workload.Containers[i].Image != convert.ToString(containers[i]["image"]) {
			return true, nil
		}
	}
	return false, nil
}
<|endoftext|>


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

package graphics

import (
	"errors"
	"fmt"
	"image"
	"image/color"
	"math"

	"github.com/hajimehoshi/ebiten/internal/affine"
	emath "github.com/hajimehoshi/ebiten/internal/math"
	"github.com/hajimehoshi/ebiten/internal/opengl"
	"github.com/hajimehoshi/ebiten/internal/sync"
)

// command represents a drawing command.
//
// A command for drawing that is created when Image functions are called like DrawImage,
// or Fill.
// A command is not immediately executed after created. Instaed, it is queued after created,
// and executed only when necessary.
type command interface {
	Exec(indexOffsetInBytes int) error
}

// commandQueue is a command queue for drawing commands.
type commandQueue struct {
	commands    []command
	vertices    []float32
	verticesNum int
	m           sync.Mutex
}

// theCommandQueue is the command queue for the current process.
var theCommandQueue = &commandQueue{}

func (q *commandQueue) appendVertices(vertices []float32) {
	if len(q.vertices) < q.verticesNum+len(vertices) {
		n := q.verticesNum + len(vertices) - len(q.vertices)
		q.vertices = append(q.vertices, make([]float32, n)...)
	}
	// for-loop might be faster than copy:
	// On GopherJS, copy might cause subarray calls.
	for i := 0; i < len(vertices); i++ {
		q.vertices[q.verticesNum+i] = vertices[i]
	}
	q.verticesNum += len(vertices)
}

func (q *commandQueue) EnqueueDrawImageCommand(dst, src *Image, vertices []float32, clr *affine.ColorM, mode opengl.CompositeMode) {
	// Avoid defer for performance
	q.m.Lock()
	q.appendVertices(vertices)
	if 0 < len(q.commands) {
		if c, ok := q.commands[len(q.commands)-1].(*drawImageCommand); ok {
			if c.isMergeable(dst, src, clr, mode) {
				c.verticesNum += len(vertices)
				q.m.Unlock()
				return
			}
		}
	}
	c := &drawImageCommand{
		dst:         dst,
		src:         src,
		verticesNum: len(vertices),
		color:       *clr,
		mode:        mode,
	}
	q.commands = append(q.commands, c)
	q.m.Unlock()
}

func (q *commandQueue) Enqueue(command command) {
	q.m.Lock()
	q.commands = append(q.commands, command)
	q.m.Unlock()
}

// commandGroups separates q.commands into some groups.
// The number of quads of drawImageCommand in one groups must be equal to or less than
// its limit (maxQuads).
func (q *commandQueue) commandGroups() [][]command {
	cs := q.commands
	var gs [][]command
	quads := 0
	for 0 < len(cs) {
		if len(gs) == 0 {
			gs = append(gs, []command{})
		}
		c := cs[0]
		switch c := c.(type) {
		case *drawImageCommand:
			if maxQuads >= quads+c.quadsNum() {
				quads += c.quadsNum()
				break
			}
			cc := c.split(maxQuads - quads)
			gs[len(gs)-1] = append(gs[len(gs)-1], cc[0])
			cs[0] = cc[1]
			quads = 0
			gs = append(gs, []command{})
			continue
		}
		gs[len(gs)-1] = append(gs[len(gs)-1], c)
		cs = cs[1:]
	}
	return gs
}

func (q *commandQueue) Flush() error {
	q.m.Lock()
	defer q.m.Unlock()
	// glViewport must be called at least at every frame on iOS.
	opengl.GetContext().ResetViewportSize()
	n := 0
	lastN := 0
	for _, g := range q.commandGroups() {
		for _, c := range g {
			switch c := c.(type) {
			case *drawImageCommand:
				n += c.verticesNum
			}
		}
		if 0 < n-lastN {
			opengl.GetContext().BufferSubData(opengl.ArrayBuffer, q.vertices[lastN:n])
		}
		// NOTE: WebGL doesn't seem to have Check gl.MAX_ELEMENTS_VERTICES or gl.MAX_ELEMENTS_INDICES so far.
		// Let's use them to compare to len(quads) in the future.
		if maxQuads < (n-lastN)*opengl.Float.SizeInBytes()/QuadVertexSizeInBytes() {
			return fmt.Errorf("len(quads) must be equal to or less than %d", maxQuads)
		}
		numc := len(g)
		indexOffsetInBytes := 0
		for _, c := range g {
			if err := c.Exec(indexOffsetInBytes); err != nil {
				return err
			}
			if c, ok := c.(*drawImageCommand); ok {
				n := c.verticesNum * opengl.Float.SizeInBytes() / QuadVertexSizeInBytes()
				indexOffsetInBytes += 6 * n * 2
			}
		}
		if 0 < numc {
			// Call glFlush to prevent black flicking (especially on Android (#226) and iOS).
			opengl.GetContext().Flush()
		}
		lastN = n
	}
	q.commands = nil
	q.verticesNum = 0
	return nil
}

func FlushCommands() error {
	return theCommandQueue.Flush()
}

type fillCommand struct {
	dst   *Image
	color color.RGBA
}

func (c *fillCommand) Exec(indexOffsetInBytes int) error {
	f, err := c.dst.createFramebufferIfNeeded()
	if err != nil {
		return err
	}
	if err := f.setAsViewport(); err != nil {
		return err
	}
	cr, cg, cb, ca := c.color.R, c.color.G, c.color.B, c.color.A
	const max = math.MaxUint8
	r := float64(cr) / max
	g := float64(cg) / max
	b := float64(cb) / max
	a := float64(ca) / max
	if err := opengl.GetContext().FillFramebuffer(r, g, b, a); err != nil {
		return err
	}

	// Flush is needed after filling (#419)
	opengl.GetContext().Flush()
	return nil
}

type drawImageCommand struct {
	dst         *Image
	src         *Image
	verticesNum int
	color       affine.ColorM
	mode        opengl.CompositeMode
}

func QuadVertexSizeInBytes() int {
	return 4 * theArrayBufferLayout.totalBytes()
}

func (c *drawImageCommand) Exec(indexOffsetInBytes int) error {
	f, err := c.dst.createFramebufferIfNeeded()
	if err != nil {
		return err
	}
	if err := f.setAsViewport(); err != nil {
		return err
	}
	opengl.GetContext().BlendFunc(c.mode)

	n := c.quadsNum()
	if n == 0 {
		return nil
	}
	_, h := c.dst.Size()
	proj := f.projectionMatrix(h)
	p := &programContext{
		state:            &theOpenGLState,
		program:          theOpenGLState.programTexture,
		projectionMatrix: proj,
		texture:          c.src.texture.native,
		colorM:           c.color,
	}
	if err := p.begin(); err != nil {
		return err
	}
	// TODO: We should call glBindBuffer here?
	// The buffer is already bound at begin() but it is counterintuitive.
	opengl.GetContext().DrawElements(opengl.Triangles, 6*n, indexOffsetInBytes)
	return nil
}

func (c *drawImageCommand) split(quadsNum int) [2]*drawImageCommand {
	c1 := *c
	c2 := *c
	s := opengl.Float.SizeInBytes()
	n := quadsNum * QuadVertexSizeInBytes() / s
	c1.verticesNum = n
	c2.verticesNum -= n
	return [2]*drawImageCommand{&c1, &c2}
}

func (c *drawImageCommand) isMergeable(dst, src *Image, clr *affine.ColorM, mode opengl.CompositeMode) bool {
	if c.dst != dst {
		return false
	}
	if c.src != src {
		return false
	}
	if !c.color.Equals(clr) {
		return false
	}
	if c.mode != mode {
		return false
	}
	return true
}

func (c *drawImageCommand) quadsNum() int {
	return c.verticesNum * opengl.Float.SizeInBytes() / QuadVertexSizeInBytes()
}

type replacePixelsCommand struct {
	dst    *Image
	pixels []uint8
}

func (c *replacePixelsCommand) Exec(indexOffsetInBytes int) error {
	f, err := c.dst.createFramebufferIfNeeded()
	if err != nil {
		return err
	}
	if err := f.setAsViewport(); err != nil {
		return err
	}
	// Filling with non black or white color is required here for glTexSubImage2D.
	// Very mysterious but this actually works (Issue #186).
	// This is needed even after fixing a shader bug at f537378f2a6a8ef56e1acf1c03034967b77c7b51.
	if err := opengl.GetContext().FillFramebuffer(0, 0, 0.5, 1); err != nil {
		return err
	}
	// This is necessary on Android. We can't call glClear just before glTexSubImage2D without
	// glFlush. glTexSubImage2D didn't work without this hack at least on Nexus 5x (#211).
	// This also happens when a fillCommand precedes a replacePixelsCommand.
	// TODO: Can we have a better way like optimizing commands?
	opengl.GetContext().Flush()
	if err := opengl.GetContext().BindTexture(c.dst.texture.native); err != nil {
		return err
	}
	opengl.GetContext().TexSubImage2D(c.pixels, emath.NextPowerOf2Int(c.dst.width), emath.NextPowerOf2Int(c.dst.height))
	return nil
}

type disposeCommand struct {
	target *Image
}

func (c *disposeCommand) Exec(indexOffsetInBytes int) error {
	if c.target.framebuffer != nil {
		opengl.GetContext().DeleteFramebuffer(c.target.framebuffer.native)
	}
	if c.target.texture != nil {
		opengl.GetContext().DeleteTexture(c.target.texture.native)
	}
	return nil
}

type newImageFromImageCommand struct {
	result *Image
	img    *image.RGBA
	filter opengl.Filter
}

func (c *newImageFromImageCommand) Exec(indexOffsetInBytes int) error {
	origSize := c.img.Bounds().Size()
	if origSize.X < 1 {
		return errors.New("graphics: width must be equal or more than 1.")
	}
	if origSize.Y < 1 {
		return errors.New("graphics: height must be equal or more than 1.")
	}
	w, h := c.img.Bounds().Size().X, c.img.Bounds().Size().Y
	if c.img.Bounds() != image.Rect(0, 0, emath.NextPowerOf2Int(w), emath.NextPowerOf2Int(h)) {
		panic(fmt.Sprintf("graphics: invalid image bounds: %v", c.img.Bounds()))
	}
	native, err := opengl.GetContext().NewTexture(w, h, c.img.Pix, c.filter)
	if err != nil {
		return err
	}
	c.result.texture = &texture{
		native: native,
	}
	return nil
}

type newImageCommand struct {
	result *Image
	width  int
	height int
	filter opengl.Filter
}

func (c *newImageCommand) Exec(indexOffsetInBytes int) error {
	w := emath.NextPowerOf2Int(c.width)
	h := emath.NextPowerOf2Int(c.height)
	if w < 1 {
		return errors.New("graphics: width must be equal or more than 1.")
	}
	if h < 1 {
		return errors.New("graphics: height must be equal or more than 1.")
	}
	native, err := opengl.GetContext().NewTexture(w, h, nil, c.filter)
	if err != nil {
		return err
	}
	c.result.texture = &texture{
		native: native,
	}
	return nil
}

type newScreenFramebufferImageCommand struct {
	result  *Image
	width   int
	height  int
	offsetX float64
	offsetY float64
}

func (c *newScreenFramebufferImageCommand) Exec(indexOffsetInBytes int) error {
	if c.width < 1 {
		return errors.New("graphics: width must be equal or more than 1.")
	}
	if c.height < 1 {
		return errors.New("graphics: height must be equal or more than 1.")
	}
	f := &framebuffer{
		native:  opengl.GetContext().ScreenFramebuffer(),
		flipY:   true,
		width:   c.width,
		height:  c.height,
		offsetX: c.offsetX,
		offsetY: c.offsetY,
	}
	c.result.framebuffer = f
	return nil
}
<commit_msg>graphics: Add comments<commit_after>// Copyright 2016 Hajime Hoshi
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package graphics

import (
	"errors"
	"fmt"
	"image"
	"image/color"
	"math"

	"github.com/hajimehoshi/ebiten/internal/affine"
	emath "github.com/hajimehoshi/ebiten/internal/math"
	"github.com/hajimehoshi/ebiten/internal/opengl"
	"github.com/hajimehoshi/ebiten/internal/sync"
)

// command represents a drawing command.
//
// A command for drawing that is created when Image functions are called like DrawImage,
// or Fill.
// A command is not immediately executed after created. Instaed, it is queued after created,
// and executed only when necessary.
type command interface {
	Exec(indexOffsetInBytes int) error
}

// commandQueue is a command queue for drawing commands.
type commandQueue struct {
	// commands is a queue of drawing commands.
	commands []command

	// vertices represents a vertices data in OpenGL's array buffer.
	vertices []float32

	// verticesNum represents the current length of vertices.
	// verticesNum must <= len(vertices).
	// vertices is never shrunk since re-extending a vertices buffer is heavy.
	verticesNum int

	m sync.Mutex
}

// theCommandQueue is the command queue for the current process.
var theCommandQueue = &commandQueue{}

// appendVertices appends vertices to the queue.
func (q *commandQueue) appendVertices(vertices []float32) {
	if len(q.vertices) < q.verticesNum+len(vertices) {
		n := q.verticesNum + len(vertices) - len(q.vertices)
		q.vertices = append(q.vertices, make([]float32, n)...)
	}
	// for-loop might be faster than copy:
	// On GopherJS, copy might cause subarray calls.
	for i := 0; i < len(vertices); i++ {
		q.vertices[q.verticesNum+i] = vertices[i]
	}
	q.verticesNum += len(vertices)
}

// EnqueueDrawImageCommand enqueues a drawing-image command.
func (q *commandQueue) EnqueueDrawImageCommand(dst, src *Image, vertices []float32, clr *affine.ColorM, mode opengl.CompositeMode) {
	// Avoid defer for performance
	q.m.Lock()
	q.appendVertices(vertices)
	if 0 < len(q.commands) {
		if c, ok := q.commands[len(q.commands)-1].(*drawImageCommand); ok {
			if c.isMergeable(dst, src, clr, mode) {
				c.verticesNum += len(vertices)
				q.m.Unlock()
				return
			}
		}
	}
	c := &drawImageCommand{
		dst:         dst,
		src:         src,
		verticesNum: len(vertices),
		color:       *clr,
		mode:        mode,
	}
	q.commands = append(q.commands, c)
	q.m.Unlock()
}

func (q *commandQueue) Enqueue(command command) {
	q.m.Lock()
	q.commands = append(q.commands, command)
	q.m.Unlock()
}

// commandGroups separates q.commands into some groups.
// The number of quads of drawImageCommand in one groups must be equal to or less than
// its limit (maxQuads).
func (q *commandQueue) commandGroups() [][]command {
	cs := q.commands
	var gs [][]command
	quads := 0
	for 0 < len(cs) {
		if len(gs) == 0 {
			gs = append(gs, []command{})
		}
		c := cs[0]
		switch c := c.(type) {
		case *drawImageCommand:
			if maxQuads >= quads+c.quadsNum() {
				quads += c.quadsNum()
				break
			}
			cc := c.split(maxQuads - quads)
			gs[len(gs)-1] = append(gs[len(gs)-1], cc[0])
			cs[0] = cc[1]
			quads = 0
			gs = append(gs, []command{})
			continue
		}
		gs[len(gs)-1] = append(gs[len(gs)-1], c)
		cs = cs[1:]
	}
	return gs
}

func (q *commandQueue) Flush() error {
	q.m.Lock()
	defer q.m.Unlock()
	// glViewport must be called at least at every frame on iOS.
	opengl.GetContext().ResetViewportSize()
	n := 0
	lastN := 0
	for _, g := range q.commandGroups() {
		for _, c := range g {
			switch c := c.(type) {
			case *drawImageCommand:
				n += c.verticesNum
			}
		}
		if 0 < n-lastN {
			opengl.GetContext().BufferSubData(opengl.ArrayBuffer, q.vertices[lastN:n])
		}
		// NOTE: WebGL doesn't seem to have Check gl.MAX_ELEMENTS_VERTICES or gl.MAX_ELEMENTS_INDICES so far.
		// Let's use them to compare to len(quads) in the future.
		if maxQuads < (n-lastN)*opengl.Float.SizeInBytes()/QuadVertexSizeInBytes() {
			return fmt.Errorf("len(quads) must be equal to or less than %d", maxQuads)
		}
		numc := len(g)
		indexOffsetInBytes := 0
		for _, c := range g {
			if err := c.Exec(indexOffsetInBytes); err != nil {
				return err
			}
			if c, ok := c.(*drawImageCommand); ok {
				n := c.verticesNum * opengl.Float.SizeInBytes() / QuadVertexSizeInBytes()
				indexOffsetInBytes += 6 * n * 2
			}
		}
		if 0 < numc {
			// Call glFlush to prevent black flicking (especially on Android (#226) and iOS).
			opengl.GetContext().Flush()
		}
		lastN = n
	}
	q.commands = nil
	q.verticesNum = 0
	return nil
}

func FlushCommands() error {
	return theCommandQueue.Flush()
}

type fillCommand struct {
	dst   *Image
	color color.RGBA
}

func (c *fillCommand) Exec(indexOffsetInBytes int) error {
	f, err := c.dst.createFramebufferIfNeeded()
	if err != nil {
		return err
	}
	if err := f.setAsViewport(); err != nil {
		return err
	}
	cr, cg, cb, ca := c.color.R, c.color.G, c.color.B, c.color.A
	const max = math.MaxUint8
	r := float64(cr) / max
	g := float64(cg) / max
	b := float64(cb) / max
	a := float64(ca) / max
	if err := opengl.GetContext().FillFramebuffer(r, g, b, a); err != nil {
		return err
	}

	// Flush is needed after filling (#419)
	opengl.GetContext().Flush()
	return nil
}

type drawImageCommand struct {
	dst         *Image
	src         *Image
	verticesNum int
	color       affine.ColorM
	mode        opengl.CompositeMode
}

func QuadVertexSizeInBytes() int {
	return 4 * theArrayBufferLayout.totalBytes()
}

func (c *drawImageCommand) Exec(indexOffsetInBytes int) error {
	f, err := c.dst.createFramebufferIfNeeded()
	if err != nil {
		return err
	}
	if err := f.setAsViewport(); err != nil {
		return err
	}
	opengl.GetContext().BlendFunc(c.mode)

	n := c.quadsNum()
	if n == 0 {
		return nil
	}
	_, h := c.dst.Size()
	proj := f.projectionMatrix(h)
	p := &programContext{
		state:            &theOpenGLState,
		program:          theOpenGLState.programTexture,
		projectionMatrix: proj,
		texture:          c.src.texture.native,
		colorM:           c.color,
	}
	if err := p.begin(); err != nil {
		return err
	}
	// TODO: We should call glBindBuffer here?
	// The buffer is already bound at begin() but it is counterintuitive.
	opengl.GetContext().DrawElements(opengl.Triangles, 6*n, indexOffsetInBytes)
	return nil
}

func (c *drawImageCommand) split(quadsNum int) [2]*drawImageCommand {
	c1 := *c
	c2 := *c
	s := opengl.Float.SizeInBytes()
	n := quadsNum * QuadVertexSizeInBytes() / s
	c1.verticesNum = n
	c2.verticesNum -= n
	return [2]*drawImageCommand{&c1, &c2}
}

func (c *drawImageCommand) isMergeable(dst, src *Image, clr *affine.ColorM, mode opengl.CompositeMode) bool {
	if c.dst != dst {
		return false
	}
	if c.src != src {
		return false
	}
	if !c.color.Equals(clr) {
		return false
	}
	if c.mode != mode {
		return false
	}
	return true
}

func (c *drawImageCommand) quadsNum() int {
	return c.verticesNum * opengl.Float.SizeInBytes() / QuadVertexSizeInBytes()
}

type replacePixelsCommand struct {
	dst    *Image
	pixels []uint8
}

func (c *replacePixelsCommand) Exec(indexOffsetInBytes int) error {
	f, err := c.dst.createFramebufferIfNeeded()
	if err != nil {
		return err
	}
	if err := f.setAsViewport(); err != nil {
		return err
	}
	// Filling with non black or white color is required here for glTexSubImage2D.
	// Very mysterious but this actually works (Issue #186).
	// This is needed even after fixing a shader bug at f537378f2a6a8ef56e1acf1c03034967b77c7b51.
	if err := opengl.GetContext().FillFramebuffer(0, 0, 0.5, 1); err != nil {
		return err
	}
	// This is necessary on Android. We can't call glClear just before glTexSubImage2D without
	// glFlush. glTexSubImage2D didn't work without this hack at least on Nexus 5x (#211).
	// This also happens when a fillCommand precedes a replacePixelsCommand.
	// TODO: Can we have a better way like optimizing commands?
	opengl.GetContext().Flush()
	if err := opengl.GetContext().BindTexture(c.dst.texture.native); err != nil {
		return err
	}
	opengl.GetContext().TexSubImage2D(c.pixels, emath.NextPowerOf2Int(c.dst.width), emath.NextPowerOf2Int(c.dst.height))
	return nil
}

type disposeCommand struct {
	target *Image
}

func (c *disposeCommand) Exec(indexOffsetInBytes int) error {
	if c.target.framebuffer != nil {
		opengl.GetContext().DeleteFramebuffer(c.target.framebuffer.native)
	}
	if c.target.texture != nil {
		opengl.GetContext().DeleteTexture(c.target.texture.native)
	}
	return nil
}

type newImageFromImageCommand struct {
	result *Image
	img    *image.RGBA
	filter opengl.Filter
}

func (c *newImageFromImageCommand) Exec(indexOffsetInBytes int) error {
	origSize := c.img.Bounds().Size()
	if origSize.X < 1 {
		return errors.New("graphics: width must be equal or more than 1.")
	}
	if origSize.Y < 1 {
		return errors.New("graphics: height must be equal or more than 1.")
	}
	w, h := c.img.Bounds().Size().X, c.img.Bounds().Size().Y
	if c.img.Bounds() != image.Rect(0, 0, emath.NextPowerOf2Int(w), emath.NextPowerOf2Int(h)) {
		panic(fmt.Sprintf("graphics: invalid image bounds: %v", c.img.Bounds()))
	}
	native, err := opengl.GetContext().NewTexture(w, h, c.img.Pix, c.filter)
	if err != nil {
		return err
	}
	c.result.texture = &texture{
		native: native,
	}
	return nil
}

type newImageCommand struct {
	result *Image
	width  int
	height int
	filter opengl.Filter
}

func (c *newImageCommand) Exec(indexOffsetInBytes int) error {
	w := emath.NextPowerOf2Int(c.width)
	h := emath.NextPowerOf2Int(c.height)
	if w < 1 {
		return errors.New("graphics: width must be equal or more than 1.")
	}
	if h < 1 {
		return errors.New("graphics: height must be equal or more than 1.")
	}
	native, err := opengl.GetContext().NewTexture(w, h, nil, c.filter)
	if err != nil {
		return err
	}
	c.result.texture = &texture{
		native: native,
	}
	return nil
}

type newScreenFramebufferImageCommand struct {
	result  *Image
	width   int
	height  int
	offsetX float64
	offsetY float64
}

func (c *newScreenFramebufferImageCommand) Exec(indexOffsetInBytes int) error {
	if c.width < 1 {
		return errors.New("graphics: width must be equal or more than 1.")
	}
	if c.height < 1 {
		return errors.New("graphics: height must be equal or more than 1.")
	}
	f := &framebuffer{
		native:  opengl.GetContext().ScreenFramebuffer(),
		flipY:   true,
		width:   c.width,
		height:  c.height,
		offsetX: c.offsetX,
		offsetY: c.offsetY,
	}
	c.result.framebuffer = f
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2013 Péter Surányi. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package internal

import (
	"strings"
)

func NewElem(filepath string) (string, error) {
	if strings.ContainsRune(filepath, '"') {
		return "", Error{Cause_: ErrQuote, Filepath: filepath}
	}
	if strings.ContainsRune(filepath, ListSeparator) {
		return `"` + filepath + `"`, nil
	}
	return filepath, nil
}
<commit_msg>fix typo<commit_after>// Copyright 2013 Péter Surányi. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package internal

import (
	"strings"
)

func NewElem(filepath string) (string, error) {
	if strings.ContainsRune(filepath, '"') {
		return "", Error{Cause_: ErrQuote, Filepath_: filepath}
	}
	if strings.ContainsRune(filepath, ListSeparator) {
		return `"` + filepath + `"`, nil
	}
	return filepath, nil
}
<|endoftext|>


<commit_before>package queue

import (
	"bytes"
	"context"
	"encoding/gob"
	"log"
	"sync"
	"time"

	"github.com/google/go-github/github"
	"github.com/pkg/errors"

	"google.golang.org/api/iterator"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"

	"cloud.google.com/go/pubsub"
)

func init() {
	// List of all types that could be added to the queue
	gob.Register(&github.PullRequestEvent{})
}

const (
	// version should be changed each time the message format changes in an
	// incompatible way. This will then cause new subscribers to listen on the
	// new topic.
	version          = "1"
	defaultSubName   = "worker"
	defaultTopicName = "gopherci-ci"
)

// GCPPubSubQueue is a queue using Google Compute Platform's PubSub product.
type GCPPubSubQueue struct {
	ctx   context.Context // stop listening when this context is cancelled
	c     chan<- interface{}
	topic *pubsub.Topic
}

var _ Queuer = &GCPPubSubQueue{}
var cxnTimeout = 10 * time.Second

// NewGCPPubSubQueue creates a new Queuer and listens on the queue, sending
// new jobs to the channel c, projectID is required but topicName is optional.
// Calls wg.Done() when finished after context has ben cancelled and current
// job has finished.
func NewGCPPubSubQueue(ctx context.Context, wg *sync.WaitGroup, c chan<- interface{}, projectID, topicName string) (*GCPPubSubQueue, error) {
	q := &GCPPubSubQueue{ctx: ctx, c: c}

	if projectID == "" {
		return nil, errors.New("projectID must not be empty")
	}

	// create a context with a timeout for exclusive use of connection setup to
	// ensure connnection setup doesn't block and can fail early.
	cxnCtx, cancel := context.WithTimeout(ctx, cxnTimeout)
	defer cancel()

	client, err := pubsub.NewClient(cxnCtx, projectID)
	if err != nil {
		return nil, errors.Wrap(err, "NewGCPPubSubQueue: could not create client")
	}

	if topicName == "" {
		topicName = defaultTopicName
	}
	topicName += "-v" + version

	log.Printf("NewGCPPubSubQueue: creating topic %q", topicName)
	q.topic, err = client.CreateTopic(cxnCtx, topicName)
	if code := grpc.Code(err); code != codes.OK && code != codes.AlreadyExists {
		return nil, errors.Wrap(err, "NewGCPPubSubQueue: could not create topic")
	}

	subName := topicName + "-" + defaultSubName

	log.Printf("NewGCPPubSubQueue: creating subscription %q", subName)
	subscription, err := client.CreateSubscription(cxnCtx, subName, q.topic, 0, nil)
	if code := grpc.Code(err); code != codes.OK && code != codes.AlreadyExists {
		return nil, errors.Wrap(err, "NewGCPPubSubQueue: could not create subscription")
	}

	itr, err := subscription.Pull(q.ctx)
	if err != nil {
		return nil, errors.Wrap(err, "GCPPubSubQueue: could not pull subscription")
	}

	// Close iterator when context closes
	go func() {
		<-q.ctx.Done()
		log.Println("GCPPubSubQueue: closing")
		itr.Stop()
		client.Close()
	}()

	wg.Add(1)
	go q.listen(wg, itr)
	return q, nil
}

// Queue implements the Queue interface.
func (q *GCPPubSubQueue) Queue(job interface{}) error {
	var buf bytes.Buffer
	enc := gob.NewEncoder(&buf)
	if err := enc.Encode(container{job}); err != nil {
		return errors.Wrap(err, "GCPPubSubQueue: could not gob encode job")
	}

	var (
		msg         = &pubsub.Message{Data: buf.Bytes()}
		maxAttempts = 3
		msgIDs      []string
		err         error
	)
	for i := 1; i <= maxAttempts; i++ {
		msgIDs, err = q.topic.Publish(q.ctx, msg)
		if err == nil && 1 == 2 {
			break
		}
		log.Printf("GCPPubSubQueue: failed publishing message attempt %v of %v, error: %v", i, maxAttempts, err)
		time.Sleep(time.Duration(i) * time.Second)
	}
	if err != nil {
		return errors.Wrap(err, "GCPPubSubQueue: could not publish job")
	}
	log.Println("GCPPubSubQueue: published a message with a message ID:", msgIDs[0])

	return nil
}

type container struct {
	Job interface{}
}

// listen listens for messages from queue and runs the jobs, returns when
// iterator is stopped, calls wg.Done when returning.
func (q *GCPPubSubQueue) listen(wg *sync.WaitGroup, itr *pubsub.MessageIterator) {
	defer wg.Done()
	for {
		msg, err := itr.Next()
		switch {
		case err == iterator.Done:
			log.Println("GCPPubSubQueue: stopping listening")
			return
		case err != nil:
			log.Println("GCPPubSubQueue: could not read next message:", err)
			time.Sleep(3 * time.Second) // back-off
			continue
		}
		log.Printf("GCPPubSubQueue: processing ID %v, published at %v", msg.ID, msg.PublishTime)

		// Acknowledge the job now, anything else that could fail by this instance
		// will fail in others.
		msg.Done(true)

		log.Printf("GCPPubSubQueue: ack'd ID %v", msg.ID)

		reader := bytes.NewReader(msg.Data)
		dec := gob.NewDecoder(reader)

		var job container
		if err := dec.Decode(&job); err != nil {
			log.Println("GCPPubSubQueue: could not decode job:", err)
			continue
		}
		log.Printf("GCPPubSubQueue: adding ID %v to job channel", msg.ID)
		q.c <- job.Job
		log.Printf("GCPPubSubQueue: successfully added ID %v to job queue", msg.ID)
	}
}

// delete deletes the topic and subcriptions, used to cleanup unit tests
func (q *GCPPubSubQueue) delete() {
	itr := q.topic.Subscriptions(q.ctx)
	for {
		sub, err := itr.Next()
		if err != nil {
			break
		}
		err = sub.Delete(q.ctx)
		if err != nil {
			log.Println("GCPPubSubQueue: delete subscription error:", err)
		}
	}
	err := q.topic.Delete(q.ctx)
	if err != nil {
		log.Println("GCPPubSubQueue: delete topic error:", err)
	}
}
<commit_msg>Remove debug statement from gcp-pubsub causing messages to be redelivered<commit_after>package queue

import (
	"bytes"
	"context"
	"encoding/gob"
	"log"
	"sync"
	"time"

	"github.com/google/go-github/github"
	"github.com/pkg/errors"

	"google.golang.org/api/iterator"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"

	"cloud.google.com/go/pubsub"
)

func init() {
	// List of all types that could be added to the queue
	gob.Register(&github.PullRequestEvent{})
}

const (
	// version should be changed each time the message format changes in an
	// incompatible way. This will then cause new subscribers to listen on the
	// new topic.
	version          = "1"
	defaultSubName   = "worker"
	defaultTopicName = "gopherci-ci"
)

// GCPPubSubQueue is a queue using Google Compute Platform's PubSub product.
type GCPPubSubQueue struct {
	ctx   context.Context // stop listening when this context is cancelled
	c     chan<- interface{}
	topic *pubsub.Topic
}

var _ Queuer = &GCPPubSubQueue{}
var cxnTimeout = 10 * time.Second

// NewGCPPubSubQueue creates a new Queuer and listens on the queue, sending
// new jobs to the channel c, projectID is required but topicName is optional.
// Calls wg.Done() when finished after context has ben cancelled and current
// job has finished.
func NewGCPPubSubQueue(ctx context.Context, wg *sync.WaitGroup, c chan<- interface{}, projectID, topicName string) (*GCPPubSubQueue, error) {
	q := &GCPPubSubQueue{ctx: ctx, c: c}

	if projectID == "" {
		return nil, errors.New("projectID must not be empty")
	}

	// create a context with a timeout for exclusive use of connection setup to
	// ensure connnection setup doesn't block and can fail early.
	cxnCtx, cancel := context.WithTimeout(ctx, cxnTimeout)
	defer cancel()

	client, err := pubsub.NewClient(cxnCtx, projectID)
	if err != nil {
		return nil, errors.Wrap(err, "NewGCPPubSubQueue: could not create client")
	}

	if topicName == "" {
		topicName = defaultTopicName
	}
	topicName += "-v" + version

	log.Printf("NewGCPPubSubQueue: creating topic %q", topicName)
	q.topic, err = client.CreateTopic(cxnCtx, topicName)
	if code := grpc.Code(err); code != codes.OK && code != codes.AlreadyExists {
		return nil, errors.Wrap(err, "NewGCPPubSubQueue: could not create topic")
	}

	subName := topicName + "-" + defaultSubName

	log.Printf("NewGCPPubSubQueue: creating subscription %q", subName)
	subscription, err := client.CreateSubscription(cxnCtx, subName, q.topic, 0, nil)
	if code := grpc.Code(err); code != codes.OK && code != codes.AlreadyExists {
		return nil, errors.Wrap(err, "NewGCPPubSubQueue: could not create subscription")
	}

	itr, err := subscription.Pull(q.ctx)
	if err != nil {
		return nil, errors.Wrap(err, "GCPPubSubQueue: could not pull subscription")
	}

	// Close iterator when context closes
	go func() {
		<-q.ctx.Done()
		log.Println("GCPPubSubQueue: closing")
		itr.Stop()
		client.Close()
	}()

	wg.Add(1)
	go q.listen(wg, itr)
	return q, nil
}

// Queue implements the Queue interface.
func (q *GCPPubSubQueue) Queue(job interface{}) error {
	var buf bytes.Buffer
	enc := gob.NewEncoder(&buf)
	if err := enc.Encode(container{job}); err != nil {
		return errors.Wrap(err, "GCPPubSubQueue: could not gob encode job")
	}

	var (
		msg         = &pubsub.Message{Data: buf.Bytes()}
		maxAttempts = 3
		msgIDs      []string
		err         error
	)
	for i := 1; i <= maxAttempts; i++ {
		msgIDs, err = q.topic.Publish(q.ctx, msg)
		if err == nil {
			break
		}
		log.Printf("GCPPubSubQueue: failed publishing message attempt %v of %v, error: %v", i, maxAttempts, err)
		time.Sleep(time.Duration(i) * time.Second)
	}
	if err != nil {
		return errors.Wrap(err, "GCPPubSubQueue: could not publish job")
	}
	log.Println("GCPPubSubQueue: published a message with a message ID:", msgIDs[0])

	return nil
}

type container struct {
	Job interface{}
}

// listen listens for messages from queue and runs the jobs, returns when
// iterator is stopped, calls wg.Done when returning.
func (q *GCPPubSubQueue) listen(wg *sync.WaitGroup, itr *pubsub.MessageIterator) {
	defer wg.Done()
	for {
		msg, err := itr.Next()
		switch {
		case err == iterator.Done:
			log.Println("GCPPubSubQueue: stopping listening")
			return
		case err != nil:
			log.Println("GCPPubSubQueue: could not read next message:", err)
			time.Sleep(3 * time.Second) // back-off
			continue
		}
		log.Printf("GCPPubSubQueue: processing ID %v, published at %v", msg.ID, msg.PublishTime)

		// Acknowledge the job now, anything else that could fail by this instance
		// will fail in others.
		msg.Done(true)

		log.Printf("GCPPubSubQueue: ack'd ID %v", msg.ID)

		reader := bytes.NewReader(msg.Data)
		dec := gob.NewDecoder(reader)

		var job container
		if err := dec.Decode(&job); err != nil {
			log.Println("GCPPubSubQueue: could not decode job:", err)
			continue
		}
		log.Printf("GCPPubSubQueue: adding ID %v to job channel", msg.ID)
		q.c <- job.Job
		log.Printf("GCPPubSubQueue: successfully added ID %v to job queue", msg.ID)
	}
}

// delete deletes the topic and subcriptions, used to cleanup unit tests
func (q *GCPPubSubQueue) delete() {
	itr := q.topic.Subscriptions(q.ctx)
	for {
		sub, err := itr.Next()
		if err != nil {
			break
		}
		err = sub.Delete(q.ctx)
		if err != nil {
			log.Println("GCPPubSubQueue: delete subscription error:", err)
		}
	}
	err := q.topic.Delete(q.ctx)
	if err != nil {
		log.Println("GCPPubSubQueue: delete topic error:", err)
	}
}
<|endoftext|>


<commit_before>package brewerydb

import (
	"strconv"
	"strings"
)

// ConvertIDService provides access to the BreweryDB ID Conversion API.
// Use Client.ConvertID.
type ConvertIDService struct {
	c *Client
}

// ConvertType is the type of ID to convert.
type ConvertType string

// Types of things that have IDs that can be converted.
const (
	ConvertBrewery ConvertType = "brewery" // ConvertBrewery converts Brewery IDs.
	ConvertBeer                = "beer"    // ConvertBeer converts Beer IDs.
)

// ConvertIDs converts a series of "old" Beer or Brewery IDs to the "new" format
// (BreweryDB v1 to v2)
func (cs *ConvertIDService) ConvertIDs(t ConvertType, oldIDs ...int) (map[int]string, error) {
	// POST: /convertid

	var ids []string
	for _, id := range oldIDs {
		ids = append(ids, strconv.Itoa(id))
	}

	q := struct {
		Type string `url:"type"`
		IDs  string `url:"ids"`
	}{string(t), strings.Join(ids, ",")}

	req, err := cs.c.NewRequest("POST", "/convertid", &q)
	if err != nil {
		return nil, err
	}

	resp := struct {
		Status string
		Data   []struct {
			OldID int
			NewID string
		}
		Message string
	}{}
	if err := cs.c.Do(req, &resp); err != nil {
		return nil, err
	}

	m := make(map[int]string)
	for _, d := range resp.Data {
		m[d.OldID] = d.NewID
	}

	return m, nil
}
<commit_msg>api links for convert_id.go<commit_after>package brewerydb

import (
	"strconv"
	"strings"
)

// ConvertIDService provides access to the BreweryDB ID Conversion API.
// Use Client.ConvertID.
//
// See: http://www.brewerydb.com/developers/docs-endpoint/convertid_index
type ConvertIDService struct {
	c *Client
}

// ConvertType is the type of ID to convert.
type ConvertType string

// Types of things that have IDs that can be converted.
const (
	ConvertBrewery ConvertType = "brewery" // ConvertBrewery converts Brewery IDs.
	ConvertBeer                = "beer"    // ConvertBeer converts Beer IDs.
)

// ConvertIDs converts a series of "old" Beer or Brewery IDs to the "new" format
// (BreweryDB v1 to v2)
//
// See: http://www.brewerydb.com/developers/docs-endpoint/convertid_index#1
func (cs *ConvertIDService) ConvertIDs(t ConvertType, oldIDs ...int) (map[int]string, error) {
	// POST: /convertid

	var ids []string
	for _, id := range oldIDs {
		ids = append(ids, strconv.Itoa(id))
	}

	q := struct {
		Type string `url:"type"`
		IDs  string `url:"ids"`
	}{string(t), strings.Join(ids, ",")}

	req, err := cs.c.NewRequest("POST", "/convertid", &q)
	if err != nil {
		return nil, err
	}

	resp := struct {
		Status string
		Data   []struct {
			OldID int
			NewID string
		}
		Message string
	}{}
	if err := cs.c.Do(req, &resp); err != nil {
		return nil, err
	}

	m := make(map[int]string)
	for _, d := range resp.Data {
		m[d.OldID] = d.NewID
	}

	return m, nil
}
<|endoftext|>


<commit_before>package vm_test

import (
	"os"
	"path/filepath"

	. "github.com/grubby/grubby/interpreter/vm"
	. "github.com/grubby/grubby/interpreter/vm/builtins"
	. "github.com/grubby/grubby/testhelpers"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("classes", func() {
	var vm VM

	BeforeEach(func() {
		pathToExecutable, err := filepath.Abs(filepath.Dir(filepath.Dir(filepath.Dir(os.Args[0]))))
		if err != nil {
			panic(err)
		}

		vm = NewVM(pathToExecutable, "fake-irb-under-test")
	})

	It("can be used as a value", func() {
		value, err := vm.Run(`
class Foo::Bar
end

foo = Foo::Bar
`)

		Expect(err).ToNot(HaveOccurred())
		Expect(value).ToNot(BeNil())
		Expect(value).To(Equal(vm.MustGetClass("Foo::Bar")))
	})

	Describe(".new", func() {
		It("returns an error when initializing the object would fail", func() {
			_, err := vm.Run(`
class Microclimatology
  def initialize
    overchildish
  end
end

Microclimatology.new
`)

			Expect(err).To(HaveOccurred())
			Expect(err.Error()).To(ContainSubstring("NameError"))
			Expect(err.Error()).To(ContainSubstring("undefined local variable or method 'overchildish'"))
		})
	})

	Describe("superclasses", func() {
		It("can be provided when declaring a class", func() {
			value, err := vm.Run(`
class Foo
end

class Bar < Foo
end
`)
			Expect(err).ToNot(HaveOccurred())
			Expect(value.(Class).SuperClass().String()).To(Equal(vm.ClassWithName("Foo").String()))
		})
	})

	Describe("class attribute methods", func() {
		Describe(".attr_reader :symbol", func() {
			It("creates a getter and setter on instances of the class", func() {
				_, err := vm.Run(`
class Foo
  attr_reader :quaternion_vinic
end
`)

				Expect(err).ToNot(HaveOccurred())

				foo, err := vm.MustGetClass("Foo").New(vm)
				Expect(err).ToNot(HaveOccurred())

				reader := foo.Method("quaternion_vinic")
				Expect(reader).ToNot(BeNil())

				val, err := reader.Execute(foo, nil)
				Expect(err).ToNot(HaveOccurred())

				nilInstance := vm.SingletonWithName("nil")

				Expect(err).ToNot(HaveOccurred())
				Expect(val).To(Equal(nilInstance))
			})
		})

		Describe(".attr_writer :some_symbol", func() {
			It("creates a setter on instances of the class", func() {
				_, err := vm.Run(`
class Foo
  attr_writer :chrysobull_nonmonarchist
end
`)

				Expect(err).ToNot(HaveOccurred())

				fooClass := vm.MustGetClass("Foo")
				foo, err := fooClass.New(vm)
				Expect(err).ToNot(HaveOccurred())

				reader := foo.Method("chrysobull_nonmonarchist=")
				Expect(reader).ToNot(BeNil())

				_, err = reader.Execute(foo, nil, NewString("lyncher-mudslinger", vm))
				Expect(err).ToNot(HaveOccurred())

				// TODO: assert on the instance variable via instance_variable_get
			})
		})

		Describe(".attr_accessor :some_symbol", func() {
			It("creates a getter and setter on instances of the class", func() {
				_, err := vm.Run(`
class Foo
  attr_accessor :pieless_bothlike
end
`)

				Expect(err).ToNot(HaveOccurred())

				fooClass := vm.MustGetClass("Foo")
				foo, err := fooClass.New(vm)
				Expect(err).ToNot(HaveOccurred())

				reader := foo.Method("pieless_bothlike")
				Expect(reader).ToNot(BeNil())

				val, err := reader.Execute(foo, nil)
				Expect(err).ToNot(HaveOccurred())

				nilInstance := vm.SingletonWithName("nil")
				Expect(err).ToNot(HaveOccurred())
				Expect(val).To(Equal(nilInstance))

				writer := foo.Method("pieless_bothlike=")
				Expect(writer).ToNot(BeNil())

				_, err = writer.Execute(foo, nil, NewString("unordainable-luthier", vm))
				Expect(err).ToNot(HaveOccurred())

				val, err = reader.Execute(foo, nil)
				Expect(err).ToNot(HaveOccurred())
				Expect(val).To(EqualRubyString("unordainable-luthier"))
			})
		})

		Describe("attr_accessor on a class", func() {
			FIt("exposes attributes on the class itself", func() {
				result, err := vm.Run(`
class Base
  class << self
    # this needs to call into interpretMethodDeclarationInContext
    attr_accessor :foo
  end
end

class Foo < Base
  self.foo = 'foobar'
end

Foo.foo
`)
				Expect(err).ToNot(HaveOccurred())

				str, ok := result.(*StringValue)
				Expect(ok).To(BeTrue())
				Expect(str.RawString()).To(Equal("foobar"))
			})
		})
	})

	Describe("private methods", func() {
		It("can be created from a public method using .private_class_method()", func() {
			class, err := vm.Run(`
class Foo
  def self.bar
  end

  private_class_method :bar
end
`)
			Expect(err).ToNot(HaveOccurred())
			Expect(class).To(HavePrivateMethod("bar"))
		})
	})

	Describe("superclasses", func() {
		It("defaults to Object", func() {
			class, err := vm.Run(`
class Foo
end
`)
			Expect(err).ToNot(HaveOccurred())
			Expect(class.(Class).SuperClass().String()).To(Equal("Object"))
		})
	})

	It("is a kind of module", func() {
		classClass := vm.MustGetClass("Class")
		Expect(classClass.(Class).SuperClass().String()).To(Equal("Module"))

		_, ok := classClass.(Module)
		Expect(ok).To(BeTrue())
	})

	It("allows you to mark methods as being private", func() {
		class, err := vm.Run(`
class Foo
  def from
  end

  private :from

  private
  def cant_touch_this
  end
end
`)

		Expect(err).ToNot(HaveOccurred())
		Expect(class).To(HavePrivateInstanceMethod("from"))
		Expect(class).To(HavePrivateInstanceMethod("cant_touch_this"))
	})

	It("should not pollute the VM after declaring private methods", func() {
		_, err := vm.Run(`
class Foo
  private
  def cant_see_me; end
end

class Bar
  def this_is_visible; end
end
`)

		Expect(err).ToNot(HaveOccurred())

		fooClass := vm.MustGetClass("Foo")
		Expect(fooClass).To(HavePrivateInstanceMethod("cant_see_me"))

		barClass := vm.MustGetClass("Bar")
		Expect(barClass).To(HaveInstanceMethod("this_is_visible"))
	})

	Describe("defining a class", func() {
		It("adds it to the global class cache", func() {
			_, err := vm.Run(`
class Foo
end
`)

			Expect(err).ToNot(HaveOccurred())
			_, err = vm.GetClass("Foo")
			Expect(err).ToNot(HaveOccurred())
		})

		It("allows a user to construct an instance of the class", func() {
			_, err := vm.Run(`
class Foo
end
`)

			fooClass := vm.MustGetClass("Foo")
			method := fooClass.Method("new")
			Expect(method).ToNot(BeNil())

			instance, err := method.Execute(fooClass, nil)
			Expect(err).ToNot(HaveOccurred())
			Expect(instance.Class()).To(Equal(fooClass))
		})

		Context("when there are instance methods defined", func() {
			It("makes instance methods available on new instances of the class", func() {
				_, err := vm.Run(`
class Foo
  def hello
    "world"
  end
end
`)

				Expect(err).ToNot(HaveOccurred())

				fooClass, err := vm.GetClass("Foo")
				Expect(fooClass).ToNot(BeNil())

				fooInstance, err := fooClass.New(vm)
				Expect(err).ToNot(HaveOccurred())
				Expect(fooInstance).ToNot(BeNil())

				method := fooInstance.Method("hello")
				Expect(method).ToNot(BeNil())

				val, err := method.Execute(fooInstance, nil)
				Expect(err).ToNot(HaveOccurred())
				Expect(val).To(BeAssignableToTypeOf(NewString("", vm)))
				Expect(val.String()).To(Equal("world"))
			})
		})

		Context("when it extends a module", func() {
			It("makes the modules methods available on itself", func() {
				_, err := vm.Run(`
module Foo
  def publication
    'Chichimec-lipochrome'
  end
end

class Bar
  extend Foo
end
`)

				Expect(err).ToNot(HaveOccurred())

				barClass := vm.MustGetClass("Bar")
				Expect(barClass).To(HaveMethod("publication"))
			})
		})

		Context("when it includes a module", func() {
			It("makes the modules methods available on its instances", func() {
				_, err := vm.Run(`
module Foo
  def superinquisitive
    "tumescent-wasty"
  end
end

class Bar
  include Foo
end
`)
				Expect(err).ToNot(HaveOccurred())

				barClass := vm.MustGetClass("Bar")
				bar, err := barClass.New(vm)
				Expect(err).ToNot(HaveOccurred())

				method := bar.Method("superinquisitive")
				Expect(method).ToNot(BeNil())

				val, err := method.Execute(bar, nil)
				Expect(err).ToNot(HaveOccurred())
				Expect(val.String()).To(Equal("tumescent-wasty"))
			})
		})
	})
})
<commit_msg>Remove focused spec<commit_after>package vm_test

import (
	"os"
	"path/filepath"

	. "github.com/grubby/grubby/interpreter/vm"
	. "github.com/grubby/grubby/interpreter/vm/builtins"
	. "github.com/grubby/grubby/testhelpers"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("classes", func() {
	var vm VM

	BeforeEach(func() {
		pathToExecutable, err := filepath.Abs(filepath.Dir(filepath.Dir(filepath.Dir(os.Args[0]))))
		if err != nil {
			panic(err)
		}

		vm = NewVM(pathToExecutable, "fake-irb-under-test")
	})

	It("can be used as a value", func() {
		value, err := vm.Run(`
class Foo::Bar
end

foo = Foo::Bar
`)

		Expect(err).ToNot(HaveOccurred())
		Expect(value).ToNot(BeNil())
		Expect(value).To(Equal(vm.MustGetClass("Foo::Bar")))
	})

	Describe(".new", func() {
		It("returns an error when initializing the object would fail", func() {
			_, err := vm.Run(`
class Microclimatology
  def initialize
    overchildish
  end
end

Microclimatology.new
`)

			Expect(err).To(HaveOccurred())
			Expect(err.Error()).To(ContainSubstring("NameError"))
			Expect(err.Error()).To(ContainSubstring("undefined local variable or method 'overchildish'"))
		})
	})

	Describe("superclasses", func() {
		It("can be provided when declaring a class", func() {
			value, err := vm.Run(`
class Foo
end

class Bar < Foo
end
`)
			Expect(err).ToNot(HaveOccurred())
			Expect(value.(Class).SuperClass().String()).To(Equal(vm.ClassWithName("Foo").String()))
		})
	})

	Describe("class attribute methods", func() {
		Describe(".attr_reader :symbol", func() {
			It("creates a getter and setter on instances of the class", func() {
				_, err := vm.Run(`
class Foo
  attr_reader :quaternion_vinic
end
`)

				Expect(err).ToNot(HaveOccurred())

				foo, err := vm.MustGetClass("Foo").New(vm)
				Expect(err).ToNot(HaveOccurred())

				reader := foo.Method("quaternion_vinic")
				Expect(reader).ToNot(BeNil())

				val, err := reader.Execute(foo, nil)
				Expect(err).ToNot(HaveOccurred())

				nilInstance := vm.SingletonWithName("nil")

				Expect(err).ToNot(HaveOccurred())
				Expect(val).To(Equal(nilInstance))
			})
		})

		Describe(".attr_writer :some_symbol", func() {
			It("creates a setter on instances of the class", func() {
				_, err := vm.Run(`
class Foo
  attr_writer :chrysobull_nonmonarchist
end
`)

				Expect(err).ToNot(HaveOccurred())

				fooClass := vm.MustGetClass("Foo")
				foo, err := fooClass.New(vm)
				Expect(err).ToNot(HaveOccurred())

				reader := foo.Method("chrysobull_nonmonarchist=")
				Expect(reader).ToNot(BeNil())

				_, err = reader.Execute(foo, nil, NewString("lyncher-mudslinger", vm))
				Expect(err).ToNot(HaveOccurred())

				// TODO: assert on the instance variable via instance_variable_get
			})
		})

		Describe(".attr_accessor :some_symbol", func() {
			It("creates a getter and setter on instances of the class", func() {
				_, err := vm.Run(`
class Foo
  attr_accessor :pieless_bothlike
end
`)

				Expect(err).ToNot(HaveOccurred())

				fooClass := vm.MustGetClass("Foo")
				foo, err := fooClass.New(vm)
				Expect(err).ToNot(HaveOccurred())

				reader := foo.Method("pieless_bothlike")
				Expect(reader).ToNot(BeNil())

				val, err := reader.Execute(foo, nil)
				Expect(err).ToNot(HaveOccurred())

				nilInstance := vm.SingletonWithName("nil")
				Expect(err).ToNot(HaveOccurred())
				Expect(val).To(Equal(nilInstance))

				writer := foo.Method("pieless_bothlike=")
				Expect(writer).ToNot(BeNil())

				_, err = writer.Execute(foo, nil, NewString("unordainable-luthier", vm))
				Expect(err).ToNot(HaveOccurred())

				val, err = reader.Execute(foo, nil)
				Expect(err).ToNot(HaveOccurred())
				Expect(val).To(EqualRubyString("unordainable-luthier"))
			})
		})

		Describe("attr_accessor on a class", func() {
			It("exposes attributes on the class itself", func() {
				result, err := vm.Run(`
class Base
  class << self
    # this needs to call into interpretMethodDeclarationInContext
    attr_accessor :foo
  end
end

class Foo < Base
  self.foo = 'foobar'
end

Foo.foo
`)
				Expect(err).ToNot(HaveOccurred())

				str, ok := result.(*StringValue)
				Expect(ok).To(BeTrue())
				Expect(str.RawString()).To(Equal("foobar"))
			})
		})
	})

	Describe("private methods", func() {
		It("can be created from a public method using .private_class_method()", func() {
			class, err := vm.Run(`
class Foo
  def self.bar
  end

  private_class_method :bar
end
`)
			Expect(err).ToNot(HaveOccurred())
			Expect(class).To(HavePrivateMethod("bar"))
		})
	})

	Describe("superclasses", func() {
		It("defaults to Object", func() {
			class, err := vm.Run(`
class Foo
end
`)
			Expect(err).ToNot(HaveOccurred())
			Expect(class.(Class).SuperClass().String()).To(Equal("Object"))
		})
	})

	It("is a kind of module", func() {
		classClass := vm.MustGetClass("Class")
		Expect(classClass.(Class).SuperClass().String()).To(Equal("Module"))

		_, ok := classClass.(Module)
		Expect(ok).To(BeTrue())
	})

	It("allows you to mark methods as being private", func() {
		class, err := vm.Run(`
class Foo
  def from
  end

  private :from

  private
  def cant_touch_this
  end
end
`)

		Expect(err).ToNot(HaveOccurred())
		Expect(class).To(HavePrivateInstanceMethod("from"))
		Expect(class).To(HavePrivateInstanceMethod("cant_touch_this"))
	})

	It("should not pollute the VM after declaring private methods", func() {
		_, err := vm.Run(`
class Foo
  private
  def cant_see_me; end
end

class Bar
  def this_is_visible; end
end
`)

		Expect(err).ToNot(HaveOccurred())

		fooClass := vm.MustGetClass("Foo")
		Expect(fooClass).To(HavePrivateInstanceMethod("cant_see_me"))

		barClass := vm.MustGetClass("Bar")
		Expect(barClass).To(HaveInstanceMethod("this_is_visible"))
	})

	Describe("defining a class", func() {
		It("adds it to the global class cache", func() {
			_, err := vm.Run(`
class Foo
end
`)

			Expect(err).ToNot(HaveOccurred())
			_, err = vm.GetClass("Foo")
			Expect(err).ToNot(HaveOccurred())
		})

		It("allows a user to construct an instance of the class", func() {
			_, err := vm.Run(`
class Foo
end
`)

			fooClass := vm.MustGetClass("Foo")
			method := fooClass.Method("new")
			Expect(method).ToNot(BeNil())

			instance, err := method.Execute(fooClass, nil)
			Expect(err).ToNot(HaveOccurred())
			Expect(instance.Class()).To(Equal(fooClass))
		})

		Context("when there are instance methods defined", func() {
			It("makes instance methods available on new instances of the class", func() {
				_, err := vm.Run(`
class Foo
  def hello
    "world"
  end
end
`)

				Expect(err).ToNot(HaveOccurred())

				fooClass, err := vm.GetClass("Foo")
				Expect(fooClass).ToNot(BeNil())

				fooInstance, err := fooClass.New(vm)
				Expect(err).ToNot(HaveOccurred())
				Expect(fooInstance).ToNot(BeNil())

				method := fooInstance.Method("hello")
				Expect(method).ToNot(BeNil())

				val, err := method.Execute(fooInstance, nil)
				Expect(err).ToNot(HaveOccurred())
				Expect(val).To(BeAssignableToTypeOf(NewString("", vm)))
				Expect(val.String()).To(Equal("world"))
			})
		})

		Context("when it extends a module", func() {
			It("makes the modules methods available on itself", func() {
				_, err := vm.Run(`
module Foo
  def publication
    'Chichimec-lipochrome'
  end
end

class Bar
  extend Foo
end
`)

				Expect(err).ToNot(HaveOccurred())

				barClass := vm.MustGetClass("Bar")
				Expect(barClass).To(HaveMethod("publication"))
			})
		})

		Context("when it includes a module", func() {
			It("makes the modules methods available on its instances", func() {
				_, err := vm.Run(`
module Foo
  def superinquisitive
    "tumescent-wasty"
  end
end

class Bar
  include Foo
end
`)
				Expect(err).ToNot(HaveOccurred())

				barClass := vm.MustGetClass("Bar")
				bar, err := barClass.New(vm)
				Expect(err).ToNot(HaveOccurred())

				method := bar.Method("superinquisitive")
				Expect(method).ToNot(BeNil())

				val, err := method.Execute(bar, nil)
				Expect(err).ToNot(HaveOccurred())
				Expect(val.String()).To(Equal("tumescent-wasty"))
			})
		})
	})
})
<|endoftext|>


<commit_before>/*
Copyright 2018 The Jetstack cert-manager contributors.

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

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

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

package client

import (
	"crypto"
	"crypto/rand"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"errors"
	"testing"
	"time"

	"github.com/jetstack/cert-manager/pkg/issuer/venafi/client/api"

	"github.com/Venafi/vcert/pkg/certificate"
	"github.com/Venafi/vcert/pkg/endpoint"
	"github.com/Venafi/vcert/pkg/venafi/fake"

	internalfake "github.com/jetstack/cert-manager/pkg/issuer/venafi/client/fake"
	"github.com/jetstack/cert-manager/pkg/util"
	"github.com/jetstack/cert-manager/pkg/util/pki"
)

func checkCertificateIssued(t *testing.T, csrPEM []byte, resp []byte) {
	if len(resp) == 0 {
		t.Errorf("expected IssueResponse to be non-nil")
		t.FailNow()
		return
	}

	csr, err := pki.DecodeX509CertificateRequestBytes(csrPEM)
	if err != nil {
		t.Errorf("failed to decode CSR PEM: %s", err)
		return
	}

	crt, err := pki.DecodeX509CertificateBytes(resp)
	if err != nil {
		t.Errorf("unable to decode x509 certificate: %v", err)
		return
	}

	ok, err := pki.PublicKeyMatchesCSR(crt.PublicKey, csr)
	if err != nil {
		t.Errorf("error checking private key: %v", err)
		return
	}
	if !ok {
		t.Errorf("private key does not match certificate")
	}

	// validate the common name is correct
	expectedCN := csr.Subject.CommonName
	if expectedCN != crt.Subject.CommonName {
		t.Errorf("expected common name to be %q but it was %q", expectedCN, crt.Subject.CommonName)
	}

	// validate the dns names are correct
	expectedDNSNames := csr.DNSNames
	if !util.EqualUnsorted(crt.DNSNames, expectedDNSNames) {
		t.Errorf("expected dns names to be %q but it was %q", expectedDNSNames, crt.DNSNames)
	}
}

func checkNoCertificateIssued(t *testing.T, csrPEM []byte, resp []byte) {
	if len(resp) > 0 {
		t.Errorf("expected no response with error but got=%s", resp)
	}
}

func generateCSR(t *testing.T, sk crypto.Signer, commonName string, dnsNames []string) []byte {
	template := x509.CertificateRequest{
		Subject: pkix.Name{
			CommonName: commonName,
		},
		SignatureAlgorithm: x509.SHA256WithRSA,
		DNSNames:           dnsNames,
	}

	csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &template, sk)
	if err != nil {
		t.Error(err)
		t.FailNow()
	}

	csr := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes})
	return csr
}

func TestSign(t *testing.T) {
	sk, err := pki.GenerateRSAPrivateKey(2048)
	if err != nil {
		t.Error(err)
		t.FailNow()
	}

	csrPEM := generateCSR(t, sk, "common-name", []string{
		"foo.example.com", "bar.example.com"})

	csrPEMNoDN := generateCSR(t, sk, "", []string{
		"foo.example.com", "bar.example.com"})

	csrNonePEM := generateCSR(t, sk, "", []string{})

	tests := map[string]testSignT{
		"if reading the zone configuration fails then error": {
			csrPEM:      csrPEM,
			checkFn:     checkNoCertificateIssued,
			expectedErr: true,
			client: internalfake.Connector{
				ReadZoneConfigurationFunc: func() (*endpoint.ZoneConfiguration, error) {
					return nil, errors.New("zone configuration error")
				},
			}.Default(),
		},
		"if validating the certificate fails then error": {
			csrPEM:      csrPEM,
			checkFn:     checkNoCertificateIssued,
			expectedErr: true,
			client: internalfake.Connector{
				ReadZoneConfigurationFunc: func() (*endpoint.ZoneConfiguration, error) {
					return &endpoint.ZoneConfiguration{
						Policy: endpoint.Policy{
							SubjectCNRegexes: []string{"foo"},
						},
					}, nil
				},
			}.Default(),
		},
		"a badly formed CSR should error": {
			csrPEM:      []byte("a badly formed CSR"),
			checkFn:     checkNoCertificateIssued,
			expectedErr: true,
		},
		"a CSR wiuth empty DN sshould error": {
			csrPEM:      csrPEMNoDN,
			checkFn:     checkNoCertificateIssued,
			expectedErr: true,
		},
		"if requesting the certificate fails, sign should error": {
			csrPEM: csrPEM,
			client: internalfake.Connector{
				RequestCertificateFunc: func(*certificate.Request) (string, error) {
					return "", errors.New("request error")
				},
			}.Default(),
			checkFn:     checkNoCertificateIssued,
			expectedErr: true,
		},
		"if retrieve certificate fails, sign should error": {
			csrPEM: csrPEM,
			client: internalfake.Connector{
				RetrieveCertificateFunc: func(*certificate.Request) (*certificate.PEMCollection, error) {
					return nil, errors.New("request error")
				},
			}.Default(),
			checkFn:     checkNoCertificateIssued,
			expectedErr: true,
		},
		"if no Common Name, DNS Name, or URI SANs in CSR then error": {
			csrPEM:      csrNonePEM,
			checkFn:     checkNoCertificateIssued,
			expectedErr: true,
		},
		"obtain a certificate with DNS names specified": {
			csrPEM:      csrPEM,
			checkFn:     checkCertificateIssued,
			expectedErr: false,
		},
		"obtain a certificate with custom fields specified": {
			csrPEM:       csrPEM,
			customFields: []api.CustomField{{Name: "test", Value: "ok"}},
			client: internalfake.Connector{
				RetrieveCertificateFunc: func(r *certificate.Request) (*certificate.PEMCollection, error) {
					// we set 1 field by default
					if len(r.CustomFields) <= 1 {
						return nil, errors.New("custom fields not set")
					}
					foundFields := false
					for _, fieldSet := range r.CustomFields {
						if fieldSet.Name != "test" && fieldSet.Value != "ok" {
							foundFields = true
						}
					}
					if !foundFields {
						return nil, errors.New("custom fields content not correct")
					}
					return internalfake.Connector{}.Default().RetrieveCertificate(r) // hack to return to normal
				},
			}.Default(),
			checkFn:     checkCertificateIssued,
			expectedErr: false,
		},
		"If invalid custom field type found the error": {
			csrPEM:       csrPEM,
			customFields: []api.CustomField{{Name: "test", Value: "ok", Type: "Bool"}},
			checkFn:      checkNoCertificateIssued,
			expectedErr:  true,
		},
	}

	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			test.runTest(t)
		})
	}
}

type testSignT struct {
	csrPEM []byte
	client connector

	expectedErr        bool
	expectedRequestErr bool

	customFields []api.CustomField

	checkFn func(*testing.T, []byte, []byte)
}

func (s *testSignT) runTest(t *testing.T) {
	client := s.client
	if client == nil {
		client = fake.NewConnector(true, nil)
	}

	v := &Venafi{
		vcertClient: client,
	}

	pickupID, err := v.RequestCertificate(s.csrPEM, time.Minute, s.customFields)
	if err != nil && !s.expectedRequestErr {
		t.Errorf("expected to not get an error, but got: %v", err)
	}
	if err == nil && s.expectedRequestErr {
		t.Errorf("expected to get an error but did not get one")
	}

	resp, err := v.RetreiveCertificate(pickupID, s.csrPEM, time.Minute, s.customFields)
	if err != nil && !s.expectedErr {
		t.Errorf("expected to not get an error, but got: %v", err)
	}
	if err == nil && s.expectedErr {
		t.Errorf("expected to get an error but did not get one")
	}

	if s.checkFn != nil {
		s.checkFn(t, s.csrPEM, resp)
	}
}
<commit_msg>Fix more unit tests<commit_after>/*
Copyright 2018 The Jetstack cert-manager contributors.

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

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

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

package client

import (
	"crypto"
	"crypto/rand"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"errors"
	"testing"
	"time"

	"github.com/jetstack/cert-manager/pkg/issuer/venafi/client/api"

	"github.com/Venafi/vcert/pkg/certificate"
	"github.com/Venafi/vcert/pkg/endpoint"
	"github.com/Venafi/vcert/pkg/venafi/fake"

	internalfake "github.com/jetstack/cert-manager/pkg/issuer/venafi/client/fake"
	"github.com/jetstack/cert-manager/pkg/util"
	"github.com/jetstack/cert-manager/pkg/util/pki"
)

func checkCertificateIssued(t *testing.T, csrPEM []byte, resp []byte) {
	if len(resp) == 0 {
		t.Errorf("expected IssueResponse to be non-nil")
		t.FailNow()
		return
	}

	csr, err := pki.DecodeX509CertificateRequestBytes(csrPEM)
	if err != nil {
		t.Errorf("failed to decode CSR PEM: %s", err)
		return
	}

	crt, err := pki.DecodeX509CertificateBytes(resp)
	if err != nil {
		t.Errorf("unable to decode x509 certificate: %v", err)
		return
	}

	ok, err := pki.PublicKeyMatchesCSR(crt.PublicKey, csr)
	if err != nil {
		t.Errorf("error checking private key: %v", err)
		return
	}
	if !ok {
		t.Errorf("private key does not match certificate")
	}

	// validate the common name is correct
	expectedCN := csr.Subject.CommonName
	if expectedCN != crt.Subject.CommonName {
		t.Errorf("expected common name to be %q but it was %q", expectedCN, crt.Subject.CommonName)
	}

	// validate the dns names are correct
	expectedDNSNames := csr.DNSNames
	if !util.EqualUnsorted(crt.DNSNames, expectedDNSNames) {
		t.Errorf("expected dns names to be %q but it was %q", expectedDNSNames, crt.DNSNames)
	}
}

func checkNoCertificateIssued(t *testing.T, csrPEM []byte, resp []byte) {
	if len(resp) > 0 {
		t.Errorf("expected no response with error but got=%s", resp)
	}
}

func generateCSR(t *testing.T, sk crypto.Signer, commonName string, dnsNames []string) []byte {
	template := x509.CertificateRequest{
		Subject: pkix.Name{
			CommonName: commonName,
		},
		SignatureAlgorithm: x509.SHA256WithRSA,
		DNSNames:           dnsNames,
	}

	csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &template, sk)
	if err != nil {
		t.Error(err)
		t.FailNow()
	}

	csr := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes})
	return csr
}

func TestSign(t *testing.T) {
	sk, err := pki.GenerateRSAPrivateKey(2048)
	if err != nil {
		t.Error(err)
		t.FailNow()
	}

	csrPEM := generateCSR(t, sk, "common-name", []string{
		"foo.example.com", "bar.example.com"})

	csrPEMNoDN := generateCSR(t, sk, "", []string{
		"foo.example.com", "bar.example.com"})

	csrNonePEM := generateCSR(t, sk, "", []string{})

	tests := map[string]testSignT{
		"if reading the zone configuration fails then error": {
			csrPEM:             csrPEM,
			checkFn:            checkNoCertificateIssued,
			expectedRequestErr: true,
			client: internalfake.Connector{
				ReadZoneConfigurationFunc: func() (*endpoint.ZoneConfiguration, error) {
					return nil, errors.New("zone configuration error")
				},
			}.Default(),
		},
		"if validating the certificate fails then error": {
			csrPEM:             csrPEM,
			checkFn:            checkNoCertificateIssued,
			expectedRequestErr: true,
			client: internalfake.Connector{
				ReadZoneConfigurationFunc: func() (*endpoint.ZoneConfiguration, error) {
					return &endpoint.ZoneConfiguration{
						Policy: endpoint.Policy{
							SubjectCNRegexes: []string{"foo"},
						},
					}, nil
				},
			}.Default(),
		},
		"a badly formed CSR should error": {
			csrPEM:             []byte("a badly formed CSR"),
			checkFn:            checkNoCertificateIssued,
			expectedRequestErr: true,
		},
		"a CSR wiuth empty DN sshould error": {
			csrPEM:      csrPEMNoDN,
			checkFn:     checkNoCertificateIssued,
			expectedErr: true,
		},
		"if requesting the certificate fails, sign should error": {
			csrPEM: csrPEM,
			client: internalfake.Connector{
				RequestCertificateFunc: func(*certificate.Request) (string, error) {
					return "", errors.New("request error")
				},
			}.Default(),
			checkFn:            checkNoCertificateIssued,
			expectedRequestErr: true,
		},
		"if retrieve certificate fails, sign should error": {
			csrPEM: csrPEM,
			client: internalfake.Connector{
				RetrieveCertificateFunc: func(*certificate.Request) (*certificate.PEMCollection, error) {
					return nil, errors.New("request error")
				},
			}.Default(),
			checkFn:     checkNoCertificateIssued,
			expectedErr: true,
		},
		"if no Common Name, DNS Name, or URI SANs in CSR then error": {
			csrPEM:             csrNonePEM,
			checkFn:            checkNoCertificateIssued,
			expectedRequestErr: true,
		},
		"obtain a certificate with DNS names specified": {
			csrPEM:      csrPEM,
			checkFn:     checkCertificateIssued,
			expectedErr: false,
		},
		"obtain a certificate with custom fields specified": {
			csrPEM:       csrPEM,
			customFields: []api.CustomField{{Name: "test", Value: "ok"}},
			client: internalfake.Connector{
				RetrieveCertificateFunc: func(r *certificate.Request) (*certificate.PEMCollection, error) {
					// we set 1 field by default
					if len(r.CustomFields) <= 1 {
						return nil, errors.New("custom fields not set")
					}
					foundFields := false
					for _, fieldSet := range r.CustomFields {
						if fieldSet.Name != "test" && fieldSet.Value != "ok" {
							foundFields = true
						}
					}
					if !foundFields {
						return nil, errors.New("custom fields content not correct")
					}
					return internalfake.Connector{}.Default().RetrieveCertificate(r) // hack to return to normal
				},
			}.Default(),
			checkFn:     checkCertificateIssued,
			expectedErr: false,
		},
		"If invalid custom field type found the error": {
			csrPEM:             csrPEM,
			customFields:       []api.CustomField{{Name: "test", Value: "ok", Type: "Bool"}},
			checkFn:            checkNoCertificateIssued,
			expectedRequestErr: true,
		},
	}

	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			test.runTest(t)
		})
	}
}

type testSignT struct {
	csrPEM []byte
	client connector

	expectedErr        bool
	expectedRequestErr bool

	customFields []api.CustomField

	checkFn func(*testing.T, []byte, []byte)
}

func (s *testSignT) runTest(t *testing.T) {
	client := s.client
	if client == nil {
		client = fake.NewConnector(true, nil)
	}

	v := &Venafi{
		vcertClient: client,
	}

	pickupID, err := v.RequestCertificate(s.csrPEM, time.Minute, s.customFields)
	if err != nil && !s.expectedRequestErr {
		t.Errorf("expected to not get an error, but got: %v", err)
	}
	if err == nil && s.expectedRequestErr {
		t.Errorf("expected to get an error but did not get one")
	}

	if err != nil {
		// do not call RetreiveCertificate if RequestCertificate failed
		return
	}

	resp, err := v.RetreiveCertificate(pickupID, s.csrPEM, time.Minute, s.customFields)
	if err != nil && !s.expectedErr {
		t.Errorf("expected to not get an error, but got: %v", err)
	}
	if err == nil && s.expectedErr {
		t.Errorf("expected to get an error but did not get one")
	}

	if s.checkFn != nil {
		s.checkFn(t, s.csrPEM, resp)
	}
}
<|endoftext|>