feat(train): add new command to interact with aws and train models

This commit is contained in:
2021-10-17 19:15:44 +02:00
parent 5436dfebc2
commit 538cea18f2
1064 changed files with 282251 additions and 89305 deletions

View File

@ -0,0 +1,39 @@
# v1.5.1 (2021-09-17)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.5.0 (2021-08-27)
* **Feature**: Updated `github.com/aws/smithy-go` to latest version
* **Dependency Update**: Updated to the latest SDK module versions
# v1.4.1 (2021-08-19)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.4.0 (2021-08-04)
* **Feature**: adds error handling for defered close calls
* **Dependency Update**: Updated `github.com/aws/smithy-go` to latest version.
* **Dependency Update**: Updated to the latest SDK module versions
# v1.3.0 (2021-07-15)
* **Feature**: Support has been added for EC2 IPv6-enabled Instance Metadata Service Endpoints.
* **Dependency Update**: Updated `github.com/aws/smithy-go` to latest version
* **Dependency Update**: Updated to the latest SDK module versions
# v1.2.0 (2021-06-25)
* **Feature**: Updated `github.com/aws/smithy-go` to latest version
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.1 (2021-05-20)
* **Dependency Update**: Updated to the latest SDK module versions
# v1.1.0 (2021-05-14)
* **Feature**: Constant has been added to modules to enable runtime version inspection for reporting.
* **Dependency Update**: Updated to the latest SDK module versions

View File

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

View File

@ -0,0 +1,318 @@
package imds
import (
"context"
"fmt"
"net"
"net/http"
"os"
"strings"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/aws/retry"
awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
internalconfig "github.com/aws/aws-sdk-go-v2/feature/ec2/imds/internal/config"
"github.com/aws/smithy-go"
"github.com/aws/smithy-go/logging"
"github.com/aws/smithy-go/middleware"
smithyhttp "github.com/aws/smithy-go/transport/http"
)
// ServiceID provides the unique name of this API client
const ServiceID = "ec2imds"
// Client provides the API client for interacting with the Amazon EC2 Instance
// Metadata Service API.
type Client struct {
options Options
}
// ClientEnableState provides an enumeration if the client is enabled,
// disabled, or default behavior.
type ClientEnableState = internalconfig.ClientEnableState
// Enumeration values for ClientEnableState
const (
ClientDefaultEnableState ClientEnableState = internalconfig.ClientDefaultEnableState // default behavior
ClientDisabled ClientEnableState = internalconfig.ClientDisabled // client disabled
ClientEnabled ClientEnableState = internalconfig.ClientEnabled // client enabled
)
// EndpointModeState is an enum configuration variable describing the client endpoint mode.
// Not configurable directly, but used when using the NewFromConfig.
type EndpointModeState = internalconfig.EndpointModeState
// Enumeration values for EndpointModeState
const (
EndpointModeStateUnset EndpointModeState = internalconfig.EndpointModeStateUnset
EndpointModeStateIPv4 EndpointModeState = internalconfig.EndpointModeStateIPv4
EndpointModeStateIPv6 EndpointModeState = internalconfig.EndpointModeStateIPv6
)
const (
disableClientEnvVar = "AWS_EC2_METADATA_DISABLED"
// Client endpoint options
endpointEnvVar = "AWS_EC2_METADATA_SERVICE_ENDPOINT"
defaultIPv4Endpoint = "http://169.254.169.254"
defaultIPv6Endpoint = "http://[fd00:ec2::254]"
)
// New returns an initialized Client based on the functional options. Provide
// additional functional options to further configure the behavior of the client,
// such as changing the client's endpoint or adding custom middleware behavior.
func New(options Options, optFns ...func(*Options)) *Client {
options = options.Copy()
for _, fn := range optFns {
fn(&options)
}
options.HTTPClient = resolveHTTPClient(options.HTTPClient)
if options.Retryer == nil {
options.Retryer = retry.NewStandard()
}
options.Retryer = retry.AddWithMaxBackoffDelay(options.Retryer, 1*time.Second)
if options.ClientEnableState == ClientDefaultEnableState {
if v := os.Getenv(disableClientEnvVar); strings.EqualFold(v, "true") {
options.ClientEnableState = ClientDisabled
}
}
if len(options.Endpoint) == 0 {
if v := os.Getenv(endpointEnvVar); len(v) != 0 {
options.Endpoint = v
}
}
client := &Client{
options: options,
}
if client.options.tokenProvider == nil && !client.options.disableAPIToken {
client.options.tokenProvider = newTokenProvider(client, defaultTokenTTL)
}
return client
}
// NewFromConfig returns an initialized Client based the AWS SDK config, and
// functional options. Provide additional functional options to further
// configure the behavior of the client, such as changing the client's endpoint
// or adding custom middleware behavior.
func NewFromConfig(cfg aws.Config, optFns ...func(*Options)) *Client {
opts := Options{
APIOptions: append([]func(*middleware.Stack) error{}, cfg.APIOptions...),
HTTPClient: cfg.HTTPClient,
}
if cfg.Retryer != nil {
opts.Retryer = cfg.Retryer()
}
resolveClientEnableState(cfg, &opts)
resolveEndpointConfig(cfg, &opts)
resolveEndpointModeConfig(cfg, &opts)
return New(opts, optFns...)
}
// Options provides the fields for configuring the API client's behavior.
type Options struct {
// Set of options to modify how an operation is invoked. These apply to all
// operations invoked for this client. Use functional options on operation
// call to modify this list for per operation behavior.
APIOptions []func(*middleware.Stack) error
// The endpoint the client will use to retrieve EC2 instance metadata.
//
// Specifies the EC2 Instance Metadata Service endpoint to use. If specified it overrides EndpointMode.
//
// If unset, and the environment variable AWS_EC2_METADATA_SERVICE_ENDPOINT
// has a value the client will use the value of the environment variable as
// the endpoint for operation calls.
//
// AWS_EC2_METADATA_SERVICE_ENDPOINT=http://[::1]
Endpoint string
// The endpoint selection mode the client will use if no explicit endpoint is provided using the Endpoint field.
//
// Setting EndpointMode to EndpointModeStateIPv4 will configure the client to use the default EC2 IPv4 endpoint.
// Setting EndpointMode to EndpointModeStateIPv6 will configure the client to use the default EC2 IPv6 endpoint.
//
// By default if EndpointMode is not set (EndpointModeStateUnset) than the default endpoint selection mode EndpointModeStateIPv4.
EndpointMode EndpointModeState
// The HTTP client to invoke API calls with. Defaults to client's default
// HTTP implementation if nil.
HTTPClient HTTPClient
// Retryer guides how HTTP requests should be retried in case of recoverable
// failures. When nil the API client will use a default retryer.
Retryer aws.Retryer
// Changes if the EC2 Instance Metadata client is enabled or not. Client
// will default to enabled if not set to ClientDisabled. When the client is
// disabled it will return an error for all operation calls.
//
// If ClientEnableState value is ClientDefaultEnableState (default value),
// and the environment variable "AWS_EC2_METADATA_DISABLED" is set to
// "true", the client will be disabled.
//
// AWS_EC2_METADATA_DISABLED=true
ClientEnableState ClientEnableState
// Configures the events that will be sent to the configured logger.
ClientLogMode aws.ClientLogMode
// The logger writer interface to write logging messages to.
Logger logging.Logger
// provides the caching of API tokens used for operation calls. If unset,
// the API token will not be retrieved for the operation.
tokenProvider *tokenProvider
// option to disable the API token provider for testing.
disableAPIToken bool
}
// HTTPClient provides the interface for a client making HTTP requests with the
// API.
type HTTPClient interface {
Do(*http.Request) (*http.Response, error)
}
// Copy creates a copy of the API options.
func (o Options) Copy() Options {
to := o
to.APIOptions = append([]func(*middleware.Stack) error{}, o.APIOptions...)
return to
}
// WithAPIOptions wraps the API middleware functions, as a functional option
// for the API Client Options. Use this helper to add additional functional
// options to the API client, or operation calls.
func WithAPIOptions(optFns ...func(*middleware.Stack) error) func(*Options) {
return func(o *Options) {
o.APIOptions = append(o.APIOptions, optFns...)
}
}
func (c *Client) invokeOperation(
ctx context.Context, opID string, params interface{}, optFns []func(*Options),
stackFns ...func(*middleware.Stack, Options) error,
) (
result interface{}, metadata middleware.Metadata, err error,
) {
stack := middleware.NewStack(opID, smithyhttp.NewStackRequest)
options := c.options.Copy()
for _, fn := range optFns {
fn(&options)
}
if options.ClientEnableState == ClientDisabled {
return nil, metadata, &smithy.OperationError{
ServiceID: ServiceID,
OperationName: opID,
Err: fmt.Errorf(
"access disabled to EC2 IMDS via client option, or %q environment variable",
disableClientEnvVar),
}
}
for _, fn := range stackFns {
if err := fn(stack, options); err != nil {
return nil, metadata, err
}
}
for _, fn := range options.APIOptions {
if err := fn(stack); err != nil {
return nil, metadata, err
}
}
handler := middleware.DecorateHandler(smithyhttp.NewClientHandler(options.HTTPClient), stack)
result, metadata, err = handler.Handle(ctx, params)
if err != nil {
return nil, metadata, &smithy.OperationError{
ServiceID: ServiceID,
OperationName: opID,
Err: err,
}
}
return result, metadata, err
}
const (
// HTTP client constants
defaultDialerTimeout = 250 * time.Millisecond
defaultResponseHeaderTimeout = 500 * time.Millisecond
)
func resolveHTTPClient(client HTTPClient) HTTPClient {
if client == nil {
client = awshttp.NewBuildableClient()
}
if c, ok := client.(*awshttp.BuildableClient); ok {
client = c.
WithDialerOptions(func(d *net.Dialer) {
// Use a custom Dial timeout for the EC2 Metadata service to account
// for the possibility the application might not be running in an
// environment with the service present. The client should fail fast in
// this case.
d.Timeout = defaultDialerTimeout
}).
WithTransportOptions(func(tr *http.Transport) {
// Use a custom Transport timeout for the EC2 Metadata service to
// account for the possibility that the application might be running in
// a container, and EC2Metadata service drops the connection after a
// single IP Hop. The client should fail fast in this case.
tr.ResponseHeaderTimeout = defaultResponseHeaderTimeout
})
}
return client
}
func resolveClientEnableState(cfg aws.Config, options *Options) error {
if options.ClientEnableState != ClientDefaultEnableState {
return nil
}
value, found, err := internalconfig.ResolveClientEnableState(cfg.ConfigSources)
if err != nil || !found {
return err
}
options.ClientEnableState = value
return nil
}
func resolveEndpointModeConfig(cfg aws.Config, options *Options) error {
if options.EndpointMode != EndpointModeStateUnset {
return nil
}
value, found, err := internalconfig.ResolveEndpointModeConfig(cfg.ConfigSources)
if err != nil || !found {
return err
}
options.EndpointMode = value
return nil
}
func resolveEndpointConfig(cfg aws.Config, options *Options) error {
if len(options.Endpoint) != 0 {
return nil
}
value, found, err := internalconfig.ResolveEndpointConfig(cfg.ConfigSources)
if err != nil || !found {
return err
}
options.Endpoint = value
return nil
}

View File

@ -0,0 +1,76 @@
package imds
import (
"context"
"fmt"
"io"
"github.com/aws/smithy-go/middleware"
smithyhttp "github.com/aws/smithy-go/transport/http"
)
const getDynamicDataPath = "/latest/dynamic"
// GetDynamicData uses the path provided to request information from the EC2
// instance metadata service for dynamic data. The content will be returned
// as a string, or error if the request failed.
func (c *Client) GetDynamicData(ctx context.Context, params *GetDynamicDataInput, optFns ...func(*Options)) (*GetDynamicDataOutput, error) {
if params == nil {
params = &GetDynamicDataInput{}
}
result, metadata, err := c.invokeOperation(ctx, "GetDynamicData", params, optFns,
addGetDynamicDataMiddleware,
)
if err != nil {
return nil, err
}
out := result.(*GetDynamicDataOutput)
out.ResultMetadata = metadata
return out, nil
}
// GetDynamicDataInput provides the input parameters for the GetDynamicData
// operation.
type GetDynamicDataInput struct {
// The relative dynamic data path to retrieve. Can be empty string to
// retrieve a response containing a new line separated list of dynamic data
// resources available.
//
// Must not include the dynamic data base path.
//
// May include leading slash. If Path includes trailing slash the trailing
// slash will be included in the request for the resource.
Path string
}
// GetDynamicDataOutput provides the output parameters for the GetDynamicData
// operation.
type GetDynamicDataOutput struct {
Content io.ReadCloser
ResultMetadata middleware.Metadata
}
func addGetDynamicDataMiddleware(stack *middleware.Stack, options Options) error {
return addAPIRequestMiddleware(stack,
options,
buildGetDynamicDataPath,
buildGetDynamicDataOutput)
}
func buildGetDynamicDataPath(params interface{}) (string, error) {
p, ok := params.(*GetDynamicDataInput)
if !ok {
return "", fmt.Errorf("unknown parameter type %T", params)
}
return appendURIPath(getDynamicDataPath, p.Path), nil
}
func buildGetDynamicDataOutput(resp *smithyhttp.Response) (interface{}, error) {
return &GetDynamicDataOutput{
Content: resp.Body,
}, nil
}

View File

@ -0,0 +1,102 @@
package imds
import (
"context"
"encoding/json"
"fmt"
"io"
"strings"
"time"
"github.com/aws/smithy-go"
smithyio "github.com/aws/smithy-go/io"
"github.com/aws/smithy-go/middleware"
smithyhttp "github.com/aws/smithy-go/transport/http"
)
const getIAMInfoPath = getMetadataPath + "/iam/info"
// GetIAMInfo retrieves an identity document describing an
// instance. Error is returned if the request fails or is unable to parse
// the response.
func (c *Client) GetIAMInfo(
ctx context.Context, params *GetIAMInfoInput, optFns ...func(*Options),
) (
*GetIAMInfoOutput, error,
) {
if params == nil {
params = &GetIAMInfoInput{}
}
result, metadata, err := c.invokeOperation(ctx, "GetIAMInfo", params, optFns,
addGetIAMInfoMiddleware,
)
if err != nil {
return nil, err
}
out := result.(*GetIAMInfoOutput)
out.ResultMetadata = metadata
return out, nil
}
// GetIAMInfoInput provides the input parameters for GetIAMInfo operation.
type GetIAMInfoInput struct{}
// GetIAMInfoOutput provides the output parameters for GetIAMInfo operation.
type GetIAMInfoOutput struct {
IAMInfo
ResultMetadata middleware.Metadata
}
func addGetIAMInfoMiddleware(stack *middleware.Stack, options Options) error {
return addAPIRequestMiddleware(stack,
options,
buildGetIAMInfoPath,
buildGetIAMInfoOutput,
)
}
func buildGetIAMInfoPath(params interface{}) (string, error) {
return getIAMInfoPath, nil
}
func buildGetIAMInfoOutput(resp *smithyhttp.Response) (v interface{}, err error) {
defer func() {
closeErr := resp.Body.Close()
if err == nil {
err = closeErr
} else if closeErr != nil {
err = fmt.Errorf("response body close error: %v, original error: %w", closeErr, err)
}
}()
var buff [1024]byte
ringBuffer := smithyio.NewRingBuffer(buff[:])
body := io.TeeReader(resp.Body, ringBuffer)
imdsResult := &GetIAMInfoOutput{}
if err = json.NewDecoder(body).Decode(&imdsResult.IAMInfo); err != nil {
return nil, &smithy.DeserializationError{
Err: fmt.Errorf("failed to decode instance identity document, %w", err),
Snapshot: ringBuffer.Bytes(),
}
}
// Any code other success is an error
if !strings.EqualFold(imdsResult.Code, "success") {
return nil, fmt.Errorf("failed to get EC2 IMDS IAM info, %s",
imdsResult.Code)
}
return imdsResult, nil
}
// IAMInfo provides the shape for unmarshaling an IAM info from the metadata
// API.
type IAMInfo struct {
Code string
LastUpdated time.Time
InstanceProfileArn string
InstanceProfileID string
}

View File

@ -0,0 +1,109 @@
package imds
import (
"context"
"encoding/json"
"fmt"
"io"
"time"
"github.com/aws/smithy-go"
smithyio "github.com/aws/smithy-go/io"
"github.com/aws/smithy-go/middleware"
smithyhttp "github.com/aws/smithy-go/transport/http"
)
const getInstanceIdentityDocumentPath = getDynamicDataPath + "/instance-identity/document"
// GetInstanceIdentityDocument retrieves an identity document describing an
// instance. Error is returned if the request fails or is unable to parse
// the response.
func (c *Client) GetInstanceIdentityDocument(
ctx context.Context, params *GetInstanceIdentityDocumentInput, optFns ...func(*Options),
) (
*GetInstanceIdentityDocumentOutput, error,
) {
if params == nil {
params = &GetInstanceIdentityDocumentInput{}
}
result, metadata, err := c.invokeOperation(ctx, "GetInstanceIdentityDocument", params, optFns,
addGetInstanceIdentityDocumentMiddleware,
)
if err != nil {
return nil, err
}
out := result.(*GetInstanceIdentityDocumentOutput)
out.ResultMetadata = metadata
return out, nil
}
// GetInstanceIdentityDocumentInput provides the input parameters for
// GetInstanceIdentityDocument operation.
type GetInstanceIdentityDocumentInput struct{}
// GetInstanceIdentityDocumentOutput provides the output parameters for
// GetInstanceIdentityDocument operation.
type GetInstanceIdentityDocumentOutput struct {
InstanceIdentityDocument
ResultMetadata middleware.Metadata
}
func addGetInstanceIdentityDocumentMiddleware(stack *middleware.Stack, options Options) error {
return addAPIRequestMiddleware(stack,
options,
buildGetInstanceIdentityDocumentPath,
buildGetInstanceIdentityDocumentOutput,
)
}
func buildGetInstanceIdentityDocumentPath(params interface{}) (string, error) {
return getInstanceIdentityDocumentPath, nil
}
func buildGetInstanceIdentityDocumentOutput(resp *smithyhttp.Response) (v interface{}, err error) {
defer func() {
closeErr := resp.Body.Close()
if err == nil {
err = closeErr
} else if closeErr != nil {
err = fmt.Errorf("response body close error: %v, original error: %w", closeErr, err)
}
}()
var buff [1024]byte
ringBuffer := smithyio.NewRingBuffer(buff[:])
body := io.TeeReader(resp.Body, ringBuffer)
output := &GetInstanceIdentityDocumentOutput{}
if err = json.NewDecoder(body).Decode(&output.InstanceIdentityDocument); err != nil {
return nil, &smithy.DeserializationError{
Err: fmt.Errorf("failed to decode instance identity document, %w", err),
Snapshot: ringBuffer.Bytes(),
}
}
return output, nil
}
// InstanceIdentityDocument provides the shape for unmarshaling
// an instance identity document
type InstanceIdentityDocument struct {
DevpayProductCodes []string `json:"devpayProductCodes"`
MarketplaceProductCodes []string `json:"marketplaceProductCodes"`
AvailabilityZone string `json:"availabilityZone"`
PrivateIP string `json:"privateIp"`
Version string `json:"version"`
Region string `json:"region"`
InstanceID string `json:"instanceId"`
BillingProducts []string `json:"billingProducts"`
InstanceType string `json:"instanceType"`
AccountID string `json:"accountId"`
PendingTime time.Time `json:"pendingTime"`
ImageID string `json:"imageId"`
KernelID string `json:"kernelId"`
RamdiskID string `json:"ramdiskId"`
Architecture string `json:"architecture"`
}

View File

@ -0,0 +1,76 @@
package imds
import (
"context"
"fmt"
"io"
"github.com/aws/smithy-go/middleware"
smithyhttp "github.com/aws/smithy-go/transport/http"
)
const getMetadataPath = "/latest/meta-data"
// GetMetadata uses the path provided to request information from the Amazon
// EC2 Instance Metadata Service. The content will be returned as a string, or
// error if the request failed.
func (c *Client) GetMetadata(ctx context.Context, params *GetMetadataInput, optFns ...func(*Options)) (*GetMetadataOutput, error) {
if params == nil {
params = &GetMetadataInput{}
}
result, metadata, err := c.invokeOperation(ctx, "GetMetadata", params, optFns,
addGetMetadataMiddleware,
)
if err != nil {
return nil, err
}
out := result.(*GetMetadataOutput)
out.ResultMetadata = metadata
return out, nil
}
// GetMetadataInput provides the input parameters for the GetMetadata
// operation.
type GetMetadataInput struct {
// The relative metadata path to retrieve. Can be empty string to retrieve
// a response containing a new line separated list of metadata resources
// available.
//
// Must not include the metadata base path.
//
// May include leading slash. If Path includes trailing slash the trailing slash
// will be included in the request for the resource.
Path string
}
// GetMetadataOutput provides the output parameters for the GetMetadata
// operation.
type GetMetadataOutput struct {
Content io.ReadCloser
ResultMetadata middleware.Metadata
}
func addGetMetadataMiddleware(stack *middleware.Stack, options Options) error {
return addAPIRequestMiddleware(stack,
options,
buildGetMetadataPath,
buildGetMetadataOutput)
}
func buildGetMetadataPath(params interface{}) (string, error) {
p, ok := params.(*GetMetadataInput)
if !ok {
return "", fmt.Errorf("unknown parameter type %T", params)
}
return appendURIPath(getMetadataPath, p.Path), nil
}
func buildGetMetadataOutput(resp *smithyhttp.Response) (interface{}, error) {
return &GetMetadataOutput{
Content: resp.Body,
}, nil
}

View File

@ -0,0 +1,72 @@
package imds
import (
"context"
"fmt"
"github.com/aws/smithy-go/middleware"
smithyhttp "github.com/aws/smithy-go/transport/http"
)
// GetRegion retrieves an identity document describing an
// instance. Error is returned if the request fails or is unable to parse
// the response.
func (c *Client) GetRegion(
ctx context.Context, params *GetRegionInput, optFns ...func(*Options),
) (
*GetRegionOutput, error,
) {
if params == nil {
params = &GetRegionInput{}
}
result, metadata, err := c.invokeOperation(ctx, "GetRegion", params, optFns,
addGetRegionMiddleware,
)
if err != nil {
return nil, err
}
out := result.(*GetRegionOutput)
out.ResultMetadata = metadata
return out, nil
}
// GetRegionInput provides the input parameters for GetRegion operation.
type GetRegionInput struct{}
// GetRegionOutput provides the output parameters for GetRegion operation.
type GetRegionOutput struct {
Region string
ResultMetadata middleware.Metadata
}
func addGetRegionMiddleware(stack *middleware.Stack, options Options) error {
return addAPIRequestMiddleware(stack,
options,
buildGetInstanceIdentityDocumentPath,
buildGetRegionOutput,
)
}
func buildGetRegionOutput(resp *smithyhttp.Response) (interface{}, error) {
out, err := buildGetInstanceIdentityDocumentOutput(resp)
if err != nil {
return nil, err
}
result, ok := out.(*GetInstanceIdentityDocumentOutput)
if !ok {
return nil, fmt.Errorf("unexpected instance identity document type, %T", out)
}
region := result.Region
if len(region) == 0 {
return "", fmt.Errorf("instance metadata did not return a region value")
}
return &GetRegionOutput{
Region: region,
}, nil
}

View File

@ -0,0 +1,118 @@
package imds
import (
"context"
"fmt"
"io"
"strconv"
"strings"
"time"
"github.com/aws/smithy-go/middleware"
smithyhttp "github.com/aws/smithy-go/transport/http"
)
const getTokenPath = "/latest/api/token"
const tokenTTLHeader = "X-Aws-Ec2-Metadata-Token-Ttl-Seconds"
// getToken uses the duration to return a token for EC2 IMDS, or an error if
// the request failed.
func (c *Client) getToken(ctx context.Context, params *getTokenInput, optFns ...func(*Options)) (*getTokenOutput, error) {
if params == nil {
params = &getTokenInput{}
}
result, metadata, err := c.invokeOperation(ctx, "getToken", params, optFns,
addGetTokenMiddleware,
)
if err != nil {
return nil, err
}
out := result.(*getTokenOutput)
out.ResultMetadata = metadata
return out, nil
}
type getTokenInput struct {
TokenTTL time.Duration
}
type getTokenOutput struct {
Token string
TokenTTL time.Duration
ResultMetadata middleware.Metadata
}
func addGetTokenMiddleware(stack *middleware.Stack, options Options) error {
err := addRequestMiddleware(stack,
options,
"PUT",
buildGetTokenPath,
buildGetTokenOutput)
if err != nil {
return err
}
err = stack.Serialize.Add(&tokenTTLRequestHeader{}, middleware.After)
if err != nil {
return err
}
return nil
}
func buildGetTokenPath(interface{}) (string, error) {
return getTokenPath, nil
}
func buildGetTokenOutput(resp *smithyhttp.Response) (v interface{}, err error) {
defer func() {
closeErr := resp.Body.Close()
if err == nil {
err = closeErr
} else if closeErr != nil {
err = fmt.Errorf("response body close error: %v, original error: %w", closeErr, err)
}
}()
ttlHeader := resp.Header.Get(tokenTTLHeader)
tokenTTL, err := strconv.ParseInt(ttlHeader, 10, 64)
if err != nil {
return nil, fmt.Errorf("unable to parse API token, %w", err)
}
var token strings.Builder
if _, err = io.Copy(&token, resp.Body); err != nil {
return nil, fmt.Errorf("unable to read API token, %w", err)
}
return &getTokenOutput{
Token: token.String(),
TokenTTL: time.Duration(tokenTTL) * time.Second,
}, nil
}
type tokenTTLRequestHeader struct{}
func (*tokenTTLRequestHeader) ID() string { return "tokenTTLRequestHeader" }
func (*tokenTTLRequestHeader) HandleSerialize(
ctx context.Context, in middleware.SerializeInput, next middleware.SerializeHandler,
) (
out middleware.SerializeOutput, metadata middleware.Metadata, err error,
) {
req, ok := in.Request.(*smithyhttp.Request)
if !ok {
return out, metadata, fmt.Errorf("expect HTTP transport, got %T", in.Request)
}
input, ok := in.Parameters.(*getTokenInput)
if !ok {
return out, metadata, fmt.Errorf("expect getTokenInput, got %T", in.Parameters)
}
req.Header.Set(tokenTTLHeader, strconv.Itoa(int(input.TokenTTL/time.Second)))
return next.HandleSerialize(ctx, in)
}

View File

@ -0,0 +1,60 @@
package imds
import (
"context"
"io"
"github.com/aws/smithy-go/middleware"
smithyhttp "github.com/aws/smithy-go/transport/http"
)
const getUserDataPath = "/latest/user-data"
// GetUserData uses the path provided to request information from the EC2
// instance metadata service for dynamic data. The content will be returned
// as a string, or error if the request failed.
func (c *Client) GetUserData(ctx context.Context, params *GetUserDataInput, optFns ...func(*Options)) (*GetUserDataOutput, error) {
if params == nil {
params = &GetUserDataInput{}
}
result, metadata, err := c.invokeOperation(ctx, "GetUserData", params, optFns,
addGetUserDataMiddleware,
)
if err != nil {
return nil, err
}
out := result.(*GetUserDataOutput)
out.ResultMetadata = metadata
return out, nil
}
// GetUserDataInput provides the input parameters for the GetUserData
// operation.
type GetUserDataInput struct{}
// GetUserDataOutput provides the output parameters for the GetUserData
// operation.
type GetUserDataOutput struct {
Content io.ReadCloser
ResultMetadata middleware.Metadata
}
func addGetUserDataMiddleware(stack *middleware.Stack, options Options) error {
return addAPIRequestMiddleware(stack,
options,
buildGetUserDataPath,
buildGetUserDataOutput)
}
func buildGetUserDataPath(params interface{}) (string, error) {
return getUserDataPath, nil
}
func buildGetUserDataOutput(resp *smithyhttp.Response) (interface{}, error) {
return &GetUserDataOutput{
Content: resp.Body,
}, nil
}

View File

@ -0,0 +1,6 @@
// Package imds provides the API client for interacting with the Amazon EC2
// Instance Metadata Service.
//
// See the EC2 IMDS user guide for more information on using the API.
// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html
package imds

View File

@ -0,0 +1,6 @@
// Code generated by internal/repotools/cmd/updatemodulemeta DO NOT EDIT.
package imds
// goModuleVersion is the tagged release for this module
const goModuleVersion = "1.5.1"

View File

@ -0,0 +1,98 @@
package config
import (
"fmt"
"strings"
)
// ClientEnableState provides an enumeration if the client is enabled,
// disabled, or default behavior.
type ClientEnableState uint
// Enumeration values for ClientEnableState
const (
ClientDefaultEnableState ClientEnableState = iota
ClientDisabled
ClientEnabled
)
// EndpointModeState is the EC2 IMDS Endpoint Configuration Mode
type EndpointModeState uint
// Enumeration values for ClientEnableState
const (
EndpointModeStateUnset EndpointModeState = iota
EndpointModeStateIPv4
EndpointModeStateIPv6
)
// SetFromString sets the EndpointModeState based on the provided string value. Unknown values will default to EndpointModeStateUnset
func (e *EndpointModeState) SetFromString(v string) error {
v = strings.TrimSpace(v)
switch {
case len(v) == 0:
*e = EndpointModeStateUnset
case strings.EqualFold(v, "IPv6"):
*e = EndpointModeStateIPv6
case strings.EqualFold(v, "IPv4"):
*e = EndpointModeStateIPv4
default:
return fmt.Errorf("unknown EC2 IMDS endpoint mode, must be either IPv6 or IPv4")
}
return nil
}
// ClientEnableStateResolver is a config resolver interface for retrieving whether the IMDS client is disabled.
type ClientEnableStateResolver interface {
GetEC2IMDSClientEnableState() (ClientEnableState, bool, error)
}
// EndpointModeResolver is a config resolver interface for retrieving the EndpointModeState configuration.
type EndpointModeResolver interface {
GetEC2IMDSEndpointMode() (EndpointModeState, bool, error)
}
// EndpointResolver is a config resolver interface for retrieving the endpoint.
type EndpointResolver interface {
GetEC2IMDSEndpoint() (string, bool, error)
}
// ResolveClientEnableState resolves the ClientEnableState from a list of configuration sources.
func ResolveClientEnableState(sources []interface{}) (value ClientEnableState, found bool, err error) {
for _, source := range sources {
if resolver, ok := source.(ClientEnableStateResolver); ok {
value, found, err = resolver.GetEC2IMDSClientEnableState()
if err != nil || found {
return value, found, err
}
}
}
return value, found, err
}
// ResolveEndpointModeConfig resolves the EndpointModeState from a list of configuration sources.
func ResolveEndpointModeConfig(sources []interface{}) (value EndpointModeState, found bool, err error) {
for _, source := range sources {
if resolver, ok := source.(EndpointModeResolver); ok {
value, found, err = resolver.GetEC2IMDSEndpointMode()
if err != nil || found {
return value, found, err
}
}
}
return value, found, err
}
// ResolveEndpointConfig resolves the endpoint from a list of configuration sources.
func ResolveEndpointConfig(sources []interface{}) (value string, found bool, err error) {
for _, source := range sources {
if resolver, ok := source.(EndpointResolver); ok {
value, found, err = resolver.GetEC2IMDSEndpoint()
if err != nil || found {
return value, found, err
}
}
}
return value, found, err
}

View File

@ -0,0 +1,244 @@
package imds
import (
"context"
"fmt"
"net/url"
"path"
"time"
awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware"
"github.com/aws/aws-sdk-go-v2/aws/retry"
"github.com/aws/smithy-go/middleware"
smithyhttp "github.com/aws/smithy-go/transport/http"
)
func addAPIRequestMiddleware(stack *middleware.Stack,
options Options,
getPath func(interface{}) (string, error),
getOutput func(*smithyhttp.Response) (interface{}, error),
) (err error) {
err = addRequestMiddleware(stack, options, "GET", getPath, getOutput)
if err != nil {
return err
}
// Token Serializer build and state management.
if !options.disableAPIToken {
err = stack.Finalize.Insert(options.tokenProvider, (*retry.Attempt)(nil).ID(), middleware.After)
if err != nil {
return err
}
err = stack.Deserialize.Insert(options.tokenProvider, "OperationDeserializer", middleware.Before)
if err != nil {
return err
}
}
return nil
}
func addRequestMiddleware(stack *middleware.Stack,
options Options,
method string,
getPath func(interface{}) (string, error),
getOutput func(*smithyhttp.Response) (interface{}, error),
) (err error) {
err = awsmiddleware.AddSDKAgentKey(awsmiddleware.FeatureMetadata, "ec2-imds")(stack)
if err != nil {
return err
}
// Operation timeout
err = stack.Initialize.Add(&operationTimeout{
Timeout: defaultOperationTimeout,
}, middleware.Before)
if err != nil {
return err
}
// Operation Serializer
err = stack.Serialize.Add(&serializeRequest{
GetPath: getPath,
Method: method,
}, middleware.After)
if err != nil {
return err
}
// Operation endpoint resolver
err = stack.Serialize.Insert(&resolveEndpoint{
Endpoint: options.Endpoint,
EndpointMode: options.EndpointMode,
}, "OperationSerializer", middleware.Before)
if err != nil {
return err
}
// Operation Deserializer
err = stack.Deserialize.Add(&deserializeResponse{
GetOutput: getOutput,
}, middleware.After)
if err != nil {
return err
}
// Retry support
return retry.AddRetryMiddlewares(stack, retry.AddRetryMiddlewaresOptions{
Retryer: options.Retryer,
LogRetryAttempts: options.ClientLogMode.IsRetries(),
})
}
type serializeRequest struct {
GetPath func(interface{}) (string, error)
Method string
}
func (*serializeRequest) ID() string {
return "OperationSerializer"
}
func (m *serializeRequest) HandleSerialize(
ctx context.Context, in middleware.SerializeInput, next middleware.SerializeHandler,
) (
out middleware.SerializeOutput, metadata middleware.Metadata, err error,
) {
request, ok := in.Request.(*smithyhttp.Request)
if !ok {
return out, metadata, fmt.Errorf("unknown transport type %T", in.Request)
}
reqPath, err := m.GetPath(in.Parameters)
if err != nil {
return out, metadata, fmt.Errorf("unable to get request URL path, %w", err)
}
request.Request.URL.Path = reqPath
request.Request.Method = m.Method
return next.HandleSerialize(ctx, in)
}
type deserializeResponse struct {
GetOutput func(*smithyhttp.Response) (interface{}, error)
}
func (*deserializeResponse) ID() string {
return "OperationDeserializer"
}
func (m *deserializeResponse) HandleDeserialize(
ctx context.Context, in middleware.DeserializeInput, next middleware.DeserializeHandler,
) (
out middleware.DeserializeOutput, metadata middleware.Metadata, err error,
) {
out, metadata, err = next.HandleDeserialize(ctx, in)
if err != nil {
return out, metadata, err
}
resp, ok := out.RawResponse.(*smithyhttp.Response)
if !ok {
return out, metadata, fmt.Errorf(
"unexpected transport response type, %T", out.RawResponse)
}
// Anything thats not 200 |< 300 is error
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
resp.Body.Close()
return out, metadata, &smithyhttp.ResponseError{
Response: resp,
Err: fmt.Errorf("request to EC2 IMDS failed"),
}
}
result, err := m.GetOutput(resp)
if err != nil {
return out, metadata, fmt.Errorf(
"unable to get deserialized result for response, %w", err,
)
}
out.Result = result
return out, metadata, err
}
type resolveEndpoint struct {
Endpoint string
EndpointMode EndpointModeState
}
func (*resolveEndpoint) ID() string {
return "ResolveEndpoint"
}
func (m *resolveEndpoint) HandleSerialize(
ctx context.Context, in middleware.SerializeInput, next middleware.SerializeHandler,
) (
out middleware.SerializeOutput, metadata middleware.Metadata, err error,
) {
req, ok := in.Request.(*smithyhttp.Request)
if !ok {
return out, metadata, fmt.Errorf("unknown transport type %T", in.Request)
}
var endpoint string
if len(m.Endpoint) > 0 {
endpoint = m.Endpoint
} else {
switch m.EndpointMode {
case EndpointModeStateIPv6:
endpoint = defaultIPv6Endpoint
case EndpointModeStateIPv4:
fallthrough
case EndpointModeStateUnset:
endpoint = defaultIPv4Endpoint
default:
return out, metadata, fmt.Errorf("unsupported IMDS endpoint mode")
}
}
req.URL, err = url.Parse(endpoint)
if err != nil {
return out, metadata, fmt.Errorf("failed to parse endpoint URL: %w", err)
}
return next.HandleSerialize(ctx, in)
}
const (
defaultOperationTimeout = 5 * time.Second
)
type operationTimeout struct {
Timeout time.Duration
}
func (*operationTimeout) ID() string { return "OperationTimeout" }
func (m *operationTimeout) HandleInitialize(
ctx context.Context, input middleware.InitializeInput, next middleware.InitializeHandler,
) (
output middleware.InitializeOutput, metadata middleware.Metadata, err error,
) {
var cancelFn func()
ctx, cancelFn = context.WithTimeout(ctx, m.Timeout)
defer cancelFn()
return next.HandleInitialize(ctx, input)
}
// appendURIPath joins a URI path component to the existing path with `/`
// separators between the path components. If the path being added ends with a
// trailing `/` that slash will be maintained.
func appendURIPath(base, add string) string {
reqPath := path.Join(base, add)
if len(add) != 0 && add[len(add)-1] == '/' {
reqPath += "/"
}
return reqPath
}

View File

@ -0,0 +1,237 @@
package imds
import (
"context"
"errors"
"fmt"
"net/http"
"sync"
"sync/atomic"
"time"
smithy "github.com/aws/smithy-go"
"github.com/aws/smithy-go/middleware"
smithyhttp "github.com/aws/smithy-go/transport/http"
)
const (
// Headers for Token and TTL
tokenHeader = "x-aws-ec2-metadata-token"
defaultTokenTTL = 5 * time.Minute
)
type tokenProvider struct {
client *Client
tokenTTL time.Duration
token *apiToken
tokenMux sync.RWMutex
disabled uint32 // Atomic updated
}
func newTokenProvider(client *Client, ttl time.Duration) *tokenProvider {
return &tokenProvider{
client: client,
tokenTTL: ttl,
}
}
// apiToken provides the API token used by all operation calls for th EC2
// Instance metadata service.
type apiToken struct {
token string
expires time.Time
}
var timeNow = time.Now
// Expired returns if the token is expired.
func (t *apiToken) Expired() bool {
// Calling Round(0) on the current time will truncate the monotonic reading only. Ensures credential expiry
// time is always based on reported wall-clock time.
return timeNow().Round(0).After(t.expires)
}
func (t *tokenProvider) ID() string { return "APITokenProvider" }
// HandleFinalize is the finalize stack middleware, that if the token provider is
// enabled, will attempt to add the cached API token to the request. If the API
// token is not cached, it will be retrieved in a separate API call, getToken.
//
// For retry attempts, handler must be added after attempt retryer.
//
// If request for getToken fails the token provider may be disabled from future
// requests, depending on the response status code.
func (t *tokenProvider) HandleFinalize(
ctx context.Context, input middleware.FinalizeInput, next middleware.FinalizeHandler,
) (
out middleware.FinalizeOutput, metadata middleware.Metadata, err error,
) {
if !t.enabled() {
// short-circuits to insecure data flow if token provider is disabled.
return next.HandleFinalize(ctx, input)
}
req, ok := input.Request.(*smithyhttp.Request)
if !ok {
return out, metadata, fmt.Errorf("unexpected transport request type %T", input.Request)
}
tok, err := t.getToken(ctx)
if err != nil {
// If the error allows the token to downgrade to insecure flow allow that.
var bypassErr *bypassTokenRetrievalError
if errors.As(err, &bypassErr) {
return next.HandleFinalize(ctx, input)
}
return out, metadata, fmt.Errorf("failed to get API token, %w", err)
}
req.Header.Set(tokenHeader, tok.token)
return next.HandleFinalize(ctx, input)
}
// HandleDeserialize is the deserialize stack middleware for determining if the
// operation the token provider is decorating failed because of a 401
// unauthorized status code. If the operation failed for that reason the token
// provider needs to be re-enabled so that it can start adding the API token to
// operation calls.
func (t *tokenProvider) HandleDeserialize(
ctx context.Context, input middleware.DeserializeInput, next middleware.DeserializeHandler,
) (
out middleware.DeserializeOutput, metadata middleware.Metadata, err error,
) {
out, metadata, err = next.HandleDeserialize(ctx, input)
if err == nil {
return out, metadata, err
}
resp, ok := out.RawResponse.(*smithyhttp.Response)
if !ok {
return out, metadata, fmt.Errorf("expect HTTP transport, got %T", out.RawResponse)
}
if resp.StatusCode == http.StatusUnauthorized { // unauthorized
err = &retryableError{Err: err}
t.enable()
}
return out, metadata, err
}
type retryableError struct {
Err error
}
func (*retryableError) RetryableError() bool { return true }
func (e *retryableError) Error() string { return e.Err.Error() }
func (t *tokenProvider) getToken(ctx context.Context) (tok *apiToken, err error) {
if !t.enabled() {
return nil, &bypassTokenRetrievalError{
Err: fmt.Errorf("cannot get API token, provider disabled"),
}
}
t.tokenMux.RLock()
tok = t.token
t.tokenMux.RUnlock()
if tok != nil && !tok.Expired() {
return tok, nil
}
tok, err = t.updateToken(ctx)
if err != nil {
return nil, fmt.Errorf("cannot get API token, %w", err)
}
return tok, nil
}
func (t *tokenProvider) updateToken(ctx context.Context) (*apiToken, error) {
t.tokenMux.Lock()
defer t.tokenMux.Unlock()
// Prevent multiple requests to update retrieving the token.
if t.token != nil && !t.token.Expired() {
tok := t.token
return tok, nil
}
result, err := t.client.getToken(ctx, &getTokenInput{
TokenTTL: t.tokenTTL,
})
if err != nil {
// change the disabled flag on token provider to true, when error is request timeout error.
var statusErr interface{ HTTPStatusCode() int }
if errors.As(err, &statusErr) {
switch statusErr.HTTPStatusCode() {
// Disable get token if failed because of 403, 404, or 405
case http.StatusForbidden,
http.StatusNotFound,
http.StatusMethodNotAllowed:
t.disable()
// 400 errors are terminal, and need to be upstreamed
case http.StatusBadRequest:
return nil, err
}
}
// Disable if request send failed or timed out getting response
var re *smithyhttp.RequestSendError
var ce *smithy.CanceledError
if errors.As(err, &re) || errors.As(err, &ce) {
atomic.StoreUint32(&t.disabled, 1)
}
// Token couldn't be retrieved, but bypass this, and allow the
// request to continue.
return nil, &bypassTokenRetrievalError{Err: err}
}
tok := &apiToken{
token: result.Token,
expires: timeNow().Add(result.TokenTTL),
}
t.token = tok
return tok, nil
}
type bypassTokenRetrievalError struct {
Err error
}
func (e *bypassTokenRetrievalError) Error() string {
return fmt.Sprintf("bypass token retrieval, %v", e.Err)
}
func (e *bypassTokenRetrievalError) Unwrap() error { return e.Err }
// enabled returns if the token provider is current enabled or not.
func (t *tokenProvider) enabled() bool {
return atomic.LoadUint32(&t.disabled) == 0
}
// disable disables the token provider and it will no longer attempt to inject
// the token, nor request updates.
func (t *tokenProvider) disable() {
atomic.StoreUint32(&t.disabled, 1)
}
// enable enables the token provide to start refreshing tokens, and adding them
// to the pending request.
func (t *tokenProvider) enable() {
t.tokenMux.Lock()
t.token = nil
t.tokenMux.Unlock()
atomic.StoreUint32(&t.disabled, 0)
}