diff --git a/.github/workflows/golangci-lint.yaml b/.github/workflows/golangci-lint.yaml new file mode 100644 index 0000000..5324d6f --- /dev/null +++ b/.github/workflows/golangci-lint.yaml @@ -0,0 +1,24 @@ +name: golangci-lint +on: + push: + branches: + - main + pull_request: + +permissions: + contents: read + pull-requests: read + +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: stable + - name: golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: v1.60 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 460fade..30e3c14 100644 --- a/.gitignore +++ b/.gitignore @@ -20,3 +20,4 @@ /Godeps/ /build/ /.terraform/ +.vscode/ diff --git a/cmd/main.go b/cmd/main.go index a7cacce..02fa00f 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,15 +1,14 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package main @@ -24,18 +23,19 @@ import ( "syscall" "time" - commandline "github.com/aws/amazon-ec2-instance-selector/v3/pkg/cli" - "github.com/aws/amazon-ec2-instance-selector/v3/pkg/env" - "github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes" - "github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector" - "github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector/outputs" - "github.com/aws/amazon-ec2-instance-selector/v3/pkg/sorter" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" tea "github.com/charmbracelet/bubbletea" "github.com/spf13/cobra" "go.uber.org/multierr" + + commandline "github.com/aws/amazon-ec2-instance-selector/v3/pkg/cli" + "github.com/aws/amazon-ec2-instance-selector/v3/pkg/env" + "github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes" + "github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector" + "github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector/outputs" + "github.com/aws/amazon-ec2-instance-selector/v3/pkg/sorter" ) const ( @@ -45,7 +45,7 @@ const ( defaultProfile = "default" awsConfigFile = "~/.aws/config" // 0 means the last price - // increasing this results in a lot more API calls to EC2 which can slow things down + // increasing this results in a lot more API calls to EC2 which can slow things down. spotPricingDaysBack = 0 tableOutput = "table" @@ -53,11 +53,11 @@ const ( oneLine = "one-line" bubbleTeaOutput = "interactive" - // Sort filter default + // Sort filter default. instanceNamePath = ".InstanceType" ) -// Filter Flag Constants +// Filter Flag Constants. const ( vcpus = "vcpus" memory = "memory" @@ -106,14 +106,14 @@ const ( generation = "generation" ) -// Aggregate Filter Flags +// Aggregate Filter Flags. const ( instanceTypeBase = "base-instance-type" flexible = "flexible" service = "service" ) -// Configuration Flag Constants +// Configuration Flag Constants. const ( maxResults = "max-results" profile = "profile" @@ -128,13 +128,10 @@ const ( sortBy = "sort-by" ) -var ( - // versionID is overridden at compilation with the version based on the git tag - versionID = "dev" -) +// versionID is overridden at compilation with the version based on the git tag +var versionID = "dev" func main() { - log.SetOutput(os.Stderr) log.SetPrefix("NOTE: ") log.SetFlags(log.Flags() &^ (log.Ldate | log.Ltime)) @@ -276,7 +273,7 @@ Full docs can be found at github.com/aws/amazon-` + binName cacheTTLDuration := time.Hour * time.Duration(*cli.IntMe(flags[cacheTTL])) instanceSelector, err := selector.NewWithCache(ctx, cfg, cacheTTLDuration, *cli.StringMe(flags[cacheDir])) if err != nil { - fmt.Printf("An error occurred when initialising the ec2 selector: %v", err) + fmt.Printf("An error occurred when initializing the ec2 selector: %v", err) os.Exit(1) } if flags[debug] != nil { @@ -477,7 +474,7 @@ Full docs can be found at github.com/aws/amazon-` + binName var instanceTypes []string if outputFlag != nil && *outputFlag == bubbleTeaOutput { p := tea.NewProgram(outputs.NewBubbleTeaModel(instanceTypesDetails), tea.WithMouseCellMotion()) - if err := p.Start(); err != nil { + if _, err := p.Run(); err != nil { fmt.Printf("An error occurred when starting bubble tea: %v", err) os.Exit(1) } @@ -516,7 +513,7 @@ func hydrateCaches(ctx context.Context, instanceSelector selector.Selector) (err defer waitGroup.Done() if instanceSelector.EC2Pricing.OnDemandCacheCount() == 0 { if err := instanceSelector.EC2Pricing.RefreshOnDemandCache(ctx); err != nil { - return multierr.Append(errs, fmt.Errorf("There was a problem refreshing the on-demand pricing cache: %w", err)) + return multierr.Append(errs, fmt.Errorf("there was a problem refreshing the on-demand pricing cache: %w", err)) } } return nil @@ -525,7 +522,7 @@ func hydrateCaches(ctx context.Context, instanceSelector selector.Selector) (err defer waitGroup.Done() if instanceSelector.EC2Pricing.SpotCacheCount() == 0 { if err := instanceSelector.EC2Pricing.RefreshSpotCache(ctx, spotPricingDaysBack); err != nil { - return multierr.Append(errs, fmt.Errorf("There was a problem refreshing the spot pricing cache: %w", err)) + return multierr.Append(errs, fmt.Errorf("there was a problem refreshing the spot pricing cache: %w", err)) } } return nil @@ -534,7 +531,7 @@ func hydrateCaches(ctx context.Context, instanceSelector selector.Selector) (err defer waitGroup.Done() if instanceSelector.InstanceTypesProvider.CacheCount() == 0 { if _, err := instanceSelector.InstanceTypesProvider.Get(ctx, nil); err != nil { - return multierr.Append(errs, fmt.Errorf("There was a problem refreshing the instance types cache: %w", err)) + return multierr.Append(errs, fmt.Errorf("there was a problem refreshing the instance types cache: %w", err)) } } return nil @@ -542,7 +539,11 @@ func hydrateCaches(ctx context.Context, instanceSelector selector.Selector) (err } wg.Add(len(hydrateTasks)) for _, task := range hydrateTasks { - go task(wg) + go func() { + if err := task(wg); err != nil { + log.Printf("Hydrate task error: %v", err) + } + }() } wg.Wait() return errs diff --git a/go.mod b/go.mod index e3163bb..eef1002 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,7 @@ require ( github.com/muesli/termenv v0.15.2 github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852 github.com/patrickmn/go-cache v2.1.0+incompatible + github.com/samber/lo v1.47.0 github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 go.uber.org/multierr v1.11.0 diff --git a/go.sum b/go.sum index debeb04..835b569 100644 --- a/go.sum +++ b/go.sum @@ -89,6 +89,8 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sahilm/fuzzy v0.1.1 h1:ceu5RHF8DGgoi+/dR5PsECjCDH1BE3Fnmpo7aVXOdRA= github.com/sahilm/fuzzy v0.1.1/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= +github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= +github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= diff --git a/pkg/awsapi/selectorec2.go b/pkg/awsapi/selectorec2.go index 8523d29..ea8d681 100644 --- a/pkg/awsapi/selectorec2.go +++ b/pkg/awsapi/selectorec2.go @@ -1,7 +1,21 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package awsapi import ( "context" + "github.com/aws/aws-sdk-go-v2/service/ec2" ) diff --git a/pkg/bytequantity/bytequantity.go b/pkg/bytequantity/bytequantity.go index b442a2c..b907ae1 100644 --- a/pkg/bytequantity/bytequantity.go +++ b/pkg/bytequantity/bytequantity.go @@ -1,15 +1,14 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package bytequantity @@ -22,7 +21,7 @@ import ( ) const ( - /// Examples: 1mb, 1 gb, 1.0tb, 1mib, 2g, 2.001 t + /// Examples: 1mb, 1 gb, 1.0tb, 1mib, 2g, 2.001 t. byteQuantityRegex = `^([0-9]+\.?[0-9]{0,3})[ ]?(mi?b?|gi?b?|ti?b?)?$` mib = "MiB" gib = "GiB" @@ -33,7 +32,7 @@ const ( maxTiB = math.MaxUint64 / tbConvert ) -// ByteQuantity is a data type representing a byte quantity +// ByteQuantity is a data type representing a byte quantity. type ByteQuantity struct { Quantity uint64 } @@ -54,7 +53,7 @@ func ParseToByteQuantity(byteQuantityStr string) (ByteQuantity, error) { } quantity := uint64(0) switch strings.ToLower(string(unit[0])) { - //mib + // mib case "m": inputDecSplit := strings.Split(quantityStr, ".") if len(inputDecSplit) == 2 { @@ -72,9 +71,9 @@ func ParseToByteQuantity(byteQuantityStr string) (ByteQuantity, error) { if err != nil { return ByteQuantity{}, err } - //gib + // gib case "g": - quantityDec, err := strconv.ParseFloat(quantityStr, 10) + quantityDec, err := strconv.ParseFloat(quantityStr, 64) if err != nil { return ByteQuantity{}, err } @@ -82,9 +81,9 @@ func ParseToByteQuantity(byteQuantityStr string) (ByteQuantity, error) { return ByteQuantity{}, fmt.Errorf("error GiB value is too large") } quantity = uint64(quantityDec * gbConvert) - //tib + // tib case "t": - quantityDec, err := strconv.ParseFloat(quantityStr, 10) + quantityDec, err := strconv.ParseFloat(quantityStr, 64) if err != nil { return ByteQuantity{}, err } @@ -101,53 +100,53 @@ func ParseToByteQuantity(byteQuantityStr string) (ByteQuantity, error) { }, nil } -// FromTiB returns a byte quantity of the passed in tebibytes quantity +// FromTiB returns a byte quantity of the passed in tebibytes quantity. func FromTiB(tib uint64) ByteQuantity { return ByteQuantity{ Quantity: tib * tbConvert, } } -// FromGiB returns a byte quantity of the passed in gibibytes quantity +// FromGiB returns a byte quantity of the passed in gibibytes quantity. func FromGiB(gib uint64) ByteQuantity { return ByteQuantity{ Quantity: gib * gbConvert, } } -// FromMiB returns a byte quantity of the passed in mebibytes quantity +// FromMiB returns a byte quantity of the passed in mebibytes quantity. func FromMiB(mib uint64) ByteQuantity { return ByteQuantity{ Quantity: mib, } } -// StringMiB returns a byte quantity in a mebibytes string representation +// StringMiB returns a byte quantity in a mebibytes string representation. func (bq ByteQuantity) StringMiB() string { return fmt.Sprintf("%.0f %s", bq.MiB(), mib) } -// StringGiB returns a byte quantity in a gibibytes string representation +// StringGiB returns a byte quantity in a gibibytes string representation. func (bq ByteQuantity) StringGiB() string { return fmt.Sprintf("%.3f %s", bq.GiB(), gib) } -// StringTiB returns a byte quantity in a tebibytes string representation +// StringTiB returns a byte quantity in a tebibytes string representation. func (bq ByteQuantity) StringTiB() string { return fmt.Sprintf("%.3f %s", bq.TiB(), tib) } -// MiB returns a byte quantity in mebibytes +// MiB returns a byte quantity in mebibytes. func (bq ByteQuantity) MiB() float64 { return float64(bq.Quantity) } -// GiB returns a byte quantity in gibibytes +// GiB returns a byte quantity in gibibytes. func (bq ByteQuantity) GiB() float64 { return float64(bq.Quantity) * 1 / gbConvert } -// TiB returns a byte quantity in tebibytes +// TiB returns a byte quantity in tebibytes. func (bq ByteQuantity) TiB() float64 { return float64(bq.Quantity) * 1 / tbConvert } diff --git a/pkg/bytequantity/bytequantity_test.go b/pkg/bytequantity/bytequantity_test.go index 2cae9b7..ce5230a 100644 --- a/pkg/bytequantity/bytequantity_test.go +++ b/pkg/bytequantity/bytequantity_test.go @@ -1,3 +1,16 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package bytequantity_test import ( @@ -9,7 +22,6 @@ import ( ) func TestParseToByteQuantity(t *testing.T) { - for _, testQuantity := range []string{"10mb", "10 mb", "10.0 mb", "10.0mb", "10m", "10mib", "10 M", "10.000 MiB"} { expectationVal := uint64(10) bq, err := bytequantity.ParseToByteQuantity(testQuantity) diff --git a/pkg/cli/cli.go b/pkg/cli/cli.go index 5b244da..b280339 100644 --- a/pkg/cli/cli.go +++ b/pkg/cli/cli.go @@ -1,15 +1,14 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. // Package cli provides functions to build the selector command line interface package cli @@ -21,15 +20,16 @@ import ( "reflect" "strings" - "github.com/aws/amazon-ec2-instance-selector/v3/pkg/bytequantity" - "github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector" "github.com/spf13/cobra" "github.com/spf13/pflag" + + "github.com/aws/amazon-ec2-instance-selector/v3/pkg/bytequantity" + "github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector" ) type runFunc = func(cmd *cobra.Command, args []string) -// New creates an instance of CommandLineInterface +// New creates an instance of CommandLineInterface. func New(binaryName string, shortUsage string, longUsage, examples string, run runFunc) CommandLineInterface { cmd := &cobra.Command{ Use: binaryName, @@ -49,7 +49,7 @@ func New(binaryName string, shortUsage string, longUsage, examples string, run r } } -// ParseFlags will parse flags registered in this instance of CLI from os.Args +// ParseFlags will parse flags registered in this instance of CLI from os.Args. func (cl *CommandLineInterface) ParseFlags() (map[string]interface{}, error) { cl.setUsageTemplate() // Remove Suite Flags so that args only include Config and Filter Flags @@ -78,7 +78,7 @@ func (cl *CommandLineInterface) ParseFlags() (map[string]interface{}, error) { } // ParseAndValidateFlags will parse flags registered in this instance of CLI from os.Args -// and then perform validation +// and then perform validation. func (cl *CommandLineInterface) ParseAndValidateFlags() (map[string]interface{}, error) { flags, err := cl.ParseFlags() if err != nil { @@ -91,7 +91,7 @@ func (cl *CommandLineInterface) ParseAndValidateFlags() (map[string]interface{}, } // ProcessFlags iterates through any registered processors and executes them -// Processors are executed before validators +// Processors are executed before validators. func (cl *CommandLineInterface) ProcessFlags() error { for flagName, processorFn := range cl.processors { if processorFn == nil { @@ -107,7 +107,7 @@ func (cl *CommandLineInterface) ProcessFlags() error { return nil } -// ValidateFlags iterates through any registered validators and executes them +// ValidateFlags iterates through any registered validators and executes them. func (cl *CommandLineInterface) ValidateFlags() error { for flagName, validationFn := range cl.validators { if validationFn == nil { @@ -174,7 +174,7 @@ func (cl *CommandLineInterface) SetUntouchedFlagValuesToNil() error { cl.Command.Flags().VisitAll(func(f *pflag.Flag) { if !f.Changed { // If nilDefaults entry for flag is set to false, do not change default - if val, _ := cl.nilDefaults[f.Name]; !val { + if val := cl.nilDefaults[f.Name]; !val { return } switch v := cl.Flags[f.Name].(type) { @@ -218,7 +218,7 @@ func (cl *CommandLineInterface) SetUntouchedFlagValuesToNil() error { return nil } -// ProcessRangeFilterFlags sets min and max to the appropriate 0 or max bounds based on the 3-tuple that a user specifies for base flag, min, and/or max +// ProcessRangeFilterFlags sets min and max to the appropriate 0 or max bounds based on the 3-tuple that a user specifies for base flag, min, and/or max. func (cl *CommandLineInterface) ProcessRangeFilterFlags() error { for flagName := range cl.rangeFlags { rangeHelperMin := fmt.Sprintf("%s-%s", flagName, "min") @@ -245,7 +245,7 @@ func (cl *CommandLineInterface) ProcessRangeFilterFlags() error { case *float64: cl.Flags[rangeHelperMin] = cl.Float64Me(0.0) default: - return fmt.Errorf("Unable to set %s", rangeHelperMax) + return fmt.Errorf("unable to set %s", rangeHelperMax) } } else if cl.Flags[rangeHelperMax] == nil { switch cl.Flags[rangeHelperMin].(type) { @@ -258,7 +258,7 @@ func (cl *CommandLineInterface) ProcessRangeFilterFlags() error { case *float64: cl.Flags[rangeHelperMax] = cl.Float64Me(math.MaxFloat64) default: - return fmt.Errorf("Unable to set %s", rangeHelperMin) + return fmt.Errorf("unable to set %s", rangeHelperMin) } } diff --git a/pkg/cli/cli_internal_test.go b/pkg/cli/cli_internal_test.go index 34ab191..24c66cf 100644 --- a/pkg/cli/cli_internal_test.go +++ b/pkg/cli/cli_internal_test.go @@ -1,15 +1,14 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package cli @@ -17,8 +16,9 @@ import ( "os" "testing" - h "github.com/aws/amazon-ec2-instance-selector/v3/pkg/test" "github.com/spf13/pflag" + + h "github.com/aws/amazon-ec2-instance-selector/v3/pkg/test" ) // Tests diff --git a/pkg/cli/cli_test.go b/pkg/cli/cli_test.go index 8e7155c..e62d265 100644 --- a/pkg/cli/cli_test.go +++ b/pkg/cli/cli_test.go @@ -1,15 +1,14 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package cli_test @@ -20,11 +19,12 @@ import ( "reflect" "testing" + "github.com/spf13/cobra" + "github.com/aws/amazon-ec2-instance-selector/v3/pkg/bytequantity" "github.com/aws/amazon-ec2-instance-selector/v3/pkg/cli" "github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector" h "github.com/aws/amazon-ec2-instance-selector/v3/pkg/test" - "github.com/spf13/cobra" ) const ( diff --git a/pkg/cli/flags.go b/pkg/cli/flags.go index 1e235a1..8aea97f 100644 --- a/pkg/cli/flags.go +++ b/pkg/cli/flags.go @@ -1,3 +1,16 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package cli import ( @@ -7,9 +20,10 @@ import ( "strconv" "strings" - "github.com/aws/amazon-ec2-instance-selector/v3/pkg/bytequantity" "github.com/mitchellh/go-homedir" "github.com/spf13/pflag" + + "github.com/aws/amazon-ec2-instance-selector/v3/pkg/bytequantity" ) const ( @@ -18,8 +32,8 @@ const ( maxUint64 = math.MaxUint64 ) -// RatioFlag creates and registers a flag accepting a ratio -func (cl *CommandLineInterface) RatioFlag(name string, shorthand *string, defaultValue *string, description string) error { +// RatioFlag creates and registers a flag accepting a ratio. +func (cl *CommandLineInterface) RatioFlag(name string, shorthand *string, defaultValue *string, description string) { if defaultValue == nil { cl.nilDefaults[name] = true defaultValue = cl.StringMe("") @@ -35,48 +49,47 @@ func (cl *CommandLineInterface) RatioFlag(name string, shorthand *string, defaul return nil } vcpuToMemRatioVal := *val.(*string) - valid, err := regexp.Match(`^[0-9]+:[0-9]+$`, []byte(vcpuToMemRatioVal)) + valid, err := regexp.MatchString(`^[0-9]+:[0-9]+$`, vcpuToMemRatioVal) if err != nil || !valid { - return fmt.Errorf("Invalid input for --%s. A valid example is 1:2", name) + return fmt.Errorf("invalid input for --%s. A valid example is 1:2", name) } vals := strings.Split(vcpuToMemRatioVal, ":") vcpusRatioVal, err1 := strconv.Atoi(vals[0]) memRatioVal, err2 := strconv.Atoi(vals[1]) if err1 != nil || err2 != nil { - return fmt.Errorf("Invalid input for --%s. Ratio values must be integers. A valid example is 1:2", name) + return fmt.Errorf("invalid input for --%s. Ratio values must be integers. A valid example is 1:2", name) } cl.Flags[name] = cl.Float64Me(float64(memRatioVal) / float64(vcpusRatioVal)) return nil } - return nil } -// IntMinMaxRangeFlags creates and registers a min, max, and helper flag each accepting an int +// IntMinMaxRangeFlags creates and registers a min, max, and helper flag each accepting an int. func (cl *CommandLineInterface) IntMinMaxRangeFlags(name string, shorthand *string, defaultValue *int, description string) { cl.IntMinMaxRangeFlagOnFlagSet(cl.Command.Flags(), name, shorthand, defaultValue, description) } -// Int32MinMaxRangeFlags creates and registers a min, max, and helper flag each accepting an int +// Int32MinMaxRangeFlags creates and registers a min, max, and helper flag each accepting an int. func (cl *CommandLineInterface) Int32MinMaxRangeFlags(name string, shorthand *string, defaultValue *int32, description string) { cl.Int32MinMaxRangeFlagOnFlagSet(cl.Command.Flags(), name, shorthand, defaultValue, description) } -// ByteQuantityMinMaxRangeFlags creates and registers a min, max, and helper flag each accepting a byte quantity like 512mb +// ByteQuantityMinMaxRangeFlags creates and registers a min, max, and helper flag each accepting a byte quantity like 512mb. func (cl *CommandLineInterface) ByteQuantityMinMaxRangeFlags(name string, shorthand *string, defaultValue *bytequantity.ByteQuantity, description string) { cl.ByteQuantityMinMaxRangeFlagOnFlagSet(cl.Command.Flags(), name, shorthand, defaultValue, description) } -// Float64MinMaxRangeFlags creates and registers a min, max, and helper flag each accepting a float64 +// Float64MinMaxRangeFlags creates and registers a min, max, and helper flag each accepting a float64. func (cl *CommandLineInterface) Float64MinMaxRangeFlags(name string, shorthand *string, defaultValue *float64, description string) { cl.Float64MinMaxRangeFlagOnFlagSet(cl.Command.Flags(), name, shorthand, defaultValue, description) } -// ByteQuantityFlag creates and registers a flag accepting a byte quantity like 512mb +// ByteQuantityFlag creates and registers a flag accepting a byte quantity like 512mb. func (cl *CommandLineInterface) ByteQuantityFlag(name string, shorthand *string, defaultValue *bytequantity.ByteQuantity, description string) { cl.ByteQuantityFlagOnFlagSet(cl.Command.Flags(), name, shorthand, defaultValue, description) } -// IntFlag creates and registers a flag accepting an Integer +// IntFlag creates and registers a flag accepting an Integer. func (cl *CommandLineInterface) IntFlag(name string, shorthand *string, defaultValue *int, description string) { cl.IntFlagOnFlagSet(cl.Command.Flags(), name, shorthand, defaultValue, description) } @@ -107,67 +120,67 @@ func (cl *CommandLineInterface) StringOptionsFlag(name string, shorthand *string cl.StringOptionsFlagOnFlagSet(cl.Command.Flags(), name, shorthand, defaultValue, description, validOpts) } -// BoolFlag creates and registers a flag accepting a boolean +// BoolFlag creates and registers a flag accepting a boolean. func (cl *CommandLineInterface) BoolFlag(name string, shorthand *string, defaultValue *bool, description string) { cl.BoolFlagOnFlagSet(cl.Command.Flags(), name, shorthand, defaultValue, description) } // ConfigStringFlag creates and registers a flag accepting a String for configuration purposes. -// Config flags will be grouped at the bottom in the output of --help +// Config flags will be grouped at the bottom in the output of --help. func (cl *CommandLineInterface) ConfigStringFlag(name string, shorthand *string, defaultValue *string, description string, validationFn validator) { cl.StringFlagOnFlagSet(cl.Command.PersistentFlags(), name, shorthand, defaultValue, description, nil, validationFn) } // ConfigStringSliceFlag creates and registers a flag accepting a list of strings. -// Config flags will be grouped at the bottom in the output of --help +// Config flags will be grouped at the bottom in the output of --help. func (cl *CommandLineInterface) ConfigStringSliceFlag(name string, shorthand *string, defaultValue []string, description string) { cl.StringSliceFlagOnFlagSet(cl.Command.PersistentFlags(), name, shorthand, defaultValue, description) } // ConfigPathFlag creates and registers a flag accepting a string representing a path and validates that it is a valid path. -// Config flags will be grouped at the bottom in the output of --help +// Config flags will be grouped at the bottom in the output of --help. func (cl *CommandLineInterface) ConfigPathFlag(name string, shorthand *string, defaultValue *string, description string) { cl.PathFlagOnFlagSet(cl.Command.PersistentFlags(), name, shorthand, defaultValue, description) } // ConfigIntFlag creates and registers a flag accepting an Integer for configuration purposes. -// Config flags will be grouped at the bottom in the output of --help +// Config flags will be grouped at the bottom in the output of --help. func (cl *CommandLineInterface) ConfigIntFlag(name string, shorthand *string, defaultValue *int, description string) { cl.IntFlagOnFlagSet(cl.Command.PersistentFlags(), name, shorthand, defaultValue, description) } // ConfigBoolFlag creates and registers a flag accepting a boolean for configuration purposes. -// Config flags will be grouped at the bottom in the output of --help +// Config flags will be grouped at the bottom in the output of --help. func (cl *CommandLineInterface) ConfigBoolFlag(name string, shorthand *string, defaultValue *bool, description string) { cl.BoolFlagOnFlagSet(cl.Command.PersistentFlags(), name, shorthand, defaultValue, description) } // ConfigStringOptionsFlag creates and registers a flag accepting a string and valid options for use in validation. -// Config flags will be grouped at the bottom in the output of --help +// Config flags will be grouped at the bottom in the output of --help. func (cl *CommandLineInterface) ConfigStringOptionsFlag(name string, shorthand *string, defaultValue *string, description string, validOpts []string) { cl.StringOptionsFlagOnFlagSet(cl.Command.PersistentFlags(), name, shorthand, defaultValue, description, validOpts) } // SuiteBoolFlag creates and registers a flag accepting a boolean for aggregate filters. -// Suite flags will be grouped in the middle of the output --help +// Suite flags will be grouped in the middle of the output --help. func (cl *CommandLineInterface) SuiteBoolFlag(name string, shorthand *string, defaultValue *bool, description string) { cl.BoolFlagOnFlagSet(cl.suiteFlags, name, shorthand, defaultValue, description) } // SuiteStringFlag creates and registers a flag accepting a string for aggreagate filters. -// Suite flags will be grouped in the middle of the output --help +// Suite flags will be grouped in the middle of the output --help. func (cl *CommandLineInterface) SuiteStringFlag(name string, shorthand *string, defaultValue *string, description string, validationFn validator) { cl.StringFlagOnFlagSet(cl.suiteFlags, name, shorthand, defaultValue, description, nil, validationFn) } // SuiteStringOptionsFlag creates and registers a flag accepting a string and valid options for use in validation. -// Suite flags will be grouped in the middle of the output --help +// Suite flags will be grouped in the middle of the output --help. func (cl *CommandLineInterface) SuiteStringOptionsFlag(name string, shorthand *string, defaultValue *string, description string, validOpts []string) { cl.StringOptionsFlagOnFlagSet(cl.suiteFlags, name, shorthand, defaultValue, description, validOpts) } // SuiteStringSliceFlag creates and registers a flag accepting a list of strings. -// Suite flags will be grouped in the middle of the output --help +// Suite flags will be grouped in the middle of the output --help. func (cl *CommandLineInterface) SuiteStringSliceFlag(name string, shorthand *string, defaultValue []string, description string) { cl.StringSliceFlagOnFlagSet(cl.suiteFlags, name, shorthand, defaultValue, description) } @@ -185,7 +198,7 @@ func (cl *CommandLineInterface) BoolFlagOnFlagSet(flagSet *pflag.FlagSet, name s cl.Flags[name] = flagSet.Bool(name, *defaultValue, description) } -// IntMinMaxRangeFlagOnFlagSet creates and registers a min, max, and helper flag each accepting an int +// IntMinMaxRangeFlagOnFlagSet creates and registers a min, max, and helper flag each accepting an int. func (cl *CommandLineInterface) IntMinMaxRangeFlagOnFlagSet(flagSet *pflag.FlagSet, name string, shorthand *string, defaultValue *int, description string) { cl.IntFlagOnFlagSet(flagSet, name, shorthand, defaultValue, fmt.Sprintf("%s (sets --%s-min and -max to the same value)", description, name)) cl.IntFlagOnFlagSet(flagSet, name+"-min", nil, nil, fmt.Sprintf("Minimum %s If --%s-max is not specified, the upper bound will be infinity", description, name)) @@ -206,7 +219,7 @@ func (cl *CommandLineInterface) IntMinMaxRangeFlagOnFlagSet(flagSet *pflag.FlagS cl.rangeFlags[name] = true } -// Int32MinMaxRangeFlagOnFlagSet creates and registers a min, max, and helper flag each accepting an int +// Int32MinMaxRangeFlagOnFlagSet creates and registers a min, max, and helper flag each accepting an int. func (cl *CommandLineInterface) Int32MinMaxRangeFlagOnFlagSet(flagSet *pflag.FlagSet, name string, shorthand *string, defaultValue *int32, description string) { cl.Int32FlagOnFlagSet(flagSet, name, shorthand, defaultValue, fmt.Sprintf("%s (sets --%s-min and -max to the same value)", description, name)) cl.Int32FlagOnFlagSet(flagSet, name+"-min", nil, nil, fmt.Sprintf("Minimum %s If --%s-max is not specified, the upper bound will be infinity", description, name)) @@ -227,7 +240,7 @@ func (cl *CommandLineInterface) Int32MinMaxRangeFlagOnFlagSet(flagSet *pflag.Fla cl.rangeFlags[name] = true } -// Float64MinMaxRangeFlagOnFlagSet creates and registers a min, max, and helper flag each accepting a float64 +// Float64MinMaxRangeFlagOnFlagSet creates and registers a min, max, and helper flag each accepting a float64. func (cl *CommandLineInterface) Float64MinMaxRangeFlagOnFlagSet(flagSet *pflag.FlagSet, name string, shorthand *string, defaultValue *float64, description string) { cl.Float64FlagOnFlagSet(flagSet, name, shorthand, defaultValue, fmt.Sprintf("%s (sets --%s-min and -max to the same value)", description, name)) cl.Float64FlagOnFlagSet(flagSet, name+"-min", nil, nil, fmt.Sprintf("Minimum %s If --%s-max is not specified, the upper bound will be infinity", description, name)) @@ -248,7 +261,7 @@ func (cl *CommandLineInterface) Float64MinMaxRangeFlagOnFlagSet(flagSet *pflag.F cl.rangeFlags[name] = true } -// ByteQuantityMinMaxRangeFlagOnFlagSet creates and registers a min, max, and helper flag each accepting a ByteQuantity like 5mb or 12gb +// ByteQuantityMinMaxRangeFlagOnFlagSet creates and registers a min, max, and helper flag each accepting a ByteQuantity like 5mb or 12gb. func (cl *CommandLineInterface) ByteQuantityMinMaxRangeFlagOnFlagSet(flagSet *pflag.FlagSet, name string, shorthand *string, defaultValue *bytequantity.ByteQuantity, description string) { cl.ByteQuantityFlagOnFlagSet(flagSet, name, shorthand, defaultValue, fmt.Sprintf("%s (sets --%s-min and -max to the same value)", description, name)) cl.ByteQuantityFlagOnFlagSet(flagSet, name+"-min", nil, nil, fmt.Sprintf("Minimum %s If --%s-max is not specified, the upper bound will be infinity", description, name)) @@ -269,9 +282,9 @@ func (cl *CommandLineInterface) ByteQuantityMinMaxRangeFlagOnFlagSet(flagSet *pf cl.rangeFlags[name] = true } -// ByteQuantityFlagOnFlagSet creates and registers a flag accepting a ByteQuantity +// ByteQuantityFlagOnFlagSet creates and registers a flag accepting a ByteQuantity. func (cl *CommandLineInterface) ByteQuantityFlagOnFlagSet(flagSet *pflag.FlagSet, name string, shorthand *string, defaultValue *bytequantity.ByteQuantity, description string) { - invalidInputMsg := fmt.Sprintf("Invalid input for --%s. A valid example is 16gb. ", name) + invalidInputMsg := fmt.Sprintf("Invalid input for --%s. A valid example is 16gb.", name) byteQuantityProcessor := func(val interface{}) error { if val == nil { return nil @@ -280,13 +293,13 @@ func (cl *CommandLineInterface) ByteQuantityFlagOnFlagSet(flagSet *pflag.FlagSet case *string: bq, err := bytequantity.ParseToByteQuantity(*byteQuantityInput) if err != nil { - return fmt.Errorf(invalidInputMsg+"Can't parse byte quantity %s.", *byteQuantityInput) + return fmt.Errorf("%s Can't parse byte quantity %s", invalidInputMsg, *byteQuantityInput) } cl.Flags[name] = &bq case *bytequantity.ByteQuantity: return nil default: - return fmt.Errorf(invalidInputMsg + "Input type is unsupported.") + return fmt.Errorf("%s Input type is unsupported", invalidInputMsg) } return nil } @@ -298,7 +311,7 @@ func (cl *CommandLineInterface) ByteQuantityFlagOnFlagSet(flagSet *pflag.FlagSet case *bytequantity.ByteQuantity: return nil default: - return fmt.Errorf(invalidInputMsg + "Processing failed.") + return fmt.Errorf("%s Processing failed", invalidInputMsg) } } var stringDefaultValue *string @@ -310,7 +323,7 @@ func (cl *CommandLineInterface) ByteQuantityFlagOnFlagSet(flagSet *pflag.FlagSet cl.StringFlagOnFlagSet(flagSet, name, shorthand, stringDefaultValue, description, byteQuantityProcessor, byteQuantityValidator) } -// IntFlagOnFlagSet creates and registers a flag accepting an int +// IntFlagOnFlagSet creates and registers a flag accepting an int. func (cl *CommandLineInterface) IntFlagOnFlagSet(flagSet *pflag.FlagSet, name string, shorthand *string, defaultValue *int, description string) { if defaultValue == nil { cl.nilDefaults[name] = true @@ -323,7 +336,7 @@ func (cl *CommandLineInterface) IntFlagOnFlagSet(flagSet *pflag.FlagSet, name st cl.Flags[name] = flagSet.Int(name, *defaultValue, description) } -// Int32FlagOnFlagSet creates and registers a flag accepting an int +// Int32FlagOnFlagSet creates and registers a flag accepting an int. func (cl *CommandLineInterface) Int32FlagOnFlagSet(flagSet *pflag.FlagSet, name string, shorthand *string, defaultValue *int32, description string) { if defaultValue == nil { cl.nilDefaults[name] = true @@ -336,7 +349,7 @@ func (cl *CommandLineInterface) Int32FlagOnFlagSet(flagSet *pflag.FlagSet, name cl.Flags[name] = flagSet.Int32(name, *defaultValue, description) } -// Float64FlagOnFlagSet creates and registers a flag accepting a float64 +// Float64FlagOnFlagSet creates and registers a flag accepting a float64. func (cl *CommandLineInterface) Float64FlagOnFlagSet(flagSet *pflag.FlagSet, name string, shorthand *string, defaultValue *float64, description string) { if defaultValue == nil { cl.nilDefaults[name] = true @@ -366,7 +379,7 @@ func (cl *CommandLineInterface) StringFlagOnFlagSet(flagSet *pflag.FlagSet, name } // StringOptionsFlagOnFlagSet creates and registers a flag accepting a string with valid options. -// The validOpts slice of strings will be used to perform validation +// The validOpts slice of strings will be used to perform validation. func (cl *CommandLineInterface) StringOptionsFlagOnFlagSet(flagSet *pflag.FlagSet, name string, shorthand *string, defaultValue *string, description string, validOpts []string) { validationFn := func(val interface{}) error { if val == nil { @@ -397,7 +410,7 @@ func (cl *CommandLineInterface) StringSliceFlagOnFlagSet(flagSet *pflag.FlagSet, // RegexFlagOnFlagSet creates and registers a flag accepting a string slice of regular expressions. func (cl *CommandLineInterface) RegexFlagOnFlagSet(flagSet *pflag.FlagSet, name string, shorthand *string, defaultValue *string, description string) { - invalidInputMsg := fmt.Sprintf("Invalid regex input for --%s. ", name) + invalidInputMsg := fmt.Sprintf("Invalid regex input for --%s.", name) regexProcessor := func(val interface{}) error { if val == nil { return nil @@ -406,13 +419,13 @@ func (cl *CommandLineInterface) RegexFlagOnFlagSet(flagSet *pflag.FlagSet, name case *string: regexVal, err := regexp.Compile(*v) if err != nil { - return fmt.Errorf(invalidInputMsg + "Unable to compile the regex.") + return fmt.Errorf("%s Unable to compile the regex", invalidInputMsg) } cl.Flags[name] = regexVal case *regexp.Regexp: return nil default: - return fmt.Errorf(invalidInputMsg + "Input type is unsupported.") + return fmt.Errorf("%s Input type is unsupported", invalidInputMsg) } return nil @@ -425,13 +438,13 @@ func (cl *CommandLineInterface) RegexFlagOnFlagSet(flagSet *pflag.FlagSet, name case *regexp.Regexp: return nil default: - return fmt.Errorf(invalidInputMsg + "Processing failed.") + return fmt.Errorf("%s Processing failed", invalidInputMsg) } } cl.StringFlagOnFlagSet(flagSet, name, shorthand, defaultValue, description, regexProcessor, regexValidator) } -// PathFlagOnFlagSet creates and registers a flag accepting a string as a path +// PathFlagOnFlagSet creates and registers a flag accepting a string as a path. func (cl *CommandLineInterface) PathFlagOnFlagSet(flagSet *pflag.FlagSet, name string, shorthand *string, defaultValue *string, description string) { invalidInputMsg := fmt.Sprintf("Invalid path input for --%s. ", name) pathProcessor := func(val interface{}) error { @@ -442,11 +455,11 @@ func (cl *CommandLineInterface) PathFlagOnFlagSet(flagSet *pflag.FlagSet, name s case *string: path, err := homedir.Expand(*v) if err != nil { - return fmt.Errorf(invalidInputMsg + "Unable to expand path.") + return fmt.Errorf("%s Unable to expand path", invalidInputMsg) } cl.Flags[name] = &path default: - return fmt.Errorf(invalidInputMsg + "Input type is unsupported.") + return fmt.Errorf("%s Input type is unsupported", invalidInputMsg) } return nil } diff --git a/pkg/cli/flags_test.go b/pkg/cli/flags_test.go index 6f15aa0..70191a4 100644 --- a/pkg/cli/flags_test.go +++ b/pkg/cli/flags_test.go @@ -1,15 +1,14 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package cli_test diff --git a/pkg/cli/types.go b/pkg/cli/types.go index ea23a99..58beaa6 100644 --- a/pkg/cli/types.go +++ b/pkg/cli/types.go @@ -1,15 +1,14 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. // Package cli provides functions to build the selector command line interface package cli @@ -18,14 +17,15 @@ import ( "log" "regexp" - "github.com/aws/amazon-ec2-instance-selector/v3/pkg/bytequantity" - "github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector" "github.com/spf13/cobra" "github.com/spf13/pflag" + + "github.com/aws/amazon-ec2-instance-selector/v3/pkg/bytequantity" + "github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector" ) const ( - // Usage Template to run on --help + // Usage Template to run on --help. usageTemplate = `Usage:{{if .Runnable}} {{.UseLine}}{{end}}{{if .HasAvailableSubCommands}} {{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}} @@ -48,13 +48,13 @@ Global Flags: {{end}}` ) -// validator defines the function for providing validation on a flag +// validator defines the function for providing validation on a flag. type validator = func(val interface{}) error -// processor defines the function for providing mutating processing on a flag +// processor defines the function for providing mutating processing on a flag. type processor = func(val interface{}) error -// CommandLineInterface is a type to group CLI funcs and state +// CommandLineInterface is a type to group CLI funcs and state. type CommandLineInterface struct { Command *cobra.Command Flags map[string]interface{} @@ -66,7 +66,7 @@ type CommandLineInterface struct { } // Float64Me takes an interface and returns a pointer to a float64 value -// If the underlying interface kind is not float64 or *float64 then nil is returned +// If the underlying interface kind is not float64 or *float64 then nil is returned. func (*CommandLineInterface) Float64Me(i interface{}) *float64 { if i == nil { return nil @@ -83,7 +83,7 @@ func (*CommandLineInterface) Float64Me(i interface{}) *float64 { } // IntMe takes an interface and returns a pointer to an int value -// If the underlying interface kind is not int or *int then nil is returned +// If the underlying interface kind is not int or *int then nil is returned. func (*CommandLineInterface) IntMe(i interface{}) *int { if i == nil { return nil @@ -106,7 +106,7 @@ func (*CommandLineInterface) IntMe(i interface{}) *int { } // Int32Me takes an interface and returns a pointer to an int value -// If the underlying interface kind is not int or *int then nil is returned +// If the underlying interface kind is not int or *int then nil is returned. func (*CommandLineInterface) Int32Me(i interface{}) *int32 { if i == nil { return nil @@ -129,7 +129,7 @@ func (*CommandLineInterface) Int32Me(i interface{}) *int32 { } // IntRangeMe takes an interface and returns a pointer to an IntRangeFilter value -// If the underlying interface kind is not IntRangeFilter or *IntRangeFilter then nil is returned +// If the underlying interface kind is not IntRangeFilter or *IntRangeFilter then nil is returned. func (*CommandLineInterface) IntRangeMe(i interface{}) *selector.IntRangeFilter { if i == nil { return nil @@ -146,7 +146,7 @@ func (*CommandLineInterface) IntRangeMe(i interface{}) *selector.IntRangeFilter } // Int32RangeMe takes an interface and returns a pointer to an Int32RangeFilter value -// If the underlying interface kind is not Int32RangeFilter or *Int32RangeFilter then nil is returned +// If the underlying interface kind is not Int32RangeFilter or *Int32RangeFilter then nil is returned. func (*CommandLineInterface) Int32RangeMe(i interface{}) *selector.Int32RangeFilter { if i == nil { return nil @@ -163,7 +163,7 @@ func (*CommandLineInterface) Int32RangeMe(i interface{}) *selector.Int32RangeFil } // ByteQuantityRangeMe takes an interface and returns a pointer to a ByteQuantityRangeFilter value -// If the underlying interface kind is not ByteQuantityRangeFilter or *ByteQuantityRangeFilter then nil is returned +// If the underlying interface kind is not ByteQuantityRangeFilter or *ByteQuantityRangeFilter then nil is returned. func (*CommandLineInterface) ByteQuantityRangeMe(i interface{}) *selector.ByteQuantityRangeFilter { if i == nil { return nil @@ -180,7 +180,7 @@ func (*CommandLineInterface) ByteQuantityRangeMe(i interface{}) *selector.ByteQu } // Float64RangeMe takes an interface and returns a pointer to a Float64RangeFilter value -// If the underlying interface kind is not Float64RangeFilter or *Float64RangeFilter then nil is returned +// If the underlying interface kind is not Float64RangeFilter or *Float64RangeFilter then nil is returned. func (*CommandLineInterface) Float64RangeMe(i interface{}) *selector.Float64RangeFilter { if i == nil { return nil @@ -197,7 +197,7 @@ func (*CommandLineInterface) Float64RangeMe(i interface{}) *selector.Float64Rang } // StringMe takes an interface and returns a pointer to a string value -// If the underlying interface kind is not string or *string then nil is returned +// If the underlying interface kind is not string or *string then nil is returned. func (*CommandLineInterface) StringMe(i interface{}) *string { if i == nil { return nil @@ -214,7 +214,7 @@ func (*CommandLineInterface) StringMe(i interface{}) *string { } // BoolMe takes an interface and returns a pointer to a bool value -// If the underlying interface kind is not bool or *bool then nil is returned +// If the underlying interface kind is not bool or *bool then nil is returned. func (*CommandLineInterface) BoolMe(i interface{}) *bool { if i == nil { return nil @@ -231,7 +231,7 @@ func (*CommandLineInterface) BoolMe(i interface{}) *bool { } // StringSliceMe takes an interface and returns a pointer to a string slice -// If the underlying interface kind is not []string or *[]string then nil is returned +// If the underlying interface kind is not []string or *[]string then nil is returned. func (*CommandLineInterface) StringSliceMe(i interface{}) *[]string { if i == nil { return nil @@ -248,7 +248,7 @@ func (*CommandLineInterface) StringSliceMe(i interface{}) *[]string { } // RegexMe takes an interface and returns a pointer to a regex -// If the underlying interface kind is not regexp.Regexp or *regexp.Regexp then nil is returned +// If the underlying interface kind is not regexp.Regexp or *regexp.Regexp then nil is returned. func (*CommandLineInterface) RegexMe(i interface{}) *regexp.Regexp { if i == nil { return nil @@ -265,7 +265,7 @@ func (*CommandLineInterface) RegexMe(i interface{}) *regexp.Regexp { } // ByteQuantityMe takes an interface and returns a pointer to a ByteQuantity -// If the underlying interface kind is not bytequantity.ByteQuantity or *bytequantity.ByteQuantity then nil is returned +// If the underlying interface kind is not bytequantity.ByteQuantity or *bytequantity.ByteQuantity then nil is returned. func (*CommandLineInterface) ByteQuantityMe(i interface{}) *bytequantity.ByteQuantity { if i == nil { return nil diff --git a/pkg/cli/types_test.go b/pkg/cli/types_test.go index 2763181..b13fa12 100644 --- a/pkg/cli/types_test.go +++ b/pkg/cli/types_test.go @@ -1,15 +1,14 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package cli_test @@ -121,6 +120,7 @@ func TestByteQuantityRangeMe(t *testing.T) { val = cli.ByteQuantityRangeMe(nil) h.Assert(t, val == nil, "Should return nil if nil is passed in") } + func TestRegexMe(t *testing.T) { cli := getTestCLI() regexVal, err := regexp.Compile("c4.*") diff --git a/pkg/ec2pricing/ec2pricing.go b/pkg/ec2pricing/ec2pricing.go index 527713b..ddef1af 100644 --- a/pkg/ec2pricing/ec2pricing.go +++ b/pkg/ec2pricing/ec2pricing.go @@ -1,20 +1,20 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package ec2pricing import ( "context" + "fmt" "log" "time" @@ -30,18 +30,16 @@ const ( serviceCode = "AmazonEC2" ) -var ( - DefaultSpotDaysBack = 30 -) +var DefaultSpotDaysBack = 30 -// EC2Pricing is the public struct to interface with AWS pricing APIs +// EC2Pricing is the public struct to interface with AWS pricing APIs. type EC2Pricing struct { ODPricing *OnDemandPricing SpotPricing *SpotPricing logger *log.Logger } -// EC2PricingIface is the EC2Pricing interface mainly used to mock out ec2pricing during testing +// EC2PricingIface is the EC2Pricing interface mainly used to mock out ec2pricing during testing. type EC2PricingIface interface { GetOnDemandInstanceTypeCost(ctx context.Context, instanceType ec2types.InstanceType) (float64, error) GetSpotInstanceTypeNDayAvgCost(ctx context.Context, instanceType ec2types.InstanceType, availabilityZones []string, days int) (float64, error) @@ -61,7 +59,7 @@ func modifyPricingRegion(opt *pricing.Options) { opt.Region = "us-east-1" } -// New creates an instance of instance-selector EC2Pricing +// New creates an instance of instance-selector EC2Pricing. func New(ctx context.Context, cfg aws.Config) (*EC2Pricing, error) { return NewWithCache(ctx, cfg, 0, "") } @@ -69,9 +67,17 @@ func New(ctx context.Context, cfg aws.Config) (*EC2Pricing, error) { func NewWithCache(ctx context.Context, cfg aws.Config, ttl time.Duration, cacheDir string) (*EC2Pricing, error) { pricingClient := pricing.NewFromConfig(cfg, modifyPricingRegion) ec2Client := ec2.NewFromConfig(cfg) + odPricingCache, err := LoadODCacheOrNew(ctx, pricingClient, cfg.Region, ttl, cacheDir) + if err != nil { + return nil, fmt.Errorf("unable to initialize the OD pricing cache: %w", err) + } + spotPricingCache, err := LoadSpotCacheOrNew(ctx, ec2Client, cfg.Region, ttl, cacheDir, DefaultSpotDaysBack) + if err != nil { + return nil, fmt.Errorf("unable to initialize the spot pricing cache: %w", err) + } return &EC2Pricing{ - ODPricing: LoadODCacheOrNew(ctx, pricingClient, cfg.Region, ttl, cacheDir), - SpotPricing: LoadSpotCacheOrNew(ctx, ec2Client, cfg.Region, ttl, cacheDir, DefaultSpotDaysBack), + ODPricing: odPricingCache, + SpotPricing: spotPricingCache, }, nil } @@ -81,18 +87,18 @@ func (p *EC2Pricing) SetLogger(logger *log.Logger) { p.SpotPricing.SetLogger(logger) } -// OnDemandCacheCount returns the number of items in the OD cache +// OnDemandCacheCount returns the number of items in the OD cache. func (p *EC2Pricing) OnDemandCacheCount() int { return p.ODPricing.Count() } -// SpotCacheCount returns the number of items in the spot cache +// SpotCacheCount returns the number of items in the spot cache. func (p *EC2Pricing) SpotCacheCount() int { return p.SpotPricing.Count() } // GetSpotInstanceTypeNDayAvgCost retrieves the spot price history for a given AZ from the past N days and averages the price -// Passing an empty list for availabilityZones will retrieve avg cost for all AZs in the current AWSSession's region +// Passing an empty list for availabilityZones will retrieve avg cost for all AZs in the current AWSSession's region. func (p *EC2Pricing) GetSpotInstanceTypeNDayAvgCost(ctx context.Context, instanceType ec2types.InstanceType, availabilityZones []string, days int) (float64, error) { if len(availabilityZones) == 0 { return p.SpotPricing.Get(ctx, instanceType, "", days) @@ -113,17 +119,17 @@ func (p *EC2Pricing) GetSpotInstanceTypeNDayAvgCost(ctx context.Context, instanc return costs[0], nil } -// GetOnDemandInstanceTypeCost retrieves the on-demand hourly cost for the specified instance type +// GetOnDemandInstanceTypeCost retrieves the on-demand hourly cost for the specified instance type. func (p *EC2Pricing) GetOnDemandInstanceTypeCost(ctx context.Context, instanceType ec2types.InstanceType) (float64, error) { return p.ODPricing.Get(ctx, instanceType) } -// RefreshOnDemandCache makes a bulk request to the pricing api to retrieve all instance type pricing and stores them in a local cache +// RefreshOnDemandCache makes a bulk request to the pricing api to retrieve all instance type pricing and stores them in a local cache. func (p *EC2Pricing) RefreshOnDemandCache(ctx context.Context) error { return p.ODPricing.Refresh(ctx) } -// RefreshSpotCache makes a bulk request to the ec2 api to retrieve all spot instance type pricing and stores them in a local cache +// RefreshSpotCache makes a bulk request to the ec2 api to retrieve all spot instance type pricing and stores them in a local cache. func (p *EC2Pricing) RefreshSpotCache(ctx context.Context, days int) error { return p.SpotPricing.Refresh(ctx, days) } diff --git a/pkg/ec2pricing/ec2pricing_test.go b/pkg/ec2pricing/ec2pricing_test.go index 72d2c64..b95c121 100644 --- a/pkg/ec2pricing/ec2pricing_test.go +++ b/pkg/ec2pricing/ec2pricing_test.go @@ -1,15 +1,14 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package ec2pricing_test @@ -20,12 +19,13 @@ import ( "os" "testing" + "github.com/aws/aws-sdk-go-v2/service/ec2" ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" + "github.com/aws/aws-sdk-go-v2/service/pricing" + "github.com/samber/lo" "github.com/aws/amazon-ec2-instance-selector/v3/pkg/ec2pricing" h "github.com/aws/amazon-ec2-instance-selector/v3/pkg/test" - "github.com/aws/aws-sdk-go-v2/service/ec2" - "github.com/aws/aws-sdk-go-v2/service/pricing" ) const ( @@ -42,7 +42,7 @@ type mockedPricing struct { GetProductsErr error } -func (m mockedPricing) GetProducts(ctx context.Context, input *pricing.GetProductsInput, optFns ...func(*pricing.Options)) (*pricing.GetProductsOutput, error) { +func (m mockedPricing) GetProducts(_ context.Context, input *pricing.GetProductsInput, optFns ...func(*pricing.Options)) (*pricing.GetProductsOutput, error) { return &m.GetProductsResp, m.GetProductsErr } @@ -52,14 +52,14 @@ type mockedSpotEC2 struct { DescribeSpotPriceHistoryPagesErr error } -func (m mockedSpotEC2) DescribeSpotPriceHistory(ctx context.Context, input *ec2.DescribeSpotPriceHistoryInput, optFns ...func(*ec2.Options)) (*ec2.DescribeSpotPriceHistoryOutput, error) { +func (m mockedSpotEC2) DescribeSpotPriceHistory(_ context.Context, input *ec2.DescribeSpotPriceHistoryInput, optFns ...func(*ec2.Options)) (*ec2.DescribeSpotPriceHistoryOutput, error) { return &m.DescribeSpotPriceHistoryPagesResp, m.DescribeSpotPriceHistoryPagesErr } func setupOdMock(t *testing.T, api string, file string) mockedPricing { mockFilename := fmt.Sprintf("%s/%s/%s", mockFilesPath, api, file) mockFile, err := os.ReadFile(mockFilename) - h.Assert(t, err == nil, "Error reading mock file "+string(mockFilename)) + h.Assert(t, err == nil, "Error reading mock file "+mockFilename) switch api { case getProducts: priceList := []string{string(mockFile)} @@ -79,7 +79,7 @@ func setupOdMock(t *testing.T, api string, file string) mockedPricing { func setupEc2Mock(t *testing.T, api string, file string) mockedSpotEC2 { mockFilename := fmt.Sprintf("%s/%s/%s", mockFilesPath, api, file) mockFile, err := os.ReadFile(mockFilename) - h.Assert(t, err == nil, "Error reading mock file "+string(mockFilename)) + h.Assert(t, err == nil, "Error reading mock file "+mockFilename) switch api { case describeSpotPriceHistory: dspho := ec2.DescribeSpotPriceHistoryOutput{} @@ -99,7 +99,7 @@ func TestGetOndemandInstanceTypeCost_m5large(t *testing.T) { pricingMock := setupOdMock(t, getProducts, "m5_large.json") ctx := context.Background() ec2pricingClient := ec2pricing.EC2Pricing{ - ODPricing: ec2pricing.LoadODCacheOrNew(ctx, pricingMock, "us-east-1", 0, ""), + ODPricing: lo.Must(ec2pricing.LoadODCacheOrNew(ctx, pricingMock, "us-east-1", 0, "")), } price, err := ec2pricingClient.GetOnDemandInstanceTypeCost(ctx, ec2types.InstanceTypeM5Large) h.Ok(t, err) @@ -110,7 +110,7 @@ func TestRefreshOnDemandCache(t *testing.T) { pricingMock := setupOdMock(t, getProducts, "m5_large.json") ctx := context.Background() ec2pricingClient := ec2pricing.EC2Pricing{ - ODPricing: ec2pricing.LoadODCacheOrNew(ctx, pricingMock, "us-east-1", 0, ""), + ODPricing: lo.Must(ec2pricing.LoadODCacheOrNew(ctx, pricingMock, "us-east-1", 0, "")), } err := ec2pricingClient.RefreshOnDemandCache(ctx) h.Ok(t, err) @@ -124,7 +124,7 @@ func TestGetSpotInstanceTypeNDayAvgCost(t *testing.T) { ec2Mock := setupEc2Mock(t, describeSpotPriceHistory, "m5_large.json") ctx := context.Background() ec2pricingClient := ec2pricing.EC2Pricing{ - SpotPricing: ec2pricing.LoadSpotCacheOrNew(ctx, ec2Mock, "us-east-1", 0, "", 30), + SpotPricing: lo.Must(ec2pricing.LoadSpotCacheOrNew(ctx, ec2Mock, "us-east-1", 0, "", 30)), } price, err := ec2pricingClient.GetSpotInstanceTypeNDayAvgCost(ctx, ec2types.InstanceTypeM5Large, []string{"us-east-1a"}, 30) h.Ok(t, err) @@ -135,7 +135,7 @@ func TestRefreshSpotCache(t *testing.T) { ec2Mock := setupEc2Mock(t, describeSpotPriceHistory, "m5_large.json") ctx := context.Background() ec2pricingClient := ec2pricing.EC2Pricing{ - SpotPricing: ec2pricing.LoadSpotCacheOrNew(ctx, ec2Mock, "us-east-1", 0, "", 30), + SpotPricing: lo.Must(ec2pricing.LoadSpotCacheOrNew(ctx, ec2Mock, "us-east-1", 0, "", 30)), } err := ec2pricingClient.RefreshSpotCache(ctx, 30) h.Ok(t, err) diff --git a/pkg/ec2pricing/odpricing.go b/pkg/ec2pricing/odpricing.go index 7d1a0ee..eb23410 100644 --- a/pkg/ec2pricing/odpricing.go +++ b/pkg/ec2pricing/odpricing.go @@ -1,15 +1,14 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package ec2pricing @@ -85,18 +84,10 @@ type PriceDimensionInfo struct { PricePerUnit map[string]string `json:"pricePerUnit"` } -func LoadODCacheOrNew(ctx context.Context, pricingClient pricing.GetProductsAPIClient, region string, fullRefreshTTL time.Duration, directoryPath string) *OnDemandPricing { +func LoadODCacheOrNew(ctx context.Context, pricingClient pricing.GetProductsAPIClient, region string, fullRefreshTTL time.Duration, directoryPath string) (*OnDemandPricing, error) { expandedDirPath, err := homedir.Expand(directoryPath) if err != nil { - log.Printf("Unable to load on-demand pricing cache directory %s: %v", expandedDirPath, err) - return &OnDemandPricing{ - Region: region, - FullRefreshTTL: 0, - DirectoryPath: directoryPath, - cache: cache.New(fullRefreshTTL, fullRefreshTTL), - pricingClient: pricingClient, - logger: log.New(io.Discard, "", 0), - } + return nil, fmt.Errorf("unable to load on-demand pricing cache directory %s: %w", expandedDirPath, err) } odPricing := &OnDemandPricing{ Region: region, @@ -107,20 +98,22 @@ func LoadODCacheOrNew(ctx context.Context, pricingClient pricing.GetProductsAPIC logger: log.New(io.Discard, "", 0), } if fullRefreshTTL <= 0 { - odPricing.Clear() - return odPricing + if err := odPricing.Clear(); err != nil { + return nil, fmt.Errorf("unable to clear od pricing cache due to ttl <= 0 %w", err) + } + return odPricing, nil } // Start the cache refresh job - go odCacheRefreshJob(ctx, odPricing) + go odPricing.odCacheRefreshJob(ctx) odCache, err := loadODCacheFrom(fullRefreshTTL, region, expandedDirPath) + if err != nil && !errors.Is(err, os.ErrNotExist) { + return nil, fmt.Errorf("an on-demand pricing cache file could not be loaded: %v", err) + } if err != nil { - if !errors.Is(err, os.ErrNotExist) { - log.Printf("An on-demand pricing cache file could not be loaded: %v", err) - } - return odPricing + odCache = cache.New(0, 0) } odPricing.cache = odCache - return odPricing + return odPricing, nil } func loadODCacheFrom(itemTTL time.Duration, region string, expandedDirPath string) (*cache.Cache, error) { @@ -141,14 +134,14 @@ func getODCacheFilePath(region string, directoryPath string) string { return filepath.Join(directoryPath, fmt.Sprintf("%s-%s", region, ODCacheFileName)) } -func odCacheRefreshJob(ctx context.Context, odPricing *OnDemandPricing) { - if odPricing.FullRefreshTTL <= 0 { +func (c *OnDemandPricing) odCacheRefreshJob(ctx context.Context) { + if c.FullRefreshTTL <= 0 { return } - refreshTicker := time.NewTicker(odPricing.FullRefreshTTL) + refreshTicker := time.NewTicker(c.FullRefreshTTL) for range refreshTicker.C { - if err := odPricing.Refresh(ctx); err != nil { - log.Println(err) + if err := c.Refresh(ctx); err != nil { + c.logger.Printf("Periodic OD Cache Refresh Error: %v", err) } } } @@ -187,7 +180,7 @@ func (c *OnDemandPricing) Get(ctx context.Context, instanceType ec2types.Instanc return costs[string(instanceType)], nil } -// Count of items in the cache +// Count of items in the cache. func (c *OnDemandPricing) Count() int { return c.cache.ItemCount() } @@ -200,17 +193,20 @@ func (c *OnDemandPricing) Save() error { if err != nil { return err } - if err := os.Mkdir(c.DirectoryPath, 0755); err != nil && !errors.Is(err, os.ErrExist) { + if err := os.Mkdir(c.DirectoryPath, 0o755); err != nil && !errors.Is(err, os.ErrExist) { return err } - return os.WriteFile(getODCacheFilePath(c.Region, c.DirectoryPath), cacheBytes, 0644) + return os.WriteFile(getODCacheFilePath(c.Region, c.DirectoryPath), cacheBytes, 0600) } func (c *OnDemandPricing) Clear() error { c.Lock() defer c.Unlock() c.cache.Flush() - return os.Remove(getODCacheFilePath(c.Region, c.DirectoryPath)) + if err := os.Remove(getODCacheFilePath(c.Region, c.DirectoryPath)); err != nil && !os.IsNotExist(err) { + return err + } + return nil } // fetchOnDemandPricing makes a bulk request to the pricing api to retrieve all instance type pricing if the instanceType is the empty string @@ -251,7 +247,7 @@ func (c *OnDemandPricing) fetchOnDemandPricing(ctx context.Context, instanceType } // StringMe takes an interface and returns a pointer to a string value -// If the underlying interface kind is not string or *string then nil is returned +// If the underlying interface kind is not string or *string then nil is returned. func (c *OnDemandPricing) StringMe(i interface{}) *string { if i == nil { return nil @@ -282,7 +278,7 @@ func (c *OnDemandPricing) getProductsInputFilters(instanceType ec2types.Instance return filters } -// parseOndemandUnitPrice takes a priceList from the pricing API and parses its weirdness +// parseOndemandUnitPrice takes a priceList from the pricing API and parses its weirdness. func (c *OnDemandPricing) parseOndemandUnitPrice(priceList string) (string, float64, error) { var productPriceList PricingList err := json.Unmarshal([]byte(priceList), &productPriceList) diff --git a/pkg/ec2pricing/spotpricing.go b/pkg/ec2pricing/spotpricing.go index 1f4d85d..be3b1fd 100644 --- a/pkg/ec2pricing/spotpricing.go +++ b/pkg/ec2pricing/spotpricing.go @@ -1,15 +1,14 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package ec2pricing @@ -55,18 +54,10 @@ type spotPricingEntry struct { Zone string } -func LoadSpotCacheOrNew(ctx context.Context, ec2Client ec2.DescribeSpotPriceHistoryAPIClient, region string, fullRefreshTTL time.Duration, directoryPath string, days int) *SpotPricing { +func LoadSpotCacheOrNew(ctx context.Context, ec2Client ec2.DescribeSpotPriceHistoryAPIClient, region string, fullRefreshTTL time.Duration, directoryPath string, days int) (*SpotPricing, error) { expandedDirPath, err := homedir.Expand(directoryPath) if err != nil { - log.Printf("Unable to load spot pricing cache directory %s: %v", expandedDirPath, err) - return &SpotPricing{ - Region: region, - FullRefreshTTL: 0, - DirectoryPath: directoryPath, - cache: cache.New(fullRefreshTTL, fullRefreshTTL), - ec2Client: ec2Client, - logger: log.New(io.Discard, "", 0), - } + return nil, fmt.Errorf("unable to load spot pricing cache directory %s: %w", expandedDirPath, err) } spotPricing := &SpotPricing{ Region: region, @@ -77,21 +68,23 @@ func LoadSpotCacheOrNew(ctx context.Context, ec2Client ec2.DescribeSpotPriceHist logger: log.New(io.Discard, "", 0), } if fullRefreshTTL <= 0 { - spotPricing.Clear() - return spotPricing + if err := spotPricing.Clear(); err != nil { + return nil, err + } + return spotPricing, nil } gob.Register([]*spotPricingEntry{}) // Start the cache refresh job - go spotCacheRefreshJob(ctx, spotPricing, days) + go spotPricing.spotCacheRefreshJob(ctx, days) spotCache, err := loadSpotCacheFrom(fullRefreshTTL, region, expandedDirPath) + if err != nil && !os.IsNotExist(err) { + return nil, fmt.Errorf("a spot pricing cache file could not be loaded: %w", err) + } if err != nil { - if !errors.Is(err, os.ErrNotExist) { - log.Printf("A spot pricing cache file could not be loaded: %v", err) - } - return spotPricing + spotCache = cache.New(0, 0) } spotPricing.cache = spotCache - return spotPricing + return spotPricing, nil } func loadSpotCacheFrom(itemTTL time.Duration, region string, expandedDirPath string) (*cache.Cache, error) { @@ -113,14 +106,14 @@ func getSpotCacheFilePath(region string, directoryPath string) string { return filepath.Join(directoryPath, fmt.Sprintf("%s-%s", region, SpotCacheFileName)) } -func spotCacheRefreshJob(ctx context.Context, spotPricing *SpotPricing, days int) { - if spotPricing.FullRefreshTTL <= 0 { +func (c *SpotPricing) spotCacheRefreshJob(ctx context.Context, days int) { + if c.FullRefreshTTL <= 0 { return } - refreshTicker := time.NewTicker(spotPricing.FullRefreshTTL) + refreshTicker := time.NewTicker(c.FullRefreshTTL) for range refreshTicker.C { - if err := spotPricing.Refresh(ctx, days); err != nil { - log.Println(err) + if err := c.Refresh(ctx, days); err != nil { + c.logger.Printf("Periodic Spot Cache Refresh Error: %v", err) } } } @@ -218,7 +211,7 @@ func (c *SpotPricing) filterOn(zone string, pricingEntries []*spotPricingEntry) return filtered } -// Count of items in the cache +// Count of items in the cache. func (c *SpotPricing) Count() int { return c.cache.ItemCount() } @@ -227,7 +220,7 @@ func (c *SpotPricing) Save() error { if c.FullRefreshTTL <= 0 || c.Count() == 0 { return nil } - if err := os.Mkdir(c.DirectoryPath, 0755); err != nil && !errors.Is(err, os.ErrExist) { + if err := os.Mkdir(c.DirectoryPath, 0o755); err != nil && !errors.Is(err, os.ErrExist) { return err } file, err := os.Create(getSpotCacheFilePath(c.Region, c.DirectoryPath)) @@ -243,11 +236,14 @@ func (c *SpotPricing) Clear() error { c.Lock() defer c.Unlock() c.cache.Flush() - return os.Remove(getSpotCacheFilePath(c.Region, c.DirectoryPath)) + if err := os.Remove(getSpotCacheFilePath(c.Region, c.DirectoryPath)); err != nil && !os.IsNotExist(err) { + return err + } + return nil } // fetchSpotPricingTimeSeries makes a bulk request to the ec2 api to retrieve all spot instance type pricing for the past n days -// If instanceType is empty, it will fetch for all instance types +// If instanceType is empty, it will fetch for all instance types. func (c *SpotPricing) fetchSpotPricingTimeSeries(ctx context.Context, instanceType ec2types.InstanceType, days int) (map[string][]*spotPricingEntry, error) { start := time.Now() calls := 0 diff --git a/pkg/env/env.go b/pkg/env/env.go index 86aac10..4992d41 100644 --- a/pkg/env/env.go +++ b/pkg/env/env.go @@ -2,7 +2,9 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -18,7 +20,7 @@ import ( ) // WithDefaultInt returns the int value of the supplied environment variable or, if not present, -// the supplied default value. If the int conversion fails, returns the default +// the supplied default value. If the int conversion fails, returns the default. func WithDefaultInt(key string, def int) *int { val, ok := os.LookupEnv(key) if !ok { diff --git a/pkg/instancetypes/instancetypes.go b/pkg/instancetypes/instancetypes.go index fa44f53..4b2c403 100644 --- a/pkg/instancetypes/instancetypes.go +++ b/pkg/instancetypes/instancetypes.go @@ -1,15 +1,14 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package instancetypes @@ -30,11 +29,9 @@ import ( "github.com/patrickmn/go-cache" ) -var ( - CacheFileName = "ec2-instance-types.json" -) +var CacheFileName = "ec2-instance-types.json" -// Details hold all the information on an ec2 instance type +// Details hold all the information on an ec2 instance type. type Details struct { ec2types.InstanceTypeInfo OndemandPricePerHour *float64 @@ -51,40 +48,37 @@ type Provider struct { logger *log.Logger } -// NewProvider creates a new Instance Types provider used to fetch Instance Type information from EC2 -func NewProvider(directoryPath string, region string, ttl time.Duration, ec2Client ec2.DescribeInstanceTypesAPIClient) *Provider { - expandedDirPath, err := homedir.Expand(directoryPath) - if err != nil { - log.Printf("Unable to expand instance type cache directory %s: %v", directoryPath, err) - } +// NewProvider creates a new Instance Types provider used to fetch Instance Type information from EC2. +func NewProvider(region string, ec2Client ec2.DescribeInstanceTypesAPIClient) *Provider { return &Provider{ Region: region, - DirectoryPath: expandedDirPath, - FullRefreshTTL: ttl, + DirectoryPath: "", + FullRefreshTTL: 0, ec2Client: ec2Client, - cache: cache.New(ttl, ttl), + cache: cache.New(0, 0), logger: log.New(io.Discard, "", 0), } } -// NewProvider creates a new Instance Types provider used to fetch Instance Type information from EC2 and optionally cache -func LoadFromOrNew(directoryPath string, region string, ttl time.Duration, ec2Client ec2.DescribeInstanceTypesAPIClient) *Provider { +// NewProvider creates a new Instance Types provider used to fetch Instance Type information from EC2 and optionally cache. +func LoadFromOrNew(directoryPath string, region string, ttl time.Duration, ec2Client ec2.DescribeInstanceTypesAPIClient) (*Provider, error) { expandedDirPath, err := homedir.Expand(directoryPath) if err != nil { - log.Printf("Unable to load instance-type cache directory %s: %v", expandedDirPath, err) - return NewProvider(directoryPath, region, ttl, ec2Client) + return nil, fmt.Errorf("unable to load instance-type cache directory %s: %w", expandedDirPath, err) } if ttl <= 0 { - provider := NewProvider(directoryPath, region, ttl, ec2Client) - provider.Clear() - return provider + provider := NewProvider(region, ec2Client) + if err := provider.Clear(); err != nil { + return nil, err + } + return provider, nil } itCache, err := loadFrom(ttl, region, expandedDirPath) + if err != nil && !os.IsNotExist(err) { + return nil, fmt.Errorf("unable to load instance-type cache from %s: %w", expandedDirPath, err) + } if err != nil { - if !errors.Is(err, os.ErrNotExist) { - log.Printf("Unable to load instance-type cache from %s: %v", expandedDirPath, err) - } - return NewProvider(directoryPath, region, ttl, ec2Client) + itCache = cache.New(0, 0) } return &Provider{ Region: region, @@ -92,7 +86,7 @@ func LoadFromOrNew(directoryPath string, region string, ttl time.Duration, ec2Cl ec2Client: ec2Client, cache: itCache, logger: log.New(io.Discard, "", 0), - } + }, nil } func loadFrom(ttl time.Duration, region string, expandedDirPath string) (*cache.Cache, error) { @@ -183,15 +177,18 @@ func (p *Provider) Save() error { if err != nil { return err } - if err := os.Mkdir(p.DirectoryPath, 0755); err != nil && !errors.Is(err, os.ErrExist) { + if err := os.Mkdir(p.DirectoryPath, 0o755); err != nil && !errors.Is(err, os.ErrExist) { return err } - return os.WriteFile(getCacheFilePath(p.Region, p.DirectoryPath), cacheBytes, 0644) + return os.WriteFile(getCacheFilePath(p.Region, p.DirectoryPath), cacheBytes, 0600) } func (p *Provider) Clear() error { p.cache.Flush() - return os.Remove(getCacheFilePath(p.Region, p.DirectoryPath)) + if err := os.Remove(getCacheFilePath(p.Region, p.DirectoryPath)); err != nil && !os.IsNotExist(err) { + return err + } + return nil } func (p *Provider) CacheCount() int { diff --git a/pkg/selector/aggregates.go b/pkg/selector/aggregates.go index 16e8a49..6d3fb74 100644 --- a/pkg/selector/aggregates.go +++ b/pkg/selector/aggregates.go @@ -1,3 +1,16 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package selector import ( @@ -5,33 +18,36 @@ import ( "fmt" "regexp" - "github.com/aws/amazon-ec2-instance-selector/v3/pkg/bytequantity" "github.com/aws/aws-sdk-go-v2/service/ec2" ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" + + "github.com/aws/amazon-ec2-instance-selector/v3/pkg/bytequantity" ) const ( - // AggregateLowPercentile is the default lower percentile for resource ranges on similar instance type comparisons + // AggregateLowPercentile is the default lower percentile for resource ranges on similar instance type comparisons. AggregateLowPercentile = 0.9 - // AggregateHighPercentile is the default upper percentile for resource ranges on similar instance type comparisons + // AggregateHighPercentile is the default upper percentile for resource ranges on similar instance type comparisons. AggregateHighPercentile = 1.2 ) -// FiltersTransform can be implemented to provide custom transforms +var baseAllowedInstanceTypesRE = regexp.MustCompile(`^[cmr][3-9][agi]?\..*$|^t[2-9][gi]?\..*$`) + +// FiltersTransform can be implemented to provide custom transforms. type FiltersTransform interface { Transform(context.Context, Filters) (Filters, error) } -// TransformFn is the func type definition for a FiltersTransform +// TransformFn is the func type definition for a FiltersTransform. type TransformFn func(context.Context, Filters) (Filters, error) // Transform implements FiltersTransform interface on TransformFn -// This allows any TransformFn to be passed into funcs accepting FiltersTransform interface +// This allows any TransformFn to be passed into funcs accepting FiltersTransform interface. func (fn TransformFn) Transform(ctx context.Context, filters Filters) (Filters, error) { return fn(ctx, filters) } -// TransformBaseInstanceType transforms lower level filters based on the instanceTypeBase specs +// TransformBaseInstanceType transforms lower level filters based on the instanceTypeBase specs. func (itf Selector) TransformBaseInstanceType(ctx context.Context, filters Filters) (Filters, error) { if filters.InstanceTypeBase == nil { return filters, nil @@ -83,7 +99,7 @@ func (itf Selector) TransformBaseInstanceType(ctx context.Context, filters Filte return filters, nil } -// TransformFlexible transforms lower level filters based on a set of opinions +// TransformFlexible transforms lower level filters based on a set of opinions. func (itf Selector) TransformFlexible(ctx context.Context, filters Filters) (Filters, error) { if filters.Flexible == nil { return filters, nil @@ -102,11 +118,7 @@ func (itf Selector) TransformFlexible(ctx context.Context, filters Filters) (Fil } if filters.AllowList == nil { - baseAllowedInstanceTypes, err := regexp.Compile("^[cmr][3-9][ag]?\\..*$|^a[1-9]\\..*$|^t[2-9]\\..*$") - if err != nil { - return filters, err - } - filters.AllowList = baseAllowedInstanceTypes + filters.AllowList = baseAllowedInstanceTypesRE } if filters.VCpusRange == nil && filters.MemoryRange == nil { @@ -117,7 +129,7 @@ func (itf Selector) TransformFlexible(ctx context.Context, filters Filters) (Fil return filters, nil } -// TransformForService transforms lower level filters based on the service +// TransformForService transforms lower level filters based on the service. func (itf Selector) TransformForService(ctx context.Context, filters Filters) (Filters, error) { return itf.ServiceRegistry.ExecuteTransforms(filters) } diff --git a/pkg/selector/aggregates_test.go b/pkg/selector/aggregates_test.go index 84d5549..5801621 100644 --- a/pkg/selector/aggregates_test.go +++ b/pkg/selector/aggregates_test.go @@ -1,15 +1,14 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package selector_test diff --git a/pkg/selector/comparators.go b/pkg/selector/comparators.go index 94236bf..6b1852a 100644 --- a/pkg/selector/comparators.go +++ b/pkg/selector/comparators.go @@ -1,15 +1,14 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package selector @@ -392,7 +391,7 @@ func getCPUManufacturer(instanceTypeInfo *ec2types.InstanceTypeInfo) CPUManufact // getInstanceTypeGeneration returns the generation from an instance type name // i.e. c7i.xlarge -> 7 -// if any error occurs, 0 will be returned +// if any error occurs, 0 will be returned. func getInstanceTypeGeneration(instanceTypeName string) *int { zero := 0 matches := generationRE.FindStringSubmatch(instanceTypeName) @@ -407,7 +406,7 @@ func getInstanceTypeGeneration(instanceTypeName string) *int { } // supportSyntaxToBool takes an instance spec field that uses ["unsupported", "supported", "required", or "default"] -// and transforms it to a *bool to use in filter execution +// and transforms it to a *bool to use in filter execution. func supportSyntaxToBool(instanceTypeSupport *string) *bool { if instanceTypeSupport == nil { return nil diff --git a/pkg/selector/comparators_internal_test.go b/pkg/selector/comparators_internal_test.go index 40beb1a..a8860b6 100644 --- a/pkg/selector/comparators_internal_test.go +++ b/pkg/selector/comparators_internal_test.go @@ -1,15 +1,14 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package selector @@ -17,8 +16,9 @@ import ( "math" "testing" - h "github.com/aws/amazon-ec2-instance-selector/v3/pkg/test" "github.com/aws/aws-sdk-go-v2/aws" + + h "github.com/aws/amazon-ec2-instance-selector/v3/pkg/test" ) func TestIsSupportedFromStrings_Supported(t *testing.T) { diff --git a/pkg/selector/emr.go b/pkg/selector/emr.go index 212f16f..3a04f75 100644 --- a/pkg/selector/emr.go +++ b/pkg/selector/emr.go @@ -1,15 +1,14 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package selector @@ -25,10 +24,10 @@ const ( fallbackVersion = "5.20.0" ) -// EMR is a Service type for a custom service filter transform +// EMR is a Service type for a custom service filter transform. type EMR struct{} -// Filters implements the Service interface contract for EMR +// Filters implements the Service interface contract for EMR. func (e EMR) Filters(version string) (Filters, error) { filters := Filters{} if version == "" { @@ -53,7 +52,7 @@ func (e EMR) Filters(version string) (Filters, error) { return filters, nil } -// getEMRInstanceTypes returns a list of instance types that emr supports +// getEMRInstanceTypes returns a list of instance types that emr supports. func (e EMR) getEMRInstanceTypes(version semver.Version) ([]string, error) { instanceTypes := []string{} diff --git a/pkg/selector/emr_test.go b/pkg/selector/emr_test.go index 504951f..df35319 100644 --- a/pkg/selector/emr_test.go +++ b/pkg/selector/emr_test.go @@ -1,15 +1,14 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package selector_test @@ -20,7 +19,7 @@ import ( h "github.com/aws/amazon-ec2-instance-selector/v3/pkg/test" ) -// Tests +// Tests. var emr = "emr" func TestEMRDefaultService(t *testing.T) { diff --git a/pkg/selector/outputs/bubbletea.go b/pkg/selector/outputs/bubbletea.go index e1e8210..216cc8e 100644 --- a/pkg/selector/outputs/bubbletea.go +++ b/pkg/selector/outputs/bubbletea.go @@ -1,28 +1,28 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package outputs import ( - "github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes" - "github.com/aws/amazon-ec2-instance-selector/v3/pkg/sorter" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/muesli/termenv" + + "github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes" + "github.com/aws/amazon-ec2-instance-selector/v3/pkg/sorter" ) const ( - // can't get terminal dimensions on startup, so use this + // can't get terminal dimensions on startup, so use this. initialDimensionVal = 30 instanceTypeKey = "instance type" @@ -30,17 +30,15 @@ const ( ) const ( - // table states + // table states. stateTable = "table" stateVerbose = "verbose" stateSorting = "sorting" ) -var ( - controlsStyle = lipgloss.NewStyle().Faint(true) -) +var controlsStyle = lipgloss.NewStyle().Faint(true) -// BubbleTeaModel is used to hold the state of the bubble tea TUI +// BubbleTeaModel is used to hold the state of the bubble tea TUI. type BubbleTeaModel struct { // holds the output currentState of the model currentState string @@ -56,23 +54,23 @@ type BubbleTeaModel struct { } // NewBubbleTeaModel initializes a new bubble tea Model which represents -// a stylized table to display instance types +// a stylized table to display instance types. func NewBubbleTeaModel(instanceTypes []*instancetypes.Details) BubbleTeaModel { return BubbleTeaModel{ currentState: stateTable, tableModel: *initTableModel(instanceTypes), - verboseModel: *initVerboseModel(instanceTypes), + verboseModel: *initVerboseModel(), sortingModel: *initSortingModel(instanceTypes), } } -// Init is used by bubble tea to initialize a bubble tea table +// Init is used by bubble tea to initialize a bubble tea table. func (m BubbleTeaModel) Init() tea.Cmd { return nil } // Update is used by bubble tea to update the state of the bubble -// tea model based on user input +// tea model based on user input. func (m BubbleTeaModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: @@ -163,7 +161,7 @@ func (m BubbleTeaModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea.WindowSizeMsg: // This is needed to handle a bug with bubble tea // where resizing causes misprints (https://github.com/Evertras/bubble-table/issues/121) - termenv.ClearScreen() + termenv.ClearScreen() //nolint:staticcheck // handle screen resizing m.tableModel = m.tableModel.resizeView(msg) @@ -185,7 +183,7 @@ func (m BubbleTeaModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, cmd } -// View is used by bubble tea to render the bubble tea model +// View is used by bubble tea to render the bubble tea model. func (m BubbleTeaModel) View() string { switch m.currentState { case stateTable: diff --git a/pkg/selector/outputs/bubbletea_internal_test.go b/pkg/selector/outputs/bubbletea_internal_test.go index e2f9f82..922cf6a 100644 --- a/pkg/selector/outputs/bubbletea_internal_test.go +++ b/pkg/selector/outputs/bubbletea_internal_test.go @@ -1,15 +1,14 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package outputs @@ -20,9 +19,10 @@ import ( "strings" "testing" + "github.com/evertras/bubble-table/table" + "github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes" h "github.com/aws/amazon-ec2-instance-selector/v3/pkg/test" - "github.com/evertras/bubble-table/table" ) const ( @@ -32,12 +32,12 @@ const ( // helpers // getInstanceTypeDetails unmarshalls the json file in the given testing folder -// and returns a list of instance type details +// and returns a list of instance type details. func getInstanceTypeDetails(t *testing.T, file string) []*instancetypes.Details { folder := "FilterVerbose" mockFilename := fmt.Sprintf("%s/%s/%s", mockFilesPath, folder, file) mockFile, err := os.ReadFile(mockFilename) - h.Assert(t, err == nil, "Error reading mock file "+string(mockFilename)) + h.Assert(t, err == nil, "Error reading mock file "+mockFilename) instanceTypes := []*instancetypes.Details{} err = json.Unmarshal(mockFile, &instanceTypes) @@ -45,7 +45,7 @@ func getInstanceTypeDetails(t *testing.T, file string) []*instancetypes.Details return instanceTypes } -// getRowsInstances reformats the given table rows into a list of instance type names +// getRowsInstances reformats the given table rows into a list of instance type names. func getRowsInstances(rows []table.Row) string { instances := []string{} diff --git a/pkg/selector/outputs/outputs.go b/pkg/selector/outputs/outputs.go index 4fe1da4..a460f5f 100644 --- a/pkg/selector/outputs/outputs.go +++ b/pkg/selector/outputs/outputs.go @@ -1,15 +1,14 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. // Package outputs provides types for implementing instance type output functions as well as prebuilt output functions. package outputs @@ -30,7 +29,7 @@ import ( const columnTag = "column" // wideColumnsData stores the data that should be displayed on each column -// of a wide output row +// of a wide output row. type wideColumnsData struct { instanceName string `column:"Instance Type"` vcpu int32 `column:"VCPUs"` @@ -48,7 +47,7 @@ type wideColumnsData struct { spotPrice string `column:"Spot Price/Hr"` } -// SimpleInstanceTypeOutput is an OutputFn which outputs a slice of instance type names +// SimpleInstanceTypeOutput is an OutputFn which outputs a slice of instance type names. func SimpleInstanceTypeOutput(instanceTypeInfoSlice []*instancetypes.Details) []string { instanceTypeStrings := []string{} for _, instanceTypeInfo := range instanceTypeInfoSlice { @@ -57,7 +56,7 @@ func SimpleInstanceTypeOutput(instanceTypeInfoSlice []*instancetypes.Details) [] return instanceTypeStrings } -// VerboseInstanceTypeOutput is an OutputFn which outputs a slice of instance type names +// VerboseInstanceTypeOutput is an OutputFn which outputs a slice of instance type names. func VerboseInstanceTypeOutput(instanceTypeInfoSlice []*instancetypes.Details) []string { output, err := json.MarshalIndent(instanceTypeInfoSlice, "", " ") if err != nil { @@ -70,7 +69,7 @@ func VerboseInstanceTypeOutput(instanceTypeInfoSlice []*instancetypes.Details) [ return []string{string(output)} } -// TableOutputShort is an OutputFn which returns a CLI table for easy reading +// TableOutputShort is an OutputFn which returns a CLI table for easy reading. func TableOutputShort(instanceTypeInfoSlice []*instancetypes.Details) []string { if len(instanceTypeInfoSlice) == 0 { return nil @@ -106,7 +105,7 @@ func TableOutputShort(instanceTypeInfoSlice []*instancetypes.Details) []string { return []string{buf.String()} } -// TableOutputWide is an OutputFn which returns a detailed CLI table for easy reading +// TableOutputWide is an OutputFn which returns a detailed CLI table for easy reading. func TableOutputWide(instanceTypeInfoSlice []*instancetypes.Details) []string { if len(instanceTypeInfoSlice) == 0 { return nil @@ -157,7 +156,7 @@ func TableOutputWide(instanceTypeInfoSlice []*instancetypes.Details) []string { return []string{buf.String()} } -// OneLineOutput is an output function which prints the instance type names on a single line separated by commas +// OneLineOutput is an output function which prints the instance type names on a single line separated by commas. func OneLineOutput(instanceTypeInfoSlice []*instancetypes.Details) []string { instanceTypeNames := []string{} for _, instanceType := range instanceTypeInfoSlice { @@ -196,7 +195,7 @@ func reverse(s string) string { } // getWideColumnsData returns the column data necessary for a wide output for each of -// the given instance types +// the given instance types. func getWideColumnsData(instanceTypes []*instancetypes.Details) []*wideColumnsData { columnsData := []*wideColumnsData{} @@ -254,7 +253,7 @@ func getWideColumnsData(instanceTypes []*instancetypes.Details) []*wideColumnsDa } // getUnderlyingValue returns the underlying value of the given -// reflect.Value type +// reflect.Value type. func getUnderlyingValue(value reflect.Value) interface{} { var val interface{} diff --git a/pkg/selector/outputs/outputs_test.go b/pkg/selector/outputs/outputs_test.go index e7b2a00..36ac4c2 100644 --- a/pkg/selector/outputs/outputs_test.go +++ b/pkg/selector/outputs/outputs_test.go @@ -1,15 +1,14 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package outputs_test @@ -20,10 +19,11 @@ import ( "strings" "testing" + "github.com/aws/aws-sdk-go-v2/service/ec2" + "github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes" "github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector/outputs" h "github.com/aws/amazon-ec2-instance-selector/v3/pkg/test" - "github.com/aws/aws-sdk-go-v2/service/ec2" ) const ( diff --git a/pkg/selector/outputs/sortingView.go b/pkg/selector/outputs/sortingView.go index f873a26..cca1a23 100644 --- a/pkg/selector/outputs/sortingView.go +++ b/pkg/selector/outputs/sortingView.go @@ -1,15 +1,14 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package outputs @@ -18,31 +17,32 @@ import ( "io" "strings" - "github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes" - "github.com/aws/amazon-ec2-instance-selector/v3/pkg/sorter" "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/list" "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + + "github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes" + "github.com/aws/amazon-ec2-instance-selector/v3/pkg/sorter" ) const ( - // formatting + // formatting. sortDirectionPadding = 2 sortingTitlePadding = 3 sortingFooterPadding = 2 - // controls + // controls. sortingListControls = "Controls: ↑/↓ - up/down • enter - select filter • tab - toggle direction • esc - return to table • q - quit" sortingTextControls = "Controls: ↑/↓ - up/down • tab - toggle direction • enter - enter json path" - // sort direction text + // sort direction text. ascendingText = "ASCENDING" descendingText = "DESCENDING" ) -// sortingModel holds the state for the sorting view +// sortingModel holds the state for the sorting view. type sortingModel struct { // list which holds the available shorting shorthands shorthandList list.Model @@ -55,32 +55,32 @@ type sortingModel struct { isDescending bool } -// format styles +// format styles. var ( - // list + // list. listTitleStyle = lipgloss.NewStyle().Bold(true).Underline(true) listItemStyle = lipgloss.NewStyle().PaddingLeft(4) selectedItemStyle = lipgloss.NewStyle().PaddingLeft(2).Foreground(lipgloss.Color("170")) - // text + // text. descendingStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#0096FF")) ascendingStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#DAF7A6")) sortDirectionStyle = lipgloss.NewStyle().Bold(true).Underline(true).PaddingLeft(2) ) -// implement Item interface for list +// implement Item interface for list. type item string func (i item) FilterValue() string { return "" } func (i item) Title() string { return string(i) } func (i item) Description() string { return "" } -// implement ItemDelegate for list +// implement ItemDelegate for list. type itemDelegate struct{} -func (d itemDelegate) Height() int { return 1 } -func (d itemDelegate) Spacing() int { return 0 } -func (d itemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil } +func (d itemDelegate) Height() int { return 1 } +func (d itemDelegate) Spacing() int { return 0 } +func (d itemDelegate) Update(_ tea.Msg, _ *list.Model) tea.Cmd { return nil } func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list.Item) { i, ok := listItem.(item) if !ok { @@ -99,11 +99,11 @@ func (d itemDelegate) Render(w io.Writer, m list.Model, index int, listItem list } } - fmt.Fprintf(w, fn(str)) + fmt.Fprint(w, fn(str)) } // initSortingModel initializes and returns a new tableModel based on the given -// instance type details +// instance type details. func initSortingModel(instanceTypes []*instancetypes.Details) *sortingModel { shorthandList := list.New(*createListItems(), itemDelegate{}, initialDimensionVal, initialDimensionVal) shorthandList.Title = "Select sorting filter:" @@ -126,7 +126,7 @@ func initSortingModel(instanceTypes []*instancetypes.Details) *sortingModel { } } -// createListKeyMap creates a KeyMap with the controls for the shorthand list +// createListKeyMap creates a KeyMap with the controls for the shorthand list. func createListKeyMap() list.KeyMap { return list.KeyMap{ CursorDown: key.NewBinding( @@ -138,7 +138,7 @@ func createListKeyMap() list.KeyMap { } } -// createListItems creates a list item for shorthand sorting flag +// createListItems creates a list item for shorthand sorting flag. func createListItems() *[]list.Item { shorthandFlags := []string{ sorter.GPUCountField, @@ -166,7 +166,7 @@ func createListItems() *[]list.Item { // resizeSortingView will change the dimensions of the sorting view // in order to accommodate the new window dimensions represented by -// the given tea.WindowSizeMsg +// the given tea.WindowSizeMsg. func (m sortingModel) resizeView(msg tea.WindowSizeMsg) sortingModel { shorthandList := &m.shorthandList shorthandList.SetWidth(msg.Width) @@ -189,7 +189,7 @@ func (m sortingModel) resizeView(msg tea.WindowSizeMsg) sortingModel { return m } -// update updates the state of the sortingModel +// update updates the state of the sortingModel. func (m sortingModel) update(msg tea.Msg) (sortingModel, tea.Cmd) { var cmd tea.Cmd var cmds []tea.Cmd @@ -227,7 +227,7 @@ func (m sortingModel) update(msg tea.Msg) (sortingModel, tea.Cmd) { return m, tea.Batch(cmds...) } -// view returns a string representing the sorting view +// view returns a string representing the sorting view. func (m sortingModel) view() string { outputStr := strings.Builder{} diff --git a/pkg/selector/outputs/tableView.go b/pkg/selector/outputs/tableView.go index 4c33725..015007b 100644 --- a/pkg/selector/outputs/tableView.go +++ b/pkg/selector/outputs/tableView.go @@ -1,15 +1,14 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package outputs @@ -18,21 +17,22 @@ import ( "reflect" "strings" - "github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes" - "github.com/aws/amazon-ec2-instance-selector/v3/pkg/sorter" "github.com/charmbracelet/bubbles/key" "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/evertras/bubble-table/table" + + "github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes" + "github.com/aws/amazon-ec2-instance-selector/v3/pkg/sorter" ) const ( - // table formatting + // table formatting. headerAndFooterPadding = 8 headerPadding = 2 - // controls + // controls. tableControls = "Controls: ↑/↓ - up/down • ←/→ - left/right • shift + ←/→ - pg up/down • e - expand • f - filter • t - trim toggle • space - select • s - sort • q - quit" ellipses = "..." @@ -60,30 +60,28 @@ type tableModel struct { canSelectRows bool } -var ( - customBorder = table.Border{ - Top: "─", - Left: "│", - Right: "│", - Bottom: "─", - - TopRight: "╮", - TopLeft: "╭", - BottomRight: "╯", - BottomLeft: "╰", - - TopJunction: "┬", - LeftJunction: "├", - RightJunction: "┤", - BottomJunction: "┴", - InnerJunction: "┼", - - InnerDivider: "│", - } -) +var customBorder = table.Border{ + Top: "─", + Left: "│", + Right: "│", + Bottom: "─", + + TopRight: "╮", + TopLeft: "╭", + BottomRight: "╯", + BottomLeft: "╰", + + TopJunction: "┬", + LeftJunction: "├", + RightJunction: "┤", + BottomJunction: "┴", + InnerJunction: "┼", + + InnerDivider: "│", +} // initTableModel initializes and returns a new tableModel based on the given -// instance type details +// instance type details. func initTableModel(instanceTypes []*instancetypes.Details) *tableModel { table := createTable(instanceTypes) @@ -97,7 +95,7 @@ func initTableModel(instanceTypes []*instancetypes.Details) *tableModel { } } -// createFilterTextInput creates and styles a text input for filtering +// createFilterTextInput creates and styles a text input for filtering. func createFilterTextInput() textinput.Model { filterTextInput := textinput.New() filterTextInput.Prompt = "Filter: " @@ -106,7 +104,7 @@ func createFilterTextInput() textinput.Model { return filterTextInput } -// createRows creates a row for each instance type in the passed in list +// createRows creates a row for each instance type in the passed in list. func createRows(columnsData []*wideColumnsData, instanceTypes []*instancetypes.Details) *[]table.Row { rows := []table.Row{} @@ -139,7 +137,7 @@ func createRows(columnsData []*wideColumnsData, instanceTypes []*instancetypes.D return &rows } -// maxColWidth finds the maximum width element in the given column +// maxColWidth finds the maximum width element in the given column. func maxColWidth(columnsData []*wideColumnsData, columnHeader string) int { // default max width is the width of the header itself with padding maxWidth := len(columnHeader) + headerPadding @@ -171,7 +169,7 @@ func maxColWidth(columnsData []*wideColumnsData, columnHeader string) int { } // createColumns creates columns based on the tags in the wideColumnsData -// struct +// struct. func createColumns(columnsData []*wideColumnsData) *[]table.Column { columns := []table.Column{} @@ -189,7 +187,7 @@ func createColumns(columnsData []*wideColumnsData) *[]table.Column { return &columns } -// createTableKeyMap creates a KeyMap with the controls for the table +// createTableKeyMap creates a KeyMap with the controls for the table. func createTableKeyMap() *table.KeyMap { keys := table.KeyMap{ RowDown: key.NewBinding( @@ -216,7 +214,7 @@ func createTableKeyMap() *table.KeyMap { } // createTable creates an intractable table which contains information about all of -// the given instance types +// the given instance types. func createTable(instanceTypes []*instancetypes.Details) table.Model { // calculate and fetch all column data from instance types columnsData := getWideColumnsData(instanceTypes) @@ -241,7 +239,7 @@ func createTable(instanceTypes []*instancetypes.Details) table.Model { } // resizeView will change the dimensions of the table in order to accommodate -// the new window dimensions represented by the given tea.WindowSizeMsg +// the new window dimensions represented by the given tea.WindowSizeMsg. func (m tableModel) resizeView(msg tea.WindowSizeMsg) tableModel { // handle width changes m.table = m.table.WithMaxTotalWidth(msg.Width) @@ -266,7 +264,7 @@ func (m tableModel) resizeView(msg tea.WindowSizeMsg) tableModel { return m } -// updateFooter updates the page and controls string in the table footer +// updateFooter updates the page and controls string in the table footer. func (m tableModel) updateFooter() tableModel { controlsStr := tableControls @@ -289,7 +287,7 @@ func (m tableModel) updateFooter() tableModel { return m } -// update updates the state of the tableModel +// update updates the state of the tableModel. func (m tableModel) update(msg tea.Msg) (tableModel, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: @@ -364,7 +362,7 @@ func (m tableModel) update(msg tea.Msg) (tableModel, tea.Cmd) { return m, cmd } -// view returns a string representing the table view +// view returns a string representing the table view. func (m tableModel) view() string { outputStr := strings.Builder{} @@ -379,7 +377,7 @@ func (m tableModel) view() string { return outputStr.String() } -// sortTable sorts the table based on the sorting direction and sorting filter +// sortTable sorts the table based on the sorting direction and sorting filter. func (m tableModel) sortTable(sortFilter string, sortDirection string) (tableModel, error) { instanceTypes, rowMap := m.getInstanceTypeFromRows() _ = rowMap @@ -408,7 +406,7 @@ func (m tableModel) sortTable(sortFilter string, sortDirection string) (tableMod } // getInstanceTypeFromRows goes through the rows of the table model and returns both a list of instance -// types and a mapping of instances to rows +// types and a mapping of instances to rows. func (m tableModel) getInstanceTypeFromRows() ([]*instancetypes.Details, map[string]table.Row) { instanceTypes := []*instancetypes.Details{} rowMap := make(map[string]table.Row) @@ -437,7 +435,7 @@ func (m tableModel) getInstanceTypeFromRows() ([]*instancetypes.Details, map[str return instanceTypes, rowMap } -// getUnfilteredRows gets the rows in the given table model without any filtering applied +// getUnfilteredRows gets the rows in the given table model without any filtering applied. func (m tableModel) getUnfilteredRows() []table.Row { m.table = m.table.Filtered(false) rows := m.table.GetVisibleRows() @@ -445,7 +443,7 @@ func (m tableModel) getUnfilteredRows() []table.Row { return rows } -// trim will trim the table to only the selected rows +// trim will trim the table to only the selected rows. func (m tableModel) trim() tableModel { // store current state of rows before trimming m.originalRows = m.getUnfilteredRows() @@ -461,7 +459,7 @@ func (m tableModel) trim() tableModel { return m } -// untrim will return the table to the original rows +// untrim will return the table to the original rows. func (m tableModel) untrim() tableModel { m.table = m.table.WithRows(m.originalRows) diff --git a/pkg/selector/outputs/verboseView.go b/pkg/selector/outputs/verboseView.go index 7201aaf..a2481b1 100644 --- a/pkg/selector/outputs/verboseView.go +++ b/pkg/selector/outputs/verboseView.go @@ -1,15 +1,14 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package outputs @@ -18,7 +17,6 @@ import ( "math" "strings" - "github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes" ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" "github.com/charmbracelet/bubbles/viewport" tea "github.com/charmbracelet/bubbletea" @@ -26,14 +24,14 @@ import ( ) const ( - // verbose view formatting + // verbose view formatting. outlinePadding = 8 - // controls + // controls. verboseControls = "Controls: ↑/↓ - up/down • esc - return to table • q - quit" ) -// verboseModel represents the current state of the verbose view +// verboseModel represents the current state of the verbose view. type verboseModel struct { // model for verbose output viewport viewport viewport.Model @@ -42,7 +40,7 @@ type verboseModel struct { focusedInstanceName ec2types.InstanceType } -// styling for viewport +// styling for viewport. var ( titleStyle = func() lipgloss.Style { b := lipgloss.RoundedBorder() @@ -53,13 +51,13 @@ var ( infoStyle = func() lipgloss.Style { b := lipgloss.RoundedBorder() b.Left = "┤" - return titleStyle.Copy().BorderStyle(b) + return titleStyle.BorderStyle(b) }() ) // initVerboseModel initializes and returns a new verboseModel based on the given -// instance type details -func initVerboseModel(instanceTypes []*instancetypes.Details) *verboseModel { +// instance type details. +func initVerboseModel() *verboseModel { viewportModel := viewport.New(initialDimensionVal, initialDimensionVal) viewportModel.MouseWheelEnabled = true @@ -69,7 +67,7 @@ func initVerboseModel(instanceTypes []*instancetypes.Details) *verboseModel { } // resizeView will change the dimensions of the verbose viewport in order to accommodate -// the new window dimensions represented by the given tea.WindowSizeMsg +// the new window dimensions represented by the given tea.WindowSizeMsg. func (m verboseModel) resizeView(msg tea.WindowSizeMsg) verboseModel { // handle width changes m.viewport.Width = msg.Width @@ -86,7 +84,7 @@ func (m verboseModel) resizeView(msg tea.WindowSizeMsg) verboseModel { return m } -// update updates the state of the verboseModel +// update updates the state of the verboseModel. func (m verboseModel) update(msg tea.Msg) (verboseModel, tea.Cmd) { var cmd tea.Cmd m.viewport, cmd = m.viewport.Update(msg) diff --git a/pkg/selector/selector.go b/pkg/selector/selector.go index 1032dfe..c7d933f 100644 --- a/pkg/selector/selector.go +++ b/pkg/selector/selector.go @@ -1,15 +1,14 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. // Package selector provides filtering logic for Amazon EC2 Instance Types based on declarative resource specfications. package selector @@ -26,21 +25,20 @@ import ( "sync" "time" - "github.com/aws/amazon-ec2-instance-selector/v3/pkg/ec2pricing" - "github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes" - "github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector/outputs" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/aws/middleware" "github.com/aws/aws-sdk-go-v2/service/ec2" ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" "go.uber.org/multierr" -) -var ( - // Version is overridden at compilation with the version based on the git tag - versionID = "dev" + "github.com/aws/amazon-ec2-instance-selector/v3/pkg/ec2pricing" + "github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes" + "github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector/outputs" ) +// Version is overridden at compilation with the version based on the git tag +var versionID = "dev" + const ( locationFilterKey = "location" zoneIDLocationType = ec2types.LocationTypeAvailabilityZoneId @@ -48,7 +46,7 @@ const ( regionNameLocationType = ec2types.LocationTypeRegion sdkName = "instance-selector" - // Filter Keys + // Filter Keys. cpuArchitecture = "cpuArchitecture" cpuManufacturer = "cpuManufacturer" @@ -101,12 +99,12 @@ const ( pricePerHour = "pricePerHour" ) -// New creates an instance of Selector provided an aws session +// New creates an instance of Selector provided an aws session. func New(ctx context.Context, cfg aws.Config) (*Selector, error) { return NewWithCache(ctx, cfg, 0, "") } -// NewWithCache creates an instance of Selector backed by an on-disk cache provided an aws session and cache configuration parameters +// NewWithCache creates an instance of Selector backed by an on-disk cache provided an aws session and cache configuration parameters. func NewWithCache(ctx context.Context, cfg aws.Config, ttl time.Duration, cacheDir string) (*Selector, error) { serviceRegistry := NewRegistry() serviceRegistry.RegisterAWSServices() @@ -118,10 +116,15 @@ func NewWithCache(ctx context.Context, cfg aws.Config, ttl time.Duration, cacheD return nil, err } + instanceTypeProvider, err := instancetypes.LoadFromOrNew(cacheDir, cfg.Region, ttl, ec2Client) + if err != nil { + return nil, fmt.Errorf("unable to initialize instance type provider: %w", err) + } + return &Selector{ EC2: ec2Client, EC2Pricing: pricingClient, - InstanceTypesProvider: instancetypes.LoadFromOrNew(cacheDir, cfg.Region, ttl, ec2Client), + InstanceTypesProvider: instanceTypeProvider, ServiceRegistry: serviceRegistry, Logger: log.New(io.Discard, "", 0), }, nil @@ -129,20 +132,20 @@ func NewWithCache(ctx context.Context, cfg aws.Config, ttl time.Duration, cacheD // SetLogger can be called to log more detailed logs about what selector is doing // including things like API timings -// If SetLogger is not called, no logs will be displayed +// If SetLogger is not called, no logs will be displayed. func (s *Selector) SetLogger(logger *log.Logger) { s.Logger = logger s.InstanceTypesProvider.SetLogger(logger) s.EC2Pricing.SetLogger(logger) } -// Save persists the selector cache data to disk if caching is configured +// Save persists the selector cache data to disk if caching is configured. func (s Selector) Save() error { return multierr.Append(s.EC2Pricing.Save(), s.InstanceTypesProvider.Save()) } // Filter accepts a Filters struct which is used to select the available instance types -// matching the criteria within Filters and returns a simple list of instance type strings +// matching the criteria within Filters and returns a simple list of instance type strings. func (s Selector) Filter(ctx context.Context, filters Filters) ([]string, error) { outputFn := InstanceTypesOutputFn(outputs.SimpleInstanceTypeOutput) output, _, err := s.FilterWithOutput(ctx, filters, outputFn) @@ -150,7 +153,7 @@ func (s Selector) Filter(ctx context.Context, filters Filters) ([]string, error) } // FilterVerbose accepts a Filters struct which is used to select the available instance types -// matching the criteria within Filters and returns a list instanceTypeInfo +// matching the criteria within Filters and returns a list instanceTypeInfo. func (s Selector) FilterVerbose(ctx context.Context, filters Filters) ([]*instancetypes.Details, error) { instanceTypeInfoSlice, err := s.rawFilter(ctx, filters) if err != nil { @@ -161,7 +164,7 @@ func (s Selector) FilterVerbose(ctx context.Context, filters Filters) ([]*instan } // FilterWithOutput accepts a Filters struct which is used to select the available instance types -// matching the criteria within Filters and returns a list of strings based on the custom outputFn +// matching the criteria within Filters and returns a list of strings based on the custom outputFn. func (s Selector) FilterWithOutput(ctx context.Context, filters Filters, outputFn InstanceTypesOutput) ([]string, int, error) { instanceTypeInfoSlice, err := s.rawFilter(ctx, filters) if err != nil { @@ -201,7 +204,7 @@ func (s Selector) AggregateFilterTransform(ctx context.Context, filters Filters) } // rawFilter accepts a Filters struct which is used to select the available instance types -// matching the criteria within Filters and returns the detailed specs of matching instance types +// matching the criteria within Filters and returns the detailed specs of matching instance types. func (s Selector) rawFilter(ctx context.Context, filters Filters) ([]*instancetypes.Details, error) { filters, err := s.AggregateFilterTransform(ctx, filters) if err != nil { @@ -366,7 +369,7 @@ func (s Selector) prepareFilter(ctx context.Context, filters Filters, instanceTy return &instanceTypeInfo, nil } -// sortInstanceTypeInfo will sort based on instance type info alpha-numerically +// sortInstanceTypeInfo will sort based on instance type info alpha-numerically. func sortInstanceTypeInfo(instanceTypeInfoSlice []*instancetypes.Details) []*instancetypes.Details { if len(instanceTypeInfoSlice) < 2 { return instanceTypeInfoSlice @@ -426,7 +429,7 @@ func (s Selector) executeFilters(ctx context.Context, filterToInstanceSpecMappin } // exec executes a specific filterPair (user value & instance spec) with a specific instance type -// If the filterPair matches, true is returned +// If the filterPair matches, true is returned. func exec(instanceType ec2types.InstanceType, filterName string, filter filterPair) (bool, error) { filterVal := filter.filterValue instanceSpec := filter.instanceSpec @@ -612,7 +615,7 @@ func exec(instanceType ec2types.InstanceType, filterName string, filter filterPa // RetrieveInstanceTypesSupportedInLocations returns a map of instance type -> AZ or Region for all instance types supported in the intersected locations passed in // The location can be a zone-id (ie. use1-az1), a zone-name (us-east-1a), or a region name (us-east-1). -// Note that zone names are not necessarily the same across accounts +// Note that zone names are not necessarily the same across accounts. func (s Selector) RetrieveInstanceTypesSupportedInLocations(ctx context.Context, locations []string) (map[ec2types.InstanceType]string, error) { if len(locations) == 0 { return nil, nil diff --git a/pkg/selector/selector_test.go b/pkg/selector/selector_test.go index 16fdd83..8e0ed86 100644 --- a/pkg/selector/selector_test.go +++ b/pkg/selector/selector_test.go @@ -1,15 +1,14 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package selector_test @@ -24,15 +23,16 @@ import ( "strconv" "testing" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/ec2" + ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" + "github.com/aws/amazon-ec2-instance-selector/v3/pkg/awsapi" "github.com/aws/amazon-ec2-instance-selector/v3/pkg/bytequantity" "github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes" "github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector" h "github.com/aws/amazon-ec2-instance-selector/v3/pkg/test" - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/config" - "github.com/aws/aws-sdk-go-v2/service/ec2" - ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" ) const ( @@ -42,7 +42,7 @@ const ( mockFilesPath = "../../test/static" ) -// Mocking helpers +// Mocking helpers. type mockedEC2 struct { awsapi.SelectorInterface DescribeInstanceTypesResp ec2.DescribeInstanceTypesOutput @@ -137,7 +137,7 @@ func getSelector(ec2Mock mockedEC2) selector.Selector { return selector.Selector{ EC2: ec2Mock, EC2Pricing: &ec2PricingMock{}, - InstanceTypesProvider: instancetypes.NewProvider("", "us-east-1", 0, ec2Mock), + InstanceTypesProvider: instancetypes.NewProvider("us-east-1", ec2Mock), } } diff --git a/pkg/selector/services.go b/pkg/selector/services.go index c9d460e..4a1cf0b 100644 --- a/pkg/selector/services.go +++ b/pkg/selector/services.go @@ -1,15 +1,14 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package selector @@ -20,33 +19,33 @@ import ( "dario.cat/mergo" ) -// Service is used to write custom service filter transforms +// Service is used to write custom service filter transforms. type Service interface { Filters(version string) (Filters, error) } -// ServiceFiltersFn is the func type definition for the Service interface +// ServiceFiltersFn is the func type definition for the Service interface. type ServiceFiltersFn func(version string) (Filters, error) // Filters implements the Service interface on ServiceFiltersFn -// This allows any ServiceFiltersFn to be passed into funcs accepting the Service interface +// This allows any ServiceFiltersFn to be passed into funcs accepting the Service interface. func (fn ServiceFiltersFn) Filters(version string) (Filters, error) { return fn(version) } -// ServiceRegistry is used to register service filter transforms +// ServiceRegistry is used to register service filter transforms. type ServiceRegistry struct { services map[string]*Service } -// NewRegistry creates a new instance of a ServiceRegistry +// NewRegistry creates a new instance of a ServiceRegistry. func NewRegistry() ServiceRegistry { return ServiceRegistry{ services: make(map[string]*Service), } } -// Register takes a service name and Service implementation that will be executed on an ExecuteTransforms call +// Register takes a service name and Service implementation that will be executed on an ExecuteTransforms call. func (sr *ServiceRegistry) Register(name string, service Service) { if sr.services == nil { sr.services = make(map[string]*Service) @@ -57,13 +56,13 @@ func (sr *ServiceRegistry) Register(name string, service Service) { sr.services[name] = &service } -// RegisterAWSServices registers the built-in AWS service filter transforms +// RegisterAWSServices registers the built-in AWS service filter transforms. func (sr *ServiceRegistry) RegisterAWSServices() { sr.Register("emr", &EMR{}) } // ExecuteTransforms will execute the ServiceRegistry's registered service filter transforms -// Filters.Service will be parsed as - and passed to Service.Filters +// Filters.Service will be parsed as - and passed to Service.Filters. func (sr *ServiceRegistry) ExecuteTransforms(filters Filters) (Filters, error) { if filters.Service == nil || *filters.Service == "" || *filters.Service == "eks" { return filters, nil diff --git a/pkg/selector/services_test.go b/pkg/selector/services_test.go index cd14af9..c336954 100644 --- a/pkg/selector/services_test.go +++ b/pkg/selector/services_test.go @@ -1,24 +1,24 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package selector_test import ( "testing" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector" h "github.com/aws/amazon-ec2-instance-selector/v3/pkg/test" - "github.com/aws/aws-sdk-go-v2/aws" ) // Tests diff --git a/pkg/selector/types.go b/pkg/selector/types.go index 23a017b..9c76dd7 100644 --- a/pkg/selector/types.go +++ b/pkg/selector/types.go @@ -1,15 +1,14 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package selector @@ -18,29 +17,29 @@ import ( "log" "regexp" - "github.com/aws/amazon-ec2-instance-selector/v3/pkg/awsapi" ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" + "github.com/aws/amazon-ec2-instance-selector/v3/pkg/awsapi" "github.com/aws/amazon-ec2-instance-selector/v3/pkg/bytequantity" "github.com/aws/amazon-ec2-instance-selector/v3/pkg/ec2pricing" "github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes" ) -// InstanceTypesOutput can be implemented to provide custom output to instance type results +// InstanceTypesOutput can be implemented to provide custom output to instance type results. type InstanceTypesOutput interface { Output([]*instancetypes.Details) []string } -// InstanceTypesOutputFn is the func type definition for InstanceTypesOuput +// InstanceTypesOutputFn is the func type definition for InstanceTypesOuput. type InstanceTypesOutputFn func([]*instancetypes.Details) []string // Output implements InstanceTypesOutput interface on InstanceTypesOutputFn -// This allows any InstanceTypesOutputFn to be passed into funcs accepting InstanceTypesOutput interface +// This allows any InstanceTypesOutputFn to be passed into funcs accepting InstanceTypesOutput interface. func (fn InstanceTypesOutputFn) Output(instanceTypes []*instancetypes.Details) []string { return fn(instanceTypes) } -// Selector is used to filter instance type resource specs +// Selector is used to filter instance type resource specs. type Selector struct { EC2 awsapi.SelectorInterface EC2Pricing ec2pricing.EC2PricingIface @@ -50,41 +49,41 @@ type Selector struct { } // IntRangeFilter holds an upper and lower bound int -// The lower and upper bound are used to range filter resource specs +// The lower and upper bound are used to range filter resource specs. type IntRangeFilter struct { UpperBound int LowerBound int } // Int32RangeFilter holds an upper and lower bound int -// The lower and upper bound are used to range filter resource specs +// The lower and upper bound are used to range filter resource specs. type Int32RangeFilter struct { UpperBound int32 LowerBound int32 } // Uint64RangeFilter holds an upper and lower bound uint64 -// The lower and upper bound are used to range filter resource specs +// The lower and upper bound are used to range filter resource specs. type Uint64RangeFilter struct { UpperBound uint64 LowerBound uint64 } // ByteQuantityRangeFilter holds an upper and lower bound byte quantity -// The lower and upper bound are used to range filter resource specs +// The lower and upper bound are used to range filter resource specs. type ByteQuantityRangeFilter struct { UpperBound bytequantity.ByteQuantity LowerBound bytequantity.ByteQuantity } // Float64RangeFilter holds an upper and lower bound float64 -// The lower and upper bound are used to range filter resource specs +// The lower and upper bound are used to range filter resource specs. type Float64RangeFilter struct { UpperBound float64 LowerBound float64 } -// filterPair holds a tuple of the passed in filter value and the instance resource spec value +// filterPair holds a tuple of the passed in filter value and the instance resource spec value. type filterPair struct { filterValue interface{} instanceSpec interface{} @@ -98,7 +97,7 @@ func getRegexpString(r *regexp.Regexp) *string { return &rStr } -// MarshalIndent is used to return a pretty-print json representation of a Filters struct +// MarshalIndent is used to return a pretty-print json representation of a Filters struct. func (f *Filters) MarshalIndent(prefix, indent string) ([]byte, error) { type Alias Filters return json.MarshalIndent(&struct { @@ -112,7 +111,7 @@ func (f *Filters) MarshalIndent(prefix, indent string) ([]byte, error) { }, prefix, indent) } -// Filters is used to group instance type resource attributes for filtering +// Filters is used to group instance type resource attributes for filtering. type Filters struct { // AvailabilityZones is the AWS Availability Zones where instances will be provisioned. // Instance type capacity can vary between availability zones. @@ -285,7 +284,7 @@ type Filters struct { type CPUManufacturer string -// Enum values for CPUManufacturer +// Enum values for CPUManufacturer. const ( CPUManufacturerAWS CPUManufacturer = "aws" CPUManufacturerAMD CPUManufacturer = "amd" @@ -303,12 +302,12 @@ func (CPUManufacturer) Values() []CPUManufacturer { } } -// ArchitectureTypeAMD64 is a legacy type we support for b/c that isn't in the API +// ArchitectureTypeAMD64 is a legacy type we support for b/c that isn't in the API. const ( ArchitectureTypeAMD64 ec2types.ArchitectureType = "amd64" ) -// ArchitectureTypeAMD64 is a legacy type we support for b/c that isn't in the API +// ArchitectureTypeAMD64 is a legacy type we support for b/c that isn't in the API. const ( VirtualizationTypePv ec2types.VirtualizationType = "pv" ) diff --git a/pkg/selector/types_test.go b/pkg/selector/types_test.go index c61bff8..ce22252 100644 --- a/pkg/selector/types_test.go +++ b/pkg/selector/types_test.go @@ -1,15 +1,14 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package selector_test @@ -18,9 +17,10 @@ import ( "strings" "testing" + ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" + "github.com/aws/amazon-ec2-instance-selector/v3/pkg/selector" h "github.com/aws/amazon-ec2-instance-selector/v3/pkg/test" - ec2types "github.com/aws/aws-sdk-go-v2/service/ec2/types" ) // Tests @@ -40,7 +40,6 @@ func TestMarshalIndent(t *testing.T) { h.Ok(t, err) h.Assert(t, strings.Contains(outStr, "AllowList") && strings.Contains(outStr, allowRegex), "Does not include AllowList regex string") h.Assert(t, strings.Contains(outStr, "DenyList") && strings.Contains(outStr, denyRegex), "Does not include DenyList regex string") - } func TestMarshalIndent_nil(t *testing.T) { @@ -55,5 +54,4 @@ func TestMarshalIndent_nil(t *testing.T) { h.Ok(t, err) h.Assert(t, strings.Contains(outStr, "AllowList") && strings.Contains(outStr, "null"), "Does not include AllowList null entry") h.Assert(t, strings.Contains(outStr, "DenyList") && strings.Contains(outStr, denyRegex), "Does not include DenyList regex string") - } diff --git a/pkg/sorter/sorter.go b/pkg/sorter/sorter.go index 418fc19..1bb2fed 100644 --- a/pkg/sorter/sorter.go +++ b/pkg/sorter/sorter.go @@ -1,15 +1,14 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package sorter @@ -20,12 +19,13 @@ import ( "sort" "strings" - "github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes" "github.com/oliveagle/jsonpath" + + "github.com/aws/amazon-ec2-instance-selector/v3/pkg/instancetypes" ) const ( - // Sort direction + // Sort direction. SortAscending = "ascending" SortAsc = "asc" @@ -38,7 +38,7 @@ const ( GPUCountField = "gpus" InferenceAcceleratorsField = "inference-accelerators" - // shorthand flags + // shorthand flags. VCPUs = "vcpus" Memory = "memory" @@ -51,7 +51,7 @@ const ( EBSOptimizedBaselineThroughput = "ebs-optimized-baseline-throughput" EBSOptimizedBaselineIOPS = "ebs-optimized-baseline-iops" - // JSON field paths for shorthand flags + // JSON field paths for shorthand flags. instanceNamePath = ".InstanceType" vcpuPath = ".VCpuInfo.DefaultVCpus" @@ -67,14 +67,14 @@ const ( ) // sorterNode represents a sortable instance type which holds the value -// to sort by instance sort +// to sort by instance sort. type sorterNode struct { instanceType *instancetypes.Details fieldValue reflect.Value } // sorter is used to sort instance types based on a sorting field -// and direction +// and direction. type sorter struct { sorters []*sorterNode sortField string @@ -170,7 +170,7 @@ func formatSortField(sortField string) string { } // newSorterNode creates a new sorterNode object which represents the given instance type -// and can be used in sorting of instance types based on the given sortField +// and can be used in sorting of instance types based on the given sortField. func newSorterNode(instanceType *instancetypes.Details, sortField string) (*sorterNode, error) { // some important fields (such as gpu count) can not be accessed directly in the instancetypes.Details // struct, so we have special hard-coded flags to handle such cases @@ -223,7 +223,7 @@ func newSorterNode(instanceType *instancetypes.Details, sortField string) (*sort } // sort the instance types in the Sorter based on the Sorter's sort field and -// direction +// direction. func (s *sorter) sort() error { if len(s.sorters) <= 1 { return nil @@ -247,7 +247,7 @@ func (s *sorter) sort() error { } // isLess determines whether the first value (valI) is less than the -// second value (valJ) or not +// second value (valJ) or not. func isLess(valI, valJ reflect.Value, isDescending bool) (bool, error) { switch valI.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: @@ -334,7 +334,7 @@ func isLess(valI, valJ reflect.Value, isDescending bool) (bool, error) { } } -// instanceTypes returns the list of instance types held in the Sorter +// instanceTypes returns the list of instance types held in the Sorter. func (s *sorter) instanceTypes() []*instancetypes.Details { instanceTypes := []*instancetypes.Details{} @@ -347,7 +347,7 @@ func (s *sorter) instanceTypes() []*instancetypes.Details { // helper functions for special sorting fields -// getTotalGpusCount calculates the number of gpus in the given instance type +// getTotalGpusCount calculates the number of gpus in the given instance type. func getTotalGpusCount(instanceType *instancetypes.Details) *int32 { gpusInfo := instanceType.GpuInfo @@ -364,7 +364,7 @@ func getTotalGpusCount(instanceType *instancetypes.Details) *int32 { } // getTotalAcceleratorsCount calculates the total number of inference accelerators -// in the given instance type +// in the given instance type. func getTotalAcceleratorsCount(instanceType *instancetypes.Details) *int32 { acceleratorInfo := instanceType.InferenceAcceleratorInfo diff --git a/pkg/sorter/sorter_test.go b/pkg/sorter/sorter_test.go index 25e8cfa..24a0d29 100644 --- a/pkg/sorter/sorter_test.go +++ b/pkg/sorter/sorter_test.go @@ -1,15 +1,14 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package sorter_test @@ -34,7 +33,7 @@ const ( // Helpers // getInstanceTypeDetails unmarshalls the json file in the given testing folder -// and returns a list of instance type details +// and returns a list of instance type details. func getInstanceTypeDetails(t *testing.T, file string) []*instancetypes.Details { folder := "FilterVerbose" mockFilename := fmt.Sprintf("%s/%s/%s", mockFilesPath, folder, file) diff --git a/pkg/test/helpers.go b/pkg/test/helpers.go index f5fd95e..facb996 100644 --- a/pkg/test/helpers.go +++ b/pkg/test/helpers.go @@ -1,15 +1,14 @@ -// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// Licensed under the Apache License, Version 2.0 (the "License"). You may -// not use this file except in compliance with the License. A copy of the -// License is located at +// http://www.apache.org/licenses/LICENSE-2.0 // -// http://aws.amazon.com/apache2.0/ -// -// or in the "license" file accompanying this file. This file is distributed -// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either -// express or implied. See the License for the specific language governing -// permissions and limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package test @@ -55,5 +54,4 @@ func Equals(tb testing.TB, exp, act interface{}) { fmt.Printf("\033[31m%s:%d:\n\n\texp: %#v\n\n\tgot: %#v\033[39m\n\n", filepath.Base(file), line, exp, act) tb.FailNow() } - } diff --git a/test/readme-test/readme-codeblocks.go b/test/readme-test/readme-codeblocks.go index 0fadb5f..9a4b0c2 100644 --- a/test/readme-test/readme-codeblocks.go +++ b/test/readme-test/readme-codeblocks.go @@ -1,3 +1,17 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package main import ( @@ -10,7 +24,7 @@ import ( "strings" ) -// CodeBlock models the rundoc codeblock output +// CodeBlock models the rundoc codeblock output. type CodeBlock struct { Code string `json:"code"` Interpreter string `json:"interpreter"` @@ -18,7 +32,7 @@ type CodeBlock struct { Tags []string `json:"tags"` } -// RunDoc is the outer model for rundocs output +// RunDoc is the outer model for rundocs output. type RunDoc struct { CodeBlocks []CodeBlock `json:"code_blocks"` }