Merge pull request #5922 from spotinst/feature-spotinst-aws

New integration: Spotinst
This commit is contained in:
k8s-ci-robot 2018-10-15 09:26:13 -07:00 committed by GitHub
commit 1fbc6331c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
83 changed files with 10113 additions and 1030 deletions

40
Gopkg.lock generated
View File

@ -1074,6 +1074,27 @@
pruneopts = "UT"
revision = "7fb2782df3d83e0036cc89f461ed0422628776f4"
[[projects]]
branch = "master"
digest = "1:1c5e9c80b7f295cb22373c59300c27c27c122a9c07410a6505b137afacf3583c"
name = "github.com/spotinst/spotinst-sdk-go"
packages = [
"service/elastigroup",
"service/elastigroup/providers/aws",
"service/elastigroup/providers/azure",
"service/elastigroup/providers/gce",
"spotinst",
"spotinst/client",
"spotinst/credentials",
"spotinst/log",
"spotinst/session",
"spotinst/util/jsonutil",
"spotinst/util/stringutil",
"spotinst/util/uritemplates",
]
pruneopts = "UT"
revision = "fed4677dbf8fe026a81e09e66fc38d863d091f9b"
[[projects]]
digest = "1:67ba0f5b63fa937e1e78273904a1fa0f7c2358c4dac967ac16e678f8e50e8aa5"
name = "github.com/stretchr/testify"
@ -1615,7 +1636,7 @@
version = "kubernetes-1.11.3"
[[projects]]
digest = "1:b1d67a042544a528d86e37e7ace303dc74313d18c3aab360037db5b797ddde0b"
digest = "1:004509a1d109aec0996f8ea494a216f5e74268469661689178ca06134c9d6f02"
name = "k8s.io/client-go"
packages = [
"discovery",
@ -1794,7 +1815,6 @@
"util/integer",
"util/jsonpath",
"util/retry",
"util/workqueue",
]
pruneopts = "UT"
revision = "2cefa64ff137e128daeddbd1775cd775708a05bf"
@ -2178,6 +2198,14 @@
"github.com/spf13/cobra/doc",
"github.com/spf13/pflag",
"github.com/spf13/viper",
"github.com/spotinst/spotinst-sdk-go/service/elastigroup",
"github.com/spotinst/spotinst-sdk-go/service/elastigroup/providers/aws",
"github.com/spotinst/spotinst-sdk-go/spotinst",
"github.com/spotinst/spotinst-sdk-go/spotinst/client",
"github.com/spotinst/spotinst-sdk-go/spotinst/credentials",
"github.com/spotinst/spotinst-sdk-go/spotinst/log",
"github.com/spotinst/spotinst-sdk-go/spotinst/session",
"github.com/spotinst/spotinst-sdk-go/spotinst/util/stringutil",
"github.com/stretchr/testify/assert",
"github.com/urfave/cli",
"github.com/vmware/govmomi",
@ -2203,7 +2231,6 @@
"google.golang.org/api/storage/v1",
"gopkg.in/gcfg.v1",
"gopkg.in/yaml.v2",
"k8s.io/api/apps/v1",
"k8s.io/api/core/v1",
"k8s.io/api/extensions/v1beta1",
"k8s.io/api/rbac/v1beta1",
@ -2228,6 +2255,7 @@
"k8s.io/apimachinery/pkg/util/validation",
"k8s.io/apimachinery/pkg/util/validation/field",
"k8s.io/apimachinery/pkg/util/wait",
"k8s.io/apimachinery/pkg/util/yaml",
"k8s.io/apimachinery/pkg/version",
"k8s.io/apimachinery/pkg/watch",
"k8s.io/apiserver/pkg/authentication/user",
@ -2243,23 +2271,17 @@
"k8s.io/apiserver/pkg/util/logs",
"k8s.io/client-go/discovery",
"k8s.io/client-go/discovery/fake",
"k8s.io/client-go/informers",
"k8s.io/client-go/kubernetes",
"k8s.io/client-go/kubernetes/fake",
"k8s.io/client-go/kubernetes/scheme",
"k8s.io/client-go/kubernetes/typed/core/v1",
"k8s.io/client-go/listers/apps/v1",
"k8s.io/client-go/plugin/pkg/client/auth",
"k8s.io/client-go/rest",
"k8s.io/client-go/testing",
"k8s.io/client-go/tools/cache",
"k8s.io/client-go/tools/clientcmd",
"k8s.io/client-go/tools/clientcmd/api",
"k8s.io/client-go/tools/clientcmd/api/v1",
"k8s.io/client-go/tools/record",
"k8s.io/client-go/util/flowcontrol",
"k8s.io/client-go/util/homedir",
"k8s.io/client-go/util/workqueue",
"k8s.io/code-generator/cmd/client-gen",
"k8s.io/code-generator/cmd/conversion-gen",
"k8s.io/code-generator/cmd/deepcopy-gen",

View File

@ -139,6 +139,10 @@ type CreateClusterOptions struct {
// We can remove this once we support higher versions.
VSphereDatastore string
// Spotinst options
SpotinstProduct string
SpotinstOrientation string
// ConfigBase is the location where we will store the configuration, it defaults to the state store
ConfigBase string
@ -353,6 +357,13 @@ func NewCmdCreateCluster(f *util.Factory, out io.Writer) *cobra.Command {
cmd.Flags().StringVar(&options.VSphereCoreDNSServer, "vsphere-coredns-server", options.VSphereCoreDNSServer, "vsphere-coredns-server is required for vSphere.")
cmd.Flags().StringVar(&options.VSphereDatastore, "vsphere-datastore", options.VSphereDatastore, "vsphere-datastore is required for vSphere. Set a valid datastore in which to store dynamic provision volumes.")
}
if featureflag.Spotinst.Enabled() {
// Spotinst flags
cmd.Flags().StringVar(&options.SpotinstProduct, "spotinst-product", options.SpotinstProduct, "Set the product description (valid values: Linux/UNIX, Linux/UNIX (Amazon VPC), Windows and Windows (Amazon VPC))")
cmd.Flags().StringVar(&options.SpotinstOrientation, "spotinst-orientation", options.SpotinstOrientation, "Set the prediction strategy (valid values: balanced, cost, equal-distribution and availability)")
}
return cmd
}
@ -840,6 +851,18 @@ func RunCreateCluster(f *util.Factory, out io.Writer, c *CreateClusterOptions) e
}
cluster.Spec.CloudConfig.VSphereDatastore = fi.String(c.VSphereDatastore)
}
if featureflag.Spotinst.Enabled() {
if cluster.Spec.CloudConfig == nil {
cluster.Spec.CloudConfig = &api.CloudConfiguration{}
}
if c.SpotinstProduct != "" {
cluster.Spec.CloudConfig.SpotinstProduct = fi.String(c.SpotinstProduct)
}
if c.SpotinstOrientation != "" {
cluster.Spec.CloudConfig.SpotinstOrientation = fi.String(c.SpotinstOrientation)
}
}
}
// Populate project

View File

@ -108,6 +108,7 @@ k8s.io/kops/pkg/model/gcemodel
k8s.io/kops/pkg/model/iam
k8s.io/kops/pkg/model/openstackmodel
k8s.io/kops/pkg/model/resources
k8s.io/kops/pkg/model/spotinstmodel
k8s.io/kops/pkg/model/vspheremodel
k8s.io/kops/pkg/openapi
k8s.io/kops/pkg/pki
@ -120,6 +121,7 @@ k8s.io/kops/pkg/resources/digitalocean/dns
k8s.io/kops/pkg/resources/gce
k8s.io/kops/pkg/resources/openstack
k8s.io/kops/pkg/resources/ops
k8s.io/kops/pkg/resources/spotinst
k8s.io/kops/pkg/sshcredentials
k8s.io/kops/pkg/systemd
k8s.io/kops/pkg/templates
@ -163,6 +165,7 @@ k8s.io/kops/upup/pkg/fi/cloudup/gce
k8s.io/kops/upup/pkg/fi/cloudup/gcetasks
k8s.io/kops/upup/pkg/fi/cloudup/openstack
k8s.io/kops/upup/pkg/fi/cloudup/openstacktasks
k8s.io/kops/upup/pkg/fi/cloudup/spotinsttasks
k8s.io/kops/upup/pkg/fi/cloudup/terraform
k8s.io/kops/upup/pkg/fi/cloudup/vsphere
k8s.io/kops/upup/pkg/fi/cloudup/vspheretasks

View File

@ -493,6 +493,9 @@ type CloudConfiguration struct {
VSphereResourcePool *string `json:"vSphereResourcePool,omitempty"`
VSphereDatastore *string `json:"vSphereDatastore,omitempty"`
VSphereCoreDNSServer *string `json:"vSphereCoreDNSServer,omitempty"`
// Spotinst cloud-config specs
SpotinstProduct *string `json:"spotinstProduct,omitempty"`
SpotinstOrientation *string `json:"spotinstOrientation,omitempty"`
}
// HasAdmissionController checks if a specific admission controller is enabled

View File

@ -493,6 +493,9 @@ type CloudConfiguration struct {
VSphereResourcePool *string `json:"vSphereResourcePool,omitempty"`
VSphereDatastore *string `json:"vSphereDatastore,omitempty"`
VSphereCoreDNSServer *string `json:"vSphereCoreDNSServer,omitempty"`
// Spotinst cloud-config specs
SpotinstProduct *string `json:"spotinstProduct,omitempty"`
SpotinstOrientation *string `json:"spotinstOrientation,omitempty"`
}
// HasAdmissionController checks if a specific admission controller is enabled

View File

@ -656,6 +656,8 @@ func autoConvert_v1alpha1_CloudConfiguration_To_kops_CloudConfiguration(in *Clou
out.VSphereResourcePool = in.VSphereResourcePool
out.VSphereDatastore = in.VSphereDatastore
out.VSphereCoreDNSServer = in.VSphereCoreDNSServer
out.SpotinstProduct = in.SpotinstProduct
out.SpotinstOrientation = in.SpotinstOrientation
return nil
}
@ -677,6 +679,8 @@ func autoConvert_kops_CloudConfiguration_To_v1alpha1_CloudConfiguration(in *kops
out.VSphereResourcePool = in.VSphereResourcePool
out.VSphereDatastore = in.VSphereDatastore
out.VSphereCoreDNSServer = in.VSphereCoreDNSServer
out.SpotinstProduct = in.SpotinstProduct
out.SpotinstOrientation = in.SpotinstOrientation
return nil
}

View File

@ -525,6 +525,24 @@ func (in *CloudConfiguration) DeepCopyInto(out *CloudConfiguration) {
**out = **in
}
}
if in.SpotinstProduct != nil {
in, out := &in.SpotinstProduct, &out.SpotinstProduct
if *in == nil {
*out = nil
} else {
*out = new(string)
**out = **in
}
}
if in.SpotinstOrientation != nil {
in, out := &in.SpotinstOrientation, &out.SpotinstOrientation
if *in == nil {
*out = nil
} else {
*out = new(string)
**out = **in
}
}
return
}

View File

@ -493,6 +493,9 @@ type CloudConfiguration struct {
VSphereResourcePool *string `json:"vSphereResourcePool,omitempty"`
VSphereDatastore *string `json:"vSphereDatastore,omitempty"`
VSphereCoreDNSServer *string `json:"vSphereCoreDNSServer,omitempty"`
// Spotinst cloud-config specs
SpotinstProduct *string `json:"spotinstProduct,omitempty"`
SpotinstOrientation *string `json:"spotinstOrientation,omitempty"`
}
// HasAdmissionController checks if a specific admission controller is enabled

View File

@ -692,6 +692,8 @@ func autoConvert_v1alpha2_CloudConfiguration_To_kops_CloudConfiguration(in *Clou
out.VSphereResourcePool = in.VSphereResourcePool
out.VSphereDatastore = in.VSphereDatastore
out.VSphereCoreDNSServer = in.VSphereCoreDNSServer
out.SpotinstProduct = in.SpotinstProduct
out.SpotinstOrientation = in.SpotinstOrientation
return nil
}
@ -713,6 +715,8 @@ func autoConvert_kops_CloudConfiguration_To_v1alpha2_CloudConfiguration(in *kops
out.VSphereResourcePool = in.VSphereResourcePool
out.VSphereDatastore = in.VSphereDatastore
out.VSphereCoreDNSServer = in.VSphereCoreDNSServer
out.SpotinstProduct = in.SpotinstProduct
out.SpotinstOrientation = in.SpotinstOrientation
return nil
}

View File

@ -498,6 +498,24 @@ func (in *CloudConfiguration) DeepCopyInto(out *CloudConfiguration) {
**out = **in
}
}
if in.SpotinstProduct != nil {
in, out := &in.SpotinstProduct, &out.SpotinstProduct
if *in == nil {
*out = nil
} else {
*out = new(string)
**out = **in
}
}
if in.SpotinstOrientation != nil {
in, out := &in.SpotinstOrientation, &out.SpotinstOrientation
if *in == nil {
*out = nil
} else {
*out = new(string)
**out = **in
}
}
return
}

View File

@ -76,8 +76,10 @@ func awsValidateMachineType(fieldPath *field.Path, machineType string) field.Err
allErrs := field.ErrorList{}
if machineType != "" {
if _, err := awsup.GetMachineTypeInfo(machineType); err != nil {
allErrs = append(allErrs, field.Invalid(fieldPath, machineType, "machine type specified is invalid"))
for _, typ := range strings.Split(machineType, ",") {
if _, err := awsup.GetMachineTypeInfo(typ); err != nil {
allErrs = append(allErrs, field.Invalid(fieldPath, typ, "machine type specified is invalid"))
}
}
}
@ -92,15 +94,16 @@ func awsValidateAMIforNVMe(fieldPath *field.Path, ig *kops.InstanceGroup) field.
allErrs := field.ErrorList{}
for _, prefix := range NVMe_INSTANCE_PREFIXES {
if strings.Contains(strings.ToUpper(ig.Spec.MachineType), strings.ToUpper(prefix)) {
glog.V(2).Infof("machineType %s requires an image based on stretch to operate. Trying to check compatibility", ig.Spec.MachineType)
if strings.Contains(ig.Spec.Image, "jessie") {
errString := fmt.Sprintf("%s cannot use machineType %s with image based on Debian jessie.", ig.Name, ig.Spec.MachineType)
allErrs = append(allErrs, field.Forbidden(fieldPath, errString))
continue
for _, machineType := range strings.Split(ig.Spec.MachineType, ",") {
if strings.Contains(strings.ToUpper(machineType), strings.ToUpper(prefix)) {
glog.V(2).Infof("machineType %s requires an image based on stretch to operate. Trying to check compatibility", machineType)
if strings.Contains(ig.Spec.Image, "jessie") {
errString := fmt.Sprintf("%s cannot use machineType %s with image based on Debian jessie.", ig.Name, machineType)
allErrs = append(allErrs, field.Forbidden(fieldPath, errString))
continue
}
}
}
}
return allErrs
}

View File

@ -603,6 +603,24 @@ func (in *CloudConfiguration) DeepCopyInto(out *CloudConfiguration) {
**out = **in
}
}
if in.SpotinstProduct != nil {
in, out := &in.SpotinstProduct, &out.SpotinstProduct
if *in == nil {
*out = nil
} else {
*out = new(string)
**out = **in
}
}
if in.SpotinstOrientation != nil {
in, out := &in.SpotinstOrientation, &out.SpotinstOrientation
if *in == nil {
*out = nil
} else {
*out = new(string)
**out = **in
}
}
return
}

View File

@ -77,6 +77,9 @@ var GoogleCloudBucketAcl = New("GoogleCloudBucketAcl", Bool(false))
// EnableNodeAuthorization enables the node authorization features
var EnableNodeAuthorization = New("EnableNodeAuthorization", Bool(false))
// Spotinst toggles the use of Spotinst integration.
var Spotinst = New("Spotinst", Bool(false))
var flags = make(map[string]*FeatureFlag)
var flagsMutex sync.Mutex

View File

@ -18,6 +18,7 @@ package awsmodel
import (
"fmt"
"strings"
"github.com/golang/glog"
@ -98,7 +99,7 @@ func (b *AutoscalingGroupModelBuilder) Build(c *fi.ModelBuilderContext) error {
},
IAMInstanceProfile: link,
ImageID: s(ig.Spec.Image),
InstanceType: s(ig.Spec.MachineType),
InstanceType: s(strings.Split(ig.Spec.MachineType, ",")[0]),
InstanceMonitoring: ig.Spec.DetailedInstanceMonitoring,
RootVolumeSize: i64(int64(volumeSize)),

View File

@ -0,0 +1,19 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["elastigroup.go"],
importpath = "k8s.io/kops/pkg/model/spotinstmodel",
visibility = ["//visibility:public"],
deps = [
"//pkg/apis/kops:go_default_library",
"//pkg/model:go_default_library",
"//pkg/model/awsmodel:go_default_library",
"//pkg/model/defaults:go_default_library",
"//upup/pkg/fi:go_default_library",
"//upup/pkg/fi/cloudup/awstasks:go_default_library",
"//upup/pkg/fi/cloudup/awsup:go_default_library",
"//upup/pkg/fi/cloudup/spotinsttasks:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
],
)

View File

@ -0,0 +1,340 @@
/*
Copyright 2016 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 spotinstmodel
import (
"fmt"
"strconv"
"strings"
"github.com/golang/glog"
"k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/model"
"k8s.io/kops/pkg/model/awsmodel"
"k8s.io/kops/pkg/model/defaults"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awstasks"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
"k8s.io/kops/upup/pkg/fi/cloudup/spotinsttasks"
)
const (
// InstanceGroupLabelOrientation is the metadata label used on the
// instance group to specify which orientation should be used.
InstanceGroupLabelOrientation = "spotinst.io/orientation"
// InstanceGroupLabelUtilizeReservedInstances is the metadata label used
// on the instance group to specify whether reserved instances should be
// utilized.
InstanceGroupLabelUtilizeReservedInstances = "spotinst.io/utilize-reserved-instances"
// InstanceGroupLabelFallbackToOnDemand is the metadata label used on the
// instance group to specify whether fallback to on-demand instances should
// be enabled.
InstanceGroupLabelFallbackToOnDemand = "spotinst.io/fallback-to-ondemand"
// InstanceGroupLabelAutoScalerDisabled is the metadata label used on the
// instance group to specify whether the auto-scaler should be enabled.
InstanceGroupLabelAutoScalerDisabled = "spotinst.io/autoscaler-disabled"
// InstanceGroupLabelAutoScalerDefaultNodeLabels is the metadata label used on the
// instance group to specify whether default node labels should be set for
// the auto-scaler.
InstanceGroupLabelAutoScalerDefaultNodeLabels = "spotinst.io/autoscaler-default-node-labels"
)
// ElastigroupModelBuilder configures Elastigroup objects
type ElastigroupModelBuilder struct {
*awsmodel.AWSModelContext
BootstrapScript *model.BootstrapScript
Lifecycle *fi.Lifecycle
SecurityLifecycle *fi.Lifecycle
}
var _ fi.ModelBuilder = &ElastigroupModelBuilder{}
func (b *ElastigroupModelBuilder) Build(c *fi.ModelBuilderContext) error {
for _, ig := range b.InstanceGroups {
glog.V(2).Infof("Building instance group %q", b.AutoscalingGroupName(ig))
group := &spotinsttasks.Elastigroup{
Lifecycle: b.Lifecycle,
Name: fi.String(b.AutoscalingGroupName(ig)),
ImageID: fi.String(ig.Spec.Image),
Monitoring: fi.Bool(false),
OnDemandInstanceType: fi.String(strings.Split(ig.Spec.MachineType, ",")[0]),
SpotInstanceTypes: strings.Split(ig.Spec.MachineType, ","),
SecurityGroups: []*awstasks.SecurityGroup{
b.LinkToSecurityGroup(ig.Spec.Role),
},
}
// Cloud config.
if cfg := b.Cluster.Spec.CloudConfig; cfg != nil {
group.Product = cfg.SpotinstProduct
group.Orientation = cfg.SpotinstOrientation
}
// Strategy.
for k, v := range ig.ObjectMeta.Labels {
switch k {
case InstanceGroupLabelOrientation:
group.Orientation = fi.String(v)
break
case InstanceGroupLabelUtilizeReservedInstances:
b, err := parseBool(v)
if err != nil {
return err
}
group.UtilizeReservedInstances = b
break
case InstanceGroupLabelFallbackToOnDemand:
b, err := parseBool(v)
if err != nil {
return err
}
group.FallbackToOnDemand = b
break
}
}
// Instance profile.
iprof, err := b.LinkToIAMInstanceProfile(ig)
if err != nil {
return err
}
group.IAMInstanceProfile = iprof
// Root volume.
volumeSize := fi.Int32Value(ig.Spec.RootVolumeSize)
if volumeSize == 0 {
var err error
volumeSize, err = defaults.DefaultInstanceGroupVolumeSize(ig.Spec.Role)
if err != nil {
return err
}
}
volumeType := fi.StringValue(ig.Spec.RootVolumeType)
if volumeType == "" {
volumeType = awsmodel.DefaultVolumeType
}
group.RootVolumeSize = fi.Int64(int64(volumeSize))
group.RootVolumeType = fi.String(volumeType)
group.RootVolumeOptimization = ig.Spec.RootVolumeOptimization
// Tenancy.
if ig.Spec.Tenancy != "" {
group.Tenancy = fi.String(ig.Spec.Tenancy)
}
// Risk.
var risk float64
switch ig.Spec.Role {
case kops.InstanceGroupRoleMaster:
risk = 0
case kops.InstanceGroupRoleNode:
risk = 100
case kops.InstanceGroupRoleBastion:
risk = 0
default:
return fmt.Errorf("spotinst: kops.Role not found %s", ig.Spec.Role)
}
group.Risk = &risk
// Security groups.
for _, id := range ig.Spec.AdditionalSecurityGroups {
sgTask := &awstasks.SecurityGroup{
Name: fi.String(id),
ID: fi.String(id),
Shared: fi.Bool(true),
}
if err := c.EnsureTask(sgTask); err != nil {
return err
}
group.SecurityGroups = append(group.SecurityGroups, sgTask)
}
// SSH Key.
sshKey, err := b.LinkToSSHKey()
if err != nil {
return err
}
group.SSHKey = sshKey
// Load balancer.
var lb *awstasks.LoadBalancer
switch ig.Spec.Role {
case kops.InstanceGroupRoleMaster:
if b.UseLoadBalancerForAPI() {
lb = b.LinkToELB("api")
}
case kops.InstanceGroupRoleBastion:
lb = b.LinkToELB(model.BastionELBSecurityGroupPrefix)
}
if lb != nil {
group.LoadBalancer = lb
}
// User data.
userData, err := b.BootstrapScript.ResourceNodeUp(ig, b.Cluster)
if err != nil {
return err
}
group.UserData = userData
// Public IP.
subnetMap := make(map[string]*kops.ClusterSubnetSpec)
for i := range b.Cluster.Spec.Subnets {
subnet := &b.Cluster.Spec.Subnets[i]
subnetMap[subnet.Name] = subnet
}
var subnetType kops.SubnetType
for _, subnetName := range ig.Spec.Subnets {
subnet := subnetMap[subnetName]
if subnet == nil {
return fmt.Errorf("spotinst: InstanceGroup %q uses subnet %q that does not exist", ig.ObjectMeta.Name, subnetName)
}
if subnetType != "" && subnetType != subnet.Type {
return fmt.Errorf("spotinst: InstanceGroup %q cannot be in subnets of different Type", ig.ObjectMeta.Name)
}
subnetType = subnet.Type
}
associatePublicIP := true
switch subnetType {
case kops.SubnetTypePublic, kops.SubnetTypeUtility:
associatePublicIP = true
if ig.Spec.AssociatePublicIP != nil {
associatePublicIP = *ig.Spec.AssociatePublicIP
}
case kops.SubnetTypePrivate:
associatePublicIP = false
if ig.Spec.AssociatePublicIP != nil {
if *ig.Spec.AssociatePublicIP {
glog.Warningf("Ignoring AssociatePublicIP=true for private InstanceGroup %q", ig.ObjectMeta.Name)
}
}
default:
return fmt.Errorf("spotinst: unknown subnet type %q", subnetType)
}
group.AssociatePublicIP = &associatePublicIP
// Subnets.
subnets, err := b.GatherSubnets(ig)
if err != nil {
return err
}
if len(subnets) == 0 {
return fmt.Errorf("spotinst: could not determine any subnets for InstanceGroup %q; subnets was %s", ig.ObjectMeta.Name, ig.Spec.Subnets)
}
for _, subnet := range subnets {
group.Subnets = append(group.Subnets, b.LinkToSubnet(subnet))
}
// Capacity.
minSize := int32(1)
if ig.Spec.MinSize != nil {
minSize = fi.Int32Value(ig.Spec.MinSize)
} else if ig.Spec.Role == kops.InstanceGroupRoleNode {
minSize = 2
}
maxSize := int32(1)
if ig.Spec.MaxSize != nil {
maxSize = *ig.Spec.MaxSize
} else if ig.Spec.Role == kops.InstanceGroupRoleNode {
maxSize = 2
}
group.MinSize = fi.Int64(int64(minSize))
group.MaxSize = fi.Int64(int64(maxSize))
// Tags.
tags, err := b.CloudTagsForInstanceGroup(ig)
if err != nil {
return fmt.Errorf("spotinst: error building cloud tags: %v", err)
}
tags[awsup.TagClusterName] = b.ClusterName()
tags["Name"] = b.AutoscalingGroupName(ig)
group.Tags = tags
// Auto Scaler.
if ig.Spec.Role != kops.InstanceGroupRoleBastion {
group.AutoScalerClusterID = fi.String(b.ClusterName())
// Toggle auto scaler's features.
var autoScalerDisabled bool
var autoScalerNodeLabels bool
{
for k, v := range ig.ObjectMeta.Labels {
switch k {
case InstanceGroupLabelAutoScalerDisabled:
b, err := parseBool(v)
if err != nil {
return err
}
autoScalerDisabled = fi.BoolValue(b)
break
case InstanceGroupLabelAutoScalerDefaultNodeLabels:
b, err := parseBool(v)
if err != nil {
return err
}
autoScalerNodeLabels = fi.BoolValue(b)
break
}
}
}
// Toggle the auto scaler.
group.AutoScalerEnabled = fi.Bool(!autoScalerDisabled)
// Set the node labels.
if ig.Spec.Role == kops.InstanceGroupRoleNode {
nodeLabels := make(map[string]string)
for k, v := range ig.Spec.NodeLabels {
if strings.HasPrefix(k, kops.NodeLabelInstanceGroup) && !autoScalerNodeLabels {
continue
}
nodeLabels[k] = v
}
if len(nodeLabels) > 0 {
group.AutoScalerNodeLabels = nodeLabels
}
}
}
c.AddTask(group)
}
return nil
}
func parseBool(str string) (*bool, error) {
b, err := strconv.ParseBool(str)
if err != nil {
return nil, fmt.Errorf("spotinst: unexpected boolean value: %q", str)
}
return fi.Bool(b), nil
}

View File

@ -18,7 +18,9 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//pkg/dns:go_default_library",
"//pkg/featureflag:go_default_library",
"//pkg/resources:go_default_library",
"//pkg/resources/spotinst:go_default_library",
"//upup/pkg/fi:go_default_library",
"//upup/pkg/fi/cloudup/awsup:go_default_library",
"//vendor/github.com/aws/aws-sdk-go/aws:go_default_library",

View File

@ -34,7 +34,9 @@ import (
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/kops/pkg/dns"
"k8s.io/kops/pkg/featureflag"
"k8s.io/kops/pkg/resources"
"k8s.io/kops/pkg/resources/spotinst"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awsup"
)
@ -74,8 +76,6 @@ func ListResourcesAWS(cloud awsup.AWSCloud, clusterName string) (map[string]*res
ListELBs,
ListELBV2s,
ListTargetGroups,
// ASG
ListAutoScalingGroups,
// Route 53
ListRoute53Records,
@ -83,6 +83,15 @@ func ListResourcesAWS(cloud awsup.AWSCloud, clusterName string) (map[string]*res
ListIAMInstanceProfiles,
ListIAMRoles,
}
if featureflag.Spotinst.Enabled() {
// Spotinst Elastigroups
listFunctions = append(listFunctions, ListSpotinstElastigroups)
} else {
// AutoScaling Groups
listFunctions = append(listFunctions, ListAutoScalingGroups)
}
for _, fn := range listFunctions {
rt, err := fn(cloud, clusterName)
if err != nil {
@ -2024,6 +2033,10 @@ func ListIAMInstanceProfiles(cloud fi.Cloud, clusterName string) ([]*resources.R
return resourceTrackers, nil
}
func ListSpotinstElastigroups(cloud fi.Cloud, clusterName string) ([]*resources.Resource, error) {
return spotinst.ListGroups(cloud.(awsup.AWSCloud).Spotinst(), clusterName)
}
func FindName(tags []*ec2.Tag) string {
if name, found := awsup.FindEC2Tag(tags, "Name"); found {
return name

View File

@ -0,0 +1,28 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"aws.go",
"interfaces.go",
"resources.go",
"spotinst.go",
],
importpath = "k8s.io/kops/pkg/resources/spotinst",
visibility = ["//visibility:public"],
deps = [
"//:go_default_library",
"//pkg/apis/kops:go_default_library",
"//pkg/cloudinstances:go_default_library",
"//pkg/resources:go_default_library",
"//upup/pkg/fi:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/spotinst/spotinst-sdk-go/service/elastigroup:go_default_library",
"//vendor/github.com/spotinst/spotinst-sdk-go/service/elastigroup/providers/aws:go_default_library",
"//vendor/github.com/spotinst/spotinst-sdk-go/spotinst:go_default_library",
"//vendor/github.com/spotinst/spotinst-sdk-go/spotinst/credentials:go_default_library",
"//vendor/github.com/spotinst/spotinst-sdk-go/spotinst/log:go_default_library",
"//vendor/github.com/spotinst/spotinst-sdk-go/spotinst/session:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
],
)

View File

@ -0,0 +1,153 @@
/*
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 spotinst
import (
"context"
"github.com/spotinst/spotinst-sdk-go/service/elastigroup/providers/aws"
"k8s.io/kops/upup/pkg/fi"
)
type awsService struct {
svc aws.Service
}
// List returns a list of Elastigroups.
func (s *awsService) List(ctx context.Context) ([]Elastigroup, error) {
output, err := s.svc.List(ctx, nil)
if err != nil {
return nil, err
}
groups := make([]Elastigroup, len(output.Groups))
for i, group := range output.Groups {
groups[i] = &awsElastigroup{group}
}
return groups, nil
}
// Create creates a new Elastigroup and returns its ID.
func (s *awsService) Create(ctx context.Context, group Elastigroup) (string, error) {
input := &aws.CreateGroupInput{
Group: group.Obj().(*aws.Group),
}
output, err := s.svc.Create(ctx, input)
if err != nil {
return "", err
}
return fi.StringValue(output.Group.ID), nil
}
// Read returns an existing Elastigroup by ID.
func (s *awsService) Read(ctx context.Context, groupID string) (Elastigroup, error) {
input := &aws.ReadGroupInput{
GroupID: fi.String(groupID),
}
output, err := s.svc.Read(ctx, input)
if err != nil {
return nil, err
}
return &awsElastigroup{output.Group}, nil
}
// Update updates an existing Elastigroup.
func (s *awsService) Update(ctx context.Context, group Elastigroup) error {
input := &aws.UpdateGroupInput{
Group: group.Obj().(*aws.Group),
}
_, err := s.svc.Update(ctx, input)
return err
}
// Delete deletes an existing Elastigroup by ID.
func (s *awsService) Delete(ctx context.Context, groupID string) error {
input := &aws.DeleteGroupInput{
GroupID: fi.String(groupID),
}
_, err := s.svc.Delete(ctx, input)
return err
}
// Detach removes one or more instances from the specified Elastigroup.
func (s *awsService) Detach(ctx context.Context, groupID string, instanceIDs []string) error {
input := &aws.DetachGroupInput{
GroupID: fi.String(groupID),
InstanceIDs: instanceIDs,
ShouldDecrementTargetCapacity: fi.Bool(false),
ShouldTerminateInstances: fi.Bool(true),
}
_, err := s.svc.Detach(ctx, input)
return err
}
// Instances returns a list of all instances that belong to specified Elastigroup.
func (s *awsService) Instances(ctx context.Context, groupID string) ([]Instance, error) {
input := &aws.StatusGroupInput{
GroupID: fi.String(groupID),
}
output, err := s.svc.Status(ctx, input)
if err != nil {
return nil, err
}
instances := make([]Instance, len(output.Instances))
for i, instance := range output.Instances {
instances[i] = &awsInstance{instance}
}
return instances, err
}
type awsElastigroup struct {
obj *aws.Group
}
// Id returns the ID of the Elastigroup.
func (e *awsElastigroup) Id() string { return fi.StringValue(e.obj.ID) }
// Name returns the name of the Elastigroup.
func (e *awsElastigroup) Name() string { return fi.StringValue(e.obj.Name) }
// MinSize returns the minimum size of the Elastigroup.
func (e *awsElastigroup) MinSize() int { return fi.IntValue(e.obj.Capacity.Minimum) }
// MaxSize returns the maximum size of the Elastigroup.
func (e *awsElastigroup) MaxSize() int { return fi.IntValue(e.obj.Capacity.Maximum) }
// Obj returns the raw object which is a cloud-specific implementation.
func (e *awsElastigroup) Obj() interface{} { return e.obj }
type awsInstance struct {
obj *aws.Instance
}
// Id returns the ID of the instance.
func (i *awsInstance) Id() string { return fi.StringValue(i.obj.ID) }
// Obj returns the raw object which is a cloud-specific implementation.
func (i *awsInstance) Obj() interface{} { return i.obj }

View File

@ -0,0 +1,76 @@
/*
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 spotinst
import (
"context"
)
type (
// Elastigroup contains configuration info and functions to control a set
// of instances.
Elastigroup interface {
// Id returns the ID of the Elastigroup.
Id() string
// Name returns the name of the Elastigroup.
Name() string
// MinSize returns the minimum size of the Elastigroup.
MinSize() int
// MaxSize returns the maximum size of the Elastigroup.
MaxSize() int
// Obj returns the raw object which is a cloud-specific implementation.
Obj() interface{}
}
// Instance wraps a cloud-specific instance object.
Instance interface {
// Id returns the ID of the instance.
Id() string
// Obj returns the raw object which is a cloud-specific implementation.
Obj() interface{}
}
// Service is an interface that a cloud provider that is supported
// by Spotinst MUST implement to manage its Elastigroups.
Service interface {
// List returns a list of Elastigroups.
List(ctx context.Context) ([]Elastigroup, error)
// Create creates a new Elastigroup and returns its ID.
Create(ctx context.Context, group Elastigroup) (string, error)
// Read returns an existing Elastigroup by ID.
Read(ctx context.Context, groupID string) (Elastigroup, error)
// Update updates an existing Elastigroup.
Update(ctx context.Context, group Elastigroup) error
// Delete deletes an existing Elastigroup by ID.
Delete(ctx context.Context, groupID string) error
// Detach removes one or more instances from the specified Elastigroup.
Detach(ctx context.Context, groupID string, instanceIDs []string) error
// Instances returns a list of all instances that belong to specified Elastigroup.
Instances(ctx context.Context, groupID string) ([]Instance, error)
}
)

View File

@ -0,0 +1,193 @@
/*
Copyright 2016 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 spotinst
import (
"context"
"fmt"
"strings"
"time"
"github.com/golang/glog"
"k8s.io/api/core/v1"
"k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/cloudinstances"
"k8s.io/kops/pkg/resources"
"k8s.io/kops/upup/pkg/fi"
)
// ListGroups returns a list of all Elastigroups as Resource objects.
func ListGroups(svc Service, clusterName string) ([]*resources.Resource, error) {
glog.V(2).Info("Listing all Elastigroups")
groups, err := svc.List(context.Background())
if err != nil {
return nil, err
}
var resourceTrackers []*resources.Resource
for _, group := range groups {
if strings.HasSuffix(group.Name(), clusterName) {
resource := &resources.Resource{
ID: group.Id(),
Name: group.Name(),
Obj: group,
Deleter: deleter(svc, group),
Dumper: dumper,
}
resourceTrackers = append(resourceTrackers, resource)
}
}
return resourceTrackers, nil
}
// DeleteGroup deletes an existing Elastigroup.
func DeleteGroup(svc Service, group *cloudinstances.CloudInstanceGroup) error {
glog.V(2).Infof("Deleting Elastigroup %q", group.HumanName)
return svc.Delete(
context.Background(),
group.Raw.(Elastigroup).Id())
}
// DeleteInstance removes an instance from its Elastigroup.
func DeleteInstance(svc Service, instance *cloudinstances.CloudInstanceGroupMember) error {
glog.V(2).Infof("Detaching instance %q from Elastigroup", instance.ID)
return svc.Detach(
context.Background(),
instance.CloudInstanceGroup.Raw.(Elastigroup).Id(),
[]string{instance.ID})
}
// GetCloudGroups returns a list of Elastigroups as CloudInstanceGroup objects.
func GetCloudGroups(svc Service, cluster *kops.Cluster, instancegroups []*kops.InstanceGroup,
warnUnmatched bool, nodes []v1.Node) (map[string]*cloudinstances.CloudInstanceGroup, error) {
glog.V(2).Info("Listing all Elastigroups")
groups, err := svc.List(context.Background())
if err != nil {
return nil, err
}
instanceGroups := make(map[string]*cloudinstances.CloudInstanceGroup)
nodeMap := cloudinstances.GetNodeMap(nodes, cluster)
for _, group := range groups {
// Find matching instance group.
var instancegroup *kops.InstanceGroup
for _, ig := range instancegroups {
name := getGroupNameByRole(cluster, ig)
if name == "" {
continue
}
if name == group.Name() {
if instancegroup != nil {
return nil, fmt.Errorf("spotinst: found multiple instance groups matching group %q", group.Name())
}
instancegroup = ig
}
}
if instancegroup == nil {
if warnUnmatched {
glog.Warningf("Found group with no corresponding instance group %q", group.Name())
}
continue
}
// Build the instance group.
ig, err := buildCloudInstanceGroup(svc, instancegroup, group, nodeMap)
if err != nil {
return nil, fmt.Errorf("spotinst: failed to build instance group: %v", err)
}
instanceGroups[instancegroup.ObjectMeta.Name] = ig
}
return instanceGroups, nil
}
func getGroupNameByRole(cluster *kops.Cluster, ig *kops.InstanceGroup) string {
var groupName string
switch ig.Spec.Role {
case kops.InstanceGroupRoleMaster:
groupName = ig.ObjectMeta.Name + ".masters." + cluster.ObjectMeta.Name
case kops.InstanceGroupRoleNode:
groupName = ig.ObjectMeta.Name + "." + cluster.ObjectMeta.Name
case kops.InstanceGroupRoleBastion:
groupName = ig.ObjectMeta.Name + "." + cluster.ObjectMeta.Name
default:
glog.Warningf("Ignoring InstanceGroup of unknown role %q", ig.Spec.Role)
}
return groupName
}
func buildCloudInstanceGroup(svc Service, ig *kops.InstanceGroup, group Elastigroup,
nodeMap map[string]*v1.Node) (*cloudinstances.CloudInstanceGroup, error) {
instances, err := svc.Instances(context.Background(), group.Id())
if err != nil {
return nil, err
}
instanceGroup := &cloudinstances.CloudInstanceGroup{
HumanName: group.Name(),
InstanceGroup: ig,
MinSize: group.MinSize(),
MaxSize: group.MaxSize(),
Raw: group,
}
currentName := group.Name()
newName := fmt.Sprintf("%s:%d", group.Name(), time.Now().Nanosecond())
for _, instance := range instances {
if instance.Id() == "" {
glog.Warningf("Ignoring instance with no ID: %v", instance)
continue
}
if err := instanceGroup.NewCloudInstanceGroupMember(
instance.Id(), currentName, newName, nodeMap); err != nil {
return nil, fmt.Errorf("spotinst: error creating cloud instance group member: %v", err)
}
}
return instanceGroup, nil
}
func deleter(svc Service, group Elastigroup) func(fi.Cloud, *resources.Resource) error {
return func(cloud fi.Cloud, resource *resources.Resource) error {
glog.V(2).Infof("Deleting Elastigroup %q", group.Id())
return svc.Delete(context.Background(), group.Id())
}
}
func dumper(op *resources.DumpOperation, resource *resources.Resource) error {
data := make(map[string]interface{})
data["id"] = resource.ID
data["type"] = resource.Type
data["raw"] = resource.Obj
op.Dump.Resources = append(op.Dump.Resources, data)
return nil
}

View File

@ -0,0 +1,98 @@
/*
Copyright 2017 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 spotinst
import (
"fmt"
"github.com/golang/glog"
"github.com/spotinst/spotinst-sdk-go/service/elastigroup"
"github.com/spotinst/spotinst-sdk-go/service/elastigroup/providers/aws"
"github.com/spotinst/spotinst-sdk-go/spotinst"
"github.com/spotinst/spotinst-sdk-go/spotinst/credentials"
"github.com/spotinst/spotinst-sdk-go/spotinst/log"
"github.com/spotinst/spotinst-sdk-go/spotinst/session"
kopsv "k8s.io/kops"
"k8s.io/kops/pkg/apis/kops"
)
// NewService returns a Service interface for the specified cloud provider.
func NewService(cloudProviderID kops.CloudProviderID) (Service, error) {
svc := elastigroup.New(session.New(NewConfig()))
switch cloudProviderID {
case kops.CloudProviderAWS:
return &awsService{svc.CloudProviderAWS()}, nil
default:
return nil, fmt.Errorf("spotinst: unsupported cloud provider: %s", cloudProviderID)
}
}
// NewConfig returns a new configuration object.
func NewConfig() *spotinst.Config {
config := spotinst.DefaultConfig()
config.WithCredentials(NewCredentials())
config.WithLogger(NewStdLogger())
config.WithUserAgent("kubernetes-kops/" + kopsv.Version)
return config
}
// NewCredentials returns a new chain-credentials object.
func NewCredentials() *credentials.Credentials {
return credentials.NewChainCredentials(
new(credentials.EnvProvider),
new(credentials.FileProvider),
)
}
// NewStdLogger returns a new Logger.
func NewStdLogger() log.Logger {
return log.LoggerFunc(func(format string, args ...interface{}) {
glog.V(2).Infof(format, args...)
})
}
// NewElastigroup returns an Elastigroup wrapper for the specified cloud provider.
func NewElastigroup(cloudProviderID kops.CloudProviderID,
obj interface{}) (Elastigroup, error) {
switch cloudProviderID {
case kops.CloudProviderAWS:
return &awsElastigroup{obj.(*aws.Group)}, nil
default:
return nil, fmt.Errorf("spotinst: unsupported cloud provider: %s", cloudProviderID)
}
}
// LoadCredentials attempts to load credentials using the default chain.
func LoadCredentials() (credentials.Value, error) {
var (
chain = NewCredentials()
creds credentials.Value
err error
)
// Attempt to load the credentials.
creds, err = chain.Get()
if err != nil {
return creds, fmt.Errorf("spotinst: unable to load credentials: %v", err)
}
return creds, nil
}

View File

@ -0,0 +1,129 @@
# ------------------------------------------
# Config Map
# ------------------------------------------
apiVersion: v1
kind: ConfigMap
metadata:
name: spotinst-kubernetes-cluster-controller-config
namespace: kube-system
data:
spotinst.token: {{ SpotinstToken }}
spotinst.account: {{ SpotinstAccount }}
spotinst.cluster-identifier: {{ ClusterName }}
---
# ------------------------------------------
# Secret
# ------------------------------------------
apiVersion: v1
kind: Secret
metadata:
name: spotinst-kubernetes-cluster-controller-certs
namespace: kube-system
type: Opaque
---
# ------------------------------------------
# Service Account
# ------------------------------------------
apiVersion: v1
kind: ServiceAccount
metadata:
name: spotinst-kubernetes-cluster-controller
namespace: kube-system
---
# ------------------------------------------
# Cluster Role
# ------------------------------------------
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: spotinst-kubernetes-cluster-controller
namespace: kube-system
rules:
- apiGroups: [""]
resources: ["pods", "nodes", "replicationcontrollers", "events", "limitranges", "services", "persistentvolumes", "persistentvolumeclaims"]
verbs: ["get", "delete", "list", "patch", "update"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get","list","patch"]
- apiGroups: ["extensions"]
resources: ["replicasets"]
verbs: ["get","list"]
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["clusterroles"]
verbs: ["patch"]
- apiGroups: ["policy"]
resources: ["poddisruptionbudgets"]
verbs: ["list"]
- nonResourceURLs: ["/version/", "/version"]
verbs: ["get"]
---
# ------------------------------------------
# Cluster Role Binding
# ------------------------------------------
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: spotinst-kubernetes-cluster-controller
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: spotinst-kubernetes-cluster-controller
subjects:
- kind: ServiceAccount
name: spotinst-kubernetes-cluster-controller
namespace: kube-system
---
# ------------------------------------------
# Deployment
# ------------------------------------------
apiVersion: apps/v1beta1
kind: Deployment
metadata:
labels:
k8s-addon: spotinst-kubernetes-cluster-controller.addons.k8s.io
name: spotinst-kubernetes-cluster-controller
namespace: kube-system
spec:
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
k8s-addon: spotinst-kubernetes-cluster-controller.addons.k8s.io
template:
metadata:
labels:
k8s-addon: spotinst-kubernetes-cluster-controller.addons.k8s.io
spec:
containers:
- name: spotinst-kubernetes-cluster-controller
image: spotinst/kubernetes-cluster-controller:1.0.16
imagePullPolicy: Always
env:
- name: SPOTINST_TOKEN
valueFrom:
configMapKeyRef:
name: spotinst-kubernetes-cluster-controller-config
key: spotinst.token
- name: SPOTINST_ACCOUNT
valueFrom:
configMapKeyRef:
name: spotinst-kubernetes-cluster-controller-config
key: spotinst.account
- name: CLUSTER_IDENTIFIER
valueFrom:
configMapKeyRef:
name: spotinst-kubernetes-cluster-controller-config
key: spotinst.cluster-identifier
volumeMounts:
- name: spotinst-kubernetes-cluster-controller-certs
mountPath: /certs
volumes:
- name: spotinst-kubernetes-cluster-controller-certs
secret:
secretName: spotinst-kubernetes-cluster-controller-certs
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
serviceAccountName: spotinst-kubernetes-cluster-controller
---

View File

@ -0,0 +1,129 @@
# ------------------------------------------
# Config Map
# ------------------------------------------
apiVersion: v1
kind: ConfigMap
metadata:
name: spotinst-kubernetes-cluster-controller-config
namespace: kube-system
data:
spotinst.token: {{ SpotinstToken }}
spotinst.account: {{ SpotinstAccount }}
spotinst.cluster-identifier: {{ ClusterName }}
---
# ------------------------------------------
# Secret
# ------------------------------------------
apiVersion: v1
kind: Secret
metadata:
name: spotinst-kubernetes-cluster-controller-certs
namespace: kube-system
type: Opaque
---
# ------------------------------------------
# Service Account
# ------------------------------------------
apiVersion: v1
kind: ServiceAccount
metadata:
name: spotinst-kubernetes-cluster-controller
namespace: kube-system
---
# ------------------------------------------
# Cluster Role
# ------------------------------------------
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: spotinst-kubernetes-cluster-controller
namespace: kube-system
rules:
- apiGroups: [""]
resources: ["pods", "nodes", "replicationcontrollers", "events", "limitranges", "services", "persistentvolumes", "persistentvolumeclaims"]
verbs: ["get", "delete", "list", "patch", "update"]
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get","list","patch"]
- apiGroups: ["extensions"]
resources: ["replicasets"]
verbs: ["get","list"]
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["clusterroles"]
verbs: ["patch"]
- apiGroups: ["policy"]
resources: ["poddisruptionbudgets"]
verbs: ["list"]
- nonResourceURLs: ["/version/", "/version"]
verbs: ["get"]
---
# ------------------------------------------
# Cluster Role Binding
# ------------------------------------------
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: spotinst-kubernetes-cluster-controller
namespace: kube-system
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: spotinst-kubernetes-cluster-controller
subjects:
- kind: ServiceAccount
name: spotinst-kubernetes-cluster-controller
namespace: kube-system
---
# ------------------------------------------
# Deployment
# ------------------------------------------
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
k8s-addon: spotinst-kubernetes-cluster-controller.addons.k8s.io
name: spotinst-kubernetes-cluster-controller
namespace: kube-system
spec:
replicas: 1
revisionHistoryLimit: 10
selector:
matchLabels:
k8s-addon: spotinst-kubernetes-cluster-controller.addons.k8s.io
template:
metadata:
labels:
k8s-addon: spotinst-kubernetes-cluster-controller.addons.k8s.io
spec:
containers:
- name: spotinst-kubernetes-cluster-controller
image: spotinst/kubernetes-cluster-controller:1.0.16
imagePullPolicy: Always
env:
- name: SPOTINST_TOKEN
valueFrom:
configMapKeyRef:
name: spotinst-kubernetes-cluster-controller-config
key: spotinst.token
- name: SPOTINST_ACCOUNT
valueFrom:
configMapKeyRef:
name: spotinst-kubernetes-cluster-controller-config
key: spotinst.account
- name: CLUSTER_IDENTIFIER
valueFrom:
configMapKeyRef:
name: spotinst-kubernetes-cluster-controller-config
key: spotinst.cluster-identifier
volumeMounts:
- name: spotinst-kubernetes-cluster-controller-certs
mountPath: /certs
volumes:
- name: spotinst-kubernetes-cluster-controller-certs
secret:
secretName: spotinst-kubernetes-cluster-controller-certs
tolerations:
- key: node-role.kubernetes.io/master
effect: NoSchedule
serviceAccountName: spotinst-kubernetes-cluster-controller
---

View File

@ -48,8 +48,10 @@ go_library(
"//pkg/model/domodel:go_default_library",
"//pkg/model/gcemodel:go_default_library",
"//pkg/model/openstackmodel:go_default_library",
"//pkg/model/spotinstmodel:go_default_library",
"//pkg/model/vspheremodel:go_default_library",
"//pkg/resources/digitalocean:go_default_library",
"//pkg/resources/spotinst:go_default_library",
"//pkg/templates:go_default_library",
"//upup/models:go_default_library",
"//upup/pkg/fi:go_default_library",
@ -66,6 +68,7 @@ go_library(
"//upup/pkg/fi/cloudup/gcetasks:go_default_library",
"//upup/pkg/fi/cloudup/openstack:go_default_library",
"//upup/pkg/fi/cloudup/openstacktasks:go_default_library",
"//upup/pkg/fi/cloudup/spotinsttasks:go_default_library",
"//upup/pkg/fi/cloudup/terraform:go_default_library",
"//upup/pkg/fi/cloudup/vsphere:go_default_library",
"//upup/pkg/fi/cloudup/vspheretasks:go_default_library",

View File

@ -45,6 +45,7 @@ import (
"k8s.io/kops/pkg/model/domodel"
"k8s.io/kops/pkg/model/gcemodel"
"k8s.io/kops/pkg/model/openstackmodel"
"k8s.io/kops/pkg/model/spotinstmodel"
"k8s.io/kops/pkg/model/vspheremodel"
"k8s.io/kops/pkg/resources/digitalocean"
"k8s.io/kops/pkg/templates"
@ -62,6 +63,7 @@ import (
"k8s.io/kops/upup/pkg/fi/cloudup/gcetasks"
"k8s.io/kops/upup/pkg/fi/cloudup/openstack"
"k8s.io/kops/upup/pkg/fi/cloudup/openstacktasks"
"k8s.io/kops/upup/pkg/fi/cloudup/spotinsttasks"
"k8s.io/kops/upup/pkg/fi/cloudup/terraform"
"k8s.io/kops/upup/pkg/fi/cloudup/vsphere"
"k8s.io/kops/upup/pkg/fi/cloudup/vspheretasks"
@ -399,6 +401,9 @@ func (c *ApplyClusterCmd) Run() error {
// Autoscaling
"autoscalingGroup": &awstasks.AutoscalingGroup{},
"launchConfiguration": &awstasks.LaunchConfiguration{},
// Spotinst
"spotinstElastigroup": &spotinsttasks.Elastigroup{},
})
if len(sshPublicKeys) == 0 {
@ -673,13 +678,21 @@ func (c *ApplyClusterCmd) Run() error {
KopsModelContext: modelContext,
}
l.Builders = append(l.Builders, &awsmodel.AutoscalingGroupModelBuilder{
AWSModelContext: awsModelContext,
BootstrapScript: bootstrapScriptBuilder,
Lifecycle: &clusterLifecycle,
SecurityLifecycle: &securityLifecycle,
})
if featureflag.Spotinst.Enabled() {
l.Builders = append(l.Builders, &spotinstmodel.ElastigroupModelBuilder{
AWSModelContext: awsModelContext,
BootstrapScript: bootstrapScriptBuilder,
Lifecycle: &clusterLifecycle,
SecurityLifecycle: &securityLifecycle,
})
} else {
l.Builders = append(l.Builders, &awsmodel.AutoscalingGroupModelBuilder{
AWSModelContext: awsModelContext,
BootstrapScript: bootstrapScriptBuilder,
Lifecycle: &clusterLifecycle,
SecurityLifecycle: &securityLifecycle,
})
}
case kops.CloudProviderDO:
doModelContext := &domodel.DOModelContext{
KopsModelContext: modelContext,

View File

@ -21,6 +21,8 @@ go_library(
"//pkg/apis/kops:go_default_library",
"//pkg/apis/kops/model:go_default_library",
"//pkg/cloudinstances:go_default_library",
"//pkg/featureflag:go_default_library",
"//pkg/resources/spotinst:go_default_library",
"//protokube/pkg/etcd:go_default_library",
"//upup/pkg/fi:go_default_library",
"//vendor/github.com/aws/aws-sdk-go/aws:go_default_library",

View File

@ -48,6 +48,8 @@ import (
"k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/apis/kops/model"
"k8s.io/kops/pkg/cloudinstances"
"k8s.io/kops/pkg/featureflag"
"k8s.io/kops/pkg/resources/spotinst"
"k8s.io/kops/upup/pkg/fi"
k8s_aws "k8s.io/kubernetes/pkg/cloudprovider/providers/aws"
)
@ -102,6 +104,7 @@ type AWSCloud interface {
ELBV2() elbv2iface.ELBV2API
Autoscaling() autoscalingiface.AutoScalingAPI
Route53() route53iface.Route53API
Spotinst() spotinst.Service
// TODO: Document and rationalize these tags/filters methods
AddTags(name *string, tags map[string]string)
@ -157,6 +160,7 @@ type awsCloudImplementation struct {
elbv2 *elbv2.ELBV2
autoscaling *autoscaling.AutoScaling
route53 *route53.Route53
spotinst spotinst.Service
region string
@ -265,6 +269,13 @@ func NewAWSCloud(region string, tags map[string]string) (AWSCloud, error) {
c.route53.Handlers.Send.PushFront(requestLogger)
c.addHandlers(region, &c.route53.Handlers)
if featureflag.Spotinst.Enabled() {
c.spotinst, err = spotinst.NewService(kops.CloudProviderAWS)
if err != nil {
return c, err
}
}
awsCloudInstances[region] = c
raw = c
}
@ -325,6 +336,10 @@ func NewEC2Filter(name string, values ...string) *ec2.Filter {
// DeleteGroup deletes an aws autoscaling group
func (c *awsCloudImplementation) DeleteGroup(g *cloudinstances.CloudInstanceGroup) error {
if c.spotinst != nil {
return spotinst.DeleteGroup(c.spotinst, g)
}
return deleteGroup(c, g)
}
@ -366,6 +381,10 @@ func deleteGroup(c AWSCloud, g *cloudinstances.CloudInstanceGroup) error {
// DeleteInstance deletes an aws instance
func (c *awsCloudImplementation) DeleteInstance(i *cloudinstances.CloudInstanceGroupMember) error {
if c.spotinst != nil {
return spotinst.DeleteInstance(c.spotinst, i)
}
return deleteInstance(c, i)
}
@ -393,6 +412,11 @@ func deleteInstance(c AWSCloud, i *cloudinstances.CloudInstanceGroupMember) erro
// GetCloudGroups returns a groups of instances that back a kops instance groups
func (c *awsCloudImplementation) GetCloudGroups(cluster *kops.Cluster, instancegroups []*kops.InstanceGroup, warnUnmatched bool, nodes []v1.Node) (map[string]*cloudinstances.CloudInstanceGroup, error) {
if c.spotinst != nil {
return spotinst.GetCloudGroups(c.spotinst, cluster,
instancegroups, warnUnmatched, nodes)
}
return getCloudGroups(c, cluster, instancegroups, warnUnmatched, nodes)
}
@ -1140,6 +1164,10 @@ func (c *awsCloudImplementation) Route53() route53iface.Route53API {
return c.route53
}
func (c *awsCloudImplementation) Spotinst() spotinst.Service {
return c.spotinst
}
func (c *awsCloudImplementation) FindVPCInfo(vpcID string) (*fi.VPCInfo, error) {
return findVPCInfo(c, vpcID)
}

View File

@ -34,6 +34,7 @@ import (
dnsproviderroute53 "k8s.io/kops/dnsprovider/pkg/dnsprovider/providers/aws/route53"
"k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/cloudinstances"
"k8s.io/kops/pkg/resources/spotinst"
"k8s.io/kops/upup/pkg/fi"
)
@ -78,6 +79,7 @@ type MockCloud struct {
MockRoute53 route53iface.Route53API
MockELB elbiface.ELBAPI
MockELBV2 elbv2iface.ELBV2API
MockSpotinst spotinst.Service
}
func (c *MockAWSCloud) DeleteGroup(g *cloudinstances.CloudInstanceGroup) error {
@ -236,6 +238,13 @@ func (c *MockAWSCloud) Route53() route53iface.Route53API {
return c.MockRoute53
}
func (c *MockAWSCloud) Spotinst() spotinst.Service {
if c.MockSpotinst == nil {
glog.Fatalf("MockSpotinst not set")
}
return c.MockSpotinst
}
func (c *MockAWSCloud) FindVPCInfo(id string) (*fi.VPCInfo, error) {
return findVPCInfo(c, id)
}

View File

@ -445,6 +445,41 @@ func (b *BootstrapChannelBuilder) buildManifest() (*channelsapi.Addons, map[stri
}
}
if featureflag.Spotinst.Enabled() {
key := "spotinst-kubernetes-cluster-controller.addons.k8s.io"
version := "1.0.16"
{
id := "v1.8.0"
location := key + "/" + id + ".yaml"
addons.Spec.Addons = append(addons.Spec.Addons, &channelsapi.AddonSpec{
Name: fi.String(key),
Version: fi.String(version),
Selector: map[string]string{"k8s-addon": key},
Manifest: fi.String(location),
KubernetesVersion: "<1.9.0",
Id: id,
})
manifests[key+"-"+id] = "addons/" + location
}
{
id := "v1.9.0"
location := key + "/" + id + ".yaml"
addons.Spec.Addons = append(addons.Spec.Addons, &channelsapi.AddonSpec{
Name: fi.String(key),
Version: fi.String(version),
Selector: map[string]string{"k8s-addon": key},
Manifest: fi.String(location),
KubernetesVersion: ">=1.9.0",
Id: id,
})
manifests[key+"-"+id] = "addons/" + location
}
}
// The role.kubernetes.io/networking is used to label anything related to a networking addin,
// so that if we switch networking plugins (e.g. calico -> weave or vice-versa), we'll replace the
// old networking plugin, and there won't be old pods "floating around".

View File

@ -0,0 +1,25 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"elastigroup.go",
"elastigroup_fitask.go",
],
importpath = "k8s.io/kops/upup/pkg/fi/cloudup/spotinsttasks",
visibility = ["//visibility:public"],
deps = [
"//pkg/resources/spotinst:go_default_library",
"//upup/pkg/fi:go_default_library",
"//upup/pkg/fi/cloudup/awstasks:go_default_library",
"//upup/pkg/fi/cloudup/awsup:go_default_library",
"//upup/pkg/fi/cloudup/terraform:go_default_library",
"//upup/pkg/fi/utils:go_default_library",
"//vendor/github.com/aws/aws-sdk-go/service/ec2:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/spotinst/spotinst-sdk-go/service/elastigroup/providers/aws:go_default_library",
"//vendor/github.com/spotinst/spotinst-sdk-go/spotinst/client:go_default_library",
"//vendor/github.com/spotinst/spotinst-sdk-go/spotinst/util/stringutil:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
],
)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,75 @@
/*
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.
*/
// Code generated by ""fitask" -type=Elastigroup"; DO NOT EDIT
package spotinsttasks
import (
"encoding/json"
"k8s.io/kops/upup/pkg/fi"
)
// Elastigroup
// JSON marshalling boilerplate
type realElastigroup Elastigroup
// UnmarshalJSON implements conversion to JSON, supporitng an alternate specification of the object as a string
func (o *Elastigroup) UnmarshalJSON(data []byte) error {
var jsonName string
if err := json.Unmarshal(data, &jsonName); err == nil {
o.Name = &jsonName
return nil
}
var r realElastigroup
if err := json.Unmarshal(data, &r); err != nil {
return err
}
*o = Elastigroup(r)
return nil
}
var _ fi.HasLifecycle = &Elastigroup{}
// GetLifecycle returns the Lifecycle of the object, implementing fi.HasLifecycle
func (o *Elastigroup) GetLifecycle() *fi.Lifecycle {
return o.Lifecycle
}
// SetLifecycle sets the Lifecycle of the object, implementing fi.SetLifecycle
func (o *Elastigroup) SetLifecycle(lifecycle fi.Lifecycle) {
o.Lifecycle = &lifecycle
}
var _ fi.HasName = &Elastigroup{}
// GetName returns the Name of the object, implementing fi.HasName
func (o *Elastigroup) GetName() *string {
return o.Name
}
// SetName sets the Name of the object, implementing fi.SetName
func (o *Elastigroup) SetName(name string) {
o.Name = &name
}
// String is the stringer function for the task, producing readable output using fi.TaskAsString
func (o *Elastigroup) String() string {
return fi.TaskAsString(o)
}

View File

@ -37,7 +37,9 @@ import (
"k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/dns"
"k8s.io/kops/pkg/featureflag"
"k8s.io/kops/pkg/model"
"k8s.io/kops/pkg/resources/spotinst"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/gce"
@ -102,6 +104,13 @@ func (tf *TemplateFunctions) AddTo(dest template.FuncMap, secretStore fi.SecretS
return os.Getenv("DIGITALOCEAN_ACCESS_TOKEN")
}
if featureflag.Spotinst.Enabled() {
if creds, err := spotinst.LoadCredentials(); err == nil {
dest["SpotinstToken"] = func() string { return creds.Token }
dest["SpotinstAccount"] = func() string { return creds.Account }
}
}
if tf.cluster.Spec.Networking != nil && tf.cluster.Spec.Networking.Flannel != nil {
flannelBackendType := tf.cluster.Spec.Networking.Flannel.Backend
if flannelBackendType == "" {

201
vendor/github.com/spotinst/spotinst-sdk-go/LICENSE generated vendored Normal file
View File

@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

View File

@ -0,0 +1,17 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["elastigroup.go"],
importmap = "k8s.io/kops/vendor/github.com/spotinst/spotinst-sdk-go/service/elastigroup",
importpath = "github.com/spotinst/spotinst-sdk-go/service/elastigroup",
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/spotinst/spotinst-sdk-go/service/elastigroup/providers/aws:go_default_library",
"//vendor/github.com/spotinst/spotinst-sdk-go/service/elastigroup/providers/azure:go_default_library",
"//vendor/github.com/spotinst/spotinst-sdk-go/service/elastigroup/providers/gce:go_default_library",
"//vendor/github.com/spotinst/spotinst-sdk-go/spotinst:go_default_library",
"//vendor/github.com/spotinst/spotinst-sdk-go/spotinst/client:go_default_library",
"//vendor/github.com/spotinst/spotinst-sdk-go/spotinst/session:go_default_library",
],
)

View File

@ -0,0 +1,47 @@
package elastigroup
import (
"github.com/spotinst/spotinst-sdk-go/service/elastigroup/providers/aws"
"github.com/spotinst/spotinst-sdk-go/service/elastigroup/providers/azure"
"github.com/spotinst/spotinst-sdk-go/service/elastigroup/providers/gce"
"github.com/spotinst/spotinst-sdk-go/spotinst"
"github.com/spotinst/spotinst-sdk-go/spotinst/client"
"github.com/spotinst/spotinst-sdk-go/spotinst/session"
)
// Service provides the API operation methods for making requests to
// endpoints of the Spotinst API. See this package's package overview docs
// for details on the service.
type Service interface {
CloudProviderAWS() aws.Service
CloudProviderAzure() azure.Service
CloudProviderGCE() gce.Service
}
type ServiceOp struct {
Client *client.Client
}
var _ Service = &ServiceOp{}
func New(sess *session.Session, cfgs ...*spotinst.Config) *ServiceOp {
cfg := &spotinst.Config{}
cfg.Merge(sess.Config)
cfg.Merge(cfgs...)
return &ServiceOp{
Client: client.New(cfg),
}
}
func (s *ServiceOp) CloudProviderAWS() aws.Service {
return &aws.ServiceOp{s.Client}
}
func (s *ServiceOp) CloudProviderAzure() azure.Service {
return &azure.ServiceOp{s.Client}
}
func (s *ServiceOp) CloudProviderGCE() gce.Service {
return &gce.ServiceOp{s.Client}
}

View File

@ -0,0 +1,20 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"aws.go",
"service.go",
"tag.go",
],
importmap = "k8s.io/kops/vendor/github.com/spotinst/spotinst-sdk-go/service/elastigroup/providers/aws",
importpath = "github.com/spotinst/spotinst-sdk-go/service/elastigroup/providers/aws",
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/spotinst/spotinst-sdk-go/spotinst:go_default_library",
"//vendor/github.com/spotinst/spotinst-sdk-go/spotinst/client:go_default_library",
"//vendor/github.com/spotinst/spotinst-sdk-go/spotinst/session:go_default_library",
"//vendor/github.com/spotinst/spotinst-sdk-go/spotinst/util/jsonutil:go_default_library",
"//vendor/github.com/spotinst/spotinst-sdk-go/spotinst/util/uritemplates:go_default_library",
],
)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,44 @@
package aws
import (
"context"
"github.com/spotinst/spotinst-sdk-go/spotinst"
"github.com/spotinst/spotinst-sdk-go/spotinst/client"
"github.com/spotinst/spotinst-sdk-go/spotinst/session"
)
// Service provides the API operation methods for making requests to
// endpoints of the Spotinst API. See this package's package overview docs
// for details on the service.
type Service interface {
List(context.Context, *ListGroupsInput) (*ListGroupsOutput, error)
Create(context.Context, *CreateGroupInput) (*CreateGroupOutput, error)
Read(context.Context, *ReadGroupInput) (*ReadGroupOutput, error)
Update(context.Context, *UpdateGroupInput) (*UpdateGroupOutput, error)
Delete(context.Context, *DeleteGroupInput) (*DeleteGroupOutput, error)
Status(context.Context, *StatusGroupInput) (*StatusGroupOutput, error)
Detach(context.Context, *DetachGroupInput) (*DetachGroupOutput, error)
Roll(context.Context, *RollGroupInput) (*RollGroupOutput, error)
Scale(context.Context, *ScaleGroupInput) (*ScaleGroupOutput, error)
ImportBeanstalkEnv(context.Context, *ImportBeanstalkInput) (*ImportBeanstalkOutput, error)
StartBeanstalkMaintenance(context.Context, *BeanstalkMaintenanceInput) (*BeanstalkMaintenanceOutput, error)
FinishBeanstalkMaintenance(context.Context, *BeanstalkMaintenanceInput) (*BeanstalkMaintenanceOutput, error)
GetBeanstalkMaintenanceStatus(context.Context, *BeanstalkMaintenanceInput) (*string, error)
}
type ServiceOp struct {
Client *client.Client
}
var _ Service = &ServiceOp{}
func New(sess *session.Session, cfgs ...*spotinst.Config) *ServiceOp {
cfg := &spotinst.Config{}
cfg.Merge(sess.Config)
cfg.Merge(cfgs...)
return &ServiceOp{
Client: client.New(sess.Config),
}
}

View File

@ -0,0 +1,31 @@
package aws
import "github.com/spotinst/spotinst-sdk-go/spotinst/util/jsonutil"
type Tag struct {
Key *string `json:"tagKey,omitempty"`
Value *string `json:"tagValue,omitempty"`
forceSendFields []string `json:"-"`
nullFields []string `json:"-"`
}
func (o *Tag) MarshalJSON() ([]byte, error) {
type noMethod Tag
raw := noMethod(*o)
return jsonutil.MarshalJSON(raw, o.forceSendFields, o.nullFields)
}
func (o *Tag) SetKey(v *string) *Tag {
if o.Key = v; o.Key == nil {
o.nullFields = append(o.nullFields, "Key")
}
return o
}
func (o *Tag) SetValue(v *string) *Tag {
if o.Value = v; o.Value == nil {
o.nullFields = append(o.nullFields, "Value")
}
return o
}

View File

@ -0,0 +1,20 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"azure.go",
"service.go",
"tag.go",
],
importmap = "k8s.io/kops/vendor/github.com/spotinst/spotinst-sdk-go/service/elastigroup/providers/azure",
importpath = "github.com/spotinst/spotinst-sdk-go/service/elastigroup/providers/azure",
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/spotinst/spotinst-sdk-go/spotinst:go_default_library",
"//vendor/github.com/spotinst/spotinst-sdk-go/spotinst/client:go_default_library",
"//vendor/github.com/spotinst/spotinst-sdk-go/spotinst/session:go_default_library",
"//vendor/github.com/spotinst/spotinst-sdk-go/spotinst/util/jsonutil:go_default_library",
"//vendor/github.com/spotinst/spotinst-sdk-go/spotinst/util/uritemplates:go_default_library",
],
)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,39 @@
package azure
import (
"context"
"github.com/spotinst/spotinst-sdk-go/spotinst"
"github.com/spotinst/spotinst-sdk-go/spotinst/client"
"github.com/spotinst/spotinst-sdk-go/spotinst/session"
)
// Service provides the API operation methods for making requests to
// endpoints of the Spotinst API. See this package's package overview docs
// for details on the service.
type Service interface {
List(context.Context, *ListGroupsInput) (*ListGroupsOutput, error)
Create(context.Context, *CreateGroupInput) (*CreateGroupOutput, error)
Read(context.Context, *ReadGroupInput) (*ReadGroupOutput, error)
Update(context.Context, *UpdateGroupInput) (*UpdateGroupOutput, error)
Delete(context.Context, *DeleteGroupInput) (*DeleteGroupOutput, error)
Status(context.Context, *StatusGroupInput) (*StatusGroupOutput, error)
Detach(context.Context, *DetachGroupInput) (*DetachGroupOutput, error)
Roll(context.Context, *RollGroupInput) (*RollGroupOutput, error)
}
type ServiceOp struct {
Client *client.Client
}
var _ Service = &ServiceOp{}
func New(sess *session.Session, cfgs ...*spotinst.Config) *ServiceOp {
cfg := &spotinst.Config{}
cfg.Merge(sess.Config)
cfg.Merge(cfgs...)
return &ServiceOp{
Client: client.New(sess.Config),
}
}

View File

@ -0,0 +1,31 @@
package azure
import "github.com/spotinst/spotinst-sdk-go/spotinst/util/jsonutil"
type Tag struct {
Key *string `json:"tagKey,omitempty"`
Value *string `json:"tagValue,omitempty"`
forceSendFields []string `json:"-"`
nullFields []string `json:"-"`
}
func (o *Tag) MarshalJSON() ([]byte, error) {
type noMethod Tag
raw := noMethod(*o)
return jsonutil.MarshalJSON(raw, o.forceSendFields, o.nullFields)
}
func (o *Tag) SetKey(v *string) *Tag {
if o.Key = v; o.Key == nil {
o.nullFields = append(o.nullFields, "Key")
}
return o
}
func (o *Tag) SetValue(v *string) *Tag {
if o.Value = v; o.Value == nil {
o.nullFields = append(o.nullFields, "Value")
}
return o
}

View File

@ -0,0 +1,18 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"service.go",
"tag.go",
],
importmap = "k8s.io/kops/vendor/github.com/spotinst/spotinst-sdk-go/service/elastigroup/providers/gce",
importpath = "github.com/spotinst/spotinst-sdk-go/service/elastigroup/providers/gce",
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/spotinst/spotinst-sdk-go/spotinst:go_default_library",
"//vendor/github.com/spotinst/spotinst-sdk-go/spotinst/client:go_default_library",
"//vendor/github.com/spotinst/spotinst-sdk-go/spotinst/session:go_default_library",
"//vendor/github.com/spotinst/spotinst-sdk-go/spotinst/util/jsonutil:go_default_library",
],
)

View File

@ -0,0 +1,28 @@
package gce
import (
"github.com/spotinst/spotinst-sdk-go/spotinst"
"github.com/spotinst/spotinst-sdk-go/spotinst/client"
"github.com/spotinst/spotinst-sdk-go/spotinst/session"
)
// Service provides the API operation methods for making requests to
// endpoints of the Spotinst API. See this package's package overview docs
// for details on the service.
type Service interface{}
type ServiceOp struct {
Client *client.Client
}
var _ Service = &ServiceOp{}
func New(sess *session.Session, cfgs ...*spotinst.Config) *ServiceOp {
cfg := &spotinst.Config{}
cfg.Merge(sess.Config)
cfg.Merge(cfgs...)
return &ServiceOp{
Client: client.New(sess.Config),
}
}

View File

@ -0,0 +1,31 @@
package gce
import "github.com/spotinst/spotinst-sdk-go/spotinst/util/jsonutil"
type Tag struct {
Key *string `json:"tagKey,omitempty"`
Value *string `json:"tagValue,omitempty"`
forceSendFields []string `json:"-"`
nullFields []string `json:"-"`
}
func (o *Tag) MarshalJSON() ([]byte, error) {
type noMethod Tag
raw := noMethod(*o)
return jsonutil.MarshalJSON(raw, o.forceSendFields, o.nullFields)
}
func (o *Tag) SetKey(v *string) *Tag {
if o.Key = v; o.Key == nil {
o.nullFields = append(o.nullFields, "Key")
}
return o
}
func (o *Tag) SetValue(v *string) *Tag {
if o.Value = v; o.Value == nil {
o.nullFields = append(o.nullFields, "Value")
}
return o
}

View File

@ -0,0 +1,17 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"config.go",
"types.go",
"version.go",
],
importmap = "k8s.io/kops/vendor/github.com/spotinst/spotinst-sdk-go/spotinst",
importpath = "github.com/spotinst/spotinst-sdk-go/spotinst",
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/spotinst/spotinst-sdk-go/spotinst/credentials:go_default_library",
"//vendor/github.com/spotinst/spotinst-sdk-go/spotinst/log:go_default_library",
],
)

View File

@ -0,0 +1,14 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"client.go",
"request.go",
"response.go",
],
importmap = "k8s.io/kops/vendor/github.com/spotinst/spotinst-sdk-go/spotinst/client",
importpath = "github.com/spotinst/spotinst-sdk-go/spotinst/client",
visibility = ["//visibility:public"],
deps = ["//vendor/github.com/spotinst/spotinst-sdk-go/spotinst:go_default_library"],
)

View File

@ -0,0 +1,81 @@
package client
import (
"context"
"net/http"
"net/http/httputil"
"net/url"
"github.com/spotinst/spotinst-sdk-go/spotinst"
)
// Client provides a client to the API.
type Client struct {
config *spotinst.Config
}
// New returns a new client.
func New(cfg *spotinst.Config) *Client {
if cfg == nil {
cfg = spotinst.DefaultConfig()
}
return &Client{cfg}
}
// NewRequest is used to create a new request.
func NewRequest(method, path string) *Request {
return &Request{
method: method,
url: &url.URL{
Path: path,
},
header: make(http.Header),
Params: make(url.Values),
}
}
// Do runs a request with our client.
func (c *Client) Do(ctx context.Context, r *Request) (*http.Response, error) {
req, err := r.toHTTP(ctx, c.config)
if err != nil {
return nil, err
}
c.logRequest(req)
resp, err := c.config.HTTPClient.Do(req)
c.logResponse(resp)
return resp, err
}
func (c *Client) logf(format string, args ...interface{}) {
if c.config.Logger != nil {
c.config.Logger.Printf(format, args...)
}
}
const logReqMsg = `SPOTINST: Request "%s %s" details:
---[ REQUEST ]---------------------------------------
%s
-----------------------------------------------------`
func (c *Client) logRequest(req *http.Request) {
if c.config.Logger != nil && req != nil {
out, err := httputil.DumpRequestOut(req, true)
if err == nil {
c.logf(logReqMsg, req.Method, req.URL, string(out))
}
}
}
const logRespMsg = `SPOTINST: Response "%s %s" details:
---[ RESPONSE ]----------------------------------------
%s
-------------------------------------------------------`
func (c *Client) logResponse(resp *http.Response) {
if c.config.Logger != nil && resp != nil {
out, err := httputil.DumpResponse(resp, true)
if err == nil {
c.logf(logRespMsg, resp.Request.Method, resp.Request.URL, string(out))
}
}
}

View File

@ -0,0 +1,76 @@
package client
import (
"bytes"
"context"
"encoding/json"
"io"
"net/http"
"net/url"
"github.com/spotinst/spotinst-sdk-go/spotinst"
)
type Request struct {
Obj interface{}
Params url.Values
url *url.URL
method string
body io.Reader
header http.Header
}
// toHTTP converts the request to an HTTP request.
func (r *Request) toHTTP(ctx context.Context, cfg *spotinst.Config) (*http.Request, error) {
// Set the user credentials.
creds, err := cfg.Credentials.Get()
if err != nil {
return nil, err
}
if creds.Token != "" {
r.header.Set("Authorization", "Bearer "+creds.Token)
}
if creds.Account != "" {
r.Params.Set("accountId", creds.Account)
}
// Encode the query parameters.
r.url.RawQuery = r.Params.Encode()
// Check if we should encode the body.
if r.body == nil && r.Obj != nil {
if b, err := EncodeBody(r.Obj); err != nil {
return nil, err
} else {
r.body = b
}
}
// Create the HTTP request.
req, err := http.NewRequest(r.method, r.url.RequestURI(), r.body)
if err != nil {
return nil, err
}
// Set request base URL.
req.URL.Host = cfg.BaseURL.Host
req.URL.Scheme = cfg.BaseURL.Scheme
// Set request headers.
req.Host = cfg.BaseURL.Host
req.Header = r.header
req.Header.Set("Content-Type", cfg.ContentType)
req.Header.Add("Accept", cfg.ContentType)
req.Header.Add("User-Agent", cfg.UserAgent)
return req.WithContext(ctx), nil
}
// EncodeBody is used to encode a request body
func EncodeBody(obj interface{}) (io.Reader, error) {
buf := bytes.NewBuffer(nil)
if err := json.NewEncoder(buf).Encode(obj); err != nil {
return nil, err
}
return buf, nil
}

View File

@ -0,0 +1,110 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"strconv"
)
type Response struct {
Request struct {
ID string `json:"id"`
} `json:"request"`
Response struct {
Errors []responseError `json:"errors"`
Items []json.RawMessage `json:"items"`
} `json:"response"`
}
type responseError struct {
Code string `json:"code"`
Message string `json:"message"`
Field string `json:"field"`
}
type Error struct {
Response *http.Response `json:"-"`
Code string `json:"code"`
Message string `json:"message"`
Field string `json:"field"`
RequestID string `json:"requestId"`
}
func (e Error) Error() string {
msg := fmt.Sprintf("%v %v: %d (request: %q) %v: %v",
e.Response.Request.Method, e.Response.Request.URL,
e.Response.StatusCode, e.RequestID, e.Code, e.Message)
if e.Field != "" {
msg = fmt.Sprintf("%s (field: %v)", msg, e.Field)
}
return msg
}
type Errors []Error
func (es Errors) Error() string {
var stack string
for _, e := range es {
stack += e.Error() + "\n"
}
return stack
}
// DecodeBody is used to JSON decode a body
func DecodeBody(resp *http.Response, out interface{}) error {
return json.NewDecoder(resp.Body).Decode(out)
}
// RequireOK is used to verify response status code is a successful one (200 OK)
func RequireOK(resp *http.Response, err error) (*http.Response, error) {
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, extractError(resp)
}
return resp, nil
}
// extractError is used to extract inner/logical errors from the response
func extractError(resp *http.Response) error {
buf := bytes.NewBuffer(nil)
// TeeReader returns a Reader that writes to b what it reads from r.Body.
reader := io.TeeReader(resp.Body, buf)
defer resp.Body.Close()
resp.Body = ioutil.NopCloser(buf)
var out Response
if err := json.NewDecoder(reader).Decode(&out); err != nil {
return err
}
var errors Errors
if errs := out.Response.Errors; len(errs) > 0 {
for _, err := range errs {
errors = append(errors, Error{
Response: resp,
RequestID: out.Request.ID,
Code: err.Code,
Message: err.Message,
Field: err.Field,
})
}
} else {
errors = append(errors, Error{
Response: resp,
RequestID: out.Request.ID,
Code: strconv.Itoa(resp.StatusCode),
Message: http.StatusText(resp.StatusCode),
})
}
return errors
}

View File

@ -0,0 +1,161 @@
package spotinst
import (
"fmt"
"net"
"net/http"
"net/url"
"time"
"github.com/spotinst/spotinst-sdk-go/spotinst/credentials"
"github.com/spotinst/spotinst-sdk-go/spotinst/log"
)
const (
// defaultBaseURL is the default base URL of the Spotinst API.
// It is used e.g. when initializing a new Client without a specific address.
defaultBaseURL = "https://api.spotinst.io"
// defaultContentType is the default content type to use when making HTTP
// calls.
defaultContentType = "application/json"
// defaultUserAgent is the default user agent to use when making HTTP
// calls.
defaultUserAgent = SDKName + "/" + SDKVersion
// defaultMaxRetries is the number of retries for a single request after
// the client will give up and return an error. It is zero by default, so
// retry is disabled by default.
defaultMaxRetries = 0
// defaultGzipEnabled specifies if gzip compression is enabled by default.
defaultGzipEnabled = false
)
// A Config provides Configuration to a service client instance.
type Config struct {
BaseURL *url.URL
HTTPClient *http.Client
Credentials *credentials.Credentials
Logger log.Logger
UserAgent string
ContentType string
}
func DefaultBaseURL() *url.URL {
baseURL, _ := url.Parse(defaultBaseURL)
return baseURL
}
// DefaultTransport returns a new http.Transport with similar default
// values to http.DefaultTransport. Do not use this for transient transports as
// it can leak file descriptors over time. Only use this for transports that
// will be re-used for the same host(s).
func DefaultTransport() *http.Transport {
return &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).DialContext,
TLSHandshakeTimeout: 10 * time.Second,
DisableKeepAlives: false,
MaxIdleConnsPerHost: 1,
}
}
// DefaultHTTPClient returns a new http.Client with similar default values to
// http.Client, but with a non-shared Transport, idle connections disabled, and
// KeepAlives disabled.
func DefaultHTTPClient() *http.Client {
return &http.Client{
Transport: DefaultTransport(),
}
}
// DefaultConfig returns a default configuration for the client. By default this
// will pool and reuse idle connections to API. If you have a long-lived
// client object, this is the desired behavior and should make the most efficient
// use of the connections to API.
func DefaultConfig() *Config {
return &Config{
BaseURL: DefaultBaseURL(),
HTTPClient: DefaultHTTPClient(),
UserAgent: defaultUserAgent,
ContentType: defaultContentType,
Credentials: credentials.NewChainCredentials(
new(credentials.EnvProvider),
new(credentials.FileProvider),
),
}
}
// WithBaseURL defines the base URL of the Spotinst API.
func (c *Config) WithBaseURL(rawurl string) *Config {
baseURL, _ := url.Parse(rawurl)
c.BaseURL = baseURL
return c
}
// WithHTTPClient defines the HTTP client.
func (c *Config) WithHTTPClient(client *http.Client) *Config {
c.HTTPClient = client
return c
}
// WithCredentials defines the credentials.
func (c *Config) WithCredentials(creds *credentials.Credentials) *Config {
c.Credentials = creds
return c
}
// WithUserAgent defines the user agent.
func (c *Config) WithUserAgent(ua string) *Config {
c.UserAgent = fmt.Sprintf("%s,%s", ua, c.UserAgent)
return c
}
// WithContentType defines the content type.
func (c *Config) WithContentType(ct string) *Config {
c.ContentType = ct
return c
}
// WithLogger defines the logger for informational messages, e.g. requests
// and their response times. It is nil by default.
func (c *Config) WithLogger(logger log.Logger) *Config {
c.Logger = logger
return c
}
// Merge merges the passed in configs into the existing config object.
func (c *Config) Merge(cfgs ...*Config) {
for _, other := range cfgs {
mergeConfig(c, other)
}
}
func mergeConfig(dst *Config, other *Config) {
if other == nil {
return
}
if other.BaseURL != nil {
dst.BaseURL = other.BaseURL
}
if other.Credentials != nil {
dst.Credentials = other.Credentials
}
if other.HTTPClient != nil {
dst.HTTPClient = other.HTTPClient
}
if other.UserAgent != "" {
dst.UserAgent = other.UserAgent
}
if other.ContentType != "" {
dst.ContentType = other.ContentType
}
if other.Logger != nil {
dst.Logger = other.Logger
}
}

View File

@ -0,0 +1,15 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"credentials.go",
"provider_chain.go",
"provider_env.go",
"provider_file.go",
"provider_static.go",
],
importmap = "k8s.io/kops/vendor/github.com/spotinst/spotinst-sdk-go/spotinst/credentials",
importpath = "github.com/spotinst/spotinst-sdk-go/spotinst/credentials",
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,86 @@
package credentials
import (
"fmt"
"sync"
)
// A Value is the Spotinst credentials value for individual credential fields.
type Value struct {
// Spotinst API token.
Token string `json:"token"`
// Spotinst account ID.
Account string `json:"account"`
// Provider used to get credentials.
ProviderName string `json:"-"`
}
// A Provider is the interface for any component which will provide credentials
// Value.
//
// The Provider should not need to implement its own mutexes, because
// that will be managed by Credentials.
type Provider interface {
fmt.Stringer
// Refresh returns nil if it successfully retrieved the value.
// Error is returned if the value were not obtainable, or empty.
Retrieve() (Value, error)
}
// A Credentials provides synchronous safe retrieval of Spotinst credentials.
// Credentials will cache the credentials value.
//
// Credentials is safe to use across multiple goroutines and will manage the
// synchronous state so the Providers do not need to implement their own
// synchronization.
//
// The first Credentials.Get() will always call Provider.Retrieve() to get the
// first instance of the credentials Value. All calls to Get() after that
// will return the cached credentials Value.
type Credentials struct {
provider Provider
mu sync.Mutex
forceRefresh bool
creds Value
}
// NewCredentials returns a pointer to a new Credentials with the provider set.
func NewCredentials(provider Provider) *Credentials {
return &Credentials{
provider: provider,
forceRefresh: true,
}
}
// Get returns the credentials value, or error if the credentials Value failed
// to be retrieved.
//
// Will return the cached credentials Value. If the credentials Value is empty
// the Provider's Retrieve() will be called to refresh the credentials.
func (c *Credentials) Get() (Value, error) {
c.mu.Lock()
defer c.mu.Unlock()
if c.creds.Token == "" || c.forceRefresh {
creds, err := c.provider.Retrieve()
if err != nil {
return Value{}, err
}
c.creds = creds
c.forceRefresh = false
}
return c.creds, nil
}
// Refresh refreshes the credentials and forces them to be retrieved on the
// next call to Get().
func (c *Credentials) Refresh() {
c.mu.Lock()
defer c.mu.Unlock()
c.forceRefresh = true
}

View File

@ -0,0 +1,102 @@
package credentials
import (
"errors"
"fmt"
)
// ErrNoValidProvidersFoundInChain Is returned when there are no valid
// credentials providers in the ChainProvider.
var ErrNoValidProvidersFoundInChain = errors.New("spotinst: no valid credentials providers in chain")
// A ChainProvider will search for a provider which returns credentials
// and cache that provider until Retrieve is called again.
//
// The ChainProvider provides a way of chaining multiple providers together
// which will pick the first available using priority order of the Providers
// in the list.
//
// If none of the Providers retrieve valid credentials Value, ChainProvider's
// Retrieve() will return the error ErrNoValidProvidersFoundInChain.
//
// If a Provider is found which returns valid credentials Value ChainProvider
// will cache that Provider for all calls until Retrieve is called again.
//
// Example of ChainProvider to be used with an EnvCredentialsProvider and
// FileCredentialsProvider. In this example EnvProvider will first check if
// any credentials are available via the environment variables. If there are
// none ChainProvider will check the next Provider in the list, FileProvider
// in this case. If FileCredentialsProvider does not return any credentials
// ChainProvider will return the error ErrNoValidProvidersFoundInChain.
//
// creds := credentials.NewChainCredentials(
// new(credentials.EnvProvider),
// new(credentials.FileProvider),
// )
type ChainProvider struct {
Providers []Provider
active Provider
}
// NewChainCredentials returns a pointer to a new Credentials object
// wrapping a chain of providers.
func NewChainCredentials(providers ...Provider) *Credentials {
return NewCredentials(&ChainProvider{
Providers: providers,
})
}
// Retrieve returns the credentials value or error if no provider returned
// without error. If a provider is found it will be cached.
func (c *ChainProvider) Retrieve() (Value, error) {
var errs errorList
for _, p := range c.Providers {
value, err := p.Retrieve()
if err == nil {
c.active = p
return value, nil
}
errs = append(errs, err)
}
c.active = nil
err := ErrNoValidProvidersFoundInChain
if len(errs) > 0 {
err = errs
}
return Value{}, err
}
func (c *ChainProvider) String() string {
var out string
for i, provider := range c.Providers {
out += provider.String()
if i < len(c.Providers)-1 {
out += " "
}
}
return out
}
// An error list that satisfies the error interface.
type errorList []error
// Error returns the string representation of the error.
//
// Satisfies the error interface.
func (e errorList) Error() string {
msg := ""
if size := len(e); size > 0 {
for i := 0; i < size; i++ {
msg += fmt.Sprintf("%s", e[i].Error())
// We check the next index to see if it is within the slice.
// If it is, then we append a newline. We do this, because unit tests
// could be broken with the additional '\n'.
if i+1 < size {
msg += "\n"
}
}
}
return msg
}

View File

@ -0,0 +1,62 @@
package credentials
import (
"fmt"
"os"
)
const (
// EnvCredentialsProviderName provides a name of Env provider.
EnvCredentialsProviderName = "EnvCredentialsProvider"
// EnvCredentialsVarToken specifies the name of the environment variable
// points to the Spotinst Token.
EnvCredentialsVarToken = "SPOTINST_TOKEN"
// EnvCredentialsVarAccount specifies the name of the environment variable
// points to the Spotinst account ID.
EnvCredentialsVarAccount = "SPOTINST_ACCOUNT"
)
// ErrEnvCredentialsTokenNotFound is returned when the Spotinst Token can't be
// found in the process's environment.
var ErrEnvCredentialsTokenNotFound = fmt.Errorf("spotinst: %s not found in environment", EnvCredentialsVarToken)
// A EnvProvider retrieves credentials from the environment variables of the
// running process.
//
// Environment variables used:
// * Token: SPOTINST_TOKEN
type EnvProvider struct {
retrieved bool
}
// NewEnvCredentials returns a pointer to a new Credentials object
// wrapping the environment variable provider.
func NewEnvCredentials() *Credentials {
return NewCredentials(&EnvProvider{})
}
// Retrieve retrieves the keys from the environment.
func (e *EnvProvider) Retrieve() (Value, error) {
e.retrieved = false
token := os.Getenv(EnvCredentialsVarToken)
if token == "" {
return Value{ProviderName: EnvCredentialsProviderName},
ErrEnvCredentialsTokenNotFound
}
e.retrieved = true
value := Value{
Token: token,
Account: os.Getenv(EnvCredentialsVarAccount),
ProviderName: EnvCredentialsProviderName,
}
return value, nil
}
func (e *EnvProvider) String() string {
return EnvCredentialsProviderName
}

View File

@ -0,0 +1,129 @@
package credentials
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
)
const (
// FileCredentialsProviderName provides a name of File provider.
FileCredentialsProviderName = "FileCredentialsProvider"
// FileCredentialsEnvVarFile specifies the name of the environment variable
// points to the location of the credentials file.
FileCredentialsEnvVarFile = "SPOTINST_CREDENTIALS_FILE"
)
var (
// ErrFileCredentialsHomeNotFound is emitted when the user directory
// cannot be found.
ErrFileCredentialsHomeNotFound = errors.New("spotinst: user home directory not found")
// ErrFileCredentialsLoadFailed is emitted when the provider is unable to
// load credentials from the credentials file.
ErrFileCredentialsLoadFailed = errors.New("spotinst: failed to load credentials file")
// ErrFileCredentialsTokenNotFound is emitted when the loaded credentials
// did not contain a valid token.
ErrFileCredentialsTokenNotFound = errors.New("spotinst: credentials did not contain token")
)
// A FileProvider retrieves credentials from the current user's home
// directory.
type FileProvider struct {
// Path to the credentials file.
//
// If empty will look for FileCredentialsEnvVarFile env variable. If the
// env value is empty will default to current user's home directory.
// Linux/OSX: "$HOME/.spotinst/credentials.json"
// Windows: "%USERPROFILE%\.spotinst\credentials.json"
Filename string
// retrieved states if the credentials have been successfully retrieved.
retrieved bool
}
// NewFileCredentials returns a pointer to a new Credentials object
// wrapping the file provider.
func NewFileCredentials(filename string) *Credentials {
return NewCredentials(&FileProvider{
Filename: filename,
})
}
// Retrieve reads and extracts the shared credentials from the current
// users home directory.
func (p *FileProvider) Retrieve() (Value, error) {
p.retrieved = false
filename, err := p.filename()
if err != nil {
return Value{ProviderName: FileCredentialsProviderName}, err
}
creds, err := p.loadCredentials(filename)
if err != nil {
return Value{ProviderName: FileCredentialsProviderName}, err
}
if len(creds.ProviderName) == 0 {
creds.ProviderName = FileCredentialsProviderName
}
p.retrieved = true
return creds, nil
}
func (p *FileProvider) String() string {
return FileCredentialsProviderName
}
// filename returns the filename to use to read Spotinst credentials.
//
// Will return an error if the user's home directory path cannot be found.
func (p *FileProvider) filename() (string, error) {
if p.Filename == "" {
if p.Filename = os.Getenv(FileCredentialsEnvVarFile); p.Filename != "" {
return p.Filename, nil
}
homeDir := os.Getenv("HOME") // *nix
if homeDir == "" { // Windows
homeDir = os.Getenv("USERPROFILE")
}
if homeDir == "" {
return "", ErrFileCredentialsHomeNotFound
}
p.Filename = filepath.Join(homeDir, ".spotinst", "credentials")
}
return p.Filename, nil
}
// loadCredentials loads the credentials from the file pointed to by filename.
// The credentials retrieved from the profile will be returned or error. Error will be
// returned if it fails to read from the file, or the data is invalid.
func (p *FileProvider) loadCredentials(filename string) (Value, error) {
f, err := os.Open(filename)
if err != nil {
return Value{ProviderName: FileCredentialsProviderName},
fmt.Errorf("%s: %s", ErrFileCredentialsLoadFailed.Error(), err)
}
defer f.Close()
var value Value
if err := json.NewDecoder(f).Decode(&value); err != nil {
return Value{ProviderName: FileCredentialsProviderName},
fmt.Errorf("%s: %s", ErrFileCredentialsLoadFailed.Error(), err)
}
if token := value.Token; len(token) == 0 {
return Value{ProviderName: FileCredentialsProviderName},
ErrFileCredentialsTokenNotFound
}
return value, nil
}

View File

@ -0,0 +1,41 @@
package credentials
import (
"errors"
)
// StaticCredentialsProviderName provides a name of Static provider.
const StaticCredentialsProviderName = "StaticProvider"
// ErrStaticCredentialsEmpty is emitted when static credentials are empty.
var ErrStaticCredentialsEmpty = errors.New("spotinst: static credentials are empty")
// A StaticProvider is a set of credentials which are set programmatically.
type StaticProvider struct {
Value
}
// NewStaticCredentials returns a pointer to a new Credentials object
// wrapping a static credentials value provider.
func NewStaticCredentials(token, account string) *Credentials {
return NewCredentials(&StaticProvider{Value: Value{
Token: token,
Account: account,
}})
}
// Retrieve returns the credentials or error if the credentials are invalid.
func (s *StaticProvider) Retrieve() (Value, error) {
if s.Token == "" {
return Value{ProviderName: StaticCredentialsProviderName},
ErrStaticCredentialsEmpty
}
if len(s.Value.ProviderName) == 0 {
s.Value.ProviderName = StaticCredentialsProviderName
}
return s.Value, nil
}
func (s *StaticProvider) String() string {
return StaticCredentialsProviderName
}

View File

@ -0,0 +1,9 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["log.go"],
importmap = "k8s.io/kops/vendor/github.com/spotinst/spotinst-sdk-go/spotinst/log",
importpath = "github.com/spotinst/spotinst-sdk-go/spotinst/log",
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,26 @@
package log
import (
"log"
"os"
)
// DefaultStdLogger represents the default logging object that
// generates lines of output to os.Stderr.
var DefaultStdLogger Logger = log.New(os.Stderr, "", log.LstdFlags)
// Logger specifies the interface for all log operations.
type Logger interface {
Printf(format string, args ...interface{})
}
// The LoggerFunc type is an adapter to allow the use of
// ordinary functions as Logger. If f is a function
// with the appropriate signature, LoggerFunc(f) is a
// Logger that calls f.
type LoggerFunc func(format string, args ...interface{})
// Printf calls f(format, args).
func (f LoggerFunc) Printf(format string, args ...interface{}) {
f(format, args...)
}

View File

@ -0,0 +1,10 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["session.go"],
importmap = "k8s.io/kops/vendor/github.com/spotinst/spotinst-sdk-go/spotinst/session",
importpath = "github.com/spotinst/spotinst-sdk-go/spotinst/session",
visibility = ["//visibility:public"],
deps = ["//vendor/github.com/spotinst/spotinst-sdk-go/spotinst:go_default_library"],
)

View File

@ -0,0 +1,22 @@
package session
import (
"github.com/spotinst/spotinst-sdk-go/spotinst"
)
// A Session provides a central location to create service clients.
//
// Sessions are safe to create service clients concurrently, but it is not safe
// to mutate the Session concurrently.
type Session struct {
Config *spotinst.Config
}
// New creates a new instance of Session. Once the Session is created it
// can be mutated to modify the Config. The Session is safe to be read
// concurrently, but it should not be written to concurrently.
func New(cfgs ...*spotinst.Config) *Session {
s := &Session{Config: spotinst.DefaultConfig()}
s.Config.Merge(cfgs...)
return s
}

View File

@ -0,0 +1,357 @@
package spotinst
import "time"
// String returns a pointer to of the string value passed in.
func String(v string) *string {
return &v
}
// StringValue returns the value of the string pointer passed in or
// "" if the pointer is nil.
func StringValue(v *string) string {
if v != nil {
return *v
}
return ""
}
// StringSlice converts a slice of string values into a slice of
// string pointers
func StringSlice(src []string) []*string {
dst := make([]*string, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// StringValueSlice converts a slice of string pointers into a slice of
// string values
func StringValueSlice(src []*string) []string {
dst := make([]string, len(src))
for i := 0; i < len(src); i++ {
if src[i] != nil {
dst[i] = *(src[i])
}
}
return dst
}
// StringMap converts a string map of string values into a string
// map of string pointers
func StringMap(src map[string]string) map[string]*string {
dst := make(map[string]*string)
for k, val := range src {
v := val
dst[k] = &v
}
return dst
}
// StringValueMap converts a string map of string pointers into a string
// map of string values
func StringValueMap(src map[string]*string) map[string]string {
dst := make(map[string]string)
for k, val := range src {
if val != nil {
dst[k] = *val
}
}
return dst
}
// Bool returns a pointer to of the bool value passed in.
func Bool(v bool) *bool {
return &v
}
// BoolValue returns the value of the bool pointer passed in or
// false if the pointer is nil.
func BoolValue(v *bool) bool {
if v != nil {
return *v
}
return false
}
// BoolSlice converts a slice of bool values into a slice of
// bool pointers
func BoolSlice(src []bool) []*bool {
dst := make([]*bool, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// BoolValueSlice converts a slice of bool pointers into a slice of
// bool values
func BoolValueSlice(src []*bool) []bool {
dst := make([]bool, len(src))
for i := 0; i < len(src); i++ {
if src[i] != nil {
dst[i] = *(src[i])
}
}
return dst
}
// BoolMap converts a string map of bool values into a string
// map of bool pointers
func BoolMap(src map[string]bool) map[string]*bool {
dst := make(map[string]*bool)
for k, val := range src {
v := val
dst[k] = &v
}
return dst
}
// BoolValueMap converts a string map of bool pointers into a string
// map of bool values
func BoolValueMap(src map[string]*bool) map[string]bool {
dst := make(map[string]bool)
for k, val := range src {
if val != nil {
dst[k] = *val
}
}
return dst
}
// Int returns a pointer to of the int value passed in.
func Int(v int) *int {
return &v
}
// IntValue returns the value of the int pointer passed in or
// 0 if the pointer is nil.
func IntValue(v *int) int {
if v != nil {
return *v
}
return 0
}
// IntSlice converts a slice of int values into a slice of
// int pointers.
func IntSlice(src []int) []*int {
dst := make([]*int, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// IntValueSlice converts a slice of int pointers into a slice of
// int values.
func IntValueSlice(src []*int) []int {
dst := make([]int, len(src))
for i := 0; i < len(src); i++ {
if src[i] != nil {
dst[i] = *(src[i])
}
}
return dst
}
// IntMap converts a string map of int values into a string
// map of int pointers.
func IntMap(src map[string]int) map[string]*int {
dst := make(map[string]*int)
for k, val := range src {
v := val
dst[k] = &v
}
return dst
}
// IntValueMap converts a string map of int pointers into a string
// map of int values.
func IntValueMap(src map[string]*int) map[string]int {
dst := make(map[string]int)
for k, val := range src {
if val != nil {
dst[k] = *val
}
}
return dst
}
// Int64 returns a pointer to of the int64 value passed in.
func Int64(v int64) *int64 {
return &v
}
// Int64Value returns the value of the int64 pointer passed in or
// 0 if the pointer is nil.
func Int64Value(v *int64) int64 {
if v != nil {
return *v
}
return 0
}
// Int64Slice converts a slice of int64 values into a slice of
// int64 pointers.
func Int64Slice(src []int64) []*int64 {
dst := make([]*int64, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// Int64ValueSlice converts a slice of int64 pointers into a slice of
// int64 values.
func Int64ValueSlice(src []*int64) []int64 {
dst := make([]int64, len(src))
for i := 0; i < len(src); i++ {
if src[i] != nil {
dst[i] = *(src[i])
}
}
return dst
}
// Int64Map converts a string map of int64 values into a string
// map of int64 pointers.
func Int64Map(src map[string]int64) map[string]*int64 {
dst := make(map[string]*int64)
for k, val := range src {
v := val
dst[k] = &v
}
return dst
}
// Int64ValueMap converts a string map of int64 pointers into a string
// map of int64 values.
func Int64ValueMap(src map[string]*int64) map[string]int64 {
dst := make(map[string]int64)
for k, val := range src {
if val != nil {
dst[k] = *val
}
}
return dst
}
// Float64 returns a pointer to of the float64 value passed in.
func Float64(v float64) *float64 {
return &v
}
// Float64Value returns the value of the float64 pointer passed in or
// 0 if the pointer is nil.
func Float64Value(v *float64) float64 {
if v != nil {
return *v
}
return 0
}
// Float64Slice converts a slice of float64 values into a slice of
// float64 pointers.
func Float64Slice(src []float64) []*float64 {
dst := make([]*float64, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// Float64ValueSlice converts a slice of float64 pointers into a slice of
// float64 values.
func Float64ValueSlice(src []*float64) []float64 {
dst := make([]float64, len(src))
for i := 0; i < len(src); i++ {
if src[i] != nil {
dst[i] = *(src[i])
}
}
return dst
}
// Float64Map converts a string map of float64 values into a string
// map of float64 pointers.
func Float64Map(src map[string]float64) map[string]*float64 {
dst := make(map[string]*float64)
for k, val := range src {
v := val
dst[k] = &v
}
return dst
}
// Float64ValueMap converts a string map of float64 pointers into a string
// map of float64 values.
func Float64ValueMap(src map[string]*float64) map[string]float64 {
dst := make(map[string]float64)
for k, val := range src {
if val != nil {
dst[k] = *val
}
}
return dst
}
// Time returns a pointer to of the time.Time value passed in.
func Time(v time.Time) *time.Time {
return &v
}
// TimeValue returns the value of the time.Time pointer passed in or
// time.Time{} if the pointer is nil.
func TimeValue(v *time.Time) time.Time {
if v != nil {
return *v
}
return time.Time{}
}
// TimeSlice converts a slice of time.Time values into a slice of
// time.Time pointers.
func TimeSlice(src []time.Time) []*time.Time {
dst := make([]*time.Time, len(src))
for i := 0; i < len(src); i++ {
dst[i] = &(src[i])
}
return dst
}
// TimeValueSlice converts a slice of time.Time pointers into a slice of
// time.Time values.
func TimeValueSlice(src []*time.Time) []time.Time {
dst := make([]time.Time, len(src))
for i := 0; i < len(src); i++ {
if src[i] != nil {
dst[i] = *(src[i])
}
}
return dst
}
// TimeMap converts a string map of time.Time values into a string
// map of time.Time pointers.
func TimeMap(src map[string]time.Time) map[string]*time.Time {
dst := make(map[string]*time.Time)
for k, val := range src {
v := val
dst[k] = &v
}
return dst
}
// TimeValueMap converts a string map of time.Time pointers into a string
// map of time.Time values.
func TimeValueMap(src map[string]*time.Time) map[string]time.Time {
dst := make(map[string]time.Time)
for k, val := range src {
if val != nil {
dst[k] = *val
}
}
return dst
}

View File

@ -0,0 +1,9 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["json.go"],
importmap = "k8s.io/kops/vendor/github.com/spotinst/spotinst-sdk-go/spotinst/util/jsonutil",
importpath = "github.com/spotinst/spotinst-sdk-go/spotinst/util/jsonutil",
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,184 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package jsonutil
import (
"encoding/json"
"fmt"
"reflect"
"strings"
)
// MarshalJSON returns a JSON encoding of schema containing only selected fields.
// A field is selected if any of the following is true:
// * it has a non-empty value
// * its field name is present in forceSendFields and it is not a nil pointer or nil interface
// * its field name is present in nullFields.
// The JSON key for each selected field is taken from the field's json: struct tag.
func MarshalJSON(schema interface{}, forceSendFields, nullFields []string) ([]byte, error) {
if len(forceSendFields) == 0 && len(nullFields) == 0 {
return json.Marshal(schema)
}
mustInclude := make(map[string]struct{})
for _, f := range forceSendFields {
mustInclude[f] = struct{}{}
}
useNull := make(map[string]struct{})
for _, f := range nullFields {
useNull[f] = struct{}{}
}
dataMap, err := schemaToMap(schema, mustInclude, useNull)
if err != nil {
return nil, err
}
return json.Marshal(dataMap)
}
func schemaToMap(schema interface{}, mustInclude, useNull map[string]struct{}) (map[string]interface{}, error) {
m := make(map[string]interface{})
s := reflect.ValueOf(schema)
st := s.Type()
for i := 0; i < s.NumField(); i++ {
jsonTag := st.Field(i).Tag.Get("json")
if jsonTag == "" {
continue
}
tag, err := parseJSONTag(jsonTag)
if err != nil {
return nil, err
}
if tag.ignore {
continue
}
v := s.Field(i)
f := st.Field(i)
if _, ok := useNull[f.Name]; ok {
if !isEmptyValue(v) {
return nil, fmt.Errorf("field %q in NullFields has non-empty value", f.Name)
}
m[tag.apiName] = nil
continue
}
if !includeField(v, f, mustInclude) {
continue
}
// nil maps are treated as empty maps.
if f.Type.Kind() == reflect.Map && v.IsNil() {
m[tag.apiName] = map[string]string{}
continue
}
// nil slices are treated as empty slices.
if f.Type.Kind() == reflect.Slice && v.IsNil() {
m[tag.apiName] = []bool{}
continue
}
if tag.stringFormat {
m[tag.apiName] = formatAsString(v, f.Type.Kind())
} else {
m[tag.apiName] = v.Interface()
}
}
return m, nil
}
// formatAsString returns a string representation of v, dereferencing it first if possible.
func formatAsString(v reflect.Value, kind reflect.Kind) string {
if kind == reflect.Ptr && !v.IsNil() {
v = v.Elem()
}
return fmt.Sprintf("%v", v.Interface())
}
// jsonTag represents a restricted version of the struct tag format used by encoding/json.
// It is used to describe the JSON encoding of fields in a Schema struct.
type jsonTag struct {
apiName string
stringFormat bool
ignore bool
}
// parseJSONTag parses a restricted version of the struct tag format used by encoding/json.
// The format of the tag must match that generated by the Schema.writeSchemaStruct method
// in the api generator.
func parseJSONTag(val string) (jsonTag, error) {
if val == "-" {
return jsonTag{ignore: true}, nil
}
var tag jsonTag
i := strings.Index(val, ",")
if i == -1 || val[:i] == "" {
return tag, fmt.Errorf("malformed json tag: %s", val)
}
tag = jsonTag{
apiName: val[:i],
}
switch val[i+1:] {
case "omitempty":
case "omitempty,string":
tag.stringFormat = true
default:
return tag, fmt.Errorf("malformed json tag: %s", val)
}
return tag, nil
}
// Reports whether the struct field "f" with value "v" should be included in JSON output.
func includeField(v reflect.Value, f reflect.StructField, mustInclude map[string]struct{}) bool {
// The regular JSON encoding of a nil pointer is "null", which means "delete this field".
// Therefore, we could enable field deletion by honoring pointer fields' presence in the mustInclude set.
// However, many fields are not pointers, so there would be no way to delete these fields.
// Rather than partially supporting field deletion, we ignore mustInclude for nil pointer fields.
// Deletion will be handled by a separate mechanism.
if f.Type.Kind() == reflect.Ptr && v.IsNil() {
return false
}
// The "any" type is represented as an interface{}. If this interface
// is nil, there is no reasonable representation to send. We ignore
// these fields, for the same reasons as given above for pointers.
if f.Type.Kind() == reflect.Interface && v.IsNil() {
return false
}
_, ok := mustInclude[f.Name]
return ok || !isEmptyValue(v)
}
// isEmptyValue reports whether v is the empty value for its type. This
// implementation is based on that of the encoding/json package, but its
// correctness does not depend on it being identical. What's important is that
// this function return false in situations where v should not be sent as part
// of a PATCH operation.
func isEmptyValue(v reflect.Value) bool {
switch v.Kind() {
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Ptr:
return v.IsNil()
}
return false
}

View File

@ -0,0 +1,9 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["stringutil.go"],
importmap = "k8s.io/kops/vendor/github.com/spotinst/spotinst-sdk-go/spotinst/util/stringutil",
importpath = "github.com/spotinst/spotinst-sdk-go/spotinst/util/stringutil",
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,69 @@
package stringutil
import (
"bytes"
"fmt"
"io"
"reflect"
)
// Stringify attempts to create a reasonable string representation of types.
// It does things like resolve pointers to their values and omits struct
// fields with nil values.
func Stringify(message interface{}) string {
var buf bytes.Buffer
v := reflect.ValueOf(message)
stringifyValue(&buf, v)
return buf.String()
}
// stringifyValue was heavily inspired by the goprotobuf library.
func stringifyValue(w io.Writer, val reflect.Value) {
if val.Kind() == reflect.Ptr && val.IsNil() {
w.Write([]byte("<nil>"))
return
}
v := reflect.Indirect(val)
switch v.Kind() {
case reflect.String:
fmt.Fprintf(w, `"%s"`, v)
case reflect.Slice:
w.Write([]byte{'['})
for i := 0; i < v.Len(); i++ {
if i > 0 {
w.Write([]byte{' '})
}
stringifyValue(w, v.Index(i))
}
w.Write([]byte{']'})
return
case reflect.Struct:
if v.Type().Name() != "" {
w.Write([]byte(v.Type().String()))
}
w.Write([]byte{'{'})
var sep bool
for i := 0; i < v.NumField(); i++ {
fv := v.Field(i)
if fv.Kind() == reflect.Ptr && fv.IsNil() {
continue
}
if fv.Kind() == reflect.Slice && fv.IsNil() {
continue
}
if sep {
w.Write([]byte(", "))
} else {
sep = true
}
w.Write([]byte(v.Type().Field(i).Name))
w.Write([]byte{':'})
stringifyValue(w, fv)
}
w.Write([]byte{'}'})
default:
if v.CanInterface() {
fmt.Fprint(w, v.Interface())
}
}
}

View File

@ -0,0 +1,12 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"uritemplates.go",
"utils.go",
],
importmap = "k8s.io/kops/vendor/github.com/spotinst/spotinst-sdk-go/spotinst/util/uritemplates",
importpath = "github.com/spotinst/spotinst-sdk-go/spotinst/util/uritemplates",
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,18 @@
Copyright (c) 2013 Joshua Tacoma
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,361 @@
// Copyright 2013 Joshua Tacoma. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package uritemplates is a level 4 implementation of RFC 6570 (URI
// Template, http://tools.ietf.org/html/rfc6570).
//
// To use uritemplates, parse a template string and expand it with a value
// map:
//
// template, _ := uritemplates.Parse("https://api.github.com/repos{/user,repo}")
// values := make(map[string]interface{})
// values["user"] = "jtacoma"
// values["repo"] = "uritemplates"
// expanded, _ := template.ExpandString(values)
// fmt.Printf(expanded)
//
package uritemplates
import (
"bytes"
"errors"
"fmt"
"reflect"
"regexp"
"strconv"
"strings"
)
var (
unreserved = regexp.MustCompile("[^A-Za-z0-9\\-._~]")
reserved = regexp.MustCompile("[^A-Za-z0-9\\-._~:/?#[\\]@!$&'()*+,;=]")
validname = regexp.MustCompile("^([A-Za-z0-9_\\.]|%[0-9A-Fa-f][0-9A-Fa-f])+$")
hex = []byte("0123456789ABCDEF")
)
func pctEncode(src []byte) []byte {
dst := make([]byte, len(src)*3)
for i, b := range src {
buf := dst[i*3 : i*3+3]
buf[0] = 0x25
buf[1] = hex[b/16]
buf[2] = hex[b%16]
}
return dst
}
func escape(s string, allowReserved bool) (escaped string) {
if allowReserved {
escaped = string(reserved.ReplaceAllFunc([]byte(s), pctEncode))
} else {
escaped = string(unreserved.ReplaceAllFunc([]byte(s), pctEncode))
}
return escaped
}
type Values map[string]interface{}
// A Template is a parsed representation of a URI template.
type Template struct {
raw string
parts []templatePart
}
// Parse parses a URI template string into a Template object.
func Parse(rawtemplate string) (template *Template, err error) {
template = new(Template)
template.raw = rawtemplate
split := strings.Split(rawtemplate, "{")
template.parts = make([]templatePart, len(split)*2-1)
for i, s := range split {
if i == 0 {
if strings.Contains(s, "}") {
err = errors.New("unexpected }")
break
}
template.parts[i].raw = s
} else {
subsplit := strings.Split(s, "}")
if len(subsplit) != 2 {
err = errors.New("malformed template")
break
}
expression := subsplit[0]
template.parts[i*2-1], err = parseExpression(expression)
if err != nil {
break
}
template.parts[i*2].raw = subsplit[1]
}
}
if err != nil {
template = nil
}
return template, err
}
type templatePart struct {
raw string
terms []templateTerm
first string
sep string
named bool
ifemp string
allowReserved bool
}
type templateTerm struct {
name string
explode bool
truncate int
}
func parseExpression(expression string) (result templatePart, err error) {
switch expression[0] {
case '+':
result.sep = ","
result.allowReserved = true
expression = expression[1:]
case '.':
result.first = "."
result.sep = "."
expression = expression[1:]
case '/':
result.first = "/"
result.sep = "/"
expression = expression[1:]
case ';':
result.first = ";"
result.sep = ";"
result.named = true
expression = expression[1:]
case '?':
result.first = "?"
result.sep = "&"
result.named = true
result.ifemp = "="
expression = expression[1:]
case '&':
result.first = "&"
result.sep = "&"
result.named = true
result.ifemp = "="
expression = expression[1:]
case '#':
result.first = "#"
result.sep = ","
result.allowReserved = true
expression = expression[1:]
default:
result.sep = ","
}
rawterms := strings.Split(expression, ",")
result.terms = make([]templateTerm, len(rawterms))
for i, raw := range rawterms {
result.terms[i], err = parseTerm(raw)
if err != nil {
break
}
}
return result, err
}
func parseTerm(term string) (result templateTerm, err error) {
if strings.HasSuffix(term, "*") {
result.explode = true
term = term[:len(term)-1]
}
split := strings.Split(term, ":")
if len(split) == 1 {
result.name = term
} else if len(split) == 2 {
result.name = split[0]
var parsed int64
parsed, err = strconv.ParseInt(split[1], 10, 0)
result.truncate = int(parsed)
} else {
err = errors.New("multiple colons in same term")
}
if !validname.MatchString(result.name) {
err = errors.New("not a valid name: " + result.name)
}
if result.explode && result.truncate > 0 {
err = errors.New("both explode and prefix modifers on same term")
}
return result, err
}
// Expand expands a URI template with a set of values to produce a string.
func (self *Template) Expand(value interface{}) (string, error) {
values, ismap := value.(Values)
if !ismap {
if m, ismap := struct2map(value); !ismap {
return "", errors.New("expected Values, struct, or pointer to struct")
} else {
return self.Expand(m)
}
}
var buf bytes.Buffer
for _, p := range self.parts {
err := p.expand(&buf, values)
if err != nil {
return "", err
}
}
return buf.String(), nil
}
func (self *templatePart) expand(buf *bytes.Buffer, values Values) error {
if len(self.raw) > 0 {
buf.WriteString(self.raw)
return nil
}
var zeroLen = buf.Len()
buf.WriteString(self.first)
var firstLen = buf.Len()
for _, term := range self.terms {
value, exists := values[term.name]
if !exists {
continue
}
if buf.Len() != firstLen {
buf.WriteString(self.sep)
}
switch v := value.(type) {
case string:
self.expandString(buf, term, v)
case []interface{}:
self.expandArray(buf, term, v)
case map[string]interface{}:
if term.truncate > 0 {
return errors.New("cannot truncate a map expansion")
}
self.expandMap(buf, term, v)
default:
if m, ismap := struct2map(value); ismap {
if term.truncate > 0 {
return errors.New("cannot truncate a map expansion")
}
self.expandMap(buf, term, m)
} else {
str := fmt.Sprintf("%v", value)
self.expandString(buf, term, str)
}
}
}
if buf.Len() == firstLen {
original := buf.Bytes()[:zeroLen]
buf.Reset()
buf.Write(original)
}
return nil
}
func (self *templatePart) expandName(buf *bytes.Buffer, name string, empty bool) {
if self.named {
buf.WriteString(name)
if empty {
buf.WriteString(self.ifemp)
} else {
buf.WriteString("=")
}
}
}
func (self *templatePart) expandString(buf *bytes.Buffer, t templateTerm, s string) {
if len(s) > t.truncate && t.truncate > 0 {
s = s[:t.truncate]
}
self.expandName(buf, t.name, len(s) == 0)
buf.WriteString(escape(s, self.allowReserved))
}
func (self *templatePart) expandArray(buf *bytes.Buffer, t templateTerm, a []interface{}) {
if len(a) == 0 {
return
} else if !t.explode {
self.expandName(buf, t.name, false)
}
for i, value := range a {
if t.explode && i > 0 {
buf.WriteString(self.sep)
} else if i > 0 {
buf.WriteString(",")
}
var s string
switch v := value.(type) {
case string:
s = v
default:
s = fmt.Sprintf("%v", v)
}
if len(s) > t.truncate && t.truncate > 0 {
s = s[:t.truncate]
}
if self.named && t.explode {
self.expandName(buf, t.name, len(s) == 0)
}
buf.WriteString(escape(s, self.allowReserved))
}
}
func (self *templatePart) expandMap(buf *bytes.Buffer, t templateTerm, m map[string]interface{}) {
if len(m) == 0 {
return
}
if !t.explode {
self.expandName(buf, t.name, len(m) == 0)
}
var firstLen = buf.Len()
for k, value := range m {
if firstLen != buf.Len() {
if t.explode {
buf.WriteString(self.sep)
} else {
buf.WriteString(",")
}
}
var s string
switch v := value.(type) {
case string:
s = v
default:
s = fmt.Sprintf("%v", v)
}
if t.explode {
buf.WriteString(escape(k, self.allowReserved))
buf.WriteRune('=')
buf.WriteString(escape(s, self.allowReserved))
} else {
buf.WriteString(escape(k, self.allowReserved))
buf.WriteRune(',')
buf.WriteString(escape(s, self.allowReserved))
}
}
}
func struct2map(v interface{}) (map[string]interface{}, bool) {
value := reflect.ValueOf(v)
switch value.Type().Kind() {
case reflect.Ptr:
return struct2map(value.Elem().Interface())
case reflect.Struct:
m := make(map[string]interface{})
for i := 0; i < value.NumField(); i++ {
tag := value.Type().Field(i).Tag
var name string
if strings.Contains(string(tag), ":") {
name = tag.Get("uri")
} else {
name = strings.TrimSpace(string(tag))
}
if len(name) == 0 {
name = value.Type().Field(i).Name
}
m[name] = value.Field(i).Interface()
}
return m, true
}
return nil, false
}

View File

@ -0,0 +1,9 @@
package uritemplates
func Expand(path string, values Values) (string, error) {
template, err := Parse(path)
if err != nil {
return "", err
}
return template.Expand(values)
}

View File

@ -0,0 +1,7 @@
package spotinst
// SDKVersion is the current version of the SDK.
const SDKVersion = "3.2.24"
// SDKName is the name of the SDK.
const SDKName = "spotinst-sdk-go"

View File

@ -1,22 +0,0 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"default_rate_limiters.go",
"delaying_queue.go",
"doc.go",
"metrics.go",
"parallelizer.go",
"queue.go",
"rate_limitting_queue.go",
],
importmap = "k8s.io/kops/vendor/k8s.io/client-go/util/workqueue",
importpath = "k8s.io/client-go/util/workqueue",
visibility = ["//visibility:public"],
deps = [
"//vendor/golang.org/x/time/rate:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/clock:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
],
)

View File

@ -1,211 +0,0 @@
/*
Copyright 2016 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 workqueue
import (
"math"
"sync"
"time"
"golang.org/x/time/rate"
)
type RateLimiter interface {
// When gets an item and gets to decide how long that item should wait
When(item interface{}) time.Duration
// Forget indicates that an item is finished being retried. Doesn't matter whether its for perm failing
// or for success, we'll stop tracking it
Forget(item interface{})
// NumRequeues returns back how many failures the item has had
NumRequeues(item interface{}) int
}
// DefaultControllerRateLimiter is a no-arg constructor for a default rate limiter for a workqueue. It has
// both overall and per-item rate limitting. The overall is a token bucket and the per-item is exponential
func DefaultControllerRateLimiter() RateLimiter {
return NewMaxOfRateLimiter(
NewItemExponentialFailureRateLimiter(5*time.Millisecond, 1000*time.Second),
// 10 qps, 100 bucket size. This is only for retry speed and its only the overall factor (not per item)
&BucketRateLimiter{Limiter: rate.NewLimiter(rate.Limit(10), 100)},
)
}
// BucketRateLimiter adapts a standard bucket to the workqueue ratelimiter API
type BucketRateLimiter struct {
*rate.Limiter
}
var _ RateLimiter = &BucketRateLimiter{}
func (r *BucketRateLimiter) When(item interface{}) time.Duration {
return r.Limiter.Reserve().Delay()
}
func (r *BucketRateLimiter) NumRequeues(item interface{}) int {
return 0
}
func (r *BucketRateLimiter) Forget(item interface{}) {
}
// ItemExponentialFailureRateLimiter does a simple baseDelay*10^<num-failures> limit
// dealing with max failures and expiration are up to the caller
type ItemExponentialFailureRateLimiter struct {
failuresLock sync.Mutex
failures map[interface{}]int
baseDelay time.Duration
maxDelay time.Duration
}
var _ RateLimiter = &ItemExponentialFailureRateLimiter{}
func NewItemExponentialFailureRateLimiter(baseDelay time.Duration, maxDelay time.Duration) RateLimiter {
return &ItemExponentialFailureRateLimiter{
failures: map[interface{}]int{},
baseDelay: baseDelay,
maxDelay: maxDelay,
}
}
func DefaultItemBasedRateLimiter() RateLimiter {
return NewItemExponentialFailureRateLimiter(time.Millisecond, 1000*time.Second)
}
func (r *ItemExponentialFailureRateLimiter) When(item interface{}) time.Duration {
r.failuresLock.Lock()
defer r.failuresLock.Unlock()
exp := r.failures[item]
r.failures[item] = r.failures[item] + 1
// The backoff is capped such that 'calculated' value never overflows.
backoff := float64(r.baseDelay.Nanoseconds()) * math.Pow(2, float64(exp))
if backoff > math.MaxInt64 {
return r.maxDelay
}
calculated := time.Duration(backoff)
if calculated > r.maxDelay {
return r.maxDelay
}
return calculated
}
func (r *ItemExponentialFailureRateLimiter) NumRequeues(item interface{}) int {
r.failuresLock.Lock()
defer r.failuresLock.Unlock()
return r.failures[item]
}
func (r *ItemExponentialFailureRateLimiter) Forget(item interface{}) {
r.failuresLock.Lock()
defer r.failuresLock.Unlock()
delete(r.failures, item)
}
// ItemFastSlowRateLimiter does a quick retry for a certain number of attempts, then a slow retry after that
type ItemFastSlowRateLimiter struct {
failuresLock sync.Mutex
failures map[interface{}]int
maxFastAttempts int
fastDelay time.Duration
slowDelay time.Duration
}
var _ RateLimiter = &ItemFastSlowRateLimiter{}
func NewItemFastSlowRateLimiter(fastDelay, slowDelay time.Duration, maxFastAttempts int) RateLimiter {
return &ItemFastSlowRateLimiter{
failures: map[interface{}]int{},
fastDelay: fastDelay,
slowDelay: slowDelay,
maxFastAttempts: maxFastAttempts,
}
}
func (r *ItemFastSlowRateLimiter) When(item interface{}) time.Duration {
r.failuresLock.Lock()
defer r.failuresLock.Unlock()
r.failures[item] = r.failures[item] + 1
if r.failures[item] <= r.maxFastAttempts {
return r.fastDelay
}
return r.slowDelay
}
func (r *ItemFastSlowRateLimiter) NumRequeues(item interface{}) int {
r.failuresLock.Lock()
defer r.failuresLock.Unlock()
return r.failures[item]
}
func (r *ItemFastSlowRateLimiter) Forget(item interface{}) {
r.failuresLock.Lock()
defer r.failuresLock.Unlock()
delete(r.failures, item)
}
// MaxOfRateLimiter calls every RateLimiter and returns the worst case response
// When used with a token bucket limiter, the burst could be apparently exceeded in cases where particular items
// were separately delayed a longer time.
type MaxOfRateLimiter struct {
limiters []RateLimiter
}
func (r *MaxOfRateLimiter) When(item interface{}) time.Duration {
ret := time.Duration(0)
for _, limiter := range r.limiters {
curr := limiter.When(item)
if curr > ret {
ret = curr
}
}
return ret
}
func NewMaxOfRateLimiter(limiters ...RateLimiter) RateLimiter {
return &MaxOfRateLimiter{limiters: limiters}
}
func (r *MaxOfRateLimiter) NumRequeues(item interface{}) int {
ret := 0
for _, limiter := range r.limiters {
curr := limiter.NumRequeues(item)
if curr > ret {
ret = curr
}
}
return ret
}
func (r *MaxOfRateLimiter) Forget(item interface{}) {
for _, limiter := range r.limiters {
limiter.Forget(item)
}
}

View File

@ -1,255 +0,0 @@
/*
Copyright 2016 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 workqueue
import (
"container/heap"
"time"
"k8s.io/apimachinery/pkg/util/clock"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
)
// DelayingInterface is an Interface that can Add an item at a later time. This makes it easier to
// requeue items after failures without ending up in a hot-loop.
type DelayingInterface interface {
Interface
// AddAfter adds an item to the workqueue after the indicated duration has passed
AddAfter(item interface{}, duration time.Duration)
}
// NewDelayingQueue constructs a new workqueue with delayed queuing ability
func NewDelayingQueue() DelayingInterface {
return newDelayingQueue(clock.RealClock{}, "")
}
func NewNamedDelayingQueue(name string) DelayingInterface {
return newDelayingQueue(clock.RealClock{}, name)
}
func newDelayingQueue(clock clock.Clock, name string) DelayingInterface {
ret := &delayingType{
Interface: NewNamed(name),
clock: clock,
heartbeat: clock.NewTicker(maxWait),
stopCh: make(chan struct{}),
waitingForAddCh: make(chan *waitFor, 1000),
metrics: newRetryMetrics(name),
}
go ret.waitingLoop()
return ret
}
// delayingType wraps an Interface and provides delayed re-enquing
type delayingType struct {
Interface
// clock tracks time for delayed firing
clock clock.Clock
// stopCh lets us signal a shutdown to the waiting loop
stopCh chan struct{}
// heartbeat ensures we wait no more than maxWait before firing
heartbeat clock.Ticker
// waitingForAddCh is a buffered channel that feeds waitingForAdd
waitingForAddCh chan *waitFor
// metrics counts the number of retries
metrics retryMetrics
}
// waitFor holds the data to add and the time it should be added
type waitFor struct {
data t
readyAt time.Time
// index in the priority queue (heap)
index int
}
// waitForPriorityQueue implements a priority queue for waitFor items.
//
// waitForPriorityQueue implements heap.Interface. The item occurring next in
// time (i.e., the item with the smallest readyAt) is at the root (index 0).
// Peek returns this minimum item at index 0. Pop returns the minimum item after
// it has been removed from the queue and placed at index Len()-1 by
// container/heap. Push adds an item at index Len(), and container/heap
// percolates it into the correct location.
type waitForPriorityQueue []*waitFor
func (pq waitForPriorityQueue) Len() int {
return len(pq)
}
func (pq waitForPriorityQueue) Less(i, j int) bool {
return pq[i].readyAt.Before(pq[j].readyAt)
}
func (pq waitForPriorityQueue) Swap(i, j int) {
pq[i], pq[j] = pq[j], pq[i]
pq[i].index = i
pq[j].index = j
}
// Push adds an item to the queue. Push should not be called directly; instead,
// use `heap.Push`.
func (pq *waitForPriorityQueue) Push(x interface{}) {
n := len(*pq)
item := x.(*waitFor)
item.index = n
*pq = append(*pq, item)
}
// Pop removes an item from the queue. Pop should not be called directly;
// instead, use `heap.Pop`.
func (pq *waitForPriorityQueue) Pop() interface{} {
n := len(*pq)
item := (*pq)[n-1]
item.index = -1
*pq = (*pq)[0:(n - 1)]
return item
}
// Peek returns the item at the beginning of the queue, without removing the
// item or otherwise mutating the queue. It is safe to call directly.
func (pq waitForPriorityQueue) Peek() interface{} {
return pq[0]
}
// ShutDown gives a way to shut off this queue
func (q *delayingType) ShutDown() {
q.Interface.ShutDown()
close(q.stopCh)
q.heartbeat.Stop()
}
// AddAfter adds the given item to the work queue after the given delay
func (q *delayingType) AddAfter(item interface{}, duration time.Duration) {
// don't add if we're already shutting down
if q.ShuttingDown() {
return
}
q.metrics.retry()
// immediately add things with no delay
if duration <= 0 {
q.Add(item)
return
}
select {
case <-q.stopCh:
// unblock if ShutDown() is called
case q.waitingForAddCh <- &waitFor{data: item, readyAt: q.clock.Now().Add(duration)}:
}
}
// maxWait keeps a max bound on the wait time. It's just insurance against weird things happening.
// Checking the queue every 10 seconds isn't expensive and we know that we'll never end up with an
// expired item sitting for more than 10 seconds.
const maxWait = 10 * time.Second
// waitingLoop runs until the workqueue is shutdown and keeps a check on the list of items to be added.
func (q *delayingType) waitingLoop() {
defer utilruntime.HandleCrash()
// Make a placeholder channel to use when there are no items in our list
never := make(<-chan time.Time)
waitingForQueue := &waitForPriorityQueue{}
heap.Init(waitingForQueue)
waitingEntryByData := map[t]*waitFor{}
for {
if q.Interface.ShuttingDown() {
return
}
now := q.clock.Now()
// Add ready entries
for waitingForQueue.Len() > 0 {
entry := waitingForQueue.Peek().(*waitFor)
if entry.readyAt.After(now) {
break
}
entry = heap.Pop(waitingForQueue).(*waitFor)
q.Add(entry.data)
delete(waitingEntryByData, entry.data)
}
// Set up a wait for the first item's readyAt (if one exists)
nextReadyAt := never
if waitingForQueue.Len() > 0 {
entry := waitingForQueue.Peek().(*waitFor)
nextReadyAt = q.clock.After(entry.readyAt.Sub(now))
}
select {
case <-q.stopCh:
return
case <-q.heartbeat.C():
// continue the loop, which will add ready items
case <-nextReadyAt:
// continue the loop, which will add ready items
case waitEntry := <-q.waitingForAddCh:
if waitEntry.readyAt.After(q.clock.Now()) {
insert(waitingForQueue, waitingEntryByData, waitEntry)
} else {
q.Add(waitEntry.data)
}
drained := false
for !drained {
select {
case waitEntry := <-q.waitingForAddCh:
if waitEntry.readyAt.After(q.clock.Now()) {
insert(waitingForQueue, waitingEntryByData, waitEntry)
} else {
q.Add(waitEntry.data)
}
default:
drained = true
}
}
}
}
}
// insert adds the entry to the priority queue, or updates the readyAt if it already exists in the queue
func insert(q *waitForPriorityQueue, knownEntries map[t]*waitFor, entry *waitFor) {
// if the entry already exists, update the time only if it would cause the item to be queued sooner
existing, exists := knownEntries[entry.data]
if exists {
if existing.readyAt.After(entry.readyAt) {
existing.readyAt = entry.readyAt
heap.Fix(q, existing.index)
}
return
}
heap.Push(q, entry)
knownEntries[entry.data] = entry
}

View File

@ -1,26 +0,0 @@
/*
Copyright 2014 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 workqueue provides a simple queue that supports the following
// features:
// * Fair: items processed in the order in which they are added.
// * Stingy: a single item will not be processed multiple times concurrently,
// and if an item is added multiple times before it can be processed, it
// will only be processed once.
// * Multiple consumers and producers. In particular, it is allowed for an
// item to be reenqueued while it is being processed.
// * Shutdown notifications.
package workqueue

View File

@ -1,195 +0,0 @@
/*
Copyright 2016 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 workqueue
import (
"sync"
"time"
)
// This file provides abstractions for setting the provider (e.g., prometheus)
// of metrics.
type queueMetrics interface {
add(item t)
get(item t)
done(item t)
}
// GaugeMetric represents a single numerical value that can arbitrarily go up
// and down.
type GaugeMetric interface {
Inc()
Dec()
}
// CounterMetric represents a single numerical value that only ever
// goes up.
type CounterMetric interface {
Inc()
}
// SummaryMetric captures individual observations.
type SummaryMetric interface {
Observe(float64)
}
type noopMetric struct{}
func (noopMetric) Inc() {}
func (noopMetric) Dec() {}
func (noopMetric) Observe(float64) {}
type defaultQueueMetrics struct {
// current depth of a workqueue
depth GaugeMetric
// total number of adds handled by a workqueue
adds CounterMetric
// how long an item stays in a workqueue
latency SummaryMetric
// how long processing an item from a workqueue takes
workDuration SummaryMetric
addTimes map[t]time.Time
processingStartTimes map[t]time.Time
}
func (m *defaultQueueMetrics) add(item t) {
if m == nil {
return
}
m.adds.Inc()
m.depth.Inc()
if _, exists := m.addTimes[item]; !exists {
m.addTimes[item] = time.Now()
}
}
func (m *defaultQueueMetrics) get(item t) {
if m == nil {
return
}
m.depth.Dec()
m.processingStartTimes[item] = time.Now()
if startTime, exists := m.addTimes[item]; exists {
m.latency.Observe(sinceInMicroseconds(startTime))
delete(m.addTimes, item)
}
}
func (m *defaultQueueMetrics) done(item t) {
if m == nil {
return
}
if startTime, exists := m.processingStartTimes[item]; exists {
m.workDuration.Observe(sinceInMicroseconds(startTime))
delete(m.processingStartTimes, item)
}
}
// Gets the time since the specified start in microseconds.
func sinceInMicroseconds(start time.Time) float64 {
return float64(time.Since(start).Nanoseconds() / time.Microsecond.Nanoseconds())
}
type retryMetrics interface {
retry()
}
type defaultRetryMetrics struct {
retries CounterMetric
}
func (m *defaultRetryMetrics) retry() {
if m == nil {
return
}
m.retries.Inc()
}
// MetricsProvider generates various metrics used by the queue.
type MetricsProvider interface {
NewDepthMetric(name string) GaugeMetric
NewAddsMetric(name string) CounterMetric
NewLatencyMetric(name string) SummaryMetric
NewWorkDurationMetric(name string) SummaryMetric
NewRetriesMetric(name string) CounterMetric
}
type noopMetricsProvider struct{}
func (_ noopMetricsProvider) NewDepthMetric(name string) GaugeMetric {
return noopMetric{}
}
func (_ noopMetricsProvider) NewAddsMetric(name string) CounterMetric {
return noopMetric{}
}
func (_ noopMetricsProvider) NewLatencyMetric(name string) SummaryMetric {
return noopMetric{}
}
func (_ noopMetricsProvider) NewWorkDurationMetric(name string) SummaryMetric {
return noopMetric{}
}
func (_ noopMetricsProvider) NewRetriesMetric(name string) CounterMetric {
return noopMetric{}
}
var metricsFactory = struct {
metricsProvider MetricsProvider
setProviders sync.Once
}{
metricsProvider: noopMetricsProvider{},
}
func newQueueMetrics(name string) queueMetrics {
var ret *defaultQueueMetrics
if len(name) == 0 {
return ret
}
return &defaultQueueMetrics{
depth: metricsFactory.metricsProvider.NewDepthMetric(name),
adds: metricsFactory.metricsProvider.NewAddsMetric(name),
latency: metricsFactory.metricsProvider.NewLatencyMetric(name),
workDuration: metricsFactory.metricsProvider.NewWorkDurationMetric(name),
addTimes: map[t]time.Time{},
processingStartTimes: map[t]time.Time{},
}
}
func newRetryMetrics(name string) retryMetrics {
var ret *defaultRetryMetrics
if len(name) == 0 {
return ret
}
return &defaultRetryMetrics{
retries: metricsFactory.metricsProvider.NewRetriesMetric(name),
}
}
// SetProvider sets the metrics provider of the metricsFactory.
func SetProvider(metricsProvider MetricsProvider) {
metricsFactory.setProviders.Do(func() {
metricsFactory.metricsProvider = metricsProvider
})
}

View File

@ -1,52 +0,0 @@
/*
Copyright 2016 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 workqueue
import (
"sync"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
)
type DoWorkPieceFunc func(piece int)
// Parallelize is a very simple framework that allow for parallelizing
// N independent pieces of work.
func Parallelize(workers, pieces int, doWorkPiece DoWorkPieceFunc) {
toProcess := make(chan int, pieces)
for i := 0; i < pieces; i++ {
toProcess <- i
}
close(toProcess)
if pieces < workers {
workers = pieces
}
wg := sync.WaitGroup{}
wg.Add(workers)
for i := 0; i < workers; i++ {
go func() {
defer utilruntime.HandleCrash()
defer wg.Done()
for piece := range toProcess {
doWorkPiece(piece)
}
}()
}
wg.Wait()
}

View File

@ -1,172 +0,0 @@
/*
Copyright 2015 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 workqueue
import (
"sync"
)
type Interface interface {
Add(item interface{})
Len() int
Get() (item interface{}, shutdown bool)
Done(item interface{})
ShutDown()
ShuttingDown() bool
}
// New constructs a new work queue (see the package comment).
func New() *Type {
return NewNamed("")
}
func NewNamed(name string) *Type {
return &Type{
dirty: set{},
processing: set{},
cond: sync.NewCond(&sync.Mutex{}),
metrics: newQueueMetrics(name),
}
}
// Type is a work queue (see the package comment).
type Type struct {
// queue defines the order in which we will work on items. Every
// element of queue should be in the dirty set and not in the
// processing set.
queue []t
// dirty defines all of the items that need to be processed.
dirty set
// Things that are currently being processed are in the processing set.
// These things may be simultaneously in the dirty set. When we finish
// processing something and remove it from this set, we'll check if
// it's in the dirty set, and if so, add it to the queue.
processing set
cond *sync.Cond
shuttingDown bool
metrics queueMetrics
}
type empty struct{}
type t interface{}
type set map[t]empty
func (s set) has(item t) bool {
_, exists := s[item]
return exists
}
func (s set) insert(item t) {
s[item] = empty{}
}
func (s set) delete(item t) {
delete(s, item)
}
// Add marks item as needing processing.
func (q *Type) Add(item interface{}) {
q.cond.L.Lock()
defer q.cond.L.Unlock()
if q.shuttingDown {
return
}
if q.dirty.has(item) {
return
}
q.metrics.add(item)
q.dirty.insert(item)
if q.processing.has(item) {
return
}
q.queue = append(q.queue, item)
q.cond.Signal()
}
// Len returns the current queue length, for informational purposes only. You
// shouldn't e.g. gate a call to Add() or Get() on Len() being a particular
// value, that can't be synchronized properly.
func (q *Type) Len() int {
q.cond.L.Lock()
defer q.cond.L.Unlock()
return len(q.queue)
}
// Get blocks until it can return an item to be processed. If shutdown = true,
// the caller should end their goroutine. You must call Done with item when you
// have finished processing it.
func (q *Type) Get() (item interface{}, shutdown bool) {
q.cond.L.Lock()
defer q.cond.L.Unlock()
for len(q.queue) == 0 && !q.shuttingDown {
q.cond.Wait()
}
if len(q.queue) == 0 {
// We must be shutting down.
return nil, true
}
item, q.queue = q.queue[0], q.queue[1:]
q.metrics.get(item)
q.processing.insert(item)
q.dirty.delete(item)
return item, false
}
// Done marks item as done processing, and if it has been marked as dirty again
// while it was being processed, it will be re-added to the queue for
// re-processing.
func (q *Type) Done(item interface{}) {
q.cond.L.Lock()
defer q.cond.L.Unlock()
q.metrics.done(item)
q.processing.delete(item)
if q.dirty.has(item) {
q.queue = append(q.queue, item)
q.cond.Signal()
}
}
// ShutDown will cause q to ignore all new items added to it. As soon as the
// worker goroutines have drained the existing items in the queue, they will be
// instructed to exit.
func (q *Type) ShutDown() {
q.cond.L.Lock()
defer q.cond.L.Unlock()
q.shuttingDown = true
q.cond.Broadcast()
}
func (q *Type) ShuttingDown() bool {
q.cond.L.Lock()
defer q.cond.L.Unlock()
return q.shuttingDown
}

View File

@ -1,69 +0,0 @@
/*
Copyright 2016 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 workqueue
// RateLimitingInterface is an interface that rate limits items being added to the queue.
type RateLimitingInterface interface {
DelayingInterface
// AddRateLimited adds an item to the workqueue after the rate limiter says its ok
AddRateLimited(item interface{})
// Forget indicates that an item is finished being retried. Doesn't matter whether its for perm failing
// or for success, we'll stop the rate limiter from tracking it. This only clears the `rateLimiter`, you
// still have to call `Done` on the queue.
Forget(item interface{})
// NumRequeues returns back how many times the item was requeued
NumRequeues(item interface{}) int
}
// NewRateLimitingQueue constructs a new workqueue with rateLimited queuing ability
// Remember to call Forget! If you don't, you may end up tracking failures forever.
func NewRateLimitingQueue(rateLimiter RateLimiter) RateLimitingInterface {
return &rateLimitingType{
DelayingInterface: NewDelayingQueue(),
rateLimiter: rateLimiter,
}
}
func NewNamedRateLimitingQueue(rateLimiter RateLimiter, name string) RateLimitingInterface {
return &rateLimitingType{
DelayingInterface: NewNamedDelayingQueue(name),
rateLimiter: rateLimiter,
}
}
// rateLimitingType wraps an Interface and provides rateLimited re-enquing
type rateLimitingType struct {
DelayingInterface
rateLimiter RateLimiter
}
// AddRateLimited AddAfter's the item based on the time when the rate limiter says its ok
func (q *rateLimitingType) AddRateLimited(item interface{}) {
q.DelayingInterface.AddAfter(item, q.rateLimiter.When(item))
}
func (q *rateLimitingType) NumRequeues(item interface{}) int {
return q.rateLimiter.NumRequeues(item)
}
func (q *rateLimitingType) Forget(item interface{}) {
q.rateLimiter.Forget(item)
}