1
0
mirror of https://github.com/openshift/installer.git synced 2026-02-05 15:47:14 +01:00

Merge pull request #5962 from sameshai/samdev

Bug 2047732: [IBM]Volume is not deleted after destroy cluster
This commit is contained in:
openshift-ci[bot]
2022-06-23 10:48:45 +00:00
committed by GitHub
13 changed files with 24653 additions and 5945 deletions

4
go.mod
View File

@@ -12,10 +12,10 @@ require (
github.com/IBM-Cloud/bluemix-go v0.0.0-20211102075456-ffc4e11dfb16
github.com/IBM-Cloud/power-go-client v1.1.5
github.com/IBM/go-sdk-core/v4 v4.9.0
github.com/IBM/go-sdk-core/v5 v5.9.1
github.com/IBM/go-sdk-core/v5 v5.9.5
github.com/IBM/networking-go-sdk v0.14.0
github.com/IBM/platform-services-go-sdk v0.18.16
github.com/IBM/vpc-go-sdk v1.0.1
github.com/IBM/vpc-go-sdk v0.20.0
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1264
github.com/aliyun/aliyun-oss-go-sdk v2.1.8+incompatible
github.com/apparentlymart/go-cidr v1.1.0

8
go.sum
View File

@@ -100,18 +100,18 @@ github.com/IBM/go-sdk-core v1.1.0/go.mod h1:2pcx9YWsIsZ3I7kH+1amiAkXvLTZtAq9kbxs
github.com/IBM/go-sdk-core/v4 v4.5.1/go.mod h1:lTUXbqIX6/aAbSCkP6q59+dyFsTwZAc0ewRS2vJWVbg=
github.com/IBM/go-sdk-core/v4 v4.9.0 h1:OkSg5kaEfVoNuBA4IsIOz8Ur5rbGHbWxmWCZ7nK/oc0=
github.com/IBM/go-sdk-core/v4 v4.9.0/go.mod h1:DbQ+3pFoIjxGGTEiA9zQ2V0cemMNmFMkLBBnR729HKg=
github.com/IBM/go-sdk-core/v5 v5.2.0/go.mod h1:vyNdbFujJtdTj9HbihtvKwwS3k/GKSKpOx9ZIQ6MWDY=
github.com/IBM/go-sdk-core/v5 v5.4.0/go.mod h1:+MNa5Jbqb9FO7KEevo982Pb/YXr4adkyEffJlPs2TGc=
github.com/IBM/go-sdk-core/v5 v5.4.2/go.mod h1:Sn+z+qTDREQvCr+UFa22TqqfXNxx3o723y8GsfLV8e0=
github.com/IBM/go-sdk-core/v5 v5.9.1 h1:06pXbD9Rgmqqe2HA5YAeQbB4eYRRFgIoOT+Kh3cp1zo=
github.com/IBM/go-sdk-core/v5 v5.9.1/go.mod h1:axE2JrRq79gIJTjKPBwV6gWHswvVptBjbcvvCPIxARM=
github.com/IBM/go-sdk-core/v5 v5.9.5 h1:+uMyHpOyBlFFd/I0PB+7JqqXOPY2DzRR0tbBjTc4d/g=
github.com/IBM/go-sdk-core/v5 v5.9.5/go.mod h1:YlOwV9LeuclmT/qi/LAK2AsobbAP42veV0j68/rlZsE=
github.com/IBM/networking-go-sdk v0.14.0 h1:CWQufnSxynqxYORGbkSqePPSZ33fUijiwmcuZsMRv/Q=
github.com/IBM/networking-go-sdk v0.14.0/go.mod h1:8f3hEoWVUSYKbaIj7WZhdeJaseYGDSY85Iz+PqxLEbQ=
github.com/IBM/platform-services-go-sdk v0.18.16 h1:blYycstPoNtPKtu1uZe240WvzcJENy/Lzx+HMUA8bOo=
github.com/IBM/platform-services-go-sdk v0.18.16/go.mod h1:awc7TZUeGMlToSeMSaWEz34Knf0lQnuGWumcI4pcuoM=
github.com/IBM/vpc-go-sdk v0.6.0/go.mod h1:wxicPDnSTPXt1eNxSO/9KNGqOW9RMgxPoSh4gd8KJY4=
github.com/IBM/vpc-go-sdk v1.0.1 h1:D2cu4KRsM8Q8bLWz/uxp8m7nzUm33mcgDv1sD0w/E8M=
github.com/IBM/vpc-go-sdk v1.0.1/go.mod h1:bhd7r482lV30UJz46r2oRgYGawGEo+TuS41ZLIY65y0=
github.com/IBM/vpc-go-sdk v0.20.0 h1:xetXFYv/GDSOVTm2h7MSki2D9x2dpNsiwHVRmdSIrPc=
github.com/IBM/vpc-go-sdk v0.20.0/go.mod h1:YPyIfI+/qhPqlYp+I7dyx2U1GLcXgp/jzVvsZfUH4y8=
github.com/InVisionApp/go-health v2.1.0+incompatible/go.mod h1:/+Gv1o8JUsrjC6pi6MN6/CgKJo4OqZ6x77XAnImrzhg=
github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=

View File

@@ -0,0 +1,128 @@
package ibmcloud
import (
"net/http"
"strings"
"github.com/pkg/errors"
)
func (o *ClusterUninstaller) listDisks() ([]cloudResource, error) {
o.Logger.Infof("Listing disks")
result := []cloudResource{}
clusterOwnedTag := o.clusterLabelFilter()
options := o.vpcSvc.NewListVolumesOptions()
for {
ctx, cancel := o.contextWithTimeout()
defer cancel()
options.SetLimit(100)
resources, _, err := o.vpcSvc.ListVolumesWithContext(ctx, options)
if err != nil {
return nil, errors.Wrap(err, "Listing disks failed")
}
for _, volume := range resources.Volumes {
userTags := strings.Join(volume.UserTags, ",")
if strings.Contains(userTags, clusterOwnedTag) {
o.Logger.Debugf("Found disk: %s", *volume.ID)
result = append(result, cloudResource{
key: *volume.ID,
name: *volume.Name,
status: *volume.Status,
typeName: "disk",
id: *volume.ID,
})
}
}
//This was the last page, please exit the loop.
if resources.Next == nil {
o.Logger.Debugf("All disks fetched")
break
}
//Set the start for the next page.
start, _ := resources.GetNextStart()
o.Logger.Debugf("Listing next page %s", *start)
options.SetStart(*start)
}
return result, nil
}
func (o *ClusterUninstaller) deleteDisk(item cloudResource) error {
o.Logger.Infof("Deleting disk %s", item.id)
ctx, cancel := o.contextWithTimeout()
defer cancel()
options := o.vpcSvc.NewDeleteVolumeOptions(item.id)
details, err := o.vpcSvc.DeleteVolumeWithContext(ctx, options)
if err != nil && details.StatusCode != http.StatusNotFound {
return errors.Wrapf(err, "Failed to delete disk name=%s, id=%s.If this error continues to persist for more than 20 minutes then please try to manually cleanup the volume using - ibmcloud is vold %s", item.name, item.id, item.id)
}
if err != nil && details.StatusCode == http.StatusNotFound {
// The resource is gone
o.deletePendingItems(item.typeName, []cloudResource{item})
o.Logger.Infof("Deleted disk %s", item.id)
}
return nil
}
func (o *ClusterUninstaller) waitForDiskDeletion(item cloudResource) error {
o.Logger.Infof("Waiting for disk %s to be deleted", item.id)
var skip = false
err := o.Retry(func() (error, bool) {
ctx, cancel := o.contextWithTimeout()
defer cancel()
volumeOptions := o.vpcSvc.NewGetVolumeOptions(item.id)
_, response, err := o.vpcSvc.GetVolumeWithContext(ctx, volumeOptions)
// Keep retry, until GetVolume returns volume not found
if err != nil && response.StatusCode == http.StatusNotFound {
skip = true
return nil, skip
}
return err, false // continue retry as we are not seeing error which means volume is available
})
if err == nil && skip {
// The resource is gone
o.deletePendingItems(item.typeName, []cloudResource{item})
o.Logger.Infof("Deleted disk %s", item.id)
} else {
return errors.Wrapf(err, "Failed to delete disk name=%s, id=%s.If this error continues to persist for more than 20 minutes then please try to manually cleanup the volume using - ibmcloud is vold %s", item.name, item.id, item.id)
}
return err
}
// destroyDisks removes all disk resources that have a name prefixed
// with the cluster's infra ID.
func (o *ClusterUninstaller) destroyDisks() error {
found, err := o.listDisks()
if err != nil {
return err
}
items := o.insertPendingItems("disk", found)
for _, item := range items {
err := o.deleteDisk(item)
if err != nil {
o.errorTracker.suppressWarning(item.key, err, o.Logger)
}
}
for _, item := range items {
err := o.waitForDiskDeletion(item)
if err != nil {
o.errorTracker.suppressWarning(item.key, err, o.Logger)
}
}
if items = o.getPendingItems("disk"); len(items) > 0 {
return errors.Errorf("%d items pending", len(items))
}
return nil
}

View File

@@ -51,6 +51,7 @@ type ClusterUninstaller struct {
iamPolicyManagementSvc *iampolicymanagementv1.IamPolicyManagementV1
zonesSvc *zonesv1.ZonesV1
dnsRecordsSvc *dnsrecordsv1.DnsRecordsV1
maxRetryAttempt int
resourceGroupID string
cosInstanceID string
@@ -74,9 +75,33 @@ func New(logger logrus.FieldLogger, metadata *types.ClusterMetadata) (providers.
UserProvidedSubnets: metadata.ClusterPlatformMetadata.IBMCloud.Subnets,
UserProvidedVPC: metadata.ClusterPlatformMetadata.IBMCloud.VPC,
pendingItemTracker: newPendingItemTracker(),
maxRetryAttempt: 30,
}, nil
}
// Retry ...
func (o *ClusterUninstaller) Retry(funcToRetry func() (error, bool)) error {
var err error
var stopRetry bool
retryGap := 10
for i := 0; i < o.maxRetryAttempt; i++ {
if i > 0 {
time.Sleep(time.Duration(retryGap) * time.Second)
}
// Call function which required retry, retry is decided by function itself
err, stopRetry = funcToRetry()
if stopRetry {
break
}
if (i + 1) < o.maxRetryAttempt {
o.Logger.Infof("UNEXPECTED RESULT, Re-attempting execution .., attempt=%d, retry-gap=%d, max-retry-Attempts=%d, stopRetry=%t, error=%v", i+1,
retryGap, o.maxRetryAttempt, stopRetry, err)
}
}
return err
}
// Run is the entrypoint to start the uninstall process
func (o *ClusterUninstaller) Run() (*types.ClusterQuota, error) {
err := o.loadSDKServices()
@@ -100,6 +125,7 @@ func (o *ClusterUninstaller) destroyCluster() error {
{name: "Stop instances", execute: o.stopInstances},
}, {
{name: "Instances", execute: o.destroyInstances},
{name: "Disks", execute: o.destroyDisks},
}, {
{name: "Load Balancers", execute: o.destroyLoadBalancers},
}, {
@@ -403,3 +429,7 @@ func (t pendingItemTracker) deletePendingItems(itemType string, items []cloudRes
func isErrorStatus(code int64) bool {
return code != 0 && (code < 200 || code >= 300)
}
func (o *ClusterUninstaller) clusterLabelFilter() string {
return fmt.Sprintf("kubernetes-io-cluster-%s:owned", o.InfraID)
}

View File

@@ -1,6 +1,6 @@
package core
// (C) Copyright IBM Corp. 2019, 2021.
// (C) Copyright IBM Corp. 2019, 2022.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -227,31 +227,69 @@ func (service *BaseService) SetDefaultHeaders(headers http.Header) {
service.DefaultHeaders = headers
}
// SetHTTPClient updates the client handling the requests.
// SetHTTPClient will set "client" as the http.Client instance to be used
// to invoke individual HTTP requests.
// If automatic retries are currently enabled on "service", then
// "client" will be set as the embedded client instance within
// the retryable client; otherwise "client" will be stored
// directly on "service".
func (service *BaseService) SetHTTPClient(client *http.Client) {
service.Client = client
setMinimumTLSVersion(client)
if isRetryableClient(service.Client) {
// If "service" is currently holding a retryable client,
// then set "client" as the embedded client used for individual requests.
tr := service.Client.Transport.(*retryablehttp.RoundTripper)
tr.Client.HTTPClient = client
} else {
// Otherwise, just hang "client" directly off the base service.
service.Client = client
}
}
// DisableSSLVerification skips SSL verification.
// This function sets a new http.Client instance on the service
// and configures it to bypass verification of server certificates
// and host names, making the client susceptible to "man-in-the-middle"
// attacks. This should be used only for testing.
// GetHTTPClient will return the http.Client instance used
// to invoke individual HTTP requests.
// If automatic retries are enabled, the returned value will
// be the http.Client instance embedded within the retryable client.
// If automatic retries are not enabled, then the returned value
// will simply be the "Client" field of the base service.
func (service *BaseService) GetHTTPClient() *http.Client {
if isRetryableClient(service.Client) {
tr := service.Client.Transport.(*retryablehttp.RoundTripper)
return tr.Client.HTTPClient
}
return service.Client
}
// DisableSSLVerification will configure the service to
// skip the verification of server certificates and hostnames.
// This will make the client susceptible to "man-in-the-middle"
// attacks. This should be used only for testing or in secure
// environments.
func (service *BaseService) DisableSSLVerification() {
client := DefaultHTTPClient()
tr, ok := client.Transport.(*http.Transport)
if tr != nil && ok {
tr.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} // #nosec G402
// Make sure we have a non-nil client hanging off the BaseService.
if service.Client == nil {
service.Client = DefaultHTTPClient()
}
service.SetHTTPClient(client)
client := service.GetHTTPClient()
if tr, ok := client.Transport.(*http.Transport); tr != nil && ok {
// If no TLS config, then create a new one.
if tr.TLSClientConfig == nil {
tr.TLSClientConfig = &tls.Config{} // #nosec G402
}
// Disable server ssl cert & hostname verification.
tr.TLSClientConfig.InsecureSkipVerify = true // #nosec G402
}
}
// IsSSLDisabled returns true if and only if the service's http.Client instance
// is configured to skip verification of server SSL certificates.
func (service *BaseService) IsSSLDisabled() bool {
if service.Client != nil {
if tr, ok := service.Client.Transport.(*http.Transport); tr != nil && ok {
client := service.GetHTTPClient()
if client != nil {
if tr, ok := client.Transport.(*http.Transport); tr != nil && ok {
if tr.TLSClientConfig != nil {
return tr.TLSClientConfig.InsecureSkipVerify
}
@@ -260,6 +298,17 @@ func (service *BaseService) IsSSLDisabled() bool {
return false
}
// setMinimumTLSVersion sets the minimum TLS version required by the client to TLS v1.2
func setMinimumTLSVersion(client *http.Client) {
if tr, ok := client.Transport.(*http.Transport); tr != nil && ok {
if tr.TLSClientConfig == nil {
tr.TLSClientConfig = &tls.Config{} // #nosec G402
}
tr.TLSClientConfig.MinVersion = tls.VersionTLS12
}
}
// SetEnableGzipCompression sets the service's EnableGzipCompression field
func (service *BaseService) SetEnableGzipCompression(enableGzip bool) {
service.Options.EnableGzipCompression = enableGzip
@@ -278,7 +327,7 @@ func (service *BaseService) buildUserAgent() string {
// SetUserAgent sets the user agent value.
func (service *BaseService) SetUserAgent(userAgentString string) {
if userAgentString == "" {
service.UserAgent = service.buildUserAgent()
userAgentString = service.buildUserAgent()
}
service.UserAgent = userAgentString
}
@@ -348,25 +397,9 @@ func (service *BaseService) Request(req *http.Request, result interface{}) (deta
}
}
// Invoke the request, then check for errors during the invocation.
var httpResponse *http.Response
// Try to get the retryable Client hidden inside service.Client
retryableClient := getRetryableHTTPClient(service.Client)
if retryableClient != nil {
retryableRequest, retryableErr := retryablehttp.FromRequest(req)
if retryableErr != nil {
err = fmt.Errorf(ERRORMSG_CREATE_RETRYABLE_REQ, retryableErr.Error())
return
}
// Invoke the retryable request.
httpResponse, err = retryableClient.Do(retryableRequest)
} else {
// Invoke the normal (non-retryable) request.
httpResponse, err = service.Client.Do(req)
}
// Check for errors during the invocation.
httpResponse, err = service.Client.Do(req)
if err != nil {
if strings.Contains(err.Error(), SSL_CERTIFICATION_ERROR) {
err = fmt.Errorf(ERRORMSG_SSL_VERIFICATION_FAILED + "\n" + err.Error())
@@ -577,31 +610,89 @@ func getErrorMessage(responseMap map[string]interface{}, statusCode int) string
return http.StatusText(statusCode)
}
// EnableRetries will construct a "retryable" HTTP Client with the specified
// configuration, and then set it on the service instance.
// If maxRetries and/or maxRetryInterval are specified as 0, then default values
// are used instead.
func (service *BaseService) EnableRetries(maxRetries int, maxRetryInterval time.Duration) {
client := NewRetryableHTTPClient()
if maxRetries > 0 {
client.RetryMax = maxRetries
// isRetryableClient() will return true if and only if "client" is
// an http.Client instance that is configured for automatic retries.
// A retryable client is a client whose transport is a
// retryablehttp.RoundTripper instance.
func isRetryableClient(client *http.Client) bool {
var isRetryable bool = false
if client != nil && client.Transport != nil {
_, isRetryable = client.Transport.(*retryablehttp.RoundTripper)
}
if maxRetryInterval > 0 {
client.RetryWaitMax = maxRetryInterval
}
service.SetHTTPClient(client.StandardClient())
return isRetryable
}
// DisableRetries will disable automatic retries by constructing a new
// default (non-retryable) HTTP Client instance and setting it on the service.
// EnableRetries will configure the service to perform automatic retries of failed requests.
// If "maxRetries" and/or "maxRetryInterval" are specified as 0, then default values
// are used instead.
//
// In a scenario where retries ARE NOT enabled:
// - BaseService.Client will be a "normal" http.Client instance used to invoke requests
// - BaseService.Client.Transport will be an instance of the default http.RoundTripper
// - BaseService.Client.Do() calls http.RoundTripper.RoundTrip() to invoke the request
// - Only one http.Client instance needed/used (BaseService.Client) in this scenario
// - Result: "normal" request processing without any automatic retries being performed
//
// In a scenario where retries ARE enabled:
// - BaseService.Client will be a "shim" http.Client instance
// - BaseService.Client.Transport will be an instance of retryablehttp.RoundTripper
// - BaseService.Client.Do() calls retryablehttp.RoundTripper.RoundTrip() (via the shim)
// to invoke the request
// - The retryablehttp.RoundTripper instance is configured with the retryablehttp.Client
// instance which holds the various retry config properties (max retries, max interval, etc.)
// - The retryablehttp.RoundTripper.RoundTrip() method triggers the retry logic in the retryablehttp.Client
// - The retryablehttp.Client instance's HTTPClient field holds a "normal" http.Client instance,
// which is used to invoke individual requests within the retry loop.
// - To summarize, there are three client instances used for request processing in this scenario:
// - The "shim" http.Client instance (BaseService.Client)
// - The retryablehttp.Client instance that implements the retry logic
// - The "normal" http.Client instance embedded in the retryablehttp.Client which is used to invoke
// individual requests within the retry logic
// - Result: Each request is invoked such that the automatic retry logic is employed
func (service *BaseService) EnableRetries(maxRetries int, maxRetryInterval time.Duration) {
if isRetryableClient(service.Client) {
// If retries are already enabled, then we just need to adjust
// the retryable client's config using "maxRetries" and "maxRetryInterval".
tr := service.Client.Transport.(*retryablehttp.RoundTripper)
if maxRetries > 0 {
tr.Client.RetryMax = maxRetries
}
if maxRetryInterval > 0 {
tr.Client.RetryWaitMax = maxRetryInterval
}
} else {
// Otherwise, we need to create a new retryable client instance
// and hang it off the base service.
client := NewRetryableClientWithHTTPClient(service.Client)
if maxRetries > 0 {
client.RetryMax = maxRetries
}
if maxRetryInterval > 0 {
client.RetryWaitMax = maxRetryInterval
}
// Hang the retryable client off the base service via the "shim" client.
service.Client = client.StandardClient()
}
}
// DisableRetries will disable automatic retries in the service.
func (service *BaseService) DisableRetries() {
service.SetHTTPClient(DefaultHTTPClient())
if isRetryableClient(service.Client) {
// If the current client hanging off the base service is retryable,
// then we need to get ahold of the embedded http.Client instance
// and set that on the base service and effectively remove
// the retryable client instance.
tr := service.Client.Transport.(*retryablehttp.RoundTripper)
service.Client = tr.Client.HTTPClient
}
}
// DefaultHTTPClient returns a non-retryable http client with default configuration.
func DefaultHTTPClient() *http.Client {
return cleanhttp.DefaultPooledClient()
client := cleanhttp.DefaultPooledClient()
setMinimumTLSVersion(client)
return client
}
// httpLogger is a shim layer used to allow the Go core's logger to be used with the retryablehttp interfaces.
@@ -615,30 +706,34 @@ func (l *httpLogger) Printf(format string, inserts ...interface{}) {
}
}
// NewRetryableHTTPClient returns a new instance of go-retryablehttp.Client
// NewRetryableHTTPClient returns a new instance of a retryable client
// with a default configuration that supports Go SDK usage.
func NewRetryableHTTPClient() *retryablehttp.Client {
return NewRetryableClientWithHTTPClient(nil)
}
// NewRetryableClientWithHTTPClient will return a new instance of a
// retryable client, using "httpClient" as the embedded client used to
// invoke individual requests within the retry logic.
// If "httpClient" is passed in as nil, then a default HTTP client will be
// used as the embedded client instead.
func NewRetryableClientWithHTTPClient(httpClient *http.Client) *retryablehttp.Client {
client := retryablehttp.NewClient()
client.Logger = &httpLogger{}
client.CheckRetry = IBMCloudSDKRetryPolicy
client.Backoff = IBMCloudSDKBackoffPolicy
client.ErrorHandler = retryablehttp.PassthroughErrorHandler
return client
}
// getRetryableHTTPClient returns the "retryable" Client hidden inside the specified http.Client instance
// or nil if "client" is not hiding a retryable Client instance.
func getRetryableHTTPClient(client *http.Client) *retryablehttp.Client {
if client != nil {
if client.Transport != nil {
// A retryable client will have its Transport field set to an
// instance of retryablehttp.RoundTripper.
if rt, ok := client.Transport.(*retryablehttp.RoundTripper); ok {
return rt.Client
}
}
if httpClient != nil {
// If a non-nil http client was passed in, then let's use that
// as our embedded client used to invoke individual requests.
client.HTTPClient = httpClient
} else {
// Otherwise, we'll use construct a default HTTP client and use that
client.HTTPClient = DefaultHTTPClient()
}
return nil
return client
}
var (

View File

@@ -1,6 +1,6 @@
package core
// (C) Copyright IBM Corp. 2019, 2021.
// (C) Copyright IBM Corp. 2019, 2022.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@@ -76,7 +76,6 @@ const (
ERRORMSG_PARAM_NOT_SLICE = "The 'slice' parameter must be a slice"
ERRORMSG_MARSHAL_SLICE = "An error occurred while marshalling the slice: %s"
ERRORMSG_CONVERT_SLICE = "An error occurred while converting 'slice' to string slice"
ERRORMSG_CREATE_RETRYABLE_REQ = "An error occurred while creating a retryable http Request: %s"
ERRORMSG_UNEXPECTED_STATUS_CODE = "Unexpected HTTP status code %d (%s)"
ERRORMSG_UNMARSHAL_AUTH_RESPONSE = "error unmarshalling authentication response: %s"
ERRORMSG_UNABLE_RETRIEVE_CRTOKEN = "unable to retrieve compute resource token value: %s" // #nosec G101

View File

@@ -54,7 +54,8 @@ type ContainerAuthenticator struct {
// [optional] The IAM token server's base endpoint URL.
// Default value: "https://iam.cloud.ibm.com"
URL string
URL string
urlInit sync.Once
// [optional] The ClientID and ClientSecret fields are used to form a "basic auth"
// Authorization header for interactions with the IAM token server.
@@ -223,6 +224,20 @@ func (authenticator *ContainerAuthenticator) Authenticate(request *http.Request)
return nil
}
// url returns the authenticator's URL property after potentially initializing it.
func (authenticator *ContainerAuthenticator) url() string {
authenticator.urlInit.Do(func() {
if authenticator.URL == "" {
// If URL was not specified, then use the default IAM endpoint.
authenticator.URL = defaultIamTokenServerEndpoint
} else {
// Canonicalize the URL by removing the operation path if it was specified by the user.
authenticator.URL = strings.TrimSuffix(authenticator.URL, iamAuthOperationPathGetToken)
}
})
return authenticator.URL
}
// getTokenData returns the tokenData field from the authenticator with synchronization.
func (authenticator *ContainerAuthenticator) getTokenData() *iamTokenData {
authenticator.tokenDataMutex.Lock()
@@ -332,7 +347,6 @@ func (authenticator *ContainerAuthenticator) invokeRequestTokenData() error {
// that to obtain a new IAM access token from the IAM token server.
func (authenticator *ContainerAuthenticator) RequestToken() (*IamTokenServerResponse, error) {
var err error
var operationPath string = "/identity/token"
// First, retrieve the CR token value for this compute resource.
crToken, err := authenticator.retrieveCRToken()
@@ -343,18 +357,9 @@ func (authenticator *ContainerAuthenticator) RequestToken() (*IamTokenServerResp
return nil, NewAuthenticationError(&DetailedResponse{}, err)
}
// Use the default IAM URL if one was not specified by the user.
url := authenticator.URL
if url == "" {
url = defaultIamTokenServerEndpoint
} else {
// Canonicalize the URL by removing the operation path if it was specified by the user.
url = strings.TrimSuffix(url, operationPath)
}
// Set up the request for the IAM "get token" invocation.
builder := NewRequestBuilder(POST)
_, err = builder.ResolveRequestURL(url, operationPath, nil)
_, err = builder.ResolveRequestURL(authenticator.url(), iamAuthOperationPathGetToken, nil)
if err != nil {
return nil, NewAuthenticationError(&DetailedResponse{}, err)
}

View File

@@ -48,7 +48,8 @@ type IamAuthenticator struct {
// The URL representing the IAM token server's endpoint; If not specified,
// a suitable default value will be used [optional].
URL string
URL string
urlInit sync.Once
// The ClientId and ClientSecret fields are used to form a "basic auth"
// Authorization header for interactions with the IAM token server.
@@ -226,6 +227,20 @@ func (authenticator *IamAuthenticator) Authenticate(request *http.Request) error
return nil
}
// url returns the authenticator's URL property after potentially initializing it.
func (authenticator *IamAuthenticator) url() string {
authenticator.urlInit.Do(func() {
if authenticator.URL == "" {
// If URL was not specified, then use the default IAM endpoint.
authenticator.URL = defaultIamTokenServerEndpoint
} else {
// Canonicalize the URL by removing the operation path if it was specified by the user.
authenticator.URL = strings.TrimSuffix(authenticator.URL, iamAuthOperationPathGetToken)
}
})
return authenticator.URL
}
// getTokenData returns the tokenData field from the authenticator.
func (authenticator *IamAuthenticator) getTokenData() *iamTokenData {
authenticator.tokenDataMutex.Lock()
@@ -260,9 +275,24 @@ func (authenticator *IamAuthenticator) setTokenData(tokenData *iamTokenData) {
// and that the ClientId and ClientSecret properties are mutually inclusive.
func (this *IamAuthenticator) Validate() error {
// The user should specify exactly one of ApiKey or RefreshToken.
if this.ApiKey == "" && this.RefreshToken == "" ||
this.ApiKey != "" && this.RefreshToken != "" {
// The user should specify at least one of ApiKey or RefreshToken.
// Note: We'll allow both ApiKey and RefreshToken to be specified,
// in which case we'd use ApiKey in the RequestToken() method.
// Consider this scenario...
// - An IamAuthenticator instance is configured with an apikey and is initially
// declared to be "valid" by the Validate() method.
// - The authenticator is used to construct a service, then an operation is
// invoked which then triggers the very first call to RequestToken().
// - The authenticator invokes the IAM get_token operation and then receives
// the response. The authenticator copies the refresh_token value from the response
// to the authenticator's RefreshToken field.
// - At this point, the authenticator would have non-empty values in both the
// ApiKey and RefreshToken fields.
// This all means that we must try to make sure that a previously-validated
// instance of the authenticator doesn't become invalidated simply through
// normal use.
//
if this.ApiKey == "" && this.RefreshToken == "" {
return fmt.Errorf(ERRORMSG_EXCLUSIVE_PROPS_ERROR, "ApiKey", "RefreshToken")
}
@@ -271,10 +301,9 @@ func (this *IamAuthenticator) Validate() error {
}
// Validate ClientId and ClientSecret.
// If RefreshToken is not specified, then both or neither should be specified.
// If RefreshToken is specified, then both must be specified.
if this.ClientId == "" && this.ClientSecret == "" && this.RefreshToken == "" {
// Do nothing as this is the valid scenario
// Either both or neither should be specified.
if this.ClientId == "" && this.ClientSecret == "" {
// Do nothing as this is the valid scenario.
} else {
// Since it is NOT the case that both properties are empty, make sure BOTH are specified.
if this.ClientId == "" {
@@ -348,17 +377,8 @@ func (authenticator *IamAuthenticator) invokeRequestTokenData() error {
// RequestToken fetches a new access token from the token server.
func (authenticator *IamAuthenticator) RequestToken() (*IamTokenServerResponse, error) {
// Use the default IAM URL if one was not specified by the user.
url := authenticator.URL
if url == "" {
url = defaultIamTokenServerEndpoint
} else {
// Canonicalize the URL by removing the operation path if it was specified by the user.
url = strings.TrimSuffix(url, iamAuthOperationPathGetToken)
}
builder := NewRequestBuilder(POST)
_, err := builder.ResolveRequestURL(url, iamAuthOperationPathGetToken, nil)
_, err := builder.ResolveRequestURL(authenticator.url(), iamAuthOperationPathGetToken, nil)
if err != nil {
return nil, err
}

View File

@@ -15,4 +15,4 @@ package core
// limitations under the License.
// Version of the SDK
const __VERSION__ = "5.9.1"
const __VERSION__ = "5.9.5"

View File

@@ -280,11 +280,6 @@ func (authenticator *VpcInstanceAuthenticator) invokeRequestTokenData() error {
// and then (2) exchange that for an IAM access token.
func (authenticator *VpcInstanceAuthenticator) RequestToken() (iamTokenResponse *IamTokenServerResponse, err error) {
// Use the default VPC base endpoint if user didn't specifiy the URL property.
if authenticator.URL == "" {
authenticator.URL = vpcauthDefaultIMSEndpoint
}
// Retrieve the instance identity token from the VPC Instance Metadata Service.
instanceIdentityToken, err := authenticator.retrieveInstanceIdentityToken()
if err != nil {

View File

@@ -1,4 +1,4 @@
package common
// Version of the SDK
const Version = "1.0.1"
const Version = "0.20.0"

File diff suppressed because it is too large Load Diff

6
vendor/modules.txt vendored
View File

@@ -120,7 +120,7 @@ github.com/IBM-Cloud/power-go-client/power/models
# github.com/IBM/go-sdk-core/v4 v4.9.0
## explicit; go 1.12
github.com/IBM/go-sdk-core/v4/core
# github.com/IBM/go-sdk-core/v5 v5.9.1
# github.com/IBM/go-sdk-core/v5 v5.9.5
## explicit; go 1.14
github.com/IBM/go-sdk-core/v5/core
# github.com/IBM/networking-go-sdk v0.14.0
@@ -135,8 +135,8 @@ github.com/IBM/platform-services-go-sdk/iamidentityv1
github.com/IBM/platform-services-go-sdk/iampolicymanagementv1
github.com/IBM/platform-services-go-sdk/resourcecontrollerv2
github.com/IBM/platform-services-go-sdk/resourcemanagerv2
# github.com/IBM/vpc-go-sdk v1.0.1
## explicit; go 1.14
# github.com/IBM/vpc-go-sdk v0.20.0
## explicit; go 1.16
github.com/IBM/vpc-go-sdk/common
github.com/IBM/vpc-go-sdk/vpcv1
# github.com/PaesslerAG/gval v1.0.0