feat(spot/ocean): support for block device mappings in launchspec

This commit is contained in:
liranp 2021-03-10 13:05:55 +02:00
parent de22989eda
commit dc1ee9402a
No known key found for this signature in database
GPG Key ID: D5F03857002C1A93
33 changed files with 2263 additions and 224 deletions

4
go.mod
View File

@ -76,8 +76,8 @@ require (
github.com/spf13/cobra v1.1.1
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.7.0
github.com/spotinst/spotinst-sdk-go v1.76.0
github.com/stretchr/testify v1.6.1
github.com/spotinst/spotinst-sdk-go v1.80.0
github.com/stretchr/testify v1.7.0
github.com/weaveworks/mesh v0.0.0-20170419100114-1f158d31de55
github.com/zclconf/go-cty v1.3.1
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83

10
go.sum
View File

@ -1006,8 +1006,8 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/spotinst/spotinst-sdk-go v1.76.0 h1:tlFKrUAu7h3FgZM3Xy7436XR5lYo5Q/uePEy0AX5Ydw=
github.com/spotinst/spotinst-sdk-go v1.76.0/go.mod h1:sSRVZTSdUAPxeELD/urZkxcfU/DcxO1/UIdOxagqFBc=
github.com/spotinst/spotinst-sdk-go v1.80.0 h1:ygqAYGTA2Z/1nlIue1YtXsDGQCv6Lgx3fwO34EIi36E=
github.com/spotinst/spotinst-sdk-go v1.80.0/go.mod h1:tJtl0XQCi8YaZnCQfC/y3qHB9OpaxADH/N7EPa0hkt8=
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/storageos/go-api v2.2.0+incompatible/go.mod h1:ZrLn+e0ZuF3Y65PNF6dIwbJPZqfmtCXxFm9ckv0agOY=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
@ -1021,8 +1021,9 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
@ -1561,8 +1562,9 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.52.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.57.0 h1:9unxIsFcTt4I55uWluz+UmL95q4kdJ0buvQ1ZIqVQww=
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/mcuadros/go-syslog.v2 v2.2.1/go.mod h1:l5LPIyOOyIdQquNg+oU6Z3524YwrcqEm0aKH+5zpt2U=
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=

View File

@ -503,9 +503,7 @@ func (b *SpotInstanceGroupModelBuilder) buildOcean(c *fi.ModelBuilderContext, ig
}
func (b *SpotInstanceGroupModelBuilder) buildLaunchSpec(c *fi.ModelBuilderContext,
ig, igOcean *kops.InstanceGroup, ocean *spotinsttasks.Ocean) (err error) {
klog.V(4).Infof("Building instance group as LaunchSpec: %q", b.AutoscalingGroupName(ig))
launchSpec := &spotinsttasks.LaunchSpec{
Name: fi.String(b.AutoscalingGroupName(ig)),
@ -564,8 +562,6 @@ func (b *SpotInstanceGroupModelBuilder) buildLaunchSpec(c *fi.ModelBuilderContex
}
if rootVolumeOpts != nil { // remove unsupported options
launchSpec.RootVolumeOpts = rootVolumeOpts
launchSpec.RootVolumeOpts.Type = nil
launchSpec.RootVolumeOpts.IOPS = nil
launchSpec.RootVolumeOpts.Optimization = nil
}
@ -613,7 +609,6 @@ func (b *SpotInstanceGroupModelBuilder) buildLaunchSpec(c *fi.ModelBuilderContex
func (b *SpotInstanceGroupModelBuilder) buildSecurityGroups(c *fi.ModelBuilderContext,
ig *kops.InstanceGroup) ([]*awstasks.SecurityGroup, error) {
securityGroups := []*awstasks.SecurityGroup{
b.LinkToSecurityGroup(ig.Spec.Role),
}
@ -692,9 +687,7 @@ func (b *SpotInstanceGroupModelBuilder) buildPublicIpOpts(ig *kops.InstanceGroup
}
func (b *SpotInstanceGroupModelBuilder) buildRootVolumeOpts(ig *kops.InstanceGroup) (*spotinsttasks.RootVolumeOpts, error) {
opts := &spotinsttasks.RootVolumeOpts{
IOPS: ig.Spec.RootVolumeIops,
}
opts := new(spotinsttasks.RootVolumeOpts)
// Optimization.
{
@ -713,7 +706,7 @@ func (b *SpotInstanceGroupModelBuilder) buildRootVolumeOpts(ig *kops.InstanceGro
return nil, err
}
}
opts.Size = fi.Int32(size)
opts.Size = fi.Int64(int64(size))
}
// Type.
@ -725,6 +718,22 @@ func (b *SpotInstanceGroupModelBuilder) buildRootVolumeOpts(ig *kops.InstanceGro
opts.Type = fi.String(typ)
}
// IOPS.
{
iops := fi.Int32Value(ig.Spec.RootVolumeIops)
if iops > 0 {
opts.IOPS = fi.Int64(int64(iops))
}
}
// Throughput.
{
throughput := fi.Int32Value(ig.Spec.RootVolumeThroughput)
if throughput > 0 {
opts.Throughput = fi.Int64(int64(throughput))
}
}
return opts, nil
}

View File

@ -27,7 +27,6 @@ go_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/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/klog/v2:go_default_library",
],
)

View File

@ -31,7 +31,6 @@ import (
"github.com/spotinst/spotinst-sdk-go/spotinst/client"
"github.com/spotinst/spotinst-sdk-go/spotinst/util/stringutil"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/klog/v2"
"k8s.io/kops/pkg/resources/spotinst"
"k8s.io/kops/upup/pkg/fi"
@ -76,8 +75,9 @@ type Elastigroup struct {
type RootVolumeOpts struct {
Type *string
Size *int32
IOPS *int32
Size *int64
IOPS *int64
Throughput *int64
Optimization *bool
}
@ -284,12 +284,18 @@ func (e *Elastigroup) Find(c *fi.Context) (*Elastigroup, error) {
if actual.RootVolumeOpts == nil {
actual.RootVolumeOpts = new(RootVolumeOpts)
}
if b.EBS.IOPS != nil {
actual.RootVolumeOpts.IOPS = fi.Int32(int32(fi.IntValue(b.EBS.IOPS)))
if b.EBS.VolumeType != nil {
actual.RootVolumeOpts.Type = fi.String(strings.ToLower(fi.StringValue(b.EBS.VolumeType)))
}
if b.EBS.VolumeSize != nil {
actual.RootVolumeOpts.Size = fi.Int64(int64(fi.IntValue(b.EBS.VolumeSize)))
}
if b.EBS.IOPS != nil {
actual.RootVolumeOpts.IOPS = fi.Int64(int64(fi.IntValue(b.EBS.IOPS)))
}
if b.EBS.Throughput != nil {
actual.RootVolumeOpts.Throughput = fi.Int64(int64(fi.IntValue(b.EBS.Throughput)))
}
actual.RootVolumeOpts.Type = fi.String(strings.ToLower(fi.StringValue(b.EBS.VolumeType)))
actual.RootVolumeOpts.Size = fi.Int32(int32(fi.IntValue(b.EBS.VolumeSize)))
}
}
}
@ -548,28 +554,25 @@ func (_ *Elastigroup) create(cloud awsup.AWSCloud, a, e, changes *Elastigroup) e
// Block device mappings.
{
rootDevices, err := e.buildRootDevice(cloud)
rootDevice, err := buildRootDevice(cloud, e.RootVolumeOpts, e.ImageID)
if err != nil {
return err
}
ephemeralDevices, err := e.buildEphemeralDevices(cloud, e.OnDemandInstanceType)
mappings := []*aws.BlockDeviceMapping{
e.convertBlockDeviceMapping(rootDevice),
}
ephemeralDevices, err := buildEphemeralDevices(cloud, e.OnDemandInstanceType)
if err != nil {
return err
}
if len(rootDevices) != 0 || len(ephemeralDevices) != 0 {
var mappings []*aws.BlockDeviceMapping
for device, bdm := range rootDevices {
mappings = append(mappings, e.buildBlockDeviceMapping(device, bdm))
}
for device, bdm := range ephemeralDevices {
mappings = append(mappings, e.buildBlockDeviceMapping(device, bdm))
}
if len(mappings) > 0 {
group.Compute.LaunchSpecification.SetBlockDeviceMappings(mappings)
}
for _, bdm := range ephemeralDevices {
mappings = append(mappings, e.convertBlockDeviceMapping(bdm))
}
group.Compute.LaunchSpecification.SetBlockDeviceMappings(mappings)
}
// Image.
@ -1001,40 +1004,36 @@ func (_ *Elastigroup) update(cloud awsup.AWSCloud, a, e, changes *Elastigroup) e
// Root volume options.
{
if opts := changes.RootVolumeOpts; opts != nil {
// Block device mappings.
{
if opts.Type != nil || opts.Size != nil || opts.IOPS != nil {
rootDevices, err := e.buildRootDevice(cloud)
rootDevice, err := buildRootDevice(cloud, opts, e.ImageID)
if err != nil {
return err
}
ephemeralDevices, err := e.buildEphemeralDevices(cloud, e.OnDemandInstanceType)
mappings := []*aws.BlockDeviceMapping{
e.convertBlockDeviceMapping(rootDevice),
}
ephemeralDevices, err := buildEphemeralDevices(cloud, e.OnDemandInstanceType)
if err != nil {
return err
}
if len(rootDevices) != 0 || len(ephemeralDevices) != 0 {
var mappings []*aws.BlockDeviceMapping
for device, bdm := range rootDevices {
mappings = append(mappings, e.buildBlockDeviceMapping(device, bdm))
}
for device, bdm := range ephemeralDevices {
mappings = append(mappings, e.buildBlockDeviceMapping(device, bdm))
}
if len(mappings) > 0 {
if group.Compute == nil {
group.Compute = new(aws.Compute)
}
if group.Compute.LaunchSpecification == nil {
group.Compute.LaunchSpecification = new(aws.LaunchSpecification)
}
group.Compute.LaunchSpecification.SetBlockDeviceMappings(mappings)
changed = true
}
for _, bdm := range ephemeralDevices {
mappings = append(mappings, e.convertBlockDeviceMapping(bdm))
}
if group.Compute == nil {
group.Compute = new(aws.Compute)
}
if group.Compute.LaunchSpecification == nil {
group.Compute.LaunchSpecification = new(aws.LaunchSpecification)
}
group.Compute.LaunchSpecification.SetBlockDeviceMappings(mappings)
changed = true
}
}
@ -1379,6 +1378,8 @@ type terraformElastigroupBlockDevice struct {
VirtualName *string `json:"virtual_name,omitempty" cty:"virtual_name"`
VolumeType *string `json:"volume_type,omitempty" cty:"volume_type"`
VolumeSize *int64 `json:"volume_size,omitempty" cty:"volume_size"`
VolumeIOPS *int64 `json:"iops,omitempty" cty:"iops"`
VolumeThroughput *int64 `json:"throughput,omitempty" cty:"throughput"`
DeleteOnTermination *bool `json:"delete_on_termination,omitempty" cty:"delete_on_termination"`
}
@ -1556,42 +1557,34 @@ func (_ *Elastigroup) RenderTerraform(t *terraform.TerraformTarget, a, e, change
// Root volume options.
{
if opts := e.RootVolumeOpts; opts != nil {
// Block device mappings.
{
rootDevices, err := e.buildRootDevice(t.Cloud.(awsup.AWSCloud))
rootDevice, err := buildRootDevice(t.Cloud.(awsup.AWSCloud), e.RootVolumeOpts, e.ImageID)
if err != nil {
return err
}
ephemeralDevices, err := e.buildEphemeralDevices(cloud, e.OnDemandInstanceType)
if err != nil {
return err
tf.RootBlockDevice = &terraformElastigroupBlockDevice{
DeviceName: rootDevice.DeviceName,
VolumeType: rootDevice.EbsVolumeType,
VolumeSize: rootDevice.EbsVolumeSize,
VolumeIOPS: rootDevice.EbsVolumeIops,
VolumeThroughput: rootDevice.EbsVolumeThroughput,
DeleteOnTermination: fi.Bool(true),
}
if len(rootDevices) != 0 {
if len(rootDevices) != 1 {
return fmt.Errorf("unexpectedly found multiple root devices")
}
for name, bdm := range rootDevices {
tf.RootBlockDevice = &terraformElastigroupBlockDevice{
DeviceName: fi.String(name),
VolumeType: bdm.EbsVolumeType,
VolumeSize: bdm.EbsVolumeSize,
DeleteOnTermination: fi.Bool(true),
}
}
ephemeralDevices, err := buildEphemeralDevices(cloud, e.OnDemandInstanceType)
if err != nil {
return err
}
if len(ephemeralDevices) != 0 {
tf.EphemeralBlockDevice = []*terraformElastigroupBlockDevice{}
for _, deviceName := range sets.StringKeySet(ephemeralDevices).List() {
bdm := ephemeralDevices[deviceName]
tf.EphemeralBlockDevice = append(tf.EphemeralBlockDevice, &terraformElastigroupBlockDevice{
tf.EphemeralBlockDevice = make([]*terraformElastigroupBlockDevice, len(ephemeralDevices))
for i, bdm := range ephemeralDevices {
tf.EphemeralBlockDevice[i] = &terraformElastigroupBlockDevice{
DeviceName: bdm.DeviceName,
VirtualName: bdm.VirtualName,
DeviceName: fi.String(deviceName),
})
}
}
}
}
@ -1696,70 +1689,76 @@ func (e *Elastigroup) buildAutoScaleLabels(labelsMap map[string]string) []*aws.A
return labels
}
func (e *Elastigroup) buildEphemeralDevices(c awsup.AWSCloud, instanceTypeName *string) (map[string]*awstasks.BlockDeviceMapping, error) {
if instanceTypeName == nil {
return nil, fi.RequiredField("InstanceType")
}
instanceType, err := awsup.GetMachineTypeInfo(c, *instanceTypeName)
func buildEphemeralDevices(cloud awsup.AWSCloud, machineType *string) ([]*awstasks.BlockDeviceMapping, error) {
info, err := awsup.GetMachineTypeInfo(cloud, fi.StringValue(machineType))
if err != nil {
return nil, err
}
blockDeviceMappings := make(map[string]*awstasks.BlockDeviceMapping)
for _, ed := range instanceType.EphemeralDevices() {
m := &awstasks.BlockDeviceMapping{
bdms := make([]*awstasks.BlockDeviceMapping, len(info.EphemeralDevices()))
for i, ed := range info.EphemeralDevices() {
bdms[i] = &awstasks.BlockDeviceMapping{
DeviceName: fi.String(ed.DeviceName),
VirtualName: fi.String(ed.VirtualName),
}
blockDeviceMappings[ed.DeviceName] = m
}
return blockDeviceMappings, nil
return bdms, nil
}
func (e *Elastigroup) buildRootDevice(cloud awsup.AWSCloud) (map[string]*awstasks.BlockDeviceMapping, error) {
image, err := resolveImage(cloud, fi.StringValue(e.ImageID))
func buildRootDevice(cloud awsup.AWSCloud, volumeOpts *RootVolumeOpts,
imageID *string) (*awstasks.BlockDeviceMapping, error) {
img, err := resolveImage(cloud, fi.StringValue(imageID))
if err != nil {
return nil, err
}
rootDeviceName := fi.StringValue(image.RootDeviceName)
blockDeviceMappings := make(map[string]*awstasks.BlockDeviceMapping)
rootDeviceMapping := &awstasks.BlockDeviceMapping{
bdm := &awstasks.BlockDeviceMapping{
DeviceName: img.RootDeviceName,
EbsVolumeSize: volumeOpts.Size,
EbsVolumeType: volumeOpts.Type,
EbsDeleteOnTermination: fi.Bool(true),
EbsVolumeSize: fi.Int64(int64(fi.Int32Value(e.RootVolumeOpts.Size))),
EbsVolumeType: e.RootVolumeOpts.Type,
}
// The parameter IOPS is not supported for gp2 volumes.
if e.RootVolumeOpts.IOPS != nil && fi.StringValue(e.RootVolumeOpts.Type) != "gp2" {
rootDeviceMapping.EbsVolumeIops = fi.Int64(int64(fi.Int32Value(e.RootVolumeOpts.IOPS)))
// IOPS is not supported for gp2 volumes.
if volumeOpts.IOPS != nil && fi.StringValue(volumeOpts.Type) != "gp2" {
bdm.EbsVolumeIops = volumeOpts.IOPS
}
blockDeviceMappings[rootDeviceName] = rootDeviceMapping
// Throughput is only supported for gp3 volumes.
if volumeOpts.Throughput != nil && fi.StringValue(volumeOpts.Type) == "gp3" {
bdm.EbsVolumeThroughput = volumeOpts.Throughput
}
return blockDeviceMappings, nil
return bdm, nil
}
func (e *Elastigroup) buildBlockDeviceMapping(deviceName string, i *awstasks.BlockDeviceMapping) *aws.BlockDeviceMapping {
o := &aws.BlockDeviceMapping{}
o.DeviceName = fi.String(deviceName)
o.VirtualName = i.VirtualName
func (e *Elastigroup) convertBlockDeviceMapping(in *awstasks.BlockDeviceMapping) *aws.BlockDeviceMapping {
out := &aws.BlockDeviceMapping{
DeviceName: in.DeviceName,
VirtualName: in.VirtualName,
}
if i.EbsDeleteOnTermination != nil || i.EbsVolumeSize != nil || i.EbsVolumeType != nil {
o.EBS = &aws.EBS{}
o.EBS.DeleteOnTermination = i.EbsDeleteOnTermination
o.EBS.VolumeSize = fi.Int(int(fi.Int64Value(i.EbsVolumeSize)))
o.EBS.VolumeType = i.EbsVolumeType
if in.EbsDeleteOnTermination != nil || in.EbsVolumeSize != nil || in.EbsVolumeType != nil {
out.EBS = &aws.EBS{
VolumeType: in.EbsVolumeType,
VolumeSize: fi.Int(int(fi.Int64Value(in.EbsVolumeSize))),
DeleteOnTermination: in.EbsDeleteOnTermination,
}
// The parameter IOPS is not supported for gp2 volumes.
if i.EbsVolumeIops != nil && fi.StringValue(i.EbsVolumeType) != "gp2" {
o.EBS.IOPS = fi.Int(int(fi.Int64Value(i.EbsVolumeIops)))
// IOPS is not valid for gp2 volumes.
if in.EbsVolumeIops != nil && fi.StringValue(in.EbsVolumeType) != "gp2" {
out.EBS.IOPS = fi.Int(int(fi.Int64Value(in.EbsVolumeIops)))
}
// Throughput is only valid for gp3 volumes.
if in.EbsVolumeThroughput != nil && fi.StringValue(in.EbsVolumeType) == "gp3" {
out.EBS.Throughput = fi.Int(int(fi.Int64Value(in.EbsVolumeThroughput)))
}
}
return o
return out
}
func (e *Elastigroup) applyDefaults() {

View File

@ -182,9 +182,38 @@ func (o *LaunchSpec) Find(c *fi.Context) (*LaunchSpec, error) {
// Root volume options.
{
if spec.RootVolumeSize != nil {
actual.RootVolumeOpts = new(RootVolumeOpts)
actual.RootVolumeOpts.Size = fi.Int32(int32(*spec.RootVolumeSize))
// Volume size.
{
if spec.RootVolumeSize != nil {
actual.RootVolumeOpts = new(RootVolumeOpts)
actual.RootVolumeOpts.Size = fi.Int64(int64(*spec.RootVolumeSize))
}
}
// Block device mappings.
{
if spec.BlockDeviceMappings != nil {
for _, b := range spec.BlockDeviceMappings {
if b.EBS == nil || b.EBS.SnapshotID != nil {
continue // not the root
}
if actual.RootVolumeOpts == nil {
actual.RootVolumeOpts = new(RootVolumeOpts)
}
if b.EBS.VolumeType != nil {
actual.RootVolumeOpts.Type = fi.String(strings.ToLower(fi.StringValue(b.EBS.VolumeType)))
}
if b.EBS.VolumeSize != nil {
actual.RootVolumeOpts.Size = fi.Int64(int64(fi.IntValue(b.EBS.VolumeSize)))
}
if b.EBS.IOPS != nil {
actual.RootVolumeOpts.IOPS = fi.Int64(int64(fi.IntValue(b.EBS.IOPS)))
}
if b.EBS.Throughput != nil {
actual.RootVolumeOpts.Throughput = fi.Int64(int64(fi.IntValue(b.EBS.Throughput)))
}
}
}
}
}
@ -367,11 +396,15 @@ func (_ *LaunchSpec) create(cloud awsup.AWSCloud, a, e, changes *LaunchSpec) err
// Root volume options.
{
if opts := e.RootVolumeOpts; opts != nil {
// Volume size.
if opts.Size != nil {
spec.SetRootVolumeSize(fi.Int(int(*opts.Size)))
rootDevice, err := buildRootDevice(cloud, e.RootVolumeOpts, e.ImageID)
if err != nil {
return err
}
spec.SetRootVolumeSize(nil) // mutually exclusive
spec.SetBlockDeviceMappings([]*aws.BlockDeviceMapping{
e.convertBlockDeviceMapping(rootDevice),
})
}
}
@ -548,14 +581,17 @@ func (_ *LaunchSpec) update(cloud awsup.AWSCloud, a, e, changes *LaunchSpec) err
// Root volume options.
{
if opts := changes.RootVolumeOpts; opts != nil {
// Volume size.
if opts.Size != nil {
spec.SetRootVolumeSize(fi.Int(int(*opts.Size)))
changed = true
rootDevice, err := buildRootDevice(cloud, opts, e.ImageID)
if err != nil {
return err
}
spec.SetRootVolumeSize(nil) // mutually exclusive
spec.SetBlockDeviceMappings([]*aws.BlockDeviceMapping{
e.convertBlockDeviceMapping(rootDevice),
})
changes.RootVolumeOpts = nil
changed = true
}
}
@ -743,7 +779,7 @@ type terraformLaunchSpec struct {
ImageID *string `json:"image_id,omitempty" cty:"image_id"`
AssociatePublicIPAddress *bool `json:"associate_public_ip_address,omitempty" cty:"associate_public_ip_address"`
RestrictScaleDown *bool `json:"restrict_scale_down,omitempty" cty:"restrict_scale_down"`
RootVolumeSize *int32 `json:"root_volume_size,omitempty" cty:"root_volume_size"`
RootVolumeSize *int64 `json:"root_volume_size,omitempty" cty:"root_volume_size"`
UserData *terraform.Literal `json:"user_data,omitempty" cty:"user_data"`
IAMInstanceProfile *terraform.Literal `json:"iam_instance_profile,omitempty" cty:"iam_instance_profile"`
KeyName *terraform.Literal `json:"key_name,omitempty" cty:"key_name"`
@ -754,6 +790,7 @@ type terraformLaunchSpec struct {
Labels []*terraformKV `json:"labels,omitempty" cty:"labels"`
Tags []*terraformKV `json:"tags,omitempty" cty:"tags"`
Headrooms []*terraformAutoScalerHeadroom `json:"autoscale_headrooms,omitempty" cty:"autoscale_headrooms"`
BlockDeviceMappings []*terraformBlockDeviceMapping `json:"block_device_mappings,omitempty" cty:"block_device_mappings"`
Strategy *terraformLaunchSpecStrategy `json:"strategy,omitempty" cty:"strategy"`
}
@ -761,6 +798,20 @@ type terraformLaunchSpecStrategy struct {
SpotPercentage *int64 `json:"spot_percentage,omitempty" cty:"spot_percentage"`
}
type terraformBlockDeviceMapping struct {
DeviceName *string `json:"device_name,omitempty" cty:"device_name"`
EBS *terraformBlockDeviceMappingEBS `json:"ebs,omitempty" cty:"ebs"`
}
type terraformBlockDeviceMappingEBS struct {
VirtualName *string `json:"virtual_name,omitempty" cty:"virtual_name"`
VolumeType *string `json:"volume_type,omitempty" cty:"volume_type"`
VolumeSize *int64 `json:"volume_size,omitempty" cty:"volume_size"`
VolumeIOPS *int64 `json:"iops,omitempty" cty:"iops"`
VolumeThroughput *int64 `json:"throughput,omitempty" cty:"throughput"`
DeleteOnTermination *bool `json:"delete_on_termination,omitempty" cty:"delete_on_termination"`
}
func (_ *LaunchSpec) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *LaunchSpec) error {
cloud := t.Cloud.(awsup.AWSCloud)
@ -840,11 +891,21 @@ func (_ *LaunchSpec) RenderTerraform(t *terraform.TerraformTarget, a, e, changes
// Root volume options.
if opts := e.RootVolumeOpts; opts != nil {
// Volume size.
if opts.Size != nil {
tf.RootVolumeSize = opts.Size
rootDevice, err := buildRootDevice(t.Cloud.(awsup.AWSCloud), e.RootVolumeOpts, e.ImageID)
if err != nil {
return err
}
tf.BlockDeviceMappings = append(tf.BlockDeviceMappings, &terraformBlockDeviceMapping{
DeviceName: rootDevice.DeviceName,
EBS: &terraformBlockDeviceMappingEBS{
VolumeType: rootDevice.EbsVolumeType,
VolumeSize: rootDevice.EbsVolumeSize,
VolumeIOPS: rootDevice.EbsVolumeIops,
VolumeThroughput: rootDevice.EbsVolumeThroughput,
DeleteOnTermination: fi.Bool(true),
},
})
}
// Tags.
@ -937,3 +998,30 @@ func (o *LaunchSpec) buildTags() []*aws.Tag {
return tags
}
func (o *LaunchSpec) convertBlockDeviceMapping(in *awstasks.BlockDeviceMapping) *aws.BlockDeviceMapping {
out := &aws.BlockDeviceMapping{
DeviceName: in.DeviceName,
VirtualName: in.VirtualName,
}
if in.EbsDeleteOnTermination != nil || in.EbsVolumeSize != nil || in.EbsVolumeType != nil {
out.EBS = &aws.EBS{
VolumeType: in.EbsVolumeType,
VolumeSize: fi.Int(int(fi.Int64Value(in.EbsVolumeSize))),
DeleteOnTermination: in.EbsDeleteOnTermination,
}
// IOPS is not valid for gp2 volumes.
if in.EbsVolumeIops != nil && fi.StringValue(in.EbsVolumeType) != "gp2" {
out.EBS.IOPS = fi.Int(int(fi.Int64Value(in.EbsVolumeIops)))
}
// Throughput is only valid for gp3 volumes.
if in.EbsVolumeThroughput != nil && fi.StringValue(in.EbsVolumeType) == "gp3" {
out.EBS.Throughput = fi.Int(int(fi.Int64Value(in.EbsVolumeThroughput)))
}
}
return out
}

View File

@ -275,7 +275,7 @@ func (o *Ocean) Find(c *fi.Context) (*Ocean, error) {
// Root volume options.
if lc.RootVolumeSize != nil {
actual.RootVolumeOpts = new(RootVolumeOpts)
actual.RootVolumeOpts.Size = fi.Int32(int32(*lc.RootVolumeSize))
actual.RootVolumeOpts.Size = fi.Int64(int64(*lc.RootVolumeSize))
}
// Monitoring.
@ -1034,7 +1034,7 @@ type terraformOcean struct {
EBSOptimized *bool `json:"ebs_optimized,omitempty" cty:"ebs_optimized"`
ImageID *string `json:"image_id,omitempty" cty:"image_id"`
AssociatePublicIPAddress *bool `json:"associate_public_ip_address,omitempty" cty:"associate_public_ip_address"`
RootVolumeSize *int32 `json:"root_volume_size,omitempty" cty:"root_volume_size"`
RootVolumeSize *int64 `json:"root_volume_size,omitempty" cty:"root_volume_size"`
UserData *terraform.Literal `json:"user_data,omitempty" cty:"user_data"`
IAMInstanceProfile *terraform.Literal `json:"iam_instance_profile,omitempty" cty:"iam_instance_profile"`
KeyName *terraform.Literal `json:"key_name,omitempty" cty:"key_name"`

View File

@ -9,6 +9,7 @@ go_library(
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/azure/v3:go_default_library",
"//vendor/github.com/spotinst/spotinst-sdk-go/service/elastigroup/providers/gcp: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",

View File

@ -3,6 +3,7 @@ package elastigroup
import (
"github.com/spotinst/spotinst-sdk-go/service/elastigroup/providers/aws"
"github.com/spotinst/spotinst-sdk-go/service/elastigroup/providers/azure"
azurev3 "github.com/spotinst/spotinst-sdk-go/service/elastigroup/providers/azure/v3"
"github.com/spotinst/spotinst-sdk-go/service/elastigroup/providers/gcp"
"github.com/spotinst/spotinst-sdk-go/spotinst"
"github.com/spotinst/spotinst-sdk-go/spotinst/client"
@ -15,6 +16,7 @@ import (
type Service interface {
CloudProviderAWS() aws.Service
CloudProviderAzure() azure.Service
CloudProviderAzureV3() azurev3.Service
CloudProviderGCP() gcp.Service
}
@ -46,6 +48,12 @@ func (s *ServiceOp) CloudProviderAzure() azure.Service {
}
}
func (s *ServiceOp) CloudProviderAzureV3() azurev3.Service {
return &azurev3.ServiceOp{
Client: s.Client,
}
}
func (s *ServiceOp) CloudProviderGCP() gcp.Service {
return &gcp.ServiceOp{
Client: s.Client,

View File

@ -499,6 +499,7 @@ type Strategy struct {
RevertToSpot *RevertToSpot `json:"revertToSpot,omitempty"`
ScalingStrategy *ScalingStrategy `json:"scalingStrategy,omitempty"`
UtilizeCommitments *bool `json:"utilizeCommitments,omitempty"`
MinimumInstanceLifetime *int `json:"minimumInstanceLifetime,omitempty"`
forceSendFields []string
nullFields []string
@ -618,6 +619,22 @@ type LaunchSpecification struct {
NetworkInterfaces []*NetworkInterface `json:"networkInterfaces,omitempty"`
Tags []*Tag `json:"tags,omitempty"`
MetadataOptions *MetadataOptions `json:"metadataOptions,omitempty"`
CPUOptions *CPUOptions `json:"cpuOptions,omitempty"`
forceSendFields []string
nullFields []string
}
type MetadataOptions struct {
HTTPTokens *string `json:"httpTokens,omitempty"`
HTTPPutResponseHopLimit *int `json:"httpPutResponseHopLimit,omitempty"`
forceSendFields []string
nullFields []string
}
type CPUOptions struct {
ThreadsPerCore *int `json:"threadsPerCore,omitempty"`
forceSendFields []string
nullFields []string
@ -920,14 +937,6 @@ type StopDeploymentInput struct {
type StopDeploymentOutput struct{}
type MetadataOptions struct {
HTTPTokens *string `json:"httpTokens,omitempty"`
HTTPPutResponseHopLimit *int `json:"httpPutResponseHopLimit,omitempty"`
forceSendFields []string
nullFields []string
}
func deploymentStatusFromJSON(in []byte) (*RollGroupStatus, error) {
b := new(RollGroupStatus)
if err := json.Unmarshal(in, b); err != nil {
@ -3064,6 +3073,13 @@ func (o *Strategy) SetUtilizeCommitments(v *bool) *Strategy {
return o
}
func (o *Strategy) SetMinimumInstanceLifetime(v *int) *Strategy {
if o.MinimumInstanceLifetime = v; o.MinimumInstanceLifetime == nil {
o.nullFields = append(o.nullFields, "MinimumInstanceLifetime")
}
return o
}
// endregion
// region ScalingStrategy
@ -3547,6 +3563,13 @@ func (o *LaunchSpecification) SetMetadataOptions(v *MetadataOptions) *LaunchSpec
return o
}
func (o *LaunchSpecification) SetCPUOptions(v *CPUOptions) *LaunchSpecification {
if o.CPUOptions = v; o.CPUOptions == nil {
o.nullFields = append(o.nullFields, "CPUOptions")
}
return o
}
// endregion
// region LoadBalancersConfig
@ -4313,3 +4336,19 @@ func (o *MetadataOptions) SetHTTPPutResponseHopLimit(v *int) *MetadataOptions {
}
// endregion
// region CPUOptions
func (o CPUOptions) MarshalJSON() ([]byte, error) {
type noMethod CPUOptions
raw := noMethod(o)
return jsonutil.MarshalJSON(raw, o.forceSendFields, o.nullFields)
}
func (o *CPUOptions) SetThreadsPerCore(v *int) *CPUOptions {
if o.ThreadsPerCore = v; o.ThreadsPerCore == nil {
o.nullFields = append(o.nullFields, "ThreadsPerCore")
}
return o
}
// endregion

View File

@ -766,7 +766,7 @@ func (s *ServiceOp) List(ctx context.Context, input *ListGroupsInput) (*ListGrou
}
func (s *ServiceOp) Create(ctx context.Context, input *CreateGroupInput) (*CreateGroupOutput, error) {
r := client.NewRequest(http.MethodPost, "/compute/azure/group")
r := client.NewRequest(http.MethodPost, "/azure/compute/group")
r.Obj = input
resp, err := client.RequireOK(s.Client.Do(ctx, r))

View File

@ -0,0 +1,19 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"azure.go",
"service.go",
],
importmap = "k8s.io/kops/vendor/github.com/spotinst/spotinst-sdk-go/service/elastigroup/providers/azure/v3",
importpath = "github.com/spotinst/spotinst-sdk-go/service/elastigroup/providers/azure/v3",
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",
],
)

View File

@ -0,0 +1,774 @@
package v3
import (
"context"
"encoding/json"
"io/ioutil"
"net/http"
"time"
"github.com/spotinst/spotinst-sdk-go/spotinst"
"github.com/spotinst/spotinst-sdk-go/spotinst/client"
"github.com/spotinst/spotinst-sdk-go/spotinst/util/jsonutil"
"github.com/spotinst/spotinst-sdk-go/spotinst/util/uritemplates"
)
type Group struct {
ID *string `json:"id,omitempty"`
Name *string `json:"name,omitempty"`
ResourceGroupName *string `json:"resourceGroupName,omitempty"`
Region *string `json:"region,omitempty"`
Capacity *Capacity `json:"capacity,omitempty"`
Compute *Compute `json:"compute,omitempty"`
Strategy *Strategy `json:"strategy,omitempty"`
// Read-only fields.
CreatedAt *time.Time `json:"createdAt,omitempty"`
UpdatedAt *time.Time `json:"updatedAt,omitempty"`
// forceSendFields is a list of field names (e.g. "Keys") to
// unconditionally include in API requests. By default, fields with
// empty values are omitted from API requests. However, any non-pointer,
// non-interface field appearing in ForceSendFields will be sent to the
// server regardless of whether the field is empty or not. This may be
// used to include empty fields in Patch requests.
forceSendFields []string
// nullFields is a list of field names (e.g. "Keys") to include in API
// requests with the JSON null value. By default, fields with empty
// values are omitted from API requests. However, any field with an
// empty value appearing in NullFields will be sent to the server as
// null. It is an error if a field in this list has a non-empty value.
// This may be used to include null fields in Patch requests.
nullFields []string
}
type Strategy struct {
OnDemandCount *int `json:"onDemandCount,omitempty"`
DrainingTimeout *int `json:"drainingTimeout,omitempty"`
SpotPercentage *int `json:"spotPercentage,omitempty"`
FallbackToOnDemand *bool `json:"fallbackToOd,omitempty"`
forceSendFields []string
nullFields []string
}
type Capacity struct {
Minimum *int `json:"minimum,omitempty"`
Maximum *int `json:"maximum,omitempty"`
Target *int `json:"target,omitempty"`
forceSendFields []string
nullFields []string
}
type Compute struct {
VMSizes *VMSizes `json:"vmSizes,omitempty"`
OS *string `json:"os,omitempty"`
LaunchSpecification *LaunchSpecification `json:"launchSpecification,omitempty"`
forceSendFields []string
nullFields []string
}
type VMSizes struct {
OnDemandSizes []string `json:"odSizes,omitempty"`
SpotSizes []string `json:"spotSizes,omitempty"`
forceSendFields []string
nullFields []string
}
type LaunchSpecification struct {
Image *Image `json:"image,omitempty"`
Network *Network `json:"network,omitempty"`
Login *Login `json:"login,omitempty"`
forceSendFields []string
nullFields []string
}
type Image struct {
MarketPlace *MarketPlaceImage `json:"marketplace,omitempty"`
Custom *CustomImage `json:"custom,omitempty"`
forceSendFields []string
nullFields []string
}
type MarketPlaceImage struct {
Publisher *string `json:"publisher,omitempty"`
Offer *string `json:"offer,omitempty"`
SKU *string `json:"sku,omitempty"`
Version *string `json:"version,omitempty"`
forceSendFields []string
nullFields []string
}
type CustomImage struct {
ResourceGroupName *string `json:"resourceGroupName,omitempty"`
Name *string `json:"name,omitempty"`
forceSendFields []string
nullFields []string
}
type Network struct {
VirtualNetworkName *string `json:"virtualNetworkName,omitempty"`
ResourceGroupName *string `json:"resourceGroupName,omitempty"`
NetworkInterfaces []*NetworkInterface `json:"networkInterfaces,omitempty"`
forceSendFields []string
nullFields []string
}
type NetworkInterface struct {
SubnetName *string `json:"subnetName,omitempty"`
AssignPublicIP *bool `json:"assignPublicIp,omitempty"`
IsPrimary *bool `json:"isPrimary,omitempty"`
AdditionalIPConfigs []*AdditionalIPConfig `json:"additionalIpConfigurations,omitempty"`
forceSendFields []string
nullFields []string
}
type AdditionalIPConfig struct {
Name *string `json:"name,omitempty"`
PrivateIPAddressVersion *string `json:"privateIpAddressVersion,omitempty"`
forceSendFields []string
nullFields []string
}
type Login struct {
UserName *string `json:"userName,omitempty"`
SSHPublicKey *string `json:"sshPublicKey,omitempty"`
Password *string `json:"password,omitempty"`
forceSendFields []string
nullFields []string
}
type CreateGroupInput struct {
Group *Group `json:"group,omitempty"`
}
type CreateGroupOutput struct {
Group *Group `json:"group,omitempty"`
}
type ReadGroupInput struct {
GroupID *string `json:"groupId,omitempty"`
}
type ReadGroupOutput struct {
Group *Group `json:"group,omitempty"`
}
type UpdateGroupInput struct {
Group *Group `json:"group,omitempty"`
}
type UpdateGroupOutput struct {
Group *Group `json:"group,omitempty"`
}
type DeleteGroupInput struct {
GroupID *string `json:"groupId,omitempty"`
}
type DeleteGroupOutput struct{}
type ListGroupsInput struct{}
type ListGroupsOutput struct {
Groups []*Group `json:"groups,omitempty"`
}
// region Unmarshallers
func groupFromJSON(in []byte) (*Group, error) {
b := new(Group)
if err := json.Unmarshal(in, b); err != nil {
return nil, err
}
return b, nil
}
func groupsFromJSON(in []byte) ([]*Group, error) {
var rw client.Response
if err := json.Unmarshal(in, &rw); err != nil {
return nil, err
}
out := make([]*Group, len(rw.Response.Items))
if len(out) == 0 {
return out, nil
}
for i, rb := range rw.Response.Items {
b, err := groupFromJSON(rb)
if err != nil {
return nil, err
}
out[i] = b
}
return out, nil
}
func groupsFromHttpResponse(resp *http.Response) ([]*Group, error) {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return groupsFromJSON(body)
}
// endregion
// region API requests
func (s *ServiceOp) List(ctx context.Context, input *ListGroupsInput) (*ListGroupsOutput, error) {
r := client.NewRequest(http.MethodGet, "/azure/compute/group")
resp, err := client.RequireOK(s.Client.Do(ctx, r))
if err != nil {
return nil, err
}
defer resp.Body.Close()
gs, err := groupsFromHttpResponse(resp)
if err != nil {
return nil, err
}
return &ListGroupsOutput{Groups: gs}, nil
}
func (s *ServiceOp) Create(ctx context.Context, input *CreateGroupInput) (*CreateGroupOutput, error) {
r := client.NewRequest(http.MethodPost, "/azure/compute/group")
r.Obj = input
resp, err := client.RequireOK(s.Client.Do(ctx, r))
if err != nil {
return nil, err
}
defer resp.Body.Close()
gs, err := groupsFromHttpResponse(resp)
if err != nil {
return nil, err
}
output := new(CreateGroupOutput)
if len(gs) > 0 {
output.Group = gs[0]
}
return output, nil
}
func (s *ServiceOp) Read(ctx context.Context, input *ReadGroupInput) (*ReadGroupOutput, error) {
path, err := uritemplates.Expand("/azure/compute/group/{groupId}", uritemplates.Values{
"groupId": spotinst.StringValue(input.GroupID),
})
if err != nil {
return nil, err
}
r := client.NewRequest(http.MethodGet, path)
resp, err := client.RequireOK(s.Client.Do(ctx, r))
if err != nil {
return nil, err
}
defer resp.Body.Close()
gs, err := groupsFromHttpResponse(resp)
if err != nil {
return nil, err
}
output := new(ReadGroupOutput)
if len(gs) > 0 {
output.Group = gs[0]
}
return output, nil
}
func (s *ServiceOp) Update(ctx context.Context, input *UpdateGroupInput) (*UpdateGroupOutput, error) {
path, err := uritemplates.Expand("/azure/compute/group/{groupId}", uritemplates.Values{
"groupId": spotinst.StringValue(input.Group.ID),
})
if err != nil {
return nil, err
}
// We do NOT need the ID anymore, so let's drop it.
input.Group.ID = nil
r := client.NewRequest(http.MethodPut, path)
r.Obj = input
resp, err := client.RequireOK(s.Client.Do(ctx, r))
if err != nil {
return nil, err
}
defer resp.Body.Close()
gs, err := groupsFromHttpResponse(resp)
if err != nil {
return nil, err
}
output := new(UpdateGroupOutput)
if len(gs) > 0 {
output.Group = gs[0]
}
return output, nil
}
func (s *ServiceOp) Delete(ctx context.Context, input *DeleteGroupInput) (*DeleteGroupOutput, error) {
path, err := uritemplates.Expand("/azure/compute/group/{groupId}", uritemplates.Values{
"groupId": spotinst.StringValue(input.GroupID),
})
if err != nil {
return nil, err
}
r := client.NewRequest(http.MethodDelete, path)
resp, err := client.RequireOK(s.Client.Do(ctx, r))
if err != nil {
return nil, err
}
defer resp.Body.Close()
return &DeleteGroupOutput{}, nil
}
// endregion
// region Group
func (o Group) MarshalJSON() ([]byte, error) {
type noMethod Group
raw := noMethod(o)
return jsonutil.MarshalJSON(raw, o.forceSendFields, o.nullFields)
}
func (o *Group) SetId(v *string) *Group {
if o.ID = v; o.ID == nil {
o.nullFields = append(o.nullFields, "ID")
}
return o
}
func (o *Group) SetName(v *string) *Group {
if o.Name = v; o.Name == nil {
o.nullFields = append(o.nullFields, "Name")
}
return o
}
func (o *Group) SetResourceGroupName(v *string) *Group {
if o.ResourceGroupName = v; o.ResourceGroupName == nil {
o.nullFields = append(o.nullFields, "ResourceGroupName")
}
return o
}
func (o *Group) SetCapacity(v *Capacity) *Group {
if o.Capacity = v; o.Capacity == nil {
o.nullFields = append(o.nullFields, "Capacity")
}
return o
}
func (o *Group) SetCompute(v *Compute) *Group {
if o.Compute = v; o.Compute == nil {
o.nullFields = append(o.nullFields, "Compute")
}
return o
}
func (o *Group) SetStrategy(v *Strategy) *Group {
if o.Strategy = v; o.Strategy == nil {
o.nullFields = append(o.nullFields, "Strategy")
}
return o
}
func (o *Group) SetRegion(v *string) *Group {
if o.Region = v; o.Region == nil {
o.nullFields = append(o.nullFields, "Region")
}
return o
}
// endregion
// region Strategy
func (o Strategy) MarshalJSON() ([]byte, error) {
type noMethod Strategy
raw := noMethod(o)
return jsonutil.MarshalJSON(raw, o.forceSendFields, o.nullFields)
}
func (o *Strategy) SetOnDemandCount(v *int) *Strategy {
if o.OnDemandCount = v; o.OnDemandCount == nil {
o.nullFields = append(o.nullFields, "OnDemandCount")
}
return o
}
func (o *Strategy) SetDrainingTimeout(v *int) *Strategy {
if o.DrainingTimeout = v; o.DrainingTimeout == nil {
o.nullFields = append(o.nullFields, "DrainingTimeout")
}
return o
}
func (o *Strategy) SetSpotPercentage(v *int) *Strategy {
if o.SpotPercentage = v; o.SpotPercentage == nil {
o.nullFields = append(o.nullFields, "SpotPercentage")
}
return o
}
func (o *Strategy) SetFallbackToOnDemand(v *bool) *Strategy {
if o.FallbackToOnDemand = v; o.FallbackToOnDemand == nil {
o.nullFields = append(o.nullFields, "FallbackToOnDemand")
}
return o
}
// endregion
// region Capacity
func (o Capacity) MarshalJSON() ([]byte, error) {
type noMethod Capacity
raw := noMethod(o)
return jsonutil.MarshalJSON(raw, o.forceSendFields, o.nullFields)
}
func (o *Capacity) SetMinimum(v *int) *Capacity {
if o.Minimum = v; o.Minimum == nil {
o.nullFields = append(o.nullFields, "Minimum")
}
return o
}
func (o *Capacity) SetMaximum(v *int) *Capacity {
if o.Maximum = v; o.Maximum == nil {
o.nullFields = append(o.nullFields, "Maximum")
}
return o
}
func (o *Capacity) SetTarget(v *int) *Capacity {
if o.Target = v; o.Target == nil {
o.nullFields = append(o.nullFields, "Target")
}
return o
}
// endregion
// region Compute
func (o Compute) MarshalJSON() ([]byte, error) {
type noMethod Compute
raw := noMethod(o)
return jsonutil.MarshalJSON(raw, o.forceSendFields, o.nullFields)
}
func (o *Compute) SetVMSizes(v *VMSizes) *Compute {
if o.VMSizes = v; o.VMSizes == nil {
o.nullFields = append(o.nullFields, "VMSizes")
}
return o
}
func (o *Compute) SetOS(v *string) *Compute {
if o.OS = v; o.OS == nil {
o.nullFields = append(o.nullFields, "OS")
}
return o
}
func (o *Compute) SetLaunchSpecification(v *LaunchSpecification) *Compute {
if o.LaunchSpecification = v; o.LaunchSpecification == nil {
o.nullFields = append(o.nullFields, "LaunchSpecification")
}
return o
}
// endregion
// region VMSize
func (o VMSizes) MarshalJSON() ([]byte, error) {
type noMethod VMSizes
raw := noMethod(o)
return jsonutil.MarshalJSON(raw, o.forceSendFields, o.nullFields)
}
func (o *VMSizes) SetOnDemandSizes(v []string) *VMSizes {
if o.OnDemandSizes = v; o.OnDemandSizes == nil {
o.nullFields = append(o.nullFields, "OnDemandSizes")
}
return o
}
func (o *VMSizes) SetSpotSizes(v []string) *VMSizes {
if o.SpotSizes = v; o.SpotSizes == nil {
o.nullFields = append(o.nullFields, "SpotSizes")
}
return o
}
// endregion
// region LaunchSpecification
func (o LaunchSpecification) MarshalJSON() ([]byte, error) {
type noMethod LaunchSpecification
raw := noMethod(o)
return jsonutil.MarshalJSON(raw, o.forceSendFields, o.nullFields)
}
func (o *LaunchSpecification) SetImage(v *Image) *LaunchSpecification {
if o.Image = v; o.Image == nil {
o.nullFields = append(o.nullFields, "Image")
}
return o
}
func (o *LaunchSpecification) SetNetwork(v *Network) *LaunchSpecification {
if o.Network = v; o.Network == nil {
o.nullFields = append(o.nullFields, "Network")
}
return o
}
func (o *LaunchSpecification) SetLogin(v *Login) *LaunchSpecification {
if o.Login = v; o.Login == nil {
o.nullFields = append(o.nullFields, "Login")
}
return o
}
// endregion
// region Image
func (o Image) MarshalJSON() ([]byte, error) {
type noMethod Image
raw := noMethod(o)
return jsonutil.MarshalJSON(raw, o.forceSendFields, o.nullFields)
}
func (o *Image) SetMarketPlaceImage(v *MarketPlaceImage) *Image {
if o.MarketPlace = v; o.MarketPlace == nil {
o.nullFields = append(o.nullFields, "MarketPlace")
}
return o
}
func (o *Image) SetCustom(v *CustomImage) *Image {
if o.Custom = v; o.Custom == nil {
o.nullFields = append(o.nullFields, "Custom")
}
return o
}
// endregion
// region MarketPlaceImage
func (o MarketPlaceImage) MarshalJSON() ([]byte, error) {
type noMethod MarketPlaceImage
raw := noMethod(o)
return jsonutil.MarshalJSON(raw, o.forceSendFields, o.nullFields)
}
func (o *MarketPlaceImage) SetPublisher(v *string) *MarketPlaceImage {
if o.Publisher = v; o.Publisher == nil {
o.nullFields = append(o.nullFields, "Publisher")
}
return o
}
func (o *MarketPlaceImage) SetOffer(v *string) *MarketPlaceImage {
if o.Offer = v; o.Offer == nil {
o.nullFields = append(o.nullFields, "Offer")
}
return o
}
func (o *MarketPlaceImage) SetSKU(v *string) *MarketPlaceImage {
if o.SKU = v; o.SKU == nil {
o.nullFields = append(o.nullFields, "SKU")
}
return o
}
func (o *MarketPlaceImage) SetVersion(v *string) *MarketPlaceImage {
if o.Version = v; o.Version == nil {
o.nullFields = append(o.nullFields, "Version")
}
return o
}
// endregion
// region CustomImage
func (o CustomImage) MarshalJSON() ([]byte, error) {
type noMethod CustomImage
raw := noMethod(o)
return jsonutil.MarshalJSON(raw, o.forceSendFields, o.nullFields)
}
func (o *CustomImage) SetResourceGroupName(v *string) *CustomImage {
if o.ResourceGroupName = v; o.ResourceGroupName == nil {
o.nullFields = append(o.nullFields, "ResourceGroupName")
}
return o
}
func (o *CustomImage) SetName(v *string) *CustomImage {
if o.Name = v; o.Name == nil {
o.nullFields = append(o.nullFields, "Name")
}
return o
}
// endregion
// region Network
func (o Network) MarshalJSON() ([]byte, error) {
type noMethod Network
raw := noMethod(o)
return jsonutil.MarshalJSON(raw, o.forceSendFields, o.nullFields)
}
func (o *Network) SetVirtualNetworkName(v *string) *Network {
if o.VirtualNetworkName = v; o.VirtualNetworkName == nil {
o.nullFields = append(o.nullFields, "VirtualNetworkName")
}
return o
}
func (o *Network) SetResourceGroupName(v *string) *Network {
if o.ResourceGroupName = v; o.ResourceGroupName == nil {
o.nullFields = append(o.nullFields, "ResourceGroupName")
}
return o
}
func (o *Network) SetNetworkInterfaces(v []*NetworkInterface) *Network {
if o.NetworkInterfaces = v; o.NetworkInterfaces == nil {
o.nullFields = append(o.nullFields, "NetworkInterfaces")
}
return o
}
// endregion
// region NetworkInterface
func (o NetworkInterface) MarshalJSON() ([]byte, error) {
type noMethod NetworkInterface
raw := noMethod(o)
return jsonutil.MarshalJSON(raw, o.forceSendFields, o.nullFields)
}
func (o *NetworkInterface) SetSubnetName(v *string) *NetworkInterface {
if o.SubnetName = v; o.SubnetName == nil {
o.nullFields = append(o.nullFields, "SubnetName")
}
return o
}
func (o *NetworkInterface) SetAdditionalIPConfigs(v []*AdditionalIPConfig) *NetworkInterface {
if o.AdditionalIPConfigs = v; o.AdditionalIPConfigs == nil {
o.nullFields = append(o.nullFields, "AdditionalIPConfigs")
}
return o
}
func (o *NetworkInterface) SetAssignPublicIP(v *bool) *NetworkInterface {
if o.AssignPublicIP = v; o.AssignPublicIP == nil {
o.nullFields = append(o.nullFields, "AssignPublicIP")
}
return o
}
func (o *NetworkInterface) SetIsPrimary(v *bool) *NetworkInterface {
if o.IsPrimary = v; o.IsPrimary == nil {
o.nullFields = append(o.nullFields, "IsPrimary")
}
return o
}
// endregion
// region AdditionalIPConfig
func (o AdditionalIPConfig) MarshalJSON() ([]byte, error) {
type noMethod AdditionalIPConfig
raw := noMethod(o)
return jsonutil.MarshalJSON(raw, o.forceSendFields, o.nullFields)
}
// SetName sets the name
func (o *AdditionalIPConfig) SetName(v *string) *AdditionalIPConfig {
if o.Name = v; o.Name == nil {
o.nullFields = append(o.nullFields, "Name")
}
return o
}
// SetPrivateIPAddressVersion sets the ip address version
func (o *AdditionalIPConfig) SetPrivateIPAddressVersion(v *string) *AdditionalIPConfig {
if o.PrivateIPAddressVersion = v; o.PrivateIPAddressVersion == nil {
o.nullFields = append(o.nullFields, "PrivateIPAddressVersion")
}
return o
}
// endregion
// region Login
func (o Login) MarshalJSON() ([]byte, error) {
type noMethod Login
raw := noMethod(o)
return jsonutil.MarshalJSON(raw, o.forceSendFields, o.nullFields)
}
func (o *Login) SetUserName(v *string) *Login {
if o.UserName = v; o.UserName == nil {
o.nullFields = append(o.nullFields, "UserName")
}
return o
}
func (o *Login) SetSSHPublicKey(v *string) *Login {
if o.SSHPublicKey = v; o.SSHPublicKey == nil {
o.nullFields = append(o.nullFields, "SSHPublicKey")
}
return o
}
func (o *Login) SetPassword(v *string) *Login {
if o.Password = v; o.Password == nil {
o.nullFields = append(o.nullFields, "Password")
}
return o
}
// endregion

View File

@ -0,0 +1,36 @@
package v3
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 {
Create(context.Context, *CreateGroupInput) (*CreateGroupOutput, error)
Read(context.Context, *ReadGroupInput) (*ReadGroupOutput, error)
Update(context.Context, *UpdateGroupInput) (*UpdateGroupOutput, error)
Delete(context.Context, *DeleteGroupInput) (*DeleteGroupOutput, error)
List(context.Context, *ListGroupsInput) (*ListGroupsOutput, 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

@ -26,6 +26,7 @@ type ECSLaunchSpec struct {
BlockDeviceMappings []*ECSBlockDeviceMapping `json:"blockDeviceMappings,omitempty"`
Tags []*Tag `json:"tags,omitempty"`
InstanceTypes []string `json:"instanceTypes,omitempty"`
RestrictScaleDown *bool `json:"restrictScaleDown,omitempty"`
// Read-only fields.
CreatedAt *time.Time `json:"createdAt,omitempty"`
@ -397,6 +398,13 @@ func (o *ECSLaunchSpec) SetInstanceTypes(v []string) *ECSLaunchSpec {
return o
}
func (o *ECSLaunchSpec) SetRestrictScaleDown(v *bool) *ECSLaunchSpec {
if o.RestrictScaleDown = v; o.RestrictScaleDown == nil {
o.nullFields = append(o.nullFields, "RestrictScaleDown")
}
return o
}
// endregion
// region Attributes

View File

@ -13,13 +13,14 @@ import (
)
type LaunchSpec struct {
ID *string `json:"id,omitempty"`
OceanID *string `json:"oceanId,omitempty"`
SourceImage *string `json:"sourceImage,omitempty"`
Metadata []*Metadata `json:"metadata,omitempty"`
Labels []*Label `json:"labels,omitempty"`
Taints []*Taint `json:"taints,omitempty"`
AutoScale *AutoScale `json:"autoScale,omitempty"`
ID *string `json:"id,omitempty"`
OceanID *string `json:"oceanId,omitempty"`
SourceImage *string `json:"sourceImage,omitempty"`
Metadata []*Metadata `json:"metadata,omitempty"`
Labels []*Label `json:"labels,omitempty"`
Taints []*Taint `json:"taints,omitempty"`
AutoScale *AutoScale `json:"autoScale,omitempty"`
RestrictScaleDown *bool `json:"restrictScaleDown,omitempty"`
// forceSendFields is a list of field names (e.g. "Keys") to
// unconditionally include in API requests. By default, fields with
@ -356,6 +357,13 @@ func (o *LaunchSpec) SetAutoScale(v *AutoScale) *LaunchSpec {
return o
}
func (o *LaunchSpec) SetRestrictScaleDown(v *bool) *LaunchSpec {
if o.RestrictScaleDown = v; o.RestrictScaleDown == nil {
o.nullFields = append(o.nullFields, "RestrictScaleDown")
}
return o
}
// endregion
// region Label

View File

@ -14,7 +14,7 @@ go_library(
importpath = "github.com/spotinst/spotinst-sdk-go/spotinst/credentials",
visibility = ["//visibility:public"],
deps = [
"//vendor/github.com/go-ini/ini:go_default_library",
"//vendor/github.com/spotinst/spotinst-sdk-go/spotinst/featureflag:go_default_library",
"//vendor/gopkg.in/ini.v1:go_default_library",
],
)

View File

@ -8,7 +8,7 @@ import (
"path/filepath"
"runtime"
"github.com/go-ini/ini"
"gopkg.in/ini.v1"
)
const (

View File

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

View File

@ -6,6 +6,7 @@ go_library(
"assertion_compare.go",
"assertion_format.go",
"assertion_forward.go",
"assertion_order.go",
"assertions.go",
"doc.go",
"errors.go",

View File

@ -13,12 +13,42 @@ const (
compareGreater
)
var (
intType = reflect.TypeOf(int(1))
int8Type = reflect.TypeOf(int8(1))
int16Type = reflect.TypeOf(int16(1))
int32Type = reflect.TypeOf(int32(1))
int64Type = reflect.TypeOf(int64(1))
uintType = reflect.TypeOf(uint(1))
uint8Type = reflect.TypeOf(uint8(1))
uint16Type = reflect.TypeOf(uint16(1))
uint32Type = reflect.TypeOf(uint32(1))
uint64Type = reflect.TypeOf(uint64(1))
float32Type = reflect.TypeOf(float32(1))
float64Type = reflect.TypeOf(float64(1))
stringType = reflect.TypeOf("")
)
func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
obj1Value := reflect.ValueOf(obj1)
obj2Value := reflect.ValueOf(obj2)
// throughout this switch we try and avoid calling .Convert() if possible,
// as this has a pretty big performance impact
switch kind {
case reflect.Int:
{
intobj1 := obj1.(int)
intobj2 := obj2.(int)
intobj1, ok := obj1.(int)
if !ok {
intobj1 = obj1Value.Convert(intType).Interface().(int)
}
intobj2, ok := obj2.(int)
if !ok {
intobj2 = obj2Value.Convert(intType).Interface().(int)
}
if intobj1 > intobj2 {
return compareGreater, true
}
@ -31,8 +61,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
}
case reflect.Int8:
{
int8obj1 := obj1.(int8)
int8obj2 := obj2.(int8)
int8obj1, ok := obj1.(int8)
if !ok {
int8obj1 = obj1Value.Convert(int8Type).Interface().(int8)
}
int8obj2, ok := obj2.(int8)
if !ok {
int8obj2 = obj2Value.Convert(int8Type).Interface().(int8)
}
if int8obj1 > int8obj2 {
return compareGreater, true
}
@ -45,8 +81,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
}
case reflect.Int16:
{
int16obj1 := obj1.(int16)
int16obj2 := obj2.(int16)
int16obj1, ok := obj1.(int16)
if !ok {
int16obj1 = obj1Value.Convert(int16Type).Interface().(int16)
}
int16obj2, ok := obj2.(int16)
if !ok {
int16obj2 = obj2Value.Convert(int16Type).Interface().(int16)
}
if int16obj1 > int16obj2 {
return compareGreater, true
}
@ -59,8 +101,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
}
case reflect.Int32:
{
int32obj1 := obj1.(int32)
int32obj2 := obj2.(int32)
int32obj1, ok := obj1.(int32)
if !ok {
int32obj1 = obj1Value.Convert(int32Type).Interface().(int32)
}
int32obj2, ok := obj2.(int32)
if !ok {
int32obj2 = obj2Value.Convert(int32Type).Interface().(int32)
}
if int32obj1 > int32obj2 {
return compareGreater, true
}
@ -73,8 +121,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
}
case reflect.Int64:
{
int64obj1 := obj1.(int64)
int64obj2 := obj2.(int64)
int64obj1, ok := obj1.(int64)
if !ok {
int64obj1 = obj1Value.Convert(int64Type).Interface().(int64)
}
int64obj2, ok := obj2.(int64)
if !ok {
int64obj2 = obj2Value.Convert(int64Type).Interface().(int64)
}
if int64obj1 > int64obj2 {
return compareGreater, true
}
@ -87,8 +141,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
}
case reflect.Uint:
{
uintobj1 := obj1.(uint)
uintobj2 := obj2.(uint)
uintobj1, ok := obj1.(uint)
if !ok {
uintobj1 = obj1Value.Convert(uintType).Interface().(uint)
}
uintobj2, ok := obj2.(uint)
if !ok {
uintobj2 = obj2Value.Convert(uintType).Interface().(uint)
}
if uintobj1 > uintobj2 {
return compareGreater, true
}
@ -101,8 +161,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
}
case reflect.Uint8:
{
uint8obj1 := obj1.(uint8)
uint8obj2 := obj2.(uint8)
uint8obj1, ok := obj1.(uint8)
if !ok {
uint8obj1 = obj1Value.Convert(uint8Type).Interface().(uint8)
}
uint8obj2, ok := obj2.(uint8)
if !ok {
uint8obj2 = obj2Value.Convert(uint8Type).Interface().(uint8)
}
if uint8obj1 > uint8obj2 {
return compareGreater, true
}
@ -115,8 +181,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
}
case reflect.Uint16:
{
uint16obj1 := obj1.(uint16)
uint16obj2 := obj2.(uint16)
uint16obj1, ok := obj1.(uint16)
if !ok {
uint16obj1 = obj1Value.Convert(uint16Type).Interface().(uint16)
}
uint16obj2, ok := obj2.(uint16)
if !ok {
uint16obj2 = obj2Value.Convert(uint16Type).Interface().(uint16)
}
if uint16obj1 > uint16obj2 {
return compareGreater, true
}
@ -129,8 +201,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
}
case reflect.Uint32:
{
uint32obj1 := obj1.(uint32)
uint32obj2 := obj2.(uint32)
uint32obj1, ok := obj1.(uint32)
if !ok {
uint32obj1 = obj1Value.Convert(uint32Type).Interface().(uint32)
}
uint32obj2, ok := obj2.(uint32)
if !ok {
uint32obj2 = obj2Value.Convert(uint32Type).Interface().(uint32)
}
if uint32obj1 > uint32obj2 {
return compareGreater, true
}
@ -143,8 +221,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
}
case reflect.Uint64:
{
uint64obj1 := obj1.(uint64)
uint64obj2 := obj2.(uint64)
uint64obj1, ok := obj1.(uint64)
if !ok {
uint64obj1 = obj1Value.Convert(uint64Type).Interface().(uint64)
}
uint64obj2, ok := obj2.(uint64)
if !ok {
uint64obj2 = obj2Value.Convert(uint64Type).Interface().(uint64)
}
if uint64obj1 > uint64obj2 {
return compareGreater, true
}
@ -157,8 +241,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
}
case reflect.Float32:
{
float32obj1 := obj1.(float32)
float32obj2 := obj2.(float32)
float32obj1, ok := obj1.(float32)
if !ok {
float32obj1 = obj1Value.Convert(float32Type).Interface().(float32)
}
float32obj2, ok := obj2.(float32)
if !ok {
float32obj2 = obj2Value.Convert(float32Type).Interface().(float32)
}
if float32obj1 > float32obj2 {
return compareGreater, true
}
@ -171,8 +261,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
}
case reflect.Float64:
{
float64obj1 := obj1.(float64)
float64obj2 := obj2.(float64)
float64obj1, ok := obj1.(float64)
if !ok {
float64obj1 = obj1Value.Convert(float64Type).Interface().(float64)
}
float64obj2, ok := obj2.(float64)
if !ok {
float64obj2 = obj2Value.Convert(float64Type).Interface().(float64)
}
if float64obj1 > float64obj2 {
return compareGreater, true
}
@ -185,8 +281,14 @@ func compare(obj1, obj2 interface{}, kind reflect.Kind) (CompareType, bool) {
}
case reflect.String:
{
stringobj1 := obj1.(string)
stringobj2 := obj2.(string)
stringobj1, ok := obj1.(string)
if !ok {
stringobj1 = obj1Value.Convert(stringType).Interface().(string)
}
stringobj2, ok := obj2.(string)
if !ok {
stringobj2 = obj2Value.Convert(stringType).Interface().(string)
}
if stringobj1 > stringobj2 {
return compareGreater, true
}
@ -240,6 +342,24 @@ func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...inter
return compareTwoValues(t, e1, e2, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs)
}
// Positive asserts that the specified element is positive
//
// assert.Positive(t, 1)
// assert.Positive(t, 1.23)
func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
zero := reflect.Zero(reflect.TypeOf(e))
return compareTwoValues(t, e, zero.Interface(), []CompareType{compareGreater}, "\"%v\" is not positive", msgAndArgs)
}
// Negative asserts that the specified element is negative
//
// assert.Negative(t, -1)
// assert.Negative(t, -1.23)
func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) bool {
zero := reflect.Zero(reflect.TypeOf(e))
return compareTwoValues(t, e, zero.Interface(), []CompareType{compareLess}, "\"%v\" is not negative", msgAndArgs)
}
func compareTwoValues(t TestingT, e1 interface{}, e2 interface{}, allowedComparesResults []CompareType, failMessage string, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()

View File

@ -114,6 +114,24 @@ func Errorf(t TestingT, err error, msg string, args ...interface{}) bool {
return Error(t, err, append([]interface{}{msg}, args...)...)
}
// ErrorAsf asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value.
// This is a wrapper for errors.As.
func ErrorAsf(t TestingT, err error, target interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return ErrorAs(t, err, target, append([]interface{}{msg}, args...)...)
}
// ErrorIsf asserts that at least one of the errors in err's chain matches target.
// This is a wrapper for errors.Is.
func ErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return ErrorIs(t, err, target, append([]interface{}{msg}, args...)...)
}
// Eventuallyf asserts that given condition will be met in waitFor time,
// periodically checking target function each tick.
//
@ -321,6 +339,54 @@ func InEpsilonSlicef(t TestingT, expected interface{}, actual interface{}, epsil
return InEpsilonSlice(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...)
}
// IsDecreasingf asserts that the collection is decreasing
//
// assert.IsDecreasingf(t, []int{2, 1, 0}, "error message %s", "formatted")
// assert.IsDecreasingf(t, []float{2, 1}, "error message %s", "formatted")
// assert.IsDecreasingf(t, []string{"b", "a"}, "error message %s", "formatted")
func IsDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return IsDecreasing(t, object, append([]interface{}{msg}, args...)...)
}
// IsIncreasingf asserts that the collection is increasing
//
// assert.IsIncreasingf(t, []int{1, 2, 3}, "error message %s", "formatted")
// assert.IsIncreasingf(t, []float{1, 2}, "error message %s", "formatted")
// assert.IsIncreasingf(t, []string{"a", "b"}, "error message %s", "formatted")
func IsIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return IsIncreasing(t, object, append([]interface{}{msg}, args...)...)
}
// IsNonDecreasingf asserts that the collection is not decreasing
//
// assert.IsNonDecreasingf(t, []int{1, 1, 2}, "error message %s", "formatted")
// assert.IsNonDecreasingf(t, []float{1, 2}, "error message %s", "formatted")
// assert.IsNonDecreasingf(t, []string{"a", "b"}, "error message %s", "formatted")
func IsNonDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return IsNonDecreasing(t, object, append([]interface{}{msg}, args...)...)
}
// IsNonIncreasingf asserts that the collection is not increasing
//
// assert.IsNonIncreasingf(t, []int{2, 1, 1}, "error message %s", "formatted")
// assert.IsNonIncreasingf(t, []float{2, 1}, "error message %s", "formatted")
// assert.IsNonIncreasingf(t, []string{"b", "a"}, "error message %s", "formatted")
func IsNonIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return IsNonIncreasing(t, object, append([]interface{}{msg}, args...)...)
}
// IsTypef asserts that the specified objects are of the same type.
func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
@ -375,6 +441,17 @@ func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args .
return LessOrEqual(t, e1, e2, append([]interface{}{msg}, args...)...)
}
// Negativef asserts that the specified element is negative
//
// assert.Negativef(t, -1, "error message %s", "formatted")
// assert.Negativef(t, -1.23, "error message %s", "formatted")
func Negativef(t TestingT, e interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Negative(t, e, append([]interface{}{msg}, args...)...)
}
// Neverf asserts that the given condition doesn't satisfy in waitFor time,
// periodically checking the target function each tick.
//
@ -476,6 +553,15 @@ func NotEqualValuesf(t TestingT, expected interface{}, actual interface{}, msg s
return NotEqualValues(t, expected, actual, append([]interface{}{msg}, args...)...)
}
// NotErrorIsf asserts that at none of the errors in err's chain matches target.
// This is a wrapper for errors.Is.
func NotErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return NotErrorIs(t, err, target, append([]interface{}{msg}, args...)...)
}
// NotNilf asserts that the specified object is not nil.
//
// assert.NotNilf(t, err, "error message %s", "formatted")
@ -572,6 +658,17 @@ func PanicsWithValuef(t TestingT, expected interface{}, f PanicTestFunc, msg str
return PanicsWithValue(t, expected, f, append([]interface{}{msg}, args...)...)
}
// Positivef asserts that the specified element is positive
//
// assert.Positivef(t, 1, "error message %s", "formatted")
// assert.Positivef(t, 1.23, "error message %s", "formatted")
func Positivef(t TestingT, e interface{}, msg string, args ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
return Positive(t, e, append([]interface{}{msg}, args...)...)
}
// Regexpf asserts that a specified regexp matches a string.
//
// assert.Regexpf(t, regexp.MustCompile("start"), "it's starting", "error message %s", "formatted")

View File

@ -204,6 +204,42 @@ func (a *Assertions) Error(err error, msgAndArgs ...interface{}) bool {
return Error(a.t, err, msgAndArgs...)
}
// ErrorAs asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value.
// This is a wrapper for errors.As.
func (a *Assertions) ErrorAs(err error, target interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return ErrorAs(a.t, err, target, msgAndArgs...)
}
// ErrorAsf asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value.
// This is a wrapper for errors.As.
func (a *Assertions) ErrorAsf(err error, target interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return ErrorAsf(a.t, err, target, msg, args...)
}
// ErrorIs asserts that at least one of the errors in err's chain matches target.
// This is a wrapper for errors.Is.
func (a *Assertions) ErrorIs(err error, target error, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return ErrorIs(a.t, err, target, msgAndArgs...)
}
// ErrorIsf asserts that at least one of the errors in err's chain matches target.
// This is a wrapper for errors.Is.
func (a *Assertions) ErrorIsf(err error, target error, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return ErrorIsf(a.t, err, target, msg, args...)
}
// Errorf asserts that a function returned an error (i.e. not `nil`).
//
// actualObj, err := SomeFunction()
@ -631,6 +667,102 @@ func (a *Assertions) InEpsilonf(expected interface{}, actual interface{}, epsilo
return InEpsilonf(a.t, expected, actual, epsilon, msg, args...)
}
// IsDecreasing asserts that the collection is decreasing
//
// a.IsDecreasing([]int{2, 1, 0})
// a.IsDecreasing([]float{2, 1})
// a.IsDecreasing([]string{"b", "a"})
func (a *Assertions) IsDecreasing(object interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return IsDecreasing(a.t, object, msgAndArgs...)
}
// IsDecreasingf asserts that the collection is decreasing
//
// a.IsDecreasingf([]int{2, 1, 0}, "error message %s", "formatted")
// a.IsDecreasingf([]float{2, 1}, "error message %s", "formatted")
// a.IsDecreasingf([]string{"b", "a"}, "error message %s", "formatted")
func (a *Assertions) IsDecreasingf(object interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return IsDecreasingf(a.t, object, msg, args...)
}
// IsIncreasing asserts that the collection is increasing
//
// a.IsIncreasing([]int{1, 2, 3})
// a.IsIncreasing([]float{1, 2})
// a.IsIncreasing([]string{"a", "b"})
func (a *Assertions) IsIncreasing(object interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return IsIncreasing(a.t, object, msgAndArgs...)
}
// IsIncreasingf asserts that the collection is increasing
//
// a.IsIncreasingf([]int{1, 2, 3}, "error message %s", "formatted")
// a.IsIncreasingf([]float{1, 2}, "error message %s", "formatted")
// a.IsIncreasingf([]string{"a", "b"}, "error message %s", "formatted")
func (a *Assertions) IsIncreasingf(object interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return IsIncreasingf(a.t, object, msg, args...)
}
// IsNonDecreasing asserts that the collection is not decreasing
//
// a.IsNonDecreasing([]int{1, 1, 2})
// a.IsNonDecreasing([]float{1, 2})
// a.IsNonDecreasing([]string{"a", "b"})
func (a *Assertions) IsNonDecreasing(object interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return IsNonDecreasing(a.t, object, msgAndArgs...)
}
// IsNonDecreasingf asserts that the collection is not decreasing
//
// a.IsNonDecreasingf([]int{1, 1, 2}, "error message %s", "formatted")
// a.IsNonDecreasingf([]float{1, 2}, "error message %s", "formatted")
// a.IsNonDecreasingf([]string{"a", "b"}, "error message %s", "formatted")
func (a *Assertions) IsNonDecreasingf(object interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return IsNonDecreasingf(a.t, object, msg, args...)
}
// IsNonIncreasing asserts that the collection is not increasing
//
// a.IsNonIncreasing([]int{2, 1, 1})
// a.IsNonIncreasing([]float{2, 1})
// a.IsNonIncreasing([]string{"b", "a"})
func (a *Assertions) IsNonIncreasing(object interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return IsNonIncreasing(a.t, object, msgAndArgs...)
}
// IsNonIncreasingf asserts that the collection is not increasing
//
// a.IsNonIncreasingf([]int{2, 1, 1}, "error message %s", "formatted")
// a.IsNonIncreasingf([]float{2, 1}, "error message %s", "formatted")
// a.IsNonIncreasingf([]string{"b", "a"}, "error message %s", "formatted")
func (a *Assertions) IsNonIncreasingf(object interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return IsNonIncreasingf(a.t, object, msg, args...)
}
// IsType asserts that the specified objects are of the same type.
func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
@ -739,6 +871,28 @@ func (a *Assertions) Lessf(e1 interface{}, e2 interface{}, msg string, args ...i
return Lessf(a.t, e1, e2, msg, args...)
}
// Negative asserts that the specified element is negative
//
// a.Negative(-1)
// a.Negative(-1.23)
func (a *Assertions) Negative(e interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Negative(a.t, e, msgAndArgs...)
}
// Negativef asserts that the specified element is negative
//
// a.Negativef(-1, "error message %s", "formatted")
// a.Negativef(-1.23, "error message %s", "formatted")
func (a *Assertions) Negativef(e interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Negativef(a.t, e, msg, args...)
}
// Never asserts that the given condition doesn't satisfy in waitFor time,
// periodically checking the target function each tick.
//
@ -941,6 +1095,24 @@ func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg str
return NotEqualf(a.t, expected, actual, msg, args...)
}
// NotErrorIs asserts that at none of the errors in err's chain matches target.
// This is a wrapper for errors.Is.
func (a *Assertions) NotErrorIs(err error, target error, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return NotErrorIs(a.t, err, target, msgAndArgs...)
}
// NotErrorIsf asserts that at none of the errors in err's chain matches target.
// This is a wrapper for errors.Is.
func (a *Assertions) NotErrorIsf(err error, target error, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return NotErrorIsf(a.t, err, target, msg, args...)
}
// NotNil asserts that the specified object is not nil.
//
// a.NotNil(err)
@ -1133,6 +1305,28 @@ func (a *Assertions) Panicsf(f PanicTestFunc, msg string, args ...interface{}) b
return Panicsf(a.t, f, msg, args...)
}
// Positive asserts that the specified element is positive
//
// a.Positive(1)
// a.Positive(1.23)
func (a *Assertions) Positive(e interface{}, msgAndArgs ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Positive(a.t, e, msgAndArgs...)
}
// Positivef asserts that the specified element is positive
//
// a.Positivef(1, "error message %s", "formatted")
// a.Positivef(1.23, "error message %s", "formatted")
func (a *Assertions) Positivef(e interface{}, msg string, args ...interface{}) bool {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
return Positivef(a.t, e, msg, args...)
}
// Regexp asserts that a specified regexp matches a string.
//
// a.Regexp(regexp.MustCompile("start"), "it's starting")

View File

@ -0,0 +1,81 @@
package assert
import (
"fmt"
"reflect"
)
// isOrdered checks that collection contains orderable elements.
func isOrdered(t TestingT, object interface{}, allowedComparesResults []CompareType, failMessage string, msgAndArgs ...interface{}) bool {
objKind := reflect.TypeOf(object).Kind()
if objKind != reflect.Slice && objKind != reflect.Array {
return false
}
objValue := reflect.ValueOf(object)
objLen := objValue.Len()
if objLen <= 1 {
return true
}
value := objValue.Index(0)
valueInterface := value.Interface()
firstValueKind := value.Kind()
for i := 1; i < objLen; i++ {
prevValue := value
prevValueInterface := valueInterface
value = objValue.Index(i)
valueInterface = value.Interface()
compareResult, isComparable := compare(prevValueInterface, valueInterface, firstValueKind)
if !isComparable {
return Fail(t, fmt.Sprintf("Can not compare type \"%s\" and \"%s\"", reflect.TypeOf(value), reflect.TypeOf(prevValue)), msgAndArgs...)
}
if !containsValue(allowedComparesResults, compareResult) {
return Fail(t, fmt.Sprintf(failMessage, prevValue, value), msgAndArgs...)
}
}
return true
}
// IsIncreasing asserts that the collection is increasing
//
// assert.IsIncreasing(t, []int{1, 2, 3})
// assert.IsIncreasing(t, []float{1, 2})
// assert.IsIncreasing(t, []string{"a", "b"})
func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
return isOrdered(t, object, []CompareType{compareLess}, "\"%v\" is not less than \"%v\"", msgAndArgs)
}
// IsNonIncreasing asserts that the collection is not increasing
//
// assert.IsNonIncreasing(t, []int{2, 1, 1})
// assert.IsNonIncreasing(t, []float{2, 1})
// assert.IsNonIncreasing(t, []string{"b", "a"})
func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
return isOrdered(t, object, []CompareType{compareEqual, compareGreater}, "\"%v\" is not greater than or equal to \"%v\"", msgAndArgs)
}
// IsDecreasing asserts that the collection is decreasing
//
// assert.IsDecreasing(t, []int{2, 1, 0})
// assert.IsDecreasing(t, []float{2, 1})
// assert.IsDecreasing(t, []string{"b", "a"})
func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
return isOrdered(t, object, []CompareType{compareGreater}, "\"%v\" is not greater than \"%v\"", msgAndArgs)
}
// IsNonDecreasing asserts that the collection is not decreasing
//
// assert.IsNonDecreasing(t, []int{1, 1, 2})
// assert.IsNonDecreasing(t, []float{1, 2})
// assert.IsNonDecreasing(t, []string{"a", "b"})
func IsNonDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) bool {
return isOrdered(t, object, []CompareType{compareLess, compareEqual}, "\"%v\" is not less than or equal to \"%v\"", msgAndArgs)
}

View File

@ -172,8 +172,8 @@ func isTest(name, prefix string) bool {
if len(name) == len(prefix) { // "Test" is ok
return true
}
rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
return !unicode.IsLower(rune)
r, _ := utf8.DecodeRuneInString(name[len(prefix):])
return !unicode.IsLower(r)
}
func messageFromMsgAndArgs(msgAndArgs ...interface{}) string {
@ -1622,6 +1622,7 @@ var spewConfig = spew.ConfigState{
DisableCapacities: true,
SortKeys: true,
DisableMethods: true,
MaxDepth: 10,
}
type tHelper interface {
@ -1693,3 +1694,81 @@ func Never(t TestingT, condition func() bool, waitFor time.Duration, tick time.D
}
}
}
// ErrorIs asserts that at least one of the errors in err's chain matches target.
// This is a wrapper for errors.Is.
func ErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
if errors.Is(err, target) {
return true
}
var expectedText string
if target != nil {
expectedText = target.Error()
}
chain := buildErrorChainString(err)
return Fail(t, fmt.Sprintf("Target error should be in err chain:\n"+
"expected: %q\n"+
"in chain: %s", expectedText, chain,
), msgAndArgs...)
}
// NotErrorIs asserts that at none of the errors in err's chain matches target.
// This is a wrapper for errors.Is.
func NotErrorIs(t TestingT, err, target error, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
if !errors.Is(err, target) {
return true
}
var expectedText string
if target != nil {
expectedText = target.Error()
}
chain := buildErrorChainString(err)
return Fail(t, fmt.Sprintf("Target error should not be in err chain:\n"+
"found: %q\n"+
"in chain: %s", expectedText, chain,
), msgAndArgs...)
}
// ErrorAs asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value.
// This is a wrapper for errors.As.
func ErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}
if errors.As(err, target) {
return true
}
chain := buildErrorChainString(err)
return Fail(t, fmt.Sprintf("Should be in error chain:\n"+
"expected: %q\n"+
"in chain: %s", target, chain,
), msgAndArgs...)
}
func buildErrorChainString(err error) string {
if err == nil {
return ""
}
e := errors.Unwrap(err)
chain := fmt.Sprintf("%q", err.Error())
for e != nil {
chain += fmt.Sprintf("\n\t%q", e.Error())
e = errors.Unwrap(e)
}
return chain
}

View File

@ -256,6 +256,54 @@ func Error(t TestingT, err error, msgAndArgs ...interface{}) {
t.FailNow()
}
// ErrorAs asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value.
// This is a wrapper for errors.As.
func ErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
}
if assert.ErrorAs(t, err, target, msgAndArgs...) {
return
}
t.FailNow()
}
// ErrorAsf asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value.
// This is a wrapper for errors.As.
func ErrorAsf(t TestingT, err error, target interface{}, msg string, args ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
}
if assert.ErrorAsf(t, err, target, msg, args...) {
return
}
t.FailNow()
}
// ErrorIs asserts that at least one of the errors in err's chain matches target.
// This is a wrapper for errors.Is.
func ErrorIs(t TestingT, err error, target error, msgAndArgs ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
}
if assert.ErrorIs(t, err, target, msgAndArgs...) {
return
}
t.FailNow()
}
// ErrorIsf asserts that at least one of the errors in err's chain matches target.
// This is a wrapper for errors.Is.
func ErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
}
if assert.ErrorIsf(t, err, target, msg, args...) {
return
}
t.FailNow()
}
// Errorf asserts that a function returned an error (i.e. not `nil`).
//
// actualObj, err := SomeFunction()
@ -806,6 +854,126 @@ func InEpsilonf(t TestingT, expected interface{}, actual interface{}, epsilon fl
t.FailNow()
}
// IsDecreasing asserts that the collection is decreasing
//
// assert.IsDecreasing(t, []int{2, 1, 0})
// assert.IsDecreasing(t, []float{2, 1})
// assert.IsDecreasing(t, []string{"b", "a"})
func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
}
if assert.IsDecreasing(t, object, msgAndArgs...) {
return
}
t.FailNow()
}
// IsDecreasingf asserts that the collection is decreasing
//
// assert.IsDecreasingf(t, []int{2, 1, 0}, "error message %s", "formatted")
// assert.IsDecreasingf(t, []float{2, 1}, "error message %s", "formatted")
// assert.IsDecreasingf(t, []string{"b", "a"}, "error message %s", "formatted")
func IsDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
}
if assert.IsDecreasingf(t, object, msg, args...) {
return
}
t.FailNow()
}
// IsIncreasing asserts that the collection is increasing
//
// assert.IsIncreasing(t, []int{1, 2, 3})
// assert.IsIncreasing(t, []float{1, 2})
// assert.IsIncreasing(t, []string{"a", "b"})
func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
}
if assert.IsIncreasing(t, object, msgAndArgs...) {
return
}
t.FailNow()
}
// IsIncreasingf asserts that the collection is increasing
//
// assert.IsIncreasingf(t, []int{1, 2, 3}, "error message %s", "formatted")
// assert.IsIncreasingf(t, []float{1, 2}, "error message %s", "formatted")
// assert.IsIncreasingf(t, []string{"a", "b"}, "error message %s", "formatted")
func IsIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
}
if assert.IsIncreasingf(t, object, msg, args...) {
return
}
t.FailNow()
}
// IsNonDecreasing asserts that the collection is not decreasing
//
// assert.IsNonDecreasing(t, []int{1, 1, 2})
// assert.IsNonDecreasing(t, []float{1, 2})
// assert.IsNonDecreasing(t, []string{"a", "b"})
func IsNonDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
}
if assert.IsNonDecreasing(t, object, msgAndArgs...) {
return
}
t.FailNow()
}
// IsNonDecreasingf asserts that the collection is not decreasing
//
// assert.IsNonDecreasingf(t, []int{1, 1, 2}, "error message %s", "formatted")
// assert.IsNonDecreasingf(t, []float{1, 2}, "error message %s", "formatted")
// assert.IsNonDecreasingf(t, []string{"a", "b"}, "error message %s", "formatted")
func IsNonDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
}
if assert.IsNonDecreasingf(t, object, msg, args...) {
return
}
t.FailNow()
}
// IsNonIncreasing asserts that the collection is not increasing
//
// assert.IsNonIncreasing(t, []int{2, 1, 1})
// assert.IsNonIncreasing(t, []float{2, 1})
// assert.IsNonIncreasing(t, []string{"b", "a"})
func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
}
if assert.IsNonIncreasing(t, object, msgAndArgs...) {
return
}
t.FailNow()
}
// IsNonIncreasingf asserts that the collection is not increasing
//
// assert.IsNonIncreasingf(t, []int{2, 1, 1}, "error message %s", "formatted")
// assert.IsNonIncreasingf(t, []float{2, 1}, "error message %s", "formatted")
// assert.IsNonIncreasingf(t, []string{"b", "a"}, "error message %s", "formatted")
func IsNonIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
}
if assert.IsNonIncreasingf(t, object, msg, args...) {
return
}
t.FailNow()
}
// IsType asserts that the specified objects are of the same type.
func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs ...interface{}) {
if h, ok := t.(tHelper); ok {
@ -944,6 +1112,34 @@ func Lessf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...inter
t.FailNow()
}
// Negative asserts that the specified element is negative
//
// assert.Negative(t, -1)
// assert.Negative(t, -1.23)
func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
}
if assert.Negative(t, e, msgAndArgs...) {
return
}
t.FailNow()
}
// Negativef asserts that the specified element is negative
//
// assert.Negativef(t, -1, "error message %s", "formatted")
// assert.Negativef(t, -1.23, "error message %s", "formatted")
func Negativef(t TestingT, e interface{}, msg string, args ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
}
if assert.Negativef(t, e, msg, args...) {
return
}
t.FailNow()
}
// Never asserts that the given condition doesn't satisfy in waitFor time,
// periodically checking the target function each tick.
//
@ -1200,6 +1396,30 @@ func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string,
t.FailNow()
}
// NotErrorIs asserts that at none of the errors in err's chain matches target.
// This is a wrapper for errors.Is.
func NotErrorIs(t TestingT, err error, target error, msgAndArgs ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
}
if assert.NotErrorIs(t, err, target, msgAndArgs...) {
return
}
t.FailNow()
}
// NotErrorIsf asserts that at none of the errors in err's chain matches target.
// This is a wrapper for errors.Is.
func NotErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
}
if assert.NotErrorIsf(t, err, target, msg, args...) {
return
}
t.FailNow()
}
// NotNil asserts that the specified object is not nil.
//
// assert.NotNil(t, err)
@ -1446,6 +1666,34 @@ func Panicsf(t TestingT, f assert.PanicTestFunc, msg string, args ...interface{}
t.FailNow()
}
// Positive asserts that the specified element is positive
//
// assert.Positive(t, 1)
// assert.Positive(t, 1.23)
func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
}
if assert.Positive(t, e, msgAndArgs...) {
return
}
t.FailNow()
}
// Positivef asserts that the specified element is positive
//
// assert.Positivef(t, 1, "error message %s", "formatted")
// assert.Positivef(t, 1.23, "error message %s", "formatted")
func Positivef(t TestingT, e interface{}, msg string, args ...interface{}) {
if h, ok := t.(tHelper); ok {
h.Helper()
}
if assert.Positivef(t, e, msg, args...) {
return
}
t.FailNow()
}
// Regexp asserts that a specified regexp matches a string.
//
// assert.Regexp(t, regexp.MustCompile("start"), "it's starting")

View File

@ -205,6 +205,42 @@ func (a *Assertions) Error(err error, msgAndArgs ...interface{}) {
Error(a.t, err, msgAndArgs...)
}
// ErrorAs asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value.
// This is a wrapper for errors.As.
func (a *Assertions) ErrorAs(err error, target interface{}, msgAndArgs ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
ErrorAs(a.t, err, target, msgAndArgs...)
}
// ErrorAsf asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value.
// This is a wrapper for errors.As.
func (a *Assertions) ErrorAsf(err error, target interface{}, msg string, args ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
ErrorAsf(a.t, err, target, msg, args...)
}
// ErrorIs asserts that at least one of the errors in err's chain matches target.
// This is a wrapper for errors.Is.
func (a *Assertions) ErrorIs(err error, target error, msgAndArgs ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
ErrorIs(a.t, err, target, msgAndArgs...)
}
// ErrorIsf asserts that at least one of the errors in err's chain matches target.
// This is a wrapper for errors.Is.
func (a *Assertions) ErrorIsf(err error, target error, msg string, args ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
ErrorIsf(a.t, err, target, msg, args...)
}
// Errorf asserts that a function returned an error (i.e. not `nil`).
//
// actualObj, err := SomeFunction()
@ -632,6 +668,102 @@ func (a *Assertions) InEpsilonf(expected interface{}, actual interface{}, epsilo
InEpsilonf(a.t, expected, actual, epsilon, msg, args...)
}
// IsDecreasing asserts that the collection is decreasing
//
// a.IsDecreasing([]int{2, 1, 0})
// a.IsDecreasing([]float{2, 1})
// a.IsDecreasing([]string{"b", "a"})
func (a *Assertions) IsDecreasing(object interface{}, msgAndArgs ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
IsDecreasing(a.t, object, msgAndArgs...)
}
// IsDecreasingf asserts that the collection is decreasing
//
// a.IsDecreasingf([]int{2, 1, 0}, "error message %s", "formatted")
// a.IsDecreasingf([]float{2, 1}, "error message %s", "formatted")
// a.IsDecreasingf([]string{"b", "a"}, "error message %s", "formatted")
func (a *Assertions) IsDecreasingf(object interface{}, msg string, args ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
IsDecreasingf(a.t, object, msg, args...)
}
// IsIncreasing asserts that the collection is increasing
//
// a.IsIncreasing([]int{1, 2, 3})
// a.IsIncreasing([]float{1, 2})
// a.IsIncreasing([]string{"a", "b"})
func (a *Assertions) IsIncreasing(object interface{}, msgAndArgs ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
IsIncreasing(a.t, object, msgAndArgs...)
}
// IsIncreasingf asserts that the collection is increasing
//
// a.IsIncreasingf([]int{1, 2, 3}, "error message %s", "formatted")
// a.IsIncreasingf([]float{1, 2}, "error message %s", "formatted")
// a.IsIncreasingf([]string{"a", "b"}, "error message %s", "formatted")
func (a *Assertions) IsIncreasingf(object interface{}, msg string, args ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
IsIncreasingf(a.t, object, msg, args...)
}
// IsNonDecreasing asserts that the collection is not decreasing
//
// a.IsNonDecreasing([]int{1, 1, 2})
// a.IsNonDecreasing([]float{1, 2})
// a.IsNonDecreasing([]string{"a", "b"})
func (a *Assertions) IsNonDecreasing(object interface{}, msgAndArgs ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
IsNonDecreasing(a.t, object, msgAndArgs...)
}
// IsNonDecreasingf asserts that the collection is not decreasing
//
// a.IsNonDecreasingf([]int{1, 1, 2}, "error message %s", "formatted")
// a.IsNonDecreasingf([]float{1, 2}, "error message %s", "formatted")
// a.IsNonDecreasingf([]string{"a", "b"}, "error message %s", "formatted")
func (a *Assertions) IsNonDecreasingf(object interface{}, msg string, args ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
IsNonDecreasingf(a.t, object, msg, args...)
}
// IsNonIncreasing asserts that the collection is not increasing
//
// a.IsNonIncreasing([]int{2, 1, 1})
// a.IsNonIncreasing([]float{2, 1})
// a.IsNonIncreasing([]string{"b", "a"})
func (a *Assertions) IsNonIncreasing(object interface{}, msgAndArgs ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
IsNonIncreasing(a.t, object, msgAndArgs...)
}
// IsNonIncreasingf asserts that the collection is not increasing
//
// a.IsNonIncreasingf([]int{2, 1, 1}, "error message %s", "formatted")
// a.IsNonIncreasingf([]float{2, 1}, "error message %s", "formatted")
// a.IsNonIncreasingf([]string{"b", "a"}, "error message %s", "formatted")
func (a *Assertions) IsNonIncreasingf(object interface{}, msg string, args ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
IsNonIncreasingf(a.t, object, msg, args...)
}
// IsType asserts that the specified objects are of the same type.
func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) {
if h, ok := a.t.(tHelper); ok {
@ -740,6 +872,28 @@ func (a *Assertions) Lessf(e1 interface{}, e2 interface{}, msg string, args ...i
Lessf(a.t, e1, e2, msg, args...)
}
// Negative asserts that the specified element is negative
//
// a.Negative(-1)
// a.Negative(-1.23)
func (a *Assertions) Negative(e interface{}, msgAndArgs ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
Negative(a.t, e, msgAndArgs...)
}
// Negativef asserts that the specified element is negative
//
// a.Negativef(-1, "error message %s", "formatted")
// a.Negativef(-1.23, "error message %s", "formatted")
func (a *Assertions) Negativef(e interface{}, msg string, args ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
Negativef(a.t, e, msg, args...)
}
// Never asserts that the given condition doesn't satisfy in waitFor time,
// periodically checking the target function each tick.
//
@ -942,6 +1096,24 @@ func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg str
NotEqualf(a.t, expected, actual, msg, args...)
}
// NotErrorIs asserts that at none of the errors in err's chain matches target.
// This is a wrapper for errors.Is.
func (a *Assertions) NotErrorIs(err error, target error, msgAndArgs ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
NotErrorIs(a.t, err, target, msgAndArgs...)
}
// NotErrorIsf asserts that at none of the errors in err's chain matches target.
// This is a wrapper for errors.Is.
func (a *Assertions) NotErrorIsf(err error, target error, msg string, args ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
NotErrorIsf(a.t, err, target, msg, args...)
}
// NotNil asserts that the specified object is not nil.
//
// a.NotNil(err)
@ -1134,6 +1306,28 @@ func (a *Assertions) Panicsf(f assert.PanicTestFunc, msg string, args ...interfa
Panicsf(a.t, f, msg, args...)
}
// Positive asserts that the specified element is positive
//
// a.Positive(1)
// a.Positive(1.23)
func (a *Assertions) Positive(e interface{}, msgAndArgs ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
Positive(a.t, e, msgAndArgs...)
}
// Positivef asserts that the specified element is positive
//
// a.Positivef(1, "error message %s", "formatted")
// a.Positivef(1.23, "error message %s", "formatted")
func (a *Assertions) Positivef(e interface{}, msg string, args ...interface{}) {
if h, ok := a.t.(tHelper); ok {
h.Helper()
}
Positivef(a.t, e, msg, args...)
}
// Regexp asserts that a specified regexp matches a string.
//
// a.Regexp(regexp.MustCompile("start"), "it's starting")

18
vendor/gopkg.in/ini.v1/file.go generated vendored
View File

@ -55,6 +55,9 @@ func newFile(dataSources []dataSource, opts LoadOptions) *File {
if len(opts.KeyValueDelimiterOnWrite) == 0 {
opts.KeyValueDelimiterOnWrite = "="
}
if len(opts.ChildSectionDelimiter) == 0 {
opts.ChildSectionDelimiter = "."
}
return &File{
BlockMode: true,
@ -82,7 +85,7 @@ func (f *File) NewSection(name string) (*Section, error) {
return nil, errors.New("empty section name")
}
if f.options.Insensitive && name != DefaultSection {
if (f.options.Insensitive || f.options.InsensitiveSections) && name != DefaultSection {
name = strings.ToLower(name)
}
@ -144,7 +147,7 @@ func (f *File) SectionsByName(name string) ([]*Section, error) {
if len(name) == 0 {
name = DefaultSection
}
if f.options.Insensitive {
if f.options.Insensitive || f.options.InsensitiveSections {
name = strings.ToLower(name)
}
@ -236,7 +239,7 @@ func (f *File) DeleteSectionWithIndex(name string, index int) error {
if len(name) == 0 {
name = DefaultSection
}
if f.options.Insensitive {
if f.options.Insensitive || f.options.InsensitiveSections {
name = strings.ToLower(name)
}
@ -299,6 +302,9 @@ func (f *File) Reload() (err error) {
}
return err
}
if f.options.ShortCircuit {
return nil
}
}
return nil
}
@ -347,7 +353,7 @@ func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
}
}
if i > 0 || DefaultHeader {
if i > 0 || DefaultHeader || (i == 0 && strings.ToUpper(sec.name) != DefaultSection) {
if _, err := buf.WriteString("[" + sname + "]" + LineBreak); err != nil {
return nil, err
}
@ -451,6 +457,8 @@ func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
val = `"""` + val + `"""`
} else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") {
val = "`" + val + "`"
} else if len(strings.TrimSpace(val)) != len(val) {
val = `"` + val + `"`
}
if _, err := buf.WriteString(equalSign + val + LineBreak); err != nil {
return nil, err
@ -494,7 +502,7 @@ func (f *File) WriteTo(w io.Writer) (int64, error) {
// SaveToIndent writes content to file system with given value indention.
func (f *File) SaveToIndent(filename, indent string) error {
// Note: Because we are truncating with os.Create,
// so it's safer to save to a temporary file location and rename afte done.
// so it's safer to save to a temporary file location and rename after done.
buf, err := f.writeToBuffer(indent)
if err != nil {
return err

10
vendor/gopkg.in/ini.v1/ini.go generated vendored
View File

@ -71,12 +71,18 @@ type LoadOptions struct {
Loose bool
// Insensitive indicates whether the parser forces all section and key names to lowercase.
Insensitive bool
// InsensitiveSections indicates whether the parser forces all section to lowercase.
InsensitiveSections bool
// InsensitiveKeys indicates whether the parser forces all key names to lowercase.
InsensitiveKeys bool
// IgnoreContinuation indicates whether to ignore continuation lines while parsing.
IgnoreContinuation bool
// IgnoreInlineComment indicates whether to ignore comments at the end of value and treat it as part of value.
IgnoreInlineComment bool
// SkipUnrecognizableLines indicates whether to skip unrecognizable lines that do not conform to key/value pairs.
SkipUnrecognizableLines bool
// ShortCircuit indicates whether to ignore other configuration sources after loaded the first available configuration source.
ShortCircuit bool
// AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing.
// This type of keys are mostly used in my.cnf.
AllowBooleanKeys bool
@ -107,8 +113,10 @@ type LoadOptions struct {
UnparseableSections []string
// KeyValueDelimiters is the sequence of delimiters that are used to separate key and value. By default, it is "=:".
KeyValueDelimiters string
// KeyValueDelimiters is the delimiter that are used to separate key and value output. By default, it is "=".
// KeyValueDelimiterOnWrite is the delimiter that are used to separate key and value output. By default, it is "=".
KeyValueDelimiterOnWrite string
// ChildSectionDelimiter is the delimiter that is used to separate child sections. By default, it is ".".
ChildSectionDelimiter string
// PreserveSurroundedQuote indicates whether to preserve surrounded quote (single and double quotes).
PreserveSurroundedQuote bool
// DebugFunc is called to collect debug information (currently only useful to debug parsing Python-style multiline values).

4
vendor/gopkg.in/ini.v1/parser.go generated vendored
View File

@ -377,7 +377,7 @@ func (f *File) parse(reader io.Reader) (err error) {
// Ignore error because default section name is never empty string.
name := DefaultSection
if f.options.Insensitive {
if f.options.Insensitive || f.options.InsensitiveSections {
name = strings.ToLower(DefaultSection)
}
section, _ := f.NewSection(name)
@ -469,7 +469,7 @@ func (f *File) parse(reader io.Reader) (err error) {
inUnparseableSection = false
for i := range f.options.UnparseableSections {
if f.options.UnparseableSections[i] == name ||
(f.options.Insensitive && strings.EqualFold(f.options.UnparseableSections[i], name)) {
((f.options.Insensitive || f.options.InsensitiveSections) && strings.EqualFold(f.options.UnparseableSections[i], name)) {
inUnparseableSection = true
continue
}

10
vendor/gopkg.in/ini.v1/section.go generated vendored
View File

@ -66,7 +66,7 @@ func (s *Section) SetBody(body string) {
func (s *Section) NewKey(name, val string) (*Key, error) {
if len(name) == 0 {
return nil, errors.New("error creating new key: empty key name")
} else if s.f.options.Insensitive {
} else if s.f.options.Insensitive || s.f.options.InsensitiveKeys {
name = strings.ToLower(name)
}
@ -109,7 +109,7 @@ func (s *Section) GetKey(name string) (*Key, error) {
if s.f.BlockMode {
s.f.lock.RLock()
}
if s.f.options.Insensitive {
if s.f.options.Insensitive || s.f.options.InsensitiveKeys {
name = strings.ToLower(name)
}
key := s.keys[name]
@ -121,7 +121,7 @@ func (s *Section) GetKey(name string) (*Key, error) {
// Check if it is a child-section.
sname := s.name
for {
if i := strings.LastIndex(sname, "."); i > -1 {
if i := strings.LastIndex(sname, s.f.options.ChildSectionDelimiter); i > -1 {
sname = sname[:i]
sec, err := s.f.GetSection(sname)
if err != nil {
@ -188,7 +188,7 @@ func (s *Section) ParentKeys() []*Key {
var parentKeys []*Key
sname := s.name
for {
if i := strings.LastIndex(sname, "."); i > -1 {
if i := strings.LastIndex(sname, s.f.options.ChildSectionDelimiter); i > -1 {
sname = sname[:i]
sec, err := s.f.GetSection(sname)
if err != nil {
@ -245,7 +245,7 @@ func (s *Section) DeleteKey(name string) {
// For example, "[parent.child1]" and "[parent.child12]" are child sections
// of section "[parent]".
func (s *Section) ChildSections() []*Section {
prefix := s.name + "."
prefix := s.name + s.f.options.ChildSectionDelimiter
children := make([]*Section, 0, 3)
for _, name := range s.f.sectionList {
if strings.HasPrefix(name, prefix) {

62
vendor/gopkg.in/ini.v1/struct.go generated vendored
View File

@ -263,24 +263,21 @@ func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim stri
return nil
}
func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bool, allowNonUnique bool) {
opts := strings.SplitN(tag, ",", 4)
func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bool, allowNonUnique bool, extends bool) {
opts := strings.SplitN(tag, ",", 5)
rawName = opts[0]
if len(opts) > 1 {
omitEmpty = opts[1] == "omitempty"
for _, opt := range opts[1:] {
omitEmpty = omitEmpty || (opt == "omitempty")
allowShadow = allowShadow || (opt == "allowshadow")
allowNonUnique = allowNonUnique || (opt == "nonunique")
extends = extends || (opt == "extends")
}
if len(opts) > 2 {
allowShadow = opts[2] == "allowshadow"
}
if len(opts) > 3 {
allowNonUnique = opts[3] == "nonunique"
}
return rawName, omitEmpty, allowShadow, allowNonUnique
return rawName, omitEmpty, allowShadow, allowNonUnique, extends
}
// mapToField maps the given value to the matching field of the given section.
// The sectionIndex is the index (if non unique sections are enabled) to which the value should be added.
func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int) error {
func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int, sectionName string) error {
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
@ -295,7 +292,7 @@ func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int)
continue
}
rawName, _, allowShadow, allowNonUnique := parseTagOptions(tag)
rawName, _, allowShadow, allowNonUnique, extends := parseTagOptions(tag)
fieldName := s.parseFieldName(tpField.Name, rawName)
if len(fieldName) == 0 || !field.CanSet() {
continue
@ -303,12 +300,26 @@ func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int)
isStruct := tpField.Type.Kind() == reflect.Struct
isStructPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Type.Elem().Kind() == reflect.Struct
isAnonymous := tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous
if isAnonymous {
isAnonymousPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous
if isAnonymousPtr {
field.Set(reflect.New(tpField.Type.Elem()))
}
if isAnonymous || isStruct || isStructPtr {
if extends && (isAnonymousPtr || (isStruct && tpField.Anonymous)) {
if isStructPtr && field.IsNil() {
field.Set(reflect.New(tpField.Type.Elem()))
}
fieldSection := s
if rawName != "" {
sectionName = s.name + s.f.options.ChildSectionDelimiter + rawName
if secs, err := s.f.SectionsByName(sectionName); err == nil && sectionIndex < len(secs) {
fieldSection = secs[sectionIndex]
}
}
if err := fieldSection.mapToField(field, isStrict, sectionIndex, sectionName); err != nil {
return fmt.Errorf("map to field %q: %v", fieldName, err)
}
} else if isAnonymousPtr || isStruct || isStructPtr {
if secs, err := s.f.SectionsByName(fieldName); err == nil {
if len(secs) <= sectionIndex {
return fmt.Errorf("there are not enough sections (%d <= %d) for the field %q", len(secs), sectionIndex, fieldName)
@ -318,7 +329,7 @@ func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int)
if isStructPtr && field.IsNil() {
field.Set(reflect.New(tpField.Type.Elem()))
}
if err = secs[sectionIndex].mapToField(field, isStrict, sectionIndex); err != nil {
if err = secs[sectionIndex].mapToField(field, isStrict, sectionIndex, fieldName); err != nil {
return fmt.Errorf("map to field %q: %v", fieldName, err)
}
continue
@ -357,7 +368,7 @@ func (s *Section) mapToSlice(secName string, val reflect.Value, isStrict bool) (
typ := val.Type().Elem()
for i, sec := range secs {
elem := reflect.New(typ)
if err = sec.mapToField(elem, isStrict, i); err != nil {
if err = sec.mapToField(elem, isStrict, i, sec.name); err != nil {
return reflect.Value{}, fmt.Errorf("map to field from section %q: %v", secName, err)
}
@ -387,7 +398,7 @@ func (s *Section) mapTo(v interface{}, isStrict bool) error {
return nil
}
return s.mapToField(val, isStrict, 0)
return s.mapToField(val, isStrict, 0, s.name)
}
// MapTo maps section to given struct.
@ -479,7 +490,7 @@ func reflectSliceWithProperType(key *Key, field reflect.Value, delim string, all
_ = keyWithShadows.AddShadow(val)
}
}
key = keyWithShadows
*key = *keyWithShadows
return nil
}
@ -581,7 +592,7 @@ func (s *Section) reflectFrom(val reflect.Value) error {
continue
}
rawName, omitEmpty, allowShadow, allowNonUnique := parseTagOptions(tag)
rawName, omitEmpty, allowShadow, allowNonUnique, extends := parseTagOptions(tag)
if omitEmpty && isEmptyValue(field) {
continue
}
@ -595,7 +606,14 @@ func (s *Section) reflectFrom(val reflect.Value) error {
continue
}
if (tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous) ||
if extends && tpField.Anonymous && (tpField.Type.Kind() == reflect.Ptr || tpField.Type.Kind() == reflect.Struct) {
if err := s.reflectFrom(field); err != nil {
return fmt.Errorf("reflect from field %q: %v", fieldName, err)
}
continue
}
if (tpField.Type.Kind() == reflect.Ptr && tpField.Type.Elem().Kind() == reflect.Struct) ||
(tpField.Type.Kind() == reflect.Struct && tpField.Type.Name() != "Time") {
// Note: The only error here is section doesn't exist.
sec, err := s.f.GetSection(fieldName)

7
vendor/modules.txt generated vendored
View File

@ -551,11 +551,12 @@ github.com/spf13/pflag
# github.com/spf13/viper v1.7.0
## explicit
github.com/spf13/viper
# github.com/spotinst/spotinst-sdk-go v1.76.0
# github.com/spotinst/spotinst-sdk-go v1.80.0
## explicit
github.com/spotinst/spotinst-sdk-go/service/elastigroup
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/azure/v3
github.com/spotinst/spotinst-sdk-go/service/elastigroup/providers/gcp
github.com/spotinst/spotinst-sdk-go/service/ocean
github.com/spotinst/spotinst-sdk-go/service/ocean/providers/aws
@ -570,7 +571,7 @@ github.com/spotinst/spotinst-sdk-go/spotinst/util/jsonutil
github.com/spotinst/spotinst-sdk-go/spotinst/util/stringutil
github.com/spotinst/spotinst-sdk-go/spotinst/util/uritemplates
github.com/spotinst/spotinst-sdk-go/spotinst/util/useragent
# github.com/stretchr/testify v1.6.1
# github.com/stretchr/testify v1.7.0
## explicit
github.com/stretchr/testify/assert
github.com/stretchr/testify/require
@ -827,7 +828,7 @@ gopkg.in/gcfg.v1/types
# gopkg.in/inf.v0 v0.9.1
## explicit
gopkg.in/inf.v0
# gopkg.in/ini.v1 v1.57.0
# gopkg.in/ini.v1 v1.62.0
gopkg.in/ini.v1
# gopkg.in/square/go-jose.v2 v2.3.1
gopkg.in/square/go-jose.v2