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:
4
go.mod
4
go.mod
@@ -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
8
go.sum
@@ -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=
|
||||
|
||||
128
pkg/destroy/ibmcloud/disk.go
Normal file
128
pkg/destroy/ibmcloud/disk.go
Normal 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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
227
vendor/github.com/IBM/go-sdk-core/v5/core/base_service.go
generated
vendored
227
vendor/github.com/IBM/go-sdk-core/v5/core/base_service.go
generated
vendored
@@ -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 (
|
||||
|
||||
3
vendor/github.com/IBM/go-sdk-core/v5/core/constants.go
generated
vendored
3
vendor/github.com/IBM/go-sdk-core/v5/core/constants.go
generated
vendored
@@ -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
|
||||
|
||||
29
vendor/github.com/IBM/go-sdk-core/v5/core/container_authenticator.go
generated
vendored
29
vendor/github.com/IBM/go-sdk-core/v5/core/container_authenticator.go
generated
vendored
@@ -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)
|
||||
}
|
||||
|
||||
56
vendor/github.com/IBM/go-sdk-core/v5/core/iam_authenticator.go
generated
vendored
56
vendor/github.com/IBM/go-sdk-core/v5/core/iam_authenticator.go
generated
vendored
@@ -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
|
||||
}
|
||||
|
||||
2
vendor/github.com/IBM/go-sdk-core/v5/core/version.go
generated
vendored
2
vendor/github.com/IBM/go-sdk-core/v5/core/version.go
generated
vendored
@@ -15,4 +15,4 @@ package core
|
||||
// limitations under the License.
|
||||
|
||||
// Version of the SDK
|
||||
const __VERSION__ = "5.9.1"
|
||||
const __VERSION__ = "5.9.5"
|
||||
|
||||
5
vendor/github.com/IBM/go-sdk-core/v5/core/vpc_instance_authenticator.go
generated
vendored
5
vendor/github.com/IBM/go-sdk-core/v5/core/vpc_instance_authenticator.go
generated
vendored
@@ -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 {
|
||||
|
||||
2
vendor/github.com/IBM/vpc-go-sdk/common/version.go
generated
vendored
2
vendor/github.com/IBM/vpc-go-sdk/common/version.go
generated
vendored
@@ -1,4 +1,4 @@
|
||||
package common
|
||||
|
||||
// Version of the SDK
|
||||
const Version = "1.0.1"
|
||||
const Version = "0.20.0"
|
||||
|
||||
30098
vendor/github.com/IBM/vpc-go-sdk/vpcv1/vpc_v1.go
generated
vendored
30098
vendor/github.com/IBM/vpc-go-sdk/vpcv1/vpc_v1.go
generated
vendored
File diff suppressed because it is too large
Load Diff
6
vendor/modules.txt
vendored
6
vendor/modules.txt
vendored
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user