diff --git a/apstra/authentication/token.go b/apstra/authentication/token.go new file mode 100644 index 00000000..149fd8b4 --- /dev/null +++ b/apstra/authentication/token.go @@ -0,0 +1,99 @@ +package authentication + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + "strings" + "time" + + "github.com/Juniper/terraform-provider-apstra/apstra/private" + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework/diag" + ephemeralSchema "github.com/hashicorp/terraform-plugin-framework/ephemeral/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +const apiTokenDefaultWarning = 60 + +type ApiToken struct { + Value types.String `tfsdk:"value"` + SessionId types.String `tfsdk:"session_id"` + UserName types.String `tfsdk:"user_name"` + WarnSeconds types.Int64 `tfsdk:"warn_seconds"` + ExpiresAt time.Time `tfsdk:"-"` +} + +func (o ApiToken) EphemeralAttributes() map[string]ephemeralSchema.Attribute { + return map[string]ephemeralSchema.Attribute{ + "value": ephemeralSchema.StringAttribute{ + Computed: true, + MarkdownDescription: "The API token value.", + }, + "session_id": ephemeralSchema.StringAttribute{ + Computed: true, + MarkdownDescription: "The API session ID associated with the token.", + }, + "user_name": ephemeralSchema.StringAttribute{ + Computed: true, + MarkdownDescription: "The user name associated with the session ID.", + }, + "warn_seconds": ephemeralSchema.Int64Attribute{ + Optional: true, + Computed: true, + MarkdownDescription: fmt.Sprintf("Terraform will produce a warning when the token value is "+ + "referenced with less than this amount of time remaining before expiration. Note that "+ + "determination of remaining token lifetime depends on clock sync between the Apstra server and "+ + "the Terraform host. Value `0` disables warnings. Default value is `%d`.", apiTokenDefaultWarning), + Validators: []validator.Int64{int64validator.AtLeast(0)}, + }, + } +} + +func (o *ApiToken) LoadApiData(_ context.Context, in string, diags *diag.Diagnostics) { + parts := strings.Split(in, ".") + if len(parts) != 3 { + diags.AddError("unexpected API response", fmt.Sprintf("JWT should have 3 parts, got %d", len(parts))) + return + } + + claimsB64 := parts[1] + strings.Repeat("=", (4-len(parts[1])%4)%4) // pad the b64 part as necessary + claimsBytes, err := base64.StdEncoding.DecodeString(claimsB64) + if err != nil { + diags.AddError("failed decoding token claims", err.Error()) + return + } + + var claims struct { + Username string `json:"username"` + UserSession string `json:"user_session"` + Expiration int64 `json:"exp"` + } + err = json.Unmarshal(claimsBytes, &claims) + if err != nil { + diags.AddError("failed unpacking token claims", err.Error()) + return + } + + o.Value = types.StringValue(in) + o.UserName = types.StringValue(claims.Username) + o.SessionId = types.StringValue(claims.UserSession) + o.ExpiresAt = time.Unix(claims.Expiration, 0) +} + +func (o *ApiToken) SetDefaults() { + if o.WarnSeconds.IsNull() { + o.WarnSeconds = types.Int64Value(apiTokenDefaultWarning) + } +} + +func (o *ApiToken) SetPrivateState(ctx context.Context, ps private.State, diags *diag.Diagnostics) { + var privateEphemeralApiToken = private.EphemeralApiToken{ + Token: o.Value.ValueString(), + ExpiresAt: o.ExpiresAt, + WarnThreshold: time.Duration(o.WarnSeconds.ValueInt64()) * time.Second, + } + privateEphemeralApiToken.SetPrivateState(ctx, ps, diags) +} diff --git a/apstra/configure_data_source.go b/apstra/configure_data_source.go index 62dd343f..8873eb04 100644 --- a/apstra/configure_data_source.go +++ b/apstra/configure_data_source.go @@ -48,5 +48,4 @@ func configureDataSource(_ context.Context, ds datasource.DataSourceWithConfigur if ds, ok := ds.(datasourceWithSetFfBpClientFunc); ok { ds.setBpClientFunc(pd.getFreeformClient) } - } diff --git a/apstra/configure_ephemeral.go b/apstra/configure_ephemeral.go new file mode 100644 index 00000000..a868c90a --- /dev/null +++ b/apstra/configure_ephemeral.go @@ -0,0 +1,51 @@ +package tfapstra + +import ( + "context" + "fmt" + "github.com/Juniper/apstra-go-sdk/apstra" + "github.com/hashicorp/terraform-plugin-framework/ephemeral" +) + +type ephemeralWithSetClient interface { + ephemeral.EphemeralResourceWithConfigure + setClient(*apstra.Client) +} + +type ephemeralWithSetDcBpClientFunc interface { + ephemeral.EphemeralResourceWithConfigure + setBpClientFunc(func(context.Context, string) (*apstra.TwoStageL3ClosClient, error)) +} + +type ephemeralWithSetFfBpClientFunc interface { + ephemeral.EphemeralResourceWithConfigure + setBpClientFunc(func(context.Context, string) (*apstra.FreeformClient, error)) +} + +func configureEphemeral(_ context.Context, ep ephemeral.EphemeralResourceWithConfigure, req ephemeral.ConfigureRequest, resp *ephemeral.ConfigureResponse) { + if req.ProviderData == nil { + return // cannot continue + } + + var pd *providerData + var ok bool + + if pd, ok = req.ProviderData.(*providerData); !ok { + resp.Diagnostics.AddError( + errDataSourceConfigureProviderDataSummary, + fmt.Sprintf(errDataSourceConfigureProviderDataDetail, *pd, req.ProviderData), + ) + } + + if ep, ok := ep.(ephemeralWithSetClient); ok { + ep.setClient(pd.client) + } + + if ep, ok := ep.(ephemeralWithSetDcBpClientFunc); ok { + ep.setBpClientFunc(pd.getTwoStageL3ClosClient) + } + + if ep, ok := ep.(ephemeralWithSetFfBpClientFunc); ok { + ep.setBpClientFunc(pd.getFreeformClient) + } +} diff --git a/apstra/ephemeral_api_token.go b/apstra/ephemeral_api_token.go new file mode 100644 index 00000000..b6e70004 --- /dev/null +++ b/apstra/ephemeral_api_token.go @@ -0,0 +1,174 @@ +package tfapstra + +import ( + "context" + "errors" + "fmt" + "time" + + "github.com/Juniper/apstra-go-sdk/apstra" + "github.com/Juniper/terraform-provider-apstra/apstra/authentication" + "github.com/Juniper/terraform-provider-apstra/apstra/private" + "github.com/hashicorp/terraform-plugin-framework/ephemeral" + "github.com/hashicorp/terraform-plugin-framework/ephemeral/schema" +) + +var ( + _ ephemeral.EphemeralResource = (*ephemeralToken)(nil) + _ ephemeral.EphemeralResourceWithClose = (*ephemeralToken)(nil) + _ ephemeral.EphemeralResourceWithRenew = (*ephemeralToken)(nil) + _ ephemeral.EphemeralResourceWithConfigure = (*ephemeralToken)(nil) + _ ephemeralWithSetClient = (*ephemeralToken)(nil) +) + +type ephemeralToken struct { + client *apstra.Client +} + +func (o *ephemeralToken) Metadata(_ context.Context, req ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_api_token" +} + +func (o *ephemeralToken) Schema(_ context.Context, _ ephemeral.SchemaRequest, resp *ephemeral.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "This Ephemeral Resource retrieves a unique API token and invalidates it on exit.", + Attributes: authentication.ApiToken{}.EphemeralAttributes(), + } +} + +func (o *ephemeralToken) Configure(ctx context.Context, req ephemeral.ConfigureRequest, resp *ephemeral.ConfigureResponse) { + configureEphemeral(ctx, o, req, resp) +} + +func (o *ephemeralToken) Open(ctx context.Context, req ephemeral.OpenRequest, resp *ephemeral.OpenResponse) { + var config authentication.ApiToken + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + // set default values + config.SetDefaults() + + // create a new client using the credentials in the embedded client's config + client, err := o.client.Config().NewClient(ctx) + if err != nil { + resp.Diagnostics.AddError("error creating new client", err.Error()) + return + } + + // log in so that the new client fetches an API token + err = client.Login(ctx) + if err != nil { + resp.Diagnostics.AddError("error logging in new client", err.Error()) + return + } + + // extract the token + token := client.GetApiToken() + if token == "" { + resp.Diagnostics.AddError("requested API token is empty", "requested API token is empty") + return + } + + // destroy the new client without invalidating the API token we just collected + client.SetApiToken("") + err = client.Logout(ctx) + if err != nil { + resp.Diagnostics.AddError("error logging out client", err.Error()) + return + } + + config.LoadApiData(ctx, token, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + + config.SetPrivateState(ctx, resp.Private, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + + // sanity check the token lifetime + now := time.Now() + if now.After(config.ExpiresAt) { + resp.Diagnostics.AddError( + "Just-fetched API token is expired", + fmt.Sprintf("Token expired at: %s. Current time is: %s", config.ExpiresAt, now), + ) + return + } + + // warn the user about imminent expiration + warn := time.Duration(config.WarnSeconds.ValueInt64()) * time.Second + if now.Add(warn).After(config.ExpiresAt) { + resp.Diagnostics.AddWarning( + fmt.Sprintf("API token expires within %d second warning threshold", config.WarnSeconds), + fmt.Sprintf("API token expires at %s. Current time: %s", config.ExpiresAt, now), + ) + } + + // set the refresh timestamp + resp.RenewAt = config.ExpiresAt.Add(-1 * warn) + + // set the result + resp.Diagnostics.Append(resp.Result.Set(ctx, &config)...) +} + +func (o *ephemeralToken) Renew(ctx context.Context, req ephemeral.RenewRequest, resp *ephemeral.RenewResponse) { + var privateEphemeralApiToken private.EphemeralApiToken + privateEphemeralApiToken.LoadPrivateState(ctx, req.Private, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + + now := time.Now() + if now.After(privateEphemeralApiToken.ExpiresAt) { + resp.Diagnostics.AddError( + "API token has expired", + fmt.Sprintf("Token expired at: %s. Current time is: %s", privateEphemeralApiToken.ExpiresAt, now), + ) + return + } + + if now.Add(privateEphemeralApiToken.WarnThreshold).After(privateEphemeralApiToken.ExpiresAt) { + resp.Diagnostics.AddWarning( + fmt.Sprintf("API token expires within %d second warning threshold", privateEphemeralApiToken.WarnThreshold), + fmt.Sprintf("API token expires at %s. Current time: %s", privateEphemeralApiToken.ExpiresAt, now), + ) + } + + resp.RenewAt = privateEphemeralApiToken.ExpiresAt +} + +func (o *ephemeralToken) Close(ctx context.Context, req ephemeral.CloseRequest, resp *ephemeral.CloseResponse) { + // create a new client based on the embedded client's config + client, err := o.client.Config().NewClient(ctx) + if err != nil { + resp.Diagnostics.AddError("error creating new client", err.Error()) + return + } + + // extract the private state data + var privateEphemeralApiToken private.EphemeralApiToken + privateEphemeralApiToken.LoadPrivateState(ctx, req.Private, &resp.Diagnostics) + + // swap the API token from private state into the new client + client.SetApiToken(privateEphemeralApiToken.Token) + + // log out the client + err = client.Logout(ctx) + if err != nil { + var ace apstra.ClientErr + if errors.As(err, &ace) && ace.Type() == apstra.ErrAuthFail { + return // 401 is okay + } + + resp.Diagnostics.AddError("Error while logging out the API key", err.Error()) + return + } +} + +func (o *ephemeralToken) setClient(client *apstra.Client) { + o.client = client +} diff --git a/apstra/private/ephemeral_api_token.go b/apstra/private/ephemeral_api_token.go new file mode 100644 index 00000000..1ac3f345 --- /dev/null +++ b/apstra/private/ephemeral_api_token.go @@ -0,0 +1,45 @@ +package private + +import ( + "context" + "encoding/json" + "time" + + "github.com/hashicorp/terraform-plugin-framework/diag" +) + +type EphemeralApiToken struct { + Token string `json:"token"` + ExpiresAt time.Time `json:"expires_at"` + WarnThreshold time.Duration `json:"warn_threshold"` +} + +func (o *EphemeralApiToken) LoadApiData(_ context.Context, token string, expiresAt time.Time, warnThreshold time.Duration, diags *diag.Diagnostics) { + o.Token = token + o.ExpiresAt = expiresAt + o.WarnThreshold = warnThreshold +} + +func (o *EphemeralApiToken) LoadPrivateState(ctx context.Context, ps State, diags *diag.Diagnostics) { + b, d := ps.GetKey(ctx, "EphemeralApiToken") + diags.Append(d...) + if diags.HasError() { + return + } + + err := json.Unmarshal(b, &o) + if err != nil { + diags.AddError("failed to unmarshal private state", err.Error()) + return + } +} + +func (o *EphemeralApiToken) SetPrivateState(ctx context.Context, ps State, diags *diag.Diagnostics) { + b, err := json.Marshal(o) + if err != nil { + diags.AddError("failed to marshal private state", err.Error()) + return + } + + diags.Append(ps.SetKey(ctx, "EphemeralApiToken", b)...) +} diff --git a/apstra/provider.go b/apstra/provider.go index 5fbe372e..3e53cc4c 100644 --- a/apstra/provider.go +++ b/apstra/provider.go @@ -20,6 +20,7 @@ import ( "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/ephemeral" "github.com/hashicorp/terraform-plugin-framework/provider" "github.com/hashicorp/terraform-plugin-framework/provider/schema" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -53,7 +54,10 @@ https://registry.terraform.io/providers/Juniper/apstra/%s/docs#tls_validation_di var commit, tag string // populated by goreleaser -var _ provider.Provider = &Provider{} +var ( + _ provider.Provider = &Provider{} + _ provider.ProviderWithEphemeralResources = (*Provider)(nil) +) // NewProvider instantiates the provider in main func NewProvider() provider.Provider { @@ -488,8 +492,8 @@ func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest, return freeformClient, nil } - // data passed to Resource and DataSource Configure() methods - pd := &providerData{ + // data passed to Resource, DataSource, and Ephemeral Configure() methods + pd := providerData{ client: client, providerVersion: p.Version + "-" + p.Commit, terraformVersion: req.TerraformVersion, @@ -499,8 +503,9 @@ func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest, getFreeformClient: getFreeformClient, experimental: config.Experimental.ValueBool(), } - resp.ResourceData = pd - resp.DataSourceData = pd + resp.ResourceData = &pd + resp.DataSourceData = &pd + resp.EphemeralResourceData = &pd } // DataSources defines provider data sources @@ -592,6 +597,13 @@ func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource } } +// EphemeralResources defines provider ephemeral resources +func (p *Provider) EphemeralResources(_ context.Context) []func() ephemeral.EphemeralResource { + return []func() ephemeral.EphemeralResource{ + func() ephemeral.EphemeralResource { return &ephemeralToken{} }, + } +} + // Resources defines provider resources func (p *Provider) Resources(_ context.Context) []func() resource.Resource { return []func() resource.Resource{ diff --git a/docs/ephemeral-resources/api_token.md b/docs/ephemeral-resources/api_token.md new file mode 100644 index 00000000..61b78d49 --- /dev/null +++ b/docs/ephemeral-resources/api_token.md @@ -0,0 +1,26 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "apstra_api_token Ephemeral Resource - terraform-provider-apstra" +subcategory: "" +description: |- + This Ephemeral Resource retrieves a unique API token and invalidates it on exit. +--- + +# apstra_api_token (Ephemeral Resource) + +This Ephemeral Resource retrieves a unique API token and invalidates it on exit. + + + + +## Schema + +### Optional + +- `warn_seconds` (Number) Terraform will produce a warning when the token value is referenced with less than this amount of time remaining before expiration. Note that determination of remaining token lifetime depends on clock sync between the Apstra server and the Terraform host. Value `0` disables warnings. Default value is `60`. + +### Read-Only + +- `session_id` (String) The API session ID associated with the token. +- `user_name` (String) The user name associated with the session ID. +- `value` (String) The API token value. diff --git a/go.mod b/go.mod index 14ae49fb..3404da56 100644 --- a/go.mod +++ b/go.mod @@ -1,18 +1,20 @@ module github.com/Juniper/terraform-provider-apstra -go 1.22.5 +go 1.22.7 + +toolchain go1.22.10 //replace github.com/Juniper/apstra-go-sdk => ../apstra-go-sdk require ( github.com/IBM/netaddr v1.5.0 - github.com/Juniper/apstra-go-sdk v0.0.0-20241214203041-f5275c3222f6 + github.com/Juniper/apstra-go-sdk v0.0.0-20241215023156-efc2782b3652 github.com/chrismarget-j/go-licenses v0.0.0-20240224210557-f22f3e06d3d4 github.com/chrismarget-j/version-constraints v0.0.0-20240925155624-26771a0a6820 github.com/google/go-cmp v0.6.0 github.com/hashicorp/go-version v1.7.0 github.com/hashicorp/hcl/v2 v2.20.1 - github.com/hashicorp/terraform-plugin-docs v0.18.0 + github.com/hashicorp/terraform-plugin-docs v0.20.1 github.com/hashicorp/terraform-plugin-framework v1.13.0 github.com/hashicorp/terraform-plugin-framework-jsontypes v0.1.0 github.com/hashicorp/terraform-plugin-framework-nettypes v0.1.0 @@ -41,6 +43,7 @@ require ( github.com/aws/aws-sdk-go-v2/config v1.27.24 // indirect github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.28.2 // indirect github.com/bgentry/speakeasy v0.1.0 // indirect + github.com/bmatcuk/doublestar/v4 v4.7.1 // indirect github.com/cloudflare/circl v1.3.9 // indirect github.com/cyphar/filepath-securejoin v0.2.5 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -60,11 +63,12 @@ require ( github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-plugin v1.6.2 // indirect + github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect - github.com/hashicorp/hc-install v0.6.4 // indirect + github.com/hashicorp/hc-install v0.9.0 // indirect github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/terraform-exec v0.21.0 // indirect - github.com/hashicorp/terraform-json v0.22.1 // indirect + github.com/hashicorp/terraform-json v0.23.0 // indirect github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0 // indirect github.com/hashicorp/terraform-registry-address v0.2.3 // indirect @@ -87,7 +91,6 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/posener/complete v1.2.3 // indirect github.com/rivo/uniseg v0.4.7 // indirect - github.com/russross/blackfriday v1.6.0 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/shopspring/decimal v1.4.0 // indirect github.com/spf13/cast v1.6.0 // indirect @@ -96,17 +99,18 @@ require ( github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - github.com/yuin/goldmark v1.6.0 // indirect + github.com/yuin/goldmark v1.7.7 // indirect github.com/yuin/goldmark-meta v1.1.0 // indirect - github.com/zclconf/go-cty v1.14.4 // indirect + github.com/zclconf/go-cty v1.15.0 // indirect + go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect go.opencensus.io v0.24.0 // indirect golang.org/x/crypto v0.26.0 // indirect golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a // indirect - golang.org/x/mod v0.19.0 // indirect + golang.org/x/mod v0.21.0 // indirect golang.org/x/net v0.28.0 // indirect - golang.org/x/sync v0.8.0 // indirect + golang.org/x/sync v0.9.0 // indirect golang.org/x/sys v0.24.0 // indirect - golang.org/x/text v0.17.0 // indirect + golang.org/x/text v0.20.0 // indirect golang.org/x/tools v0.23.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240814211410-ddb44dafa142 // indirect diff --git a/go.sum b/go.sum index 1715cf4c..d5375cd3 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,8 @@ github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0 github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/IBM/netaddr v1.5.0 h1:IJlFZe1+nFs09TeMB/HOP4+xBnX2iM/xgiDOgZgTJq0= github.com/IBM/netaddr v1.5.0/go.mod h1:DDBPeYgbFzoXHjSz9Jwk7K8wmWV4+a/Kv0LqRnb8we4= -github.com/Juniper/apstra-go-sdk v0.0.0-20241214203041-f5275c3222f6 h1:uvEbqjejOZgxK6uJ4WoOoCmcU74k3e/ino30UhFLXXE= -github.com/Juniper/apstra-go-sdk v0.0.0-20241214203041-f5275c3222f6/go.mod h1:j0XhEo0IoltyST4cqdLwrDUNLDHC7JWJxBPDVffeSCg= +github.com/Juniper/apstra-go-sdk v0.0.0-20241215023156-efc2782b3652 h1:okQKM0VeFWVMGiOJPrFBO7cNY2BXrKl/TtZr7LBn4pI= +github.com/Juniper/apstra-go-sdk v0.0.0-20241215023156-efc2782b3652/go.mod h1:j0XhEo0IoltyST4cqdLwrDUNLDHC7JWJxBPDVffeSCg= github.com/Kunde21/markdownfmt/v3 v3.1.0 h1:KiZu9LKs+wFFBQKhrZJrFZwtLnCCWJahL+S+E/3VnM0= github.com/Kunde21/markdownfmt/v3 v3.1.0/go.mod h1:tPXN1RTyOzJwhfHoon9wUr4HGYmWgVxSQN6VBJDkrVc= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= @@ -58,6 +58,8 @@ github.com/aws/smithy-go v1.20.3 h1:ryHwveWzPV5BIof6fyDvor6V3iUL7nTfiTKXHiW05nE= github.com/aws/smithy-go v1.20.3/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bmatcuk/doublestar/v4 v4.7.1 h1:fdDeAqgT47acgwd9bd9HxJRDmc9UAmPpc+2m0CXv75Q= +github.com/bmatcuk/doublestar/v4 v4.7.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -155,23 +157,25 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-plugin v1.6.2 h1:zdGAEd0V1lCaU0u+MxWQhtSDQmahpkwOun8U8EiRVog= github.com/hashicorp/go-plugin v1.6.2/go.mod h1:CkgLQ5CZqNmdL9U9JzM532t8ZiYQ35+pj3b1FD37R0Q= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/hc-install v0.6.4 h1:QLqlM56/+SIIGvGcfFiwMY3z5WGXT066suo/v9Km8e0= -github.com/hashicorp/hc-install v0.6.4/go.mod h1:05LWLy8TD842OtgcfBbOT0WMoInBMUSHjmDx10zuBIA= +github.com/hashicorp/hc-install v0.9.0 h1:2dIk8LcvANwtv3QZLckxcjyF5w8KVtiMxu6G6eLhghE= +github.com/hashicorp/hc-install v0.9.0/go.mod h1:+6vOP+mf3tuGgMApVYtmsnDoKWMDcFXeTxCACYZ8SFg= github.com/hashicorp/hcl/v2 v2.20.1 h1:M6hgdyz7HYt1UN9e61j+qKJBqR3orTWbI1HKBJEdxtc= github.com/hashicorp/hcl/v2 v2.20.1/go.mod h1:TZDqQ4kNKCbh1iJp99FdPiUaVDDUPivbqxZulxDYqL4= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ= github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= -github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec= -github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A= -github.com/hashicorp/terraform-plugin-docs v0.18.0 h1:2bINhzXc+yDeAcafurshCrIjtdu1XHn9zZ3ISuEhgpk= -github.com/hashicorp/terraform-plugin-docs v0.18.0/go.mod h1:iIUfaJpdUmpi+rI42Kgq+63jAjI8aZVTyxp3Bvk9Hg8= +github.com/hashicorp/terraform-json v0.23.0 h1:sniCkExU4iKtTADReHzACkk8fnpQXrdD2xoR+lppBkI= +github.com/hashicorp/terraform-json v0.23.0/go.mod h1:MHdXbBAbSg0GvzuWazEGKAn/cyNfIB7mN6y7KJN6y2c= +github.com/hashicorp/terraform-plugin-docs v0.20.1 h1:Fq7E/HrU8kuZu3hNliZGwloFWSYfWEOWnylFhYQIoys= +github.com/hashicorp/terraform-plugin-docs v0.20.1/go.mod h1:Yz6HoK7/EgzSrHPB9J/lWFzwl9/xep2OPnc5jaJDV90= github.com/hashicorp/terraform-plugin-framework v1.13.0 h1:8OTG4+oZUfKgnfTdPTJwZ532Bh2BobF4H+yBiYJ/scw= github.com/hashicorp/terraform-plugin-framework v1.13.0/go.mod h1:j64rwMGpgM3NYXTKuxrCnyubQb/4VKldEKlcG8cvmjU= github.com/hashicorp/terraform-plugin-framework-jsontypes v0.1.0 h1:b8vZYB/SkXJT4YPbT3trzE6oJ7dPyMy68+9dEDKsJjE= @@ -262,10 +266,8 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1: github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= -github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= -github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= @@ -305,14 +307,16 @@ github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68= -github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.7.7 h1:5m9rrB1sW3JUMToKFQfb+FGt1U7r57IHu5GrYrG2nqU= +github.com/yuin/goldmark v1.7.7/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc= github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0= -github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8= -github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ= +github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b h1:FosyBZYxY34Wul7O/MSKey3txpPYyCqVO5ZyceuQJEI= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= +go.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw= +go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px76YjkOzhB4YlU= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -330,8 +334,8 @@ golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTk golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= -golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= +golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -349,8 +353,8 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= +golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -380,8 +384,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= -golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= +golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=