<commit_before>/*
Copyright 2018 The Kubernetes Authors.

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

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

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

package v1alpha1

import (
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// ApplicationSpec defines the specification for an Application.
type ApplicationSpec struct {
	// Type is the type of the application (e.g. WordPress, MySQL, Cassandra).
	Type string `json:"type,omitempty"`

	// ComponentGroupKinds is a list of Kinds for Application's components (e.g. Deployments, Pods, Services, CRDs). It
	// can be used in conjunction with the Application's Selector to list or watch the Applications components.
	ComponentGroupKinds []metav1.GroupKind `json:"componentKinds,omitempty"`

	// Selector is a label query over kinds that created by the application. It must match the component objects' labels.
	// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors
	Selector *metav1.LabelSelector `json:"selector,omitempty"`

	// Version is an optional version indicator for the Application.
	Version string `json:"version,omitempty"`

	// Description is a brief string description of the Application.
	Description string `json:"description,omitempty"`

	// Maintainers is an optional list of maintainers of the application. The maintainers in this list maintain the
	// the source code, images, and package for the application.
	Maintainers []Maintainer `json:"maintainers,omitempty"`

	// Owners is an optional list of the owners of the installed application. The owners of the application should be
	// contacted in the event of a planned or unplanned disruption affecting the application.
	Owners []string `json:"owners,omitempty"`

	// Keywords is an optional list of key words associated with the application (e.g. MySQL, RDBMS, database).
	Keywords []string `json:"keywords,omitempty"`

	// Info contains human readable key,value pairs for the Application.
	Info []InfoItem `json:"info,omitempty"`

	// Links are a list of descriptive URLs intended to be used to surface additional documentation, dashboards, etc.
	Links []Link `json:"urls,omitempty"`

	// Notes are a list of human readable snippets intended as a quick start for the users of the Application.
	Notes []string `json:"notes,omitempty"`
}

// ApplicationStatus defines controllers the observed state of Application
type ApplicationStatus struct {
	// ObservedGeneration is used by the Application Controller to report the last Generation of an Application
	// that it has observed.
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`
}

// Maintainer contains information about an individual or organization that maintains the source code, images, and
// package for an Application. An Application can have more than one maintainer.
type Maintainer struct {
	// Name is the descriptive name of the maintainer.
	Name string `json:"name,omitempty"`

	// Url could typically be a website address or a mailto: email address.
	Url string `json:"url,omitempty"`
}

// Link contains information about an URL to surface documentation, dashboards, etc.
type Link struct {
	// Type defines the purpose of the link.
	Type string `json:"type,omitempty"`

	// Description is human readable content explaining the purpose of the link.
	Description string `json:"description,omitempty"`

	// Url typically points at a website address.
	Url string `json:"url,omitempty"`
}

// InfoItem is a human readable key,value pair containing important information about how to access the Application.
type InfoItem struct {
	// Name is a human readable title for this piece of information.
	Name string `json:"name,omitempty"`

	// Value is human readable content.
	Value string `json:"value,omitempty"`
}

// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// Application
// +k8s:openapi-gen=true
// +resource:path=applications
// The Application object acts as an aggregator for components that comprise an Application. Its
// Spec.ComponentGroupKinds indicate the GroupKinds of the components the comprise the Application. Its Spec. Selector
// is used to list and watch those components. All components of an Application should be labeled such the Application's
// Spec. Selector matches.
type Application struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`
	// The specification object for the Application.
	Spec ApplicationSpec `json:"spec,omitempty"`
	// The status object for the Application.
	Status ApplicationStatus `json:"status,omitempty"`
}
<commit_msg>Add email to Maintainer struct<commit_after>/*
Copyright 2018 The Kubernetes Authors.

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

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

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

package v1alpha1

import (
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// ApplicationSpec defines the specification for an Application.
type ApplicationSpec struct {
	// Type is the type of the application (e.g. WordPress, MySQL, Cassandra).
	Type string `json:"type,omitempty"`

	// ComponentGroupKinds is a list of Kinds for Application's components (e.g. Deployments, Pods, Services, CRDs). It
	// can be used in conjunction with the Application's Selector to list or watch the Applications components.
	ComponentGroupKinds []metav1.GroupKind `json:"componentKinds,omitempty"`

	// Selector is a label query over kinds that created by the application. It must match the component objects' labels.
	// More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#label-selectors
	Selector *metav1.LabelSelector `json:"selector,omitempty"`

	// Version is an optional version indicator for the Application.
	Version string `json:"version,omitempty"`

	// Description is a brief string description of the Application.
	Description string `json:"description,omitempty"`

	// Maintainers is an optional list of maintainers of the application. The maintainers in this list maintain the
	// the source code, images, and package for the application.
	Maintainers []Maintainer `json:"maintainers,omitempty"`

	// Owners is an optional list of the owners of the installed application. The owners of the application should be
	// contacted in the event of a planned or unplanned disruption affecting the application.
	Owners []string `json:"owners,omitempty"`

	// Keywords is an optional list of key words associated with the application (e.g. MySQL, RDBMS, database).
	Keywords []string `json:"keywords,omitempty"`

	// Info contains human readable key,value pairs for the Application.
	Info []InfoItem `json:"info,omitempty"`

	// Links are a list of descriptive URLs intended to be used to surface additional documentation, dashboards, etc.
	Links []Link `json:"urls,omitempty"`

	// Notes are a list of human readable snippets intended as a quick start for the users of the Application.
	Notes []string `json:"notes,omitempty"`
}

// ApplicationStatus defines controllers the observed state of Application
type ApplicationStatus struct {
	// ObservedGeneration is used by the Application Controller to report the last Generation of an Application
	// that it has observed.
	ObservedGeneration int64 `json:"observedGeneration,omitempty"`
}

// Maintainer contains information about an individual or organization that maintains the source code, images, and
// package for an Application. An Application can have more than one maintainer.
type Maintainer struct {
	// Name is the descriptive name of the maintainer.
	Name string `json:"name,omitempty"`

	// Url could typically be a website address.
	Url string `json:"url,omitempty"`

	// Email is the email address.
	Email string `json:"email,omitempty"`
}

// Link contains information about an URL to surface documentation, dashboards, etc.
type Link struct {
	// Type defines the purpose of the link.
	Type string `json:"type,omitempty"`

	// Description is human readable content explaining the purpose of the link.
	Description string `json:"description,omitempty"`

	// Url typically points at a website address.
	Url string `json:"url,omitempty"`
}

// InfoItem is a human readable key,value pair containing important information about how to access the Application.
type InfoItem struct {
	// Name is a human readable title for this piece of information.
	Name string `json:"name,omitempty"`

	// Value is human readable content.
	Value string `json:"value,omitempty"`
}

// +genclient
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object

// Application
// +k8s:openapi-gen=true
// +resource:path=applications
// The Application object acts as an aggregator for components that comprise an Application. Its
// Spec.ComponentGroupKinds indicate the GroupKinds of the components the comprise the Application. Its Spec. Selector
// is used to list and watch those components. All components of an Application should be labeled such the Application's
// Spec. Selector matches.
type Application struct {
	metav1.TypeMeta   `json:",inline"`
	metav1.ObjectMeta `json:"metadata,omitempty"`
	// The specification object for the Application.
	Spec ApplicationSpec `json:"spec,omitempty"`
	// The status object for the Application.
	Status ApplicationStatus `json:"status,omitempty"`
}
<|endoftext|>


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

import (
	"k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// +k8s:deepcopy-gen=true
// JobStatus represents the current observed state of the training Job.
type JobStatus struct {
	// Conditions is an array of current observed job conditions.
	Conditions []JobCondition `json:"conditions"`

	// ReplicaStatuses is map of ReplicaType and ReplicaStatus,
	// specifies the status of each replica.
	ReplicaStatuses map[ReplicaType]*ReplicaStatus `json:"replicaStatuses"`

	// Represents time when the job was acknowledged by the job controller.
	// It is not guaranteed to be set in happens-before order across separate operations.
	// It is represented in RFC3339 form and is in UTC.
	StartTime *metav1.Time `json:"startTime,omitempty"`

	// Represents time when the job was completed. It is not guaranteed to
	// be set in happens-before order across separate operations.
	// It is represented in RFC3339 form and is in UTC.
	CompletionTime *metav1.Time `json:"completionTime,omitempty"`

	// Represents last time when the job was reconciled. It is not guaranteed to
	// be set in happens-before order across separate operations.
	// It is represented in RFC3339 form and is in UTC.
	LastReconcileTime *metav1.Time `json:"lastReconcileTime,omitempty"`
}

// ReplicaType represents the type of the replica. Each operator needs to define its
// own set of ReplicaTypes.
type ReplicaType string

// ReplicaStatus represents the current observed state of the replica.
type ReplicaStatus struct {
	// The number of actively running pods.
	Active int32 `json:"active,omitempty"`

	// The number of pods which reached phase Succeeded.
	Succeeded int32 `json:"succeeded,omitempty"`

	// The number of pods which reached phase Failed.
	Failed int32 `json:"failed,omitempty"`
}

// +k8s:deepcopy-gen=true
// ReplicaSpec is a description of the replica
type ReplicaSpec struct {
	// Replicas is the desired number of replicas of the given template.
	// If unspecified, defaults to 1.
	Replicas *int32 `json:"replicas,omitempty"`

	// Template is the object that describes the pod that
	// will be created for this replica. RestartPolicy in PodTemplateSpec
	// will be overide by RestartPolicy in ReplicaSpec
	Template v1.PodTemplateSpec `json:"template,omitempty"`

	// Restart policy for all replicas within the job.
	// One of Always, OnFailure, Never and ExitCode.
	// Default to Never.
	RestartPolicy RestartPolicy `json:"restartPolicy,omitempty"`
}

// +k8s:deepcopy-gen=true
// JobCondition describes the state of the job at a certain point.
type JobCondition struct {
	// Type of job condition.
	Type JobConditionType `json:"type"`
	// Status of the condition, one of True, False, Unknown.
	Status v1.ConditionStatus `json:"status"`
	// The reason for the condition's last transition.
	Reason string `json:"reason,omitempty"`
	// A human readable message indicating details about the transition.
	Message string `json:"message,omitempty"`
	// The last time this condition was updated.
	LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty"`
	// Last time the condition transitioned from one status to another.
	LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
}

// JobConditionType defines all kinds of types of JobStatus.
type JobConditionType string

const (
	// JobCreated means the job has been accepted by the system,
	// but one or more of the pods/services has not been started.
	// This includes time before pods being scheduled and launched.
	JobCreated JobConditionType = "Created"

	// JobRunning means all sub-resources (e.g. services/pods) of this job
	// have been successfully scheduled and launched.
	// The training is running without error.
	JobRunning JobConditionType = "Running"

	// JobRestarting means one or more sub-resources (e.g. services/pods) of this job
	// reached phase failed but maybe restarted according to it's restart policy
	// which specified by user in v1.PodTemplateSpec.
	// The training is freezing/pending.
	JobRestarting JobConditionType = "Restarting"

	// JobSucceeded means all sub-resources (e.g. services/pods) of this job
	// reached phase have terminated in success.
	// The training is complete without error.
	JobSucceeded JobConditionType = "Succeeded"

	// JobFailed means one or more sub-resources (e.g. services/pods) of this job
	// reached phase failed with no restarting.
	// The training has failed its execution.
	JobFailed JobConditionType = "Failed"
)

// CleanPodPolicy describes how to deal with pods when the job is finished.
type CleanPodPolicy string

const (
	CleanPodPolicyUndefined CleanPodPolicy = ""
	CleanPodPolicyAll       CleanPodPolicy = "All"
	CleanPodPolicyRunning   CleanPodPolicy = "Running"
	CleanPodPolicyNone      CleanPodPolicy = "None"
)

// RestartPolicy describes how the replicas should be restarted.
// Only one of the following restart policies may be specified.
// If none of the following policies is specified, the default one
// is RestartPolicyAlways.
type RestartPolicy string

const (
	RestartPolicyAlways    RestartPolicy = "Always"
	RestartPolicyOnFailure RestartPolicy = "OnFailure"
	RestartPolicyNever     RestartPolicy = "Never"

	// `ExitCode` policy means that user should add exit code by themselves,
	// The job operator will check these exit codes to
	// determine the behavior when an error occurs:
	// - 1-127: permanent error, do not restart.
	// - 128-255: retryable error, will restart the pod.
	RestartPolicyExitCode RestartPolicy = "ExitCode"
)
<commit_msg>fix_lint (#107)<commit_after>// Copyright 2018 The Kubeflow Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package v1alpha2

import (
	"k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// +k8s:deepcopy-gen=true
// JobStatus represents the current observed state of the training Job.
type JobStatus struct {
	// Conditions is an array of current observed job conditions.
	Conditions []JobCondition `json:"conditions"`

	// ReplicaStatuses is map of ReplicaType and ReplicaStatus,
	// specifies the status of each replica.
	ReplicaStatuses map[ReplicaType]*ReplicaStatus `json:"replicaStatuses"`

	// Represents time when the job was acknowledged by the job controller.
	// It is not guaranteed to be set in happens-before order across separate operations.
	// It is represented in RFC3339 form and is in UTC.
	StartTime *metav1.Time `json:"startTime,omitempty"`

	// Represents time when the job was completed. It is not guaranteed to
	// be set in happens-before order across separate operations.
	// It is represented in RFC3339 form and is in UTC.
	CompletionTime *metav1.Time `json:"completionTime,omitempty"`

	// Represents last time when the job was reconciled. It is not guaranteed to
	// be set in happens-before order across separate operations.
	// It is represented in RFC3339 form and is in UTC.
	LastReconcileTime *metav1.Time `json:"lastReconcileTime,omitempty"`
}

// ReplicaType represents the type of the replica. Each operator needs to define its
// own set of ReplicaTypes.
type ReplicaType string

// ReplicaStatus represents the current observed state of the replica.
type ReplicaStatus struct {
	// The number of actively running pods.
	Active int32 `json:"active,omitempty"`

	// The number of pods which reached phase Succeeded.
	Succeeded int32 `json:"succeeded,omitempty"`

	// The number of pods which reached phase Failed.
	Failed int32 `json:"failed,omitempty"`
}

// +k8s:deepcopy-gen=true
// ReplicaSpec is a description of the replica
type ReplicaSpec struct {
	// Replicas is the desired number of replicas of the given template.
	// If unspecified, defaults to 1.
	Replicas *int32 `json:"replicas,omitempty"`

	// Template is the object that describes the pod that
	// will be created for this replica. RestartPolicy in PodTemplateSpec
	// will be overide by RestartPolicy in ReplicaSpec
	Template v1.PodTemplateSpec `json:"template,omitempty"`

	// Restart policy for all replicas within the job.
	// One of Always, OnFailure, Never and ExitCode.
	// Default to Never.
	RestartPolicy RestartPolicy `json:"restartPolicy,omitempty"`
}

// +k8s:deepcopy-gen=true
// JobCondition describes the state of the job at a certain point.
type JobCondition struct {
	// Type of job condition.
	Type JobConditionType `json:"type"`
	// Status of the condition, one of True, False, Unknown.
	Status v1.ConditionStatus `json:"status"`
	// The reason for the condition's last transition.
	Reason string `json:"reason,omitempty"`
	// A human readable message indicating details about the transition.
	Message string `json:"message,omitempty"`
	// The last time this condition was updated.
	LastUpdateTime metav1.Time `json:"lastUpdateTime,omitempty"`
	// Last time the condition transitioned from one status to another.
	LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
}

// JobConditionType defines all kinds of types of JobStatus.
type JobConditionType string

const (
	// JobCreated means the job has been accepted by the system,
	// but one or more of the pods/services has not been started.
	// This includes time before pods being scheduled and launched.
	JobCreated JobConditionType = "Created"

	// JobRunning means all sub-resources (e.g. services/pods) of this job
	// have been successfully scheduled and launched.
	// The training is running without error.
	JobRunning JobConditionType = "Running"

	// JobRestarting means one or more sub-resources (e.g. services/pods) of this job
	// reached phase failed but maybe restarted according to it's restart policy
	// which specified by user in v1.PodTemplateSpec.
	// The training is freezing/pending.
	JobRestarting JobConditionType = "Restarting"

	// JobSucceeded means all sub-resources (e.g. services/pods) of this job
	// reached phase have terminated in success.
	// The training is complete without error.
	JobSucceeded JobConditionType = "Succeeded"

	// JobFailed means one or more sub-resources (e.g. services/pods) of this job
	// reached phase failed with no restarting.
	// The training has failed its execution.
	JobFailed JobConditionType = "Failed"
)

// CleanPodPolicy describes how to deal with pods when the job is finished.
type CleanPodPolicy string

// Possible values for CleanPodPolicy
const (
	CleanPodPolicyUndefined CleanPodPolicy = ""
	CleanPodPolicyAll       CleanPodPolicy = "All"
	CleanPodPolicyRunning   CleanPodPolicy = "Running"
	CleanPodPolicyNone      CleanPodPolicy = "None"
)

// RestartPolicy describes how the replicas should be restarted.
// Only one of the following restart policies may be specified.
// If none of the following policies is specified, the default one
// is RestartPolicyAlways.
type RestartPolicy string

// Possible values for RestartPolicy
const (
	RestartPolicyAlways    RestartPolicy = "Always"
	RestartPolicyOnFailure RestartPolicy = "OnFailure"
	RestartPolicyNever     RestartPolicy = "Never"

	// RestartPolicyExitCode policy means that user should add exit code by themselves,
	// The job operator will check these exit codes to
	// determine the behavior when an error occurs:
	// - 1-127: permanent error, do not restart.
	// - 128-255: retryable error, will restart the pod.
	RestartPolicyExitCode RestartPolicy = "ExitCode"
)
<|endoftext|>


<commit_before>// Package aws collects AWS-specific configuration.
package aws

import (
	"github.com/aws/aws-sdk-go/aws/session"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"

	ccaws "github.com/openshift/cloud-credential-operator/pkg/aws"
	credvalidator "github.com/openshift/cloud-credential-operator/pkg/controller/utils"
)

var installPermissions = []string{
	// EC2 related perms
	"ec2:AllocateAddress",
	"ec2:AssociateAddress",
	"ec2:AssociateDhcpOptions",
	"ec2:AssociateRouteTable",
	"ec2:AttachInternetGateway",
	"ec2:AuthorizeSecurityGroupEgress",
	"ec2:AuthorizeSecurityGroupIngress",
	"ec2:CopyImage",
	"ec2:CreateDhcpOptions",
	"ec2:CreateInternetGateway",
	"ec2:CreateNatGateway",
	"ec2:CreateRoute",
	"ec2:CreateRouteTable",
	"ec2:CreateSecurityGroup",
	"ec2:CreateSubnet",
	"ec2:CreateTags",
	"ec2:CreateVpc",
	"ec2:CreateVpcEndpoint",
	"ec2:CreateVolume",
	"ec2:DescribeAccountAttributes",
	"ec2:DescribeAddresses",
	"ec2:DescribeAvailabilityZones",
	"ec2:DescribeDhcpOptions",
	"ec2:DescribeImages",
	"ec2:DescribeInstanceAttribute",
	"ec2:DescribeInstanceCreditSpecifications",
	"ec2:DescribeInstances",
	"ec2:DescribeInternetGateways",
	"ec2:DescribeKeyPairs",
	"ec2:DescribeNatGateways",
	"ec2:DescribeNetworkAcls",
	"ec2:DescribePrefixLists",
	"ec2:DescribeRegions",
	"ec2:DescribeRouteTables",
	"ec2:DescribeSecurityGroups",
	"ec2:DescribeSubnets",
	"ec2:DescribeTags",
	"ec2:DescribeVpcEndpoints",
	"ec2:DescribeVpcs",
	"ec2:DescribeVpcAttribute",
	"ec2:DescribeVolumes",
	"ec2:DescribeVpcClassicLink",
	"ec2:DescribeVpcClassicLinkDnsSupport",
	"ec2:ModifyInstanceAttribute",
	"ec2:ModifySubnetAttribute",
	"ec2:ModifyVpcAttribute",
	"ec2:RevokeSecurityGroupEgress",
	"ec2:RunInstances",
	"ec2:TerminateInstances",
	"ec2:DeleteDhcpOptions",
	"ec2:DeleteRoute",
	"ec2:RevokeSecurityGroupIngress",
	"ec2:DisassociateRouteTable",
	"ec2:ReplaceRouteTableAssociation",
	"ec2:DeleteRouteTable",
	"ec2:DeleteSubnet",
	"ec2:DescribeNetworkInterfaces",
	"ec2:ModifyNetworkInterfaceAttribute",
	"ec2:DeleteNatGateway",
	"ec2:DeleteSecurityGroup",
	"ec2:DetachInternetGateway",
	"ec2:DeleteInternetGateway",
	"ec2:ReleaseAddress",
	"ec2:DeleteVpc",

	// ELB related perms
	"elasticloadbalancing:AddTags",
	"elasticloadbalancing:ApplySecurityGroupsToLoadBalancer",
	"elasticloadbalancing:AttachLoadBalancerToSubnets",
	"elasticloadbalancing:CreateListener",
	"elasticloadbalancing:CreateLoadBalancer",
	"elasticloadbalancing:CreateLoadBalancerListeners",
	"elasticloadbalancing:CreateTargetGroup",
	"elasticloadbalancing:ConfigureHealthCheck",
	"elasticloadbalancing:DeleteLoadBalancer",
	"elasticloadbalancing:DeregisterInstancesFromLoadBalancer",
	"elasticloadbalancing:DeregisterTargets",
	"elasticloadbalancing:DescribeInstanceHealth",
	"elasticloadbalancing:DescribeListeners",
	"elasticloadbalancing:DescribeLoadBalancers",
	"elasticloadbalancing:DescribeLoadBalancerAttributes",
	"elasticloadbalancing:DescribeTags",
	"elasticloadbalancing:DescribeTargetGroupAttributes",
	"elasticloadbalancing:DescribeTargetHealth",
	"elasticloadbalancing:ModifyLoadBalancerAttributes",
	"elasticloadbalancing:ModifyTargetGroup",
	"elasticloadbalancing:ModifyTargetGroupAttributes",
	"elasticloadbalancing:RegisterTargets",
	"elasticloadbalancing:RegisterInstancesWithLoadBalancer",
	"elasticloadbalancing:SetLoadBalancerPoliciesOfListener",

	// IAM related perms
	"iam:AddRoleToInstanceProfile",
	"iam:CreateInstanceProfile",
	"iam:CreateRole",
	"iam:DeleteInstanceProfile",
	"iam:DeleteRole",
	"iam:DeleteRolePolicy",
	"iam:GetInstanceProfile",
	"iam:GetRole",
	"iam:GetRolePolicy",
	"iam:GetUser",
	"iam:ListInstanceProfilesForRole",
	"iam:ListRoles",
	"iam:ListUsers",
	"iam:PassRole",
	"iam:PutRolePolicy",
	"iam:RemoveRoleFromInstanceProfile",
	"iam:SimulatePrincipalPolicy",
	"iam:TagRole",

	// Route53 related perms
	"route53:ChangeResourceRecordSets",
	"route53:ChangeTagsForResource",
	"route53:GetChange",
	"route53:GetHostedZone",
	"route53:CreateHostedZone",
	"route53:DeleteHostedZone",
	"route53:ListHostedZones",
	"route53:ListHostedZonesByName",
	"route53:ListResourceRecordSets",
	"route53:ListTagsForResource",
	"route53:UpdateHostedZoneComment",

	// S3 related perms
	"s3:CreateBucket",
	"s3:ListBucket",
	"s3:GetBucketCors",
	"s3:GetBucketWebsite",
	"s3:GetBucketVersioning",
	"s3:GetAccelerateConfiguration",
	"s3:GetEncryptionConfiguration",
	"s3:GetBucketRequestPayment",
	"s3:GetBucketLogging",
	"s3:GetLifecycleConfiguration",
	"s3:GetBucketReplication",
	"s3:GetReplicationConfiguration",
	"s3:GetBucketLocation",
	"s3:GetBucketTagging",
	"s3:DeleteBucket",
	"s3:PutBucketAcl",
	"s3:PutBucketTagging",
	"s3:PutEncryptionConfiguration",

	// More S3 (would be nice to limit 'Resource' to just the bucket we actualy interact with...)
	"s3:PutObject",
	"s3:PutObjectAcl",
	"s3:PutObjectTagging",
	"s3:GetObject",
	"s3:GetObjectAcl",
	"s3:GetObjectTagging",
	"s3:GetObjectVersion",
	"s3:DeleteObject",

	// Uninstall-specific perms
	"autoscaling:DescribeAutoScalingGroups",
	"ec2:DeleteNetworkInterface",
	"ec2:DeleteVolume",
	"ec2:DeleteVpcEndpoints",
	"elasticloadbalancing:DescribeTargetGroups",
	"elasticloadbalancing:DeleteTargetGroup",
	"iam:ListInstanceProfiles",
	"iam:ListRolePolicies",
	"iam:ListUserPolicies",
	"tag:GetResources",
}

// ValidateCreds will try to create an AWS session, and also verify that the current credentials
// are sufficient to perform an installation, and that they can be used for cluster runtime
// as either capable of creating new credentials for components that interact with the cloud or
// being able to be passed through as-is to the components that need cloud credentials
func ValidateCreds(ssn *session.Session) error {
	creds, err := ssn.Config.Credentials.Get()
	if err != nil {
		return errors.Wrap(err, "getting creds from session")
	}

	client, err := ccaws.NewClient([]byte(creds.AccessKeyID), []byte(creds.SecretAccessKey))
	if err != nil {
		return errors.Wrap(err, "initialize cloud-credentials client")
	}

	// Check whether we can do an installation
	logger := logrus.StandardLogger()
	canInstall, err := credvalidator.CheckPermissionsAgainstActions(client, installPermissions, logger)
	if err != nil {
		return errors.Wrap(err, "checking install permissions")
	}
	if !canInstall {
		return errors.New("current credentials insufficient for performing cluster installation")
	}

	// Check whether we can mint new creds for cluster services needing to interact with the cloud
	canMint, err := credvalidator.CheckCloudCredCreation(client, logger)
	if err != nil {
		return errors.Wrap(err, "mint credentials check")
	}

	// Check whether we can use the current credentials in passthrough mode to satisfy
	// cluster services needing to interact with the cloud
	canPassthrough, err := credvalidator.CheckCloudCredPassthrough(client, logger)
	if err != nil {
		return errors.Wrap(err, "passthrough credentials check")
	}

	if !canMint && !canPassthrough {
		return errors.New("AWS credentials cannot be used to either create new creds or use as-is")
	}

	return nil
}
<commit_msg>pkg/asset/installconfig/aws/permissions: If we can mint, skip passthrough check<commit_after>// Package aws collects AWS-specific configuration.
package aws

import (
	"github.com/aws/aws-sdk-go/aws/session"

	"github.com/pkg/errors"
	"github.com/sirupsen/logrus"

	ccaws "github.com/openshift/cloud-credential-operator/pkg/aws"
	credvalidator "github.com/openshift/cloud-credential-operator/pkg/controller/utils"
)

var installPermissions = []string{
	// EC2 related perms
	"ec2:AllocateAddress",
	"ec2:AssociateAddress",
	"ec2:AssociateDhcpOptions",
	"ec2:AssociateRouteTable",
	"ec2:AttachInternetGateway",
	"ec2:AuthorizeSecurityGroupEgress",
	"ec2:AuthorizeSecurityGroupIngress",
	"ec2:CopyImage",
	"ec2:CreateDhcpOptions",
	"ec2:CreateInternetGateway",
	"ec2:CreateNatGateway",
	"ec2:CreateRoute",
	"ec2:CreateRouteTable",
	"ec2:CreateSecurityGroup",
	"ec2:CreateSubnet",
	"ec2:CreateTags",
	"ec2:CreateVpc",
	"ec2:CreateVpcEndpoint",
	"ec2:CreateVolume",
	"ec2:DescribeAccountAttributes",
	"ec2:DescribeAddresses",
	"ec2:DescribeAvailabilityZones",
	"ec2:DescribeDhcpOptions",
	"ec2:DescribeImages",
	"ec2:DescribeInstanceAttribute",
	"ec2:DescribeInstanceCreditSpecifications",
	"ec2:DescribeInstances",
	"ec2:DescribeInternetGateways",
	"ec2:DescribeKeyPairs",
	"ec2:DescribeNatGateways",
	"ec2:DescribeNetworkAcls",
	"ec2:DescribePrefixLists",
	"ec2:DescribeRegions",
	"ec2:DescribeRouteTables",
	"ec2:DescribeSecurityGroups",
	"ec2:DescribeSubnets",
	"ec2:DescribeTags",
	"ec2:DescribeVpcEndpoints",
	"ec2:DescribeVpcs",
	"ec2:DescribeVpcAttribute",
	"ec2:DescribeVolumes",
	"ec2:DescribeVpcClassicLink",
	"ec2:DescribeVpcClassicLinkDnsSupport",
	"ec2:ModifyInstanceAttribute",
	"ec2:ModifySubnetAttribute",
	"ec2:ModifyVpcAttribute",
	"ec2:RevokeSecurityGroupEgress",
	"ec2:RunInstances",
	"ec2:TerminateInstances",
	"ec2:DeleteDhcpOptions",
	"ec2:DeleteRoute",
	"ec2:RevokeSecurityGroupIngress",
	"ec2:DisassociateRouteTable",
	"ec2:ReplaceRouteTableAssociation",
	"ec2:DeleteRouteTable",
	"ec2:DeleteSubnet",
	"ec2:DescribeNetworkInterfaces",
	"ec2:ModifyNetworkInterfaceAttribute",
	"ec2:DeleteNatGateway",
	"ec2:DeleteSecurityGroup",
	"ec2:DetachInternetGateway",
	"ec2:DeleteInternetGateway",
	"ec2:ReleaseAddress",
	"ec2:DeleteVpc",

	// ELB related perms
	"elasticloadbalancing:AddTags",
	"elasticloadbalancing:ApplySecurityGroupsToLoadBalancer",
	"elasticloadbalancing:AttachLoadBalancerToSubnets",
	"elasticloadbalancing:CreateListener",
	"elasticloadbalancing:CreateLoadBalancer",
	"elasticloadbalancing:CreateLoadBalancerListeners",
	"elasticloadbalancing:CreateTargetGroup",
	"elasticloadbalancing:ConfigureHealthCheck",
	"elasticloadbalancing:DeleteLoadBalancer",
	"elasticloadbalancing:DeregisterInstancesFromLoadBalancer",
	"elasticloadbalancing:DeregisterTargets",
	"elasticloadbalancing:DescribeInstanceHealth",
	"elasticloadbalancing:DescribeListeners",
	"elasticloadbalancing:DescribeLoadBalancers",
	"elasticloadbalancing:DescribeLoadBalancerAttributes",
	"elasticloadbalancing:DescribeTags",
	"elasticloadbalancing:DescribeTargetGroupAttributes",
	"elasticloadbalancing:DescribeTargetHealth",
	"elasticloadbalancing:ModifyLoadBalancerAttributes",
	"elasticloadbalancing:ModifyTargetGroup",
	"elasticloadbalancing:ModifyTargetGroupAttributes",
	"elasticloadbalancing:RegisterTargets",
	"elasticloadbalancing:RegisterInstancesWithLoadBalancer",
	"elasticloadbalancing:SetLoadBalancerPoliciesOfListener",

	// IAM related perms
	"iam:AddRoleToInstanceProfile",
	"iam:CreateInstanceProfile",
	"iam:CreateRole",
	"iam:DeleteInstanceProfile",
	"iam:DeleteRole",
	"iam:DeleteRolePolicy",
	"iam:GetInstanceProfile",
	"iam:GetRole",
	"iam:GetRolePolicy",
	"iam:GetUser",
	"iam:ListInstanceProfilesForRole",
	"iam:ListRoles",
	"iam:ListUsers",
	"iam:PassRole",
	"iam:PutRolePolicy",
	"iam:RemoveRoleFromInstanceProfile",
	"iam:SimulatePrincipalPolicy",
	"iam:TagRole",

	// Route53 related perms
	"route53:ChangeResourceRecordSets",
	"route53:ChangeTagsForResource",
	"route53:GetChange",
	"route53:GetHostedZone",
	"route53:CreateHostedZone",
	"route53:DeleteHostedZone",
	"route53:ListHostedZones",
	"route53:ListHostedZonesByName",
	"route53:ListResourceRecordSets",
	"route53:ListTagsForResource",
	"route53:UpdateHostedZoneComment",

	// S3 related perms
	"s3:CreateBucket",
	"s3:ListBucket",
	"s3:GetBucketCors",
	"s3:GetBucketWebsite",
	"s3:GetBucketVersioning",
	"s3:GetAccelerateConfiguration",
	"s3:GetEncryptionConfiguration",
	"s3:GetBucketRequestPayment",
	"s3:GetBucketLogging",
	"s3:GetLifecycleConfiguration",
	"s3:GetBucketReplication",
	"s3:GetReplicationConfiguration",
	"s3:GetBucketLocation",
	"s3:GetBucketTagging",
	"s3:DeleteBucket",
	"s3:PutBucketAcl",
	"s3:PutBucketTagging",
	"s3:PutEncryptionConfiguration",

	// More S3 (would be nice to limit 'Resource' to just the bucket we actualy interact with...)
	"s3:PutObject",
	"s3:PutObjectAcl",
	"s3:PutObjectTagging",
	"s3:GetObject",
	"s3:GetObjectAcl",
	"s3:GetObjectTagging",
	"s3:GetObjectVersion",
	"s3:DeleteObject",

	// Uninstall-specific perms
	"autoscaling:DescribeAutoScalingGroups",
	"ec2:DeleteNetworkInterface",
	"ec2:DeleteVolume",
	"ec2:DeleteVpcEndpoints",
	"elasticloadbalancing:DescribeTargetGroups",
	"elasticloadbalancing:DeleteTargetGroup",
	"iam:ListInstanceProfiles",
	"iam:ListRolePolicies",
	"iam:ListUserPolicies",
	"tag:GetResources",
}

// ValidateCreds will try to create an AWS session, and also verify that the current credentials
// are sufficient to perform an installation, and that they can be used for cluster runtime
// as either capable of creating new credentials for components that interact with the cloud or
// being able to be passed through as-is to the components that need cloud credentials
func ValidateCreds(ssn *session.Session) error {
	creds, err := ssn.Config.Credentials.Get()
	if err != nil {
		return errors.Wrap(err, "getting creds from session")
	}

	client, err := ccaws.NewClient([]byte(creds.AccessKeyID), []byte(creds.SecretAccessKey))
	if err != nil {
		return errors.Wrap(err, "initialize cloud-credentials client")
	}

	// Check whether we can do an installation
	logger := logrus.StandardLogger()
	canInstall, err := credvalidator.CheckPermissionsAgainstActions(client, installPermissions, logger)
	if err != nil {
		return errors.Wrap(err, "checking install permissions")
	}
	if !canInstall {
		return errors.New("current credentials insufficient for performing cluster installation")
	}

	// Check whether we can mint new creds for cluster services needing to interact with the cloud
	canMint, err := credvalidator.CheckCloudCredCreation(client, logger)
	if err != nil {
		return errors.Wrap(err, "mint credentials check")
	}
	if canMint {
		return nil
	}

	// Check whether we can use the current credentials in passthrough mode to satisfy
	// cluster services needing to interact with the cloud
	canPassthrough, err := credvalidator.CheckCloudCredPassthrough(client, logger)
	if err != nil {
		return errors.Wrap(err, "passthrough credentials check")
	}
	if canPassthrough {
		return nil
	}

	return errors.New("AWS credentials cannot be used to either create new creds or use as-is")
}
<|endoftext|>


<commit_before>package slinga

import (
	"bytes"
	"github.com/awalterschulze/gographviz"
	"github.com/golang/glog"
	"io/ioutil"
	"os/exec"
	"strconv"
	"strings"
)

// See http://www.graphviz.org/doc/info/colors.html
const colorScheme = "set19"
const colorCount = 9

// Returns name of the file where visual is stored
func (usage ServiceUsageState) GetVisualFileNamePNG() string {
	return GetAptomiDBDir() + "/" + "graph.png"
}

// Stores usage state visual into a file
func (usage ServiceUsageState) DrawVisualAndStore() {

	// Write graph into a file
	graph := gographviz.NewEscape()
	graph.SetName("Main")
	graph.AddAttr("Main", "compound", "true")
	graph.SetDir(true)

	was := make(map[string]bool)

	// Add box/subgraph for users
	addSubgraphOnce(graph, "Main", "cluster_Users", map[string]string{"label": "Users"}, was)

	// Add box/subgraph for services
	addSubgraphOnce(graph, "Main", "cluster_Services", map[string]string{"label": "Services"}, was)

	// How many colors have been used
	usedColors := 0
	colorForUser := make(map[string]int)

	// First of all, let's show all dependencies (who requested what)
	for service, userIds := range usage.Dependencies.Dependencies {
		// Add a node with service
		addNodeOnce(graph, "cluster_Services", service, nil, was)

		// For every user who has a dependency on this service
		for _, userId := range userIds {
			color := getUserColor(userId, colorForUser, &usedColors)

			// Add a node with user
			addNodeOnce(graph, "cluster_Users", userId, map[string]string{"style": "filled", "fillcolor": "/" + colorScheme + "/" + strconv.Itoa(color)}, was)

			// Add an edge from user to a service
			addEdge(graph, userId, service, map[string]string{"color": "/" + colorScheme + "/" + strconv.Itoa(color)})
		}
	}

	// Second, visualize evaluated links
	for key, linkStruct := range usage.ResolvedLinks {
		keyArray := strings.Split(key, "#")
		service := keyArray[0]
		contextAndAllocation := keyArray[1] + "#" + keyArray[2]
		component := keyArray[3]

		componentKey := service + "_" + contextAndAllocation + "_" + component
		componentLabel := service + "_" + component

		// Add box/subgraph for a given context/allocation
		addSubgraphOnce(graph, "Main", "cluster_"+contextAndAllocation, map[string]string{"label": "Context/Allocation: " + contextAndAllocation}, was)

		// Add a node with service
		addNodeOnce(graph, "cluster_Services", service, nil, was)

		// Add a node with component
		addNodeOnce(graph, "cluster_"+contextAndAllocation, componentKey, map[string]string{"label": componentLabel}, was)

		// Add an edge from service to allocation box
		for _, userId := range linkStruct.UserIds {
			color := getUserColor(userId, colorForUser, &usedColors)
			addEdge(graph, service, componentKey, map[string]string{"color": "/" + colorScheme + "/" + strconv.Itoa(color)})
		}
	}

	// Third, show cross-service dependencies
	for serviceName1, service1 := range usage.Policy.Services {
		// Resolve every component
		for _, component := range service1.Components {
			serviceName2 := component.Service
			if serviceName2 != "" {
				// Add a node with service1
				addNodeOnce(graph, "cluster_Services", serviceName1, nil, was)

				// Add a node with service2
				addNodeOnce(graph, "cluster_Services", serviceName2, nil, was)

				// Show dependency
				addEdge(graph, serviceName1, serviceName2, map[string]string{"color": "gray60"})
			}
		}
	}

	fileNameDot := GetAptomiDBDir() + "/" + "graph_full.dot"
	fileNameDotFlat := GetAptomiDBDir() + "/" + "graph_flat.dot"
	err := ioutil.WriteFile(fileNameDot, []byte(graph.String()), 0644)
	if err != nil {
		glog.Fatalf("Unable to write to a file: %s", fileNameDot)
	}

	// Call graphviz to flatten an image
	{
		cmd := "unflatten"
		args := []string{"-f", "-l", "4", "-o" + fileNameDotFlat, fileNameDot}
		command := exec.Command(cmd, args...)
		var outb, errb bytes.Buffer
		command.Stdout = &outb
		command.Stderr = &errb
		if err := command.Run(); err != nil {
			glog.Fatalf("Unable to execute graphviz (%s): %s %s", cmd, outb.String(), errb.String(), err)
		}
	}

	// Call graphviz to generate an image
	{
		// -Kfdp will call a different engine
		cmd := "dot"
		args := []string{"-Tpng", "-o" + usage.GetVisualFileNamePNG(), fileNameDotFlat}
		command := exec.Command(cmd, args...)
		var outb, errb bytes.Buffer
		command.Stdout = &outb
		command.Stderr = &errb
		if err := command.Run(); err != nil {
			glog.Fatalf("Unable to execute graphviz (%s): %s %s", cmd, outb.String(), errb.String(), err)
		}
	}
}

func getUserColor(userId string, colorForUser map[string]int, usedColors *int) int {
	color, ok := colorForUser[userId]
	if !ok {
		*usedColors++
		if *usedColors > colorCount {
			*usedColors = 1
		}
		color = *usedColors
		colorForUser[userId] = color
	}
	return color
}

// Adds an edge if it doesn't exist already
func addEdge(g *gographviz.Escape, src string, dst string, attrs map[string]string) {
	g.AddEdge(src, dst, true, attrs)
}

// Adds a subgraph if it doesn't exist already
func addSubgraphOnce(g *gographviz.Escape, parentGraph string, name string, attrs map[string]string, was map[string]bool) {
	wasKey := "SUBGRAPH" + "#" + parentGraph + "#" + name
	if !was[wasKey] {
		g.AddSubGraph(parentGraph, name, attrs)
		was[wasKey] = true
	}
}

// Adds a node if it doesn't exist already
func addNodeOnce(g *gographviz.Escape, parentGraph string, name string, attrs map[string]string, was map[string]bool) {
	wasKey := "NODE" + "#" + parentGraph + "#" + name
	if !was[wasKey] {
		g.AddNode(parentGraph, name, attrs)
		was[wasKey] = true
	}
}
<commit_msg>show graph - don't crash on first run (when db is empty)<commit_after>package slinga

import (
	"bytes"
	"github.com/awalterschulze/gographviz"
	"github.com/golang/glog"
	"io/ioutil"
	"os/exec"
	"strconv"
	"strings"
)

// See http://www.graphviz.org/doc/info/colors.html
const colorScheme = "set19"
const colorCount = 9

// Returns name of the file where visual is stored
func (usage ServiceUsageState) GetVisualFileNamePNG() string {
	return GetAptomiDBDir() + "/" + "graph.png"
}

// Stores usage state visual into a file
func (usage ServiceUsageState) DrawVisualAndStore() {

	// Write graph into a file
	graph := gographviz.NewEscape()
	graph.SetName("Main")
	graph.AddAttr("Main", "compound", "true")
	graph.SetDir(true)

	was := make(map[string]bool)

	// Add box/subgraph for users
	addSubgraphOnce(graph, "Main", "cluster_Users", map[string]string{"label": "Users"}, was)

	// Add box/subgraph for services
	addSubgraphOnce(graph, "Main", "cluster_Services", map[string]string{"label": "Services"}, was)

	// How many colors have been used
	usedColors := 0
	colorForUser := make(map[string]int)

	// First of all, let's show all dependencies (who requested what)
	if usage.Dependencies != nil {
		for service, userIds := range usage.Dependencies.Dependencies {
			// Add a node with service
			addNodeOnce(graph, "cluster_Services", service, nil, was)

			// For every user who has a dependency on this service
			for _, userId := range userIds {
				color := getUserColor(userId, colorForUser, &usedColors)

				// Add a node with user
				addNodeOnce(graph, "cluster_Users", userId, map[string]string{"style": "filled", "fillcolor": "/" + colorScheme + "/" + strconv.Itoa(color)}, was)

				// Add an edge from user to a service
				addEdge(graph, userId, service, map[string]string{"color": "/" + colorScheme + "/" + strconv.Itoa(color)})
			}
		}
	}

	// Second, visualize evaluated links
	for key, linkStruct := range usage.ResolvedLinks {
		keyArray := strings.Split(key, "#")
		service := keyArray[0]
		contextAndAllocation := keyArray[1] + "#" + keyArray[2]
		component := keyArray[3]

		componentKey := service + "_" + contextAndAllocation + "_" + component
		componentLabel := service + "_" + component

		// Add box/subgraph for a given context/allocation
		addSubgraphOnce(graph, "Main", "cluster_"+contextAndAllocation, map[string]string{"label": "Context/Allocation: " + contextAndAllocation}, was)

		// Add a node with service
		addNodeOnce(graph, "cluster_Services", service, nil, was)

		// Add a node with component
		addNodeOnce(graph, "cluster_"+contextAndAllocation, componentKey, map[string]string{"label": componentLabel}, was)

		// Add an edge from service to allocation box
		for _, userId := range linkStruct.UserIds {
			color := getUserColor(userId, colorForUser, &usedColors)
			addEdge(graph, service, componentKey, map[string]string{"color": "/" + colorScheme + "/" + strconv.Itoa(color)})
		}
	}

	// Third, show cross-service dependencies
	if usage.Policy != nil {
		for serviceName1, service1 := range usage.Policy.Services {
			// Resolve every component
			for _, component := range service1.Components {
				serviceName2 := component.Service
				if serviceName2 != "" {
					// Add a node with service1
					addNodeOnce(graph, "cluster_Services", serviceName1, nil, was)

					// Add a node with service2
					addNodeOnce(graph, "cluster_Services", serviceName2, nil, was)

					// Show dependency
					addEdge(graph, serviceName1, serviceName2, map[string]string{"color": "gray60"})
				}
			}
		}
	} else {
		addNodeOnce(graph, "", "Empty", nil, was)
	}

	fileNameDot := GetAptomiDBDir() + "/" + "graph_full.dot"
	fileNameDotFlat := GetAptomiDBDir() + "/" + "graph_flat.dot"
	err := ioutil.WriteFile(fileNameDot, []byte(graph.String()), 0644)
	if err != nil {
		glog.Fatalf("Unable to write to a file: %s", fileNameDot)
	}

	// Call graphviz to flatten an image
	{
		cmd := "unflatten"
		args := []string{"-f", "-l", "4", "-o" + fileNameDotFlat, fileNameDot}
		command := exec.Command(cmd, args...)
		var outb, errb bytes.Buffer
		command.Stdout = &outb
		command.Stderr = &errb
		if err := command.Run(); err != nil {
			glog.Fatalf("Unable to execute graphviz (%s): %s %s", cmd, outb.String(), errb.String(), err)
		}
	}

	// Call graphviz to generate an image
	{
		// -Kfdp will call a different engine
		cmd := "dot"
		args := []string{"-Tpng", "-o" + usage.GetVisualFileNamePNG(), fileNameDotFlat}
		command := exec.Command(cmd, args...)
		var outb, errb bytes.Buffer
		command.Stdout = &outb
		command.Stderr = &errb
		if err := command.Run(); err != nil {
			glog.Fatalf("Unable to execute graphviz (%s): %s %s", cmd, outb.String(), errb.String(), err)
		}
	}
}

func getUserColor(userId string, colorForUser map[string]int, usedColors *int) int {
	color, ok := colorForUser[userId]
	if !ok {
		*usedColors++
		if *usedColors > colorCount {
			*usedColors = 1
		}
		color = *usedColors
		colorForUser[userId] = color
	}
	return color
}

// Adds an edge if it doesn't exist already
func addEdge(g *gographviz.Escape, src string, dst string, attrs map[string]string) {
	g.AddEdge(src, dst, true, attrs)
}

// Adds a subgraph if it doesn't exist already
func addSubgraphOnce(g *gographviz.Escape, parentGraph string, name string, attrs map[string]string, was map[string]bool) {
	wasKey := "SUBGRAPH" + "#" + parentGraph + "#" + name
	if !was[wasKey] {
		g.AddSubGraph(parentGraph, name, attrs)
		was[wasKey] = true
	}
}

// Adds a node if it doesn't exist already
func addNodeOnce(g *gographviz.Escape, parentGraph string, name string, attrs map[string]string, was map[string]bool) {
	wasKey := "NODE" + "#" + parentGraph + "#" + name
	if !was[wasKey] {
		g.AddNode(parentGraph, name, attrs)
		was[wasKey] = true
	}
}
<|endoftext|>


<commit_before>package beaglebone

import (
	"bufio"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"

	multierror "github.com/hashicorp/go-multierror"
	"gobot.io/x/gobot"
	"gobot.io/x/gobot/drivers/i2c"
	"gobot.io/x/gobot/sysfs"
)

var glob = func(pattern string) (matches []string, err error) {
	return filepath.Glob(pattern)
}

// Adaptor is the gobot.Adaptor representation for the Beaglebone
type Adaptor struct {
	name         string
	kernel       string
	digitalPins  []sysfs.DigitalPin
	pwmPins      map[string]*pwmPin
	i2cBuses     map[int]sysfs.I2cDevice
	usrLed       string
	ocp          string
	analogPath   string
	analogPinMap map[string]string
	slots        string
}

// NewAdaptor returns a new Beaglebone Adaptor
func NewAdaptor() *Adaptor {
	b := &Adaptor{
		name:        gobot.DefaultName("Beaglebone"),
		digitalPins: make([]sysfs.DigitalPin, 120),
		pwmPins:     make(map[string]*pwmPin),
		i2cBuses:    make(map[int]sysfs.I2cDevice),
	}

	b.setSlots()
	return b
}

func (b *Adaptor) setSlots() {
	ocp := "/sys/devices/ocp.*"
	slots := "/sys/devices/bone_capemgr.*"

	b.kernel = getKernel()
	if b.kernel[:1] == "4" {
		ocp = "/sys/devices/platform/ocp/ocp*"
		slots = "/sys/devices/platform/bone_capemgr"
	}

	b.usrLed = "/sys/class/leds/beaglebone:green:"

	g, _ := glob(ocp)
	b.ocp = g[0]

	g, _ = glob(slots)
	b.slots = fmt.Sprintf("%v/slots", g[0])
}

// Name returns the Adaptor name
func (b *Adaptor) Name() string { return b.name }

// SetName sets the Adaptor name
func (b *Adaptor) SetName(n string) { b.name = n }

// Kernel returns the Linux kernel version for the BeagleBone
func (b *Adaptor) Kernel() string { return b.kernel }

// Connect initializes the pwm and analog dts.
func (b *Adaptor) Connect() error {
	// enable analog
	if b.kernel[:1] == "4" {
		if err := ensureSlot(b.slots, "BB-ADC"); err != nil {
			return err
		}

		b.analogPath = "/sys/bus/iio/devices/iio:device0"
		b.analogPinMap = analogPins44
	} else {
		if err := ensureSlot(b.slots, "cape-bone-iio"); err != nil {
			return err
		}

		g, err := glob(fmt.Sprintf("%v/helper.*", b.ocp))
		if err != nil {
			return err
		}
		b.analogPath = g[0]
		b.analogPinMap = analogPins3
	}

	// enable pwm
	if err := ensureSlot(b.slots, "am33xx_pwm"); err != nil {
		return err
	}

	return nil
}

// Finalize releases all i2c devices and exported analog, digital, pwm pins.
func (b *Adaptor) Finalize() (err error) {
	for _, pin := range b.pwmPins {
		if pin != nil {
			if e := pin.release(); e != nil {
				err = multierror.Append(err, e)
			}
		}
	}
	for _, pin := range b.digitalPins {
		if pin != nil {
			if e := pin.Unexport(); e != nil {
				err = multierror.Append(err, e)
			}
		}
	}
	for _, bus := range b.i2cBuses {
		if bus != nil {
			if e := bus.Close(); e != nil {
				err = multierror.Append(err, e)
			}
		}
	}
	return
}

// PwmWrite writes the 0-254 value to the specified pin
func (b *Adaptor) PwmWrite(pin string, val byte) (err error) {
	return b.pwmWrite(pin, val)
}

// ServoWrite writes the 0-180 degree val to the specified pin.
func (b *Adaptor) ServoWrite(pin string, val byte) (err error) {
	i, err := b.pwmPin(pin)
	if err != nil {
		return err
	}
	period := 16666666.0
	duty := (gobot.FromScale(float64(val), 0, 180.0) * 0.115) + 0.05
	return b.pwmPins[i].pwmWrite(strconv.Itoa(int(period)), strconv.Itoa(int(period*duty)))
}

// DigitalRead returns a digital value from specified pin
func (b *Adaptor) DigitalRead(pin string) (val int, err error) {
	sysfsPin, err := b.digitalPin(pin, sysfs.IN)
	if err != nil {
		return
	}
	return sysfsPin.Read()
}

// DigitalWrite writes a digital value to specified pin.
// valid usr pin values are usr0, usr1, usr2 and usr3
func (b *Adaptor) DigitalWrite(pin string, val byte) (err error) {
	if strings.Contains(pin, "usr") {
		fi, err := sysfs.OpenFile(b.usrLed+pin+"/brightness", os.O_WRONLY|os.O_APPEND, 0666)
		defer fi.Close()
		if err != nil {
			return err
		}
		_, err = fi.WriteString(strconv.Itoa(int(val)))
		return err
	}
	sysfsPin, err := b.digitalPin(pin, sysfs.OUT)
	if err != nil {
		return err
	}
	return sysfsPin.Write(int(val))
}

// AnalogRead returns an analog value from specified pin
func (b *Adaptor) AnalogRead(pin string) (val int, err error) {
	analogPin, err := b.translateAnalogPin(pin)
	if err != nil {
		return
	}
	fi, err := sysfs.OpenFile(fmt.Sprintf("%v/%v", b.analogPath, analogPin), os.O_RDONLY, 0644)
	defer fi.Close()

	if err != nil {
		return
	}

	var buf = make([]byte, 1024)
	_, err = fi.Read(buf)
	if err != nil {
		return
	}

	val, _ = strconv.Atoi(strings.Split(string(buf), "\n")[0])
	return
}

// GetConnection returns a connection to a device on a specified bus.
// Valid bus number is either 0 or 2 which corresponds to /dev/i2c-0 or /dev/i2c-2.
func (b *Adaptor) GetConnection(address int, bus int) (connection i2c.Connection, err error) {
	if (bus != 0) && (bus != 2) {
		return nil, fmt.Errorf("Bus number %d out of range", bus)
	}
	if b.i2cBuses[bus] == nil {
		b.i2cBuses[bus], err = sysfs.NewI2cDevice(fmt.Sprintf("/dev/i2c-%d", bus))
	}
	return i2c.NewConnection(b.i2cBuses[bus], address), err
}

// GetDefaultBus returns the default i2c bus for this platform
func (b *Adaptor) GetDefaultBus() int {
	return 2
}

// translatePin converts digital pin name to pin position
func (b *Adaptor) translatePin(pin string) (value int, err error) {
	for key, value := range pins {
		if key == pin {
			return value, nil
		}
	}
	err = errors.New("Not a valid pin")
	return
}

// translatePwmPin converts pwm pin name to pin position
func (b *Adaptor) translatePwmPin(pin string) (value string, err error) {
	for key, value := range pwmPins {
		if key == pin {
			return value, nil
		}
	}
	err = errors.New("Not a valid pin")
	return
}

// translateAnalogPin converts analog pin name to pin position
func (b *Adaptor) translateAnalogPin(pin string) (value string, err error) {
	for key, value := range b.analogPinMap {
		if key == pin {
			return value, nil
		}
	}
	err = errors.New("Not a valid pin")
	return
}

// digitalPin retrieves digital pin value by name
func (b *Adaptor) digitalPin(pin string, dir string) (sysfsPin sysfs.DigitalPin, err error) {
	i, err := b.translatePin(pin)
	if err != nil {
		return
	}
	if b.digitalPins[i] == nil {
		b.digitalPins[i] = sysfs.NewDigitalPin(i)
		err := b.digitalPins[i].Export()
		if err != nil {
			return nil, err
		}
	}
	if err = b.digitalPins[i].Direction(dir); err != nil {
		return
	}
	return b.digitalPins[i], nil
}

// pwPin retrieves pwm pin value by name
func (b *Adaptor) pwmPin(pin string) (i string, err error) {
	i, err = b.translatePwmPin(pin)
	if err != nil {
		return
	}
	if b.pwmPins[i] == nil {
		err = ensureSlot(b.slots, fmt.Sprintf("bone_pwm_%v", pin))
		if err != nil {
			return
		}
		b.pwmPins[i], err = newPwmPin(i, b.ocp)
		if err != nil {
			return
		}
	}
	return
}

// pwmWrite writes pwm value to specified pin
func (b *Adaptor) pwmWrite(pin string, val byte) (err error) {
	i, err := b.pwmPin(pin)
	if err != nil {
		return
	}
	period := 50000.0
	duty := gobot.FromScale(float64(val), 0, 255.0)
	return b.pwmPins[i].pwmWrite(strconv.Itoa(int(period)), strconv.Itoa(int(period*duty)))
}

func ensureSlot(slots, item string) (err error) {
	fi, err := sysfs.OpenFile(slots, os.O_RDWR|os.O_APPEND, 0666)
	defer fi.Close()
	if err != nil {
		return
	}

	// ensure the slot is not already written into the capemanager
	// (from: https://github.com/mrmorphic/hwio/blob/master/module_bb_pwm.go#L190)
	scanner := bufio.NewScanner(fi)
	for scanner.Scan() {
		line := scanner.Text()
		if strings.Index(line, item) > 0 {
			return
		}
	}

	_, err = fi.WriteString(item)
	if err != nil {
		return err
	}
	fi.Sync()

	scanner = bufio.NewScanner(fi)
	for scanner.Scan() {
		line := scanner.Text()
		if strings.Index(line, item) > 0 {
			return
		}
	}
	return
}

func getKernel() string {
	result, _ := exec.Command("uname", "-r").Output()

	return strings.TrimSpace(string(result))
}
<commit_msg>Revert "Increased PWM frequency"<commit_after>package beaglebone

import (
	"bufio"
	"errors"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"

	multierror "github.com/hashicorp/go-multierror"
	"gobot.io/x/gobot"
	"gobot.io/x/gobot/drivers/i2c"
	"gobot.io/x/gobot/sysfs"
)

var glob = func(pattern string) (matches []string, err error) {
	return filepath.Glob(pattern)
}

// Adaptor is the gobot.Adaptor representation for the Beaglebone
type Adaptor struct {
	name         string
	kernel       string
	digitalPins  []sysfs.DigitalPin
	pwmPins      map[string]*pwmPin
	i2cBuses     map[int]sysfs.I2cDevice
	usrLed       string
	ocp          string
	analogPath   string
	analogPinMap map[string]string
	slots        string
}

// NewAdaptor returns a new Beaglebone Adaptor
func NewAdaptor() *Adaptor {
	b := &Adaptor{
		name:        gobot.DefaultName("Beaglebone"),
		digitalPins: make([]sysfs.DigitalPin, 120),
		pwmPins:     make(map[string]*pwmPin),
		i2cBuses:    make(map[int]sysfs.I2cDevice),
	}

	b.setSlots()
	return b
}

func (b *Adaptor) setSlots() {
	ocp := "/sys/devices/ocp.*"
	slots := "/sys/devices/bone_capemgr.*"

	b.kernel = getKernel()
	if b.kernel[:1] == "4" {
		ocp = "/sys/devices/platform/ocp/ocp*"
		slots = "/sys/devices/platform/bone_capemgr"
	}

	b.usrLed = "/sys/class/leds/beaglebone:green:"

	g, _ := glob(ocp)
	b.ocp = g[0]

	g, _ = glob(slots)
	b.slots = fmt.Sprintf("%v/slots", g[0])
}

// Name returns the Adaptor name
func (b *Adaptor) Name() string { return b.name }

// SetName sets the Adaptor name
func (b *Adaptor) SetName(n string) { b.name = n }

// Kernel returns the Linux kernel version for the BeagleBone
func (b *Adaptor) Kernel() string { return b.kernel }

// Connect initializes the pwm and analog dts.
func (b *Adaptor) Connect() error {
	// enable analog
	if b.kernel[:1] == "4" {
		if err := ensureSlot(b.slots, "BB-ADC"); err != nil {
			return err
		}

		b.analogPath = "/sys/bus/iio/devices/iio:device0"
		b.analogPinMap = analogPins44
	} else {
		if err := ensureSlot(b.slots, "cape-bone-iio"); err != nil {
			return err
		}

		g, err := glob(fmt.Sprintf("%v/helper.*", b.ocp))
		if err != nil {
			return err
		}
		b.analogPath = g[0]
		b.analogPinMap = analogPins3
	}

	// enable pwm
	if err := ensureSlot(b.slots, "am33xx_pwm"); err != nil {
		return err
	}

	return nil
}

// Finalize releases all i2c devices and exported analog, digital, pwm pins.
func (b *Adaptor) Finalize() (err error) {
	for _, pin := range b.pwmPins {
		if pin != nil {
			if e := pin.release(); e != nil {
				err = multierror.Append(err, e)
			}
		}
	}
	for _, pin := range b.digitalPins {
		if pin != nil {
			if e := pin.Unexport(); e != nil {
				err = multierror.Append(err, e)
			}
		}
	}
	for _, bus := range b.i2cBuses {
		if bus != nil {
			if e := bus.Close(); e != nil {
				err = multierror.Append(err, e)
			}
		}
	}
	return
}

// PwmWrite writes the 0-254 value to the specified pin
func (b *Adaptor) PwmWrite(pin string, val byte) (err error) {
	return b.pwmWrite(pin, val)
}

// ServoWrite writes the 0-180 degree val to the specified pin.
func (b *Adaptor) ServoWrite(pin string, val byte) (err error) {
	i, err := b.pwmPin(pin)
	if err != nil {
		return err
	}
	period := 16666666.0
	duty := (gobot.FromScale(float64(val), 0, 180.0) * 0.115) + 0.05
	return b.pwmPins[i].pwmWrite(strconv.Itoa(int(period)), strconv.Itoa(int(period*duty)))
}

// DigitalRead returns a digital value from specified pin
func (b *Adaptor) DigitalRead(pin string) (val int, err error) {
	sysfsPin, err := b.digitalPin(pin, sysfs.IN)
	if err != nil {
		return
	}
	return sysfsPin.Read()
}

// DigitalWrite writes a digital value to specified pin.
// valid usr pin values are usr0, usr1, usr2 and usr3
func (b *Adaptor) DigitalWrite(pin string, val byte) (err error) {
	if strings.Contains(pin, "usr") {
		fi, err := sysfs.OpenFile(b.usrLed+pin+"/brightness", os.O_WRONLY|os.O_APPEND, 0666)
		defer fi.Close()
		if err != nil {
			return err
		}
		_, err = fi.WriteString(strconv.Itoa(int(val)))
		return err
	}
	sysfsPin, err := b.digitalPin(pin, sysfs.OUT)
	if err != nil {
		return err
	}
	return sysfsPin.Write(int(val))
}

// AnalogRead returns an analog value from specified pin
func (b *Adaptor) AnalogRead(pin string) (val int, err error) {
	analogPin, err := b.translateAnalogPin(pin)
	if err != nil {
		return
	}
	fi, err := sysfs.OpenFile(fmt.Sprintf("%v/%v", b.analogPath, analogPin), os.O_RDONLY, 0644)
	defer fi.Close()

	if err != nil {
		return
	}

	var buf = make([]byte, 1024)
	_, err = fi.Read(buf)
	if err != nil {
		return
	}

	val, _ = strconv.Atoi(strings.Split(string(buf), "\n")[0])
	return
}

// GetConnection returns a connection to a device on a specified bus.
// Valid bus number is either 0 or 2 which corresponds to /dev/i2c-0 or /dev/i2c-2.
func (b *Adaptor) GetConnection(address int, bus int) (connection i2c.Connection, err error) {
	if (bus != 0) && (bus != 2) {
		return nil, fmt.Errorf("Bus number %d out of range", bus)
	}
	if b.i2cBuses[bus] == nil {
		b.i2cBuses[bus], err = sysfs.NewI2cDevice(fmt.Sprintf("/dev/i2c-%d", bus))
	}
	return i2c.NewConnection(b.i2cBuses[bus], address), err
}

// GetDefaultBus returns the default i2c bus for this platform
func (b *Adaptor) GetDefaultBus() int {
	return 2
}

// translatePin converts digital pin name to pin position
func (b *Adaptor) translatePin(pin string) (value int, err error) {
	for key, value := range pins {
		if key == pin {
			return value, nil
		}
	}
	err = errors.New("Not a valid pin")
	return
}

// translatePwmPin converts pwm pin name to pin position
func (b *Adaptor) translatePwmPin(pin string) (value string, err error) {
	for key, value := range pwmPins {
		if key == pin {
			return value, nil
		}
	}
	err = errors.New("Not a valid pin")
	return
}

// translateAnalogPin converts analog pin name to pin position
func (b *Adaptor) translateAnalogPin(pin string) (value string, err error) {
	for key, value := range b.analogPinMap {
		if key == pin {
			return value, nil
		}
	}
	err = errors.New("Not a valid pin")
	return
}

// digitalPin retrieves digital pin value by name
func (b *Adaptor) digitalPin(pin string, dir string) (sysfsPin sysfs.DigitalPin, err error) {
	i, err := b.translatePin(pin)
	if err != nil {
		return
	}
	if b.digitalPins[i] == nil {
		b.digitalPins[i] = sysfs.NewDigitalPin(i)
		err := b.digitalPins[i].Export()
		if err != nil {
			return nil, err
		}
	}
	if err = b.digitalPins[i].Direction(dir); err != nil {
		return
	}
	return b.digitalPins[i], nil
}

// pwPin retrieves pwm pin value by name
func (b *Adaptor) pwmPin(pin string) (i string, err error) {
	i, err = b.translatePwmPin(pin)
	if err != nil {
		return
	}
	if b.pwmPins[i] == nil {
		err = ensureSlot(b.slots, fmt.Sprintf("bone_pwm_%v", pin))
		if err != nil {
			return
		}
		b.pwmPins[i], err = newPwmPin(i, b.ocp)
		if err != nil {
			return
		}
	}
	return
}

// pwmWrite writes pwm value to specified pin
func (b *Adaptor) pwmWrite(pin string, val byte) (err error) {
	i, err := b.pwmPin(pin)
	if err != nil {
		return
	}
	period := 500000.0
	duty := gobot.FromScale(float64(val), 0, 255.0)
	return b.pwmPins[i].pwmWrite(strconv.Itoa(int(period)), strconv.Itoa(int(period*duty)))
}

func ensureSlot(slots, item string) (err error) {
	fi, err := sysfs.OpenFile(slots, os.O_RDWR|os.O_APPEND, 0666)
	defer fi.Close()
	if err != nil {
		return
	}

	// ensure the slot is not already written into the capemanager
	// (from: https://github.com/mrmorphic/hwio/blob/master/module_bb_pwm.go#L190)
	scanner := bufio.NewScanner(fi)
	for scanner.Scan() {
		line := scanner.Text()
		if strings.Index(line, item) > 0 {
			return
		}
	}

	_, err = fi.WriteString(item)
	if err != nil {
		return err
	}
	fi.Sync()

	scanner = bufio.NewScanner(fi)
	for scanner.Scan() {
		line := scanner.Text()
		if strings.Index(line, item) > 0 {
			return
		}
	}
	return
}

func getKernel() string {
	result, _ := exec.Command("uname", "-r").Output()

	return strings.TrimSpace(string(result))
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"path"
	"testing"

	"github.com/cdipaolo/sentiment"
)

const (
	URL      = "127.0.0.1:8080"
	Protocol = "http://"
)

func init() {
	go main()
}

// post takes a path and a json to post, performs a
// POST request, and returns the status, the body,
// and any errors
func post(pth string, json string) (int, []byte, error) {
	resp, err := http.Post(Protocol+path.Join(URL, pth), "application/json", bytes.NewBuffer([]byte(json)))
	if err != nil {
		return 0, nil, err
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return 0, nil, err
	}

	return resp.StatusCode, body, nil
}

func TestSentimentShouldPass1(t *testing.T) {
	text := `The anti-immigration people have to invent some explanation to account for all the effort technology companies have expended trying to make immigration easier. So they claim it's because they want to drive down salaries. But if you talk to startups, you find practically every one over a certain size has gone through legal contortions to get programmers into the US, where they then paid them the same as they'd have paid an American. Why would they go to extra trouble to get programmers for the same price? The only explanation is that they're telling the truth: there are just not enough great programmers to go around`
	txt := fmt.Sprintf(`{
		"text": "%v"
	}`, text)

	status, body, err := post("analyze", txt)
	if err != nil {
		t.Errorf("ERROR: error trying to post\n\t%v\n", err)
	}
	if status != http.StatusOK {
		t.Errorf("ERROR: status returned should be 200 OK\n\t%v\n", string(body))
	}
	if len(body) == 0 {
		t.Fatalf("ERROR: body should not be nil!\n")
	}

	analysis := sentiment.Analysis{}
	err = json.Unmarshal(body, &analysis)
	if err != nil {
		t.Fatalf("ERROR: error unmarshalling JSON response\n\t%v\n", err)
	}

	should := model.SentimentAnalysis(text)
	if should.Score != analysis.Score {
		t.Errorf("ERROR: responded text sentiment score should equal the same score from the library!\n\tShould be: %v\n\tReturned: %v\n", should.Score, analysis.Score)
	}
	if len(should.Words) != len(analysis.Words) {
		t.Errorf("ERROR: responded individual word sentiment should equal in length the same response from the library!\n\tShould be: %v\n\tReturned: %v\n", should.Words, analysis.Words)
	}
}
<commit_msg>Added fail testing for POST /analyze<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"path"
	"testing"

	"github.com/cdipaolo/sentiment"
)

const (
	URL      = "127.0.0.1:8080"
	Protocol = "http://"
)

func init() {
	go main()
}

// post takes a path and a json to post, performs a
// POST request, and returns the status, the body,
// and any errors
func post(pth string, json string) (int, []byte, error) {
	resp, err := http.Post(Protocol+path.Join(URL, pth), "application/json", bytes.NewBuffer([]byte(json)))
	if err != nil {
		return 0, nil, err
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return 0, nil, err
	}

	return resp.StatusCode, body, nil
}

func TestSentimentShouldPass1(t *testing.T) {
	text := `The anti-immigration people have to invent some explanation to account for all the effort technology companies have expended trying to make immigration easier. So they claim it's because they want to drive down salaries. But if you talk to startups, you find practically every one over a certain size has gone through legal contortions to get programmers into the US, where they then paid them the same as they'd have paid an American. Why would they go to extra trouble to get programmers for the same price? The only explanation is that they're telling the truth: there are just not enough great programmers to go around`
	txt := fmt.Sprintf(`{
		"text": "%v"
	}`, text)

	status, body, err := post("analyze", txt)
	if err != nil {
		t.Errorf("ERROR: error trying to post\n\t%v\n", err)
	}
	if status != http.StatusOK {
		t.Errorf("ERROR: status returned should be 200 OK\n\t%v\n", string(body))
	}
	if len(body) == 0 {
		t.Fatalf("ERROR: body should not be nil!\n")
	}

	analysis := sentiment.Analysis{}
	err = json.Unmarshal(body, &analysis)
	if err != nil {
		t.Fatalf("ERROR: error unmarshalling JSON response\n\t%v\n", err)
	}

	should := model.SentimentAnalysis(text)
	if should.Score != analysis.Score {
		t.Errorf("ERROR: responded text sentiment score should equal the same score from the library!\n\tShould be: %v\n\tReturned: %v\n", should.Score, analysis.Score)
	}
	if len(should.Words) != len(analysis.Words) {
		t.Errorf("ERROR: responded individual word sentiment should equal in length the same response from the library!\n\tShould be: %v\n\tReturned: %v\n", should.Words, analysis.Words)
	}
}

func TestSentimentShouldFail1(t *testing.T) {
	text := `The anti-immigration people have to invent some explanation to account for all the effort technology companies have expended trying to make immigration easier. So they claim it's because they want to drive down salaries. But if you talk to startups, you find practically every one over a certain size has gone through legal contortions to get programmers into the US, where they then paid them the same as they'd have paid an American. Why would they go to extra trouble to get programmers for the same price? The only explanation is that they're telling the truth: there are just not enough great programmers to go around`
	txt := fmt.Sprintf(`{
		"text": "%v
	}`, text)

	status, body, err := post("analyze", txt)
	if err != nil {
		t.Errorf("ERROR: error trying to post\n\t%v\n", err)
	}
	if status != http.StatusBadRequest {
		t.Errorf("ERROR: status returned should be 400 BAD REQUEST\n\t%v\n", string(body))
	}
	if len(body) == 0 {
		t.Fatalf("ERROR: body should not be nil!\n")
	}
}

func TestSentimentShouldFail2(t *testing.T) {
	txt := ``

	status, body, err := post("analyze", txt)
	if err != nil {
		t.Errorf("ERROR: error trying to post\n\t%v\n", err)
	}
	if status != http.StatusBadRequest {
		t.Errorf("ERROR: status returned should be 400 BAD REQUEST\n\t%v\n", string(body))
	}
	if len(body) == 0 {
		t.Fatalf("ERROR: body should not be nil!\n")
	}
}
<|endoftext|>


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

package serial

/*

// MSDN article on Serial Communications:
// http://msdn.microsoft.com/en-us/library/ff802693.aspx

// Arduino Playground article on serial communication with Windows API:
// http://playground.arduino.cc/Interfacing/CPPWindows

#include <stdlib.h>
#include <windows.h>

//HANDLE invalid = INVALID_HANDLE_VALUE;

HKEY INVALID_PORT_LIST = 0;

HKEY openPortList() {
	HKEY handle;
	LPCSTR lpSubKey = "HARDWARE\\DEVICEMAP\\SERIALCOMM\\";
	DWORD res = RegOpenKeyExA(HKEY_LOCAL_MACHINE, lpSubKey, 0, KEY_READ, &handle);
	if (res != ERROR_SUCCESS)
		return INVALID_PORT_LIST;
	else
		return handle;
}

int countPortList(HKEY handle) {
	int count = 0;
	for (;;) {
		char name[256];
		DWORD nameSize = 256;
		DWORD res = RegEnumValueA(handle, count, name, &nameSize, NULL, NULL, NULL, NULL);
		if (res != ERROR_SUCCESS)
			return count;
		count++;
	}
}

char *getInPortList(HKEY handle, int i) {
	byte *data = (byte *) malloc(256);
	DWORD dataSize = 256;
	char name[256];
	DWORD nameSize = 256;
	DWORD res = RegEnumValueA(handle, i, name, &nameSize, NULL, NULL, data, &dataSize);
	if (res != ERROR_SUCCESS) {
		free(data);
		return NULL;
	}
	return data;
}

void closePortList(HKEY handle) {
	CloseHandle(handle);
}

*/
import "C"
import "syscall"
import "unsafe"

// opaque type that implements SerialPort interface for Windows
type windowsSerialPort struct {
	Handle syscall.Handle
}

func GetPortsList() ([]string, error) {
	portList := C.openPortList()
	if portList == C.INVALID_PORT_LIST {
		return nil, &SerialPortError{code: ERROR_ENUMERATING_PORTS}
	}
	n := C.countPortList(portList)

	list := make([]string, n)
	for i := range list {
		portName := C.getInPortList(portList, C.int(i))
		list[i] = C.GoString(portName)
		C.free(unsafe.Pointer(portName))
	}

	C.closePortList(portList)
	return list, nil
}

func (port windowsSerialPort) Close() error {
	return syscall.CloseHandle(port.Handle)
}

func (port windowsSerialPort) Read(p []byte) (int, error) {
	var readed uint32
	params := &DCB{}
	for {
		if err := syscall.ReadFile(port.Handle, p, &readed, nil); err != nil {
			return int(readed), err
		}
		if readed > 0 {
			return int(readed), nil
		}

		// At the moment it seems that the only reliable way to check if
		// a serial port is alive in Windows is to check if the SetCommState
		// function fails.

		GetCommState(port.Handle, params)
		if err := SetCommState(port.Handle, params); err != nil {
			port.Close()
			return 0, err
		}
	}
}

func (port windowsSerialPort) Write(p []byte) (int, error) {
	var writed uint32
	err := syscall.WriteFile(port.Handle, p, &writed, nil)
	return int(writed), err
}

const (
	DCB_BINARY                   = 0x00000001
	DCB_PARITY                   = 0x00000002
	DCB_OUT_X_CTS_FLOW           = 0x00000004
	DCB_OUT_X_DSR_FLOW           = 0x00000008
	DCB_DTR_CONTROL_DISABLE_MASK = ^0x00000030
	DCB_DTR_CONTROL_ENABLE       = 0x00000010
	DCB_DTR_CONTROL_HANDSHAKE    = 0x00000020
	DCB_DSR_SENSITIVITY          = 0x00000040
	DCB_TX_CONTINUE_ON_XOFF      = 0x00000080
	DCB_OUT_X                    = 0x00000100
	DCB_IN_X                     = 0x00000200
	DCB_ERROR_CHAR               = 0x00000400
	DCB_NULL                     = 0x00000800
	DCB_RTS_CONTROL_DISABLE_MASK = ^0x00003000
	DCB_RTS_CONTROL_ENABLE       = 0x00001000
	DCB_RTS_CONTROL_HANDSHAKE    = 0x00002000
	DCB_RTS_CONTROL_TOGGLE       = 0x00003000
	DCB_ABORT_ON_ERROR           = 0x00004000
)

type DCB struct {
	DCBlength uint32
	BaudRate  uint32

	// Flags field is a bitfield
	//  fBinary            :1
	//  fParity            :1
	//  fOutxCtsFlow       :1
	//  fOutxDsrFlow       :1
	//  fDtrControl        :2
	//  fDsrSensitivity    :1
	//  fTXContinueOnXoff  :1
	//  fOutX              :1
	//  fInX               :1
	//  fErrorChar         :1
	//  fNull              :1
	//  fRtsControl        :2
	//  fAbortOnError      :1
	//  fDummy2            :17
	Flags uint32

	wReserved  uint16
	XonLim     uint16
	XoffLim    uint16
	ByteSize   byte
	Parity     byte
	StopBits   byte
	XonChar    byte
	XoffChar   byte
	ErrorChar  byte
	EofChar    byte
	EvtChar    byte
	wReserved1 uint16
}

type COMMTIMEOUTS struct {
	ReadIntervalTimeout         uint32
	ReadTotalTimeoutMultiplier  uint32
	ReadTotalTimeoutConstant    uint32
	WriteTotalTimeoutMultiplier uint32
	WriteTotalTimeoutConstant   uint32
}

//sys GetCommState(handle syscall.Handle, dcb *DCB) (err error)
//sys SetCommState(handle syscall.Handle, dcb *DCB) (err error)
//sys SetCommTimeouts(handle syscall.Handle, timeouts *COMMTIMEOUTS) (err error)

const (
	NOPARITY    = 0
	ODDPARITY   = 1
	EVENPARITY  = 2
	MARKPARITY  = 3
	SPACEPARITY = 4
)

const (
	ONESTOPBIT   = 0
	ONE5STOPBITS = 1
	TWOSTOPBITS  = 2
)

func (port windowsSerialPort) SetSpeed(baudrate int) error {
	params := DCB{}
	if err := GetCommState(port.Handle, &params); err != nil {
		port.Close()
		return &SerialPortError{code: ERROR_INVALID_SERIAL_PORT}
	}
	params.BaudRate = uint32(baudrate)
	params.ByteSize = 8
	params.StopBits = ONESTOPBIT
	params.Parity = NOPARITY
	if err := SetCommState(port.Handle, &params); err != nil {
		port.Close()
		return &SerialPortError{code: ERROR_INVALID_SERIAL_PORT}
	}
	return nil
}

func OpenPort(portName string, useTIOCEXCL bool) (SerialPort, error) {
	portName = "\\\\.\\" + portName
	path, err := syscall.UTF16PtrFromString(portName)
	if err != nil {
		return nil, err
	}
	handle, err := syscall.CreateFile(
		path,
		syscall.GENERIC_READ|syscall.GENERIC_WRITE,
		0, nil,
		syscall.OPEN_EXISTING,
		0, //syscall.FILE_FLAG_OVERLAPPED,
		0)
	if err != nil {
		switch err {
		case syscall.ERROR_ACCESS_DENIED:
			return nil, &SerialPortError{code: ERROR_PORT_BUSY}
		case syscall.ERROR_FILE_NOT_FOUND:
			return nil, &SerialPortError{code: ERROR_PORT_NOT_FOUND}
		}
		return nil, err
	}
	// Create the serial port
	port := &windowsSerialPort{
		Handle: handle,
	}

	params := &DCB{}
	err = GetCommState(port.Handle, params)
	if err != nil {
		port.Close()
		return nil, &SerialPortError{code: ERROR_INVALID_SERIAL_PORT}
	}
	params.Flags |= DCB_RTS_CONTROL_ENABLE | DCB_DTR_CONTROL_ENABLE
	params.Flags &= ^uint32(DCB_OUT_X_CTS_FLOW)
	params.Flags &= ^uint32(DCB_OUT_X_DSR_FLOW)
	params.Flags &= ^uint32(DCB_DSR_SENSITIVITY)
	params.Flags |= DCB_TX_CONTINUE_ON_XOFF
	params.Flags &= ^uint32(DCB_IN_X | DCB_OUT_X)
	params.Flags &= ^uint32(DCB_ERROR_CHAR)
	params.Flags &= ^uint32(DCB_NULL)
	params.Flags &= ^uint32(DCB_ABORT_ON_ERROR)
	params.XonLim = 2048
	params.XoffLim = 512
	params.XonChar = 17  // DC1
	params.XoffChar = 19 // C3
	err = SetCommState(port.Handle, params)
	if err != nil {
		port.Close()
		return nil, &SerialPortError{code: ERROR_INVALID_SERIAL_PORT}
	}

	timeouts := &COMMTIMEOUTS{
		ReadIntervalTimeout:         0xFFFFFFFF,
		ReadTotalTimeoutMultiplier:  0xFFFFFFFF,
		ReadTotalTimeoutConstant:    1000, // 1 sec
		WriteTotalTimeoutConstant:   0,
		WriteTotalTimeoutMultiplier: 0,
	}
	if err := SetCommTimeouts(port.Handle, timeouts); err != nil {
		port.Close()
		return nil, &SerialPortError{code: ERROR_INVALID_SERIAL_PORT}
	}

	return port, nil
}

// vi:ts=2
<commit_msg>Updated API on windows implementation<commit_after>//
// Copyright 2014 Cristian Maglie. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//

package serial

/*

// MSDN article on Serial Communications:
// http://msdn.microsoft.com/en-us/library/ff802693.aspx

// Arduino Playground article on serial communication with Windows API:
// http://playground.arduino.cc/Interfacing/CPPWindows

#include <stdlib.h>
#include <windows.h>

//HANDLE invalid = INVALID_HANDLE_VALUE;

HKEY INVALID_PORT_LIST = 0;

HKEY openPortList() {
	HKEY handle;
	LPCSTR lpSubKey = "HARDWARE\\DEVICEMAP\\SERIALCOMM\\";
	DWORD res = RegOpenKeyExA(HKEY_LOCAL_MACHINE, lpSubKey, 0, KEY_READ, &handle);
	if (res != ERROR_SUCCESS)
		return INVALID_PORT_LIST;
	else
		return handle;
}

int countPortList(HKEY handle) {
	int count = 0;
	for (;;) {
		char name[256];
		DWORD nameSize = 256;
		DWORD res = RegEnumValueA(handle, count, name, &nameSize, NULL, NULL, NULL, NULL);
		if (res != ERROR_SUCCESS)
			return count;
		count++;
	}
}

char *getInPortList(HKEY handle, int i) {
	byte *data = (byte *) malloc(256);
	DWORD dataSize = 256;
	char name[256];
	DWORD nameSize = 256;
	DWORD res = RegEnumValueA(handle, i, name, &nameSize, NULL, NULL, data, &dataSize);
	if (res != ERROR_SUCCESS) {
		free(data);
		return NULL;
	}
	return data;
}

void closePortList(HKEY handle) {
	CloseHandle(handle);
}

*/
import "C"
import "syscall"
import "unsafe"

// opaque type that implements SerialPort interface for Windows
type windowsSerialPort struct {
	Handle syscall.Handle
}

func GetPortsList() ([]string, error) {
	portList := C.openPortList()
	if portList == C.INVALID_PORT_LIST {
		return nil, &SerialPortError{code: ERROR_ENUMERATING_PORTS}
	}
	n := C.countPortList(portList)

	list := make([]string, n)
	for i := range list {
		portName := C.getInPortList(portList, C.int(i))
		list[i] = C.GoString(portName)
		C.free(unsafe.Pointer(portName))
	}

	C.closePortList(portList)
	return list, nil
}

func (port windowsSerialPort) Close() error {
	return syscall.CloseHandle(port.Handle)
}

func (port windowsSerialPort) Read(p []byte) (int, error) {
	var readed uint32
	params := &DCB{}
	for {
		if err := syscall.ReadFile(port.Handle, p, &readed, nil); err != nil {
			return int(readed), err
		}
		if readed > 0 {
			return int(readed), nil
		}

		// At the moment it seems that the only reliable way to check if
		// a serial port is alive in Windows is to check if the SetCommState
		// function fails.

		GetCommState(port.Handle, params)
		if err := SetCommState(port.Handle, params); err != nil {
			port.Close()
			return 0, err
		}
	}
}

func (port windowsSerialPort) Write(p []byte) (int, error) {
	var writed uint32
	err := syscall.WriteFile(port.Handle, p, &writed, nil)
	return int(writed), err
}

const (
	DCB_BINARY                   = 0x00000001
	DCB_PARITY                   = 0x00000002
	DCB_OUT_X_CTS_FLOW           = 0x00000004
	DCB_OUT_X_DSR_FLOW           = 0x00000008
	DCB_DTR_CONTROL_DISABLE_MASK = ^0x00000030
	DCB_DTR_CONTROL_ENABLE       = 0x00000010
	DCB_DTR_CONTROL_HANDSHAKE    = 0x00000020
	DCB_DSR_SENSITIVITY          = 0x00000040
	DCB_TX_CONTINUE_ON_XOFF      = 0x00000080
	DCB_OUT_X                    = 0x00000100
	DCB_IN_X                     = 0x00000200
	DCB_ERROR_CHAR               = 0x00000400
	DCB_NULL                     = 0x00000800
	DCB_RTS_CONTROL_DISABLE_MASK = ^0x00003000
	DCB_RTS_CONTROL_ENABLE       = 0x00001000
	DCB_RTS_CONTROL_HANDSHAKE    = 0x00002000
	DCB_RTS_CONTROL_TOGGLE       = 0x00003000
	DCB_ABORT_ON_ERROR           = 0x00004000
)

type DCB struct {
	DCBlength uint32
	BaudRate  uint32

	// Flags field is a bitfield
	//  fBinary            :1
	//  fParity            :1
	//  fOutxCtsFlow       :1
	//  fOutxDsrFlow       :1
	//  fDtrControl        :2
	//  fDsrSensitivity    :1
	//  fTXContinueOnXoff  :1
	//  fOutX              :1
	//  fInX               :1
	//  fErrorChar         :1
	//  fNull              :1
	//  fRtsControl        :2
	//  fAbortOnError      :1
	//  fDummy2            :17
	Flags uint32

	wReserved  uint16
	XonLim     uint16
	XoffLim    uint16
	ByteSize   byte
	Parity     byte
	StopBits   byte
	XonChar    byte
	XoffChar   byte
	ErrorChar  byte
	EofChar    byte
	EvtChar    byte
	wReserved1 uint16
}

type COMMTIMEOUTS struct {
	ReadIntervalTimeout         uint32
	ReadTotalTimeoutMultiplier  uint32
	ReadTotalTimeoutConstant    uint32
	WriteTotalTimeoutMultiplier uint32
	WriteTotalTimeoutConstant   uint32
}

//sys GetCommState(handle syscall.Handle, dcb *DCB) (err error)
//sys SetCommState(handle syscall.Handle, dcb *DCB) (err error)
//sys SetCommTimeouts(handle syscall.Handle, timeouts *COMMTIMEOUTS) (err error)

const (
	NOPARITY    = 0
	ODDPARITY   = 1
	EVENPARITY  = 2
	MARKPARITY  = 3
	SPACEPARITY = 4
)

const (
	ONESTOPBIT   = 0
	ONE5STOPBITS = 1
	TWOSTOPBITS  = 2
)

func (port windowsSerialPort) SetMode(mode *Mode) error {
	params := DCB{}
	if err := GetCommState(port.Handle, &params); err != nil {
		port.Close()
		return &SerialPortError{code: ERROR_INVALID_SERIAL_PORT}
	}
	if mode.Baudrate == 0 {
		params.BaudRate = 9600 // Default to 9600
	} else {
		params.BaudRate = uint32(mode.BaudRate)
	}
	if mode.DataBits == 0 {
		params.ByteSize = 8 // Default to 8 bits
	} else {
		params.ByteSize = mode.DataBits
	}
	params.StopBits = mode.StopBits
	params.Parity = mode.Parity
	if err := SetCommState(port.Handle, &params); err != nil {
		port.Close()
		return &SerialPortError{code: ERROR_INVALID_SERIAL_PORT}
	}
	return nil
}

func OpenPort(portName string, mode Mode) (SerialPort, error) {
	portName = "\\\\.\\" + portName
	path, err := syscall.UTF16PtrFromString(portName)
	if err != nil {
		return nil, err
	}
	handle, err := syscall.CreateFile(
		path,
		syscall.GENERIC_READ|syscall.GENERIC_WRITE,
		0, nil,
		syscall.OPEN_EXISTING,
		0, //syscall.FILE_FLAG_OVERLAPPED,
		0)
	if err != nil {
		switch err {
		case syscall.ERROR_ACCESS_DENIED:
			return nil, &SerialPortError{code: ERROR_PORT_BUSY}
		case syscall.ERROR_FILE_NOT_FOUND:
			return nil, &SerialPortError{code: ERROR_PORT_NOT_FOUND}
		}
		return nil, err
	}
	// Create the serial port
	port := &windowsSerialPort{
		Handle: handle,
	}

	// Set port parameters
	if err := port.SetMode(mode); err != nil {
		port.Close()
		return nil, &SerialPortError{code: ERROR_INVALID_SERIAL_PORT}
	}

	params := &DCB{}
	err = GetCommState(port.Handle, params)
	if err != nil {
		port.Close()
		return nil, &SerialPortError{code: ERROR_INVALID_SERIAL_PORT}
	}
	params.Flags |= DCB_RTS_CONTROL_ENABLE | DCB_DTR_CONTROL_ENABLE
	params.Flags &= ^uint32(DCB_OUT_X_CTS_FLOW)
	params.Flags &= ^uint32(DCB_OUT_X_DSR_FLOW)
	params.Flags &= ^uint32(DCB_DSR_SENSITIVITY)
	params.Flags |= DCB_TX_CONTINUE_ON_XOFF
	params.Flags &= ^uint32(DCB_IN_X | DCB_OUT_X)
	params.Flags &= ^uint32(DCB_ERROR_CHAR)
	params.Flags &= ^uint32(DCB_NULL)
	params.Flags &= ^uint32(DCB_ABORT_ON_ERROR)
	params.XonLim = 2048
	params.XoffLim = 512
	params.XonChar = 17  // DC1
	params.XoffChar = 19 // C3
	err = SetCommState(port.Handle, params)
	if err != nil {
		port.Close()
		return nil, &SerialPortError{code: ERROR_INVALID_SERIAL_PORT}
	}

	// Set timeouts to 1 second
	timeouts := &COMMTIMEOUTS{
		ReadIntervalTimeout:         0xFFFFFFFF,
		ReadTotalTimeoutMultiplier:  0xFFFFFFFF,
		ReadTotalTimeoutConstant:    1000, // 1 sec
		WriteTotalTimeoutConstant:   0,
		WriteTotalTimeoutMultiplier: 0,
	}
	if err := SetCommTimeouts(port.Handle, timeouts); err != nil {
		port.Close()
		return nil, &SerialPortError{code: ERROR_INVALID_SERIAL_PORT}
	}

	return port, nil
}

// vi:ts=2
<|endoftext|>


<commit_before>package db

import (
	"os"

	"github.com/snickers/snickers/db/memory"
	"github.com/snickers/snickers/db/mongo"
	"github.com/snickers/snickers/types"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Database", func() {
	var (
		dbInstance       Storage
		runDatabaseSuite func()

		preset types.Preset
		job    types.Job
	)

	BeforeEach(func() {
		preset = types.Preset{
			Name:        "examplePreset",
			Description: "This is an example of preset",
			Container:   "mp4",
			RateControl: "vbr",
			Video: types.VideoPreset{
				Width:         "720",
				Height:        "1080",
				Codec:         "h264",
				Bitrate:       "10000",
				GopSize:       "90",
				GopMode:       "fixed",
				Profile:       "high",
				ProfileLevel:  "3.1",
				InterlaceMode: "progressive",
			},
			Audio: types.AudioPreset{
				Codec:   "aac",
				Bitrate: "64000",
			},
		}

		job = types.Job{
			ID:          "123",
			Source:      "http://source.here.mp4",
			Destination: "s3://user@pass:/bucket/destination.mp4",
			Preset:      types.Preset{Name: "presetHere"},
			Status:      types.JobCreated,
			Details:     "0%",
		}
	})

	runDatabaseSuite = func() {
		Describe("StorePreset", func() {
			It("should be able to store a preset", func() {
				res, err := dbInstance.StorePreset(preset)
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal(preset))
			})
		})

		Describe("RetrievePreset", func() {
			JustBeforeEach(func() {
				dbInstance.StorePreset(preset)
			})

			It("should be able to retrieve a preset by its name", func() {
				res, err := dbInstance.RetrievePreset("examplePreset")
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal(preset))
			})

			Context("when the present does not exist", func() {
				It("should return an error", func() {
					_, err := dbInstance.RetrievePreset("invalid-preset")
					Expect(err).To(HaveOccurred())
				})
			})
		})

		Describe("GetPresets", func() {
			var anotherPreset types.Preset

			BeforeEach(func() {
				anotherPreset = types.Preset{
					Name:        "anotherPreset",
					Description: "This is another preset",
				}
			})

			JustBeforeEach(func() {
				dbInstance.StorePreset(preset)
				dbInstance.StorePreset(anotherPreset)
			})

			It("should be able to list presets", func() {
				presets, err := dbInstance.GetPresets()
				Expect(err).NotTo(HaveOccurred())
				Expect(presets).To(ConsistOf(preset, anotherPreset))
			})
		})

		Describe("UpdatePreset", func() {
			JustBeforeEach(func() {
				dbInstance.StorePreset(preset)
			})

			It("should be able to update preset", func() {
				expectedDescription := "New description for this preset"
				preset.Description = expectedDescription
				dbInstance.UpdatePreset(preset.Name, preset)
				res, err := dbInstance.GetPresets()
				Expect(err).NotTo(HaveOccurred())

				Expect(res[0].Description).To(Equal(expectedDescription))
			})

			Context("when the present does not exist", func() {
				It("should return an error", func() {
					_, err := dbInstance.RetrievePreset("invalid-preset")
					Expect(err).To(HaveOccurred())
				})
			})
		})

		Describe("StoreJob", func() {
			It("should be able to store a job", func() {
				res, err := dbInstance.StoreJob(job)
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal(job))
			})
		})

		Describe("RetrieveJob", func() {
			JustBeforeEach(func() {
				dbInstance.StoreJob(job)
			})

			It("should be able to retrieve a job by its name", func() {
				res, err := dbInstance.RetrieveJob("123")
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal(job))
			})

			Context("when the job does not exist", func() {
				It("should return an error", func() {
					_, err := dbInstance.RetrieveJob("invalid-job")
					Expect(err).To(HaveOccurred())
				})
			})
		})

		Describe("GetJobs", func() {
			var anotherJob types.Job

			BeforeEach(func() {
				anotherJob = types.Job{
					ID:          "321",
					Source:      "http://source2.here.mp4",
					Destination: "s3://user@pass:/bucket/destination2.mp4",
					Preset:      types.Preset{Name: "presetHere2"},
					Status:      types.JobCreated,
					Details:     "0%",
				}
			})

			JustBeforeEach(func() {
				dbInstance.StoreJob(job)
				dbInstance.StoreJob(anotherJob)
			})

			It("should be able to list jobs", func() {
				jobs, err := dbInstance.GetJobs()
				Expect(err).NotTo(HaveOccurred())
				Expect(jobs).To(ConsistOf(job, anotherJob))
			})
		})

		Describe("UpdateJob", func() {
			JustBeforeEach(func() {
				dbInstance.StoreJob(job)
			})

			It("should be able to update job", func() {
				expectedStatus := types.JobDownloading
				job.Status = expectedStatus
				dbInstance.UpdateJob(job.ID, job)

				res, err := dbInstance.GetJobs()
				Expect(err).NotTo(HaveOccurred())
				Expect(res[0].Status).To(Equal(expectedStatus))
			})
		})
	}

	Describe("when the storage is in memory", func() {
		BeforeEach(func() {
			dbInstance, _ = memory.GetDatabase()
		})

		AfterEach(func() {
			dbInstance.ClearDatabase()
		})

		runDatabaseSuite()
	})

	Describe("when the storage is mongodb", func() {
		BeforeEach(func() {
			currentDir, _ := os.Getwd()
			dbInstance, _ = mongo.GetDatabase(currentDir + "/../fixtures/config.json")
		})

		AfterEach(func() {
			dbInstance.ClearDatabase()
		})

		runDatabaseSuite()
	})
})
<commit_msg>db: add tests for DeletePreset method<commit_after>package db

import (
	"os"

	"github.com/snickers/snickers/db/memory"
	"github.com/snickers/snickers/db/mongo"
	"github.com/snickers/snickers/types"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Database", func() {
	var (
		dbInstance       Storage
		runDatabaseSuite func()

		preset types.Preset
		job    types.Job
	)

	BeforeEach(func() {
		preset = types.Preset{
			Name:        "examplePreset",
			Description: "This is an example of preset",
			Container:   "mp4",
			RateControl: "vbr",
			Video: types.VideoPreset{
				Width:         "720",
				Height:        "1080",
				Codec:         "h264",
				Bitrate:       "10000",
				GopSize:       "90",
				GopMode:       "fixed",
				Profile:       "high",
				ProfileLevel:  "3.1",
				InterlaceMode: "progressive",
			},
			Audio: types.AudioPreset{
				Codec:   "aac",
				Bitrate: "64000",
			},
		}

		job = types.Job{
			ID:          "123",
			Source:      "http://source.here.mp4",
			Destination: "s3://user@pass:/bucket/destination.mp4",
			Preset:      types.Preset{Name: "presetHere"},
			Status:      types.JobCreated,
			Details:     "0%",
		}
	})

	runDatabaseSuite = func() {
		Describe("StorePreset", func() {
			It("should be able to store a preset", func() {
				res, err := dbInstance.StorePreset(preset)
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal(preset))
			})
		})

		Describe("RetrievePreset", func() {
			JustBeforeEach(func() {
				dbInstance.StorePreset(preset)
			})

			It("should be able to retrieve a preset by its name", func() {
				res, err := dbInstance.RetrievePreset("examplePreset")
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal(preset))
			})

			Context("when the present does not exist", func() {
				It("should return an error", func() {
					_, err := dbInstance.RetrievePreset("invalid-preset")
					Expect(err).To(HaveOccurred())
				})
			})
		})

		Describe("GetPresets", func() {
			var anotherPreset types.Preset

			BeforeEach(func() {
				anotherPreset = types.Preset{
					Name:        "anotherPreset",
					Description: "This is another preset",
				}
			})

			JustBeforeEach(func() {
				dbInstance.StorePreset(preset)
				dbInstance.StorePreset(anotherPreset)
			})

			It("should be able to list presets", func() {
				presets, err := dbInstance.GetPresets()
				Expect(err).NotTo(HaveOccurred())
				Expect(presets).To(ConsistOf(preset, anotherPreset))
			})
		})

		Describe("UpdatePreset", func() {
			JustBeforeEach(func() {
				dbInstance.StorePreset(preset)
			})

			It("should be able to update preset", func() {
				expectedDescription := "New description for this preset"
				preset.Description = expectedDescription
				dbInstance.UpdatePreset(preset.Name, preset)
				res, err := dbInstance.GetPresets()
				Expect(err).NotTo(HaveOccurred())

				Expect(res[0].Description).To(Equal(expectedDescription))
			})

			Context("when the present does not exist", func() {
				It("should return an error", func() {
					_, err := dbInstance.RetrievePreset("invalid-preset")
					Expect(err).To(HaveOccurred())
				})
			})
		})

		Describe("DeletePreset", func() {
			JustBeforeEach(func() {
				dbInstance.StorePreset(preset)
			})

			It("should be able to delete a preset", func() {
				dbInstance.DeletePreset("examplePreset")
				presets, err := dbInstance.GetPresets()
				Expect(err).NotTo(HaveOccurred())
				Expect(presets).Should(BeEmpty())
			})
		})

		Describe("StoreJob", func() {
			It("should be able to store a job", func() {
				res, err := dbInstance.StoreJob(job)
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal(job))
			})
		})

		Describe("RetrieveJob", func() {
			JustBeforeEach(func() {
				dbInstance.StoreJob(job)
			})

			It("should be able to retrieve a job by its name", func() {
				res, err := dbInstance.RetrieveJob("123")
				Expect(err).NotTo(HaveOccurred())
				Expect(res).To(Equal(job))
			})

			Context("when the job does not exist", func() {
				It("should return an error", func() {
					_, err := dbInstance.RetrieveJob("invalid-job")
					Expect(err).To(HaveOccurred())
				})
			})
		})

		Describe("GetJobs", func() {
			var anotherJob types.Job

			BeforeEach(func() {
				anotherJob = types.Job{
					ID:          "321",
					Source:      "http://source2.here.mp4",
					Destination: "s3://user@pass:/bucket/destination2.mp4",
					Preset:      types.Preset{Name: "presetHere2"},
					Status:      types.JobCreated,
					Details:     "0%",
				}
			})

			JustBeforeEach(func() {
				dbInstance.StoreJob(job)
				dbInstance.StoreJob(anotherJob)
			})

			It("should be able to list jobs", func() {
				jobs, err := dbInstance.GetJobs()
				Expect(err).NotTo(HaveOccurred())
				Expect(jobs).To(ConsistOf(job, anotherJob))
			})
		})

		Describe("UpdateJob", func() {
			JustBeforeEach(func() {
				dbInstance.StoreJob(job)
			})

			It("should be able to update job", func() {
				expectedStatus := types.JobDownloading
				job.Status = expectedStatus
				dbInstance.UpdateJob(job.ID, job)

				res, err := dbInstance.GetJobs()
				Expect(err).NotTo(HaveOccurred())
				Expect(res[0].Status).To(Equal(expectedStatus))
			})
		})
	}

	Describe("when the storage is in memory", func() {
		BeforeEach(func() {
			dbInstance, _ = memory.GetDatabase()
		})

		AfterEach(func() {
			dbInstance.ClearDatabase()
		})

		runDatabaseSuite()
	})

	Describe("when the storage is mongodb", func() {
		BeforeEach(func() {
			currentDir, _ := os.Getwd()
			dbInstance, _ = mongo.GetDatabase(currentDir + "/../fixtures/config.json")
		})

		AfterEach(func() {
			dbInstance.ClearDatabase()
		})

		runDatabaseSuite()
	})
})
<|endoftext|>