mirror of https://github.com/grafana/loki
fix(deps): update module github.com/prometheus/common to v0.64.0 (main) (#16750)
Signed-off-by: Paul Rogers <129207811+paul1r@users.noreply.github.com> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Paul Rogers <paul.rogers@grafana.com> Co-authored-by: Paul Rogers <129207811+paul1r@users.noreply.github.com>pull/18104/head
parent
1effa30851
commit
8b265ede15
7
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported/exported.go
generated
vendored
7
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported/exported.go
generated
vendored
2
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared/constants.go
generated
vendored
2
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared/constants.go
generated
vendored
14
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_bearer_token.go
generated
vendored
14
vendor/github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime/policy_bearer_token.go
generated
vendored
7
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/environment_credential.go
generated
vendored
7
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/environment_credential.go
generated
vendored
2
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/managed-identity-matrix.json
generated
vendored
2
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/managed-identity-matrix.json
generated
vendored
433
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/managed_identity_client.go
generated
vendored
433
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/managed_identity_client.go
generated
vendored
28
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/managed_identity_credential.go
generated
vendored
28
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/managed_identity_credential.go
generated
vendored
4
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/test-resources-post.ps1
generated
vendored
4
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/test-resources-post.ps1
generated
vendored
12
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/username_password_credential.go
generated
vendored
12
vendor/github.com/Azure/azure-sdk-for-go/sdk/azidentity/username_password_credential.go
generated
vendored
@ -0,0 +1,28 @@ |
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package managedidentity |
||||
|
||||
import ( |
||||
"context" |
||||
"net/http" |
||||
"os" |
||||
) |
||||
|
||||
func createAzureMLAuthRequest(ctx context.Context, id ID, resource string) (*http.Request, error) { |
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, os.Getenv(msiEndpointEnvVar), nil) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
req.Header.Set("secret", os.Getenv(msiSecretEnvVar)) |
||||
q := req.URL.Query() |
||||
q.Set(apiVersionQueryParameterName, azureMLAPIVersion) |
||||
q.Set(resourceQueryParameterName, resource) |
||||
q.Set("clientid", os.Getenv("DEFAULT_IDENTITY_CLIENT_ID")) |
||||
if cid, ok := id.(UserAssignedClientID); ok { |
||||
q.Set("clientid", string(cid)) |
||||
} |
||||
req.URL.RawQuery = q.Encode() |
||||
return req, nil |
||||
} |
||||
@ -0,0 +1,37 @@ |
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package managedidentity |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"io" |
||||
"net/http" |
||||
"net/url" |
||||
"os" |
||||
"strings" |
||||
) |
||||
|
||||
func createCloudShellAuthRequest(ctx context.Context, resource string) (*http.Request, error) { |
||||
msiEndpoint := os.Getenv(msiEndpointEnvVar) |
||||
msiEndpointParsed, err := url.Parse(msiEndpoint) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("couldn't parse %q: %s", msiEndpoint, err) |
||||
} |
||||
|
||||
data := url.Values{} |
||||
data.Set(resourceQueryParameterName, resource) |
||||
msiDataEncoded := data.Encode() |
||||
body := io.NopCloser(strings.NewReader(msiDataEncoded)) |
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, msiEndpointParsed.String(), body) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("error creating http request %s", err) |
||||
} |
||||
|
||||
req.Header.Set(metaHTTPHeaderName, "true") |
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") |
||||
|
||||
return req, nil |
||||
} |
||||
@ -0,0 +1,717 @@ |
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
/* |
||||
Package managedidentity provides a client for retrieval of Managed Identity applications. |
||||
The Managed Identity Client is used to acquire a token for managed identity assigned to |
||||
an azure resource such as Azure function, app service, virtual machine, etc. to acquire a token |
||||
without using credentials. |
||||
*/ |
||||
package managedidentity |
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/json" |
||||
"fmt" |
||||
"io" |
||||
"net/http" |
||||
"net/url" |
||||
"os" |
||||
"path/filepath" |
||||
"runtime" |
||||
"strings" |
||||
"sync/atomic" |
||||
"time" |
||||
|
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/errors" |
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base" |
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/base/storage" |
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops" |
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/accesstokens" |
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/oauth/ops/authority" |
||||
"github.com/AzureAD/microsoft-authentication-library-for-go/apps/internal/shared" |
||||
) |
||||
|
||||
// AuthResult contains the results of one token acquisition operation.
|
||||
// For details see https://aka.ms/msal-net-authenticationresult
|
||||
type AuthResult = base.AuthResult |
||||
|
||||
type TokenSource = base.TokenSource |
||||
|
||||
const ( |
||||
TokenSourceIdentityProvider = base.TokenSourceIdentityProvider |
||||
TokenSourceCache = base.TokenSourceCache |
||||
) |
||||
|
||||
const ( |
||||
// DefaultToIMDS indicates that the source is defaulted to IMDS when no environment variables are set.
|
||||
DefaultToIMDS Source = "DefaultToIMDS" |
||||
AzureArc Source = "AzureArc" |
||||
ServiceFabric Source = "ServiceFabric" |
||||
CloudShell Source = "CloudShell" |
||||
AzureML Source = "AzureML" |
||||
AppService Source = "AppService" |
||||
|
||||
// General request query parameter names
|
||||
metaHTTPHeaderName = "Metadata" |
||||
apiVersionQueryParameterName = "api-version" |
||||
resourceQueryParameterName = "resource" |
||||
wwwAuthenticateHeaderName = "www-authenticate" |
||||
|
||||
// UAMI query parameter name
|
||||
miQueryParameterClientId = "client_id" |
||||
miQueryParameterObjectId = "object_id" |
||||
miQueryParameterPrincipalId = "principal_id" |
||||
miQueryParameterResourceIdIMDS = "msi_res_id" |
||||
miQueryParameterResourceId = "mi_res_id" |
||||
|
||||
// IMDS
|
||||
imdsDefaultEndpoint = "http://169.254.169.254/metadata/identity/oauth2/token" |
||||
imdsAPIVersion = "2018-02-01" |
||||
systemAssignedManagedIdentity = "system_assigned_managed_identity" |
||||
|
||||
// Azure Arc
|
||||
azureArcEndpoint = "http://127.0.0.1:40342/metadata/identity/oauth2/token" |
||||
azureArcAPIVersion = "2020-06-01" |
||||
azureArcFileExtension = ".key" |
||||
azureArcMaxFileSizeBytes int64 = 4096 |
||||
linuxTokenPath = "/var/opt/azcmagent/tokens" // #nosec G101
|
||||
linuxHimdsPath = "/opt/azcmagent/bin/himds" |
||||
azureConnectedMachine = "AzureConnectedMachineAgent" |
||||
himdsExecutableName = "himds.exe" |
||||
tokenName = "Tokens" |
||||
|
||||
// App Service
|
||||
appServiceAPIVersion = "2019-08-01" |
||||
|
||||
// AzureML
|
||||
azureMLAPIVersion = "2017-09-01" |
||||
// Service Fabric
|
||||
serviceFabricAPIVersion = "2019-07-01-preview" |
||||
|
||||
// Environment Variables
|
||||
identityEndpointEnvVar = "IDENTITY_ENDPOINT" |
||||
identityHeaderEnvVar = "IDENTITY_HEADER" |
||||
azurePodIdentityAuthorityHostEnvVar = "AZURE_POD_IDENTITY_AUTHORITY_HOST" |
||||
imdsEndVar = "IMDS_ENDPOINT" |
||||
msiEndpointEnvVar = "MSI_ENDPOINT" |
||||
msiSecretEnvVar = "MSI_SECRET" |
||||
identityServerThumbprintEnvVar = "IDENTITY_SERVER_THUMBPRINT" |
||||
|
||||
defaultRetryCount = 3 |
||||
) |
||||
|
||||
var retryCodesForIMDS = []int{ |
||||
http.StatusNotFound, // 404
|
||||
http.StatusGone, // 410
|
||||
http.StatusTooManyRequests, // 429
|
||||
http.StatusInternalServerError, // 500
|
||||
http.StatusNotImplemented, // 501
|
||||
http.StatusBadGateway, // 502
|
||||
http.StatusServiceUnavailable, // 503
|
||||
http.StatusGatewayTimeout, // 504
|
||||
http.StatusHTTPVersionNotSupported, // 505
|
||||
http.StatusVariantAlsoNegotiates, // 506
|
||||
http.StatusInsufficientStorage, // 507
|
||||
http.StatusLoopDetected, // 508
|
||||
http.StatusNotExtended, // 510
|
||||
http.StatusNetworkAuthenticationRequired, // 511
|
||||
} |
||||
|
||||
var retryStatusCodes = []int{ |
||||
http.StatusRequestTimeout, // 408
|
||||
http.StatusTooManyRequests, // 429
|
||||
http.StatusInternalServerError, // 500
|
||||
http.StatusBadGateway, // 502
|
||||
http.StatusServiceUnavailable, // 503
|
||||
http.StatusGatewayTimeout, // 504
|
||||
} |
||||
|
||||
var getAzureArcPlatformPath = func(platform string) string { |
||||
switch platform { |
||||
case "windows": |
||||
return filepath.Join(os.Getenv("ProgramData"), azureConnectedMachine, tokenName) |
||||
case "linux": |
||||
return linuxTokenPath |
||||
default: |
||||
return "" |
||||
} |
||||
} |
||||
|
||||
var getAzureArcHimdsFilePath = func(platform string) string { |
||||
switch platform { |
||||
case "windows": |
||||
return filepath.Join(os.Getenv("ProgramData"), azureConnectedMachine, himdsExecutableName) |
||||
case "linux": |
||||
return linuxHimdsPath |
||||
default: |
||||
return "" |
||||
} |
||||
} |
||||
|
||||
type Source string |
||||
|
||||
type ID interface { |
||||
value() string |
||||
} |
||||
|
||||
type systemAssignedValue string // its private for a reason to make the input consistent.
|
||||
type UserAssignedClientID string |
||||
type UserAssignedObjectID string |
||||
type UserAssignedResourceID string |
||||
|
||||
func (s systemAssignedValue) value() string { return string(s) } |
||||
func (c UserAssignedClientID) value() string { return string(c) } |
||||
func (o UserAssignedObjectID) value() string { return string(o) } |
||||
func (r UserAssignedResourceID) value() string { return string(r) } |
||||
func SystemAssigned() ID { |
||||
return systemAssignedValue(systemAssignedManagedIdentity) |
||||
} |
||||
|
||||
// cache never uses the client because instance discovery is always disabled.
|
||||
var cacheManager *storage.Manager = storage.New(nil) |
||||
|
||||
type Client struct { |
||||
httpClient ops.HTTPClient |
||||
miType ID |
||||
source Source |
||||
authParams authority.AuthParams |
||||
retryPolicyEnabled bool |
||||
canRefresh *atomic.Value |
||||
} |
||||
|
||||
type AcquireTokenOptions struct { |
||||
claims string |
||||
} |
||||
|
||||
type ClientOption func(*Client) |
||||
|
||||
type AcquireTokenOption func(o *AcquireTokenOptions) |
||||
|
||||
// WithClaims sets additional claims to request for the token, such as those required by token revocation or conditional access policies.
|
||||
// Use this option when Azure AD returned a claims challenge for a prior request. The argument must be decoded.
|
||||
func WithClaims(claims string) AcquireTokenOption { |
||||
return func(o *AcquireTokenOptions) { |
||||
o.claims = claims |
||||
} |
||||
} |
||||
|
||||
// WithHTTPClient allows for a custom HTTP client to be set.
|
||||
func WithHTTPClient(httpClient ops.HTTPClient) ClientOption { |
||||
return func(c *Client) { |
||||
c.httpClient = httpClient |
||||
} |
||||
} |
||||
|
||||
func WithRetryPolicyDisabled() ClientOption { |
||||
return func(c *Client) { |
||||
c.retryPolicyEnabled = false |
||||
} |
||||
} |
||||
|
||||
// Client to be used to acquire tokens for managed identity.
|
||||
// ID: [SystemAssigned], [UserAssignedClientID], [UserAssignedResourceID], [UserAssignedObjectID]
|
||||
//
|
||||
// Options: [WithHTTPClient]
|
||||
func New(id ID, options ...ClientOption) (Client, error) { |
||||
source, err := GetSource() |
||||
if err != nil { |
||||
return Client{}, err |
||||
} |
||||
|
||||
// Check for user-assigned restrictions based on the source
|
||||
switch source { |
||||
case AzureArc: |
||||
switch id.(type) { |
||||
case UserAssignedClientID, UserAssignedResourceID, UserAssignedObjectID: |
||||
return Client{}, errors.New("Azure Arc doesn't support user-assigned managed identities") |
||||
} |
||||
case AzureML: |
||||
switch id.(type) { |
||||
case UserAssignedObjectID, UserAssignedResourceID: |
||||
return Client{}, errors.New("Azure ML supports specifying a user-assigned managed identity by client ID only") |
||||
} |
||||
case CloudShell: |
||||
switch id.(type) { |
||||
case UserAssignedClientID, UserAssignedResourceID, UserAssignedObjectID: |
||||
return Client{}, errors.New("Cloud Shell doesn't support user-assigned managed identities") |
||||
} |
||||
case ServiceFabric: |
||||
switch id.(type) { |
||||
case UserAssignedClientID, UserAssignedResourceID, UserAssignedObjectID: |
||||
return Client{}, errors.New("Service Fabric API doesn't support specifying a user-assigned identity. The identity is determined by cluster resource configuration. See https://aka.ms/servicefabricmi") |
||||
} |
||||
} |
||||
|
||||
switch t := id.(type) { |
||||
case UserAssignedClientID: |
||||
if len(string(t)) == 0 { |
||||
return Client{}, fmt.Errorf("empty %T", t) |
||||
} |
||||
case UserAssignedResourceID: |
||||
if len(string(t)) == 0 { |
||||
return Client{}, fmt.Errorf("empty %T", t) |
||||
} |
||||
case UserAssignedObjectID: |
||||
if len(string(t)) == 0 { |
||||
return Client{}, fmt.Errorf("empty %T", t) |
||||
} |
||||
case systemAssignedValue: |
||||
default: |
||||
return Client{}, fmt.Errorf("unsupported type %T", id) |
||||
} |
||||
zero := atomic.Value{} |
||||
zero.Store(false) |
||||
client := Client{ |
||||
miType: id, |
||||
httpClient: shared.DefaultClient, |
||||
retryPolicyEnabled: true, |
||||
source: source, |
||||
canRefresh: &zero, |
||||
} |
||||
for _, option := range options { |
||||
option(&client) |
||||
} |
||||
fakeAuthInfo, err := authority.NewInfoFromAuthorityURI("https://login.microsoftonline.com/managed_identity", false, true) |
||||
if err != nil { |
||||
return Client{}, err |
||||
} |
||||
client.authParams = authority.NewAuthParams(client.miType.value(), fakeAuthInfo) |
||||
return client, nil |
||||
} |
||||
|
||||
// GetSource detects and returns the managed identity source available on the environment.
|
||||
func GetSource() (Source, error) { |
||||
identityEndpoint := os.Getenv(identityEndpointEnvVar) |
||||
identityHeader := os.Getenv(identityHeaderEnvVar) |
||||
identityServerThumbprint := os.Getenv(identityServerThumbprintEnvVar) |
||||
msiEndpoint := os.Getenv(msiEndpointEnvVar) |
||||
msiSecret := os.Getenv(msiSecretEnvVar) |
||||
imdsEndpoint := os.Getenv(imdsEndVar) |
||||
|
||||
if identityEndpoint != "" && identityHeader != "" { |
||||
if identityServerThumbprint != "" { |
||||
return ServiceFabric, nil |
||||
} |
||||
return AppService, nil |
||||
} else if msiEndpoint != "" { |
||||
if msiSecret != "" { |
||||
return AzureML, nil |
||||
} else { |
||||
return CloudShell, nil |
||||
} |
||||
} else if isAzureArcEnvironment(identityEndpoint, imdsEndpoint) { |
||||
return AzureArc, nil |
||||
} |
||||
|
||||
return DefaultToIMDS, nil |
||||
} |
||||
|
||||
// This function wraps time.Now() and is used for refreshing the application
|
||||
// was created to test the function against refreshin
|
||||
var now = time.Now |
||||
|
||||
// Acquires tokens from the configured managed identity on an azure resource.
|
||||
//
|
||||
// Resource: scopes application is requesting access to
|
||||
// Options: [WithClaims]
|
||||
func (c Client) AcquireToken(ctx context.Context, resource string, options ...AcquireTokenOption) (AuthResult, error) { |
||||
resource = strings.TrimSuffix(resource, "/.default") |
||||
o := AcquireTokenOptions{} |
||||
for _, option := range options { |
||||
option(&o) |
||||
} |
||||
c.authParams.Scopes = []string{resource} |
||||
|
||||
// ignore cached access tokens when given claims
|
||||
if o.claims == "" { |
||||
stResp, err := cacheManager.Read(ctx, c.authParams) |
||||
if err != nil { |
||||
return AuthResult{}, err |
||||
} |
||||
ar, err := base.AuthResultFromStorage(stResp) |
||||
if err == nil { |
||||
if !stResp.AccessToken.RefreshOn.T.IsZero() && !stResp.AccessToken.RefreshOn.T.After(now()) && c.canRefresh.CompareAndSwap(false, true) { |
||||
defer c.canRefresh.Store(false) |
||||
if tr, er := c.getToken(ctx, resource); er == nil { |
||||
return tr, nil |
||||
} |
||||
} |
||||
ar.AccessToken, err = c.authParams.AuthnScheme.FormatAccessToken(ar.AccessToken) |
||||
return ar, err |
||||
} |
||||
} |
||||
return c.getToken(ctx, resource) |
||||
} |
||||
|
||||
func (c Client) getToken(ctx context.Context, resource string) (AuthResult, error) { |
||||
switch c.source { |
||||
case AzureArc: |
||||
return c.acquireTokenForAzureArc(ctx, resource) |
||||
case AzureML: |
||||
return c.acquireTokenForAzureML(ctx, resource) |
||||
case CloudShell: |
||||
return c.acquireTokenForCloudShell(ctx, resource) |
||||
case DefaultToIMDS: |
||||
return c.acquireTokenForIMDS(ctx, resource) |
||||
case AppService: |
||||
return c.acquireTokenForAppService(ctx, resource) |
||||
case ServiceFabric: |
||||
return c.acquireTokenForServiceFabric(ctx, resource) |
||||
default: |
||||
return AuthResult{}, fmt.Errorf("unsupported source %q", c.source) |
||||
} |
||||
} |
||||
|
||||
func (c Client) acquireTokenForAppService(ctx context.Context, resource string) (AuthResult, error) { |
||||
req, err := createAppServiceAuthRequest(ctx, c.miType, resource) |
||||
if err != nil { |
||||
return AuthResult{}, err |
||||
} |
||||
tokenResponse, err := c.getTokenForRequest(req, resource) |
||||
if err != nil { |
||||
return AuthResult{}, err |
||||
} |
||||
return authResultFromToken(c.authParams, tokenResponse) |
||||
} |
||||
|
||||
func (c Client) acquireTokenForIMDS(ctx context.Context, resource string) (AuthResult, error) { |
||||
req, err := createIMDSAuthRequest(ctx, c.miType, resource) |
||||
if err != nil { |
||||
return AuthResult{}, err |
||||
} |
||||
tokenResponse, err := c.getTokenForRequest(req, resource) |
||||
if err != nil { |
||||
return AuthResult{}, err |
||||
} |
||||
return authResultFromToken(c.authParams, tokenResponse) |
||||
} |
||||
|
||||
func (c Client) acquireTokenForCloudShell(ctx context.Context, resource string) (AuthResult, error) { |
||||
req, err := createCloudShellAuthRequest(ctx, resource) |
||||
if err != nil { |
||||
return AuthResult{}, err |
||||
} |
||||
tokenResponse, err := c.getTokenForRequest(req, resource) |
||||
if err != nil { |
||||
return AuthResult{}, err |
||||
} |
||||
return authResultFromToken(c.authParams, tokenResponse) |
||||
} |
||||
|
||||
func (c Client) acquireTokenForAzureML(ctx context.Context, resource string) (AuthResult, error) { |
||||
req, err := createAzureMLAuthRequest(ctx, c.miType, resource) |
||||
if err != nil { |
||||
return AuthResult{}, err |
||||
} |
||||
tokenResponse, err := c.getTokenForRequest(req, resource) |
||||
if err != nil { |
||||
return AuthResult{}, err |
||||
} |
||||
return authResultFromToken(c.authParams, tokenResponse) |
||||
} |
||||
|
||||
func (c Client) acquireTokenForServiceFabric(ctx context.Context, resource string) (AuthResult, error) { |
||||
req, err := createServiceFabricAuthRequest(ctx, resource) |
||||
if err != nil { |
||||
return AuthResult{}, err |
||||
} |
||||
tokenResponse, err := c.getTokenForRequest(req, resource) |
||||
if err != nil { |
||||
return AuthResult{}, err |
||||
} |
||||
return authResultFromToken(c.authParams, tokenResponse) |
||||
} |
||||
|
||||
func (c Client) acquireTokenForAzureArc(ctx context.Context, resource string) (AuthResult, error) { |
||||
req, err := createAzureArcAuthRequest(ctx, resource, "") |
||||
if err != nil { |
||||
return AuthResult{}, err |
||||
} |
||||
|
||||
response, err := c.httpClient.Do(req) |
||||
if err != nil { |
||||
return AuthResult{}, err |
||||
} |
||||
defer response.Body.Close() |
||||
|
||||
if response.StatusCode != http.StatusUnauthorized { |
||||
return AuthResult{}, fmt.Errorf("expected a 401 response, received %d", response.StatusCode) |
||||
} |
||||
|
||||
secret, err := c.getAzureArcSecretKey(response, runtime.GOOS) |
||||
if err != nil { |
||||
return AuthResult{}, err |
||||
} |
||||
|
||||
secondRequest, err := createAzureArcAuthRequest(ctx, resource, string(secret)) |
||||
if err != nil { |
||||
return AuthResult{}, err |
||||
} |
||||
|
||||
tokenResponse, err := c.getTokenForRequest(secondRequest, resource) |
||||
if err != nil { |
||||
return AuthResult{}, err |
||||
} |
||||
return authResultFromToken(c.authParams, tokenResponse) |
||||
} |
||||
|
||||
func authResultFromToken(authParams authority.AuthParams, token accesstokens.TokenResponse) (AuthResult, error) { |
||||
if cacheManager == nil { |
||||
return AuthResult{}, errors.New("cache instance is nil") |
||||
} |
||||
account, err := cacheManager.Write(authParams, token) |
||||
if err != nil { |
||||
return AuthResult{}, err |
||||
} |
||||
// if refreshOn is not set, set it to half of the time until expiry if expiry is more than 2 hours away
|
||||
if token.RefreshOn.T.IsZero() { |
||||
if lifetime := time.Until(token.ExpiresOn); lifetime > 2*time.Hour { |
||||
token.RefreshOn.T = time.Now().Add(lifetime / 2) |
||||
} |
||||
} |
||||
ar, err := base.NewAuthResult(token, account) |
||||
if err != nil { |
||||
return AuthResult{}, err |
||||
} |
||||
ar.AccessToken, err = authParams.AuthnScheme.FormatAccessToken(ar.AccessToken) |
||||
return ar, err |
||||
} |
||||
|
||||
// contains checks if the element is present in the list.
|
||||
func contains[T comparable](list []T, element T) bool { |
||||
for _, v := range list { |
||||
if v == element { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
// retry performs an HTTP request with retries based on the provided options.
|
||||
func (c Client) retry(maxRetries int, req *http.Request) (*http.Response, error) { |
||||
var resp *http.Response |
||||
var err error |
||||
for attempt := 0; attempt < maxRetries; attempt++ { |
||||
tryCtx, tryCancel := context.WithTimeout(req.Context(), time.Minute) |
||||
defer tryCancel() |
||||
if resp != nil && resp.Body != nil { |
||||
_, _ = io.Copy(io.Discard, resp.Body) |
||||
resp.Body.Close() |
||||
} |
||||
cloneReq := req.Clone(tryCtx) |
||||
resp, err = c.httpClient.Do(cloneReq) |
||||
retrylist := retryStatusCodes |
||||
if c.source == DefaultToIMDS { |
||||
retrylist = retryCodesForIMDS |
||||
} |
||||
if err == nil && !contains(retrylist, resp.StatusCode) { |
||||
return resp, nil |
||||
} |
||||
select { |
||||
case <-time.After(time.Second): |
||||
case <-req.Context().Done(): |
||||
err = req.Context().Err() |
||||
return resp, err |
||||
} |
||||
} |
||||
return resp, err |
||||
} |
||||
|
||||
func (c Client) getTokenForRequest(req *http.Request, resource string) (accesstokens.TokenResponse, error) { |
||||
r := accesstokens.TokenResponse{} |
||||
var resp *http.Response |
||||
var err error |
||||
|
||||
if c.retryPolicyEnabled { |
||||
resp, err = c.retry(defaultRetryCount, req) |
||||
} else { |
||||
resp, err = c.httpClient.Do(req) |
||||
} |
||||
if err != nil { |
||||
return r, err |
||||
} |
||||
responseBytes, err := io.ReadAll(resp.Body) |
||||
defer resp.Body.Close() |
||||
if err != nil { |
||||
return r, err |
||||
} |
||||
switch resp.StatusCode { |
||||
case http.StatusOK, http.StatusAccepted: |
||||
default: |
||||
sd := strings.TrimSpace(string(responseBytes)) |
||||
if sd != "" { |
||||
return r, errors.CallErr{ |
||||
Req: req, |
||||
Resp: resp, |
||||
Err: fmt.Errorf("http call(%s)(%s) error: reply status code was %d:\n%s", |
||||
req.URL.String(), |
||||
req.Method, |
||||
resp.StatusCode, |
||||
sd), |
||||
} |
||||
} |
||||
return r, errors.CallErr{ |
||||
Req: req, |
||||
Resp: resp, |
||||
Err: fmt.Errorf("http call(%s)(%s) error: reply status code was %d", req.URL.String(), req.Method, resp.StatusCode), |
||||
} |
||||
} |
||||
|
||||
err = json.Unmarshal(responseBytes, &r) |
||||
if err != nil { |
||||
return r, errors.InvalidJsonErr{ |
||||
Err: fmt.Errorf("error parsing the json error: %s", err), |
||||
} |
||||
} |
||||
r.GrantedScopes.Slice = append(r.GrantedScopes.Slice, resource) |
||||
|
||||
return r, err |
||||
} |
||||
|
||||
func createAppServiceAuthRequest(ctx context.Context, id ID, resource string) (*http.Request, error) { |
||||
identityEndpoint := os.Getenv(identityEndpointEnvVar) |
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, identityEndpoint, nil) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
req.Header.Set("X-IDENTITY-HEADER", os.Getenv(identityHeaderEnvVar)) |
||||
q := req.URL.Query() |
||||
q.Set("api-version", appServiceAPIVersion) |
||||
q.Set("resource", resource) |
||||
switch t := id.(type) { |
||||
case UserAssignedClientID: |
||||
q.Set(miQueryParameterClientId, string(t)) |
||||
case UserAssignedResourceID: |
||||
q.Set(miQueryParameterResourceId, string(t)) |
||||
case UserAssignedObjectID: |
||||
q.Set(miQueryParameterObjectId, string(t)) |
||||
case systemAssignedValue: |
||||
default: |
||||
return nil, fmt.Errorf("unsupported type %T", id) |
||||
} |
||||
req.URL.RawQuery = q.Encode() |
||||
return req, nil |
||||
} |
||||
|
||||
func createIMDSAuthRequest(ctx context.Context, id ID, resource string) (*http.Request, error) { |
||||
msiEndpoint, err := url.Parse(imdsDefaultEndpoint) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("couldn't parse %q: %s", imdsDefaultEndpoint, err) |
||||
} |
||||
msiParameters := msiEndpoint.Query() |
||||
msiParameters.Set(apiVersionQueryParameterName, imdsAPIVersion) |
||||
msiParameters.Set(resourceQueryParameterName, resource) |
||||
|
||||
switch t := id.(type) { |
||||
case UserAssignedClientID: |
||||
msiParameters.Set(miQueryParameterClientId, string(t)) |
||||
case UserAssignedResourceID: |
||||
msiParameters.Set(miQueryParameterResourceIdIMDS, string(t)) |
||||
case UserAssignedObjectID: |
||||
msiParameters.Set(miQueryParameterObjectId, string(t)) |
||||
case systemAssignedValue: // not adding anything
|
||||
default: |
||||
return nil, fmt.Errorf("unsupported type %T", id) |
||||
} |
||||
|
||||
msiEndpoint.RawQuery = msiParameters.Encode() |
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, msiEndpoint.String(), nil) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("error creating http request %s", err) |
||||
} |
||||
req.Header.Set(metaHTTPHeaderName, "true") |
||||
return req, nil |
||||
} |
||||
|
||||
func createAzureArcAuthRequest(ctx context.Context, resource string, key string) (*http.Request, error) { |
||||
identityEndpoint := os.Getenv(identityEndpointEnvVar) |
||||
if identityEndpoint == "" { |
||||
identityEndpoint = azureArcEndpoint |
||||
} |
||||
msiEndpoint, parseErr := url.Parse(identityEndpoint) |
||||
|
||||
if parseErr != nil { |
||||
return nil, fmt.Errorf("couldn't parse %q: %s", identityEndpoint, parseErr) |
||||
} |
||||
|
||||
msiParameters := msiEndpoint.Query() |
||||
msiParameters.Set(apiVersionQueryParameterName, azureArcAPIVersion) |
||||
msiParameters.Set(resourceQueryParameterName, resource) |
||||
|
||||
msiEndpoint.RawQuery = msiParameters.Encode() |
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, msiEndpoint.String(), nil) |
||||
if err != nil { |
||||
return nil, fmt.Errorf("error creating http request %s", err) |
||||
} |
||||
req.Header.Set(metaHTTPHeaderName, "true") |
||||
|
||||
if key != "" { |
||||
req.Header.Set("Authorization", fmt.Sprintf("Basic %s", key)) |
||||
} |
||||
|
||||
return req, nil |
||||
} |
||||
|
||||
func isAzureArcEnvironment(identityEndpoint, imdsEndpoint string) bool { |
||||
if identityEndpoint != "" && imdsEndpoint != "" { |
||||
return true |
||||
} |
||||
himdsFilePath := getAzureArcHimdsFilePath(runtime.GOOS) |
||||
if himdsFilePath != "" { |
||||
if _, err := os.Stat(himdsFilePath); err == nil { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
func (c *Client) getAzureArcSecretKey(response *http.Response, platform string) (string, error) { |
||||
wwwAuthenticateHeader := response.Header.Get(wwwAuthenticateHeaderName) |
||||
|
||||
if len(wwwAuthenticateHeader) == 0 { |
||||
return "", errors.New("response has no www-authenticate header") |
||||
} |
||||
|
||||
// check if the platform is supported
|
||||
expectedSecretFilePath := getAzureArcPlatformPath(platform) |
||||
if expectedSecretFilePath == "" { |
||||
return "", errors.New("platform not supported, expected linux or windows") |
||||
} |
||||
|
||||
parts := strings.Split(wwwAuthenticateHeader, "Basic realm=") |
||||
if len(parts) < 2 { |
||||
return "", fmt.Errorf("basic realm= not found in the string, instead found: %s", wwwAuthenticateHeader) |
||||
} |
||||
|
||||
secretFilePath := parts |
||||
|
||||
// check that the file in the file path is a .key file
|
||||
fileName := filepath.Base(secretFilePath[1]) |
||||
if !strings.HasSuffix(fileName, azureArcFileExtension) { |
||||
return "", fmt.Errorf("invalid file extension, expected %s, got %s", azureArcFileExtension, filepath.Ext(fileName)) |
||||
} |
||||
|
||||
// check that file path from header matches the expected file path for the platform
|
||||
if expectedSecretFilePath != filepath.Dir(secretFilePath[1]) { |
||||
return "", fmt.Errorf("invalid file path, expected %s, got %s", expectedSecretFilePath, filepath.Dir(secretFilePath[1])) |
||||
} |
||||
|
||||
fileInfo, err := os.Stat(secretFilePath[1]) |
||||
if err != nil { |
||||
return "", fmt.Errorf("failed to get metadata for %s due to error: %s", secretFilePath[1], err) |
||||
} |
||||
|
||||
// Throw an error if the secret file's size is greater than 4096 bytes
|
||||
if s := fileInfo.Size(); s > azureArcMaxFileSizeBytes { |
||||
return "", fmt.Errorf("invalid secret file size, expected %d, file size was %d", azureArcMaxFileSizeBytes, s) |
||||
} |
||||
|
||||
// Attempt to read the contents of the secret file
|
||||
secret, err := os.ReadFile(secretFilePath[1]) |
||||
if err != nil { |
||||
return "", fmt.Errorf("failed to read %q due to error: %s", secretFilePath[1], err) |
||||
} |
||||
|
||||
return string(secret), nil |
||||
} |
||||
@ -0,0 +1,25 @@ |
||||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
package managedidentity |
||||
|
||||
import ( |
||||
"context" |
||||
"net/http" |
||||
"os" |
||||
) |
||||
|
||||
func createServiceFabricAuthRequest(ctx context.Context, resource string) (*http.Request, error) { |
||||
identityEndpoint := os.Getenv(identityEndpointEnvVar) |
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, identityEndpoint, nil) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
req.Header.Set("Accept", "application/json") |
||||
req.Header.Set("Secret", os.Getenv(identityHeaderEnvVar)) |
||||
q := req.URL.Query() |
||||
q.Set("api-version", serviceFabricAPIVersion) |
||||
q.Set("resource", resource) |
||||
req.URL.RawQuery = q.Encode() |
||||
return req, nil |
||||
} |
||||
13
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/public/public.go
generated
vendored
13
vendor/github.com/AzureAD/microsoft-authentication-library-for-go/apps/public/public.go
generated
vendored
@ -0,0 +1,415 @@ |
||||
package godo |
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/json" |
||||
"fmt" |
||||
"net/http" |
||||
"time" |
||||
) |
||||
|
||||
const partnerNetworkConnectBasePath = "/v2/partner_network_connect/attachments" |
||||
|
||||
// PartnerAttachmentService is an interface for managing Partner Attachments with the
|
||||
// DigitalOcean API.
|
||||
// See: https://docs.digitalocean.com/reference/api/api-reference/#tag/PartnerNetworkConnect
|
||||
type PartnerAttachmentService interface { |
||||
List(context.Context, *ListOptions) ([]*PartnerAttachment, *Response, error) |
||||
Create(context.Context, *PartnerAttachmentCreateRequest) (*PartnerAttachment, *Response, error) |
||||
Get(context.Context, string) (*PartnerAttachment, *Response, error) |
||||
Update(context.Context, string, *PartnerAttachmentUpdateRequest) (*PartnerAttachment, *Response, error) |
||||
Delete(context.Context, string) (*Response, error) |
||||
GetServiceKey(context.Context, string) (*ServiceKey, *Response, error) |
||||
SetRoutes(context.Context, string, *PartnerAttachmentSetRoutesRequest) (*PartnerAttachment, *Response, error) |
||||
ListRoutes(context.Context, string, *ListOptions) ([]*RemoteRoute, *Response, error) |
||||
GetBGPAuthKey(ctx context.Context, iaID string) (*BgpAuthKey, *Response, error) |
||||
RegenerateServiceKey(ctx context.Context, iaID string) (*RegenerateServiceKey, *Response, error) |
||||
} |
||||
|
||||
var _ PartnerAttachmentService = &PartnerAttachmentServiceOp{} |
||||
|
||||
// PartnerAttachmentServiceOp interfaces with the Partner Attachment endpoints in the DigitalOcean API.
|
||||
type PartnerAttachmentServiceOp struct { |
||||
client *Client |
||||
} |
||||
|
||||
// PartnerAttachmentCreateRequest represents a request to create a Partner Attachment.
|
||||
type PartnerAttachmentCreateRequest struct { |
||||
// Name is the name of the Partner Attachment
|
||||
Name string `json:"name,omitempty"` |
||||
// ConnectionBandwidthInMbps is the bandwidth of the connection in Mbps
|
||||
ConnectionBandwidthInMbps int `json:"connection_bandwidth_in_mbps,omitempty"` |
||||
// Region is the region where the Partner Attachment is created
|
||||
Region string `json:"region,omitempty"` |
||||
// NaaSProvider is the name of the Network as a Service provider
|
||||
NaaSProvider string `json:"naas_provider,omitempty"` |
||||
// VPCIDs is the IDs of the VPCs to which the Partner Attachment is connected to
|
||||
VPCIDs []string `json:"vpc_ids,omitempty"` |
||||
// BGP is the BGP configuration of the Partner Attachment
|
||||
BGP BGP `json:"bgp,omitempty"` |
||||
} |
||||
|
||||
type partnerAttachmentRequestBody struct { |
||||
// Name is the name of the Partner Attachment
|
||||
Name string `json:"name,omitempty"` |
||||
// ConnectionBandwidthInMbps is the bandwidth of the connection in Mbps
|
||||
ConnectionBandwidthInMbps int `json:"connection_bandwidth_in_mbps,omitempty"` |
||||
// Region is the region where the Partner Attachment is created
|
||||
Region string `json:"region,omitempty"` |
||||
// NaaSProvider is the name of the Network as a Service provider
|
||||
NaaSProvider string `json:"naas_provider,omitempty"` |
||||
// VPCIDs is the IDs of the VPCs to which the Partner Attachment is connected to
|
||||
VPCIDs []string `json:"vpc_ids,omitempty"` |
||||
// BGP is the BGP configuration of the Partner Attachment
|
||||
BGP *BGPInput `json:"bgp,omitempty"` |
||||
} |
||||
|
||||
func (req *PartnerAttachmentCreateRequest) buildReq() *partnerAttachmentRequestBody { |
||||
request := &partnerAttachmentRequestBody{ |
||||
Name: req.Name, |
||||
ConnectionBandwidthInMbps: req.ConnectionBandwidthInMbps, |
||||
Region: req.Region, |
||||
NaaSProvider: req.NaaSProvider, |
||||
VPCIDs: req.VPCIDs, |
||||
} |
||||
|
||||
if req.BGP != (BGP{}) { |
||||
request.BGP = &BGPInput{ |
||||
LocalASN: req.BGP.LocalASN, |
||||
LocalRouterIP: req.BGP.LocalRouterIP, |
||||
PeerASN: req.BGP.PeerASN, |
||||
PeerRouterIP: req.BGP.PeerRouterIP, |
||||
AuthKey: req.BGP.AuthKey, |
||||
} |
||||
} |
||||
|
||||
return request |
||||
} |
||||
|
||||
// PartnerAttachmentUpdateRequest represents a request to update a Partner Attachment.
|
||||
type PartnerAttachmentUpdateRequest struct { |
||||
// Name is the name of the Partner Attachment
|
||||
Name string `json:"name,omitempty"` |
||||
//VPCIDs is the IDs of the VPCs to which the Partner Attachment is connected to
|
||||
VPCIDs []string `json:"vpc_ids,omitempty"` |
||||
} |
||||
|
||||
type PartnerAttachmentSetRoutesRequest struct { |
||||
// Routes is the list of routes to be used for the Partner Attachment
|
||||
Routes []string `json:"routes,omitempty"` |
||||
} |
||||
|
||||
// BGP represents the BGP configuration of a Partner Attachment.
|
||||
type BGP struct { |
||||
// LocalASN is the local ASN
|
||||
LocalASN int `json:"local_asn,omitempty"` |
||||
// LocalRouterIP is the local router IP
|
||||
LocalRouterIP string `json:"local_router_ip,omitempty"` |
||||
// PeerASN is the peer ASN
|
||||
PeerASN int `json:"peer_asn,omitempty"` |
||||
// PeerRouterIP is the peer router IP
|
||||
PeerRouterIP string `json:"peer_router_ip,omitempty"` |
||||
// AuthKey is the authentication key
|
||||
AuthKey string `json:"auth_key,omitempty"` |
||||
} |
||||
|
||||
func (b *BGP) UnmarshalJSON(data []byte) error { |
||||
type Alias BGP |
||||
aux := &struct { |
||||
LocalASN *int `json:"local_asn,omitempty"` |
||||
LocalRouterASN *int `json:"local_router_asn,omitempty"` |
||||
PeerASN *int `json:"peer_asn,omitempty"` |
||||
PeerRouterASN *int `json:"peer_router_asn,omitempty"` |
||||
*Alias |
||||
}{ |
||||
Alias: (*Alias)(b), |
||||
} |
||||
if err := json.Unmarshal(data, &aux); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if aux.LocalASN != nil { |
||||
b.LocalASN = *aux.LocalASN |
||||
} else if aux.LocalRouterASN != nil { |
||||
b.LocalASN = *aux.LocalRouterASN |
||||
} |
||||
|
||||
if aux.PeerASN != nil { |
||||
b.PeerASN = *aux.PeerASN |
||||
} else if aux.PeerRouterASN != nil { |
||||
b.PeerASN = *aux.PeerRouterASN |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// BGPInput represents the BGP configuration of a Partner Attachment.
|
||||
type BGPInput struct { |
||||
// LocalASN is the local ASN
|
||||
LocalASN int `json:"local_router_asn,omitempty"` |
||||
// LocalRouterIP is the local router IP
|
||||
LocalRouterIP string `json:"local_router_ip,omitempty"` |
||||
// PeerASN is the peer ASN
|
||||
PeerASN int `json:"peer_router_asn,omitempty"` |
||||
// PeerRouterIP is the peer router IP
|
||||
PeerRouterIP string `json:"peer_router_ip,omitempty"` |
||||
// AuthKey is the authentication key
|
||||
AuthKey string `json:"auth_key,omitempty"` |
||||
} |
||||
|
||||
// ServiceKey represents the service key of a Partner Attachment.
|
||||
type ServiceKey struct { |
||||
Value string `json:"value,omitempty"` |
||||
State string `json:"state,omitempty"` |
||||
CreatedAt time.Time `json:"created_at,omitempty"` |
||||
} |
||||
|
||||
// RemoteRoute represents a route for a Partner Attachment.
|
||||
type RemoteRoute struct { |
||||
// ID is the generated ID of the Route
|
||||
ID string `json:"id,omitempty"` |
||||
// Cidr is the CIDR of the route
|
||||
Cidr string `json:"cidr,omitempty"` |
||||
} |
||||
|
||||
// PartnerAttachment represents a DigitalOcean Partner Attachment.
|
||||
type PartnerAttachment struct { |
||||
// ID is the generated ID of the Partner Attachment
|
||||
ID string `json:"id,omitempty"` |
||||
// Name is the name of the Partner Attachment
|
||||
Name string `json:"name,omitempty"` |
||||
// State is the state of the Partner Attachment
|
||||
State string `json:"state,omitempty"` |
||||
// ConnectionBandwidthInMbps is the bandwidth of the connection in Mbps
|
||||
ConnectionBandwidthInMbps int `json:"connection_bandwidth_in_mbps,omitempty"` |
||||
// Region is the region where the Partner Attachment is created
|
||||
Region string `json:"region,omitempty"` |
||||
// NaaSProvider is the name of the Network as a Service provider
|
||||
NaaSProvider string `json:"naas_provider,omitempty"` |
||||
// VPCIDs is the IDs of the VPCs to which the Partner Attachment is connected to
|
||||
VPCIDs []string `json:"vpc_ids,omitempty"` |
||||
// BGP is the BGP configuration of the Partner Attachment
|
||||
BGP BGP `json:"bgp,omitempty"` |
||||
// CreatedAt is time when this Partner Attachment was first created
|
||||
CreatedAt time.Time `json:"created_at,omitempty"` |
||||
} |
||||
|
||||
type partnerNetworkConnectAttachmentRoot struct { |
||||
PartnerAttachment *PartnerAttachment `json:"partner_attachment"` |
||||
} |
||||
|
||||
type partnerNetworkConnectAttachmentsRoot struct { |
||||
PartnerAttachments []*PartnerAttachment `json:"partner_attachments"` |
||||
Links *Links `json:"links"` |
||||
Meta *Meta `json:"meta"` |
||||
} |
||||
|
||||
type serviceKeyRoot struct { |
||||
ServiceKey *ServiceKey `json:"service_key"` |
||||
} |
||||
|
||||
type remoteRoutesRoot struct { |
||||
RemoteRoutes []*RemoteRoute `json:"remote_routes"` |
||||
Links *Links `json:"links"` |
||||
Meta *Meta `json:"meta"` |
||||
} |
||||
|
||||
type BgpAuthKey struct { |
||||
Value string `json:"value"` |
||||
} |
||||
|
||||
type bgpAuthKeyRoot struct { |
||||
BgpAuthKey *BgpAuthKey `json:"bgp_auth_key"` |
||||
} |
||||
|
||||
type RegenerateServiceKey struct { |
||||
} |
||||
|
||||
type regenerateServiceKeyRoot struct { |
||||
RegenerateServiceKey *RegenerateServiceKey `json:"-"` |
||||
} |
||||
|
||||
// List returns a list of all Partner Attachment, with optional pagination.
|
||||
func (s *PartnerAttachmentServiceOp) List(ctx context.Context, opt *ListOptions) ([]*PartnerAttachment, *Response, error) { |
||||
path, err := addOptions(partnerNetworkConnectBasePath, opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
|
||||
root := new(partnerNetworkConnectAttachmentsRoot) |
||||
resp, err := s.client.Do(ctx, req, root) |
||||
if err != nil { |
||||
return nil, resp, err |
||||
} |
||||
if l := root.Links; l != nil { |
||||
resp.Links = l |
||||
} |
||||
if m := root.Meta; m != nil { |
||||
resp.Meta = m |
||||
} |
||||
return root.PartnerAttachments, resp, nil |
||||
} |
||||
|
||||
// Create creates a new Partner Attachment.
|
||||
func (s *PartnerAttachmentServiceOp) Create(ctx context.Context, create *PartnerAttachmentCreateRequest) (*PartnerAttachment, *Response, error) { |
||||
path := partnerNetworkConnectBasePath |
||||
|
||||
req, err := s.client.NewRequest(ctx, http.MethodPost, path, create.buildReq()) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
|
||||
root := new(partnerNetworkConnectAttachmentRoot) |
||||
resp, err := s.client.Do(ctx, req, root) |
||||
if err != nil { |
||||
return nil, resp, err |
||||
} |
||||
|
||||
return root.PartnerAttachment, resp, nil |
||||
} |
||||
|
||||
// Get returns the details of a Partner Attachment.
|
||||
func (s *PartnerAttachmentServiceOp) Get(ctx context.Context, id string) (*PartnerAttachment, *Response, error) { |
||||
path := fmt.Sprintf("%s/%s", partnerNetworkConnectBasePath, id) |
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
|
||||
root := new(partnerNetworkConnectAttachmentRoot) |
||||
resp, err := s.client.Do(ctx, req, root) |
||||
if err != nil { |
||||
return nil, resp, err |
||||
} |
||||
|
||||
return root.PartnerAttachment, resp, nil |
||||
} |
||||
|
||||
// Update updates a Partner Attachment properties.
|
||||
func (s *PartnerAttachmentServiceOp) Update(ctx context.Context, id string, update *PartnerAttachmentUpdateRequest) (*PartnerAttachment, *Response, error) { |
||||
path := fmt.Sprintf("%s/%s", partnerNetworkConnectBasePath, id) |
||||
req, err := s.client.NewRequest(ctx, http.MethodPatch, path, update) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
|
||||
root := new(partnerNetworkConnectAttachmentRoot) |
||||
resp, err := s.client.Do(ctx, req, root) |
||||
if err != nil { |
||||
return nil, resp, err |
||||
} |
||||
|
||||
return root.PartnerAttachment, resp, nil |
||||
} |
||||
|
||||
// Delete deletes a Partner Attachment.
|
||||
func (s *PartnerAttachmentServiceOp) Delete(ctx context.Context, id string) (*Response, error) { |
||||
path := fmt.Sprintf("%s/%s", partnerNetworkConnectBasePath, id) |
||||
req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
resp, err := s.client.Do(ctx, req, nil) |
||||
if err != nil { |
||||
return resp, err |
||||
} |
||||
|
||||
return resp, nil |
||||
} |
||||
|
||||
func (s *PartnerAttachmentServiceOp) GetServiceKey(ctx context.Context, id string) (*ServiceKey, *Response, error) { |
||||
path := fmt.Sprintf("%s/%s/service_key", partnerNetworkConnectBasePath, id) |
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
|
||||
root := new(serviceKeyRoot) |
||||
resp, err := s.client.Do(ctx, req, root) |
||||
if err != nil { |
||||
return nil, resp, err |
||||
} |
||||
|
||||
return root.ServiceKey, resp, nil |
||||
} |
||||
|
||||
// ListRoutes lists all remote routes for a Partner Attachment.
|
||||
func (s *PartnerAttachmentServiceOp) ListRoutes(ctx context.Context, id string, opt *ListOptions) ([]*RemoteRoute, *Response, error) { |
||||
path, err := addOptions(fmt.Sprintf("%s/%s/remote_routes", partnerNetworkConnectBasePath, id), opt) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
|
||||
root := new(remoteRoutesRoot) |
||||
resp, err := s.client.Do(ctx, req, root) |
||||
if err != nil { |
||||
return nil, resp, err |
||||
} |
||||
if l := root.Links; l != nil { |
||||
resp.Links = l |
||||
} |
||||
if m := root.Meta; m != nil { |
||||
resp.Meta = m |
||||
} |
||||
|
||||
return root.RemoteRoutes, resp, nil |
||||
} |
||||
|
||||
// SetRoutes updates specific properties of a Partner Attachment.
|
||||
func (s *PartnerAttachmentServiceOp) SetRoutes(ctx context.Context, id string, set *PartnerAttachmentSetRoutesRequest) (*PartnerAttachment, *Response, error) { |
||||
path := fmt.Sprintf("%s/%s/remote_routes", partnerNetworkConnectBasePath, id) |
||||
req, err := s.client.NewRequest(ctx, http.MethodPut, path, set) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
|
||||
root := new(partnerNetworkConnectAttachmentRoot) |
||||
resp, err := s.client.Do(ctx, req, root) |
||||
if err != nil { |
||||
return nil, resp, err |
||||
} |
||||
|
||||
return root.PartnerAttachment, resp, nil |
||||
} |
||||
|
||||
// GetBGPAuthKey returns Partner Attachment bgp auth key
|
||||
func (s *PartnerAttachmentServiceOp) GetBGPAuthKey(ctx context.Context, iaID string) (*BgpAuthKey, *Response, error) { |
||||
path := fmt.Sprintf("%s/%s/bgp_auth_key", partnerNetworkConnectBasePath, iaID) |
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
|
||||
root := new(bgpAuthKeyRoot) |
||||
resp, err := s.client.Do(ctx, req, root) |
||||
if err != nil { |
||||
return nil, resp, err |
||||
} |
||||
|
||||
return root.BgpAuthKey, resp, nil |
||||
} |
||||
|
||||
// RegenerateServiceKey regenerates the service key of a Partner Attachment.
|
||||
func (s *PartnerAttachmentServiceOp) RegenerateServiceKey(ctx context.Context, iaID string) (*RegenerateServiceKey, *Response, error) { |
||||
path := fmt.Sprintf("%s/%s/service_key", partnerNetworkConnectBasePath, iaID) |
||||
req, err := s.client.NewRequest(ctx, http.MethodPost, path, nil) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
|
||||
root := new(regenerateServiceKeyRoot) |
||||
resp, err := s.client.Do(ctx, req, root) |
||||
if err != nil { |
||||
return nil, resp, err |
||||
} |
||||
|
||||
return root.RegenerateServiceKey, resp, nil |
||||
} |
||||
@ -0,0 +1,186 @@ |
||||
package godo |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"net/http" |
||||
) |
||||
|
||||
const spacesKeysBasePath = "v2/spaces/keys" |
||||
|
||||
// SpacesKeysService is an interface for managing Spaces keys with the DigitalOcean API.
|
||||
type SpacesKeysService interface { |
||||
List(context.Context, *ListOptions) ([]*SpacesKey, *Response, error) |
||||
Update(context.Context, string, *SpacesKeyUpdateRequest) (*SpacesKey, *Response, error) |
||||
Create(context.Context, *SpacesKeyCreateRequest) (*SpacesKey, *Response, error) |
||||
Delete(context.Context, string) (*Response, error) |
||||
Get(context.Context, string) (*SpacesKey, *Response, error) |
||||
} |
||||
|
||||
// SpacesKeysServiceOp handles communication with the Spaces key related methods of the
|
||||
// DigitalOcean API.
|
||||
type SpacesKeysServiceOp struct { |
||||
client *Client |
||||
} |
||||
|
||||
var _ SpacesKeysService = &SpacesKeysServiceOp{} |
||||
|
||||
// SpacesKeyPermission represents a permission for a Spaces grant
|
||||
type SpacesKeyPermission string |
||||
|
||||
const ( |
||||
// SpacesKeyRead grants read-only access to the Spaces bucket
|
||||
SpacesKeyRead SpacesKeyPermission = "read" |
||||
// SpacesKeyReadWrite grants read and write access to the Spaces bucket
|
||||
SpacesKeyReadWrite SpacesKeyPermission = "readwrite" |
||||
// SpacesKeyFullAccess grants full access to the Spaces bucket
|
||||
SpacesKeyFullAccess SpacesKeyPermission = "fullaccess" |
||||
) |
||||
|
||||
// Grant represents a Grant for a Spaces key
|
||||
type Grant struct { |
||||
Bucket string `json:"bucket"` |
||||
Permission SpacesKeyPermission `json:"permission"` |
||||
} |
||||
|
||||
// SpacesKey represents a DigitalOcean Spaces key
|
||||
type SpacesKey struct { |
||||
Name string `json:"name"` |
||||
AccessKey string `json:"access_key"` |
||||
SecretKey string `json:"secret_key"` |
||||
Grants []*Grant `json:"grants"` |
||||
CreatedAt string `json:"created_at"` |
||||
} |
||||
|
||||
// SpacesKeyRoot represents a response from the DigitalOcean API
|
||||
type spacesKeyRoot struct { |
||||
Key *SpacesKey `json:"key"` |
||||
} |
||||
|
||||
// SpacesKeyCreateRequest represents a request to create a Spaces key.
|
||||
type SpacesKeyCreateRequest struct { |
||||
Name string `json:"name"` |
||||
Grants []*Grant `json:"grants"` |
||||
} |
||||
|
||||
// SpacesKeyUpdateRequest represents a request to update a Spaces key.
|
||||
type SpacesKeyUpdateRequest struct { |
||||
Name string `json:"name"` |
||||
Grants []*Grant `json:"grants"` |
||||
} |
||||
|
||||
// spacesListKeysRoot represents a response from the DigitalOcean API
|
||||
type spacesListKeysRoot struct { |
||||
Keys []*SpacesKey `json:"keys,omitempty"` |
||||
Links *Links `json:"links,omitempty"` |
||||
Meta *Meta `json:"meta"` |
||||
} |
||||
|
||||
// Create creates a new Spaces key.
|
||||
func (s *SpacesKeysServiceOp) Create(ctx context.Context, createRequest *SpacesKeyCreateRequest) (*SpacesKey, *Response, error) { |
||||
if createRequest == nil { |
||||
return nil, nil, NewArgError("createRequest", "cannot be nil") |
||||
} |
||||
|
||||
req, err := s.client.NewRequest(ctx, http.MethodPost, spacesKeysBasePath, createRequest) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
|
||||
root := new(spacesKeyRoot) |
||||
resp, err := s.client.Do(ctx, req, root) |
||||
if err != nil { |
||||
return nil, resp, err |
||||
} |
||||
|
||||
return root.Key, resp, nil |
||||
} |
||||
|
||||
// Delete deletes a Spaces key.
|
||||
func (s *SpacesKeysServiceOp) Delete(ctx context.Context, accessKey string) (*Response, error) { |
||||
if accessKey == "" { |
||||
return nil, NewArgError("accessKey", "cannot be empty") |
||||
} |
||||
|
||||
path := fmt.Sprintf("%s/%s", spacesKeysBasePath, accessKey) |
||||
req, err := s.client.NewRequest(ctx, http.MethodDelete, path, nil) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
resp, err := s.client.Do(ctx, req, nil) |
||||
if err != nil { |
||||
return resp, err |
||||
} |
||||
|
||||
return resp, nil |
||||
} |
||||
|
||||
// Update updates a Spaces key.
|
||||
func (s *SpacesKeysServiceOp) Update(ctx context.Context, accessKey string, updateRequest *SpacesKeyUpdateRequest) (*SpacesKey, *Response, error) { |
||||
if accessKey == "" { |
||||
return nil, nil, NewArgError("accessKey", "cannot be empty") |
||||
} |
||||
if updateRequest == nil { |
||||
return nil, nil, NewArgError("updateRequest", "cannot be nil") |
||||
} |
||||
|
||||
path := fmt.Sprintf("%s/%s", spacesKeysBasePath, accessKey) |
||||
req, err := s.client.NewRequest(ctx, http.MethodPut, path, updateRequest) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
root := new(spacesKeyRoot) |
||||
resp, err := s.client.Do(ctx, req, root) |
||||
if err != nil { |
||||
return nil, resp, err |
||||
} |
||||
|
||||
return root.Key, resp, nil |
||||
} |
||||
|
||||
// List returns a list of Spaces keys.
|
||||
func (s *SpacesKeysServiceOp) List(ctx context.Context, opts *ListOptions) ([]*SpacesKey, *Response, error) { |
||||
path, err := addOptions(spacesKeysBasePath, opts) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
|
||||
root := new(spacesListKeysRoot) |
||||
resp, err := s.client.Do(ctx, req, root) |
||||
if err != nil { |
||||
return nil, resp, err |
||||
} |
||||
|
||||
if root.Links != nil { |
||||
resp.Links = root.Links |
||||
} |
||||
if root.Meta != nil { |
||||
resp.Meta = root.Meta |
||||
} |
||||
|
||||
return root.Keys, resp, nil |
||||
} |
||||
|
||||
// Get retrieves a Spaces key.
|
||||
func (s *SpacesKeysServiceOp) Get(ctx context.Context, accessKey string) (*SpacesKey, *Response, error) { |
||||
if accessKey == "" { |
||||
return nil, nil, NewArgError("accessKey", "cannot be empty") |
||||
} |
||||
|
||||
path := fmt.Sprintf("%s/%s", spacesKeysBasePath, accessKey) |
||||
req, err := s.client.NewRequest(ctx, http.MethodGet, path, nil) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
root := new(spacesKeyRoot) |
||||
resp, err := s.client.Do(ctx, req, root) |
||||
if err != nil { |
||||
return nil, resp, err |
||||
} |
||||
|
||||
return root.Key, resp, nil |
||||
} |
||||
@ -0,0 +1,18 @@ |
||||
root = true |
||||
|
||||
[*] |
||||
charset = utf-8 |
||||
end_of_line = lf |
||||
indent_size = 4 |
||||
indent_style = space |
||||
insert_final_newline = true |
||||
trim_trailing_whitespace = true |
||||
|
||||
[*.go] |
||||
indent_style = tab |
||||
|
||||
[{Makefile,*.mk}] |
||||
indent_style = tab |
||||
|
||||
[*.nix] |
||||
indent_size = 2 |
||||
@ -0,0 +1,4 @@ |
||||
if ! has nix_direnv_version || ! nix_direnv_version 3.0.4; then |
||||
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.0.4/direnvrc" "sha256-DzlYZ33mWF/Gs8DDeyjr8mnVmQGx7ASYqA5WlxwvBG4=" |
||||
fi |
||||
use flake . --impure |
||||
@ -0,0 +1,6 @@ |
||||
/.devenv/ |
||||
/.direnv/ |
||||
/.pre-commit-config.yaml |
||||
/bin/ |
||||
/build/ |
||||
/var/ |
||||
@ -0,0 +1,23 @@ |
||||
run: |
||||
timeout: 5m |
||||
|
||||
linters-settings: |
||||
gci: |
||||
sections: |
||||
- standard |
||||
- default |
||||
- prefix(github.com/go-viper/mapstructure) |
||||
golint: |
||||
min-confidence: 0 |
||||
goimports: |
||||
local-prefixes: github.com/go-viper/maptstructure |
||||
|
||||
linters: |
||||
disable-all: true |
||||
enable: |
||||
- gci |
||||
- gofmt |
||||
- gofumpt |
||||
- goimports |
||||
- staticcheck |
||||
# - stylecheck |
||||
@ -0,0 +1,104 @@ |
||||
> [!WARNING] |
||||
> As of v2 of this library, change log can be found in GitHub releases. |
||||
|
||||
## 1.5.1 |
||||
|
||||
* Wrap errors so they're compatible with `errors.Is` and `errors.As` [GH-282] |
||||
* Fix map of slices not decoding properly in certain cases. [GH-266] |
||||
|
||||
## 1.5.0 |
||||
|
||||
* New option `IgnoreUntaggedFields` to ignore decoding to any fields |
||||
without `mapstructure` (or the configured tag name) set [GH-277] |
||||
* New option `ErrorUnset` which makes it an error if any fields |
||||
in a target struct are not set by the decoding process. [GH-225] |
||||
* New function `OrComposeDecodeHookFunc` to help compose decode hooks. [GH-240] |
||||
* Decoding to slice from array no longer crashes [GH-265] |
||||
* Decode nested struct pointers to map [GH-271] |
||||
* Fix issue where `,squash` was ignored if `Squash` option was set. [GH-280] |
||||
* Fix issue where fields with `,omitempty` would sometimes decode |
||||
into a map with an empty string key [GH-281] |
||||
|
||||
## 1.4.3 |
||||
|
||||
* Fix cases where `json.Number` didn't decode properly [GH-261] |
||||
|
||||
## 1.4.2 |
||||
|
||||
* Custom name matchers to support any sort of casing, formatting, etc. for |
||||
field names. [GH-250] |
||||
* Fix possible panic in ComposeDecodeHookFunc [GH-251] |
||||
|
||||
## 1.4.1 |
||||
|
||||
* Fix regression where `*time.Time` value would be set to empty and not be sent |
||||
to decode hooks properly [GH-232] |
||||
|
||||
## 1.4.0 |
||||
|
||||
* A new decode hook type `DecodeHookFuncValue` has been added that has |
||||
access to the full values. [GH-183] |
||||
* Squash is now supported with embedded fields that are struct pointers [GH-205] |
||||
* Empty strings will convert to 0 for all numeric types when weakly decoding [GH-206] |
||||
|
||||
## 1.3.3 |
||||
|
||||
* Decoding maps from maps creates a settable value for decode hooks [GH-203] |
||||
|
||||
## 1.3.2 |
||||
|
||||
* Decode into interface type with a struct value is supported [GH-187] |
||||
|
||||
## 1.3.1 |
||||
|
||||
* Squash should only squash embedded structs. [GH-194] |
||||
|
||||
## 1.3.0 |
||||
|
||||
* Added `",omitempty"` support. This will ignore zero values in the source |
||||
structure when encoding. [GH-145] |
||||
|
||||
## 1.2.3 |
||||
|
||||
* Fix duplicate entries in Keys list with pointer values. [GH-185] |
||||
|
||||
## 1.2.2 |
||||
|
||||
* Do not add unsettable (unexported) values to the unused metadata key |
||||
or "remain" value. [GH-150] |
||||
|
||||
## 1.2.1 |
||||
|
||||
* Go modules checksum mismatch fix |
||||
|
||||
## 1.2.0 |
||||
|
||||
* Added support to capture unused values in a field using the `",remain"` value |
||||
in the mapstructure tag. There is an example to showcase usage. |
||||
* Added `DecoderConfig` option to always squash embedded structs |
||||
* `json.Number` can decode into `uint` types |
||||
* Empty slices are preserved and not replaced with nil slices |
||||
* Fix panic that can occur in when decoding a map into a nil slice of structs |
||||
* Improved package documentation for godoc |
||||
|
||||
## 1.1.2 |
||||
|
||||
* Fix error when decode hook decodes interface implementation into interface |
||||
type. [GH-140] |
||||
|
||||
## 1.1.1 |
||||
|
||||
* Fix panic that can happen in `decodePtr` |
||||
|
||||
## 1.1.0 |
||||
|
||||
* Added `StringToIPHookFunc` to convert `string` to `net.IP` and `net.IPNet` [GH-133] |
||||
* Support struct to struct decoding [GH-137] |
||||
* If source map value is nil, then destination map value is nil (instead of empty) |
||||
* If source slice value is nil, then destination slice value is nil (instead of empty) |
||||
* If source pointer is nil, then destination pointer is set to nil (instead of |
||||
allocated zero value of type) |
||||
|
||||
## 1.0.0 |
||||
|
||||
* Initial tagged stable release. |
||||
@ -0,0 +1,21 @@ |
||||
The MIT License (MIT) |
||||
|
||||
Copyright (c) 2013 Mitchell Hashimoto |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is |
||||
furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in |
||||
all copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||
THE SOFTWARE. |
||||
@ -0,0 +1,80 @@ |
||||
# mapstructure |
||||
|
||||
[](https://github.com/go-viper/mapstructure/actions?query=workflow%3ACI) |
||||
[](https://pkg.go.dev/mod/github.com/go-viper/mapstructure/v2) |
||||
 |
||||
|
||||
mapstructure is a Go library for decoding generic map values to structures |
||||
and vice versa, while providing helpful error handling. |
||||
|
||||
This library is most useful when decoding values from some data stream (JSON, |
||||
Gob, etc.) where you don't _quite_ know the structure of the underlying data |
||||
until you read a part of it. You can therefore read a `map[string]interface{}` |
||||
and use this library to decode it into the proper underlying native Go |
||||
structure. |
||||
|
||||
## Installation |
||||
|
||||
```shell |
||||
go get github.com/go-viper/mapstructure/v2 |
||||
``` |
||||
|
||||
## Migrating from `github.com/mitchellh/mapstructure` |
||||
|
||||
[@mitchehllh](https://github.com/mitchellh) announced his intent to archive some of his unmaintained projects (see [here](https://gist.github.com/mitchellh/90029601268e59a29e64e55bab1c5bdc) and [here](https://github.com/mitchellh/mapstructure/issues/349)). This is a repository achieved the "blessed fork" status. |
||||
|
||||
You can migrate to this package by changing your import paths in your Go files to `github.com/go-viper/mapstructure/v2`. |
||||
The API is the same, so you don't need to change anything else. |
||||
|
||||
Here is a script that can help you with the migration: |
||||
|
||||
```shell |
||||
sed -i 's/github.com\/mitchellh\/mapstructure/github.com\/go-viper\/mapstructure\/v2/g' $(find . -type f -name '*.go') |
||||
``` |
||||
|
||||
If you need more time to migrate your code, that is absolutely fine. |
||||
|
||||
Some of the latest fixes are backported to the v1 release branch of this package, so you can use the Go modules `replace` feature until you are ready to migrate: |
||||
|
||||
```shell |
||||
replace github.com/mitchellh/mapstructure => github.com/go-viper/mapstructure v1.6.0 |
||||
``` |
||||
|
||||
## Usage & Example |
||||
|
||||
For usage and examples see the [documentation](https://pkg.go.dev/mod/github.com/go-viper/mapstructure/v2). |
||||
|
||||
The `Decode` function has examples associated with it there. |
||||
|
||||
## But Why?! |
||||
|
||||
Go offers fantastic standard libraries for decoding formats such as JSON. |
||||
The standard method is to have a struct pre-created, and populate that struct |
||||
from the bytes of the encoded format. This is great, but the problem is if |
||||
you have configuration or an encoding that changes slightly depending on |
||||
specific fields. For example, consider this JSON: |
||||
|
||||
```json |
||||
{ |
||||
"type": "person", |
||||
"name": "Mitchell" |
||||
} |
||||
``` |
||||
|
||||
Perhaps we can't populate a specific structure without first reading |
||||
the "type" field from the JSON. We could always do two passes over the |
||||
decoding of the JSON (reading the "type" first, and the rest later). |
||||
However, it is much simpler to just decode this into a `map[string]interface{}` |
||||
structure, read the "type" key, then use something like this library |
||||
to decode it into the proper structure. |
||||
|
||||
## Credits |
||||
|
||||
Mapstructure was originally created by [@mitchellh](https://github.com/mitchellh). |
||||
This is a maintained fork of the original library. |
||||
|
||||
Read more about the reasons for the fork [here](https://github.com/mitchellh/mapstructure/issues/349). |
||||
|
||||
## License |
||||
|
||||
The project is licensed under the [MIT License](LICENSE). |
||||
@ -0,0 +1,630 @@ |
||||
package mapstructure |
||||
|
||||
import ( |
||||
"encoding" |
||||
"errors" |
||||
"fmt" |
||||
"net" |
||||
"net/netip" |
||||
"net/url" |
||||
"reflect" |
||||
"strconv" |
||||
"strings" |
||||
"time" |
||||
) |
||||
|
||||
// typedDecodeHook takes a raw DecodeHookFunc (an interface{}) and turns
|
||||
// it into the proper DecodeHookFunc type, such as DecodeHookFuncType.
|
||||
func typedDecodeHook(h DecodeHookFunc) DecodeHookFunc { |
||||
// Create variables here so we can reference them with the reflect pkg
|
||||
var f1 DecodeHookFuncType |
||||
var f2 DecodeHookFuncKind |
||||
var f3 DecodeHookFuncValue |
||||
|
||||
// Fill in the variables into this interface and the rest is done
|
||||
// automatically using the reflect package.
|
||||
potential := []interface{}{f1, f2, f3} |
||||
|
||||
v := reflect.ValueOf(h) |
||||
vt := v.Type() |
||||
for _, raw := range potential { |
||||
pt := reflect.ValueOf(raw).Type() |
||||
if vt.ConvertibleTo(pt) { |
||||
return v.Convert(pt).Interface() |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
// cachedDecodeHook takes a raw DecodeHookFunc (an interface{}) and turns
|
||||
// it into a closure to be used directly
|
||||
// if the type fails to convert we return a closure always erroring to keep the previous behaviour
|
||||
func cachedDecodeHook(raw DecodeHookFunc) func(from reflect.Value, to reflect.Value) (interface{}, error) { |
||||
switch f := typedDecodeHook(raw).(type) { |
||||
case DecodeHookFuncType: |
||||
return func(from reflect.Value, to reflect.Value) (interface{}, error) { |
||||
return f(from.Type(), to.Type(), from.Interface()) |
||||
} |
||||
case DecodeHookFuncKind: |
||||
return func(from reflect.Value, to reflect.Value) (interface{}, error) { |
||||
return f(from.Kind(), to.Kind(), from.Interface()) |
||||
} |
||||
case DecodeHookFuncValue: |
||||
return func(from reflect.Value, to reflect.Value) (interface{}, error) { |
||||
return f(from, to) |
||||
} |
||||
default: |
||||
return func(from reflect.Value, to reflect.Value) (interface{}, error) { |
||||
return nil, errors.New("invalid decode hook signature") |
||||
} |
||||
} |
||||
} |
||||
|
||||
// DecodeHookExec executes the given decode hook. This should be used
|
||||
// since it'll naturally degrade to the older backwards compatible DecodeHookFunc
|
||||
// that took reflect.Kind instead of reflect.Type.
|
||||
func DecodeHookExec( |
||||
raw DecodeHookFunc, |
||||
from reflect.Value, to reflect.Value, |
||||
) (interface{}, error) { |
||||
switch f := typedDecodeHook(raw).(type) { |
||||
case DecodeHookFuncType: |
||||
return f(from.Type(), to.Type(), from.Interface()) |
||||
case DecodeHookFuncKind: |
||||
return f(from.Kind(), to.Kind(), from.Interface()) |
||||
case DecodeHookFuncValue: |
||||
return f(from, to) |
||||
default: |
||||
return nil, errors.New("invalid decode hook signature") |
||||
} |
||||
} |
||||
|
||||
// ComposeDecodeHookFunc creates a single DecodeHookFunc that
|
||||
// automatically composes multiple DecodeHookFuncs.
|
||||
//
|
||||
// The composed funcs are called in order, with the result of the
|
||||
// previous transformation.
|
||||
func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc { |
||||
cached := make([]func(from reflect.Value, to reflect.Value) (interface{}, error), 0, len(fs)) |
||||
for _, f := range fs { |
||||
cached = append(cached, cachedDecodeHook(f)) |
||||
} |
||||
return func(f reflect.Value, t reflect.Value) (interface{}, error) { |
||||
var err error |
||||
data := f.Interface() |
||||
|
||||
newFrom := f |
||||
for _, c := range cached { |
||||
data, err = c(newFrom, t) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
newFrom = reflect.ValueOf(data) |
||||
} |
||||
|
||||
return data, nil |
||||
} |
||||
} |
||||
|
||||
// OrComposeDecodeHookFunc executes all input hook functions until one of them returns no error. In that case its value is returned.
|
||||
// If all hooks return an error, OrComposeDecodeHookFunc returns an error concatenating all error messages.
|
||||
func OrComposeDecodeHookFunc(ff ...DecodeHookFunc) DecodeHookFunc { |
||||
cached := make([]func(from reflect.Value, to reflect.Value) (interface{}, error), 0, len(ff)) |
||||
for _, f := range ff { |
||||
cached = append(cached, cachedDecodeHook(f)) |
||||
} |
||||
return func(a, b reflect.Value) (interface{}, error) { |
||||
var allErrs string |
||||
var out interface{} |
||||
var err error |
||||
|
||||
for _, c := range cached { |
||||
out, err = c(a, b) |
||||
if err != nil { |
||||
allErrs += err.Error() + "\n" |
||||
continue |
||||
} |
||||
|
||||
return out, nil |
||||
} |
||||
|
||||
return nil, errors.New(allErrs) |
||||
} |
||||
} |
||||
|
||||
// StringToSliceHookFunc returns a DecodeHookFunc that converts
|
||||
// string to []string by splitting on the given sep.
|
||||
func StringToSliceHookFunc(sep string) DecodeHookFunc { |
||||
return func( |
||||
f reflect.Type, |
||||
t reflect.Type, |
||||
data interface{}, |
||||
) (interface{}, error) { |
||||
if f.Kind() != reflect.String { |
||||
return data, nil |
||||
} |
||||
if t != reflect.SliceOf(f) { |
||||
return data, nil |
||||
} |
||||
|
||||
raw := data.(string) |
||||
if raw == "" { |
||||
return []string{}, nil |
||||
} |
||||
|
||||
return strings.Split(raw, sep), nil |
||||
} |
||||
} |
||||
|
||||
// StringToTimeDurationHookFunc returns a DecodeHookFunc that converts
|
||||
// strings to time.Duration.
|
||||
func StringToTimeDurationHookFunc() DecodeHookFunc { |
||||
return func( |
||||
f reflect.Type, |
||||
t reflect.Type, |
||||
data interface{}, |
||||
) (interface{}, error) { |
||||
if f.Kind() != reflect.String { |
||||
return data, nil |
||||
} |
||||
if t != reflect.TypeOf(time.Duration(5)) { |
||||
return data, nil |
||||
} |
||||
|
||||
// Convert it by parsing
|
||||
return time.ParseDuration(data.(string)) |
||||
} |
||||
} |
||||
|
||||
// StringToURLHookFunc returns a DecodeHookFunc that converts
|
||||
// strings to *url.URL.
|
||||
func StringToURLHookFunc() DecodeHookFunc { |
||||
return func( |
||||
f reflect.Type, |
||||
t reflect.Type, |
||||
data interface{}, |
||||
) (interface{}, error) { |
||||
if f.Kind() != reflect.String { |
||||
return data, nil |
||||
} |
||||
if t != reflect.TypeOf(&url.URL{}) { |
||||
return data, nil |
||||
} |
||||
|
||||
// Convert it by parsing
|
||||
return url.Parse(data.(string)) |
||||
} |
||||
} |
||||
|
||||
// StringToIPHookFunc returns a DecodeHookFunc that converts
|
||||
// strings to net.IP
|
||||
func StringToIPHookFunc() DecodeHookFunc { |
||||
return func( |
||||
f reflect.Type, |
||||
t reflect.Type, |
||||
data interface{}, |
||||
) (interface{}, error) { |
||||
if f.Kind() != reflect.String { |
||||
return data, nil |
||||
} |
||||
if t != reflect.TypeOf(net.IP{}) { |
||||
return data, nil |
||||
} |
||||
|
||||
// Convert it by parsing
|
||||
ip := net.ParseIP(data.(string)) |
||||
if ip == nil { |
||||
return net.IP{}, fmt.Errorf("failed parsing ip %v", data) |
||||
} |
||||
|
||||
return ip, nil |
||||
} |
||||
} |
||||
|
||||
// StringToIPNetHookFunc returns a DecodeHookFunc that converts
|
||||
// strings to net.IPNet
|
||||
func StringToIPNetHookFunc() DecodeHookFunc { |
||||
return func( |
||||
f reflect.Type, |
||||
t reflect.Type, |
||||
data interface{}, |
||||
) (interface{}, error) { |
||||
if f.Kind() != reflect.String { |
||||
return data, nil |
||||
} |
||||
if t != reflect.TypeOf(net.IPNet{}) { |
||||
return data, nil |
||||
} |
||||
|
||||
// Convert it by parsing
|
||||
_, net, err := net.ParseCIDR(data.(string)) |
||||
return net, err |
||||
} |
||||
} |
||||
|
||||
// StringToTimeHookFunc returns a DecodeHookFunc that converts
|
||||
// strings to time.Time.
|
||||
func StringToTimeHookFunc(layout string) DecodeHookFunc { |
||||
return func( |
||||
f reflect.Type, |
||||
t reflect.Type, |
||||
data interface{}, |
||||
) (interface{}, error) { |
||||
if f.Kind() != reflect.String { |
||||
return data, nil |
||||
} |
||||
if t != reflect.TypeOf(time.Time{}) { |
||||
return data, nil |
||||
} |
||||
|
||||
// Convert it by parsing
|
||||
return time.Parse(layout, data.(string)) |
||||
} |
||||
} |
||||
|
||||
// WeaklyTypedHook is a DecodeHookFunc which adds support for weak typing to
|
||||
// the decoder.
|
||||
//
|
||||
// Note that this is significantly different from the WeaklyTypedInput option
|
||||
// of the DecoderConfig.
|
||||
func WeaklyTypedHook( |
||||
f reflect.Kind, |
||||
t reflect.Kind, |
||||
data interface{}, |
||||
) (interface{}, error) { |
||||
dataVal := reflect.ValueOf(data) |
||||
switch t { |
||||
case reflect.String: |
||||
switch f { |
||||
case reflect.Bool: |
||||
if dataVal.Bool() { |
||||
return "1", nil |
||||
} |
||||
return "0", nil |
||||
case reflect.Float32: |
||||
return strconv.FormatFloat(dataVal.Float(), 'f', -1, 64), nil |
||||
case reflect.Int: |
||||
return strconv.FormatInt(dataVal.Int(), 10), nil |
||||
case reflect.Slice: |
||||
dataType := dataVal.Type() |
||||
elemKind := dataType.Elem().Kind() |
||||
if elemKind == reflect.Uint8 { |
||||
return string(dataVal.Interface().([]uint8)), nil |
||||
} |
||||
case reflect.Uint: |
||||
return strconv.FormatUint(dataVal.Uint(), 10), nil |
||||
} |
||||
} |
||||
|
||||
return data, nil |
||||
} |
||||
|
||||
func RecursiveStructToMapHookFunc() DecodeHookFunc { |
||||
return func(f reflect.Value, t reflect.Value) (interface{}, error) { |
||||
if f.Kind() != reflect.Struct { |
||||
return f.Interface(), nil |
||||
} |
||||
|
||||
var i interface{} = struct{}{} |
||||
if t.Type() != reflect.TypeOf(&i).Elem() { |
||||
return f.Interface(), nil |
||||
} |
||||
|
||||
m := make(map[string]interface{}) |
||||
t.Set(reflect.ValueOf(m)) |
||||
|
||||
return f.Interface(), nil |
||||
} |
||||
} |
||||
|
||||
// TextUnmarshallerHookFunc returns a DecodeHookFunc that applies
|
||||
// strings to the UnmarshalText function, when the target type
|
||||
// implements the encoding.TextUnmarshaler interface
|
||||
func TextUnmarshallerHookFunc() DecodeHookFuncType { |
||||
return func( |
||||
f reflect.Type, |
||||
t reflect.Type, |
||||
data interface{}, |
||||
) (interface{}, error) { |
||||
if f.Kind() != reflect.String { |
||||
return data, nil |
||||
} |
||||
result := reflect.New(t).Interface() |
||||
unmarshaller, ok := result.(encoding.TextUnmarshaler) |
||||
if !ok { |
||||
return data, nil |
||||
} |
||||
str, ok := data.(string) |
||||
if !ok { |
||||
str = reflect.Indirect(reflect.ValueOf(&data)).Elem().String() |
||||
} |
||||
if err := unmarshaller.UnmarshalText([]byte(str)); err != nil { |
||||
return nil, err |
||||
} |
||||
return result, nil |
||||
} |
||||
} |
||||
|
||||
// StringToNetIPAddrHookFunc returns a DecodeHookFunc that converts
|
||||
// strings to netip.Addr.
|
||||
func StringToNetIPAddrHookFunc() DecodeHookFunc { |
||||
return func( |
||||
f reflect.Type, |
||||
t reflect.Type, |
||||
data interface{}, |
||||
) (interface{}, error) { |
||||
if f.Kind() != reflect.String { |
||||
return data, nil |
||||
} |
||||
if t != reflect.TypeOf(netip.Addr{}) { |
||||
return data, nil |
||||
} |
||||
|
||||
// Convert it by parsing
|
||||
return netip.ParseAddr(data.(string)) |
||||
} |
||||
} |
||||
|
||||
// StringToNetIPAddrPortHookFunc returns a DecodeHookFunc that converts
|
||||
// strings to netip.AddrPort.
|
||||
func StringToNetIPAddrPortHookFunc() DecodeHookFunc { |
||||
return func( |
||||
f reflect.Type, |
||||
t reflect.Type, |
||||
data interface{}, |
||||
) (interface{}, error) { |
||||
if f.Kind() != reflect.String { |
||||
return data, nil |
||||
} |
||||
if t != reflect.TypeOf(netip.AddrPort{}) { |
||||
return data, nil |
||||
} |
||||
|
||||
// Convert it by parsing
|
||||
return netip.ParseAddrPort(data.(string)) |
||||
} |
||||
} |
||||
|
||||
// StringToBasicTypeHookFunc returns a DecodeHookFunc that converts
|
||||
// strings to basic types.
|
||||
// int8, uint8, int16, uint16, int32, uint32, int64, uint64, int, uint, float32, float64, bool, byte, rune, complex64, complex128
|
||||
func StringToBasicTypeHookFunc() DecodeHookFunc { |
||||
return ComposeDecodeHookFunc( |
||||
StringToInt8HookFunc(), |
||||
StringToUint8HookFunc(), |
||||
StringToInt16HookFunc(), |
||||
StringToUint16HookFunc(), |
||||
StringToInt32HookFunc(), |
||||
StringToUint32HookFunc(), |
||||
StringToInt64HookFunc(), |
||||
StringToUint64HookFunc(), |
||||
StringToIntHookFunc(), |
||||
StringToUintHookFunc(), |
||||
StringToFloat32HookFunc(), |
||||
StringToFloat64HookFunc(), |
||||
StringToBoolHookFunc(), |
||||
// byte and rune are aliases for uint8 and int32 respectively
|
||||
// StringToByteHookFunc(),
|
||||
// StringToRuneHookFunc(),
|
||||
StringToComplex64HookFunc(), |
||||
StringToComplex128HookFunc(), |
||||
) |
||||
} |
||||
|
||||
// StringToInt8HookFunc returns a DecodeHookFunc that converts
|
||||
// strings to int8.
|
||||
func StringToInt8HookFunc() DecodeHookFunc { |
||||
return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { |
||||
if f.Kind() != reflect.String || t.Kind() != reflect.Int8 { |
||||
return data, nil |
||||
} |
||||
|
||||
// Convert it by parsing
|
||||
i64, err := strconv.ParseInt(data.(string), 0, 8) |
||||
return int8(i64), err |
||||
} |
||||
} |
||||
|
||||
// StringToUint8HookFunc returns a DecodeHookFunc that converts
|
||||
// strings to uint8.
|
||||
func StringToUint8HookFunc() DecodeHookFunc { |
||||
return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { |
||||
if f.Kind() != reflect.String || t.Kind() != reflect.Uint8 { |
||||
return data, nil |
||||
} |
||||
|
||||
// Convert it by parsing
|
||||
u64, err := strconv.ParseUint(data.(string), 0, 8) |
||||
return uint8(u64), err |
||||
} |
||||
} |
||||
|
||||
// StringToInt16HookFunc returns a DecodeHookFunc that converts
|
||||
// strings to int16.
|
||||
func StringToInt16HookFunc() DecodeHookFunc { |
||||
return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { |
||||
if f.Kind() != reflect.String || t.Kind() != reflect.Int16 { |
||||
return data, nil |
||||
} |
||||
|
||||
// Convert it by parsing
|
||||
i64, err := strconv.ParseInt(data.(string), 0, 16) |
||||
return int16(i64), err |
||||
} |
||||
} |
||||
|
||||
// StringToUint16HookFunc returns a DecodeHookFunc that converts
|
||||
// strings to uint16.
|
||||
func StringToUint16HookFunc() DecodeHookFunc { |
||||
return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { |
||||
if f.Kind() != reflect.String || t.Kind() != reflect.Uint16 { |
||||
return data, nil |
||||
} |
||||
|
||||
// Convert it by parsing
|
||||
u64, err := strconv.ParseUint(data.(string), 0, 16) |
||||
return uint16(u64), err |
||||
} |
||||
} |
||||
|
||||
// StringToInt32HookFunc returns a DecodeHookFunc that converts
|
||||
// strings to int32.
|
||||
func StringToInt32HookFunc() DecodeHookFunc { |
||||
return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { |
||||
if f.Kind() != reflect.String || t.Kind() != reflect.Int32 { |
||||
return data, nil |
||||
} |
||||
|
||||
// Convert it by parsing
|
||||
i64, err := strconv.ParseInt(data.(string), 0, 32) |
||||
return int32(i64), err |
||||
} |
||||
} |
||||
|
||||
// StringToUint32HookFunc returns a DecodeHookFunc that converts
|
||||
// strings to uint32.
|
||||
func StringToUint32HookFunc() DecodeHookFunc { |
||||
return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { |
||||
if f.Kind() != reflect.String || t.Kind() != reflect.Uint32 { |
||||
return data, nil |
||||
} |
||||
|
||||
// Convert it by parsing
|
||||
u64, err := strconv.ParseUint(data.(string), 0, 32) |
||||
return uint32(u64), err |
||||
} |
||||
} |
||||
|
||||
// StringToInt64HookFunc returns a DecodeHookFunc that converts
|
||||
// strings to int64.
|
||||
func StringToInt64HookFunc() DecodeHookFunc { |
||||
return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { |
||||
if f.Kind() != reflect.String || t.Kind() != reflect.Int64 { |
||||
return data, nil |
||||
} |
||||
|
||||
// Convert it by parsing
|
||||
return strconv.ParseInt(data.(string), 0, 64) |
||||
} |
||||
} |
||||
|
||||
// StringToUint64HookFunc returns a DecodeHookFunc that converts
|
||||
// strings to uint64.
|
||||
func StringToUint64HookFunc() DecodeHookFunc { |
||||
return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { |
||||
if f.Kind() != reflect.String || t.Kind() != reflect.Uint64 { |
||||
return data, nil |
||||
} |
||||
|
||||
// Convert it by parsing
|
||||
return strconv.ParseUint(data.(string), 0, 64) |
||||
} |
||||
} |
||||
|
||||
// StringToIntHookFunc returns a DecodeHookFunc that converts
|
||||
// strings to int.
|
||||
func StringToIntHookFunc() DecodeHookFunc { |
||||
return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { |
||||
if f.Kind() != reflect.String || t.Kind() != reflect.Int { |
||||
return data, nil |
||||
} |
||||
|
||||
// Convert it by parsing
|
||||
i64, err := strconv.ParseInt(data.(string), 0, 0) |
||||
return int(i64), err |
||||
} |
||||
} |
||||
|
||||
// StringToUintHookFunc returns a DecodeHookFunc that converts
|
||||
// strings to uint.
|
||||
func StringToUintHookFunc() DecodeHookFunc { |
||||
return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { |
||||
if f.Kind() != reflect.String || t.Kind() != reflect.Uint { |
||||
return data, nil |
||||
} |
||||
|
||||
// Convert it by parsing
|
||||
u64, err := strconv.ParseUint(data.(string), 0, 0) |
||||
return uint(u64), err |
||||
} |
||||
} |
||||
|
||||
// StringToFloat32HookFunc returns a DecodeHookFunc that converts
|
||||
// strings to float32.
|
||||
func StringToFloat32HookFunc() DecodeHookFunc { |
||||
return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { |
||||
if f.Kind() != reflect.String || t.Kind() != reflect.Float32 { |
||||
return data, nil |
||||
} |
||||
|
||||
// Convert it by parsing
|
||||
f64, err := strconv.ParseFloat(data.(string), 32) |
||||
return float32(f64), err |
||||
} |
||||
} |
||||
|
||||
// StringToFloat64HookFunc returns a DecodeHookFunc that converts
|
||||
// strings to float64.
|
||||
func StringToFloat64HookFunc() DecodeHookFunc { |
||||
return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { |
||||
if f.Kind() != reflect.String || t.Kind() != reflect.Float64 { |
||||
return data, nil |
||||
} |
||||
|
||||
// Convert it by parsing
|
||||
return strconv.ParseFloat(data.(string), 64) |
||||
} |
||||
} |
||||
|
||||
// StringToBoolHookFunc returns a DecodeHookFunc that converts
|
||||
// strings to bool.
|
||||
func StringToBoolHookFunc() DecodeHookFunc { |
||||
return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { |
||||
if f.Kind() != reflect.String || t.Kind() != reflect.Bool { |
||||
return data, nil |
||||
} |
||||
|
||||
// Convert it by parsing
|
||||
return strconv.ParseBool(data.(string)) |
||||
} |
||||
} |
||||
|
||||
// StringToByteHookFunc returns a DecodeHookFunc that converts
|
||||
// strings to byte.
|
||||
func StringToByteHookFunc() DecodeHookFunc { |
||||
return StringToUint8HookFunc() |
||||
} |
||||
|
||||
// StringToRuneHookFunc returns a DecodeHookFunc that converts
|
||||
// strings to rune.
|
||||
func StringToRuneHookFunc() DecodeHookFunc { |
||||
return StringToInt32HookFunc() |
||||
} |
||||
|
||||
// StringToComplex64HookFunc returns a DecodeHookFunc that converts
|
||||
// strings to complex64.
|
||||
func StringToComplex64HookFunc() DecodeHookFunc { |
||||
return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { |
||||
if f.Kind() != reflect.String || t.Kind() != reflect.Complex64 { |
||||
return data, nil |
||||
} |
||||
|
||||
// Convert it by parsing
|
||||
c128, err := strconv.ParseComplex(data.(string), 64) |
||||
return complex64(c128), err |
||||
} |
||||
} |
||||
|
||||
// StringToComplex128HookFunc returns a DecodeHookFunc that converts
|
||||
// strings to complex128.
|
||||
func StringToComplex128HookFunc() DecodeHookFunc { |
||||
return func(f reflect.Type, t reflect.Type, data interface{}) (interface{}, error) { |
||||
if f.Kind() != reflect.String || t.Kind() != reflect.Complex128 { |
||||
return data, nil |
||||
} |
||||
|
||||
// Convert it by parsing
|
||||
return strconv.ParseComplex(data.(string), 128) |
||||
} |
||||
} |
||||
@ -0,0 +1,472 @@ |
||||
{ |
||||
"nodes": { |
||||
"cachix": { |
||||
"inputs": { |
||||
"devenv": "devenv_2", |
||||
"flake-compat": [ |
||||
"devenv", |
||||
"flake-compat" |
||||
], |
||||
"nixpkgs": [ |
||||
"devenv", |
||||
"nixpkgs" |
||||
], |
||||
"pre-commit-hooks": [ |
||||
"devenv", |
||||
"pre-commit-hooks" |
||||
] |
||||
}, |
||||
"locked": { |
||||
"lastModified": 1712055811, |
||||
"narHash": "sha256-7FcfMm5A/f02yyzuavJe06zLa9hcMHsagE28ADcmQvk=", |
||||
"owner": "cachix", |
||||
"repo": "cachix", |
||||
"rev": "02e38da89851ec7fec3356a5c04bc8349cae0e30", |
||||
"type": "github" |
||||
}, |
||||
"original": { |
||||
"owner": "cachix", |
||||
"repo": "cachix", |
||||
"type": "github" |
||||
} |
||||
}, |
||||
"devenv": { |
||||
"inputs": { |
||||
"cachix": "cachix", |
||||
"flake-compat": "flake-compat_2", |
||||
"nix": "nix_2", |
||||
"nixpkgs": "nixpkgs_2", |
||||
"pre-commit-hooks": "pre-commit-hooks" |
||||
}, |
||||
"locked": { |
||||
"lastModified": 1717245169, |
||||
"narHash": "sha256-+mW3rTBjGU8p1THJN0lX/Dd/8FbnF+3dB+mJuSaxewE=", |
||||
"owner": "cachix", |
||||
"repo": "devenv", |
||||
"rev": "c3f9f053c077c6f88a3de5276d9178c62baa3fc3", |
||||
"type": "github" |
||||
}, |
||||
"original": { |
||||
"owner": "cachix", |
||||
"repo": "devenv", |
||||
"type": "github" |
||||
} |
||||
}, |
||||
"devenv_2": { |
||||
"inputs": { |
||||
"flake-compat": [ |
||||
"devenv", |
||||
"cachix", |
||||
"flake-compat" |
||||
], |
||||
"nix": "nix", |
||||
"nixpkgs": "nixpkgs", |
||||
"poetry2nix": "poetry2nix", |
||||
"pre-commit-hooks": [ |
||||
"devenv", |
||||
"cachix", |
||||
"pre-commit-hooks" |
||||
] |
||||
}, |
||||
"locked": { |
||||
"lastModified": 1708704632, |
||||
"narHash": "sha256-w+dOIW60FKMaHI1q5714CSibk99JfYxm0CzTinYWr+Q=", |
||||
"owner": "cachix", |
||||
"repo": "devenv", |
||||
"rev": "2ee4450b0f4b95a1b90f2eb5ffea98b90e48c196", |
||||
"type": "github" |
||||
}, |
||||
"original": { |
||||
"owner": "cachix", |
||||
"ref": "python-rewrite", |
||||
"repo": "devenv", |
||||
"type": "github" |
||||
} |
||||
}, |
||||
"flake-compat": { |
||||
"flake": false, |
||||
"locked": { |
||||
"lastModified": 1673956053, |
||||
"narHash": "sha256-4gtG9iQuiKITOjNQQeQIpoIB6b16fm+504Ch3sNKLd8=", |
||||
"owner": "edolstra", |
||||
"repo": "flake-compat", |
||||
"rev": "35bb57c0c8d8b62bbfd284272c928ceb64ddbde9", |
||||
"type": "github" |
||||
}, |
||||
"original": { |
||||
"owner": "edolstra", |
||||
"repo": "flake-compat", |
||||
"type": "github" |
||||
} |
||||
}, |
||||
"flake-compat_2": { |
||||
"flake": false, |
||||
"locked": { |
||||
"lastModified": 1696426674, |
||||
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", |
||||
"owner": "edolstra", |
||||
"repo": "flake-compat", |
||||
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", |
||||
"type": "github" |
||||
}, |
||||
"original": { |
||||
"owner": "edolstra", |
||||
"repo": "flake-compat", |
||||
"type": "github" |
||||
} |
||||
}, |
||||
"flake-parts": { |
||||
"inputs": { |
||||
"nixpkgs-lib": "nixpkgs-lib" |
||||
}, |
||||
"locked": { |
||||
"lastModified": 1717285511, |
||||
"narHash": "sha256-iKzJcpdXih14qYVcZ9QC9XuZYnPc6T8YImb6dX166kw=", |
||||
"owner": "hercules-ci", |
||||
"repo": "flake-parts", |
||||
"rev": "2a55567fcf15b1b1c7ed712a2c6fadaec7412ea8", |
||||
"type": "github" |
||||
}, |
||||
"original": { |
||||
"owner": "hercules-ci", |
||||
"repo": "flake-parts", |
||||
"type": "github" |
||||
} |
||||
}, |
||||
"flake-utils": { |
||||
"inputs": { |
||||
"systems": "systems" |
||||
}, |
||||
"locked": { |
||||
"lastModified": 1689068808, |
||||
"narHash": "sha256-6ixXo3wt24N/melDWjq70UuHQLxGV8jZvooRanIHXw0=", |
||||
"owner": "numtide", |
||||
"repo": "flake-utils", |
||||
"rev": "919d646de7be200f3bf08cb76ae1f09402b6f9b4", |
||||
"type": "github" |
||||
}, |
||||
"original": { |
||||
"owner": "numtide", |
||||
"repo": "flake-utils", |
||||
"type": "github" |
||||
} |
||||
}, |
||||
"flake-utils_2": { |
||||
"inputs": { |
||||
"systems": "systems_2" |
||||
}, |
||||
"locked": { |
||||
"lastModified": 1710146030, |
||||
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", |
||||
"owner": "numtide", |
||||
"repo": "flake-utils", |
||||
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", |
||||
"type": "github" |
||||
}, |
||||
"original": { |
||||
"owner": "numtide", |
||||
"repo": "flake-utils", |
||||
"type": "github" |
||||
} |
||||
}, |
||||
"gitignore": { |
||||
"inputs": { |
||||
"nixpkgs": [ |
||||
"devenv", |
||||
"pre-commit-hooks", |
||||
"nixpkgs" |
||||
] |
||||
}, |
||||
"locked": { |
||||
"lastModified": 1709087332, |
||||
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", |
||||
"owner": "hercules-ci", |
||||
"repo": "gitignore.nix", |
||||
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394", |
||||
"type": "github" |
||||
}, |
||||
"original": { |
||||
"owner": "hercules-ci", |
||||
"repo": "gitignore.nix", |
||||
"type": "github" |
||||
} |
||||
}, |
||||
"nix": { |
||||
"inputs": { |
||||
"flake-compat": "flake-compat", |
||||
"nixpkgs": [ |
||||
"devenv", |
||||
"cachix", |
||||
"devenv", |
||||
"nixpkgs" |
||||
], |
||||
"nixpkgs-regression": "nixpkgs-regression" |
||||
}, |
||||
"locked": { |
||||
"lastModified": 1712911606, |
||||
"narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=", |
||||
"owner": "domenkozar", |
||||
"repo": "nix", |
||||
"rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12", |
||||
"type": "github" |
||||
}, |
||||
"original": { |
||||
"owner": "domenkozar", |
||||
"ref": "devenv-2.21", |
||||
"repo": "nix", |
||||
"type": "github" |
||||
} |
||||
}, |
||||
"nix-github-actions": { |
||||
"inputs": { |
||||
"nixpkgs": [ |
||||
"devenv", |
||||
"cachix", |
||||
"devenv", |
||||
"poetry2nix", |
||||
"nixpkgs" |
||||
] |
||||
}, |
||||
"locked": { |
||||
"lastModified": 1688870561, |
||||
"narHash": "sha256-4UYkifnPEw1nAzqqPOTL2MvWtm3sNGw1UTYTalkTcGY=", |
||||
"owner": "nix-community", |
||||
"repo": "nix-github-actions", |
||||
"rev": "165b1650b753316aa7f1787f3005a8d2da0f5301", |
||||
"type": "github" |
||||
}, |
||||
"original": { |
||||
"owner": "nix-community", |
||||
"repo": "nix-github-actions", |
||||
"type": "github" |
||||
} |
||||
}, |
||||
"nix_2": { |
||||
"inputs": { |
||||
"flake-compat": [ |
||||
"devenv", |
||||
"flake-compat" |
||||
], |
||||
"nixpkgs": [ |
||||
"devenv", |
||||
"nixpkgs" |
||||
], |
||||
"nixpkgs-regression": "nixpkgs-regression_2" |
||||
}, |
||||
"locked": { |
||||
"lastModified": 1712911606, |
||||
"narHash": "sha256-BGvBhepCufsjcUkXnEEXhEVjwdJAwPglCC2+bInc794=", |
||||
"owner": "domenkozar", |
||||
"repo": "nix", |
||||
"rev": "b24a9318ea3f3600c1e24b4a00691ee912d4de12", |
||||
"type": "github" |
||||
}, |
||||
"original": { |
||||
"owner": "domenkozar", |
||||
"ref": "devenv-2.21", |
||||
"repo": "nix", |
||||
"type": "github" |
||||
} |
||||
}, |
||||
"nixpkgs": { |
||||
"locked": { |
||||
"lastModified": 1692808169, |
||||
"narHash": "sha256-x9Opq06rIiwdwGeK2Ykj69dNc2IvUH1fY55Wm7atwrE=", |
||||
"owner": "NixOS", |
||||
"repo": "nixpkgs", |
||||
"rev": "9201b5ff357e781bf014d0330d18555695df7ba8", |
||||
"type": "github" |
||||
}, |
||||
"original": { |
||||
"owner": "NixOS", |
||||
"ref": "nixpkgs-unstable", |
||||
"repo": "nixpkgs", |
||||
"type": "github" |
||||
} |
||||
}, |
||||
"nixpkgs-lib": { |
||||
"locked": { |
||||
"lastModified": 1717284937, |
||||
"narHash": "sha256-lIbdfCsf8LMFloheeE6N31+BMIeixqyQWbSr2vk79EQ=", |
||||
"type": "tarball", |
||||
"url": "https://github.com/NixOS/nixpkgs/archive/eb9ceca17df2ea50a250b6b27f7bf6ab0186f198.tar.gz" |
||||
}, |
||||
"original": { |
||||
"type": "tarball", |
||||
"url": "https://github.com/NixOS/nixpkgs/archive/eb9ceca17df2ea50a250b6b27f7bf6ab0186f198.tar.gz" |
||||
} |
||||
}, |
||||
"nixpkgs-regression": { |
||||
"locked": { |
||||
"lastModified": 1643052045, |
||||
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=", |
||||
"owner": "NixOS", |
||||
"repo": "nixpkgs", |
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", |
||||
"type": "github" |
||||
}, |
||||
"original": { |
||||
"owner": "NixOS", |
||||
"repo": "nixpkgs", |
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", |
||||
"type": "github" |
||||
} |
||||
}, |
||||
"nixpkgs-regression_2": { |
||||
"locked": { |
||||
"lastModified": 1643052045, |
||||
"narHash": "sha256-uGJ0VXIhWKGXxkeNnq4TvV3CIOkUJ3PAoLZ3HMzNVMw=", |
||||
"owner": "NixOS", |
||||
"repo": "nixpkgs", |
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", |
||||
"type": "github" |
||||
}, |
||||
"original": { |
||||
"owner": "NixOS", |
||||
"repo": "nixpkgs", |
||||
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", |
||||
"type": "github" |
||||
} |
||||
}, |
||||
"nixpkgs-stable": { |
||||
"locked": { |
||||
"lastModified": 1710695816, |
||||
"narHash": "sha256-3Eh7fhEID17pv9ZxrPwCLfqXnYP006RKzSs0JptsN84=", |
||||
"owner": "NixOS", |
||||
"repo": "nixpkgs", |
||||
"rev": "614b4613980a522ba49f0d194531beddbb7220d3", |
||||
"type": "github" |
||||
}, |
||||
"original": { |
||||
"owner": "NixOS", |
||||
"ref": "nixos-23.11", |
||||
"repo": "nixpkgs", |
||||
"type": "github" |
||||
} |
||||
}, |
||||
"nixpkgs_2": { |
||||
"locked": { |
||||
"lastModified": 1713361204, |
||||
"narHash": "sha256-TA6EDunWTkc5FvDCqU3W2T3SFn0gRZqh6D/hJnM02MM=", |
||||
"owner": "cachix", |
||||
"repo": "devenv-nixpkgs", |
||||
"rev": "285676e87ad9f0ca23d8714a6ab61e7e027020c6", |
||||
"type": "github" |
||||
}, |
||||
"original": { |
||||
"owner": "cachix", |
||||
"ref": "rolling", |
||||
"repo": "devenv-nixpkgs", |
||||
"type": "github" |
||||
} |
||||
}, |
||||
"nixpkgs_3": { |
||||
"locked": { |
||||
"lastModified": 1717112898, |
||||
"narHash": "sha256-7R2ZvOnvd9h8fDd65p0JnB7wXfUvreox3xFdYWd1BnY=", |
||||
"owner": "NixOS", |
||||
"repo": "nixpkgs", |
||||
"rev": "6132b0f6e344ce2fe34fc051b72fb46e34f668e0", |
||||
"type": "github" |
||||
}, |
||||
"original": { |
||||
"owner": "NixOS", |
||||
"ref": "nixpkgs-unstable", |
||||
"repo": "nixpkgs", |
||||
"type": "github" |
||||
} |
||||
}, |
||||
"poetry2nix": { |
||||
"inputs": { |
||||
"flake-utils": "flake-utils", |
||||
"nix-github-actions": "nix-github-actions", |
||||
"nixpkgs": [ |
||||
"devenv", |
||||
"cachix", |
||||
"devenv", |
||||
"nixpkgs" |
||||
] |
||||
}, |
||||
"locked": { |
||||
"lastModified": 1692876271, |
||||
"narHash": "sha256-IXfZEkI0Mal5y1jr6IRWMqK8GW2/f28xJenZIPQqkY0=", |
||||
"owner": "nix-community", |
||||
"repo": "poetry2nix", |
||||
"rev": "d5006be9c2c2417dafb2e2e5034d83fabd207ee3", |
||||
"type": "github" |
||||
}, |
||||
"original": { |
||||
"owner": "nix-community", |
||||
"repo": "poetry2nix", |
||||
"type": "github" |
||||
} |
||||
}, |
||||
"pre-commit-hooks": { |
||||
"inputs": { |
||||
"flake-compat": [ |
||||
"devenv", |
||||
"flake-compat" |
||||
], |
||||
"flake-utils": "flake-utils_2", |
||||
"gitignore": "gitignore", |
||||
"nixpkgs": [ |
||||
"devenv", |
||||
"nixpkgs" |
||||
], |
||||
"nixpkgs-stable": "nixpkgs-stable" |
||||
}, |
||||
"locked": { |
||||
"lastModified": 1713775815, |
||||
"narHash": "sha256-Wu9cdYTnGQQwtT20QQMg7jzkANKQjwBD9iccfGKkfls=", |
||||
"owner": "cachix", |
||||
"repo": "pre-commit-hooks.nix", |
||||
"rev": "2ac4dcbf55ed43f3be0bae15e181f08a57af24a4", |
||||
"type": "github" |
||||
}, |
||||
"original": { |
||||
"owner": "cachix", |
||||
"repo": "pre-commit-hooks.nix", |
||||
"type": "github" |
||||
} |
||||
}, |
||||
"root": { |
||||
"inputs": { |
||||
"devenv": "devenv", |
||||
"flake-parts": "flake-parts", |
||||
"nixpkgs": "nixpkgs_3" |
||||
} |
||||
}, |
||||
"systems": { |
||||
"locked": { |
||||
"lastModified": 1681028828, |
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", |
||||
"owner": "nix-systems", |
||||
"repo": "default", |
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", |
||||
"type": "github" |
||||
}, |
||||
"original": { |
||||
"owner": "nix-systems", |
||||
"repo": "default", |
||||
"type": "github" |
||||
} |
||||
}, |
||||
"systems_2": { |
||||
"locked": { |
||||
"lastModified": 1681028828, |
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", |
||||
"owner": "nix-systems", |
||||
"repo": "default", |
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", |
||||
"type": "github" |
||||
}, |
||||
"original": { |
||||
"owner": "nix-systems", |
||||
"repo": "default", |
||||
"type": "github" |
||||
} |
||||
} |
||||
}, |
||||
"root": "root", |
||||
"version": 7 |
||||
} |
||||
@ -0,0 +1,39 @@ |
||||
{ |
||||
inputs = { |
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; |
||||
flake-parts.url = "github:hercules-ci/flake-parts"; |
||||
devenv.url = "github:cachix/devenv"; |
||||
}; |
||||
|
||||
outputs = inputs@{ flake-parts, ... }: |
||||
flake-parts.lib.mkFlake { inherit inputs; } { |
||||
imports = [ |
||||
inputs.devenv.flakeModule |
||||
]; |
||||
|
||||
systems = [ "x86_64-linux" "x86_64-darwin" "aarch64-darwin" ]; |
||||
|
||||
perSystem = { config, self', inputs', pkgs, system, ... }: rec { |
||||
devenv.shells = { |
||||
default = { |
||||
languages = { |
||||
go.enable = true; |
||||
}; |
||||
|
||||
pre-commit.hooks = { |
||||
nixpkgs-fmt.enable = true; |
||||
}; |
||||
|
||||
packages = with pkgs; [ |
||||
golangci-lint |
||||
]; |
||||
|
||||
# https://github.com/cachix/devenv/issues/528#issuecomment-1556108767 |
||||
containers = pkgs.lib.mkForce { }; |
||||
}; |
||||
|
||||
ci = devenv.shells.default; |
||||
}; |
||||
}; |
||||
}; |
||||
} |
||||
@ -0,0 +1,11 @@ |
||||
package errors |
||||
|
||||
import "errors" |
||||
|
||||
func New(text string) error { |
||||
return errors.New(text) |
||||
} |
||||
|
||||
func As(err error, target interface{}) bool { |
||||
return errors.As(err, target) |
||||
} |
||||
@ -0,0 +1,9 @@ |
||||
//go:build go1.20
|
||||
|
||||
package errors |
||||
|
||||
import "errors" |
||||
|
||||
func Join(errs ...error) error { |
||||
return errors.Join(errs...) |
||||
} |
||||
@ -0,0 +1,61 @@ |
||||
//go:build !go1.20
|
||||
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package errors |
||||
|
||||
// Join returns an error that wraps the given errors.
|
||||
// Any nil error values are discarded.
|
||||
// Join returns nil if every value in errs is nil.
|
||||
// The error formats as the concatenation of the strings obtained
|
||||
// by calling the Error method of each element of errs, with a newline
|
||||
// between each string.
|
||||
//
|
||||
// A non-nil error returned by Join implements the Unwrap() []error method.
|
||||
func Join(errs ...error) error { |
||||
n := 0 |
||||
for _, err := range errs { |
||||
if err != nil { |
||||
n++ |
||||
} |
||||
} |
||||
if n == 0 { |
||||
return nil |
||||
} |
||||
e := &joinError{ |
||||
errs: make([]error, 0, n), |
||||
} |
||||
for _, err := range errs { |
||||
if err != nil { |
||||
e.errs = append(e.errs, err) |
||||
} |
||||
} |
||||
return e |
||||
} |
||||
|
||||
type joinError struct { |
||||
errs []error |
||||
} |
||||
|
||||
func (e *joinError) Error() string { |
||||
// Since Join returns nil if every value in errs is nil,
|
||||
// e.errs cannot be empty.
|
||||
if len(e.errs) == 1 { |
||||
return e.errs[0].Error() |
||||
} |
||||
|
||||
b := []byte(e.errs[0].Error()) |
||||
for _, err := range e.errs[1:] { |
||||
b = append(b, '\n') |
||||
b = append(b, err.Error()...) |
||||
} |
||||
// At this point, b has at least one byte '\n'.
|
||||
// return unsafe.String(&b[0], len(b))
|
||||
return string(b) |
||||
} |
||||
|
||||
func (e *joinError) Unwrap() []error { |
||||
return e.errs |
||||
} |
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,44 @@ |
||||
//go:build !go1.20
|
||||
|
||||
package mapstructure |
||||
|
||||
import "reflect" |
||||
|
||||
func isComparable(v reflect.Value) bool { |
||||
k := v.Kind() |
||||
switch k { |
||||
case reflect.Invalid: |
||||
return false |
||||
|
||||
case reflect.Array: |
||||
switch v.Type().Elem().Kind() { |
||||
case reflect.Interface, reflect.Array, reflect.Struct: |
||||
for i := 0; i < v.Type().Len(); i++ { |
||||
// if !v.Index(i).Comparable() {
|
||||
if !isComparable(v.Index(i)) { |
||||
return false |
||||
} |
||||
} |
||||
return true |
||||
} |
||||
return v.Type().Comparable() |
||||
|
||||
case reflect.Interface: |
||||
// return v.Elem().Comparable()
|
||||
return isComparable(v.Elem()) |
||||
|
||||
case reflect.Struct: |
||||
for i := 0; i < v.NumField(); i++ { |
||||
return false |
||||
|
||||
// if !v.Field(i).Comparable() {
|
||||
if !isComparable(v.Field(i)) { |
||||
return false |
||||
} |
||||
} |
||||
return true |
||||
|
||||
default: |
||||
return v.Type().Comparable() |
||||
} |
||||
} |
||||
@ -0,0 +1,10 @@ |
||||
//go:build go1.20
|
||||
|
||||
package mapstructure |
||||
|
||||
import "reflect" |
||||
|
||||
// TODO: remove once we drop support for Go <1.20
|
||||
func isComparable(v reflect.Value) bool { |
||||
return v.Comparable() |
||||
} |
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue