fix: add involves edge from task to agent:nomos at creation
Plus sync vendor directory for Docker build compatibility.
This commit is contained in:
410
vendor/github.com/oracle/oci-go-sdk/v65/common/circuit_breaker.go
generated
vendored
Normal file
410
vendor/github.com/oracle/oci-go-sdk/v65/common/circuit_breaker.go
generated
vendored
Normal file
@@ -0,0 +1,410 @@
|
||||
// Copyright (c) 2016, 2018, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
// This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/sony/gobreaker"
|
||||
)
|
||||
|
||||
const (
|
||||
// CircuitBreakerDefaultFailureRateThreshold is the requests failure rate which calculates in at most 120 seconds, once reaches to this rate, the circuit breaker state changes from closed to open
|
||||
CircuitBreakerDefaultFailureRateThreshold float64 = 0.80
|
||||
// CircuitBreakerDefaultClosedWindow is the default value of closeStateWindow, which is the cyclic period of the closed state
|
||||
CircuitBreakerDefaultClosedWindow time.Duration = 120 * time.Second
|
||||
// CircuitBreakerDefaultResetTimeout is the default value of openStateWindow, which is the wait time before setting the breaker to halfOpen state from open state
|
||||
CircuitBreakerDefaultResetTimeout time.Duration = 30 * time.Second
|
||||
// CircuitBreakerDefaultVolumeThreshold is the default value of minimumRequests in closed status
|
||||
CircuitBreakerDefaultVolumeThreshold uint32 = 10
|
||||
// DefaultCircuitBreakerName is the name of the circuit breaker
|
||||
DefaultCircuitBreakerName string = "DefaultCircuitBreaker"
|
||||
// DefaultCircuitBreakerServiceName is the servicename of the circuit breaker
|
||||
DefaultCircuitBreakerServiceName string = ""
|
||||
// DefaultCircuitBreakerHistoryCount is the default count of failed response history in circuit breaker
|
||||
DefaultCircuitBreakerHistoryCount int = 5
|
||||
// MinAuthClientCircuitBreakerResetTimeout is the min value of openStateWindow, which is the wait time before setting the breaker to halfOpen state from open state
|
||||
MinAuthClientCircuitBreakerResetTimeout = 30
|
||||
// MaxAuthClientCircuitBreakerResetTimeout is the max value of openStateWindow, which is the wait time before setting the breaker to halfOpen state from open state
|
||||
MaxAuthClientCircuitBreakerResetTimeout = 49
|
||||
// AuthClientCircuitBreakerName is the default circuit breaker name for the DefaultAuthClientCircuitBreakerSetting
|
||||
AuthClientCircuitBreakerName = "FederationClientCircuitBreaker"
|
||||
// AuthClientCircuitBreakerDefaultFailureThreshold is the default requests failure rate for the DefaultAuthClientCircuitBreakerSetting
|
||||
AuthClientCircuitBreakerDefaultFailureThreshold float64 = 0.65
|
||||
// AuthClientCircuitBreakerDefaultMinimumRequests is the default value of minimumRequests in closed status
|
||||
AuthClientCircuitBreakerDefaultMinimumRequests uint32 = 3
|
||||
)
|
||||
|
||||
// CircuitBreakerSetting wraps all exposed configurable params of circuit breaker
|
||||
type CircuitBreakerSetting struct {
|
||||
// Name is the Circuit Breaker's identifier
|
||||
name string
|
||||
// isEnabled is the switch of the circuit breaker, used for disable circuit breaker
|
||||
isEnabled bool
|
||||
// closeStateWindow is the cyclic period of the closed state, the default value is 120 seconds
|
||||
closeStateWindow time.Duration
|
||||
// openStateWindow is the wait time before setting the breaker to halfOpen state from open state, the default value is 30 seconds
|
||||
openStateWindow time.Duration
|
||||
// failureRateThreshold is the failure rate which calculates in at most closeStateWindow seconds, once reaches to this rate, the circuit breaker state changes from closed to open
|
||||
// the circuit will transition from closed to open, the default value is 80%
|
||||
failureRateThreshold float64
|
||||
// minimumRequests is the minimum number of counted requests in closed state, the default value is 10 requests
|
||||
minimumRequests uint32
|
||||
// successStatCodeMap is the error(s) of StatusCode returned from service, which should be considered as the success or failure accounted by circuit breaker
|
||||
// successStatCodeMap and successStatErrCodeMap are combined to use, if both StatusCode and ErrorCode are required, no need to add it to successStatCodeMap,
|
||||
// the default value is [429, 500, 502, 503, 504]
|
||||
successStatCodeMap map[int]bool
|
||||
// successStatErrCodeMap is the error(s) of StatusCode and ErrorCode returned from service, which should be considered
|
||||
// as the success or failure accounted by circuit breaker
|
||||
// the default value is {409, "IncorrectState"}
|
||||
successStatErrCodeMap map[StatErrCode]bool
|
||||
// serviceName is the name of the service which can be set using withServiceName option for NewCircuitBreaker.
|
||||
// the default value is empty string
|
||||
serviceName string
|
||||
// numberOfRecordedHistoryResponse is the number of failure responses stored in Circuit breaker history for debugging purpose
|
||||
// the default value is 5
|
||||
numberOfRecordedHistoryResponse int
|
||||
}
|
||||
|
||||
// String Converts CircuitBreakerSetting to human-readable string representation
|
||||
func (cbst CircuitBreakerSetting) String() string {
|
||||
return fmt.Sprintf("{name=%v, isEnabled=%v, closeStateWindow=%v, openStateWindow=%v, failureRateThreshold=%v, minimumRequests=%v, successStatCodeMap=%v, successStatErrCodeMap=%v, serviceName=%v, historyCount=%v}",
|
||||
cbst.name, cbst.isEnabled, cbst.closeStateWindow, cbst.openStateWindow, cbst.failureRateThreshold, cbst.minimumRequests, cbst.successStatCodeMap, cbst.successStatErrCodeMap, cbst.serviceName, cbst.numberOfRecordedHistoryResponse)
|
||||
}
|
||||
|
||||
// ResponseHistory wraps the response params
|
||||
type ResponseHistory struct {
|
||||
timestamp time.Time
|
||||
opcReqID string
|
||||
errorCode string
|
||||
errorMessage string
|
||||
statusCode int
|
||||
}
|
||||
|
||||
// String Converts ResponseHistory to human-readable string representation
|
||||
func (rh ResponseHistory) String() string {
|
||||
return fmt.Sprintf("Opc-Req-id - %v\nErrorCode - %v - %v\nErrorMessage - %v\n\n", rh.opcReqID, rh.statusCode, rh.errorCode, rh.errorMessage)
|
||||
}
|
||||
|
||||
// AddToHistory processed the response and adds to response history queue
|
||||
func (ocb *OciCircuitBreaker) AddToHistory(resp *http.Response, err ServiceError) {
|
||||
respHist := new(ResponseHistory)
|
||||
respHist.opcReqID = err.GetOpcRequestID()
|
||||
respHist.errorCode = err.GetCode()
|
||||
respHist.errorMessage = err.GetMessage()
|
||||
respHist.statusCode = err.GetHTTPStatusCode()
|
||||
respHist.timestamp, _ = time.Parse(time.RFC1123, resp.Header.Get("Date"))
|
||||
ocb.historyQueueMutex.Lock()
|
||||
defer ocb.historyQueueMutex.Unlock()
|
||||
ocb.historyQueue = append(ocb.historyQueue, *respHist)
|
||||
// cleaning up older values
|
||||
if len(ocb.historyQueue) > ocb.Cbst.numberOfRecordedHistoryResponse {
|
||||
// We have reached the capacity. Clean up the oldest value
|
||||
ocb.historyQueue = ocb.historyQueue[1:]
|
||||
}
|
||||
for index := len(ocb.historyQueue) - 1; index >= 0; index-- {
|
||||
if time.Since(ocb.historyQueue[index].timestamp) > ocb.Cbst.closeStateWindow {
|
||||
// This response is older than the circuit breaker closeStateWindow.
|
||||
// Remove all the older responses from 0 to index
|
||||
ocb.historyQueue = ocb.historyQueue[index+1:]
|
||||
break
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetHistory processes the rsponse in queue to construct a String
|
||||
func (ocb *OciCircuitBreaker) GetHistory() string {
|
||||
getHistoryString := ""
|
||||
ocb.historyQueueMutex.Lock()
|
||||
defer ocb.historyQueueMutex.Unlock()
|
||||
for _, value := range ocb.historyQueue {
|
||||
getHistoryString += value.String()
|
||||
}
|
||||
return getHistoryString
|
||||
}
|
||||
|
||||
// OciCircuitBreaker wraps all exposed configurable params of circuit breaker and 3P gobreaker CircuirBreaker
|
||||
type OciCircuitBreaker struct {
|
||||
Cbst *CircuitBreakerSetting
|
||||
Cb *gobreaker.CircuitBreaker
|
||||
historyQueue []ResponseHistory
|
||||
historyQueueMutex sync.Mutex
|
||||
}
|
||||
|
||||
// NewOciCircuitBreaker is used for initializing specified oci circuit breaker configuration with circuit breaker settings
|
||||
func NewOciCircuitBreaker(cbst *CircuitBreakerSetting, gbcb *gobreaker.CircuitBreaker) *OciCircuitBreaker {
|
||||
ocb := new(OciCircuitBreaker)
|
||||
ocb.Cbst = cbst
|
||||
if ocb.Cbst.numberOfRecordedHistoryResponse == 0 {
|
||||
fmt.Println("num hist empty")
|
||||
ocb.Cbst.numberOfRecordedHistoryResponse = getDefaultNumHistoryCount()
|
||||
}
|
||||
ocb.Cb = gbcb
|
||||
ocb.historyQueue = make([]ResponseHistory, 0, ocb.Cbst.numberOfRecordedHistoryResponse)
|
||||
|
||||
return ocb
|
||||
}
|
||||
|
||||
// CircuitBreakerOption is the type of the options for NewCircuitBreakerWithOptions.
|
||||
type CircuitBreakerOption func(cbst *CircuitBreakerSetting)
|
||||
|
||||
// NewGoCircuitBreaker is a function to initialize a CircuitBreaker object with the specified configuration
|
||||
// Add the interface, to allow the user directly use the 3P gobreaker.Setting's params.
|
||||
func NewGoCircuitBreaker(st gobreaker.Settings) *gobreaker.CircuitBreaker {
|
||||
return gobreaker.NewCircuitBreaker(st)
|
||||
}
|
||||
|
||||
// DefaultCircuitBreakerSetting is used for set circuit breaker with default config
|
||||
func DefaultCircuitBreakerSetting() *CircuitBreakerSetting {
|
||||
successStatErrCodeMap := map[StatErrCode]bool{
|
||||
{409, "IncorrectState"}: false,
|
||||
}
|
||||
successStatCodeMap := map[int]bool{
|
||||
429: false,
|
||||
500: false,
|
||||
502: false,
|
||||
503: false,
|
||||
504: false,
|
||||
}
|
||||
return newCircuitBreakerSetting(
|
||||
WithName(DefaultCircuitBreakerName),
|
||||
WithIsEnabled(true),
|
||||
WithCloseStateWindow(CircuitBreakerDefaultClosedWindow),
|
||||
WithOpenStateWindow(CircuitBreakerDefaultResetTimeout),
|
||||
WithFailureRateThreshold(CircuitBreakerDefaultFailureRateThreshold),
|
||||
WithMinimumRequests(CircuitBreakerDefaultVolumeThreshold),
|
||||
WithSuccessStatErrCodeMap(successStatErrCodeMap),
|
||||
WithSuccessStatCodeMap(successStatCodeMap),
|
||||
WithHistoryCount(getDefaultNumHistoryCount()))
|
||||
}
|
||||
|
||||
// DefaultCircuitBreakerSettingWithServiceName is used for set circuit breaker with default config
|
||||
func DefaultCircuitBreakerSettingWithServiceName(servicename string) *CircuitBreakerSetting {
|
||||
successStatErrCodeMap := map[StatErrCode]bool{
|
||||
{409, "IncorrectState"}: false,
|
||||
}
|
||||
successStatCodeMap := map[int]bool{
|
||||
429: false,
|
||||
500: false,
|
||||
502: false,
|
||||
503: false,
|
||||
504: false,
|
||||
}
|
||||
return newCircuitBreakerSetting(
|
||||
WithName(DefaultCircuitBreakerName),
|
||||
WithIsEnabled(true),
|
||||
WithCloseStateWindow(CircuitBreakerDefaultClosedWindow),
|
||||
WithOpenStateWindow(CircuitBreakerDefaultResetTimeout),
|
||||
WithFailureRateThreshold(CircuitBreakerDefaultFailureRateThreshold),
|
||||
WithMinimumRequests(CircuitBreakerDefaultVolumeThreshold),
|
||||
WithSuccessStatErrCodeMap(successStatErrCodeMap),
|
||||
WithSuccessStatCodeMap(successStatCodeMap),
|
||||
WithServiceName(servicename),
|
||||
WithHistoryCount(getDefaultNumHistoryCount()))
|
||||
}
|
||||
|
||||
// NoCircuitBreakerSetting is used for disable Circuit Breaker
|
||||
func NoCircuitBreakerSetting() *CircuitBreakerSetting {
|
||||
return NewCircuitBreakerSettingWithOptions(WithIsEnabled(false))
|
||||
}
|
||||
|
||||
// NewCircuitBreakerSettingWithOptions is a helper method to assemble a CircuitBreakerSetting object.
|
||||
// It starts out with the values returned by defaultCircuitBreakerSetting().
|
||||
func NewCircuitBreakerSettingWithOptions(opts ...CircuitBreakerOption) *CircuitBreakerSetting {
|
||||
cbst := DefaultCircuitBreakerSettingWithServiceName(DefaultCircuitBreakerServiceName)
|
||||
// allow changing values
|
||||
for _, opt := range opts {
|
||||
opt(cbst)
|
||||
}
|
||||
if defaultLogger != nil && defaultLogger.LogLevel() == verboseLogging {
|
||||
Debugf("Circuit Breaker setting: %s\n", cbst.String())
|
||||
}
|
||||
|
||||
return cbst
|
||||
}
|
||||
|
||||
// NewCircuitBreaker is used for initialing specified circuit breaker configuration with base client
|
||||
func NewCircuitBreaker(cbst *CircuitBreakerSetting) *OciCircuitBreaker {
|
||||
if !cbst.isEnabled {
|
||||
return nil
|
||||
}
|
||||
|
||||
st := gobreaker.Settings{}
|
||||
customizeGoBreakerSetting(&st, cbst)
|
||||
gbcb := gobreaker.NewCircuitBreaker(st)
|
||||
|
||||
return NewOciCircuitBreaker(cbst, gbcb)
|
||||
}
|
||||
|
||||
func newCircuitBreakerSetting(opts ...CircuitBreakerOption) *CircuitBreakerSetting {
|
||||
cbSetting := CircuitBreakerSetting{}
|
||||
|
||||
// allow changing values
|
||||
for _, opt := range opts {
|
||||
opt(&cbSetting)
|
||||
}
|
||||
return &cbSetting
|
||||
}
|
||||
|
||||
// customizeGoBreakerSetting is used for converting CircuitBreakerSetting to 3P gobreaker's setting type
|
||||
func customizeGoBreakerSetting(st *gobreaker.Settings, cbst *CircuitBreakerSetting) {
|
||||
st.Name = cbst.name
|
||||
st.Timeout = cbst.openStateWindow
|
||||
st.Interval = cbst.closeStateWindow
|
||||
st.OnStateChange = func(name string, from gobreaker.State, to gobreaker.State) {
|
||||
if to == gobreaker.StateOpen {
|
||||
Debugf("Circuit Breaker %s is now in Open State\n", name)
|
||||
}
|
||||
}
|
||||
st.ReadyToTrip = func(counts gobreaker.Counts) bool {
|
||||
failureRatio := float64(counts.TotalFailures) / float64(counts.Requests)
|
||||
return counts.Requests >= cbst.minimumRequests && failureRatio >= cbst.failureRateThreshold
|
||||
}
|
||||
st.IsSuccessful = func(err error) bool {
|
||||
if serviceErr, ok := IsServiceError(err); ok {
|
||||
if isSuccessful, ok := cbst.successStatCodeMap[serviceErr.GetHTTPStatusCode()]; ok {
|
||||
return isSuccessful
|
||||
}
|
||||
if isSuccessful, ok := cbst.successStatErrCodeMap[StatErrCode{serviceErr.GetHTTPStatusCode(), serviceErr.GetCode()}]; ok {
|
||||
return isSuccessful
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// WithName is the option for NewCircuitBreaker that sets the Name.
|
||||
func WithName(name string) CircuitBreakerOption {
|
||||
// this is the CircuitBreakerOption function type
|
||||
return func(cbst *CircuitBreakerSetting) {
|
||||
cbst.name = name
|
||||
}
|
||||
}
|
||||
|
||||
// WithIsEnabled is the option for NewCircuitBreaker that sets the isEnabled.
|
||||
func WithIsEnabled(isEnabled bool) CircuitBreakerOption {
|
||||
// this is the CircuitBreakerOption function type
|
||||
return func(cbst *CircuitBreakerSetting) {
|
||||
cbst.isEnabled = isEnabled
|
||||
}
|
||||
}
|
||||
|
||||
// WithCloseStateWindow is the option for NewCircuitBreaker that sets the closeStateWindow.
|
||||
func WithCloseStateWindow(window time.Duration) CircuitBreakerOption {
|
||||
// this is the CircuitBreakerOption function type
|
||||
return func(cbst *CircuitBreakerSetting) {
|
||||
cbst.closeStateWindow = window
|
||||
}
|
||||
}
|
||||
|
||||
// WithOpenStateWindow is the option for NewCircuitBreaker that sets the openStateWindow.
|
||||
func WithOpenStateWindow(window time.Duration) CircuitBreakerOption {
|
||||
// this is the CircuitBreakerOption function type
|
||||
return func(cbst *CircuitBreakerSetting) {
|
||||
cbst.openStateWindow = window
|
||||
}
|
||||
}
|
||||
|
||||
// WithFailureRateThreshold is the option for NewCircuitBreaker that sets the failureRateThreshold.
|
||||
func WithFailureRateThreshold(threshold float64) CircuitBreakerOption {
|
||||
// this is the CircuitBreakerOption function type
|
||||
return func(cbst *CircuitBreakerSetting) {
|
||||
cbst.failureRateThreshold = threshold
|
||||
}
|
||||
}
|
||||
|
||||
// WithMinimumRequests is the option for NewCircuitBreaker that sets the minimumRequests.
|
||||
func WithMinimumRequests(num uint32) CircuitBreakerOption {
|
||||
// this is the CircuitBreakerOption function type
|
||||
return func(cbst *CircuitBreakerSetting) {
|
||||
cbst.minimumRequests = num
|
||||
}
|
||||
}
|
||||
|
||||
// WithSuccessStatCodeMap is the option for NewCircuitBreaker that sets the successStatCodeMap.
|
||||
func WithSuccessStatCodeMap(successStatCodeMap map[int]bool) CircuitBreakerOption {
|
||||
// this is the CircuitBreakerOption function type
|
||||
return func(cbst *CircuitBreakerSetting) {
|
||||
cbst.successStatCodeMap = successStatCodeMap
|
||||
}
|
||||
}
|
||||
|
||||
// WithSuccessStatErrCodeMap is the option for NewCircuitBreaker that sets the successStatErrCodeMap.
|
||||
func WithSuccessStatErrCodeMap(successStatErrCodeMap map[StatErrCode]bool) CircuitBreakerOption {
|
||||
// this is the CircuitBreakerOption function type
|
||||
return func(cbst *CircuitBreakerSetting) {
|
||||
cbst.successStatErrCodeMap = successStatErrCodeMap
|
||||
}
|
||||
}
|
||||
|
||||
// WithServiceName is the option for NewCircuitBreaker that sets the ServiceName.
|
||||
func WithServiceName(serviceName string) CircuitBreakerOption {
|
||||
// this is the CircuitBreakerOption function type
|
||||
return func(cbst *CircuitBreakerSetting) {
|
||||
cbst.serviceName = serviceName
|
||||
}
|
||||
}
|
||||
|
||||
// WithHistoryCount to set the number of failed responses
|
||||
func WithHistoryCount(count int) CircuitBreakerOption {
|
||||
// this is the CircuitBreakerOption function type
|
||||
return func(cbst *CircuitBreakerSetting) {
|
||||
cbst.numberOfRecordedHistoryResponse = count
|
||||
}
|
||||
}
|
||||
|
||||
// getDefaultNumHistoryCount to set the number of failed responses
|
||||
func getDefaultNumHistoryCount() int {
|
||||
if val, isSet := os.LookupEnv(circuitBreakerNumberOfHistoryResponseEnv); isSet {
|
||||
count, err := strconv.Atoi(val)
|
||||
if err == nil && count > 0 {
|
||||
return count
|
||||
}
|
||||
Debugf("Invalid history count specified. Resetting to default value")
|
||||
}
|
||||
return DefaultCircuitBreakerHistoryCount
|
||||
}
|
||||
|
||||
// GlobalCircuitBreakerSetting is global level circuit breaker setting, it would impact all services, the precedence is lower
|
||||
// than client level circuit breaker
|
||||
var GlobalCircuitBreakerSetting *CircuitBreakerSetting = nil
|
||||
|
||||
// ConfigCircuitBreakerFromEnvVar is used for checking the circuit breaker environment variable setting, default value is nil
|
||||
func ConfigCircuitBreakerFromEnvVar(baseClient *BaseClient) {
|
||||
if IsEnvVarTrue(isDefaultCircuitBreakerEnabled) {
|
||||
baseClient.Configuration.CircuitBreaker = NewCircuitBreaker(DefaultCircuitBreakerSetting())
|
||||
return
|
||||
}
|
||||
if IsEnvVarFalse(isDefaultCircuitBreakerEnabled) {
|
||||
baseClient.Configuration.CircuitBreaker = nil
|
||||
}
|
||||
}
|
||||
|
||||
// ConfigCircuitBreakerFromGlobalVar is used for checking if global circuitBreakerSetting is configured, the priority is higher than cb env var
|
||||
func ConfigCircuitBreakerFromGlobalVar(baseClient *BaseClient) {
|
||||
if GlobalCircuitBreakerSetting != nil {
|
||||
baseClient.Configuration.CircuitBreaker = NewCircuitBreaker(GlobalCircuitBreakerSetting)
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultAuthClientCircuitBreakerSetting returns the default circuit breaker setting for the Auth Client
|
||||
func DefaultAuthClientCircuitBreakerSetting() *CircuitBreakerSetting {
|
||||
return NewCircuitBreakerSettingWithOptions(
|
||||
WithOpenStateWindow(time.Duration(rand.Intn(MaxAuthClientCircuitBreakerResetTimeout+1-MinAuthClientCircuitBreakerResetTimeout)+MinAuthClientCircuitBreakerResetTimeout)*time.Second),
|
||||
WithName(AuthClientCircuitBreakerName),
|
||||
WithFailureRateThreshold(AuthClientCircuitBreakerDefaultFailureThreshold),
|
||||
WithMinimumRequests(AuthClientCircuitBreakerDefaultMinimumRequests),
|
||||
)
|
||||
}
|
||||
|
||||
// GlobalAuthClientCircuitBreakerSetting is global level circuit breaker setting for the Auth Client
|
||||
// than client level circuit breaker
|
||||
var GlobalAuthClientCircuitBreakerSetting *CircuitBreakerSetting = nil
|
||||
748
vendor/github.com/oracle/oci-go-sdk/v65/common/client.go
generated
vendored
Normal file
748
vendor/github.com/oracle/oci-go-sdk/v65/common/client.go
generated
vendored
Normal file
@@ -0,0 +1,748 @@
|
||||
// Copyright (c) 2016, 2018, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
// This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
|
||||
|
||||
// Package common provides supporting functions and structs used by service packages
|
||||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/user"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultHostURLTemplate The default url template for service hosts
|
||||
DefaultHostURLTemplate = "%s.%s.oraclecloud.com"
|
||||
|
||||
// requestHeaderAccept The key for passing a header to indicate Accept
|
||||
requestHeaderAccept = "Accept"
|
||||
|
||||
// requestHeaderAuthorization The key for passing a header to indicate Authorization
|
||||
requestHeaderAuthorization = "Authorization"
|
||||
|
||||
// requestHeaderContentLength The key for passing a header to indicate Content Length
|
||||
requestHeaderContentLength = "Content-Length"
|
||||
|
||||
// requestHeaderContentType The key for passing a header to indicate Content Type
|
||||
requestHeaderContentType = "Content-Type"
|
||||
|
||||
// requestHeaderExpect The key for passing a header to indicate Expect/100-Continue
|
||||
requestHeaderExpect = "Expect"
|
||||
|
||||
// requestHeaderDate The key for passing a header to indicate Date
|
||||
requestHeaderDate = "Date"
|
||||
|
||||
// requestHeaderIfMatch The key for passing a header to indicate If Match
|
||||
requestHeaderIfMatch = "if-match"
|
||||
|
||||
// requestHeaderOpcClientInfo The key for passing a header to indicate OPC Client Info
|
||||
requestHeaderOpcClientInfo = "opc-client-info"
|
||||
|
||||
// requestHeaderOpcRetryToken The key for passing a header to indicate OPC Retry Token
|
||||
requestHeaderOpcRetryToken = "opc-retry-token"
|
||||
|
||||
// requestHeaderOpcRequestID The key for unique Oracle-assigned identifier for the request.
|
||||
requestHeaderOpcRequestID = "opc-request-id"
|
||||
|
||||
// requestHeaderOpcClientRequestID The key for unique Oracle-assigned identifier for the request.
|
||||
requestHeaderOpcClientRequestID = "opc-client-request-id"
|
||||
|
||||
// requestHeaderUserAgent The key for passing a header to indicate User Agent
|
||||
requestHeaderUserAgent = "User-Agent"
|
||||
|
||||
// requestHeaderXContentSHA256 The key for passing a header to indicate SHA256 hash
|
||||
requestHeaderXContentSHA256 = "X-Content-SHA256"
|
||||
|
||||
// requestHeaderOpcOboToken The key for passing a header to use obo token
|
||||
requestHeaderOpcOboToken = "opc-obo-token"
|
||||
|
||||
// private constants
|
||||
defaultScheme = "https"
|
||||
defaultSDKMarker = "Oracle-GoSDK"
|
||||
defaultUserAgentTemplate = "%s/%s (%s/%s; go/%s)" //SDK/SDKVersion (OS/OSVersion; Lang/LangVersion)
|
||||
// http.Client.Timeout includes Dial, TLSHandshake, Request, Response header and body
|
||||
defaultTimeout = 60 * time.Second
|
||||
defaultConfigFileName = "config"
|
||||
defaultConfigDirName = ".oci"
|
||||
configFilePathEnvVarName = "OCI_CONFIG_FILE"
|
||||
|
||||
secondaryConfigDirName = ".oraclebmc"
|
||||
maxBodyLenForDebug = 1024 * 1000
|
||||
|
||||
// appendUserAgentEnv The key for retrieving append user agent value from env var
|
||||
appendUserAgentEnv = "OCI_SDK_APPEND_USER_AGENT"
|
||||
|
||||
// requestHeaderOpcClientRetries The key for passing a header to set client retries info
|
||||
requestHeaderOpcClientRetries = "opc-client-retries"
|
||||
|
||||
// isDefaultRetryEnabled The key for set default retry disabled from env var
|
||||
isDefaultRetryEnabled = "OCI_SDK_DEFAULT_RETRY_ENABLED"
|
||||
|
||||
// isDefaultCircuitBreakerEnabled is the key for set default circuit breaker disabled from env var
|
||||
isDefaultCircuitBreakerEnabled = "OCI_SDK_DEFAULT_CIRCUITBREAKER_ENABLED"
|
||||
|
||||
//circuitBreakerNumberOfHistoryResponseEnv is the number of recorded history responses
|
||||
circuitBreakerNumberOfHistoryResponseEnv = "OCI_SDK_CIRCUITBREAKER_NUM_HISTORY_RESPONSE"
|
||||
|
||||
// ociDefaultRefreshIntervalForCustomCerts is the env var for overriding the defaultRefreshIntervalForCustomCerts.
|
||||
// The value represents the refresh interval in minutes and has a higher precedence than defaultRefreshIntervalForCustomCerts
|
||||
// but has a lower precedence then the refresh interval configured via OciGlobalRefreshIntervalForCustomCerts
|
||||
// If the value is negative, then it is assumed that this property is not configured
|
||||
// if the value is Zero, then the refresh of custom certs will be disabled
|
||||
ociDefaultRefreshIntervalForCustomCerts = "OCI_DEFAULT_REFRESH_INTERVAL_FOR_CUSTOM_CERTS"
|
||||
|
||||
// ociDefaultCertsPath is the env var for the path to the SSL cert file
|
||||
ociDefaultCertsPath = "OCI_DEFAULT_CERTS_PATH"
|
||||
|
||||
// ociDefaultClientCertsPath is the env var for the path to the custom client cert
|
||||
ociDefaultClientCertsPath = "OCI_DEFAULT_CLIENT_CERTS_PATH"
|
||||
|
||||
// ociDefaultClientCertsPrivateKeyPath is the env var for the path to the custom client cert private key
|
||||
ociDefaultClientCertsPrivateKeyPath = "OCI_DEFAULT_CLIENT_CERTS_PRIVATE_KEY_PATH"
|
||||
|
||||
//maxAttemptsForRefreshableRetry is the number of retry when 401 happened on a refreshable auth type
|
||||
maxAttemptsForRefreshableRetry = 3
|
||||
|
||||
//defaultRefreshIntervalForCustomCerts is the default refresh interval in minutes
|
||||
defaultRefreshIntervalForCustomCerts = 30
|
||||
)
|
||||
|
||||
// OciGlobalRefreshIntervalForCustomCerts is the global policy for overriding the refresh interval in minutes.
|
||||
// This variable has a higher precedence than the env variable OCI_DEFAULT_REFRESH_INTERVAL_FOR_CUSTOM_CERTS
|
||||
// and the defaultRefreshIntervalForCustomCerts values.
|
||||
// If the value is negative, then it is assumed that this property is not configured
|
||||
// if the value is Zero, then the refresh of custom certs will be disabled
|
||||
var OciGlobalRefreshIntervalForCustomCerts int = -1
|
||||
|
||||
// RequestInterceptor function used to customize the request before calling the underlying service
|
||||
type RequestInterceptor func(*http.Request) error
|
||||
|
||||
// HTTPRequestDispatcher wraps the execution of a http request, it is generally implemented by
|
||||
// http.Client.Do, but can be customized for testing
|
||||
type HTTPRequestDispatcher interface {
|
||||
Do(req *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
// CustomClientConfiguration contains configurations set at client level, currently it only includes RetryPolicy
|
||||
type CustomClientConfiguration struct {
|
||||
RetryPolicy *RetryPolicy
|
||||
CircuitBreaker *OciCircuitBreaker
|
||||
RealmSpecificServiceEndpointTemplateEnabled *bool
|
||||
}
|
||||
|
||||
// BaseClient struct implements all basic operations to call oci web services.
|
||||
type BaseClient struct {
|
||||
//HTTPClient performs the http network operations
|
||||
HTTPClient HTTPRequestDispatcher
|
||||
|
||||
//Signer performs auth operation
|
||||
Signer HTTPRequestSigner
|
||||
|
||||
//A request interceptor can be used to customize the request before signing and dispatching
|
||||
Interceptor RequestInterceptor
|
||||
|
||||
//The host of the service
|
||||
Host string
|
||||
|
||||
//The user agent
|
||||
UserAgent string
|
||||
|
||||
//Base path for all operations of this client
|
||||
BasePath string
|
||||
|
||||
Configuration CustomClientConfiguration
|
||||
}
|
||||
|
||||
// SetCustomClientConfiguration sets client with retry and other custom configurations
|
||||
func (client *BaseClient) SetCustomClientConfiguration(config CustomClientConfiguration) {
|
||||
client.Configuration = config
|
||||
}
|
||||
|
||||
// RetryPolicy returns the retryPolicy configured for client
|
||||
func (client *BaseClient) RetryPolicy() *RetryPolicy {
|
||||
return client.Configuration.RetryPolicy
|
||||
}
|
||||
|
||||
// Endpoint returns the endpoint configured for client
|
||||
func (client *BaseClient) Endpoint() string {
|
||||
host := client.Host
|
||||
if !strings.Contains(host, "http") &&
|
||||
!strings.Contains(host, "https") {
|
||||
host = fmt.Sprintf("%s://%s", defaultScheme, host)
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
func defaultUserAgent() string {
|
||||
userAgent := fmt.Sprintf(defaultUserAgentTemplate, defaultSDKMarker, Version(), runtime.GOOS, runtime.GOARCH, runtime.Version())
|
||||
appendUA := os.Getenv(appendUserAgentEnv)
|
||||
if appendUA != "" {
|
||||
userAgent = fmt.Sprintf("%s %s", userAgent, appendUA)
|
||||
}
|
||||
return userAgent
|
||||
}
|
||||
|
||||
var clientCounter int64
|
||||
|
||||
func getNextSeed() int64 {
|
||||
newCounterValue := atomic.AddInt64(&clientCounter, 1)
|
||||
return newCounterValue + time.Now().UnixNano()
|
||||
}
|
||||
|
||||
func newBaseClient(signer HTTPRequestSigner, dispatcher HTTPRequestDispatcher) BaseClient {
|
||||
rand.Seed(getNextSeed())
|
||||
|
||||
baseClient := BaseClient{
|
||||
UserAgent: defaultUserAgent(),
|
||||
Interceptor: nil,
|
||||
Signer: signer,
|
||||
HTTPClient: dispatcher,
|
||||
}
|
||||
|
||||
// check the default retry environment variable setting
|
||||
if IsEnvVarTrue(isDefaultRetryEnabled) {
|
||||
defaultRetry := DefaultRetryPolicy()
|
||||
baseClient.Configuration.RetryPolicy = &defaultRetry
|
||||
} else if IsEnvVarFalse(isDefaultRetryEnabled) {
|
||||
policy := NoRetryPolicy()
|
||||
baseClient.Configuration.RetryPolicy = &policy
|
||||
}
|
||||
// check if user defined global retry is configured
|
||||
if GlobalRetry != nil {
|
||||
baseClient.Configuration.RetryPolicy = GlobalRetry
|
||||
}
|
||||
|
||||
return baseClient
|
||||
}
|
||||
|
||||
func defaultHTTPDispatcher() http.Client {
|
||||
var httpClient http.Client
|
||||
refreshInterval := getCustomCertRefreshInterval()
|
||||
if refreshInterval <= 0 {
|
||||
Debug("Custom cert refresh has been disabled")
|
||||
}
|
||||
var tp = &OciHTTPTransportWrapper{
|
||||
RefreshRate: time.Duration(refreshInterval) * time.Minute,
|
||||
TLSConfigProvider: GetTLSConfigTemplateForTransport(),
|
||||
}
|
||||
httpClient = http.Client{
|
||||
Timeout: defaultTimeout,
|
||||
Transport: tp,
|
||||
}
|
||||
return httpClient
|
||||
}
|
||||
|
||||
func defaultBaseClient(provider KeyProvider) BaseClient {
|
||||
dispatcher := defaultHTTPDispatcher()
|
||||
signer := DefaultRequestSigner(provider)
|
||||
return newBaseClient(signer, &dispatcher)
|
||||
}
|
||||
|
||||
// DefaultBaseClientWithSigner creates a default base client with a given signer
|
||||
func DefaultBaseClientWithSigner(signer HTTPRequestSigner) BaseClient {
|
||||
dispatcher := defaultHTTPDispatcher()
|
||||
return newBaseClient(signer, &dispatcher)
|
||||
}
|
||||
|
||||
// NewClientWithConfig Create a new client with a configuration provider, the configuration provider
|
||||
// will be used for the default signer as well as reading the region
|
||||
// This function does not check for valid regions to implement forward compatibility
|
||||
func NewClientWithConfig(configProvider ConfigurationProvider) (client BaseClient, err error) {
|
||||
var ok bool
|
||||
if ok, err = IsConfigurationProviderValid(configProvider); !ok {
|
||||
err = fmt.Errorf("can not create client, bad configuration: %s", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
client = defaultBaseClient(configProvider)
|
||||
|
||||
if authConfig, e := configProvider.AuthType(); e == nil && authConfig.OboToken != nil {
|
||||
Debugf("authConfig's authType is %s, and token content is %s", authConfig.AuthType, *authConfig.OboToken)
|
||||
signOboToken(&client, *authConfig.OboToken, configProvider)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// NewClientWithOboToken Create a new client that will use oboToken for auth
|
||||
func NewClientWithOboToken(configProvider ConfigurationProvider, oboToken string) (client BaseClient, err error) {
|
||||
client, err = NewClientWithConfig(configProvider)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
signOboToken(&client, oboToken, configProvider)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Add obo token header to Interceptor and sign to client
|
||||
func signOboToken(client *BaseClient, oboToken string, configProvider ConfigurationProvider) {
|
||||
// Interceptor to add obo token header
|
||||
client.Interceptor = func(request *http.Request) error {
|
||||
request.Header.Add(requestHeaderOpcOboToken, oboToken)
|
||||
return nil
|
||||
}
|
||||
// Obo token will also be signed
|
||||
defaultHeaders := append(DefaultGenericHeaders(), requestHeaderOpcOboToken)
|
||||
client.Signer = RequestSigner(configProvider, defaultHeaders, DefaultBodyHeaders())
|
||||
}
|
||||
|
||||
func getHomeFolder() string {
|
||||
current, e := user.Current()
|
||||
if e != nil {
|
||||
//Give up and try to return something sensible
|
||||
home := os.Getenv("HOME")
|
||||
if home == "" {
|
||||
home = os.Getenv("USERPROFILE")
|
||||
}
|
||||
return home
|
||||
}
|
||||
return current.HomeDir
|
||||
}
|
||||
|
||||
// DefaultConfigProvider returns the default config provider. The default config provider
|
||||
// will look for configurations in 3 places: file in $HOME/.oci/config, HOME/.obmcs/config and
|
||||
// variables names starting with the string TF_VAR. If the same configuration is found in multiple
|
||||
// places the provider will prefer the first one.
|
||||
// If the config file is not placed in the default location, the environment variable
|
||||
// OCI_CONFIG_FILE can provide the config file location.
|
||||
func DefaultConfigProvider() ConfigurationProvider {
|
||||
defaultConfigFile := getDefaultConfigFilePath()
|
||||
homeFolder := getHomeFolder()
|
||||
secondaryConfigFile := filepath.Join(homeFolder, secondaryConfigDirName, defaultConfigFileName)
|
||||
|
||||
defaultFileProvider, _ := ConfigurationProviderFromFile(defaultConfigFile, "")
|
||||
secondaryFileProvider, _ := ConfigurationProviderFromFile(secondaryConfigFile, "")
|
||||
environmentProvider := environmentConfigurationProvider{EnvironmentVariablePrefix: "TF_VAR"}
|
||||
|
||||
provider, _ := ComposingConfigurationProvider([]ConfigurationProvider{defaultFileProvider, secondaryFileProvider, environmentProvider})
|
||||
Debugf("Configuration provided by: %s", provider)
|
||||
return provider
|
||||
}
|
||||
|
||||
// CustomProfileSessionTokenConfigProvider returns the session token config provider of the given profile.
|
||||
// This will look for the configuration in the given config file path.
|
||||
func CustomProfileSessionTokenConfigProvider(customConfigPath string, profile string) ConfigurationProvider {
|
||||
if customConfigPath == "" {
|
||||
customConfigPath = getDefaultConfigFilePath()
|
||||
}
|
||||
|
||||
sessionTokenConfigurationProvider, _ := ConfigurationProviderForSessionTokenWithProfile(customConfigPath, profile, "")
|
||||
Debugf("Configuration provided by: %s", sessionTokenConfigurationProvider)
|
||||
return sessionTokenConfigurationProvider
|
||||
}
|
||||
|
||||
func getDefaultConfigFilePath() string {
|
||||
homeFolder := getHomeFolder()
|
||||
defaultConfigFile := filepath.Join(homeFolder, defaultConfigDirName, defaultConfigFileName)
|
||||
if _, err := os.Stat(defaultConfigFile); err == nil {
|
||||
return defaultConfigFile
|
||||
}
|
||||
Debugf("The %s does not exist, will check env var %s for file path.", defaultConfigFile, configFilePathEnvVarName)
|
||||
// Read configuration file path from OCI_CONFIG_FILE env var
|
||||
fallbackConfigFile, existed := os.LookupEnv(configFilePathEnvVarName)
|
||||
if !existed {
|
||||
Debugf("The env var %s does not exist...", configFilePathEnvVarName)
|
||||
return defaultConfigFile
|
||||
}
|
||||
if _, err := os.Stat(fallbackConfigFile); os.IsNotExist(err) {
|
||||
Debugf("The specified cfg file path in the env var %s does not exist: %s", configFilePathEnvVarName, fallbackConfigFile)
|
||||
return defaultConfigFile
|
||||
}
|
||||
return fallbackConfigFile
|
||||
}
|
||||
|
||||
// setRawPath sets the Path and RawPath fields of the URL based on the provided
|
||||
// escaped path p. It maintains the invariant that RawPath is only specified
|
||||
// when it differs from the default encoding of the path.
|
||||
// For example:
|
||||
// - setPath("/foo/bar") will set Path="/foo/bar" and RawPath=""
|
||||
// - setPath("/foo%2fbar") will set Path="/foo/bar" and RawPath="/foo%2fbar"
|
||||
func setRawPath(u *url.URL) error {
|
||||
oldPath := u.Path
|
||||
path, err := url.PathUnescape(u.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.Path = path
|
||||
if escp := u.EscapedPath(); oldPath == escp {
|
||||
// Default encoding is fine.
|
||||
u.RawPath = ""
|
||||
} else {
|
||||
u.RawPath = oldPath
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CustomProfileConfigProvider returns the config provider of given profile. The custom profile config provider
|
||||
// will look for configurations in 2 places: file in $HOME/.oci/config, and variables names starting with the
|
||||
// string TF_VAR. If the same configuration is found in multiple places the provider will prefer the first one.
|
||||
func CustomProfileConfigProvider(customConfigPath string, profile string) ConfigurationProvider {
|
||||
homeFolder := getHomeFolder()
|
||||
if customConfigPath == "" {
|
||||
customConfigPath = filepath.Join(homeFolder, defaultConfigDirName, defaultConfigFileName)
|
||||
}
|
||||
customFileProvider, _ := ConfigurationProviderFromFileWithProfile(customConfigPath, profile, "")
|
||||
defaultFileProvider, _ := ConfigurationProviderFromFileWithProfile(customConfigPath, "DEFAULT", "")
|
||||
environmentProvider := environmentConfigurationProvider{EnvironmentVariablePrefix: "TF_VAR"}
|
||||
provider, _ := ComposingConfigurationProvider([]ConfigurationProvider{customFileProvider, defaultFileProvider, environmentProvider})
|
||||
Debugf("Configuration provided by: %s", provider)
|
||||
return provider
|
||||
}
|
||||
|
||||
func (client *BaseClient) prepareRequest(request *http.Request) (err error) {
|
||||
if client.UserAgent == "" {
|
||||
return fmt.Errorf("user agent can not be blank")
|
||||
}
|
||||
|
||||
if request.Header == nil {
|
||||
request.Header = http.Header{}
|
||||
}
|
||||
request.Header.Set(requestHeaderUserAgent, client.UserAgent)
|
||||
request.Header.Set(requestHeaderDate, time.Now().UTC().Format(http.TimeFormat))
|
||||
|
||||
if !strings.Contains(client.Host, "http") &&
|
||||
!strings.Contains(client.Host, "https") {
|
||||
client.Host = fmt.Sprintf("%s://%s", defaultScheme, client.Host)
|
||||
}
|
||||
|
||||
clientURL, err := url.Parse(client.Host)
|
||||
if err != nil {
|
||||
return fmt.Errorf("host is invalid. %s", err.Error())
|
||||
}
|
||||
request.URL.Host = clientURL.Host
|
||||
request.URL.Scheme = clientURL.Scheme
|
||||
currentPath := request.URL.Path
|
||||
if !strings.Contains(currentPath, fmt.Sprintf("/%s", client.BasePath)) {
|
||||
request.URL.Path = path.Clean(fmt.Sprintf("/%s/%s", client.BasePath, currentPath))
|
||||
err := setRawPath(request.URL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (client BaseClient) intercept(request *http.Request) (err error) {
|
||||
if client.Interceptor != nil {
|
||||
err = client.Interceptor(request)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// checkForSuccessfulResponse checks if the response is successful
|
||||
// If Error Code is 4XX/5XX and debug level is set to info, will log the request and response
|
||||
func checkForSuccessfulResponse(res *http.Response, requestBody *io.ReadCloser) error {
|
||||
familyStatusCode := res.StatusCode / 100
|
||||
if familyStatusCode == 4 || familyStatusCode == 5 {
|
||||
IfInfo(func() {
|
||||
// If debug level is set to verbose, the request and request body will be dumped and logged under debug level, this is to avoid duplicate logging
|
||||
if defaultLogger.LogLevel() < verboseLogging {
|
||||
logRequest(res.Request, Logf, noLogging)
|
||||
if requestBody != nil && *requestBody != http.NoBody {
|
||||
bodyContent, _ := ioutil.ReadAll(*requestBody)
|
||||
Logf("Dump Request Body: \n%s", string(bodyContent))
|
||||
}
|
||||
}
|
||||
logResponse(res, Logf, infoLogging)
|
||||
})
|
||||
return newServiceFailureFromResponse(res)
|
||||
}
|
||||
IfDebug(func() {
|
||||
logResponse(res, Debugf, verboseLogging)
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func logRequest(request *http.Request, fn func(format string, v ...interface{}), bodyLoggingLevel int) {
|
||||
if request == nil {
|
||||
return
|
||||
}
|
||||
dumpBody := true
|
||||
if checkBodyLengthExceedLimit(request.ContentLength) {
|
||||
fn("not dumping body too big\n")
|
||||
dumpBody = false
|
||||
}
|
||||
|
||||
dumpBody = dumpBody && defaultLogger.LogLevel() >= bodyLoggingLevel && bodyLoggingLevel != noLogging
|
||||
if dump, e := httputil.DumpRequestOut(request, dumpBody); e == nil {
|
||||
fn("Dump Request %s", string(dump))
|
||||
} else {
|
||||
fn("%v\n", e)
|
||||
}
|
||||
}
|
||||
|
||||
func logResponse(response *http.Response, fn func(format string, v ...interface{}), bodyLoggingLevel int) {
|
||||
if response == nil {
|
||||
return
|
||||
}
|
||||
dumpBody := true
|
||||
if checkBodyLengthExceedLimit(response.ContentLength) {
|
||||
fn("not dumping body too big\n")
|
||||
dumpBody = false
|
||||
}
|
||||
dumpBody = dumpBody && defaultLogger.LogLevel() >= bodyLoggingLevel && bodyLoggingLevel != noLogging
|
||||
if dump, e := httputil.DumpResponse(response, dumpBody); e == nil {
|
||||
fn("Dump Response %s", string(dump))
|
||||
} else {
|
||||
fn("%v\n", e)
|
||||
}
|
||||
}
|
||||
|
||||
func checkBodyLengthExceedLimit(contentLength int64) bool {
|
||||
return contentLength > maxBodyLenForDebug
|
||||
}
|
||||
|
||||
// OCIRequest is any request made to an OCI service.
|
||||
type OCIRequest interface {
|
||||
// HTTPRequest assembles an HTTP request.
|
||||
HTTPRequest(method, path string, binaryRequestBody *OCIReadSeekCloser, extraHeaders map[string]string) (http.Request, error)
|
||||
}
|
||||
|
||||
// RequestMetadata is metadata about an OCIRequest. This structure represents the behavior exhibited by the SDK when
|
||||
// issuing (or reissuing) a request.
|
||||
type RequestMetadata struct {
|
||||
// RetryPolicy is the policy for reissuing the request. If no retry policy is set on the request,
|
||||
// then the request will be issued exactly once.
|
||||
RetryPolicy *RetryPolicy
|
||||
}
|
||||
|
||||
// OCIReadSeekCloser is a thread-safe io.ReadSeekCloser to prevent racing with retrying binary requests
|
||||
type OCIReadSeekCloser struct {
|
||||
rc io.ReadCloser
|
||||
lock sync.Mutex
|
||||
isClosed bool
|
||||
}
|
||||
|
||||
// NewOCIReadSeekCloser constructs OCIReadSeekCloser, the only input is binary request body
|
||||
func NewOCIReadSeekCloser(rc io.ReadCloser) *OCIReadSeekCloser {
|
||||
rsc := OCIReadSeekCloser{}
|
||||
rsc.rc = rc
|
||||
return &rsc
|
||||
}
|
||||
|
||||
// Seek is a thread-safe operation, it implements io.seek() interface, if the original request body implements io.seek()
|
||||
// interface, or implements "well-known" data type like os.File, io.SectionReader, or wrapped by ioutil.NopCloser can be supported
|
||||
func (rsc *OCIReadSeekCloser) Seek(offset int64, whence int) (int64, error) {
|
||||
rsc.lock.Lock()
|
||||
defer rsc.lock.Unlock()
|
||||
|
||||
if _, ok := rsc.rc.(io.Seeker); ok {
|
||||
return rsc.rc.(io.Seeker).Seek(offset, whence)
|
||||
}
|
||||
// once the binary request body is wrapped with ioutil.NopCloser:
|
||||
if isNopCloser(rsc.rc) {
|
||||
unwrappedInterface := reflect.ValueOf(rsc.rc).Field(0).Interface()
|
||||
if _, ok := unwrappedInterface.(io.Seeker); ok {
|
||||
return unwrappedInterface.(io.Seeker).Seek(offset, whence)
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("current binary request body type is not seekable, if want to use retry feature, please make sure the request body implements seek() method")
|
||||
}
|
||||
|
||||
// Close is a thread-safe operation, it closes the instance of the OCIReadSeekCloser's access to the underlying io.ReadCloser.
|
||||
func (rsc *OCIReadSeekCloser) Close() error {
|
||||
rsc.lock.Lock()
|
||||
defer rsc.lock.Unlock()
|
||||
rsc.isClosed = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read is a thread-safe operation, it implements io.Read() interface
|
||||
func (rsc *OCIReadSeekCloser) Read(p []byte) (n int, err error) {
|
||||
rsc.lock.Lock()
|
||||
defer rsc.lock.Unlock()
|
||||
|
||||
if rsc.isClosed {
|
||||
return 0, io.EOF
|
||||
}
|
||||
|
||||
return rsc.rc.Read(p)
|
||||
}
|
||||
|
||||
// Seekable is used for check if the binary request body can be seek or no
|
||||
func (rsc *OCIReadSeekCloser) Seekable() bool {
|
||||
if rsc == nil {
|
||||
return false
|
||||
}
|
||||
if _, ok := rsc.rc.(io.Seeker); ok {
|
||||
return true
|
||||
}
|
||||
// once the binary request body is wrapped with ioutil.NopCloser:
|
||||
if isNopCloser(rsc.rc) {
|
||||
if _, ok := reflect.ValueOf(rsc.rc).Field(0).Interface().(io.Seeker); ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// OCIResponse is the response from issuing a request to an OCI service.
|
||||
type OCIResponse interface {
|
||||
// HTTPResponse returns the raw HTTP response.
|
||||
HTTPResponse() *http.Response
|
||||
}
|
||||
|
||||
// OCIOperation is the generalization of a request-response cycle undergone by an OCI service.
|
||||
type OCIOperation func(context.Context, OCIRequest, *OCIReadSeekCloser, map[string]string) (OCIResponse, error)
|
||||
|
||||
// ClientCallDetails a set of settings used by the a single Call operation of the http Client
|
||||
type ClientCallDetails struct {
|
||||
Signer HTTPRequestSigner
|
||||
}
|
||||
|
||||
// Call executes the http request with the given context
|
||||
func (client BaseClient) Call(ctx context.Context, request *http.Request) (response *http.Response, err error) {
|
||||
if client.IsRefreshableAuthType() {
|
||||
return client.RefreshableTokenWrappedCallWithDetails(ctx, request, ClientCallDetails{Signer: client.Signer})
|
||||
}
|
||||
return client.CallWithDetails(ctx, request, ClientCallDetails{Signer: client.Signer})
|
||||
}
|
||||
|
||||
// RefreshableTokenWrappedCallWithDetails wraps the CallWithDetails with retry on 401 for Refreshable Toekn (Instance Principal, Resource Principal etc.)
|
||||
// This is to intimitate the race condition on refresh
|
||||
func (client BaseClient) RefreshableTokenWrappedCallWithDetails(ctx context.Context, request *http.Request, details ClientCallDetails) (response *http.Response, err error) {
|
||||
for i := 0; i < maxAttemptsForRefreshableRetry; i++ {
|
||||
response, err = client.CallWithDetails(ctx, request, ClientCallDetails{Signer: client.Signer})
|
||||
if response != nil && response.StatusCode != 401 {
|
||||
return response, err
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// CallWithDetails executes the http request, the given context using details specified in the parameters, this function
|
||||
// provides a way to override some settings present in the client
|
||||
func (client BaseClient) CallWithDetails(ctx context.Context, request *http.Request, details ClientCallDetails) (response *http.Response, err error) {
|
||||
Debugln("Attempting to call downstream service")
|
||||
request = request.WithContext(ctx)
|
||||
err = client.prepareRequest(request)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
//Intercept
|
||||
err = client.intercept(request)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
//Sign the request
|
||||
err = details.Signer.Sign(request)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
//Execute the http request
|
||||
if ociGoBreaker := client.Configuration.CircuitBreaker; ociGoBreaker != nil {
|
||||
resp, cbErr := ociGoBreaker.Cb.Execute(func() (interface{}, error) {
|
||||
return client.httpDo(request)
|
||||
})
|
||||
if httpResp, ok := resp.(*http.Response); ok {
|
||||
if httpResp != nil && httpResp.StatusCode != 200 {
|
||||
if failure, ok := IsServiceError(cbErr); ok {
|
||||
ociGoBreaker.AddToHistory(resp.(*http.Response), failure)
|
||||
}
|
||||
}
|
||||
}
|
||||
if cbErr != nil && IsCircuitBreakerError(cbErr) {
|
||||
cbErr = getCircuitBreakerError(request, cbErr, ociGoBreaker)
|
||||
}
|
||||
if _, ok := resp.(*http.Response); !ok {
|
||||
return nil, cbErr
|
||||
}
|
||||
return resp.(*http.Response), cbErr
|
||||
}
|
||||
return client.httpDo(request)
|
||||
}
|
||||
|
||||
// IsRefreshableAuthType validates if a signer is from a refreshable config provider
|
||||
func (client BaseClient) IsRefreshableAuthType() bool {
|
||||
if signer, ok := client.Signer.(ociRequestSigner); ok {
|
||||
if provider, ok := signer.KeyProvider.(RefreshableConfigurationProvider); ok {
|
||||
return provider.Refreshable()
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (client BaseClient) httpDo(request *http.Request) (response *http.Response, err error) {
|
||||
|
||||
//Copy request body and save for logging
|
||||
dumpRequestBody := ioutil.NopCloser(bytes.NewBuffer(nil))
|
||||
if request.Body != nil && !checkBodyLengthExceedLimit(request.ContentLength) {
|
||||
if dumpRequestBody, request.Body, err = drainBody(request.Body); err != nil {
|
||||
dumpRequestBody = ioutil.NopCloser(bytes.NewBuffer(nil))
|
||||
}
|
||||
}
|
||||
IfDebug(func() {
|
||||
logRequest(request, Debugf, verboseLogging)
|
||||
})
|
||||
|
||||
//Execute the http request
|
||||
response, err = client.HTTPClient.Do(request)
|
||||
|
||||
if err != nil {
|
||||
IfInfo(func() {
|
||||
Logf("%v\n", err)
|
||||
})
|
||||
return response, err
|
||||
}
|
||||
|
||||
err = checkForSuccessfulResponse(response, &dumpRequestBody)
|
||||
return response, err
|
||||
}
|
||||
|
||||
// CloseBodyIfValid closes the body of an http response if the response and the body are valid
|
||||
func CloseBodyIfValid(httpResponse *http.Response) {
|
||||
if httpResponse != nil && httpResponse.Body != nil {
|
||||
if httpResponse.Header != nil && strings.ToLower(httpResponse.Header.Get("content-type")) == "text/event-stream" {
|
||||
return
|
||||
}
|
||||
httpResponse.Body.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// IsOciRealmSpecificServiceEndpointTemplateEnabled returns true if the client is configured to use realm specific service endpoint template
|
||||
// it will first check the client configuration, if not set, it will check the environment variable
|
||||
func (client BaseClient) IsOciRealmSpecificServiceEndpointTemplateEnabled() bool {
|
||||
if client.Configuration.RealmSpecificServiceEndpointTemplateEnabled != nil {
|
||||
return *client.Configuration.RealmSpecificServiceEndpointTemplateEnabled
|
||||
}
|
||||
return IsEnvVarTrue(OciRealmSpecificServiceEndpointTemplateEnabledEnvVar)
|
||||
}
|
||||
|
||||
func getCustomCertRefreshInterval() int {
|
||||
if OciGlobalRefreshIntervalForCustomCerts >= 0 {
|
||||
Debugf("Setting refresh interval as %d for custom certs via OciGlobalRefreshIntervalForCustomCerts", OciGlobalRefreshIntervalForCustomCerts)
|
||||
return OciGlobalRefreshIntervalForCustomCerts
|
||||
}
|
||||
if refreshIntervalValue, ok := os.LookupEnv(ociDefaultRefreshIntervalForCustomCerts); ok {
|
||||
refreshInterval, err := strconv.Atoi(refreshIntervalValue)
|
||||
if err != nil || refreshInterval < 0 {
|
||||
Debugf("The environment variable %s is not a valid int or is a negative value, skipping this configuration", ociDefaultRefreshIntervalForCustomCerts)
|
||||
} else {
|
||||
Debugf("Setting refresh interval as %d for custom certs via the env variable %s", refreshInterval, ociDefaultRefreshIntervalForCustomCerts)
|
||||
return refreshInterval
|
||||
}
|
||||
}
|
||||
Debugf("Setting the default refresh interval %d for custom certs", defaultRefreshIntervalForCustomCerts)
|
||||
return defaultRefreshIntervalForCustomCerts
|
||||
}
|
||||
625
vendor/github.com/oracle/oci-go-sdk/v65/common/common.go
generated
vendored
Normal file
625
vendor/github.com/oracle/oci-go-sdk/v65/common/common.go
generated
vendored
Normal file
@@ -0,0 +1,625 @@
|
||||
// Copyright (c) 2016, 2018, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
// This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Region type for regions
|
||||
type Region string
|
||||
|
||||
const (
|
||||
instanceMetadataRegionInfoURLV2 = "http://169.254.169.254/opc/v2/instance/regionInfo"
|
||||
|
||||
// Region Metadata Configuration File
|
||||
regionMetadataCfgDirName = ".oci"
|
||||
regionMetadataCfgFileName = "regions-config.json"
|
||||
|
||||
// Region Metadata Environment Variable
|
||||
regionMetadataEnvVarName = "OCI_REGION_METADATA"
|
||||
|
||||
// Default Realm Environment Variable
|
||||
defaultRealmEnvVarName = "OCI_DEFAULT_REALM"
|
||||
|
||||
//EndpointTemplateForRegionWithDot Environment Variable
|
||||
EndpointTemplateForRegionWithDot = "https://{endpoint_service_name}.{region}"
|
||||
|
||||
// Region Metadata
|
||||
regionIdentifierPropertyName = "regionIdentifier" // e.g. "ap-sydney-1"
|
||||
realmKeyPropertyName = "realmKey" // e.g. "oc1"
|
||||
realmDomainComponentPropertyName = "realmDomainComponent" // e.g. "oraclecloud.com"
|
||||
regionKeyPropertyName = "regionKey" // e.g. "SYD"
|
||||
|
||||
// OciRealmSpecificServiceEndpointTemplateEnabledEnvVar is the environment variable name to enable the realm specific service endpoint template.
|
||||
OciRealmSpecificServiceEndpointTemplateEnabledEnvVar = "OCI_REALM_SPECIFIC_SERVICE_ENDPOINT_TEMPLATE_ENABLED"
|
||||
)
|
||||
|
||||
// External region metadata info flag, used to control adding these metadata region info only once.
|
||||
var readCfgFile, readEnvVar, visitIMDS bool = true, true, false
|
||||
|
||||
// getRegionInfoFromInstanceMetadataService gets the region information
|
||||
var getRegionInfoFromInstanceMetadataService = getRegionInfoFromInstanceMetadataServiceProd
|
||||
|
||||
// OciRealmSpecificServiceEndpointTemplateEnabled is the flag to enable the realm specific service endpoint template. This one has higher priority than the environment variable.
|
||||
var OciRealmSpecificServiceEndpointTemplateEnabled *bool = nil
|
||||
|
||||
// OciSdkEnabledServicesMap is a list of services that are enabled, default is an empty list which means all services are enabled
|
||||
var OciSdkEnabledServicesMap map[string]bool
|
||||
|
||||
// OciDeveloperToolConfigurationFilePathEnvVar is the environment variable name for the OCI Developer Tool Config File Path
|
||||
const OciDeveloperToolConfigurationFilePathEnvVar = "OCI_DEVELOPER_TOOL_CONFIGURATION_FILE_PATH"
|
||||
|
||||
// OciAllowOnlyDeveloperToolConfigurationRegionsEnvVar is the environment variable name for the OCI Allow only Dev Tool Config Regions
|
||||
const OciAllowOnlyDeveloperToolConfigurationRegionsEnvVar = "OCI_ALLOW_ONLY_DEVELOPER_TOOL_CONFIGURATION_REGIONS"
|
||||
|
||||
// defaultRealmForUnknownDeveloperToolConfigurationRegion is the default realm for unknown Developer Tool Configuration Regions
|
||||
const defaultRealmForUnknownDeveloperToolConfigurationRegion = "oraclecloud.com"
|
||||
|
||||
// OciDeveloperToolConfigurationProvider is the provider name for the OCI Developer Tool Configuration file
|
||||
var OciDeveloperToolConfigurationProvider string
|
||||
|
||||
// ociAllowOnlyDeveloperToolConfigurationRegions is the flag to enable the OCI Allow Only Developer Tool Configuration Regions. This one has lower priority than the environment variable.
|
||||
var ociAllowOnlyDeveloperToolConfigurationRegions bool
|
||||
|
||||
var ociDeveloperToolConfigurationRegionSchemaList []map[string]string
|
||||
|
||||
// Endpoint returns a endpoint for a service
|
||||
func (region Region) Endpoint(service string) string {
|
||||
// Endpoint for dotted region
|
||||
if strings.Contains(string(region), ".") {
|
||||
return fmt.Sprintf("%s.%s", service, region)
|
||||
}
|
||||
return fmt.Sprintf("%s.%s.%s", service, region, region.SecondLevelDomain())
|
||||
}
|
||||
|
||||
// EndpointForTemplate returns a endpoint for a service based on template, only unknown region name can fall back to "oc1", but not short code region name.
|
||||
func (region Region) EndpointForTemplate(service string, serviceEndpointTemplate string) string {
|
||||
if strings.Contains(string(region), ".") {
|
||||
endpoint, error := region.EndpointForTemplateDottedRegion(service, serviceEndpointTemplate, "")
|
||||
if error != nil {
|
||||
Debugf("%v", error)
|
||||
|
||||
return ""
|
||||
}
|
||||
return endpoint
|
||||
}
|
||||
|
||||
if serviceEndpointTemplate == "" {
|
||||
return region.Endpoint(service)
|
||||
}
|
||||
|
||||
// replace service prefix
|
||||
endpoint := strings.Replace(serviceEndpointTemplate, "{serviceEndpointPrefix}", service, 1)
|
||||
|
||||
// replace region
|
||||
endpoint = strings.Replace(endpoint, "{region}", string(region), 1)
|
||||
|
||||
// replace second level domain
|
||||
endpoint = strings.Replace(endpoint, "{secondLevelDomain}", region.SecondLevelDomain(), 1)
|
||||
|
||||
return endpoint
|
||||
}
|
||||
|
||||
// EndpointForTemplateDottedRegion returns a endpoint for a service based on the service name and EndpointTemplateForRegionWithDot template. If a service name is missing it is obtained from serviceEndpointTemplate and endpoint is constructed usingEndpointTemplateForRegionWithDot template.
|
||||
func (region Region) EndpointForTemplateDottedRegion(service string, serviceEndpointTemplate string, endpointServiceName string) (string, error) {
|
||||
if !strings.Contains(string(region), ".") {
|
||||
var endpoint = ""
|
||||
if serviceEndpointTemplate != "" {
|
||||
endpoint = region.EndpointForTemplate(service, serviceEndpointTemplate)
|
||||
return endpoint, nil
|
||||
}
|
||||
endpoint = region.EndpointForTemplate(service, "")
|
||||
return endpoint, nil
|
||||
}
|
||||
|
||||
if endpointServiceName != "" {
|
||||
endpoint := strings.Replace(EndpointTemplateForRegionWithDot, "{endpoint_service_name}", endpointServiceName, 1)
|
||||
endpoint = strings.Replace(endpoint, "{region}", string(region), 1)
|
||||
Debugf("Constructing endpoint from service name %s and region %s. Endpoint: %s", endpointServiceName, region, endpoint)
|
||||
return endpoint, nil
|
||||
}
|
||||
if serviceEndpointTemplate != "" {
|
||||
var endpoint = ""
|
||||
res := strings.Split(serviceEndpointTemplate, "//")
|
||||
if len(res) > 1 {
|
||||
res = strings.Split(res[1], ".")
|
||||
if len(res) > 1 {
|
||||
endpoint = strings.Replace(EndpointTemplateForRegionWithDot, "{endpoint_service_name}", res[0], 1)
|
||||
endpoint = strings.Replace(endpoint, "{region}", string(region), 1)
|
||||
Debugf("Constructing endpoint from service endpoint template %s and region %s. Endpoint: %s", serviceEndpointTemplate, region, endpoint)
|
||||
} else {
|
||||
return endpoint, fmt.Errorf("Endpoint service name not present in endpoint template")
|
||||
}
|
||||
} else {
|
||||
return endpoint, fmt.Errorf("invalid serviceEndpointTemplates. ServiceEndpointTemplate should start with https://")
|
||||
}
|
||||
return endpoint, nil
|
||||
}
|
||||
return "", fmt.Errorf("EndpointForTemplateDottedRegion function requires endpointServiceName or serviceEndpointTemplate, no endpointServiceName or serviceEndpointTemplate provided")
|
||||
}
|
||||
|
||||
func (region Region) SecondLevelDomain() string {
|
||||
if realmID, ok := regionRealm[region]; ok {
|
||||
if secondLevelDomain, ok := realm[realmID]; ok {
|
||||
return secondLevelDomain
|
||||
}
|
||||
}
|
||||
if value, ok := os.LookupEnv(defaultRealmEnvVarName); ok {
|
||||
return value
|
||||
}
|
||||
Debugf("cannot find realm for region : %s, return default realm value.", region)
|
||||
if _, ok := realm["oc1"]; !ok {
|
||||
return defaultRealmForUnknownDeveloperToolConfigurationRegion
|
||||
}
|
||||
return realm["oc1"]
|
||||
}
|
||||
|
||||
// RealmID is used for getting realmID from region, if no region found, directly throw error
|
||||
func (region Region) RealmID() (string, error) {
|
||||
if realmID, ok := regionRealm[region]; ok {
|
||||
return realmID, nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("cannot find realm for region : %s", region)
|
||||
}
|
||||
|
||||
// StringToRegion convert a string to Region type
|
||||
func StringToRegion(stringRegion string) (r Region) {
|
||||
regionStr := strings.ToLower(stringRegion)
|
||||
// check for PLC related regions
|
||||
if checkAllowOnlyDeveloperToolConfigurationRegions() && (checkDeveloperToolConfigurationFile() || len(ociDeveloperToolConfigurationRegionSchemaList) != 0) {
|
||||
Debugf("Developer Tool config detected and OCI_ALLOW_ONLY_DEVELOPER_TOOL_CONFIGURATION_REGIONS is set to True, SDK will only use regions defined for Developer Tool Configuration Regions")
|
||||
setRegionMetadataFromDeveloperToolConfigurationFile(&stringRegion)
|
||||
if len(ociDeveloperToolConfigurationRegionSchemaList) != 0 {
|
||||
resetRegionInfo()
|
||||
bulkAddRegionSchema(ociDeveloperToolConfigurationRegionSchemaList)
|
||||
}
|
||||
r = Region(stringRegion)
|
||||
if _, ok := regionRealm[r]; !ok {
|
||||
Logf("You're using the %s Developer Tool configuration file, the region you're targeting is not declared in this config file. Please check if this is the correct region you're targeting or contact the %s cloud provider for help. If you want to target both OCI regions and %s regions, please set the OCI_ALLOW_ONLY_DEVELOPER_TOOL_CONFIGURATION_REGIONS env var to False.", OciDeveloperToolConfigurationProvider, OciDeveloperToolConfigurationProvider, regionStr)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// check if short region name provided
|
||||
if region, ok := shortNameRegion[regionStr]; ok {
|
||||
r = region
|
||||
return
|
||||
}
|
||||
// check if normal region name provided
|
||||
potentialRegion := Region(regionStr)
|
||||
if _, ok := regionRealm[potentialRegion]; ok {
|
||||
r = potentialRegion
|
||||
return
|
||||
}
|
||||
|
||||
Debugf("region named: %s, is not recognized from hard-coded region list, will check Region metadata info", stringRegion)
|
||||
r = checkAndAddRegionMetadata(stringRegion)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// canStringBeRegion test if the string can be a region, if it can, returns the string as is, otherwise it
|
||||
// returns an error
|
||||
var blankRegex = regexp.MustCompile(`\s`)
|
||||
|
||||
func canStringBeRegion(stringRegion string) (region string, err error) {
|
||||
if blankRegex.MatchString(stringRegion) || stringRegion == "" {
|
||||
return "", fmt.Errorf("region can not be empty or have spaces")
|
||||
}
|
||||
return stringRegion, nil
|
||||
}
|
||||
|
||||
// check region info from original map
|
||||
func checkAndAddRegionMetadata(region string) Region {
|
||||
switch {
|
||||
case setRegionMetadataFromCfgFile(®ion):
|
||||
case setRegionMetadataFromEnvVar(®ion):
|
||||
case setRegionFromInstanceMetadataService(®ion):
|
||||
default:
|
||||
//err := fmt.Errorf("failed to get region metadata information.")
|
||||
return Region(region)
|
||||
}
|
||||
return Region(region)
|
||||
}
|
||||
|
||||
// EnableInstanceMetadataServiceLookup provides the interface to lookup IMDS region info
|
||||
func EnableInstanceMetadataServiceLookup() {
|
||||
Debugf("Set visitIMDS 'true' to enable IMDS Lookup.")
|
||||
visitIMDS = true
|
||||
}
|
||||
|
||||
// setRegionMetadataFromEnvVar checks if region metadata env variable is provided, once it's there, parse and added it
|
||||
// to region map, and it can make sure the env var can only be visited once.
|
||||
// Once successfully find the expected region(region name or short code), return true, region name will be stored in
|
||||
// the input pointer.
|
||||
func setRegionMetadataFromEnvVar(region *string) bool {
|
||||
if !readEnvVar {
|
||||
Debugf("metadata region env variable had already been checked, no need to check again.")
|
||||
return false //no need to check it again.
|
||||
}
|
||||
// Mark readEnvVar Flag as false since it has already been visited.
|
||||
readEnvVar = false
|
||||
// check from env variable
|
||||
if jsonStr, existed := os.LookupEnv(regionMetadataEnvVarName); existed {
|
||||
Debugf("Raw content of region metadata env var:", jsonStr)
|
||||
var regionSchema map[string]string
|
||||
if err := json.Unmarshal([]byte(jsonStr), ®ionSchema); err != nil {
|
||||
Debugf("Can't unmarshal env var, the error info is", err)
|
||||
return false
|
||||
}
|
||||
// check if the specified region is in the env var.
|
||||
if checkSchemaItems(regionSchema) {
|
||||
// set mapping table
|
||||
addRegionSchema(regionSchema)
|
||||
if regionSchema[regionKeyPropertyName] == *region ||
|
||||
regionSchema[regionIdentifierPropertyName] == *region {
|
||||
*region = regionSchema[regionIdentifierPropertyName]
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Debugf("The Region Metadata Schema wasn't set in env variable - OCI_REGION_METADATA.")
|
||||
return false
|
||||
}
|
||||
|
||||
func setRegionMetadataFromCfgFile(region *string) bool {
|
||||
if setRegionMetadataFromDeveloperToolConfigurationFile(region) {
|
||||
return true
|
||||
}
|
||||
if setRegionMetadataFromRegionCfgFile(region) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// setRegionMetadataFromCfgFile checks if region metadata config file is provided, once it's there, parse and add all
|
||||
// the valid regions to region map, the configuration file can only be visited once.
|
||||
// Once successfully find the expected region(region name or short code), return true, region name will be stored in
|
||||
// the input pointer.
|
||||
func setRegionMetadataFromRegionCfgFile(region *string) bool {
|
||||
if !readCfgFile {
|
||||
Debugf("metadata region config file had already been checked, no need to check again.")
|
||||
return false //no need to check it again.
|
||||
}
|
||||
// Mark readCfgFile Flag as false since it has already been visited.
|
||||
readCfgFile = false
|
||||
homeFolder := getHomeFolder()
|
||||
configFile := filepath.Join(homeFolder, regionMetadataCfgDirName, regionMetadataCfgFileName)
|
||||
if jsonArr, ok := readAndParseConfigFile(&configFile); ok {
|
||||
added := false
|
||||
for _, jsonItem := range jsonArr {
|
||||
if checkSchemaItems(jsonItem) {
|
||||
addRegionSchema(jsonItem)
|
||||
if jsonItem[regionKeyPropertyName] == *region ||
|
||||
jsonItem[regionIdentifierPropertyName] == *region {
|
||||
*region = jsonItem[regionIdentifierPropertyName]
|
||||
added = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return added
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// setRegionMetadataFromDeveloperToolConfigurationFile checks if Developer Tool config file is provided, once it's there, parse and add all
|
||||
// The default location of the Developer Tool config file is ~/.oci/developer-tool-configuration.json. It will also check the environment variable
|
||||
// the valid regions to region map, the configuration file can only be visited once.
|
||||
// Once successfully find the expected region(region name or short code), return true, region name will be stored in
|
||||
// the input pointer.
|
||||
func setRegionMetadataFromDeveloperToolConfigurationFile(region *string) bool {
|
||||
if jsonArr, ok := readAndParseDeveloperToolConfigurationFile(); ok {
|
||||
added := false
|
||||
if jsonArr["regions"] == nil {
|
||||
return false
|
||||
}
|
||||
var regionJSON []map[string]string
|
||||
originalJSONContent, err := json.Marshal(jsonArr["regions"])
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
err = json.Unmarshal(originalJSONContent, ®ionJSON)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if IsEnvVarTrue(OciAllowOnlyDeveloperToolConfigurationRegionsEnvVar) {
|
||||
resetRegionInfo()
|
||||
}
|
||||
for _, jsonItem := range regionJSON {
|
||||
if checkSchemaItems(jsonItem) {
|
||||
addRegionSchema(jsonItem)
|
||||
if jsonItem[regionKeyPropertyName] == *region ||
|
||||
jsonItem[regionIdentifierPropertyName] == *region {
|
||||
*region = jsonItem[regionIdentifierPropertyName]
|
||||
added = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return added
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func readAndParseConfigFile(configFileName *string) (fileContent []map[string]string, ok bool) {
|
||||
if content, err := ioutil.ReadFile(*configFileName); err == nil {
|
||||
Debugf("Raw content of region metadata config file content:", string(content[:]))
|
||||
if err := json.Unmarshal(content, &fileContent); err != nil {
|
||||
Debugf("Can't unmarshal config file, the error info is", err)
|
||||
return
|
||||
}
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
Debugf("No Region Metadata Config File provided.")
|
||||
return
|
||||
}
|
||||
|
||||
func readAndParseDeveloperToolConfigurationFile() (fileContent map[string]interface{}, ok bool) {
|
||||
homeFolder := getHomeFolder()
|
||||
configFileName := filepath.Join(homeFolder, regionMetadataCfgDirName, "developer-tool-configuration.json")
|
||||
if path := os.Getenv(OciDeveloperToolConfigurationFilePathEnvVar); path != "" {
|
||||
configFileName = path
|
||||
}
|
||||
if content, err := ioutil.ReadFile(configFileName); err == nil {
|
||||
Debugf("Raw content of Developer Tool config file content:", string(content[:]))
|
||||
if err := json.Unmarshal(content, &fileContent); err != nil {
|
||||
Debugf("Can't unmarshal env var, the error info is", err)
|
||||
return
|
||||
}
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
Debugf("No Developer Tool Config File provided.")
|
||||
return
|
||||
}
|
||||
|
||||
func checkDeveloperToolConfigurationFile() bool {
|
||||
homeFolder := getHomeFolder()
|
||||
configFileName := filepath.Join(homeFolder, regionMetadataCfgDirName, "developer-tool-configuration.json")
|
||||
if path := os.Getenv(OciDeveloperToolConfigurationFilePathEnvVar); path != "" {
|
||||
configFileName = path
|
||||
}
|
||||
if _, err := os.Stat(configFileName); err == nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// check map regionRealm's region name, if it's already there, no need to add it.
|
||||
func addRegionSchema(regionSchema map[string]string) {
|
||||
r := Region(strings.ToLower(regionSchema[regionIdentifierPropertyName]))
|
||||
if _, ok := regionRealm[r]; !ok {
|
||||
// set mapping table
|
||||
shortNameRegion[regionSchema[regionKeyPropertyName]] = r
|
||||
realm[regionSchema[realmKeyPropertyName]] = regionSchema[realmDomainComponentPropertyName]
|
||||
regionRealm[r] = regionSchema[realmKeyPropertyName]
|
||||
return
|
||||
}
|
||||
Debugf("Region {} has already been added, no need to add again.", regionSchema[regionIdentifierPropertyName])
|
||||
}
|
||||
|
||||
// AddRegionSchemaForPlc add region schema to region map
|
||||
func AddRegionSchemaForPlc(regionSchema map[string]string) {
|
||||
ociDeveloperToolConfigurationRegionSchemaList = append(ociDeveloperToolConfigurationRegionSchemaList, regionSchema)
|
||||
addRegionSchema(regionSchema)
|
||||
// if !IsEnvVarTrue(OciPlcRegionExclusiveEnvVar) {
|
||||
// addRegionSchema(regionSchema)
|
||||
// return
|
||||
// }
|
||||
// Debugf("Plc region coexist is not enabled, remove exisiting OCI region schema and add PLC region schema.")
|
||||
// resetRegionInfo()
|
||||
// bulkAddRegionSchema(ociPlcRegionSchemaList)
|
||||
}
|
||||
|
||||
func resetRegionInfo() {
|
||||
shortNameRegion = make(map[string]Region)
|
||||
realm = make(map[string]string)
|
||||
regionRealm = make(map[Region]string)
|
||||
}
|
||||
|
||||
func bulkAddRegionSchema(regionSchemaList []map[string]string) {
|
||||
for _, regionSchema := range regionSchemaList {
|
||||
if checkSchemaItems(regionSchema) {
|
||||
addRegionSchema(regionSchema)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check region schema content if all the required contents are provided
|
||||
func checkSchemaItems(regionSchema map[string]string) bool {
|
||||
if checkSchemaItem(regionSchema, regionIdentifierPropertyName) &&
|
||||
checkSchemaItem(regionSchema, realmKeyPropertyName) &&
|
||||
checkSchemaItem(regionSchema, realmDomainComponentPropertyName) &&
|
||||
checkSchemaItem(regionSchema, regionKeyPropertyName) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// check region schema item is valid, if so, convert it to lower case.
|
||||
func checkSchemaItem(regionSchema map[string]string, key string) bool {
|
||||
if val, ok := regionSchema[key]; ok {
|
||||
if val != "" {
|
||||
regionSchema[key] = strings.ToLower(val)
|
||||
return true
|
||||
}
|
||||
Debugf("Region metadata schema {} is provided,but content is empty.", key)
|
||||
return false
|
||||
}
|
||||
Debugf("Region metadata schema {} is not provided, please update the content", key)
|
||||
return false
|
||||
}
|
||||
|
||||
// setRegionFromInstanceMetadataService checks if region metadata can be provided from InstanceMetadataService.
|
||||
// Once successfully find the expected region(region name or short code), return true, region name will be stored in
|
||||
// the input pointer.
|
||||
// setRegionFromInstanceMetadataService will only be checked on the instance, by default it will not be enabled unless
|
||||
// user explicitly enable it.
|
||||
func setRegionFromInstanceMetadataService(region *string) bool {
|
||||
// example of content:
|
||||
// {
|
||||
// "realmKey" : "oc1",
|
||||
// "realmDomainComponent" : "oraclecloud.com",
|
||||
// "regionKey" : "YUL",
|
||||
// "regionIdentifier" : "ca-montreal-1"
|
||||
// }
|
||||
// Mark visitIMDS Flag as false since it has already been visited.
|
||||
if !visitIMDS {
|
||||
Debugf("check from IMDS is disabled or IMDS had already been successfully visited, no need to check again.")
|
||||
return false
|
||||
}
|
||||
content, err := getRegionInfoFromInstanceMetadataService()
|
||||
if err != nil {
|
||||
Debugf("Failed to get instance metadata. Error: %v", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// Mark visitIMDS Flag as false since we have already successfully get the region info from IMDS.
|
||||
visitIMDS = false
|
||||
|
||||
var regionInfo map[string]string
|
||||
err = json.Unmarshal(content, ®ionInfo)
|
||||
if err != nil {
|
||||
Debugf("Failed to unmarshal the response content: %v \nError: %v", string(content), err)
|
||||
return false
|
||||
}
|
||||
|
||||
if checkSchemaItems(regionInfo) {
|
||||
addRegionSchema(regionInfo)
|
||||
if regionInfo[regionKeyPropertyName] == *region ||
|
||||
regionInfo[regionIdentifierPropertyName] == *region {
|
||||
*region = regionInfo[regionIdentifierPropertyName]
|
||||
}
|
||||
} else {
|
||||
Debugf("Region information is not valid.")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// getRegionInfoFromInstanceMetadataServiceProd calls instance metadata service and get the region information
|
||||
func getRegionInfoFromInstanceMetadataServiceProd() ([]byte, error) {
|
||||
request, _ := http.NewRequest(http.MethodGet, instanceMetadataRegionInfoURLV2, nil)
|
||||
request.Header.Add("Authorization", "Bearer Oracle")
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: time.Second * 10,
|
||||
}
|
||||
resp, err := client.Do(request)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to call instance metadata service. Error: %v", err)
|
||||
}
|
||||
|
||||
statusCode := resp.StatusCode
|
||||
|
||||
defer resp.Body.Close()
|
||||
|
||||
content, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get region information from response body. Error: %v", err)
|
||||
}
|
||||
|
||||
if statusCode != http.StatusOK {
|
||||
err = fmt.Errorf("HTTP Get failed: URL: %s, Status: %s, Message: %s",
|
||||
instanceMetadataRegionInfoURLV2, resp.Status, string(content))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return content, nil
|
||||
}
|
||||
|
||||
// TemplateParamForPerRealmEndpoint is a template parameter for per-realm endpoint.
|
||||
type TemplateParamForPerRealmEndpoint struct {
|
||||
Template string
|
||||
EndsWithDot bool
|
||||
}
|
||||
|
||||
// SetMissingTemplateParams function will parse the {} template in client host and replace with empty string.
|
||||
func SetMissingTemplateParams(client *BaseClient) {
|
||||
templateRegex := regexp.MustCompile(`{.*?}`)
|
||||
templates := templateRegex.FindAllString(client.Host, -1)
|
||||
for _, template := range templates {
|
||||
client.Host = strings.Replace(client.Host, template, "", -1)
|
||||
}
|
||||
}
|
||||
|
||||
func getOciSdkEnabledServicesMap() map[string]bool {
|
||||
var enabledMap = make(map[string]bool)
|
||||
if jsonArr, ok := readAndParseDeveloperToolConfigurationFile(); ok {
|
||||
if jsonArr["provider"] != nil {
|
||||
OciDeveloperToolConfigurationProvider = jsonArr["provider"].(string)
|
||||
}
|
||||
if jsonArr["allowOnlyDeveloperToolConfigurationRegions"] != nil && jsonArr["allowOnlyDeveloperToolConfigurationRegions"] == false {
|
||||
ociAllowOnlyDeveloperToolConfigurationRegions = jsonArr["allowOnlyDeveloperToolConfigurationRegions"].(bool)
|
||||
}
|
||||
if jsonArr["services"] == nil {
|
||||
return enabledMap
|
||||
}
|
||||
serviesJSON, ok := jsonArr["services"].([]interface{})
|
||||
if !ok {
|
||||
return enabledMap
|
||||
}
|
||||
re, _ := regexp.Compile(`[^\w]`)
|
||||
for _, jsonItem := range serviesJSON {
|
||||
serviceName := strings.ToLower(fmt.Sprint(jsonItem))
|
||||
serviceName = re.ReplaceAllString(serviceName, "")
|
||||
enabledMap[serviceName] = true
|
||||
}
|
||||
}
|
||||
return enabledMap
|
||||
}
|
||||
|
||||
// AddServiceToEnabledServicesMap adds the service to the enabledServiceMap
|
||||
// The service name will auto transit to lower case and remove all the non-word characters.
|
||||
func AddServiceToEnabledServicesMap(serviceName string) {
|
||||
if OciSdkEnabledServicesMap == nil {
|
||||
OciSdkEnabledServicesMap = make(map[string]bool)
|
||||
}
|
||||
re, _ := regexp.Compile(`[^\w]`)
|
||||
serviceName = strings.ToLower(serviceName)
|
||||
serviceName = re.ReplaceAllString(serviceName, "")
|
||||
OciSdkEnabledServicesMap[serviceName] = true
|
||||
}
|
||||
|
||||
// CheckForEnabledServices checks if the service is enabled in the enabledServiceMap.
|
||||
// It will first check if the map is initialized, if not, it will initialize the map.
|
||||
// If the map is empty, it means all the services are enabled.
|
||||
// If the map is not empty, it means only the services in the map and value is true are enabled.
|
||||
func CheckForEnabledServices(serviceName string) bool {
|
||||
if OciSdkEnabledServicesMap == nil {
|
||||
OciSdkEnabledServicesMap = getOciSdkEnabledServicesMap()
|
||||
}
|
||||
serviceName = strings.ToLower(serviceName)
|
||||
if len(OciSdkEnabledServicesMap) == 0 {
|
||||
return true
|
||||
}
|
||||
if _, ok := OciSdkEnabledServicesMap[serviceName]; !ok {
|
||||
return false
|
||||
}
|
||||
return OciSdkEnabledServicesMap[serviceName]
|
||||
}
|
||||
|
||||
// CheckAllowOnlyDeveloperToolConfigurationRegions checks if only developer tool configuration regions are allowed
|
||||
// This function will first check if the OCI_ALLOW_ONLY_DEVELOPER_TOOL_CONFIGURATION_REGIONS environment variable is set.
|
||||
// If it is set, it will return the value.
|
||||
// If it is not set, it will return the value from the ociAllowOnlyDeveloperToolConfigurationRegions variable.
|
||||
func checkAllowOnlyDeveloperToolConfigurationRegions() bool {
|
||||
if val, ok := os.LookupEnv("OCI_ALLOW_ONLY_DEVELOPER_TOOL_CONFIGURATION_REGIONS"); ok {
|
||||
return val == "true"
|
||||
}
|
||||
return ociAllowOnlyDeveloperToolConfigurationRegions
|
||||
}
|
||||
820
vendor/github.com/oracle/oci-go-sdk/v65/common/configuration.go
generated
vendored
Normal file
820
vendor/github.com/oracle/oci-go-sdk/v65/common/configuration.go
generated
vendored
Normal file
@@ -0,0 +1,820 @@
|
||||
// Copyright (c) 2016, 2018, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
// This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// AuthenticationType for auth
|
||||
type AuthenticationType string
|
||||
|
||||
const (
|
||||
// UserPrincipal is default auth type
|
||||
UserPrincipal AuthenticationType = "user_principal"
|
||||
// InstancePrincipal is used for instance principal auth type
|
||||
InstancePrincipal AuthenticationType = "instance_principal"
|
||||
// InstancePrincipalDelegationToken is used for instance principal delegation token auth type
|
||||
InstancePrincipalDelegationToken AuthenticationType = "instance_principle_delegation_token"
|
||||
// ResourcePrincipalDelegationToken is used for resource principal delegation token auth type
|
||||
ResourcePrincipalDelegationToken AuthenticationType = "resource_principle_delegation_token"
|
||||
// UnknownAuthenticationType is used for none meaningful auth type
|
||||
UnknownAuthenticationType AuthenticationType = "unknown_auth_type"
|
||||
)
|
||||
|
||||
// AuthConfig is used for getting auth related paras in config file
|
||||
type AuthConfig struct {
|
||||
AuthType AuthenticationType
|
||||
// IsFromConfigFile is used to point out if the authConfig is from configuration file
|
||||
IsFromConfigFile bool
|
||||
OboToken *string
|
||||
}
|
||||
|
||||
// ConfigurationProvider wraps information about the account owner
|
||||
type ConfigurationProvider interface {
|
||||
KeyProvider
|
||||
TenancyOCID() (string, error)
|
||||
UserOCID() (string, error)
|
||||
KeyFingerprint() (string, error)
|
||||
Region() (string, error)
|
||||
// AuthType() is used for specify the needed auth type, like UserPrincipal, InstancePrincipal, etc.
|
||||
AuthType() (AuthConfig, error)
|
||||
}
|
||||
|
||||
var fileMutex = sync.Mutex{}
|
||||
var fileCache = make(map[string][]byte)
|
||||
|
||||
// Reads the file contents from cache if present otherwise reads the file.
|
||||
// If file to be read is frequently updated/refreshed, please use readFile(filename) as readFileFromCache(filename) might return the old contents from the cache.
|
||||
func readFileFromCache(filename string) ([]byte, error) {
|
||||
fileMutex.Lock()
|
||||
defer fileMutex.Unlock()
|
||||
val, ok := fileCache[filename]
|
||||
if ok {
|
||||
return val, nil
|
||||
}
|
||||
val, err := ioutil.ReadFile(filename)
|
||||
if err == nil {
|
||||
fileCache[filename] = val
|
||||
}
|
||||
return val, err
|
||||
}
|
||||
|
||||
// Reads the file and returns the contents
|
||||
func readFile(filename string) ([]byte, error) {
|
||||
fileMutex.Lock()
|
||||
defer fileMutex.Unlock()
|
||||
val, err := os.ReadFile(filename)
|
||||
return val, err
|
||||
}
|
||||
|
||||
// IsConfigurationProviderValid Tests all parts of the configuration provider do not return an error, this method will
|
||||
// not check AuthType(), since authType() is not required to be there.
|
||||
func IsConfigurationProviderValid(conf ConfigurationProvider) (ok bool, err error) {
|
||||
baseFn := []func() (string, error){conf.TenancyOCID, conf.UserOCID, conf.KeyFingerprint, conf.Region, conf.KeyID}
|
||||
for _, fn := range baseFn {
|
||||
_, err = fn()
|
||||
ok = err == nil
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
_, err = conf.PrivateRSAKey()
|
||||
ok = err == nil
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// rawConfigurationProvider allows a user to simply construct a configuration provider from raw values.
|
||||
type rawConfigurationProvider struct {
|
||||
tenancy string
|
||||
user string
|
||||
region string
|
||||
fingerprint string
|
||||
privateKey string
|
||||
privateKeyPassphrase *string
|
||||
}
|
||||
|
||||
// NewRawConfigurationProvider will create a ConfigurationProvider with the arguments of the function
|
||||
func NewRawConfigurationProvider(tenancy, user, region, fingerprint, privateKey string, privateKeyPassphrase *string) ConfigurationProvider {
|
||||
return rawConfigurationProvider{tenancy, user, region, fingerprint, privateKey, privateKeyPassphrase}
|
||||
}
|
||||
|
||||
func (p rawConfigurationProvider) PrivateRSAKey() (key *rsa.PrivateKey, err error) {
|
||||
return PrivateKeyFromBytes([]byte(p.privateKey), p.privateKeyPassphrase)
|
||||
}
|
||||
|
||||
func (p rawConfigurationProvider) KeyID() (keyID string, err error) {
|
||||
tenancy, err := p.TenancyOCID()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
user, err := p.UserOCID()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fingerprint, err := p.KeyFingerprint()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s/%s", tenancy, user, fingerprint), nil
|
||||
}
|
||||
|
||||
func (p rawConfigurationProvider) TenancyOCID() (string, error) {
|
||||
if p.tenancy == "" {
|
||||
return "", fmt.Errorf("tenancy OCID can not be empty")
|
||||
}
|
||||
return p.tenancy, nil
|
||||
}
|
||||
|
||||
func (p rawConfigurationProvider) UserOCID() (string, error) {
|
||||
if p.user == "" {
|
||||
return "", fmt.Errorf("user OCID can not be empty")
|
||||
}
|
||||
return p.user, nil
|
||||
}
|
||||
|
||||
func (p rawConfigurationProvider) KeyFingerprint() (string, error) {
|
||||
if p.fingerprint == "" {
|
||||
return "", fmt.Errorf("fingerprint can not be empty")
|
||||
}
|
||||
return p.fingerprint, nil
|
||||
}
|
||||
|
||||
func (p rawConfigurationProvider) Region() (string, error) {
|
||||
return canStringBeRegion(p.region)
|
||||
}
|
||||
|
||||
func (p rawConfigurationProvider) AuthType() (AuthConfig, error) {
|
||||
return AuthConfig{UnknownAuthenticationType, false, nil}, nil
|
||||
}
|
||||
|
||||
// environmentConfigurationProvider reads configuration from environment variables
|
||||
type environmentConfigurationProvider struct {
|
||||
PrivateKeyPassword string
|
||||
EnvironmentVariablePrefix string
|
||||
}
|
||||
|
||||
// ConfigurationProviderEnvironmentVariables creates a ConfigurationProvider from a uniform set of environment variables starting with a prefix
|
||||
// The env variables should look like: [prefix]_private_key_path, [prefix]_tenancy_ocid, [prefix]_user_ocid, [prefix]_fingerprint
|
||||
// [prefix]_region
|
||||
func ConfigurationProviderEnvironmentVariables(environmentVariablePrefix, privateKeyPassword string) ConfigurationProvider {
|
||||
return environmentConfigurationProvider{EnvironmentVariablePrefix: environmentVariablePrefix,
|
||||
PrivateKeyPassword: privateKeyPassword}
|
||||
}
|
||||
|
||||
func (p environmentConfigurationProvider) String() string {
|
||||
return fmt.Sprintf("Configuration provided by environment variables prefixed with: %s", p.EnvironmentVariablePrefix)
|
||||
}
|
||||
|
||||
func (p environmentConfigurationProvider) PrivateRSAKey() (key *rsa.PrivateKey, err error) {
|
||||
environmentVariable := fmt.Sprintf("%s_%s", p.EnvironmentVariablePrefix, "private_key_path")
|
||||
var ok bool
|
||||
var value string
|
||||
if value, ok = os.LookupEnv(environmentVariable); !ok {
|
||||
return nil, fmt.Errorf("can not read PrivateKey from env variable: %s", environmentVariable)
|
||||
}
|
||||
|
||||
expandedPath := expandPath(value)
|
||||
pemFileContent, err := readFileFromCache(expandedPath)
|
||||
if err != nil {
|
||||
Debugln("Can not read PrivateKey location from environment variable: " + environmentVariable)
|
||||
return
|
||||
}
|
||||
|
||||
key, err = PrivateKeyFromBytes(pemFileContent, &p.PrivateKeyPassword)
|
||||
return
|
||||
}
|
||||
|
||||
func (p environmentConfigurationProvider) KeyID() (keyID string, err error) {
|
||||
ocid, err := p.TenancyOCID()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
userocid, err := p.UserOCID()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fingerprint, err := p.KeyFingerprint()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/%s/%s", ocid, userocid, fingerprint), nil
|
||||
}
|
||||
|
||||
func (p environmentConfigurationProvider) TenancyOCID() (value string, err error) {
|
||||
environmentVariable := fmt.Sprintf("%s_%s", p.EnvironmentVariablePrefix, "tenancy_ocid")
|
||||
var ok bool
|
||||
if value, ok = os.LookupEnv(environmentVariable); !ok {
|
||||
err = fmt.Errorf("can not read Tenancy from environment variable %s", environmentVariable)
|
||||
} else if value == "" {
|
||||
err = fmt.Errorf("tenancy OCID can not be empty when reading from environmental variable")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p environmentConfigurationProvider) UserOCID() (value string, err error) {
|
||||
environmentVariable := fmt.Sprintf("%s_%s", p.EnvironmentVariablePrefix, "user_ocid")
|
||||
var ok bool
|
||||
if value, ok = os.LookupEnv(environmentVariable); !ok {
|
||||
err = fmt.Errorf("can not read user id from environment variable %s", environmentVariable)
|
||||
} else if value == "" {
|
||||
err = fmt.Errorf("user OCID can not be empty when reading from environmental variable")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p environmentConfigurationProvider) KeyFingerprint() (value string, err error) {
|
||||
environmentVariable := fmt.Sprintf("%s_%s", p.EnvironmentVariablePrefix, "fingerprint")
|
||||
var ok bool
|
||||
if value, ok = os.LookupEnv(environmentVariable); !ok {
|
||||
err = fmt.Errorf("can not read fingerprint from environment variable %s", environmentVariable)
|
||||
} else if value == "" {
|
||||
err = fmt.Errorf("fingerprint can not be empty when reading from environmental variable")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p environmentConfigurationProvider) Region() (value string, err error) {
|
||||
environmentVariable := fmt.Sprintf("%s_%s", p.EnvironmentVariablePrefix, "region")
|
||||
var ok bool
|
||||
if value, ok = os.LookupEnv(environmentVariable); !ok {
|
||||
err = fmt.Errorf("can not read region from environment variable %s", environmentVariable)
|
||||
return value, err
|
||||
}
|
||||
|
||||
return canStringBeRegion(value)
|
||||
}
|
||||
|
||||
func (p environmentConfigurationProvider) AuthType() (AuthConfig, error) {
|
||||
return AuthConfig{UnknownAuthenticationType, false, nil},
|
||||
fmt.Errorf("unsupported, keep the interface")
|
||||
}
|
||||
|
||||
// fileConfigurationProvider. reads configuration information from a file
|
||||
type fileConfigurationProvider struct {
|
||||
//The path to the configuration file
|
||||
ConfigPath string
|
||||
|
||||
//The password for the private key
|
||||
PrivateKeyPassword string
|
||||
|
||||
//The profile for the configuration
|
||||
Profile string
|
||||
|
||||
//ConfigFileInfo
|
||||
FileInfo *configFileInfo
|
||||
|
||||
//Mutex to protect the config file
|
||||
configMux sync.Mutex
|
||||
}
|
||||
|
||||
type fileConfigurationProviderError struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (fpe fileConfigurationProviderError) Error() string {
|
||||
return fmt.Sprintf("%s\nFor more info about config file and how to get required information, see https://docs.oracle.com/en-us/iaas/Content/API/Concepts/sdkconfig.htm", fpe.err)
|
||||
}
|
||||
|
||||
// ConfigurationProviderFromFile creates a configuration provider from a configuration file
|
||||
// by reading the "DEFAULT" profile
|
||||
func ConfigurationProviderFromFile(configFilePath, privateKeyPassword string) (ConfigurationProvider, error) {
|
||||
if configFilePath == "" {
|
||||
return nil, fmt.Errorf("config file path can not be empty")
|
||||
}
|
||||
|
||||
return fileConfigurationProvider{
|
||||
ConfigPath: configFilePath,
|
||||
PrivateKeyPassword: privateKeyPassword,
|
||||
Profile: "DEFAULT",
|
||||
configMux: sync.Mutex{}}, nil
|
||||
}
|
||||
|
||||
// ConfigurationProviderFromFileWithProfile creates a configuration provider from a configuration file
|
||||
// and the given profile
|
||||
func ConfigurationProviderFromFileWithProfile(configFilePath, profile, privateKeyPassword string) (ConfigurationProvider, error) {
|
||||
if configFilePath == "" {
|
||||
return nil, fileConfigurationProviderError{err: fmt.Errorf("config file path can not be empty")}
|
||||
}
|
||||
|
||||
return fileConfigurationProvider{
|
||||
ConfigPath: configFilePath,
|
||||
PrivateKeyPassword: privateKeyPassword,
|
||||
Profile: profile,
|
||||
configMux: sync.Mutex{}}, nil
|
||||
}
|
||||
|
||||
type configFileInfo struct {
|
||||
UserOcid, Fingerprint, KeyFilePath, TenancyOcid, Region, Passphrase, SecurityTokenFilePath, DelegationTokenFilePath,
|
||||
AuthenticationType string
|
||||
PresentConfiguration rune
|
||||
}
|
||||
|
||||
const (
|
||||
hasTenancy = 1 << iota
|
||||
hasUser
|
||||
hasFingerprint
|
||||
hasRegion
|
||||
hasKeyFile
|
||||
hasPassphrase
|
||||
hasSecurityTokenFile
|
||||
hasDelegationTokenFile
|
||||
hasAuthenticationType
|
||||
none
|
||||
)
|
||||
|
||||
var profileRegex = regexp.MustCompile(`^\[(.*)\]`)
|
||||
|
||||
func parseConfigFile(data []byte, profile string) (info *configFileInfo, err error) {
|
||||
|
||||
if len(data) == 0 {
|
||||
return nil, fileConfigurationProviderError{err: fmt.Errorf("configuration file content is empty")}
|
||||
}
|
||||
|
||||
content := string(data)
|
||||
splitContent := strings.Split(content, "\n")
|
||||
|
||||
//Look for profile
|
||||
for i, line := range splitContent {
|
||||
if match := profileRegex.FindStringSubmatch(line); len(match) > 1 && match[1] == profile {
|
||||
start := i + 1
|
||||
return parseConfigAtLine(start, splitContent)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fileConfigurationProviderError{err: fmt.Errorf("configuration file did not contain profile: %s", profile)}
|
||||
}
|
||||
|
||||
func parseConfigAtLine(start int, content []string) (info *configFileInfo, err error) {
|
||||
var configurationPresent rune
|
||||
info = &configFileInfo{}
|
||||
for i := start; i < len(content); i++ {
|
||||
line := content[i]
|
||||
if profileRegex.MatchString(line) {
|
||||
break
|
||||
}
|
||||
|
||||
if !strings.Contains(line, "=") {
|
||||
continue
|
||||
}
|
||||
|
||||
splits := strings.Split(line, "=")
|
||||
switch key, value := strings.TrimSpace(splits[0]), strings.TrimSpace(splits[1]); strings.ToLower(key) {
|
||||
case "passphrase", "pass_phrase":
|
||||
configurationPresent = configurationPresent | hasPassphrase
|
||||
info.Passphrase = value
|
||||
case "user":
|
||||
configurationPresent = configurationPresent | hasUser
|
||||
info.UserOcid = value
|
||||
case "fingerprint":
|
||||
configurationPresent = configurationPresent | hasFingerprint
|
||||
info.Fingerprint = value
|
||||
case "key_file":
|
||||
configurationPresent = configurationPresent | hasKeyFile
|
||||
info.KeyFilePath = value
|
||||
case "tenancy":
|
||||
configurationPresent = configurationPresent | hasTenancy
|
||||
info.TenancyOcid = value
|
||||
case "region":
|
||||
configurationPresent = configurationPresent | hasRegion
|
||||
info.Region = value
|
||||
case "security_token_file":
|
||||
configurationPresent = configurationPresent | hasSecurityTokenFile
|
||||
info.SecurityTokenFilePath = value
|
||||
case "delegation_token_file":
|
||||
configurationPresent = configurationPresent | hasDelegationTokenFile
|
||||
info.DelegationTokenFilePath = value
|
||||
case "authentication_type":
|
||||
configurationPresent = configurationPresent | hasAuthenticationType
|
||||
info.AuthenticationType = value
|
||||
}
|
||||
}
|
||||
info.PresentConfiguration = configurationPresent
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
// cleans and expands the path if it contains a tilde , returns the expanded path or the input path as is if not expansion
|
||||
// was performed
|
||||
func expandPath(filename string) (expandedPath string) {
|
||||
cleanedPath := filepath.Clean(filename)
|
||||
expandedPath = cleanedPath
|
||||
if strings.HasPrefix(cleanedPath, "~") {
|
||||
rest := cleanedPath[2:]
|
||||
expandedPath = filepath.Join(getHomeFolder(), rest)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func openConfigFile(configFilePath string) (data []byte, err error) {
|
||||
expandedPath := expandPath(configFilePath)
|
||||
data, err = readFileFromCache(expandedPath)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("can not read config file: %s due to: %s", configFilePath, err.Error())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (p fileConfigurationProvider) String() string {
|
||||
return fmt.Sprintf("Configuration provided by file: %s", p.ConfigPath)
|
||||
}
|
||||
|
||||
func (p fileConfigurationProvider) readAndParseConfigFile() (info *configFileInfo, err error) {
|
||||
p.configMux.Lock()
|
||||
defer p.configMux.Unlock()
|
||||
if p.FileInfo != nil {
|
||||
return p.FileInfo, nil
|
||||
}
|
||||
|
||||
if p.ConfigPath == "" {
|
||||
return nil, fileConfigurationProviderError{err: fmt.Errorf("configuration path can not be empty")}
|
||||
}
|
||||
|
||||
data, err := openConfigFile(p.ConfigPath)
|
||||
if err != nil {
|
||||
err = fileConfigurationProviderError{err: fmt.Errorf("error while parsing config file: %s. Due to: %s", p.ConfigPath, err.Error())}
|
||||
return
|
||||
}
|
||||
|
||||
p.FileInfo, err = parseConfigFile(data, p.Profile)
|
||||
return p.FileInfo, err
|
||||
}
|
||||
|
||||
func presentOrError(value string, expectedConf, presentConf rune, confMissing string) (string, error) {
|
||||
if presentConf&expectedConf == expectedConf {
|
||||
return value, nil
|
||||
}
|
||||
return "", fileConfigurationProviderError{err: errors.New(confMissing + " configuration is missing from file")}
|
||||
}
|
||||
|
||||
func (p fileConfigurationProvider) TenancyOCID() (value string, err error) {
|
||||
info, err := p.readAndParseConfigFile()
|
||||
if err != nil {
|
||||
err = fileConfigurationProviderError{err: fmt.Errorf("can not read tenancy configuration due to: %s", err.Error())}
|
||||
return
|
||||
}
|
||||
|
||||
value, err = presentOrError(info.TenancyOcid, hasTenancy, info.PresentConfiguration, "tenancy")
|
||||
if err == nil && value == "" {
|
||||
err = fileConfigurationProviderError{err: fmt.Errorf("tenancy OCID can not be empty when reading from config file")}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p fileConfigurationProvider) UserOCID() (value string, err error) {
|
||||
info, err := p.readAndParseConfigFile()
|
||||
if err != nil {
|
||||
err = fileConfigurationProviderError{err: fmt.Errorf("can not read tenancy configuration due to: %s", err.Error())}
|
||||
return
|
||||
}
|
||||
|
||||
if value, err = presentOrError(info.UserOcid, hasUser, info.PresentConfiguration, "user"); err != nil {
|
||||
// need to check if securityTokenPath is provided, if security token is provided, userOCID can be "".
|
||||
if _, stErr := presentOrError(info.SecurityTokenFilePath, hasSecurityTokenFile, info.PresentConfiguration,
|
||||
"securityTokenPath"); stErr == nil {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p fileConfigurationProvider) KeyFingerprint() (value string, err error) {
|
||||
info, err := p.readAndParseConfigFile()
|
||||
if err != nil {
|
||||
err = fileConfigurationProviderError{err: fmt.Errorf("can not read tenancy configuration due to: %s", err.Error())}
|
||||
return
|
||||
}
|
||||
value, err = presentOrError(info.Fingerprint, hasFingerprint, info.PresentConfiguration, "fingerprint")
|
||||
if err == nil && value == "" {
|
||||
return "", fmt.Errorf("fingerprint can not be empty when reading from config file")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p fileConfigurationProvider) KeyID() (keyID string, err error) {
|
||||
tenancy, err := p.TenancyOCID()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fingerprint, err := p.KeyFingerprint()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
info, err := p.readAndParseConfigFile()
|
||||
if err != nil {
|
||||
err = fileConfigurationProviderError{err: fmt.Errorf("can not read tenancy configuration due to: %s", err.Error())}
|
||||
return
|
||||
}
|
||||
if info.PresentConfiguration&hasUser == hasUser {
|
||||
if info.UserOcid == "" {
|
||||
err = fileConfigurationProviderError{err: fmt.Errorf("user cannot be empty in the config file")}
|
||||
return
|
||||
}
|
||||
return fmt.Sprintf("%s/%s/%s", tenancy, info.UserOcid, fingerprint), nil
|
||||
}
|
||||
filePath, pathErr := presentOrError(info.SecurityTokenFilePath, hasSecurityTokenFile, info.PresentConfiguration, "securityTokenFilePath")
|
||||
if pathErr == nil {
|
||||
rawString, err := getTokenContent(filePath)
|
||||
if err != nil {
|
||||
return "", fileConfigurationProviderError{err: err}
|
||||
}
|
||||
return "ST$" + rawString, nil
|
||||
}
|
||||
err = fileConfigurationProviderError{err: fmt.Errorf("can not read SecurityTokenFilePath from configuration file due to: %s", pathErr.Error())}
|
||||
return
|
||||
}
|
||||
|
||||
func (p fileConfigurationProvider) PrivateRSAKey() (key *rsa.PrivateKey, err error) {
|
||||
info, err := p.readAndParseConfigFile()
|
||||
if err != nil {
|
||||
err = fileConfigurationProviderError{err: fmt.Errorf("can not read tenancy configuration due to: %s", err.Error())}
|
||||
return
|
||||
}
|
||||
|
||||
filePath, err := presentOrError(info.KeyFilePath, hasKeyFile, info.PresentConfiguration, "key file path")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
expandedPath := expandPath(filePath)
|
||||
pemFileContent, err := readFileFromCache(expandedPath)
|
||||
if err != nil {
|
||||
err = fileConfigurationProviderError{err: fmt.Errorf("can not read PrivateKey from configuration file due to: %s", err.Error())}
|
||||
return
|
||||
}
|
||||
|
||||
password := p.PrivateKeyPassword
|
||||
|
||||
if password == "" && ((info.PresentConfiguration & hasPassphrase) == hasPassphrase) {
|
||||
password = info.Passphrase
|
||||
}
|
||||
|
||||
key, err = PrivateKeyFromBytes(pemFileContent, &password)
|
||||
return
|
||||
}
|
||||
|
||||
func (p fileConfigurationProvider) Region() (value string, err error) {
|
||||
info, err := p.readAndParseConfigFile()
|
||||
if err != nil {
|
||||
err = fileConfigurationProviderError{err: fmt.Errorf("can not read region configuration due to: %s", err.Error())}
|
||||
return
|
||||
}
|
||||
|
||||
value, err = presentOrError(info.Region, hasRegion, info.PresentConfiguration, "region")
|
||||
if err != nil {
|
||||
val, error := getRegionFromEnvVar()
|
||||
if error != nil {
|
||||
err = fileConfigurationProviderError{err: fmt.Errorf("region configuration is missing from file, nor for OCI_REGION env var")}
|
||||
return
|
||||
}
|
||||
value = val
|
||||
}
|
||||
|
||||
return canStringBeRegion(value)
|
||||
}
|
||||
|
||||
func (p fileConfigurationProvider) AuthType() (AuthConfig, error) {
|
||||
info, err := p.readAndParseConfigFile()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("can not read tenancy configuration due to: %s", err.Error())
|
||||
return AuthConfig{UnknownAuthenticationType, true, nil}, err
|
||||
}
|
||||
val, _ := presentOrError(info.AuthenticationType, hasAuthenticationType, info.PresentConfiguration, "authentication_type")
|
||||
|
||||
if val == "instance_principal" {
|
||||
if filePath, err := presentOrError(info.DelegationTokenFilePath, hasDelegationTokenFile, info.PresentConfiguration, "delegationTokenFilePath"); err == nil {
|
||||
if delegationToken, err := getTokenContent(filePath); err == nil && delegationToken != "" {
|
||||
Debugf("delegation token content is %s, and error is %s ", delegationToken, err)
|
||||
return AuthConfig{InstancePrincipalDelegationToken, true, &delegationToken}, nil
|
||||
}
|
||||
return AuthConfig{UnknownAuthenticationType, true, nil}, err
|
||||
|
||||
}
|
||||
// normal instance principle
|
||||
return AuthConfig{InstancePrincipal, true, nil}, nil
|
||||
}
|
||||
|
||||
// by default, if no "authentication_type" is provided, just treated as user principle type, and will not return error
|
||||
return AuthConfig{UserPrincipal, true, nil}, nil
|
||||
}
|
||||
|
||||
func getTokenContent(filePath string) (string, error) {
|
||||
expandedPath := expandPath(filePath)
|
||||
tokenFileContent, err := readFile(expandedPath)
|
||||
if err != nil {
|
||||
err = fileConfigurationProviderError{err: fmt.Errorf("can not read token content from configuration file due to: %s", err.Error())}
|
||||
return "", err
|
||||
}
|
||||
return string(tokenFileContent), nil
|
||||
}
|
||||
|
||||
// A configuration provider that look for information in multiple configuration providers
|
||||
type composingConfigurationProvider struct {
|
||||
Providers []ConfigurationProvider
|
||||
}
|
||||
|
||||
// ComposingConfigurationProvider creates a composing configuration provider with the given slice of configuration providers
|
||||
// A composing provider will return the configuration of the first provider that has the required property
|
||||
// if no provider has the property it will return an error.
|
||||
func ComposingConfigurationProvider(providers []ConfigurationProvider) (ConfigurationProvider, error) {
|
||||
if len(providers) == 0 {
|
||||
return nil, fmt.Errorf("providers can not be an empty slice")
|
||||
}
|
||||
|
||||
for i, p := range providers {
|
||||
if p == nil {
|
||||
return nil, fmt.Errorf("provider in position: %d is nil. ComposingConfiurationProvider does not support nil values", i)
|
||||
}
|
||||
}
|
||||
return composingConfigurationProvider{Providers: providers}, nil
|
||||
}
|
||||
|
||||
func (c composingConfigurationProvider) TenancyOCID() (string, error) {
|
||||
for _, p := range c.Providers {
|
||||
val, err := p.TenancyOCID()
|
||||
if err == nil {
|
||||
return val, nil
|
||||
}
|
||||
Debugf("did not find a proper configuration for tenancy, err: %v", err)
|
||||
}
|
||||
return "", fmt.Errorf("did not find a proper configuration for tenancy")
|
||||
}
|
||||
|
||||
func (c composingConfigurationProvider) UserOCID() (string, error) {
|
||||
for _, p := range c.Providers {
|
||||
val, err := p.UserOCID()
|
||||
if err == nil {
|
||||
return val, nil
|
||||
}
|
||||
Debugf("did not find a proper configuration for keyFingerprint, err: %v", err)
|
||||
}
|
||||
return "", fmt.Errorf("did not find a proper configuration for user")
|
||||
}
|
||||
|
||||
func (c composingConfigurationProvider) KeyFingerprint() (string, error) {
|
||||
for _, p := range c.Providers {
|
||||
val, err := p.KeyFingerprint()
|
||||
if err == nil {
|
||||
return val, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("did not find a proper configuration for keyFingerprint")
|
||||
}
|
||||
func (c composingConfigurationProvider) Region() (string, error) {
|
||||
for _, p := range c.Providers {
|
||||
val, err := p.Region()
|
||||
if err == nil {
|
||||
return val, nil
|
||||
}
|
||||
}
|
||||
if val, err := getRegionFromEnvVar(); err == nil {
|
||||
return val, nil
|
||||
}
|
||||
return "", fmt.Errorf("did not find a proper configuration for region, nor for OCI_REGION env var")
|
||||
}
|
||||
|
||||
func (c composingConfigurationProvider) KeyID() (string, error) {
|
||||
for _, p := range c.Providers {
|
||||
val, err := p.KeyID()
|
||||
if err == nil {
|
||||
return val, nil
|
||||
}
|
||||
}
|
||||
return "", fmt.Errorf("did not find a proper configuration for key id")
|
||||
}
|
||||
|
||||
func (c composingConfigurationProvider) PrivateRSAKey() (*rsa.PrivateKey, error) {
|
||||
for _, p := range c.Providers {
|
||||
val, err := p.PrivateRSAKey()
|
||||
if err == nil {
|
||||
return val, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("did not find a proper configuration for private key")
|
||||
}
|
||||
|
||||
func (c composingConfigurationProvider) AuthType() (AuthConfig, error) {
|
||||
// only check the first default fileConfigProvider
|
||||
authConfig, err := c.Providers[0].AuthType()
|
||||
if err == nil && authConfig.AuthType != UnknownAuthenticationType {
|
||||
return authConfig, nil
|
||||
}
|
||||
return AuthConfig{UnknownAuthenticationType, false, nil}, fmt.Errorf("did not find a proper configuration for auth type")
|
||||
}
|
||||
|
||||
func getRegionFromEnvVar() (string, error) {
|
||||
regionEnvVar := "OCI_REGION"
|
||||
if region, existed := os.LookupEnv(regionEnvVar); existed {
|
||||
return region, nil
|
||||
}
|
||||
return "", fmt.Errorf("did not find OCI_REGION env var")
|
||||
}
|
||||
|
||||
type sessionTokenConfigurationProvider struct {
|
||||
fileConfigurationProvider
|
||||
}
|
||||
|
||||
func (p sessionTokenConfigurationProvider) UserOCID() (value string, err error) {
|
||||
info, err := p.readAndParseConfigFile()
|
||||
if err != nil {
|
||||
err = fileConfigurationProviderError{err: fmt.Errorf("can not read the configuration due to: %s", err.Error())}
|
||||
return
|
||||
}
|
||||
// In case of session token-based authentication, userOCID will not be present
|
||||
// need to check if session token path is provided in the configuration
|
||||
if _, stErr := presentOrError(info.SecurityTokenFilePath, hasSecurityTokenFile, info.PresentConfiguration,
|
||||
"securityTokenPath"); stErr == nil {
|
||||
err = nil
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (p sessionTokenConfigurationProvider) KeyID() (keyID string, err error) {
|
||||
_, err = p.TenancyOCID()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = p.KeyFingerprint()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
info, err := p.readAndParseConfigFile()
|
||||
if err != nil {
|
||||
err = fileConfigurationProviderError{err: fmt.Errorf("can not read SessionTokenFilePath configuration due to: %s", err.Error())}
|
||||
return
|
||||
}
|
||||
|
||||
filePath, pathErr := presentOrError(info.SecurityTokenFilePath, hasSecurityTokenFile, info.PresentConfiguration, "securityTokenFilePath")
|
||||
if pathErr == nil {
|
||||
rawString, err := getTokenContent(filePath)
|
||||
if err != nil {
|
||||
return "", fileConfigurationProviderError{err: err}
|
||||
}
|
||||
return "ST$" + rawString, nil
|
||||
}
|
||||
err = fileConfigurationProviderError{err: fmt.Errorf("can not read SessionTokenFilePath from configuration file due to: %s", pathErr.Error())}
|
||||
return
|
||||
}
|
||||
|
||||
// ConfigurationProviderForSessionToken creates a session token configuration provider from a configuration file
|
||||
// by reading the "DEFAULT" profile
|
||||
func ConfigurationProviderForSessionToken(configFilePath, privateKeyPassword string) (ConfigurationProvider, error) {
|
||||
if configFilePath == "" {
|
||||
return nil, fileConfigurationProviderError{err: fmt.Errorf("config file path can not be empty")}
|
||||
}
|
||||
|
||||
return sessionTokenConfigurationProvider{
|
||||
fileConfigurationProvider{
|
||||
ConfigPath: configFilePath,
|
||||
PrivateKeyPassword: privateKeyPassword,
|
||||
Profile: "DEFAULT",
|
||||
configMux: sync.Mutex{}}}, nil
|
||||
}
|
||||
|
||||
// ConfigurationProviderForSessionTokenWithProfile creates a session token configuration provider from a configuration file
|
||||
// by reading the given profile
|
||||
func ConfigurationProviderForSessionTokenWithProfile(configFilePath, profile, privateKeyPassword string) (ConfigurationProvider, error) {
|
||||
if configFilePath == "" {
|
||||
return nil, fileConfigurationProviderError{err: fmt.Errorf("config file path can not be empty")}
|
||||
}
|
||||
|
||||
return sessionTokenConfigurationProvider{
|
||||
fileConfigurationProvider{
|
||||
ConfigPath: configFilePath,
|
||||
PrivateKeyPassword: privateKeyPassword,
|
||||
Profile: profile,
|
||||
configMux: sync.Mutex{}}}, nil
|
||||
}
|
||||
|
||||
func (p sessionTokenConfigurationProvider) Refreshable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// RefreshableConfigurationProvider the interface to identity if the config provider is refreshable
|
||||
type RefreshableConfigurationProvider interface {
|
||||
Refreshable() bool
|
||||
}
|
||||
306
vendor/github.com/oracle/oci-go-sdk/v65/common/errors.go
generated
vendored
Normal file
306
vendor/github.com/oracle/oci-go-sdk/v65/common/errors.go
generated
vendored
Normal file
@@ -0,0 +1,306 @@
|
||||
// Copyright (c) 2016, 2018, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
// This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/sony/gobreaker"
|
||||
)
|
||||
|
||||
// ServiceError models all potential errors generated the service call
|
||||
type ServiceError interface {
|
||||
// The http status code of the error
|
||||
GetHTTPStatusCode() int
|
||||
|
||||
// The human-readable error string as sent by the service
|
||||
GetMessage() string
|
||||
|
||||
// A short error code that defines the error, meant for programmatic parsing.
|
||||
// See https://docs.oracle.com/iaas/Content/API/References/apierrors.htm
|
||||
GetCode() string
|
||||
|
||||
// Unique Oracle-assigned identifier for the request.
|
||||
// If you need to contact Oracle about a particular request, please provide the request ID.
|
||||
GetOpcRequestID() string
|
||||
}
|
||||
|
||||
// ServiceErrorRichInfo models all potential errors generated the service call and contains rich info for debugging purpose
|
||||
type ServiceErrorRichInfo interface {
|
||||
ServiceError
|
||||
// The service this service call is sending to
|
||||
GetTargetService() string
|
||||
|
||||
// The API name this service call is sending to
|
||||
GetOperationName() string
|
||||
|
||||
// The timestamp when this request is made
|
||||
GetTimestamp() SDKTime
|
||||
|
||||
// The endpoint and the Http method of this service call
|
||||
GetRequestTarget() string
|
||||
|
||||
// The client version, in this case the oci go sdk version
|
||||
GetClientVersion() string
|
||||
|
||||
// The API reference doc link for this API, optional and maybe empty
|
||||
GetOperationReferenceLink() string
|
||||
|
||||
// Troubleshooting doc link
|
||||
GetErrorTroubleshootingLink() string
|
||||
}
|
||||
|
||||
// ServiceErrorLocalizationMessage models all potential errors generated the service call and has localized error message info
|
||||
type ServiceErrorLocalizationMessage interface {
|
||||
ServiceErrorRichInfo
|
||||
// The original error message string as sent by the service
|
||||
GetOriginalMessage() string
|
||||
|
||||
// The values to be substituted into the originalMessageTemplate, expressed as a string-to-string map.
|
||||
GetMessageArgument() map[string]string
|
||||
|
||||
// Template in ICU MessageFormat for the human-readable error string in English, but without the values replaced
|
||||
GetOriginalMessageTemplate() string
|
||||
}
|
||||
|
||||
type servicefailure struct {
|
||||
StatusCode int
|
||||
Code string `json:"code,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
OriginalMessage string `json:"originalMessage"`
|
||||
OriginalMessageTemplate string `json:"originalMessageTemplate"`
|
||||
MessageArgument map[string]string `json:"messageArguments"`
|
||||
OpcRequestID string `json:"opc-request-id"`
|
||||
// debugging information
|
||||
TargetService string `json:"target-service"`
|
||||
OperationName string `json:"operation-name"`
|
||||
Timestamp SDKTime `json:"timestamp"`
|
||||
RequestTarget string `json:"request-target"`
|
||||
ClientVersion string `json:"client-version"`
|
||||
|
||||
// troubleshooting guidance
|
||||
OperationReferenceLink string `json:"operation-reference-link"`
|
||||
ErrorTroubleshootingLink string `json:"error-troubleshooting-link"`
|
||||
}
|
||||
|
||||
func newServiceFailureFromResponse(response *http.Response) error {
|
||||
var err error
|
||||
var timestamp SDKTime
|
||||
t, err := tryParsingTimeWithValidFormatsForHeaders([]byte(response.Header.Get("Date")), "Date")
|
||||
|
||||
if err != nil {
|
||||
timestamp = *now()
|
||||
} else {
|
||||
timestamp = sdkTimeFromTime(t)
|
||||
}
|
||||
|
||||
se := servicefailure{
|
||||
StatusCode: response.StatusCode,
|
||||
Code: "BadErrorResponse",
|
||||
OpcRequestID: response.Header.Get("opc-request-id"),
|
||||
Timestamp: timestamp,
|
||||
ClientVersion: defaultSDKMarker + "/" + Version(),
|
||||
RequestTarget: fmt.Sprintf("%s %s", response.Request.Method, response.Request.URL),
|
||||
}
|
||||
|
||||
//If there is an error consume the body, entirely
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
se.Message = fmt.Sprintf("The body of the response was not readable, due to :%s", err.Error())
|
||||
return se
|
||||
}
|
||||
|
||||
err = json.Unmarshal(body, &se)
|
||||
if err != nil {
|
||||
Debugf("Error response could not be parsed due to: %s", err.Error())
|
||||
se.Message = fmt.Sprintf("Failed to parse json from response body due to: %s. With response body %s.", err.Error(), string(body[:]))
|
||||
return se
|
||||
}
|
||||
return se
|
||||
}
|
||||
|
||||
// PostProcessServiceError process the service error after an error is raised and complete it with extra information
|
||||
func PostProcessServiceError(err error, service string, method string, apiReferenceLink string) error {
|
||||
var serviceFailure servicefailure
|
||||
if _, ok := err.(servicefailure); !ok {
|
||||
return err
|
||||
}
|
||||
serviceFailure = err.(servicefailure)
|
||||
serviceFailure.OperationName = method
|
||||
serviceFailure.TargetService = service
|
||||
serviceFailure.ErrorTroubleshootingLink = fmt.Sprintf("https://docs.oracle.com/iaas/Content/API/References/apierrors.htm#apierrors_%v__%v_%s", serviceFailure.StatusCode, serviceFailure.StatusCode, strings.ToLower(serviceFailure.Code))
|
||||
serviceFailure.OperationReferenceLink = apiReferenceLink
|
||||
return serviceFailure
|
||||
}
|
||||
|
||||
func (se servicefailure) Error() string {
|
||||
return fmt.Sprintf(`Error returned by %s Service. Http Status Code: %d. Error Code: %s. Opc request id: %s. Message: %s
|
||||
Operation Name: %s
|
||||
Timestamp: %s
|
||||
Client Version: %s
|
||||
Request Endpoint: %s
|
||||
Troubleshooting Tips: See %s for more information about resolving this error.%s
|
||||
To get more info on the failing request, you can set OCI_GO_SDK_DEBUG env var to info or higher level to log the request/response details.
|
||||
If you are unable to resolve this %s issue, please contact Oracle support and provide them this full error message.`,
|
||||
se.TargetService, se.StatusCode, se.Code, se.OpcRequestID, se.Message, se.OperationName, se.Timestamp, se.ClientVersion, se.RequestTarget, se.ErrorTroubleshootingLink, se.getOperationReferenceMessage(), se.TargetService)
|
||||
}
|
||||
|
||||
func (se servicefailure) getOperationReferenceMessage() string {
|
||||
if se.OperationReferenceLink == "" {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("\nAlso see %s for details on this operation's requirements.", se.OperationReferenceLink)
|
||||
}
|
||||
|
||||
func (se servicefailure) GetHTTPStatusCode() int {
|
||||
return se.StatusCode
|
||||
|
||||
}
|
||||
|
||||
func (se servicefailure) GetMessage() string {
|
||||
return se.Message
|
||||
}
|
||||
|
||||
func (se servicefailure) GetOriginalMessage() string {
|
||||
return se.OriginalMessage
|
||||
}
|
||||
|
||||
func (se servicefailure) GetOriginalMessageTemplate() string {
|
||||
return se.OriginalMessageTemplate
|
||||
}
|
||||
|
||||
func (se servicefailure) GetMessageArgument() map[string]string {
|
||||
return se.MessageArgument
|
||||
}
|
||||
|
||||
func (se servicefailure) GetCode() string {
|
||||
return se.Code
|
||||
}
|
||||
|
||||
func (se servicefailure) GetOpcRequestID() string {
|
||||
return se.OpcRequestID
|
||||
}
|
||||
|
||||
func (se servicefailure) GetTargetService() string {
|
||||
return se.TargetService
|
||||
}
|
||||
|
||||
func (se servicefailure) GetOperationName() string {
|
||||
return se.OperationName
|
||||
}
|
||||
|
||||
func (se servicefailure) GetTimestamp() SDKTime {
|
||||
return se.Timestamp
|
||||
}
|
||||
|
||||
func (se servicefailure) GetRequestTarget() string {
|
||||
return se.RequestTarget
|
||||
}
|
||||
|
||||
func (se servicefailure) GetClientVersion() string {
|
||||
return se.ClientVersion
|
||||
}
|
||||
|
||||
func (se servicefailure) GetOperationReferenceLink() string {
|
||||
return se.OperationReferenceLink
|
||||
}
|
||||
|
||||
func (se servicefailure) GetErrorTroubleshootingLink() string {
|
||||
return se.ErrorTroubleshootingLink
|
||||
}
|
||||
|
||||
// IsServiceError returns false if the error is not service side, otherwise true
|
||||
// additionally it returns an interface representing the ServiceError
|
||||
func IsServiceError(err error) (failure ServiceError, ok bool) {
|
||||
failure, ok = err.(ServiceError)
|
||||
return
|
||||
}
|
||||
|
||||
// IsServiceErrorRichInfo returns false if the error is not service side or is not containing rich info, otherwise true
|
||||
// additionally it returns an interface representing the ServiceErrorRichInfo
|
||||
func IsServiceErrorRichInfo(err error) (failure ServiceErrorRichInfo, ok bool) {
|
||||
failure, ok = err.(ServiceErrorRichInfo)
|
||||
return
|
||||
}
|
||||
|
||||
// IsServiceErrorLocalizationMessage returns false if the error is not service side, otherwise true
|
||||
// additionally it returns an interface representing the ServiceErrorOriginalMessage
|
||||
func IsServiceErrorLocalizationMessage(err error) (failure ServiceErrorLocalizationMessage, ok bool) {
|
||||
failure, ok = err.(ServiceErrorLocalizationMessage)
|
||||
return
|
||||
}
|
||||
|
||||
type deadlineExceededByBackoffError struct{}
|
||||
|
||||
func (deadlineExceededByBackoffError) Error() string {
|
||||
return "now() + computed backoff duration exceeds request deadline"
|
||||
}
|
||||
|
||||
// DeadlineExceededByBackoff is the error returned by Call() when GetNextDuration() returns a time.Duration that would
|
||||
// force the user to wait past the request deadline before re-issuing a request. This enables us to exit early, since
|
||||
// we cannot succeed based on the configured retry policy.
|
||||
var DeadlineExceededByBackoff error = deadlineExceededByBackoffError{}
|
||||
|
||||
// NonSeekableRequestRetryFailure is the error returned when the request is with binary request body, and is configured
|
||||
// retry, but the request body is not retryable
|
||||
type NonSeekableRequestRetryFailure struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (ne NonSeekableRequestRetryFailure) Error() string {
|
||||
if ne.err == nil {
|
||||
return "Unable to perform Retry on this request body type, which did not implement seek() interface"
|
||||
}
|
||||
return fmt.Sprintf("%s. Unable to perform Retry on this request body type, which did not implement seek() interface", ne.err.Error())
|
||||
}
|
||||
|
||||
// IsNetworkError validates if an error is a net.Error and check if it's temporary or timeout
|
||||
func IsNetworkError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if errors.Is(err, syscall.ECONNRESET) {
|
||||
return true
|
||||
}
|
||||
|
||||
if r, ok := err.(net.Error); ok && (r.Timeout() || strings.Contains(err.Error(), "net/http: HTTP/1.x transport connection broken")) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsCircuitBreakerError validates if an error's text is Open state ErrOpenState or HalfOpen state ErrTooManyRequests
|
||||
func IsCircuitBreakerError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if err.Error() == gobreaker.ErrOpenState.Error() || err.Error() == gobreaker.ErrTooManyRequests.Error() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getCircuitBreakerError(request *http.Request, err error, cbr *OciCircuitBreaker) error {
|
||||
cbErr := fmt.Errorf("%s, so this request was not sent to the %s service.\n\n The circuit breaker was opened because the %s service failed too many times recently. "+
|
||||
"Because the circuit breaker has been opened, requests within a %.2f second window of when the circuit breaker opened will not be sent to the %s service.\n\n"+
|
||||
"URL which circuit breaker prevented request to - %s \n Circuit Breaker Info \n Name - %s \n State - %s \n\n Errors from %s service which opened the circuit breaker:\n\n%s",
|
||||
err, cbr.Cbst.serviceName, cbr.Cbst.serviceName, cbr.Cbst.openStateWindow.Seconds(), cbr.Cbst.serviceName, request.URL.Host+request.URL.Path, cbr.Cbst.name, cbr.Cb.State().String(), cbr.Cbst.serviceName, cbr.GetHistory())
|
||||
return cbErr
|
||||
}
|
||||
|
||||
// StatErrCode is a type which wraps error's statusCode and errorCode from service end
|
||||
type StatErrCode struct {
|
||||
statusCode int
|
||||
errorCode string
|
||||
}
|
||||
467
vendor/github.com/oracle/oci-go-sdk/v65/common/eventual_consistency.go
generated
vendored
Normal file
467
vendor/github.com/oracle/oci-go-sdk/v65/common/eventual_consistency.go
generated
vendored
Normal file
@@ -0,0 +1,467 @@
|
||||
// Copyright (c) 2016, 2018, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
// This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/gofrs/flock"
|
||||
)
|
||||
|
||||
const (
|
||||
// OciGoSdkEcConfigEnvVarName contains the name of the environment variable that can be used to configure the eventual consistency (EC) communication mode.
|
||||
// Allowed values for environment variable:
|
||||
// 1. OCI_GO_SDK_EC_CONFIG = "file,/path/to/shared/timestamp/file"
|
||||
// 2. OCI_GO_SDK_EC_CONFIG = "inprocess"
|
||||
// 3. absent -- same as OCI_GO_SDK_EC_CONFIG = "inprocess"
|
||||
OciGoSdkEcConfigEnvVarName string = "OCI_GO_SDK_EC_CONFIG"
|
||||
)
|
||||
|
||||
//
|
||||
// Eventual consistency communication mode
|
||||
//
|
||||
|
||||
// EcMode is the eventual consistency (EC) communication mode used.
|
||||
type EcMode int64
|
||||
|
||||
const (
|
||||
// Uninitialized means the EC communication mode has not been set yet.
|
||||
Uninitialized EcMode = iota // 0
|
||||
|
||||
// InProcess is the default EC communication mode which only communicates the end-of-window timestamp inside the same process.
|
||||
InProcess
|
||||
|
||||
// File is the EC communication mode that uses a file to communicate the end-of-window timestamp using a file visible across processes.
|
||||
// Locking is performed using a lock file.
|
||||
File
|
||||
)
|
||||
|
||||
var (
|
||||
affectedByEventualConsistencyRetryStatusCodeMap = map[StatErrCode]bool{
|
||||
{400, "RelatedResourceNotAuthorizedOrNotFound"}: true,
|
||||
{404, "NotAuthorizedOrNotFound"}: true,
|
||||
{409, "NotAuthorizedOrResourceAlreadyExists"}: true,
|
||||
{409, "ResourceAlreadyExists"}: true,
|
||||
{400, "InsufficientServicePermissions"}: true,
|
||||
{400, "ResourceDisabled"}: true,
|
||||
}
|
||||
)
|
||||
|
||||
// IsErrorAffectedByEventualConsistency returns true if the error is affected by eventual consistency.
|
||||
func IsErrorAffectedByEventualConsistency(Error error) bool {
|
||||
if err, ok := IsServiceError(Error); ok {
|
||||
return affectedByEventualConsistencyRetryStatusCodeMap[StatErrCode{err.GetHTTPStatusCode(), err.GetCode()}]
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getEcMode(mode string) EcMode {
|
||||
var lmode = strings.ToLower(mode)
|
||||
switch lmode {
|
||||
case "file":
|
||||
return File
|
||||
case "inprocess":
|
||||
return InProcess
|
||||
}
|
||||
ecLogf("%s: Unknown ec mode '%s', assuming 'inprocess'", OciGoSdkEcConfigEnvVarName, mode)
|
||||
return InProcess
|
||||
}
|
||||
|
||||
// EventuallyConsistentContext contains the information about the end of the eventually consistent window.
|
||||
type EventuallyConsistentContext struct {
|
||||
// memory-based
|
||||
endOfWindow atomic.Value
|
||||
lock sync.RWMutex
|
||||
timeNowProvider func() time.Time
|
||||
|
||||
// mode selector
|
||||
ecMode EcMode
|
||||
|
||||
// file-based
|
||||
|
||||
// timestampFileName and timestampLockFile should be set to files that
|
||||
// are accessible by all processes that need to share information about
|
||||
// eventual consistency.
|
||||
// A sensible choice are files inside the temp directory, as returned by os.TempDir()
|
||||
timestampFileName *string
|
||||
timestampFileLock *flock.Flock
|
||||
|
||||
// lock and unlock functions
|
||||
readLock func(e *EventuallyConsistentContext) error
|
||||
readUnlock func(e *EventuallyConsistentContext) error
|
||||
writeLock func(e *EventuallyConsistentContext) error
|
||||
writeUnlock func(e *EventuallyConsistentContext) error
|
||||
|
||||
// get/set functions
|
||||
getEndOfWindowUnsynchronized func(e *EventuallyConsistentContext) (*time.Time, error)
|
||||
setEndOfWindowUnsynchronized func(e *EventuallyConsistentContext, newEndOfWindowTime *time.Time) error
|
||||
}
|
||||
|
||||
// newEcContext creates a new EC context based on the OCI_GO_SDK_EC_CONFIG environment variable.
|
||||
func newEcContext() *EventuallyConsistentContext {
|
||||
ecConfig, ecConfigProvided := os.LookupEnv(OciGoSdkEcConfigEnvVarName)
|
||||
if !ecConfigProvided {
|
||||
ecConfig = ""
|
||||
}
|
||||
|
||||
commaIndex := strings.Index(ecConfig, ",")
|
||||
var ecConfigMode = ecConfig
|
||||
var ecConfigRest = ""
|
||||
if commaIndex >= 0 {
|
||||
ecConfigMode = ecConfig[:commaIndex]
|
||||
ecConfigRest = ecConfig[commaIndex+1:]
|
||||
}
|
||||
ecMode := getEcMode(ecConfigMode)
|
||||
|
||||
switch ecMode {
|
||||
case File:
|
||||
if len(ecConfigRest) < 1 {
|
||||
ecLogf("%s: Expected file name after comma for 'File' mode ('file,/path/to/file'), was: '%s'", OciGoSdkEcConfigEnvVarName, ecConfig)
|
||||
return nil
|
||||
}
|
||||
return newEcContextFile(ecConfigRest)
|
||||
}
|
||||
|
||||
return newEcContextInProcess()
|
||||
}
|
||||
|
||||
// newEcContextInProcess creates a new in-process EC context.
|
||||
func newEcContextInProcess() *EventuallyConsistentContext {
|
||||
ecContext := EventuallyConsistentContext{
|
||||
ecMode: InProcess,
|
||||
readLock: ecInProcessReadLock,
|
||||
readUnlock: ecInProcessReadUnlock,
|
||||
writeLock: ecInProcessWriteLock,
|
||||
writeUnlock: ecInProcessWriteUnlock,
|
||||
getEndOfWindowUnsynchronized: ecInProcessGetEndOfWindowUnsynchronized,
|
||||
setEndOfWindowUnsynchronized: ecInProcessSetEndOfWindowUnsynchronized,
|
||||
timeNowProvider: func() time.Time { return time.Now() },
|
||||
}
|
||||
return &ecContext
|
||||
}
|
||||
|
||||
// newEcContextFile creates a new EC context kept in a file.
|
||||
// timestampFileName should be set to a file accessible by all processes that
|
||||
// need to share information about eventual consistency.
|
||||
// A sensible choice are files inside the temp directory, as returned by os.TempDir()
|
||||
// The lock file will use the same name, with the suffix ".lock" added.
|
||||
func newEcContextFile(timestampFileName string) *EventuallyConsistentContext {
|
||||
timestampLockFileName := timestampFileName + ".lock"
|
||||
ecContext := EventuallyConsistentContext{
|
||||
ecMode: File,
|
||||
readLock: ecFileReadLock,
|
||||
readUnlock: ecFileReadUnlock,
|
||||
writeLock: ecFileWriteLock,
|
||||
writeUnlock: ecFileWriteUnlock,
|
||||
getEndOfWindowUnsynchronized: ecFileGetEndOfWindowUnsynchronized,
|
||||
setEndOfWindowUnsynchronized: ecFileSetEndOfWindowUnsynchronized,
|
||||
timeNowProvider: func() time.Time { return time.Now() },
|
||||
timestampFileName: ×tampFileName,
|
||||
timestampFileLock: flock.New(timestampLockFileName),
|
||||
}
|
||||
ecDebugf("%s: Using file modification time of file '%s' and lock file '%s'", OciGoSdkEcConfigEnvVarName, *ecContext.timestampFileName, timestampLockFileName)
|
||||
return &ecContext
|
||||
}
|
||||
|
||||
// InitializeEcContextFromEnvVar initializes the EcContext variable as configured
|
||||
// in the OCI_GO_SDK_EC_CONFIG environment variable.
|
||||
func InitializeEcContextFromEnvVar() {
|
||||
EcContext = newEcContext()
|
||||
}
|
||||
|
||||
// InitializeEcContextInProcess initializes the EcContext variable to be in-process only.
|
||||
func InitializeEcContextInProcess() {
|
||||
EcContext = newEcContextInProcess()
|
||||
}
|
||||
|
||||
// InitializeEcContextFile initializes the EcContext variable to be kept in a timestamp file,
|
||||
// protected by a lock file.
|
||||
// timestampFileName should be set to a file accessible by all processes that
|
||||
// need to share information about eventual consistency.
|
||||
// A sensible choice are files inside the temp directory, as returned by os.TempDir()
|
||||
// The lock file will use the same name, with the suffix ".lock" added.
|
||||
func InitializeEcContextFile(timestampFileName string) {
|
||||
EcContext = newEcContextFile(timestampFileName)
|
||||
}
|
||||
|
||||
//
|
||||
// InProcess functions
|
||||
//
|
||||
|
||||
func ecInProcessReadLock(e *EventuallyConsistentContext) error {
|
||||
e.lock.RLock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func ecInProcessReadUnlock(e *EventuallyConsistentContext) error {
|
||||
e.lock.RUnlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func ecInProcessWriteLock(e *EventuallyConsistentContext) error {
|
||||
e.lock.Lock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func ecInProcessWriteUnlock(e *EventuallyConsistentContext) error {
|
||||
e.lock.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
// ecInProcessGetEndOfWindowUnsynchronized returns the end time of an eventually consistent window,
|
||||
// or nil if no eventually consistent requests were made.
|
||||
// There is no mutex synchronization.
|
||||
func ecInProcessGetEndOfWindowUnsynchronized(e *EventuallyConsistentContext) (*time.Time, error) {
|
||||
untyped := e.endOfWindow.Load() // returns nil if there has been no call to Store for this Value
|
||||
if untyped == nil {
|
||||
return (*time.Time)(nil), nil
|
||||
}
|
||||
t := untyped.(*time.Time)
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
// ecInProcessSetEndOfWindowUnsynchronized sets the end time of the eventually consistent window.
|
||||
// There is no mutex synchronization.
|
||||
func ecInProcessSetEndOfWindowUnsynchronized(e *EventuallyConsistentContext, newEndOfWindowTime *time.Time) error {
|
||||
e.endOfWindow.Store(newEndOfWindowTime) // atomically replace the current object with the new one
|
||||
return nil
|
||||
}
|
||||
|
||||
//
|
||||
// File functions
|
||||
//
|
||||
|
||||
func ecFileReadLock(e *EventuallyConsistentContext) error {
|
||||
return e.timestampFileLock.RLock()
|
||||
}
|
||||
|
||||
func ecFileReadUnlock(e *EventuallyConsistentContext) error {
|
||||
return e.timestampFileLock.Unlock()
|
||||
}
|
||||
|
||||
func ecFileWriteLock(e *EventuallyConsistentContext) error {
|
||||
return e.timestampFileLock.Lock()
|
||||
}
|
||||
|
||||
func ecFileWriteUnlock(e *EventuallyConsistentContext) error {
|
||||
return e.timestampFileLock.Unlock()
|
||||
}
|
||||
|
||||
// ecFileGetEndOfWindowUnsynchronized returns the end time of an eventually consistent window,
|
||||
// or nil if no eventually consistent requests were made.
|
||||
// There is no mutex synchronization.
|
||||
func ecFileGetEndOfWindowUnsynchronized(e *EventuallyConsistentContext) (*time.Time, error) {
|
||||
file, err := os.Stat(*e.timestampFileName)
|
||||
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
ecDebugf("%s: File '%s' does not exist, meaning no EC in effect", OciGoSdkEcConfigEnvVarName, *e.timestampFileName)
|
||||
return (*time.Time)(nil), nil
|
||||
}
|
||||
if err != nil {
|
||||
ecLogf("%s: Error getting modified time from file '%s', assuming no EC in effect: %s", OciGoSdkEcConfigEnvVarName, *e.timestampFileName, err)
|
||||
return (*time.Time)(nil), err
|
||||
}
|
||||
|
||||
t := file.ModTime()
|
||||
ecDebugf("%s: Read modified time of file '%s' as '%s'", OciGoSdkEcConfigEnvVarName, *e.timestampFileName, t)
|
||||
|
||||
return &t, nil
|
||||
}
|
||||
|
||||
// ecFileSetEndOfWindowUnsynchronized sets the end time of the eventually consistent window.
|
||||
// There is no mutex synchronization.
|
||||
func ecFileSetEndOfWindowUnsynchronized(e *EventuallyConsistentContext, newEndOfWindowTime *time.Time) error {
|
||||
if newEndOfWindowTime != nil {
|
||||
ecDebugf("%s: Updating modified time of file '%s' to '%s'", OciGoSdkEcConfigEnvVarName, *e.timestampFileName, *newEndOfWindowTime)
|
||||
} else {
|
||||
ecDebugf("%s: Updating modified time of file '%s' to <nil>", OciGoSdkEcConfigEnvVarName, *e.timestampFileName)
|
||||
}
|
||||
|
||||
if newEndOfWindowTime == nil {
|
||||
err := os.Remove(*e.timestampFileName)
|
||||
if err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
ecLogf("%s: Error removing file '%s', may draw wrong EC conflusions: %s", OciGoSdkEcConfigEnvVarName, *e.timestampFileName, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
atime := time.Now()
|
||||
var err = os.Chtimes(*e.timestampFileName, atime, *newEndOfWindowTime)
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
_, createErr := os.Create(*e.timestampFileName)
|
||||
if createErr != nil {
|
||||
ecLogf("%s: Error creating file '%s', will have to assume no EC in effect: %s", OciGoSdkEcConfigEnvVarName, *e.timestampFileName, createErr)
|
||||
return createErr
|
||||
}
|
||||
err = os.Chtimes(*e.timestampFileName, atime, *newEndOfWindowTime)
|
||||
}
|
||||
if err != nil {
|
||||
ecLogf("%s: Error changing modified time for file '%s', will have to assume no EC in effect: %s", OciGoSdkEcConfigEnvVarName, *e.timestampFileName, err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//
|
||||
// General functions for EC window handling, for all EC communication modes
|
||||
//
|
||||
|
||||
// GetEndOfWindow returns the end time an eventually consistent window,
|
||||
// or nil if no eventually consistent requests were made
|
||||
func (e *EventuallyConsistentContext) GetEndOfWindow() *time.Time {
|
||||
e.readLock(e) // synchronize with potential writers
|
||||
defer e.readUnlock(e)
|
||||
|
||||
endOfWindowTime, _ := e.getEndOfWindowUnsynchronized(e)
|
||||
|
||||
// TODO: this is noisy logging, consider removing
|
||||
if endOfWindowTime != nil {
|
||||
ecDebugln(fmt.Sprintf("EcContext.GetEndOfWindow returns %s", endOfWindowTime))
|
||||
} else {
|
||||
ecDebugln("EcContext.GetEndOfWindow returns <nil>")
|
||||
}
|
||||
|
||||
return endOfWindowTime
|
||||
}
|
||||
|
||||
// UpdateEndOfWindow sets the end time of the eventually consistent window the specified
|
||||
// duration into the future
|
||||
func (e *EventuallyConsistentContext) UpdateEndOfWindow(windowSize time.Duration) *time.Time {
|
||||
e.writeLock(e) // synchronize with other potential writers
|
||||
defer e.writeUnlock(e)
|
||||
|
||||
currentEndOfWindowTime, _ := e.getEndOfWindowUnsynchronized(e)
|
||||
var newEndOfWindowTime = e.timeNowProvider().Add(windowSize)
|
||||
if currentEndOfWindowTime == nil || newEndOfWindowTime.After(*currentEndOfWindowTime) {
|
||||
e.setEndOfWindowUnsynchronized(e, &newEndOfWindowTime)
|
||||
|
||||
// TODO: this is noisy logging, consider removing
|
||||
ecDebugln(fmt.Sprintf("EcContext.UpdateEndOfWindow to %s", newEndOfWindowTime))
|
||||
|
||||
return &newEndOfWindowTime
|
||||
}
|
||||
return currentEndOfWindowTime
|
||||
}
|
||||
|
||||
// setEndTimeOfEventuallyConsistentWindow sets the last time an eventually consistent request was made
|
||||
// to the specified time
|
||||
func (e *EventuallyConsistentContext) setEndOfWindow(newTime *time.Time) *time.Time {
|
||||
e.writeLock(e) // synchronize with other potential writers
|
||||
defer e.writeUnlock(e)
|
||||
|
||||
e.setEndOfWindowUnsynchronized(e, newTime)
|
||||
|
||||
// TODO: this is noisy logging, consider removing
|
||||
if newTime != nil {
|
||||
ecDebugln(fmt.Sprintf("EcContext.setEndOfWindow to %s", *newTime))
|
||||
} else {
|
||||
ecDebugln("EcContext.setEndOfWindow to <nil>")
|
||||
}
|
||||
|
||||
return newTime
|
||||
}
|
||||
|
||||
// EcContext contains the information about the end of the eventually consistent window for this process.
|
||||
var EcContext = newEcContext()
|
||||
|
||||
//
|
||||
// Logging helpers
|
||||
//
|
||||
|
||||
// getGID returns the Goroutine id. This is purely for logging and debugging.
|
||||
// See https://blog.sgmansfield.com/2015/12/goroutine-ids/
|
||||
func getGID() uint64 {
|
||||
b := make([]byte, 64)
|
||||
b = b[:runtime.Stack(b, false)]
|
||||
b = bytes.TrimPrefix(b, []byte("goroutine "))
|
||||
b = b[:bytes.IndexByte(b, ' ')]
|
||||
n, _ := strconv.ParseUint(string(b), 10, 64)
|
||||
return n
|
||||
}
|
||||
|
||||
// some of these errors happen so early, defaultLogger may not have been
|
||||
// initialized yet.
|
||||
func initLogIfNecessary() {
|
||||
if defaultLogger == nil {
|
||||
l, _ := NewSDKLogger()
|
||||
SetSDKLogger(l)
|
||||
}
|
||||
}
|
||||
|
||||
// Debugf logs v with the provided format if debug mode is set.
|
||||
// There is no mutex synchronization. You should have acquired e.lock first.
|
||||
func ecDebugf(format string, v ...interface{}) {
|
||||
defer func() {
|
||||
// recover from panic if one occured.
|
||||
if recover() != nil {
|
||||
Debugln("ecDebugf failed")
|
||||
}
|
||||
}()
|
||||
|
||||
str := fmt.Sprintf(format, v...)
|
||||
|
||||
initLogIfNecessary()
|
||||
|
||||
// prefix message with "(pid=25140, gid=5)"
|
||||
Debugf("(pid=%d, gid=%d) %s", os.Getpid(), getGID(), str)
|
||||
}
|
||||
|
||||
// Debug logs v if debug mode is set.
|
||||
// There is no mutex synchronization. You should have acquired e.lock first.
|
||||
func ecDebug(v ...interface{}) {
|
||||
defer func() {
|
||||
// recover from panic if one occured.
|
||||
if recover() != nil {
|
||||
Debugln("ecDebug failed")
|
||||
}
|
||||
}()
|
||||
|
||||
initLogIfNecessary()
|
||||
|
||||
// prefix message with "(pid=25140, gid=5)"
|
||||
Debug(append([]interface{}{"(pid=", os.Getpid(), ", gid=", getGID(), ") "}, v...)...)
|
||||
}
|
||||
|
||||
// Debugln logs v appending a new line if debug mode is set
|
||||
// There is no mutex synchronization. You should have acquired e.lock first.
|
||||
func ecDebugln(v ...interface{}) {
|
||||
defer func() {
|
||||
// recover from panic if one occured.
|
||||
if recover() != nil {
|
||||
Debugln("ecDebugln failed")
|
||||
}
|
||||
}()
|
||||
|
||||
initLogIfNecessary()
|
||||
|
||||
// prefix message with "(pid=25140, gid=5)"
|
||||
Debugln(append([]interface{}{"(pid=", os.Getpid(), ", gid=", getGID(), ") "}, v...)...)
|
||||
}
|
||||
|
||||
// Logf logs v with the provided format if info mode is set.
|
||||
// There is no mutex synchronization. You should have acquired e.lock first.
|
||||
func ecLogf(format string, v ...interface{}) {
|
||||
defer func() {
|
||||
// recover from panic if one occured.
|
||||
if recover() != nil {
|
||||
Debugln("ecLogf failed")
|
||||
}
|
||||
}()
|
||||
|
||||
initLogIfNecessary()
|
||||
|
||||
str := fmt.Sprintf(format, v...)
|
||||
// prefix message with "(pid=25140, gid=5)"
|
||||
Logf("(pid=%d, gid=%d) %s", os.Getpid(), getGID(), str)
|
||||
}
|
||||
308
vendor/github.com/oracle/oci-go-sdk/v65/common/helpers.go
generated
vendored
Normal file
308
vendor/github.com/oracle/oci-go-sdk/v65/common/helpers.go
generated
vendored
Normal file
@@ -0,0 +1,308 @@
|
||||
// Copyright (c) 2016, 2018, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
// This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
|
||||
|
||||
//lint:file-ignore SA1019 older versions of staticcheck (those compatible with Golang 1.17) falsely flag x509.IsEncryptedPEMBlock and x509.DecryptPEMBlock.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/textproto"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/youmark/pkcs8"
|
||||
)
|
||||
|
||||
// String returns a pointer to the provided string
|
||||
func String(value string) *string {
|
||||
return &value
|
||||
}
|
||||
|
||||
// Int returns a pointer to the provided int
|
||||
func Int(value int) *int {
|
||||
return &value
|
||||
}
|
||||
|
||||
// Int64 returns a pointer to the provided int64
|
||||
func Int64(value int64) *int64 {
|
||||
return &value
|
||||
}
|
||||
|
||||
// Uint returns a pointer to the provided uint
|
||||
func Uint(value uint) *uint {
|
||||
return &value
|
||||
}
|
||||
|
||||
// Float32 returns a pointer to the provided float32
|
||||
func Float32(value float32) *float32 {
|
||||
return &value
|
||||
}
|
||||
|
||||
// Float64 returns a pointer to the provided float64
|
||||
func Float64(value float64) *float64 {
|
||||
return &value
|
||||
}
|
||||
|
||||
// Bool returns a pointer to the provided bool
|
||||
func Bool(value bool) *bool {
|
||||
return &value
|
||||
}
|
||||
|
||||
// PointerString prints the values of pointers in a struct
|
||||
// Producing a human friendly string for an struct with pointers.
|
||||
// useful when debugging the values of a struct
|
||||
func PointerString(datastruct interface{}) (representation string) {
|
||||
val := reflect.ValueOf(datastruct)
|
||||
typ := reflect.TypeOf(datastruct)
|
||||
all := make([]string, 2)
|
||||
all = append(all, "{")
|
||||
for i := 0; i < typ.NumField(); i++ {
|
||||
sf := typ.Field(i)
|
||||
|
||||
//unexported
|
||||
if sf.PkgPath != "" && !sf.Anonymous {
|
||||
continue
|
||||
}
|
||||
|
||||
sv := val.Field(i)
|
||||
stringValue := ""
|
||||
if isNil(sv) {
|
||||
stringValue = fmt.Sprintf("%s=<nil>", sf.Name)
|
||||
} else {
|
||||
if sv.Type().Kind() == reflect.Ptr {
|
||||
sv = sv.Elem()
|
||||
}
|
||||
stringValue = fmt.Sprintf("%s=%v", sf.Name, sv)
|
||||
}
|
||||
all = append(all, stringValue)
|
||||
}
|
||||
all = append(all, "}")
|
||||
representation = strings.TrimSpace(strings.Join(all, " "))
|
||||
return
|
||||
}
|
||||
|
||||
// SDKTime a struct that parses/renders to/from json using RFC339 date-time information
|
||||
type SDKTime struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
// SDKDate a struct that parses/renders to/from json using only date information
|
||||
type SDKDate struct {
|
||||
//Date date information
|
||||
Date time.Time
|
||||
}
|
||||
|
||||
func sdkTimeFromTime(t time.Time) SDKTime {
|
||||
return SDKTime{t}
|
||||
}
|
||||
|
||||
func sdkDateFromTime(t time.Time) SDKDate {
|
||||
return SDKDate{Date: t}
|
||||
}
|
||||
|
||||
func formatTime(t SDKTime) string {
|
||||
return t.Format(sdkTimeFormat)
|
||||
}
|
||||
|
||||
func formatDate(t SDKDate) string {
|
||||
return t.Date.Format(sdkDateFormat)
|
||||
}
|
||||
|
||||
func now() *SDKTime {
|
||||
t := SDKTime{time.Now()}
|
||||
return &t
|
||||
}
|
||||
|
||||
var timeType = reflect.TypeOf(SDKTime{})
|
||||
var timeTypePtr = reflect.TypeOf(&SDKTime{})
|
||||
|
||||
var sdkDateType = reflect.TypeOf(SDKDate{})
|
||||
var sdkDateTypePtr = reflect.TypeOf(&SDKDate{})
|
||||
|
||||
// Formats for sdk supported time representations
|
||||
const sdkTimeFormat = time.RFC3339Nano
|
||||
const rfc1123OptionalLeadingDigitsInDay = "Mon, _2 Jan 2006 15:04:05 MST"
|
||||
const sdkDateFormat = "2006-01-02"
|
||||
|
||||
func tryParsingTimeWithValidFormatsForHeaders(data []byte, headerName string) (t time.Time, err error) {
|
||||
header := strings.ToLower(headerName)
|
||||
switch header {
|
||||
case "lastmodified", "date":
|
||||
t, err = tryParsing(data, time.RFC3339Nano, time.RFC3339, time.RFC1123, rfc1123OptionalLeadingDigitsInDay, time.RFC850, time.ANSIC)
|
||||
return
|
||||
default: //By default we parse with RFC3339
|
||||
t, err = time.Parse(sdkTimeFormat, string(data))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func tryParsing(data []byte, layouts ...string) (tm time.Time, err error) {
|
||||
datestring := string(data)
|
||||
for _, l := range layouts {
|
||||
tm, err = time.Parse(l, datestring)
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
err = fmt.Errorf("could not parse time: %s with formats: %s", datestring, layouts[:])
|
||||
return
|
||||
}
|
||||
|
||||
// String returns string representation of SDKDate
|
||||
func (t *SDKDate) String() string {
|
||||
return t.Date.Format(sdkDateFormat)
|
||||
}
|
||||
|
||||
// NewSDKDateFromString parses the dateString into SDKDate
|
||||
func NewSDKDateFromString(dateString string) (*SDKDate, error) {
|
||||
parsedTime, err := time.Parse(sdkDateFormat, dateString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &SDKDate{Date: parsedTime}, nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals from json
|
||||
func (t *SDKTime) UnmarshalJSON(data []byte) (e error) {
|
||||
s := string(data)
|
||||
if s == "null" {
|
||||
t.Time = time.Time{}
|
||||
} else {
|
||||
//Try parsing with RFC3339
|
||||
t.Time, e = time.Parse(`"`+sdkTimeFormat+`"`, string(data))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalJSON marshals to JSON
|
||||
func (t *SDKTime) MarshalJSON() (buff []byte, e error) {
|
||||
s := t.Format(sdkTimeFormat)
|
||||
buff = []byte(`"` + s + `"`)
|
||||
return
|
||||
}
|
||||
|
||||
// UnmarshalJSON unmarshals from json
|
||||
func (t *SDKDate) UnmarshalJSON(data []byte) (e error) {
|
||||
if string(data) == `"null"` {
|
||||
t.Date = time.Time{}
|
||||
return
|
||||
}
|
||||
|
||||
t.Date, e = tryParsing(data,
|
||||
strconv.Quote(sdkDateFormat),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// MarshalJSON marshals to JSON
|
||||
func (t *SDKDate) MarshalJSON() (buff []byte, e error) {
|
||||
s := t.Date.Format(sdkDateFormat)
|
||||
buff = []byte(strconv.Quote(s))
|
||||
return
|
||||
}
|
||||
|
||||
// PrivateKeyFromBytes is a helper function that will produce a RSA private
|
||||
// key from bytes. This function is deprecated in favour of PrivateKeyFromBytesWithPassword
|
||||
// Deprecated
|
||||
func PrivateKeyFromBytes(pemData []byte, password *string) (key *rsa.PrivateKey, e error) {
|
||||
if password == nil {
|
||||
return PrivateKeyFromBytesWithPassword(pemData, nil)
|
||||
}
|
||||
|
||||
return PrivateKeyFromBytesWithPassword(pemData, []byte(*password))
|
||||
}
|
||||
|
||||
// PrivateKeyFromBytesWithPassword is a helper function that will produce a RSA private
|
||||
// key from bytes and a password.
|
||||
func PrivateKeyFromBytesWithPassword(pemData, password []byte) (key *rsa.PrivateKey, e error) {
|
||||
pemBlock, _ := pem.Decode(pemData)
|
||||
if pemBlock == nil {
|
||||
e = fmt.Errorf("PEM data was not found in buffer")
|
||||
return
|
||||
}
|
||||
|
||||
decrypted := pemBlock.Bytes
|
||||
// Support for encrypted PKCS8 format, this format can not be handled by x509.IsEncryptedPEMBlock func
|
||||
if key, e = pkcs8.ParsePKCS8PrivateKeyRSA(pemBlock.Bytes, password); key != nil {
|
||||
return
|
||||
}
|
||||
// if pemBlock.Type == "ENCRYPTED PRIVATE KEY" {
|
||||
// return pkcs8.ParsePKCS8PrivateKeyRSA(pemData, password)
|
||||
// }
|
||||
if x509.IsEncryptedPEMBlock(pemBlock) {
|
||||
if password == nil {
|
||||
return nil, errors.New("private key password is required for encrypted private keys")
|
||||
}
|
||||
|
||||
if decrypted, e = x509.DecryptPEMBlock(pemBlock, password); e != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
key, e = parsePKCSPrivateKey(decrypted)
|
||||
return
|
||||
}
|
||||
|
||||
// ParsePrivateKey using PKCS1 or PKCS8
|
||||
func parsePKCSPrivateKey(decryptedKey []byte) (*rsa.PrivateKey, error) {
|
||||
if key, err := x509.ParsePKCS1PrivateKey(decryptedKey); err == nil {
|
||||
return key, nil
|
||||
}
|
||||
if key, err := x509.ParsePKCS8PrivateKey(decryptedKey); err == nil {
|
||||
switch key := key.(type) {
|
||||
case *rsa.PrivateKey:
|
||||
return key, nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupportesd private key type in PKCS8 wrapping")
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("failed to parse private key")
|
||||
}
|
||||
|
||||
// parseContentLength trims whitespace from cl and returns -1 if can't purse uint, or the value if it's no less than 0
|
||||
func parseContentLength(cl string) int64 {
|
||||
cl = textproto.TrimString(cl)
|
||||
n, err := strconv.ParseUint(cl, 10, 63)
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
return int64(n)
|
||||
}
|
||||
|
||||
func generateRandUUID() (string, error) {
|
||||
b := make([]byte, 16)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
uuid := fmt.Sprintf("%x%x%x%x%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
|
||||
|
||||
return uuid, nil
|
||||
}
|
||||
|
||||
func makeACopy(original []string) []string {
|
||||
tmp := make([]string, len(original))
|
||||
copy(tmp, original)
|
||||
return tmp
|
||||
}
|
||||
|
||||
// IsEnvVarFalse is used for checking if an environment variable is explicitly set to false, otherwise would set it true by default
|
||||
func IsEnvVarFalse(envVarKey string) bool {
|
||||
val, existed := os.LookupEnv(envVarKey)
|
||||
return existed && strings.ToLower(val) == "false"
|
||||
}
|
||||
|
||||
// IsEnvVarTrue is used for checking if an environment variable is explicitly set to true, otherwise would set it true by default
|
||||
func IsEnvVarTrue(envVarKey string) bool {
|
||||
val, existed := os.LookupEnv(envVarKey)
|
||||
return existed && strings.ToLower(val) == "true"
|
||||
}
|
||||
1144
vendor/github.com/oracle/oci-go-sdk/v65/common/http.go
generated
vendored
Normal file
1144
vendor/github.com/oracle/oci-go-sdk/v65/common/http.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
270
vendor/github.com/oracle/oci-go-sdk/v65/common/http_signer.go
generated
vendored
Normal file
270
vendor/github.com/oracle/oci-go-sdk/v65/common/http_signer.go
generated
vendored
Normal file
@@ -0,0 +1,270 @@
|
||||
// Copyright (c) 2016, 2018, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
// This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// HTTPRequestSigner the interface to sign a request
|
||||
type HTTPRequestSigner interface {
|
||||
Sign(r *http.Request) error
|
||||
}
|
||||
|
||||
// KeyProvider interface that wraps information about the key's account owner
|
||||
type KeyProvider interface {
|
||||
PrivateRSAKey() (*rsa.PrivateKey, error)
|
||||
KeyID() (string, error)
|
||||
}
|
||||
|
||||
const signerVersion = "1"
|
||||
|
||||
// SignerBodyHashPredicate a function that allows to disable/enable body hashing
|
||||
// of requests and headers associated with body content
|
||||
type SignerBodyHashPredicate func(r *http.Request) bool
|
||||
|
||||
// ociRequestSigner implements the http-signatures-draft spec
|
||||
// as described in https://tools.ietf.org/html/draft-cavage-http-signatures-08
|
||||
type ociRequestSigner struct {
|
||||
KeyProvider KeyProvider
|
||||
GenericHeaders []string
|
||||
BodyHeaders []string
|
||||
ShouldHashBody SignerBodyHashPredicate
|
||||
}
|
||||
|
||||
var (
|
||||
defaultGenericHeaders = []string{"date", "(request-target)", "host"}
|
||||
defaultBodyHeaders = []string{"content-length", "content-type", "x-content-sha256"}
|
||||
defaultBodyHashPredicate = func(r *http.Request) bool {
|
||||
return r.Method == http.MethodPost || r.Method == http.MethodPut || r.Method == http.MethodPatch
|
||||
}
|
||||
)
|
||||
|
||||
// DefaultGenericHeaders list of default generic headers that is used in signing
|
||||
func DefaultGenericHeaders() []string {
|
||||
return makeACopy(defaultGenericHeaders)
|
||||
}
|
||||
|
||||
// DefaultBodyHeaders list of default body headers that is used in signing
|
||||
func DefaultBodyHeaders() []string {
|
||||
return makeACopy(defaultBodyHeaders)
|
||||
}
|
||||
|
||||
// DefaultRequestSigner creates a signer with default parameters.
|
||||
func DefaultRequestSigner(provider KeyProvider) HTTPRequestSigner {
|
||||
return RequestSigner(provider, defaultGenericHeaders, defaultBodyHeaders)
|
||||
}
|
||||
|
||||
// RequestSignerExcludeBody creates a signer without hash the body.
|
||||
func RequestSignerExcludeBody(provider KeyProvider) HTTPRequestSigner {
|
||||
bodyHashPredicate := func(r *http.Request) bool {
|
||||
// week request signer will not hash the body
|
||||
return false
|
||||
}
|
||||
return RequestSignerWithBodyHashingPredicate(provider, defaultGenericHeaders, defaultBodyHeaders, bodyHashPredicate)
|
||||
}
|
||||
|
||||
// NewSignerFromOCIRequestSigner creates a copy of the request signer and attaches the new SignerBodyHashPredicate
|
||||
// returns an error if the passed signer is not of type ociRequestSigner
|
||||
func NewSignerFromOCIRequestSigner(oldSigner HTTPRequestSigner, predicate SignerBodyHashPredicate) (HTTPRequestSigner, error) {
|
||||
if oldS, ok := oldSigner.(ociRequestSigner); ok {
|
||||
s := ociRequestSigner{
|
||||
KeyProvider: oldS.KeyProvider,
|
||||
GenericHeaders: oldS.GenericHeaders,
|
||||
BodyHeaders: oldS.BodyHeaders,
|
||||
ShouldHashBody: predicate,
|
||||
}
|
||||
return s, nil
|
||||
|
||||
}
|
||||
return nil, fmt.Errorf("can not create a signer, input signer needs to be of type ociRequestSigner")
|
||||
}
|
||||
|
||||
// RequestSigner creates a signer that utilizes the specified headers for signing
|
||||
// and the default predicate for using the body of the request as part of the signature
|
||||
func RequestSigner(provider KeyProvider, genericHeaders, bodyHeaders []string) HTTPRequestSigner {
|
||||
return ociRequestSigner{
|
||||
KeyProvider: provider,
|
||||
GenericHeaders: genericHeaders,
|
||||
BodyHeaders: bodyHeaders,
|
||||
ShouldHashBody: defaultBodyHashPredicate}
|
||||
}
|
||||
|
||||
// RequestSignerWithBodyHashingPredicate creates a signer that utilizes the specified headers for signing, as well as a predicate for using
|
||||
// the body of the request and bodyHeaders parameter as part of the signature
|
||||
func RequestSignerWithBodyHashingPredicate(provider KeyProvider, genericHeaders, bodyHeaders []string, shouldHashBody SignerBodyHashPredicate) HTTPRequestSigner {
|
||||
return ociRequestSigner{
|
||||
KeyProvider: provider,
|
||||
GenericHeaders: genericHeaders,
|
||||
BodyHeaders: bodyHeaders,
|
||||
ShouldHashBody: shouldHashBody}
|
||||
}
|
||||
|
||||
func (signer ociRequestSigner) getSigningHeaders(r *http.Request) []string {
|
||||
var result []string
|
||||
result = append(result, signer.GenericHeaders...)
|
||||
|
||||
if signer.ShouldHashBody(r) {
|
||||
result = append(result, signer.BodyHeaders...)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (signer ociRequestSigner) getSigningString(request *http.Request) string {
|
||||
signingHeaders := signer.getSigningHeaders(request)
|
||||
signingParts := make([]string, len(signingHeaders))
|
||||
for i, part := range signingHeaders {
|
||||
var value string
|
||||
part = strings.ToLower(part)
|
||||
switch part {
|
||||
case "(request-target)":
|
||||
value = getRequestTarget(request)
|
||||
case "host":
|
||||
value = request.URL.Host
|
||||
if len(value) == 0 {
|
||||
value = request.Host
|
||||
}
|
||||
default:
|
||||
value = request.Header.Get(part)
|
||||
}
|
||||
signingParts[i] = fmt.Sprintf("%s: %s", part, value)
|
||||
}
|
||||
|
||||
signingString := strings.Join(signingParts, "\n")
|
||||
return signingString
|
||||
|
||||
}
|
||||
|
||||
func getRequestTarget(request *http.Request) string {
|
||||
lowercaseMethod := strings.ToLower(request.Method)
|
||||
return fmt.Sprintf("%s %s", lowercaseMethod, request.URL.RequestURI())
|
||||
}
|
||||
|
||||
func calculateHashOfBody(request *http.Request) (err error) {
|
||||
var hash string
|
||||
hash, err = GetBodyHash(request)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
request.Header.Set(requestHeaderXContentSHA256, hash)
|
||||
return
|
||||
}
|
||||
|
||||
// drainBody reads all of b to memory and then returns two equivalent
|
||||
// ReadClosers yielding the same bytes.
|
||||
//
|
||||
// It returns an error if the initial slurp of all bytes fails. It does not attempt
|
||||
// to make the returned ReadClosers have identical error-matching behavior.
|
||||
func drainBody(b io.ReadCloser) (r1, r2 io.ReadCloser, err error) {
|
||||
if b == http.NoBody {
|
||||
// No copying needed. Preserve the magic sentinel meaning of NoBody.
|
||||
return http.NoBody, http.NoBody, nil
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if _, err = buf.ReadFrom(b); err != nil {
|
||||
return nil, b, err
|
||||
}
|
||||
if err = b.Close(); err != nil {
|
||||
return nil, b, err
|
||||
}
|
||||
return ioutil.NopCloser(&buf), ioutil.NopCloser(bytes.NewReader(buf.Bytes())), nil
|
||||
}
|
||||
|
||||
func hashAndEncode(data []byte) string {
|
||||
hashedContent := sha256.Sum256(data)
|
||||
hash := base64.StdEncoding.EncodeToString(hashedContent[:])
|
||||
return hash
|
||||
}
|
||||
|
||||
// GetBodyHash creates a base64 string from the hash of body the request
|
||||
func GetBodyHash(request *http.Request) (hashString string, err error) {
|
||||
if request.Body == nil {
|
||||
request.ContentLength = 0
|
||||
request.Header.Set(requestHeaderContentLength, fmt.Sprintf("%v", request.ContentLength))
|
||||
return hashAndEncode([]byte("")), nil
|
||||
}
|
||||
|
||||
var data []byte
|
||||
var bReader io.Reader
|
||||
bReader, request.Body, err = drainBody(request.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("can not read body of request while calculating body hash: %s", err.Error())
|
||||
}
|
||||
|
||||
data, err = ioutil.ReadAll(bReader)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("can not read body of request while calculating body hash: %s", err.Error())
|
||||
}
|
||||
|
||||
// Since the request can be coming from a binary body. Make an attempt to set the body length
|
||||
request.ContentLength = int64(len(data))
|
||||
request.Header.Set(requestHeaderContentLength, fmt.Sprintf("%v", request.ContentLength))
|
||||
|
||||
hashString = hashAndEncode(data)
|
||||
return
|
||||
}
|
||||
|
||||
func (signer ociRequestSigner) computeSignature(request *http.Request) (signature string, err error) {
|
||||
signingString := signer.getSigningString(request)
|
||||
hasher := sha256.New()
|
||||
hasher.Write([]byte(signingString))
|
||||
hashed := hasher.Sum(nil)
|
||||
|
||||
privateKey, err := signer.KeyProvider.PrivateRSAKey()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var unencodedSig []byte
|
||||
unencodedSig, e := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hashed)
|
||||
if e != nil {
|
||||
err = fmt.Errorf("can not compute signature while signing the request %s: ", e.Error())
|
||||
return
|
||||
}
|
||||
|
||||
signature = base64.StdEncoding.EncodeToString(unencodedSig)
|
||||
return
|
||||
}
|
||||
|
||||
// Sign signs the http request, by inspecting the necessary headers. Once signed
|
||||
// the request will have the proper 'Authorization' header set, otherwise
|
||||
// and error is returned
|
||||
func (signer ociRequestSigner) Sign(request *http.Request) (err error) {
|
||||
if signer.ShouldHashBody(request) {
|
||||
err = calculateHashOfBody(request)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var signature string
|
||||
if signature, err = signer.computeSignature(request); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
signingHeaders := strings.Join(signer.getSigningHeaders(request), " ")
|
||||
|
||||
var keyID string
|
||||
if keyID, err = signer.KeyProvider.KeyID(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
authValue := fmt.Sprintf("Signature version=\"%s\",headers=\"%s\",keyId=\"%s\",algorithm=\"rsa-sha256\",signature=\"%s\"",
|
||||
signerVersion, signingHeaders, keyID, signature)
|
||||
|
||||
request.Header.Set(requestHeaderAuthorization, authValue)
|
||||
|
||||
return
|
||||
}
|
||||
231
vendor/github.com/oracle/oci-go-sdk/v65/common/log.go
generated
vendored
Normal file
231
vendor/github.com/oracle/oci-go-sdk/v65/common/log.go
generated
vendored
Normal file
@@ -0,0 +1,231 @@
|
||||
// Copyright (c) 2016, 2018, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
// This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// sdkLogger an interface for logging in the SDK
|
||||
type sdkLogger interface {
|
||||
//LogLevel returns the log level of sdkLogger
|
||||
LogLevel() int
|
||||
|
||||
//Log logs v with the provided format if the current log level is loglevel
|
||||
Log(logLevel int, format string, v ...interface{}) error
|
||||
}
|
||||
|
||||
// noLogging no logging messages
|
||||
const noLogging = 0
|
||||
|
||||
// infoLogging minimal logging messages
|
||||
const infoLogging = 1
|
||||
|
||||
// debugLogging some logging messages
|
||||
const debugLogging = 2
|
||||
|
||||
// verboseLogging all logging messages
|
||||
const verboseLogging = 3
|
||||
|
||||
// DefaultSDKLogger the default implementation of the sdkLogger
|
||||
type DefaultSDKLogger struct {
|
||||
currentLoggingLevel int
|
||||
verboseLogger *log.Logger
|
||||
debugLogger *log.Logger
|
||||
infoLogger *log.Logger
|
||||
nullLogger *log.Logger
|
||||
}
|
||||
|
||||
// defaultLogger is the defaultLogger in the SDK
|
||||
var defaultLogger sdkLogger
|
||||
var loggerLock sync.Mutex
|
||||
var file *os.File
|
||||
|
||||
// initializes the SDK defaultLogger as a defaultLogger
|
||||
func init() {
|
||||
l, _ := NewSDKLogger()
|
||||
SetSDKLogger(l)
|
||||
}
|
||||
|
||||
// SetSDKLogger sets the logger used by the sdk
|
||||
func SetSDKLogger(logger sdkLogger) {
|
||||
loggerLock.Lock()
|
||||
defaultLogger = logger
|
||||
loggerLock.Unlock()
|
||||
}
|
||||
|
||||
// NewSDKLogger creates a defaultSDKLogger
|
||||
// Debug logging is turned on/off by the presence of the environment variable "OCI_GO_SDK_DEBUG"
|
||||
// The value of the "OCI_GO_SDK_DEBUG" environment variable controls the logging level.
|
||||
// "null" outputs no log messages
|
||||
// "i" or "info" outputs minimal log messages
|
||||
// "d" or "debug" outputs some logs messages
|
||||
// "v" or "verbose" outputs all logs messages, including body of requests
|
||||
func NewSDKLogger() (DefaultSDKLogger, error) {
|
||||
logger := DefaultSDKLogger{}
|
||||
|
||||
logger.currentLoggingLevel = noLogging
|
||||
logger.verboseLogger = log.New(os.Stderr, "VERBOSE ", log.Ldate|log.Lmicroseconds|log.Lshortfile)
|
||||
logger.debugLogger = log.New(os.Stderr, "DEBUG ", log.Ldate|log.Lmicroseconds|log.Lshortfile)
|
||||
logger.infoLogger = log.New(os.Stderr, "INFO ", log.Ldate|log.Lmicroseconds|log.Lshortfile)
|
||||
logger.nullLogger = log.New(ioutil.Discard, "", log.Ldate|log.Lmicroseconds|log.Lshortfile)
|
||||
|
||||
configured, isLogEnabled := os.LookupEnv("OCI_GO_SDK_DEBUG")
|
||||
|
||||
// If env variable not present turn logging off
|
||||
if !isLogEnabled {
|
||||
logger.currentLoggingLevel = noLogging
|
||||
} else {
|
||||
logOutputModeConfig(logger)
|
||||
|
||||
switch strings.ToLower(configured) {
|
||||
case "null":
|
||||
logger.currentLoggingLevel = noLogging
|
||||
break
|
||||
case "i", "info":
|
||||
logger.currentLoggingLevel = infoLogging
|
||||
break
|
||||
case "d", "debug":
|
||||
logger.currentLoggingLevel = debugLogging
|
||||
break
|
||||
//1 here for backwards compatibility
|
||||
case "v", "verbose", "1":
|
||||
logger.currentLoggingLevel = verboseLogging
|
||||
break
|
||||
default:
|
||||
logger.currentLoggingLevel = infoLogging
|
||||
}
|
||||
logger.infoLogger.Println("logger level set to: ", logger.currentLoggingLevel)
|
||||
}
|
||||
|
||||
return logger, nil
|
||||
}
|
||||
|
||||
func (l DefaultSDKLogger) getLoggerForLevel(logLevel int) *log.Logger {
|
||||
if logLevel > l.currentLoggingLevel {
|
||||
return l.nullLogger
|
||||
}
|
||||
|
||||
switch logLevel {
|
||||
case noLogging:
|
||||
return l.nullLogger
|
||||
case infoLogging:
|
||||
return l.infoLogger
|
||||
case debugLogging:
|
||||
return l.debugLogger
|
||||
case verboseLogging:
|
||||
return l.verboseLogger
|
||||
default:
|
||||
return l.nullLogger
|
||||
}
|
||||
}
|
||||
|
||||
// Set SDK Log output mode
|
||||
// Output mode is switched based on environment variable "OCI_GO_SDK_LOG_OUPUT_MODE"
|
||||
// "file" outputs log to a specific file
|
||||
// "combine" outputs log to both stderr and specific file
|
||||
// other unsupported value outputs log to stderr
|
||||
// output file can be set via environment variable "OCI_GO_SDK_LOG_FILE"
|
||||
// if this environment variable is not set, a default log file will be created under project root path
|
||||
func logOutputModeConfig(logger DefaultSDKLogger) {
|
||||
logMode, isLogOutputModeEnabled := os.LookupEnv("OCI_GO_SDK_LOG_OUTPUT_MODE")
|
||||
if !isLogOutputModeEnabled {
|
||||
return
|
||||
}
|
||||
fileName, isLogFileNameProvided := os.LookupEnv("OCI_GO_SDK_LOG_FILE")
|
||||
if !isLogFileNameProvided {
|
||||
fileName = fmt.Sprintf("logging_%v%s", time.Now().Unix(), ".log")
|
||||
}
|
||||
|
||||
switch strings.ToLower(logMode) {
|
||||
case "file", "f":
|
||||
file = openLogOutputFile(logger, fileName)
|
||||
logger.infoLogger.SetOutput(file)
|
||||
logger.debugLogger.SetOutput(file)
|
||||
logger.verboseLogger.SetOutput(file)
|
||||
break
|
||||
case "combine", "c":
|
||||
file = openLogOutputFile(logger, fileName)
|
||||
wrt := io.MultiWriter(os.Stderr, file)
|
||||
|
||||
logger.infoLogger.SetOutput(wrt)
|
||||
logger.debugLogger.SetOutput(wrt)
|
||||
logger.verboseLogger.SetOutput(wrt)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func openLogOutputFile(logger DefaultSDKLogger, fileName string) *os.File {
|
||||
file, err := os.OpenFile(fileName, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
|
||||
if err != nil {
|
||||
logger.verboseLogger.Fatal(err)
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
// CloseLogFile close the logging file and return error
|
||||
func CloseLogFile() error {
|
||||
return file.Close()
|
||||
}
|
||||
|
||||
// LogLevel returns the current debug level
|
||||
func (l DefaultSDKLogger) LogLevel() int {
|
||||
return l.currentLoggingLevel
|
||||
}
|
||||
|
||||
// Log logs v with the provided format if the current log level is loglevel
|
||||
func (l DefaultSDKLogger) Log(logLevel int, format string, v ...interface{}) error {
|
||||
logger := l.getLoggerForLevel(logLevel)
|
||||
logger.Output(4, fmt.Sprintf(format, v...))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Logln logs v appending a new line at the end
|
||||
// Deprecated
|
||||
func Logln(v ...interface{}) {
|
||||
defaultLogger.Log(infoLogging, "%v\n", v...)
|
||||
}
|
||||
|
||||
// Logf logs v with the provided format
|
||||
func Logf(format string, v ...interface{}) {
|
||||
defaultLogger.Log(infoLogging, format, v...)
|
||||
}
|
||||
|
||||
// Debugf logs v with the provided format if debug mode is set
|
||||
func Debugf(format string, v ...interface{}) {
|
||||
defaultLogger.Log(debugLogging, format, v...)
|
||||
}
|
||||
|
||||
// Debug logs v if debug mode is set
|
||||
func Debug(v ...interface{}) {
|
||||
m := fmt.Sprint(v...)
|
||||
defaultLogger.Log(debugLogging, "%s", m)
|
||||
}
|
||||
|
||||
// Debugln logs v appending a new line if debug mode is set
|
||||
func Debugln(v ...interface{}) {
|
||||
m := fmt.Sprint(v...)
|
||||
defaultLogger.Log(debugLogging, "%s\n", m)
|
||||
}
|
||||
|
||||
// IfDebug executes closure if debug is enabled
|
||||
func IfDebug(fn func()) {
|
||||
if defaultLogger.LogLevel() >= debugLogging {
|
||||
fn()
|
||||
}
|
||||
}
|
||||
|
||||
// IfInfo executes closure if info is enabled
|
||||
func IfInfo(fn func()) {
|
||||
if defaultLogger.LogLevel() >= infoLogging {
|
||||
fn()
|
||||
}
|
||||
}
|
||||
120
vendor/github.com/oracle/oci-go-sdk/v65/common/oci_http_transport_wrapper.go
generated
vendored
Normal file
120
vendor/github.com/oracle/oci-go-sdk/v65/common/oci_http_transport_wrapper.go
generated
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
// Copyright (c) 2016, 2018, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
// This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// OciHTTPTransportWrapper is a http.RoundTripper that periodically refreshes
|
||||
// the underlying http.Transport according to its templates.
|
||||
// Upon the first use (or once the RefreshRate duration is elapsed),
|
||||
// a new transport will be created from the TransportTemplate (if set).
|
||||
type OciHTTPTransportWrapper struct {
|
||||
// RefreshRate specifies the duration at which http.Transport
|
||||
// (with its tls.Config) must be refreshed.
|
||||
// Defaults to 5 minutes.
|
||||
RefreshRate time.Duration
|
||||
|
||||
// TLSConfigProvider creates a new tls.Config.
|
||||
// If not set, nil tls.Config is returned.
|
||||
TLSConfigProvider TLSConfigProvider
|
||||
|
||||
// ClientTemplate is responsible for creating a new http.Client with
|
||||
// a given tls.Config.
|
||||
//
|
||||
// If not set, a new http.Client with a cloned http.DefaultTransport is returned.
|
||||
TransportTemplate TransportTemplateProvider
|
||||
|
||||
// mutable properties
|
||||
mux sync.RWMutex
|
||||
lastRefreshedAt time.Time
|
||||
delegate http.RoundTripper
|
||||
}
|
||||
|
||||
// RoundTrip implements http.RoundTripper.
|
||||
func (t *OciHTTPTransportWrapper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
delegate, err := t.refreshDelegate(false /* force */)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return delegate.RoundTrip(req)
|
||||
}
|
||||
|
||||
// Refresh forces refresh of the underlying delegate.
|
||||
func (t *OciHTTPTransportWrapper) Refresh(force bool) error {
|
||||
_, err := t.refreshDelegate(force)
|
||||
return err
|
||||
}
|
||||
|
||||
// Delegate returns the currently active http.RoundTripper.
|
||||
// Might be nil.
|
||||
func (t *OciHTTPTransportWrapper) Delegate() http.RoundTripper {
|
||||
t.mux.RLock()
|
||||
defer t.mux.RUnlock()
|
||||
|
||||
return t.delegate
|
||||
}
|
||||
|
||||
// refreshDelegate refreshes the delegate (and its TLS config) if:
|
||||
// - force is true
|
||||
// - it's been more than RefreshRate since the last time the client was refreshed.
|
||||
func (t *OciHTTPTransportWrapper) refreshDelegate(force bool) (http.RoundTripper, error) {
|
||||
// read-lock first, since it's cheaper than write lock
|
||||
t.mux.RLock()
|
||||
if !t.shouldRefreshLocked(force) {
|
||||
delegate := t.delegate
|
||||
t.mux.RUnlock()
|
||||
|
||||
return delegate, nil
|
||||
}
|
||||
|
||||
// upgrade to write-lock, and we'll need to check again for the same condition as above
|
||||
// to avoid multiple initializations by multiple "refresher" goroutines
|
||||
t.mux.RUnlock()
|
||||
t.mux.Lock()
|
||||
defer t.mux.Unlock()
|
||||
if !t.shouldRefreshLocked(force) {
|
||||
return t.delegate, nil
|
||||
}
|
||||
|
||||
// For this check we need the delegate to be set once before we check for change in cert files
|
||||
if t.delegate != nil && !t.TLSConfigProvider.WatchedFilesModified() {
|
||||
Debug("No modification in custom certs or ca bundle skipping refresh")
|
||||
// Updating the last refresh time to make sure the next check is only done after the refresh interval has passed
|
||||
t.lastRefreshedAt = time.Now()
|
||||
return t.delegate, nil
|
||||
}
|
||||
|
||||
Logf("Loading tls config from TLSConfigProvider")
|
||||
tlsConfig, err := t.TLSConfigProvider.NewOrDefault()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("refreshing tls.Config from template: %w", err)
|
||||
}
|
||||
|
||||
t.delegate, err = t.TransportTemplate.NewOrDefault(tlsConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("refreshing http.RoundTripper from template: %w", err)
|
||||
}
|
||||
|
||||
t.lastRefreshedAt = time.Now()
|
||||
return t.delegate, nil
|
||||
}
|
||||
|
||||
// shouldRefreshLocked returns whether the client (and its TLS config)
|
||||
// needs to be refreshed.
|
||||
func (t *OciHTTPTransportWrapper) shouldRefreshLocked(force bool) bool {
|
||||
if force || t.delegate == nil {
|
||||
return true
|
||||
}
|
||||
return t.refreshRate() > 0 && time.Since(t.lastRefreshedAt) > t.refreshRate()
|
||||
}
|
||||
|
||||
func (t *OciHTTPTransportWrapper) refreshRate() time.Duration {
|
||||
return t.RefreshRate
|
||||
}
|
||||
351
vendor/github.com/oracle/oci-go-sdk/v65/common/regions.go
generated
vendored
Normal file
351
vendor/github.com/oracle/oci-go-sdk/v65/common/regions.go
generated
vendored
Normal file
@@ -0,0 +1,351 @@
|
||||
// Copyright (c) 2016, 2018, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
// This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
|
||||
|
||||
package common
|
||||
|
||||
const (
|
||||
//RegionAPChuncheon1 region Chuncheon
|
||||
RegionAPChuncheon1 Region = "ap-chuncheon-1"
|
||||
//RegionAPHyderabad1 region Hyderabad
|
||||
RegionAPHyderabad1 Region = "ap-hyderabad-1"
|
||||
//RegionAPMelbourne1 region Melbourne
|
||||
RegionAPMelbourne1 Region = "ap-melbourne-1"
|
||||
//RegionAPMumbai1 region Mumbai
|
||||
RegionAPMumbai1 Region = "ap-mumbai-1"
|
||||
//RegionAPOsaka1 region Osaka
|
||||
RegionAPOsaka1 Region = "ap-osaka-1"
|
||||
//RegionAPSeoul1 region Seoul
|
||||
RegionAPSeoul1 Region = "ap-seoul-1"
|
||||
//RegionAPSydney1 region Sydney
|
||||
RegionAPSydney1 Region = "ap-sydney-1"
|
||||
//RegionAPTokyo1 region Tokyo
|
||||
RegionAPTokyo1 Region = "ap-tokyo-1"
|
||||
//RegionCAMontreal1 region Montreal
|
||||
RegionCAMontreal1 Region = "ca-montreal-1"
|
||||
//RegionCAToronto1 region Toronto
|
||||
RegionCAToronto1 Region = "ca-toronto-1"
|
||||
//RegionEUAmsterdam1 region Amsterdam
|
||||
RegionEUAmsterdam1 Region = "eu-amsterdam-1"
|
||||
//RegionFRA region Frankfurt
|
||||
RegionFRA Region = "eu-frankfurt-1"
|
||||
//RegionEUZurich1 region Zurich
|
||||
RegionEUZurich1 Region = "eu-zurich-1"
|
||||
//RegionMEJeddah1 region Jeddah
|
||||
RegionMEJeddah1 Region = "me-jeddah-1"
|
||||
//RegionMEDubai1 region Dubai
|
||||
RegionMEDubai1 Region = "me-dubai-1"
|
||||
//RegionSASaopaulo1 region Saopaulo
|
||||
RegionSASaopaulo1 Region = "sa-saopaulo-1"
|
||||
//RegionUKCardiff1 region Cardiff
|
||||
RegionUKCardiff1 Region = "uk-cardiff-1"
|
||||
//RegionLHR region London
|
||||
RegionLHR Region = "uk-london-1"
|
||||
//RegionIAD region Ashburn
|
||||
RegionIAD Region = "us-ashburn-1"
|
||||
//RegionPHX region Phoenix
|
||||
RegionPHX Region = "us-phoenix-1"
|
||||
//RegionSJC1 region Sanjose
|
||||
RegionSJC1 Region = "us-sanjose-1"
|
||||
//RegionSAVinhedo1 region Vinhedo
|
||||
RegionSAVinhedo1 Region = "sa-vinhedo-1"
|
||||
//RegionSASantiago1 region Santiago
|
||||
RegionSASantiago1 Region = "sa-santiago-1"
|
||||
//RegionILJerusalem1 region Jerusalem
|
||||
RegionILJerusalem1 Region = "il-jerusalem-1"
|
||||
//RegionEUMarseille1 region Marseille
|
||||
RegionEUMarseille1 Region = "eu-marseille-1"
|
||||
//RegionAPSingapore1 region Singapore
|
||||
RegionAPSingapore1 Region = "ap-singapore-1"
|
||||
//RegionMEAbudhabi1 region Abudhabi
|
||||
RegionMEAbudhabi1 Region = "me-abudhabi-1"
|
||||
//RegionEUMilan1 region Milan
|
||||
RegionEUMilan1 Region = "eu-milan-1"
|
||||
//RegionEUStockholm1 region Stockholm
|
||||
RegionEUStockholm1 Region = "eu-stockholm-1"
|
||||
//RegionAFJohannesburg1 region Johannesburg
|
||||
RegionAFJohannesburg1 Region = "af-johannesburg-1"
|
||||
//RegionEUParis1 region Paris
|
||||
RegionEUParis1 Region = "eu-paris-1"
|
||||
//RegionMXQueretaro1 region Queretaro
|
||||
RegionMXQueretaro1 Region = "mx-queretaro-1"
|
||||
//RegionEUMadrid1 region Madrid
|
||||
RegionEUMadrid1 Region = "eu-madrid-1"
|
||||
//RegionUSChicago1 region Chicago
|
||||
RegionUSChicago1 Region = "us-chicago-1"
|
||||
//RegionMXMonterrey1 region Monterrey
|
||||
RegionMXMonterrey1 Region = "mx-monterrey-1"
|
||||
//RegionUSSaltlake2 region Saltlake
|
||||
RegionUSSaltlake2 Region = "us-saltlake-2"
|
||||
//RegionSABogota1 region Bogota
|
||||
RegionSABogota1 Region = "sa-bogota-1"
|
||||
//RegionSAValparaiso1 region Valparaiso
|
||||
RegionSAValparaiso1 Region = "sa-valparaiso-1"
|
||||
//RegionAPSingapore2 region Singapore
|
||||
RegionAPSingapore2 Region = "ap-singapore-2"
|
||||
//RegionMERiyadh1 region Riyadh
|
||||
RegionMERiyadh1 Region = "me-riyadh-1"
|
||||
//RegionAPDelhi1 region Delhi
|
||||
RegionAPDelhi1 Region = "ap-delhi-1"
|
||||
//RegionUSLangley1 region Langley
|
||||
RegionUSLangley1 Region = "us-langley-1"
|
||||
//RegionUSLuke1 region Luke
|
||||
RegionUSLuke1 Region = "us-luke-1"
|
||||
//RegionUSGovAshburn1 gov region Ashburn
|
||||
RegionUSGovAshburn1 Region = "us-gov-ashburn-1"
|
||||
//RegionUSGovChicago1 gov region Chicago
|
||||
RegionUSGovChicago1 Region = "us-gov-chicago-1"
|
||||
//RegionUSGovPhoenix1 gov region Phoenix
|
||||
RegionUSGovPhoenix1 Region = "us-gov-phoenix-1"
|
||||
//RegionUKGovLondon1 gov region London
|
||||
RegionUKGovLondon1 Region = "uk-gov-london-1"
|
||||
//RegionUKGovCardiff1 gov region Cardiff
|
||||
RegionUKGovCardiff1 Region = "uk-gov-cardiff-1"
|
||||
//RegionAPChiyoda1 region Chiyoda
|
||||
RegionAPChiyoda1 Region = "ap-chiyoda-1"
|
||||
//RegionAPIbaraki1 region Ibaraki
|
||||
RegionAPIbaraki1 Region = "ap-ibaraki-1"
|
||||
//RegionMEDccMuscat1 region Muscat
|
||||
RegionMEDccMuscat1 Region = "me-dcc-muscat-1"
|
||||
//RegionAPDccCanberra1 region Canberra
|
||||
RegionAPDccCanberra1 Region = "ap-dcc-canberra-1"
|
||||
//RegionEUDccMilan1 region Milan
|
||||
RegionEUDccMilan1 Region = "eu-dcc-milan-1"
|
||||
//RegionEUDccMilan2 region Milan
|
||||
RegionEUDccMilan2 Region = "eu-dcc-milan-2"
|
||||
//RegionEUDccDublin2 region Dublin
|
||||
RegionEUDccDublin2 Region = "eu-dcc-dublin-2"
|
||||
//RegionEUDccRating2 region Rating
|
||||
RegionEUDccRating2 Region = "eu-dcc-rating-2"
|
||||
//RegionEUDccRating1 region Rating
|
||||
RegionEUDccRating1 Region = "eu-dcc-rating-1"
|
||||
//RegionEUDccDublin1 region Dublin
|
||||
RegionEUDccDublin1 Region = "eu-dcc-dublin-1"
|
||||
//RegionAPDccGazipur1 region Gazipur
|
||||
RegionAPDccGazipur1 Region = "ap-dcc-gazipur-1"
|
||||
//RegionEUMadrid2 region Madrid
|
||||
RegionEUMadrid2 Region = "eu-madrid-2"
|
||||
//RegionEUFrankfurt2 region Frankfurt
|
||||
RegionEUFrankfurt2 Region = "eu-frankfurt-2"
|
||||
//RegionEUJovanovac1 region Jovanovac
|
||||
RegionEUJovanovac1 Region = "eu-jovanovac-1"
|
||||
//RegionMEDccDoha1 region Doha
|
||||
RegionMEDccDoha1 Region = "me-dcc-doha-1"
|
||||
//RegionUSSomerset1 region Somerset
|
||||
RegionUSSomerset1 Region = "us-somerset-1"
|
||||
//RegionUSThames1 region Thames
|
||||
RegionUSThames1 Region = "us-thames-1"
|
||||
//RegionEUDccZurich1 region Zurich
|
||||
RegionEUDccZurich1 Region = "eu-dcc-zurich-1"
|
||||
//RegionEUCrissier1 region Crissier
|
||||
RegionEUCrissier1 Region = "eu-crissier-1"
|
||||
//RegionMEAbudhabi3 region Abudhabi
|
||||
RegionMEAbudhabi3 Region = "me-abudhabi-3"
|
||||
//RegionMEAlain1 region Alain
|
||||
RegionMEAlain1 Region = "me-alain-1"
|
||||
//RegionMEAbudhabi2 region Abudhabi
|
||||
RegionMEAbudhabi2 Region = "me-abudhabi-2"
|
||||
//RegionMEAbudhabi4 region Abudhabi
|
||||
RegionMEAbudhabi4 Region = "me-abudhabi-4"
|
||||
//RegionAPSeoul2 region Seoul
|
||||
RegionAPSeoul2 Region = "ap-seoul-2"
|
||||
//RegionAPSuwon1 region Suwon
|
||||
RegionAPSuwon1 Region = "ap-suwon-1"
|
||||
//RegionAPChuncheon2 region Chuncheon
|
||||
RegionAPChuncheon2 Region = "ap-chuncheon-2"
|
||||
//RegionUSAshburn2 region Ashburn
|
||||
RegionUSAshburn2 Region = "us-ashburn-2"
|
||||
)
|
||||
|
||||
var shortNameRegion = map[string]Region{
|
||||
"yny": RegionAPChuncheon1,
|
||||
"hyd": RegionAPHyderabad1,
|
||||
"mel": RegionAPMelbourne1,
|
||||
"bom": RegionAPMumbai1,
|
||||
"kix": RegionAPOsaka1,
|
||||
"icn": RegionAPSeoul1,
|
||||
"syd": RegionAPSydney1,
|
||||
"nrt": RegionAPTokyo1,
|
||||
"yul": RegionCAMontreal1,
|
||||
"yyz": RegionCAToronto1,
|
||||
"ams": RegionEUAmsterdam1,
|
||||
"fra": RegionFRA,
|
||||
"zrh": RegionEUZurich1,
|
||||
"jed": RegionMEJeddah1,
|
||||
"dxb": RegionMEDubai1,
|
||||
"gru": RegionSASaopaulo1,
|
||||
"cwl": RegionUKCardiff1,
|
||||
"lhr": RegionLHR,
|
||||
"iad": RegionIAD,
|
||||
"phx": RegionPHX,
|
||||
"sjc": RegionSJC1,
|
||||
"vcp": RegionSAVinhedo1,
|
||||
"scl": RegionSASantiago1,
|
||||
"mtz": RegionILJerusalem1,
|
||||
"mrs": RegionEUMarseille1,
|
||||
"sin": RegionAPSingapore1,
|
||||
"auh": RegionMEAbudhabi1,
|
||||
"lin": RegionEUMilan1,
|
||||
"arn": RegionEUStockholm1,
|
||||
"jnb": RegionAFJohannesburg1,
|
||||
"cdg": RegionEUParis1,
|
||||
"qro": RegionMXQueretaro1,
|
||||
"mad": RegionEUMadrid1,
|
||||
"ord": RegionUSChicago1,
|
||||
"mty": RegionMXMonterrey1,
|
||||
"aga": RegionUSSaltlake2,
|
||||
"bog": RegionSABogota1,
|
||||
"vap": RegionSAValparaiso1,
|
||||
"xsp": RegionAPSingapore2,
|
||||
"ruh": RegionMERiyadh1,
|
||||
"onm": RegionAPDelhi1,
|
||||
"lfi": RegionUSLangley1,
|
||||
"luf": RegionUSLuke1,
|
||||
"ric": RegionUSGovAshburn1,
|
||||
"pia": RegionUSGovChicago1,
|
||||
"tus": RegionUSGovPhoenix1,
|
||||
"ltn": RegionUKGovLondon1,
|
||||
"brs": RegionUKGovCardiff1,
|
||||
"nja": RegionAPChiyoda1,
|
||||
"ukb": RegionAPIbaraki1,
|
||||
"mct": RegionMEDccMuscat1,
|
||||
"wga": RegionAPDccCanberra1,
|
||||
"bgy": RegionEUDccMilan1,
|
||||
"mxp": RegionEUDccMilan2,
|
||||
"snn": RegionEUDccDublin2,
|
||||
"dtm": RegionEUDccRating2,
|
||||
"dus": RegionEUDccRating1,
|
||||
"ork": RegionEUDccDublin1,
|
||||
"dac": RegionAPDccGazipur1,
|
||||
"vll": RegionEUMadrid2,
|
||||
"str": RegionEUFrankfurt2,
|
||||
"beg": RegionEUJovanovac1,
|
||||
"doh": RegionMEDccDoha1,
|
||||
"ebb": RegionUSSomerset1,
|
||||
"ebl": RegionUSThames1,
|
||||
"avz": RegionEUDccZurich1,
|
||||
"avf": RegionEUCrissier1,
|
||||
"ahu": RegionMEAbudhabi3,
|
||||
"rba": RegionMEAlain1,
|
||||
"rkt": RegionMEAbudhabi2,
|
||||
"shj": RegionMEAbudhabi4,
|
||||
"dtz": RegionAPSeoul2,
|
||||
"dln": RegionAPSuwon1,
|
||||
"bno": RegionAPChuncheon2,
|
||||
"yxj": RegionUSAshburn2,
|
||||
}
|
||||
|
||||
var realm = map[string]string{
|
||||
"oc1": "oraclecloud.com",
|
||||
"oc2": "oraclegovcloud.com",
|
||||
"oc3": "oraclegovcloud.com",
|
||||
"oc4": "oraclegovcloud.uk",
|
||||
"oc8": "oraclecloud8.com",
|
||||
"oc9": "oraclecloud9.com",
|
||||
"oc10": "oraclecloud10.com",
|
||||
"oc14": "oraclecloud14.com",
|
||||
"oc15": "oraclecloud15.com",
|
||||
"oc19": "oraclecloud.eu",
|
||||
"oc20": "oraclecloud20.com",
|
||||
"oc21": "oraclecloud21.com",
|
||||
"oc23": "oraclecloud23.com",
|
||||
"oc24": "oraclecloud24.com",
|
||||
"oc26": "oraclecloud26.com",
|
||||
"oc29": "oraclecloud29.com",
|
||||
"oc35": "oraclecloud35.com",
|
||||
"oc42": "oraclecloud42.com",
|
||||
}
|
||||
|
||||
var regionRealm = map[Region]string{
|
||||
RegionAPChuncheon1: "oc1",
|
||||
RegionAPHyderabad1: "oc1",
|
||||
RegionAPMelbourne1: "oc1",
|
||||
RegionAPMumbai1: "oc1",
|
||||
RegionAPOsaka1: "oc1",
|
||||
RegionAPSeoul1: "oc1",
|
||||
RegionAPSydney1: "oc1",
|
||||
RegionAPTokyo1: "oc1",
|
||||
RegionCAMontreal1: "oc1",
|
||||
RegionCAToronto1: "oc1",
|
||||
RegionEUAmsterdam1: "oc1",
|
||||
RegionFRA: "oc1",
|
||||
RegionEUZurich1: "oc1",
|
||||
RegionMEJeddah1: "oc1",
|
||||
RegionMEDubai1: "oc1",
|
||||
RegionSASaopaulo1: "oc1",
|
||||
RegionUKCardiff1: "oc1",
|
||||
RegionLHR: "oc1",
|
||||
RegionIAD: "oc1",
|
||||
RegionPHX: "oc1",
|
||||
RegionSJC1: "oc1",
|
||||
RegionSAVinhedo1: "oc1",
|
||||
RegionSASantiago1: "oc1",
|
||||
RegionILJerusalem1: "oc1",
|
||||
RegionEUMarseille1: "oc1",
|
||||
RegionAPSingapore1: "oc1",
|
||||
RegionMEAbudhabi1: "oc1",
|
||||
RegionEUMilan1: "oc1",
|
||||
RegionEUStockholm1: "oc1",
|
||||
RegionAFJohannesburg1: "oc1",
|
||||
RegionEUParis1: "oc1",
|
||||
RegionMXQueretaro1: "oc1",
|
||||
RegionEUMadrid1: "oc1",
|
||||
RegionUSChicago1: "oc1",
|
||||
RegionMXMonterrey1: "oc1",
|
||||
RegionUSSaltlake2: "oc1",
|
||||
RegionSABogota1: "oc1",
|
||||
RegionSAValparaiso1: "oc1",
|
||||
RegionAPSingapore2: "oc1",
|
||||
RegionMERiyadh1: "oc1",
|
||||
RegionAPDelhi1: "oc1",
|
||||
|
||||
RegionUSLangley1: "oc2",
|
||||
RegionUSLuke1: "oc2",
|
||||
|
||||
RegionUSGovAshburn1: "oc3",
|
||||
RegionUSGovChicago1: "oc3",
|
||||
RegionUSGovPhoenix1: "oc3",
|
||||
|
||||
RegionUKGovLondon1: "oc4",
|
||||
RegionUKGovCardiff1: "oc4",
|
||||
|
||||
RegionAPChiyoda1: "oc8",
|
||||
RegionAPIbaraki1: "oc8",
|
||||
|
||||
RegionMEDccMuscat1: "oc9",
|
||||
|
||||
RegionAPDccCanberra1: "oc10",
|
||||
|
||||
RegionEUDccMilan1: "oc14",
|
||||
RegionEUDccMilan2: "oc14",
|
||||
RegionEUDccDublin2: "oc14",
|
||||
RegionEUDccRating2: "oc14",
|
||||
RegionEUDccRating1: "oc14",
|
||||
RegionEUDccDublin1: "oc14",
|
||||
|
||||
RegionAPDccGazipur1: "oc15",
|
||||
|
||||
RegionEUMadrid2: "oc19",
|
||||
RegionEUFrankfurt2: "oc19",
|
||||
|
||||
RegionEUJovanovac1: "oc20",
|
||||
|
||||
RegionMEDccDoha1: "oc21",
|
||||
|
||||
RegionUSSomerset1: "oc23",
|
||||
RegionUSThames1: "oc23",
|
||||
|
||||
RegionEUDccZurich1: "oc24",
|
||||
RegionEUCrissier1: "oc24",
|
||||
|
||||
RegionMEAbudhabi3: "oc26",
|
||||
RegionMEAlain1: "oc26",
|
||||
|
||||
RegionMEAbudhabi2: "oc29",
|
||||
RegionMEAbudhabi4: "oc29",
|
||||
|
||||
RegionAPSeoul2: "oc35",
|
||||
RegionAPSuwon1: "oc35",
|
||||
RegionAPChuncheon2: "oc35",
|
||||
|
||||
RegionUSAshburn2: "oc42",
|
||||
}
|
||||
452
vendor/github.com/oracle/oci-go-sdk/v65/common/regions.json
generated
vendored
Normal file
452
vendor/github.com/oracle/oci-go-sdk/v65/common/regions.json
generated
vendored
Normal file
@@ -0,0 +1,452 @@
|
||||
[
|
||||
{
|
||||
"regionKey": "yny",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "ap-chuncheon-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "hyd",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "ap-hyderabad-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "mel",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "ap-melbourne-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "bom",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "ap-mumbai-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "kix",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "ap-osaka-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "icn",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "ap-seoul-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "syd",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "ap-sydney-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "nrt",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "ap-tokyo-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "yul",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "ca-montreal-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "yyz",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "ca-toronto-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "ams",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "eu-amsterdam-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "fra",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "eu-frankfurt-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "zrh",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "eu-zurich-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "jed",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "me-jeddah-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "dxb",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "me-dubai-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "gru",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "sa-saopaulo-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "cwl",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "uk-cardiff-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "lhr",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "uk-london-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "iad",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "us-ashburn-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "phx",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "us-phoenix-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "sjc",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "us-sanjose-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "vcp",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "sa-vinhedo-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "scl",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "sa-santiago-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "lfi",
|
||||
"realmDomainComponent": "oraclegovcloud.com",
|
||||
"regionIdentifier": "us-langley-1",
|
||||
"realmKey": "oc2"
|
||||
},
|
||||
{
|
||||
"regionKey": "luf",
|
||||
"realmDomainComponent": "oraclegovcloud.com",
|
||||
"regionIdentifier": "us-luke-1",
|
||||
"realmKey": "oc2"
|
||||
},
|
||||
{
|
||||
"regionKey": "ric",
|
||||
"realmDomainComponent": "oraclegovcloud.com",
|
||||
"regionIdentifier": "us-gov-ashburn-1",
|
||||
"realmKey": "oc3"
|
||||
},
|
||||
{
|
||||
"regionKey": "pia",
|
||||
"realmDomainComponent": "oraclegovcloud.com",
|
||||
"regionIdentifier": "us-gov-chicago-1",
|
||||
"realmKey": "oc3"
|
||||
},
|
||||
{
|
||||
"regionKey": "tus",
|
||||
"realmDomainComponent": "oraclegovcloud.com",
|
||||
"regionIdentifier": "us-gov-phoenix-1",
|
||||
"realmKey": "oc3"
|
||||
},
|
||||
{
|
||||
"regionKey": "ltn",
|
||||
"realmDomainComponent": "oraclegovcloud.uk",
|
||||
"regionIdentifier": "uk-gov-london-1",
|
||||
"realmKey": "oc4"
|
||||
},
|
||||
{
|
||||
"regionKey": "brs",
|
||||
"realmDomainComponent": "oraclegovcloud.uk",
|
||||
"regionIdentifier": "uk-gov-cardiff-1",
|
||||
"realmKey": "oc4"
|
||||
},
|
||||
{
|
||||
"regionKey": "nja",
|
||||
"realmDomainComponent": "oraclecloud8.com",
|
||||
"regionIdentifier": "ap-chiyoda-1",
|
||||
"realmKey": "oc8"
|
||||
},
|
||||
{
|
||||
"regionKey": "ukb",
|
||||
"realmDomainComponent": "oraclecloud8.com",
|
||||
"regionIdentifier": "ap-ibaraki-1",
|
||||
"realmKey": "oc8"
|
||||
},
|
||||
{
|
||||
"regionKey": "mtz",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "il-jerusalem-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "mrs",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "eu-marseille-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "sin",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "ap-singapore-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "auh",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "me-abudhabi-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "lin",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "eu-milan-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "arn",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "eu-stockholm-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "jnb",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "af-johannesburg-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "mct",
|
||||
"realmDomainComponent": "oraclecloud9.com",
|
||||
"regionIdentifier": "me-dcc-muscat-1",
|
||||
"realmKey": "oc9"
|
||||
},
|
||||
{
|
||||
"regionKey": "wga",
|
||||
"realmDomainComponent": "oraclecloud10.com",
|
||||
"regionIdentifier": "ap-dcc-canberra-1",
|
||||
"realmKey": "oc10"
|
||||
},
|
||||
{
|
||||
"regionKey": "cdg",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "eu-paris-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "qro",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "mx-queretaro-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "mad",
|
||||
"realmDomainComponent": "oraclecloud.com",
|
||||
"regionIdentifier": "eu-madrid-1",
|
||||
"realmKey": "oc1"
|
||||
},
|
||||
{
|
||||
"regionKey": "bgy",
|
||||
"realmDomainComponent": "oraclecloud14.com",
|
||||
"regionIdentifier": "eu-dcc-milan-1",
|
||||
"realmKey": "oc14"
|
||||
},
|
||||
{
|
||||
"regionKey": "ord",
|
||||
"realmKey": "oc1",
|
||||
"regionIdentifier": "us-chicago-1",
|
||||
"realmDomainComponent": "oraclecloud.com"
|
||||
},
|
||||
{
|
||||
"regionKey": "mxp",
|
||||
"realmKey": "oc14",
|
||||
"regionIdentifier": "eu-dcc-milan-2",
|
||||
"realmDomainComponent": "oraclecloud14.com"
|
||||
},
|
||||
{
|
||||
"regionKey": "snn",
|
||||
"realmKey": "oc14",
|
||||
"regionIdentifier": "eu-dcc-dublin-2",
|
||||
"realmDomainComponent": "oraclecloud14.com"
|
||||
},
|
||||
{
|
||||
"regionKey": "dtm",
|
||||
"realmKey": "oc14",
|
||||
"regionIdentifier": "eu-dcc-rating-2",
|
||||
"realmDomainComponent": "oraclecloud14.com"
|
||||
},
|
||||
{
|
||||
"regionKey": "dus",
|
||||
"realmKey": "oc14",
|
||||
"regionIdentifier": "eu-dcc-rating-1",
|
||||
"realmDomainComponent": "oraclecloud14.com"
|
||||
},
|
||||
{
|
||||
"regionKey": "ork",
|
||||
"realmKey": "oc14",
|
||||
"regionIdentifier": "eu-dcc-dublin-1",
|
||||
"realmDomainComponent": "oraclecloud14.com"
|
||||
},
|
||||
{
|
||||
"regionKey": "beg",
|
||||
"realmKey": "oc20",
|
||||
"regionIdentifier": "eu-jovanovac-1",
|
||||
"realmDomainComponent": "oraclecloud20.com"
|
||||
},
|
||||
{
|
||||
"regionKey": "vll",
|
||||
"realmKey": "oc19",
|
||||
"regionIdentifier": "eu-madrid-2",
|
||||
"realmDomainComponent": "oraclecloud.eu"
|
||||
},
|
||||
{
|
||||
"regionKey": "str",
|
||||
"realmKey": "oc19",
|
||||
"regionIdentifier": "eu-frankfurt-2",
|
||||
"realmDomainComponent": "oraclecloud.eu"
|
||||
},
|
||||
{
|
||||
"regionKey": "mty",
|
||||
"realmKey": "oc1",
|
||||
"regionIdentifier": "mx-monterrey-1",
|
||||
"realmDomainComponent": "oraclecloud.com"
|
||||
},
|
||||
{
|
||||
"regionKey": "aga",
|
||||
"realmKey": "oc1",
|
||||
"regionIdentifier": "us-saltlake-2",
|
||||
"realmDomainComponent": "oraclecloud.com"
|
||||
},
|
||||
{
|
||||
"regionKey": "avz",
|
||||
"realmKey": "oc24",
|
||||
"regionIdentifier": "eu-dcc-zurich-1",
|
||||
"realmDomainComponent": "oraclecloud24.com"
|
||||
},
|
||||
{
|
||||
"regionKey": "bog",
|
||||
"realmKey": "oc1",
|
||||
"regionIdentifier": "sa-bogota-1",
|
||||
"realmDomainComponent": "oraclecloud.com"
|
||||
},
|
||||
{
|
||||
"regionKey": "vap",
|
||||
"realmKey": "oc1",
|
||||
"regionIdentifier": "sa-valparaiso-1",
|
||||
"realmDomainComponent": "oraclecloud.com"
|
||||
},
|
||||
{
|
||||
"regionKey": "doh",
|
||||
"realmKey": "oc21",
|
||||
"regionIdentifier": "me-dcc-doha-1",
|
||||
"realmDomainComponent": "oraclecloud21.com"
|
||||
},
|
||||
{
|
||||
"regionKey": "ahu",
|
||||
"realmKey": "oc26",
|
||||
"regionIdentifier": "me-abudhabi-3",
|
||||
"realmDomainComponent": "oraclecloud26.com"
|
||||
},
|
||||
{
|
||||
"regionKey": "dac",
|
||||
"realmKey": "oc15",
|
||||
"regionIdentifier": "ap-dcc-gazipur-1",
|
||||
"realmDomainComponent": "oraclecloud15.com"
|
||||
},
|
||||
{
|
||||
"regionKey": "xsp",
|
||||
"realmKey": "oc1",
|
||||
"regionIdentifier": "ap-singapore-2",
|
||||
"realmDomainComponent": "oraclecloud.com"
|
||||
},
|
||||
{
|
||||
"regionKey": "rkt",
|
||||
"realmKey": "oc29",
|
||||
"regionIdentifier": "me-abudhabi-2",
|
||||
"realmDomainComponent": "oraclecloud29.com"
|
||||
},
|
||||
{
|
||||
"regionKey": "ruh",
|
||||
"realmKey": "oc1",
|
||||
"regionIdentifier": "me-riyadh-1",
|
||||
"realmDomainComponent": "oraclecloud.com"
|
||||
},
|
||||
{
|
||||
"regionKey": "shj",
|
||||
"realmKey": "oc29",
|
||||
"regionIdentifier": "me-abudhabi-4",
|
||||
"realmDomainComponent": "oraclecloud29.com"
|
||||
},
|
||||
{
|
||||
"regionKey": "avf",
|
||||
"realmKey": "oc24",
|
||||
"regionIdentifier": "eu-crissier-1",
|
||||
"realmDomainComponent": "oraclecloud24.com"
|
||||
},
|
||||
{
|
||||
"regionKey": "ebb",
|
||||
"realmKey": "oc23",
|
||||
"regionIdentifier": "us-somerset-1",
|
||||
"realmDomainComponent": "oraclecloud23.com"
|
||||
},
|
||||
{
|
||||
"regionKey": "ebl",
|
||||
"realmKey": "oc23",
|
||||
"regionIdentifier": "us-thames-1",
|
||||
"realmDomainComponent": "oraclecloud23.com"
|
||||
},
|
||||
{
|
||||
"regionKey": "dtz",
|
||||
"realmKey": "oc35",
|
||||
"regionIdentifier": "ap-seoul-2",
|
||||
"realmDomainComponent": "oraclecloud35.com"
|
||||
},
|
||||
{
|
||||
"regionKey": "dln",
|
||||
"realmKey": "oc35",
|
||||
"regionIdentifier": "ap-suwon-1",
|
||||
"realmDomainComponent": "oraclecloud35.com"
|
||||
},
|
||||
{
|
||||
"regionKey": "bno",
|
||||
"realmKey": "oc35",
|
||||
"regionIdentifier": "ap-chuncheon-2",
|
||||
"realmDomainComponent": "oraclecloud35.com"
|
||||
},
|
||||
{
|
||||
"regionKey": "rba",
|
||||
"realmKey": "oc26",
|
||||
"regionIdentifier": "me-alain-1",
|
||||
"realmDomainComponent": "oraclecloud26.com"
|
||||
},
|
||||
{
|
||||
"regionKey": "yxj",
|
||||
"realmKey": "oc42",
|
||||
"regionIdentifier": "us-ashburn-2",
|
||||
"realmDomainComponent": "oraclecloud42.com"
|
||||
},
|
||||
{
|
||||
"regionKey": "onm",
|
||||
"realmKey": "oc1",
|
||||
"regionIdentifier": "ap-delhi-1",
|
||||
"realmDomainComponent": "oraclecloud.com"
|
||||
}
|
||||
]
|
||||
911
vendor/github.com/oracle/oci-go-sdk/v65/common/retry.go
generated
vendored
Normal file
911
vendor/github.com/oracle/oci-go-sdk/v65/common/retry.go
generated
vendored
Normal file
@@ -0,0 +1,911 @@
|
||||
// Copyright (c) 2016, 2018, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
// This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// UnlimitedNumAttemptsValue is the value for indicating unlimited attempts for reaching success
|
||||
UnlimitedNumAttemptsValue = uint(0)
|
||||
|
||||
// number of characters contained in the generated retry token
|
||||
generatedRetryTokenLength = 32
|
||||
)
|
||||
|
||||
// OCIRetryableRequest represents a request that can be reissued according to the specified policy.
|
||||
type OCIRetryableRequest interface {
|
||||
// Any retryable request must implement the OCIRequest interface
|
||||
OCIRequest
|
||||
|
||||
// Each operation should implement this method, if has binary body, return OCIReadSeekCloser and true, otherwise return nil, false
|
||||
BinaryRequestBody() (*OCIReadSeekCloser, bool)
|
||||
|
||||
// Each operation specifies default retry behavior. By passing no arguments to this method, the default retry
|
||||
// behavior, as determined on a per-operation-basis, will be honored. Variadic retry policy option arguments
|
||||
// passed to this method will override the default behavior.
|
||||
RetryPolicy() *RetryPolicy
|
||||
}
|
||||
|
||||
// OCIOperationResponse represents the output of an OCIOperation, with additional context of error message
|
||||
// and operation attempt number.
|
||||
type OCIOperationResponse struct {
|
||||
// Response from OCI Operation
|
||||
Response OCIResponse
|
||||
|
||||
// Error from OCI Operation
|
||||
Error error
|
||||
|
||||
// Operation Attempt Number (one-based)
|
||||
AttemptNumber uint
|
||||
|
||||
// End of eventually consistent effects, or nil if no such effects
|
||||
EndOfWindowTime *time.Time
|
||||
|
||||
// Backoff scaling factor (only used for dealing with eventual consistency)
|
||||
BackoffScalingFactor float64
|
||||
|
||||
// Time of the initial attempt
|
||||
InitialAttemptTime time.Time
|
||||
}
|
||||
|
||||
const (
|
||||
defaultMaximumNumberAttempts = uint(8)
|
||||
defaultExponentialBackoffBase = 2.0
|
||||
defaultMinSleepBetween = 0.0
|
||||
defaultMaxSleepBetween = 30.0
|
||||
|
||||
ecMaximumNumberAttempts = uint(9)
|
||||
ecExponentialBackoffBase = 3.52
|
||||
ecMinSleepBetween = 0.0
|
||||
ecMaxSleepBetween = 45.0
|
||||
)
|
||||
|
||||
var (
|
||||
defaultRetryStatusCodeMap = map[StatErrCode]bool{
|
||||
{409, "IncorrectState"}: true,
|
||||
{429, "TooManyRequests"}: true,
|
||||
|
||||
{501, "MethodNotImplemented"}: false,
|
||||
}
|
||||
)
|
||||
|
||||
// IsErrorRetryableByDefault returns true if the error is retryable by OCI default retry policy
|
||||
func IsErrorRetryableByDefault(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if IsNetworkError(err) {
|
||||
return true
|
||||
}
|
||||
|
||||
if err == io.EOF {
|
||||
return true
|
||||
}
|
||||
|
||||
if err, ok := IsServiceError(err); ok {
|
||||
if shouldRetry, ok := defaultRetryStatusCodeMap[StatErrCode{err.GetHTTPStatusCode(), err.GetCode()}]; ok {
|
||||
return shouldRetry
|
||||
}
|
||||
|
||||
return 500 <= err.GetHTTPStatusCode() && err.GetHTTPStatusCode() < 505
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// NewOCIOperationResponse assembles an OCI Operation Response object.
|
||||
// Note that InitialAttemptTime is not set, nor is EndOfWindowTime, and BackoffScalingFactor is set to 1.0.
|
||||
// EndOfWindowTime and BackoffScalingFactor are only important for eventual consistency.
|
||||
// InitialAttemptTime can be useful for time-based (as opposed to count-based) retry policies.
|
||||
func NewOCIOperationResponse(response OCIResponse, err error, attempt uint) OCIOperationResponse {
|
||||
return OCIOperationResponse{
|
||||
Response: response,
|
||||
Error: err,
|
||||
AttemptNumber: attempt,
|
||||
BackoffScalingFactor: 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
// NewOCIOperationResponseExtended assembles an OCI Operation Response object, with the value for the EndOfWindowTime, BackoffScalingFactor, and InitialAttemptTime set.
|
||||
// EndOfWindowTime and BackoffScalingFactor are only important for eventual consistency.
|
||||
// InitialAttemptTime can be useful for time-based (as opposed to count-based) retry policies.
|
||||
func NewOCIOperationResponseExtended(response OCIResponse, err error, attempt uint, endOfWindowTime *time.Time, backoffScalingFactor float64,
|
||||
initialAttemptTime time.Time) OCIOperationResponse {
|
||||
return OCIOperationResponse{
|
||||
Response: response,
|
||||
Error: err,
|
||||
AttemptNumber: attempt,
|
||||
EndOfWindowTime: endOfWindowTime,
|
||||
BackoffScalingFactor: backoffScalingFactor,
|
||||
InitialAttemptTime: initialAttemptTime,
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// RetryPolicy
|
||||
//
|
||||
|
||||
// RetryPolicy is the class that holds all relevant information for retrying operations.
|
||||
type RetryPolicy struct {
|
||||
// MaximumNumberAttempts is the maximum number of times to retry a request. Zero indicates an unlimited
|
||||
// number of attempts.
|
||||
MaximumNumberAttempts uint
|
||||
|
||||
// ShouldRetryOperation inspects the http response, error, and operation attempt number, and
|
||||
// - returns true if we should retry the operation
|
||||
// - returns false otherwise
|
||||
ShouldRetryOperation func(OCIOperationResponse) bool
|
||||
|
||||
// GetNextDuration computes the duration to pause between operation retries.
|
||||
NextDuration func(OCIOperationResponse) time.Duration
|
||||
|
||||
// minimum sleep between attempts in seconds
|
||||
MinSleepBetween float64
|
||||
|
||||
// maximum sleep between attempts in seconds
|
||||
MaxSleepBetween float64
|
||||
|
||||
// the base for the exponential backoff
|
||||
ExponentialBackoffBase float64
|
||||
|
||||
// DeterminePolicyToUse may modify the policy to handle eventual consistency; the return values are
|
||||
// the retry policy to use, the end of the eventually consistent time window, and the backoff scaling factor
|
||||
// If eventual consistency is not considered, this function should return the unmodified policy that was
|
||||
// provided as input, along with (*time.Time)(nil) (no time window), and 1.0 (unscaled backoff).
|
||||
DeterminePolicyToUse func(policy RetryPolicy) (RetryPolicy, *time.Time, float64)
|
||||
|
||||
// if the retry policy considers eventual consistency, but there is no eventual consistency present
|
||||
// the retries will fall back to the policy specified here; recommendation is to set this to DefaultRetryPolicyWithoutEventualConsistency()
|
||||
NonEventuallyConsistentPolicy *RetryPolicy
|
||||
|
||||
// Stores the maximum cumulative backoff in seconds. This can usually be calculated using
|
||||
// MaximumNumberAttempts, MinSleepBetween, MaxSleepBetween, and ExponentialBackoffBase,
|
||||
// but if MaximumNumberAttempts is 0 (unlimited attempts), then this needs to be set explicitly
|
||||
// for Eventual Consistency retries to work.
|
||||
MaximumCumulativeBackoffWithoutJitter float64
|
||||
}
|
||||
|
||||
// GlobalRetry is user defined global level retry policy, it would impact all services, the precedence is lower
|
||||
// than user defined client/request level retry policy
|
||||
var GlobalRetry *RetryPolicy = nil
|
||||
|
||||
// RetryPolicyOption is the type of the options for NewRetryPolicy.
|
||||
type RetryPolicyOption func(rp *RetryPolicy)
|
||||
|
||||
// String Converts retry policy to human-readable string representation
|
||||
func (rp RetryPolicy) String() string {
|
||||
return fmt.Sprintf("{MaximumNumberAttempts=%v, MinSleepBetween=%v, MaxSleepBetween=%v, ExponentialBackoffBase=%v, NonEventuallyConsistentPolicy=%v}",
|
||||
rp.MaximumNumberAttempts, rp.MinSleepBetween, rp.MaxSleepBetween, rp.ExponentialBackoffBase, rp.NonEventuallyConsistentPolicy)
|
||||
}
|
||||
|
||||
// Validate returns true if the RetryPolicy is valid; if not, it also returns an error.
|
||||
func (rp *RetryPolicy) validate() (success bool, err error) {
|
||||
var errorStrings []string
|
||||
if rp.ShouldRetryOperation == nil {
|
||||
errorStrings = append(errorStrings, "ShouldRetryOperation may not be nil")
|
||||
}
|
||||
if rp.NextDuration == nil {
|
||||
errorStrings = append(errorStrings, "NextDuration may not be nil")
|
||||
}
|
||||
if rp.NonEventuallyConsistentPolicy != nil {
|
||||
if rp.MaximumNumberAttempts == 0 && rp.MaximumCumulativeBackoffWithoutJitter <= 0 {
|
||||
errorStrings = append(errorStrings, "If eventual consistency is handled, and the MaximumNumberAttempts of the EC retry policy is 0 (unlimited attempts), then the MaximumCumulativeBackoffWithoutJitter of the EC retry policy must be positive; used WithUnlimitedAttempts instead")
|
||||
}
|
||||
nonEcRp := rp.NonEventuallyConsistentPolicy
|
||||
if nonEcRp.MaximumNumberAttempts == 0 && nonEcRp.MaximumCumulativeBackoffWithoutJitter <= 0 {
|
||||
errorStrings = append(errorStrings, "If eventual consistency is handled, and the MaximumNumberAttempts of the non-EC retry policy is 0 (unlimited attempts), then the MaximumCumulativeBackoffWithoutJitter of the non-EC retry policy must be positive; used WithUnlimitedAttempts instead")
|
||||
}
|
||||
}
|
||||
if len(errorStrings) > 0 {
|
||||
return false, errors.New(strings.Join(errorStrings, ", "))
|
||||
}
|
||||
|
||||
// some legacy code constructing RetryPolicy instances directly may not have set DeterminePolicyToUse.
|
||||
// In that case, just assume that it doesn't handle eventual consistency.
|
||||
if rp.DeterminePolicyToUse == nil {
|
||||
rp.DeterminePolicyToUse = returnSamePolicy
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// GetMaximumCumulativeBackoffWithoutJitter returns the maximum cumulative backoff the retry policy would do,
|
||||
// taking into account whether eventually consistency is considered or not.
|
||||
// This function uses either GetMaximumCumulativeBackoffWithoutJitter or GetMaximumCumulativeEventuallyConsistentBackoffWithoutJitter,
|
||||
// whichever is appropriate
|
||||
func (rp RetryPolicy) GetMaximumCumulativeBackoffWithoutJitter() time.Duration {
|
||||
if rp.NonEventuallyConsistentPolicy == nil {
|
||||
return GetMaximumCumulativeBackoffWithoutJitter(rp)
|
||||
}
|
||||
return GetMaximumCumulativeEventuallyConsistentBackoffWithoutJitter(rp)
|
||||
}
|
||||
|
||||
//
|
||||
// Functions to calculate backoff and maximum cumulative backoff
|
||||
//
|
||||
|
||||
// GetBackoffWithoutJitter calculates the backoff without jitter for the attempt, given the retry policy.
|
||||
func GetBackoffWithoutJitter(policy RetryPolicy, attempt uint) time.Duration {
|
||||
return time.Duration(getBackoffWithoutJitterHelper(policy.MinSleepBetween, policy.MaxSleepBetween, policy.ExponentialBackoffBase, attempt)) * time.Second
|
||||
}
|
||||
|
||||
// getBackoffWithoutJitterHelper calculates the backoff without jitter for the attempt, given the loose retry policy values.
|
||||
func getBackoffWithoutJitterHelper(minSleepBetween float64, maxSleepBetween float64, exponentialBackoffBase float64, attempt uint) float64 {
|
||||
sleepTime := math.Pow(exponentialBackoffBase, float64(attempt-1))
|
||||
if sleepTime < minSleepBetween {
|
||||
sleepTime = minSleepBetween
|
||||
}
|
||||
if sleepTime > maxSleepBetween {
|
||||
sleepTime = maxSleepBetween
|
||||
}
|
||||
return sleepTime
|
||||
}
|
||||
|
||||
// GetMaximumCumulativeBackoffWithoutJitter calculates the maximum backoff without jitter, according to the retry
|
||||
// policy, if every retry attempt is made.
|
||||
func GetMaximumCumulativeBackoffWithoutJitter(policy RetryPolicy) time.Duration {
|
||||
return getMaximumCumulativeBackoffWithoutJitterHelper(policy.MinSleepBetween, policy.MaxSleepBetween, policy.ExponentialBackoffBase, policy.MaximumNumberAttempts, policy.MaximumCumulativeBackoffWithoutJitter)
|
||||
}
|
||||
|
||||
func getMaximumCumulativeBackoffWithoutJitterHelper(minSleepBetween float64, maxSleepBetween float64, exponentialBackoffBase float64, MaximumNumberAttempts uint, MaximumCumulativeBackoffWithoutJitter float64) time.Duration {
|
||||
var cumulative time.Duration = 0
|
||||
|
||||
if MaximumNumberAttempts == 0 {
|
||||
// unlimited
|
||||
return time.Duration(MaximumCumulativeBackoffWithoutJitter) * time.Second
|
||||
}
|
||||
|
||||
// use a one-based counter because it's easier to think about operation retry in terms of attempt numbering
|
||||
for currentOperationAttempt := uint(1); currentOperationAttempt < MaximumNumberAttempts; currentOperationAttempt++ {
|
||||
cumulative += time.Duration(getBackoffWithoutJitterHelper(minSleepBetween, maxSleepBetween, exponentialBackoffBase, currentOperationAttempt)) * time.Second
|
||||
}
|
||||
return cumulative
|
||||
}
|
||||
|
||||
//
|
||||
// Functions to calculate backoff and maximum cumulative backoff for eventual consistency
|
||||
//
|
||||
|
||||
// GetEventuallyConsistentBackoffWithoutJitter calculates the backoff without jitter for the attempt, given the retry policy
|
||||
// and dealing with eventually consistent effects. The result is then multiplied by backoffScalingFactor.
|
||||
func GetEventuallyConsistentBackoffWithoutJitter(policy RetryPolicy, attempt uint, backoffScalingFactor float64) time.Duration {
|
||||
return time.Duration(getEventuallyConsistentBackoffWithoutJitterHelper(policy.MinSleepBetween, policy.MaxSleepBetween, policy.ExponentialBackoffBase, attempt, backoffScalingFactor,
|
||||
func(minSleepBetween float64, maxSleepBetween float64, exponentialBackoffBase float64, attempt uint) float64 {
|
||||
rp := policy.NonEventuallyConsistentPolicy
|
||||
return getBackoffWithoutJitterHelper(rp.MinSleepBetween, rp.MaxSleepBetween, rp.ExponentialBackoffBase, attempt)
|
||||
})*1000) * time.Millisecond
|
||||
}
|
||||
|
||||
// getEventuallyConsistentBackoffWithoutJitterHelper calculates the backoff without jitter for the attempt, given the loose retry policy values,
|
||||
// and dealing with eventually consistent effects. The result is then multiplied by backoffScalingFactor.
|
||||
func getEventuallyConsistentBackoffWithoutJitterHelper(minSleepBetween float64, maxSleepBetween float64, exponentialBackoffBase float64, attempt uint, backoffScalingFactor float64,
|
||||
defaultBackoffWithoutJitterHelper func(minSleepBetween float64, maxSleepBetween float64, exponentialBackoffBase float64, attempt uint) float64) float64 {
|
||||
var sleepTime = math.Pow(exponentialBackoffBase, float64(attempt-1))
|
||||
if sleepTime < minSleepBetween {
|
||||
sleepTime = minSleepBetween
|
||||
}
|
||||
if sleepTime > maxSleepBetween {
|
||||
sleepTime = maxSleepBetween
|
||||
}
|
||||
sleepTime = sleepTime * backoffScalingFactor
|
||||
defaultSleepTime := defaultBackoffWithoutJitterHelper(minSleepBetween, maxSleepBetween, exponentialBackoffBase, attempt)
|
||||
if defaultSleepTime > sleepTime {
|
||||
sleepTime = defaultSleepTime
|
||||
}
|
||||
return sleepTime
|
||||
}
|
||||
|
||||
// GetMaximumCumulativeEventuallyConsistentBackoffWithoutJitter calculates the maximum backoff without jitter, according to the retry
|
||||
// policy and taking eventually consistent effects into account, if every retry attempt is made.
|
||||
func GetMaximumCumulativeEventuallyConsistentBackoffWithoutJitter(policy RetryPolicy) time.Duration {
|
||||
return getMaximumCumulativeEventuallyConsistentBackoffWithoutJitterHelper(policy.MinSleepBetween, policy.MaxSleepBetween, policy.ExponentialBackoffBase,
|
||||
policy.MaximumNumberAttempts, policy.MaximumCumulativeBackoffWithoutJitter,
|
||||
func(minSleepBetween float64, maxSleepBetween float64, exponentialBackoffBase float64, attempt uint) float64 {
|
||||
rp := policy.NonEventuallyConsistentPolicy
|
||||
return getBackoffWithoutJitterHelper(rp.MinSleepBetween, rp.MaxSleepBetween, rp.ExponentialBackoffBase, attempt)
|
||||
})
|
||||
}
|
||||
|
||||
func getMaximumCumulativeEventuallyConsistentBackoffWithoutJitterHelper(minSleepBetween float64, maxSleepBetween float64, exponentialBackoffBase float64, MaximumNumberAttempts uint,
|
||||
MaximumCumulativeBackoffWithoutJitter float64,
|
||||
defaultBackoffWithoutJitterHelper func(minSleepBetween float64, maxSleepBetween float64, exponentialBackoffBase float64, attempt uint) float64) time.Duration {
|
||||
if MaximumNumberAttempts == 0 {
|
||||
// unlimited
|
||||
return time.Duration(MaximumCumulativeBackoffWithoutJitter) * time.Second
|
||||
}
|
||||
|
||||
var cumulative time.Duration = 0
|
||||
// use a one-based counter because it's easier to think about operation retry in terms of attempt numbering
|
||||
for currentOperationAttempt := uint(1); currentOperationAttempt < MaximumNumberAttempts; currentOperationAttempt++ {
|
||||
cumulative += time.Duration(getEventuallyConsistentBackoffWithoutJitterHelper(minSleepBetween, maxSleepBetween, exponentialBackoffBase, currentOperationAttempt, 1.0, defaultBackoffWithoutJitterHelper)*1000) * time.Millisecond
|
||||
}
|
||||
return cumulative
|
||||
}
|
||||
|
||||
func returnSamePolicy(policy RetryPolicy) (RetryPolicy, *time.Time, float64) {
|
||||
// we're returning the end of window time nonetheless, even though the default non-eventual consistency (EC)
|
||||
// retry policy doesn't use it; this is useful in case developers wants to write an EC-aware retry policy
|
||||
// on their own
|
||||
eowt := EcContext.GetEndOfWindow()
|
||||
return policy, eowt, 1.0
|
||||
}
|
||||
|
||||
// NoRetryPolicy is a helper method that assembles and returns a return policy that indicates an operation should
|
||||
// never be retried (the operation is performed exactly once).
|
||||
func NoRetryPolicy() RetryPolicy {
|
||||
dontRetryOperation := func(OCIOperationResponse) bool { return false }
|
||||
zeroNextDuration := func(OCIOperationResponse) time.Duration { return 0 * time.Second }
|
||||
return newRetryPolicyWithOptionsNoDefault(
|
||||
WithMaximumNumberAttempts(1),
|
||||
WithShouldRetryOperation(dontRetryOperation),
|
||||
WithNextDuration(zeroNextDuration),
|
||||
withMinSleepBetween(0.0*time.Second),
|
||||
withMaxSleepBetween(0.0*time.Second),
|
||||
withExponentialBackoffBase(0.0),
|
||||
withDeterminePolicyToUse(returnSamePolicy),
|
||||
withNonEventuallyConsistentPolicy(nil))
|
||||
}
|
||||
|
||||
// DefaultShouldRetryOperation is the function that should be used for RetryPolicy.ShouldRetryOperation when
|
||||
// not taking eventual consistency into account.
|
||||
func DefaultShouldRetryOperation(r OCIOperationResponse) bool {
|
||||
if r.Error == nil && 199 < r.Response.HTTPResponse().StatusCode && r.Response.HTTPResponse().StatusCode < 300 {
|
||||
// success
|
||||
return false
|
||||
}
|
||||
return IsErrorRetryableByDefault(r.Error)
|
||||
}
|
||||
|
||||
// DefaultRetryPolicy is a helper method that assembles and returns a return policy that is defined to be a default one
|
||||
// The default retry policy will retry on (409, IncorrectState), (429, TooManyRequests) and any 5XX errors except (501, MethodNotImplemented)
|
||||
// The default retry behavior is using exponential backoff with jitter, the maximum wait time is 30s plus 1s jitter
|
||||
// The maximum cumulative backoff after all 8 attempts have been made is about 1.5 minutes.
|
||||
// It will also retry on errors affected by eventual consistency.
|
||||
// The eventual consistency retry behavior is using exponential backoff with jitter, the maximum wait time is 45s plus 1s jitter
|
||||
// Under eventual consistency, the maximum cumulative backoff after all 9 attempts have been made is about 4 minutes.
|
||||
func DefaultRetryPolicy() RetryPolicy {
|
||||
return NewRetryPolicyWithOptions(
|
||||
ReplaceWithValuesFromRetryPolicy(DefaultRetryPolicyWithoutEventualConsistency()),
|
||||
WithEventualConsistency())
|
||||
}
|
||||
|
||||
// DefaultRetryPolicyWithoutEventualConsistency is a helper method that assembles and returns a return policy that is defined to be a default one
|
||||
// The default retry policy will retry on (409, IncorrectState), (429, TooManyRequests) and any 5XX errors except (501, MethodNotImplemented)
|
||||
// It will not retry on errors affected by eventual consistency.
|
||||
// The default retry behavior is using exponential backoff with jitter, the maximum wait time is 30s plus 1s jitter
|
||||
func DefaultRetryPolicyWithoutEventualConsistency() RetryPolicy {
|
||||
exponentialBackoffWithJitter := func(r OCIOperationResponse) time.Duration {
|
||||
sleepTime := getBackoffWithoutJitterHelper(defaultMinSleepBetween, defaultMaxSleepBetween, defaultExponentialBackoffBase, r.AttemptNumber)
|
||||
nextDuration := time.Duration(1000.0*(sleepTime+rand.Float64())) * time.Millisecond
|
||||
return nextDuration
|
||||
}
|
||||
return newRetryPolicyWithOptionsNoDefault(
|
||||
WithMaximumNumberAttempts(defaultMaximumNumberAttempts),
|
||||
WithShouldRetryOperation(DefaultShouldRetryOperation),
|
||||
WithNextDuration(exponentialBackoffWithJitter),
|
||||
withMinSleepBetween(defaultMinSleepBetween*time.Second),
|
||||
withMaxSleepBetween(defaultMaxSleepBetween*time.Second),
|
||||
withExponentialBackoffBase(defaultExponentialBackoffBase),
|
||||
withDeterminePolicyToUse(returnSamePolicy),
|
||||
withNonEventuallyConsistentPolicy(nil))
|
||||
}
|
||||
|
||||
// EventuallyConsistentShouldRetryOperation is the function that should be used for RetryPolicy.ShouldRetryOperation when
|
||||
// taking eventual consistency into account
|
||||
func EventuallyConsistentShouldRetryOperation(r OCIOperationResponse) bool {
|
||||
if r.Error == nil && 199 < r.Response.HTTPResponse().StatusCode && r.Response.HTTPResponse().StatusCode < 300 {
|
||||
// success
|
||||
Debugln(fmt.Sprintf("EC.ShouldRetryOperation, status = %v, 2xx, returning false", r.Response.HTTPResponse().StatusCode))
|
||||
return false
|
||||
}
|
||||
if IsErrorRetryableByDefault(r.Error) {
|
||||
return true
|
||||
}
|
||||
// not retryable by default
|
||||
if _, ok := IsServiceError(r.Error); ok {
|
||||
now := EcContext.timeNowProvider()
|
||||
if r.EndOfWindowTime == nil || r.EndOfWindowTime.Before(now) {
|
||||
// either no eventually consistent effects, or they have disappeared by now
|
||||
Debugln(fmt.Sprintf("EC.ShouldRetryOperation, no EC or in the past, returning false: endOfWindowTime = %v, now = %v", r.EndOfWindowTime, now))
|
||||
return false
|
||||
}
|
||||
// there were eventually consistent effects present at the time of the first request
|
||||
// and they could still affect the retries
|
||||
if IsErrorAffectedByEventualConsistency(r.Error) {
|
||||
// and it's one of the three affected error codes
|
||||
Debugln(fmt.Sprintf("EC.ShouldRetryOperation, affected by EC, EC is present: endOfWindowTime = %v, now = %v", r.EndOfWindowTime, now))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// EventuallyConsistentRetryPolicy is a helper method that assembles and returns a return policy that is defined to be a default one
|
||||
// plus dealing with errors affected by eventual consistency.
|
||||
// The default retry behavior is using exponential backoff with jitter, the maximum wait time is 45s plus 1s jitter
|
||||
func EventuallyConsistentRetryPolicy(nonEventuallyConsistentPolicy RetryPolicy) RetryPolicy {
|
||||
if nonEventuallyConsistentPolicy.NonEventuallyConsistentPolicy != nil {
|
||||
// already deals with eventual consistency
|
||||
return nonEventuallyConsistentPolicy
|
||||
}
|
||||
exponentialBackoffWithJitter := func(r OCIOperationResponse) time.Duration {
|
||||
sleepTime := getEventuallyConsistentBackoffWithoutJitterHelper(ecMinSleepBetween, ecMaxSleepBetween, ecExponentialBackoffBase, r.AttemptNumber, r.BackoffScalingFactor,
|
||||
func(minSleepBetween float64, maxSleepBetween float64, exponentialBackoffBase float64, attempt uint) float64 {
|
||||
rp := nonEventuallyConsistentPolicy
|
||||
return getBackoffWithoutJitterHelper(rp.MinSleepBetween, rp.MaxSleepBetween, rp.ExponentialBackoffBase, attempt)
|
||||
})
|
||||
nextDuration := time.Duration(1000.0*(sleepTime+rand.Float64())) * time.Millisecond
|
||||
Debugln(fmt.Sprintf("EventuallyConsistentRetryPolicy.NextDuration for attempt %v: sleepTime = %.1fs, nextDuration = %v", r.AttemptNumber, sleepTime, nextDuration))
|
||||
return nextDuration
|
||||
}
|
||||
returnModifiedPolicy := func(policy RetryPolicy) (RetryPolicy, *time.Time, float64) { return determinePolicyToUse(policy) }
|
||||
nonEventuallyConsistentPolicyCopy := newRetryPolicyWithOptionsNoDefault(
|
||||
ReplaceWithValuesFromRetryPolicy(nonEventuallyConsistentPolicy))
|
||||
return newRetryPolicyWithOptionsNoDefault(
|
||||
WithMaximumNumberAttempts(ecMaximumNumberAttempts),
|
||||
WithShouldRetryOperation(EventuallyConsistentShouldRetryOperation),
|
||||
WithNextDuration(exponentialBackoffWithJitter),
|
||||
withMinSleepBetween(ecMinSleepBetween*time.Second),
|
||||
withMaxSleepBetween(ecMaxSleepBetween*time.Second),
|
||||
withExponentialBackoffBase(ecExponentialBackoffBase),
|
||||
withDeterminePolicyToUse(returnModifiedPolicy),
|
||||
withNonEventuallyConsistentPolicy(&nonEventuallyConsistentPolicyCopy))
|
||||
}
|
||||
|
||||
// NewRetryPolicy is a helper method for assembling a Retry Policy object. It does not handle eventual consistency, so as to not break existing code.
|
||||
// If you want to handle eventual consistency, the simplest way to do that is to replace the code
|
||||
//
|
||||
// NewRetryPolicy(a, r, n)
|
||||
//
|
||||
// with the code
|
||||
//
|
||||
// NewRetryPolicyWithOptions(
|
||||
// WithMaximumNumberAttempts(a),
|
||||
// WithFixedBackoff(fb) // fb is the fixed backoff duration
|
||||
// WithShouldRetryOperation(r))
|
||||
//
|
||||
// or
|
||||
//
|
||||
// NewRetryPolicyWithOptions(
|
||||
// WithMaximumNumberAttempts(a),
|
||||
// WithExponentialBackoff(mb, e) // mb is the maximum backoff duration, and e is the base for exponential backoff, e.g. 2.0
|
||||
// WithShouldRetryOperation(r))
|
||||
//
|
||||
// or, if a == 0 (the maximum number of attempts is unlimited)
|
||||
//
|
||||
// NewRetryPolicyWithEventualConsistencyUnlimitedAttempts(a, r, n, mcb) // mcb is the maximum cumulative backoff duration without jitter
|
||||
func NewRetryPolicy(attempts uint, retryOperation func(OCIOperationResponse) bool, nextDuration func(OCIOperationResponse) time.Duration) RetryPolicy {
|
||||
return NewRetryPolicyWithOptions(
|
||||
ReplaceWithValuesFromRetryPolicy(DefaultRetryPolicyWithoutEventualConsistency()),
|
||||
WithMaximumNumberAttempts(attempts),
|
||||
WithShouldRetryOperation(retryOperation),
|
||||
WithNextDuration(nextDuration),
|
||||
)
|
||||
}
|
||||
|
||||
// NewRetryPolicyWithEventualConsistencyUnlimitedAttempts is a helper method for assembling a Retry Policy object.
|
||||
// It does handle eventual consistency, but other than that, it is very similar to NewRetryPolicy.
|
||||
// NewRetryPolicyWithEventualConsistency does not support limited attempts, use NewRetryPolicyWithEventualConsistency instead.
|
||||
func NewRetryPolicyWithEventualConsistencyUnlimitedAttempts(attempts uint, retryOperation func(OCIOperationResponse) bool, nextDuration func(OCIOperationResponse) time.Duration,
|
||||
maximumCumulativeBackoffWithoutJitter time.Duration) (*RetryPolicy, error) {
|
||||
|
||||
if attempts != 0 {
|
||||
return nil, fmt.Errorf("NewRetryPolicyWithEventualConsistencyUnlimitedAttempts cannot be used with attempts != 0 (limited attempts), use NewRetryPolicyWithEventualConsistency instead")
|
||||
}
|
||||
|
||||
result := NewRetryPolicyWithOptions(
|
||||
ReplaceWithValuesFromRetryPolicy(DefaultRetryPolicyWithoutEventualConsistency()),
|
||||
WithUnlimitedAttempts(maximumCumulativeBackoffWithoutJitter),
|
||||
WithShouldRetryOperation(retryOperation),
|
||||
WithNextDuration(nextDuration),
|
||||
)
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
// NewRetryPolicyWithOptions is a helper method for assembling a Retry Policy object.
|
||||
// It starts out with the values returned by DefaultRetryPolicy() and does handle eventual consistency,
|
||||
// unless you replace all options set using ReplaceWithValuesFromRetryPolicy(DefaultRetryPolicyWithoutEventualConsistency()).
|
||||
func NewRetryPolicyWithOptions(opts ...RetryPolicyOption) RetryPolicy {
|
||||
rp := &RetryPolicy{}
|
||||
|
||||
// start with the default retry policy
|
||||
ReplaceWithValuesFromRetryPolicy(DefaultRetryPolicyWithoutEventualConsistency())(rp)
|
||||
WithEventualConsistency()(rp)
|
||||
|
||||
// then allow changing values
|
||||
for _, opt := range opts {
|
||||
opt(rp)
|
||||
}
|
||||
|
||||
if rp.DeterminePolicyToUse == nil {
|
||||
rp.DeterminePolicyToUse = returnSamePolicy
|
||||
}
|
||||
|
||||
return *rp
|
||||
}
|
||||
|
||||
// newRetryPolicyWithOptionsNoDefault is a helper method for assembling a Retry Policy object.
|
||||
// Contrary to newRetryPolicyWithOptions, it does not start out with the values returned by
|
||||
// DefaultRetryPolicy().
|
||||
func newRetryPolicyWithOptionsNoDefault(opts ...RetryPolicyOption) RetryPolicy {
|
||||
rp := &RetryPolicy{}
|
||||
|
||||
// then allow changing values
|
||||
for _, opt := range opts {
|
||||
opt(rp)
|
||||
}
|
||||
|
||||
if rp.DeterminePolicyToUse == nil {
|
||||
rp.DeterminePolicyToUse = returnSamePolicy
|
||||
}
|
||||
|
||||
return *rp
|
||||
}
|
||||
|
||||
// WithMaximumNumberAttempts is the option for NewRetryPolicyWithOptions that sets the maximum number of attempts.
|
||||
func WithMaximumNumberAttempts(attempts uint) RetryPolicyOption {
|
||||
// this is the RetryPolicyOption function type
|
||||
return func(rp *RetryPolicy) {
|
||||
rp.MaximumNumberAttempts = attempts
|
||||
}
|
||||
}
|
||||
|
||||
// WithUnlimitedAttempts is the option for NewRetryPolicyWithOptions that sets unlimited number of attempts,
|
||||
// but it needs to set a MaximumCumulativeBackoffWithoutJitter duration.
|
||||
// If you use WithUnlimitedAttempts, you should set your own NextDuration function using WithNextDuration.
|
||||
func WithUnlimitedAttempts(maximumCumulativeBackoffWithoutJitter time.Duration) RetryPolicyOption {
|
||||
// this is the RetryPolicyOption function type
|
||||
return func(rp *RetryPolicy) {
|
||||
rp.MaximumNumberAttempts = 0
|
||||
rp.MaximumCumulativeBackoffWithoutJitter = float64(maximumCumulativeBackoffWithoutJitter / time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
// WithShouldRetryOperation is the option for NewRetryPolicyWithOptions that sets the function that checks
|
||||
// whether retries should be performed.
|
||||
func WithShouldRetryOperation(retryOperation func(OCIOperationResponse) bool) RetryPolicyOption {
|
||||
// this is the RetryPolicyOption function type
|
||||
return func(rp *RetryPolicy) {
|
||||
rp.ShouldRetryOperation = retryOperation
|
||||
}
|
||||
}
|
||||
|
||||
// WithNextDuration is the option for NewRetryPolicyWithOptions that sets the function for computing the next
|
||||
// backoff duration.
|
||||
// It is preferred to use WithFixedBackoff or WithExponentialBackoff instead.
|
||||
func WithNextDuration(nextDuration func(OCIOperationResponse) time.Duration) RetryPolicyOption {
|
||||
// this is the RetryPolicyOption function type
|
||||
return func(rp *RetryPolicy) {
|
||||
rp.NextDuration = nextDuration
|
||||
}
|
||||
}
|
||||
|
||||
// withMinSleepBetween is the option for NewRetryPolicyWithOptions that sets the minimum backoff duration.
|
||||
func withMinSleepBetween(minSleepBetween time.Duration) RetryPolicyOption {
|
||||
// this is the RetryPolicyOption function type
|
||||
return func(rp *RetryPolicy) {
|
||||
rp.MinSleepBetween = float64(minSleepBetween / time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
// withMaxsSleepBetween is the option for NewRetryPolicyWithOptions that sets the maximum backoff duration.
|
||||
func withMaxSleepBetween(maxSleepBetween time.Duration) RetryPolicyOption {
|
||||
// this is the RetryPolicyOption function type
|
||||
return func(rp *RetryPolicy) {
|
||||
rp.MaxSleepBetween = float64(maxSleepBetween / time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
// withExponentialBackoffBase is the option for NewRetryPolicyWithOptions that sets the base for the
|
||||
// exponential backoff
|
||||
func withExponentialBackoffBase(base float64) RetryPolicyOption {
|
||||
// this is the RetryPolicyOption function type
|
||||
return func(rp *RetryPolicy) {
|
||||
rp.ExponentialBackoffBase = base
|
||||
}
|
||||
}
|
||||
|
||||
// withDeterminePolicyToUse is the option for NewRetryPolicyWithOptions that sets the function that
|
||||
// determines which polich should be used and if eventual consistency should be considered
|
||||
func withDeterminePolicyToUse(determinePolicyToUse func(policy RetryPolicy) (RetryPolicy, *time.Time, float64)) RetryPolicyOption {
|
||||
// this is the RetryPolicyOption function type
|
||||
return func(rp *RetryPolicy) {
|
||||
rp.DeterminePolicyToUse = determinePolicyToUse
|
||||
}
|
||||
}
|
||||
|
||||
// withNonEventuallyConsistentPolicy is the option for NewRetryPolicyWithOptions that sets the fallback
|
||||
// strategy if eventual consistency should not be considered
|
||||
func withNonEventuallyConsistentPolicy(nonEventuallyConsistentPolicy *RetryPolicy) RetryPolicyOption {
|
||||
// this is the RetryPolicyOption function type
|
||||
return func(rp *RetryPolicy) {
|
||||
// we want a non-EC policy for NonEventuallyConsistentPolicy; make sure that NonEventuallyConsistentPolicy is nil
|
||||
for nonEventuallyConsistentPolicy != nil && nonEventuallyConsistentPolicy.NonEventuallyConsistentPolicy != nil {
|
||||
nonEventuallyConsistentPolicy = nonEventuallyConsistentPolicy.NonEventuallyConsistentPolicy
|
||||
}
|
||||
rp.NonEventuallyConsistentPolicy = nonEventuallyConsistentPolicy
|
||||
}
|
||||
}
|
||||
|
||||
// WithExponentialBackoff is an option for NewRetryPolicyWithOptions that sets the exponential backoff base,
|
||||
// minimum and maximum sleep between attempts, and next duration function.
|
||||
// Therefore, WithExponentialBackoff is a combination of WithNextDuration, withMinSleepBetween, withMaxSleepBetween,
|
||||
// and withExponentialBackoffBase.
|
||||
func WithExponentialBackoff(newMaxSleepBetween time.Duration, newExponentialBackoffBase float64) RetryPolicyOption {
|
||||
exponentialBackoffWithJitter := func(r OCIOperationResponse) time.Duration {
|
||||
sleepTime := getBackoffWithoutJitterHelper(defaultMinSleepBetween, newMaxSleepBetween.Seconds(), newExponentialBackoffBase, r.AttemptNumber)
|
||||
nextDuration := time.Duration(1000.0*(sleepTime+rand.Float64())) * time.Millisecond
|
||||
Debugln(fmt.Sprintf("NextDuration for attempt %v: sleepTime = %.1fs, nextDuration = %v", r.AttemptNumber, sleepTime, nextDuration))
|
||||
return nextDuration
|
||||
}
|
||||
|
||||
// this is the RetryPolicyOption function type
|
||||
return func(rp *RetryPolicy) {
|
||||
withMinSleepBetween(0)(rp)
|
||||
withMaxSleepBetween(newMaxSleepBetween)(rp)
|
||||
withExponentialBackoffBase(newExponentialBackoffBase)(rp)
|
||||
WithNextDuration(exponentialBackoffWithJitter)(rp)
|
||||
}
|
||||
}
|
||||
|
||||
// WithFixedBackoff is an option for NewRetryPolicyWithOptions that sets the backoff to always be exactly the same value. There is no jitter either.
|
||||
// Therefore, WithFixedBackoff is a combination of WithNextDuration, withMinSleepBetween, withMaxSleepBetween, and withExponentialBackoffBase.
|
||||
func WithFixedBackoff(newSleepBetween time.Duration) RetryPolicyOption {
|
||||
fixedBackoffWithoutJitter := func(r OCIOperationResponse) time.Duration {
|
||||
nextDuration := newSleepBetween
|
||||
Debugln(fmt.Sprintf("NextDuration for attempt %v: nextDuration = %v", r.AttemptNumber, nextDuration))
|
||||
return nextDuration
|
||||
}
|
||||
|
||||
// this is the RetryPolicyOption function type
|
||||
return func(rp *RetryPolicy) {
|
||||
withMinSleepBetween(newSleepBetween)(rp)
|
||||
withMaxSleepBetween(newSleepBetween)(rp)
|
||||
withExponentialBackoffBase(1.0)(rp)
|
||||
WithNextDuration(fixedBackoffWithoutJitter)(rp)
|
||||
}
|
||||
}
|
||||
|
||||
// WithEventualConsistency is the option for NewRetryPolicyWithOptions that enables considering eventual backoff for the policy.
|
||||
func WithEventualConsistency() RetryPolicyOption {
|
||||
// this is the RetryPolicyOption function type
|
||||
return func(rp *RetryPolicy) {
|
||||
copy := RetryPolicy{
|
||||
MaximumNumberAttempts: rp.MaximumNumberAttempts,
|
||||
ShouldRetryOperation: rp.ShouldRetryOperation,
|
||||
NextDuration: rp.NextDuration,
|
||||
MinSleepBetween: rp.MinSleepBetween,
|
||||
MaxSleepBetween: rp.MaxSleepBetween,
|
||||
ExponentialBackoffBase: rp.ExponentialBackoffBase,
|
||||
DeterminePolicyToUse: rp.DeterminePolicyToUse,
|
||||
NonEventuallyConsistentPolicy: rp.NonEventuallyConsistentPolicy,
|
||||
}
|
||||
ecrp := EventuallyConsistentRetryPolicy(copy)
|
||||
rp.MaximumNumberAttempts = ecrp.MaximumNumberAttempts
|
||||
rp.ShouldRetryOperation = ecrp.ShouldRetryOperation
|
||||
rp.NextDuration = ecrp.NextDuration
|
||||
rp.MinSleepBetween = ecrp.MinSleepBetween
|
||||
rp.MaxSleepBetween = ecrp.MaxSleepBetween
|
||||
rp.ExponentialBackoffBase = ecrp.ExponentialBackoffBase
|
||||
rp.DeterminePolicyToUse = ecrp.DeterminePolicyToUse
|
||||
rp.NonEventuallyConsistentPolicy = ecrp.NonEventuallyConsistentPolicy
|
||||
}
|
||||
}
|
||||
|
||||
// WithConditionalOption is an option for NewRetryPolicyWithOptions that enables or disables another option.
|
||||
func WithConditionalOption(enabled bool, otherOption RetryPolicyOption) RetryPolicyOption {
|
||||
// this is the RetryPolicyOption function type
|
||||
return func(rp *RetryPolicy) {
|
||||
if enabled {
|
||||
otherOption(rp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ReplaceWithValuesFromRetryPolicy is an option for NewRetryPolicyWithOptions that copies over all settings from another RetryPolicy
|
||||
func ReplaceWithValuesFromRetryPolicy(other RetryPolicy) RetryPolicyOption {
|
||||
// this is the RetryPolicyOption function type
|
||||
return func(rp *RetryPolicy) {
|
||||
rp.MaximumNumberAttempts = other.MaximumNumberAttempts
|
||||
rp.ShouldRetryOperation = other.ShouldRetryOperation
|
||||
rp.NextDuration = other.NextDuration
|
||||
rp.MinSleepBetween = other.MinSleepBetween
|
||||
rp.MaxSleepBetween = other.MaxSleepBetween
|
||||
rp.ExponentialBackoffBase = other.ExponentialBackoffBase
|
||||
rp.DeterminePolicyToUse = other.DeterminePolicyToUse
|
||||
rp.NonEventuallyConsistentPolicy = other.NonEventuallyConsistentPolicy
|
||||
rp.MaximumCumulativeBackoffWithoutJitter = other.MaximumCumulativeBackoffWithoutJitter
|
||||
}
|
||||
}
|
||||
|
||||
// shouldContinueIssuingRequests returns true if we should continue retrying a request, based on the current attempt
|
||||
// number and the maximum number of attempts specified, or false otherwise.
|
||||
func shouldContinueIssuingRequests(current, maximum uint) bool {
|
||||
return maximum == UnlimitedNumAttemptsValue || current <= maximum
|
||||
}
|
||||
|
||||
// RetryToken generates a retry token that must be included on any request passed to the Retry method.
|
||||
func RetryToken() string {
|
||||
alphanumericChars := []rune("abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
retryToken := make([]rune, generatedRetryTokenLength)
|
||||
for i := range retryToken {
|
||||
retryToken[i] = alphanumericChars[rand.Intn(len(alphanumericChars))]
|
||||
}
|
||||
return string(retryToken)
|
||||
}
|
||||
|
||||
func determinePolicyToUse(policy RetryPolicy) (RetryPolicy, *time.Time, float64) {
|
||||
initialAttemptTime := EcContext.timeNowProvider()
|
||||
var useDefaultTimingInstead = true
|
||||
var endOfWindowTime = (*time.Time)(nil)
|
||||
var backoffScalingFactor = 1.0
|
||||
var policyToUse = policy
|
||||
|
||||
eowt := EcContext.GetEndOfWindow()
|
||||
if eowt != nil {
|
||||
// there was an eventually consistent request
|
||||
if eowt.After(initialAttemptTime) {
|
||||
// and the eventually consistent effects may still be present
|
||||
endOfWindowTime = eowt
|
||||
// if the time between now and the end of the window is less than the time we normally would retry, use the default timing
|
||||
durationToEndOfWindow := endOfWindowTime.Sub(initialAttemptTime)
|
||||
maxCumulativeBackoffWithoutJitter := GetMaximumCumulativeBackoffWithoutJitter(*policy.NonEventuallyConsistentPolicy)
|
||||
Debugln(fmt.Sprintf("durationToEndOfWindow = %v, maxCumulativeBackoffWithoutJitter = %v", durationToEndOfWindow, maxCumulativeBackoffWithoutJitter))
|
||||
if durationToEndOfWindow > maxCumulativeBackoffWithoutJitter {
|
||||
// the end of the eventually consistent window is later than when default retries would end
|
||||
// do not use default timing
|
||||
maximumCumulativeBackoffWithoutJitter := GetMaximumCumulativeEventuallyConsistentBackoffWithoutJitter(policy)
|
||||
backoffScalingFactor = float64(durationToEndOfWindow) / float64(maximumCumulativeBackoffWithoutJitter)
|
||||
useDefaultTimingInstead = false
|
||||
Debugln(fmt.Sprintf("Use eventually consistent timing, durationToEndOfWindow = %v, maximumCumulativeBackoffWithoutJitter = %v, backoffScalingFactor = %.2f",
|
||||
durationToEndOfWindow, maximumCumulativeBackoffWithoutJitter, backoffScalingFactor))
|
||||
} else {
|
||||
Debugln("Use default timing, end of EC window is sooner than default retries")
|
||||
}
|
||||
} else {
|
||||
useDefaultTimingInstead = false
|
||||
policyToUse = *policy.NonEventuallyConsistentPolicy
|
||||
Debugln("Use default timing and strategy, end of EC window is in the past")
|
||||
}
|
||||
} else {
|
||||
useDefaultTimingInstead = false
|
||||
policyToUse = *policy.NonEventuallyConsistentPolicy
|
||||
Debugln("Use default timing and strategy, no EC window set")
|
||||
}
|
||||
|
||||
if useDefaultTimingInstead {
|
||||
// use timing from defaultRetryPolicy, but whether to retry from the policy that was passed into this request
|
||||
policyToUse = NewRetryPolicyWithOptions(
|
||||
ReplaceWithValuesFromRetryPolicy(*policy.NonEventuallyConsistentPolicy),
|
||||
WithShouldRetryOperation(policy.ShouldRetryOperation))
|
||||
}
|
||||
|
||||
return policyToUse, endOfWindowTime, backoffScalingFactor
|
||||
}
|
||||
|
||||
// Retry is a package-level operation that executes the retryable request using the specified operation and retry policy.
|
||||
func Retry(ctx context.Context, request OCIRetryableRequest, operation OCIOperation, policy RetryPolicy) (OCIResponse, error) {
|
||||
type retrierResult struct {
|
||||
response OCIResponse
|
||||
err error
|
||||
}
|
||||
|
||||
var response OCIResponse
|
||||
var err error
|
||||
retrierChannel := make(chan retrierResult, 1)
|
||||
|
||||
validated, validateError := policy.validate()
|
||||
if !validated {
|
||||
return nil, validateError
|
||||
}
|
||||
|
||||
initialAttemptTime := time.Now()
|
||||
|
||||
go func() {
|
||||
// Deal with panics more graciously
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
stackBuffer := make([]byte, 1024)
|
||||
bytesWritten := runtime.Stack(stackBuffer, false)
|
||||
stack := string(stackBuffer[:bytesWritten])
|
||||
error := fmt.Errorf("panicked while retrying operation. Panic was: %s\nStack: %s", r, stack)
|
||||
Debugln(error)
|
||||
retrierChannel <- retrierResult{nil, error}
|
||||
}
|
||||
}()
|
||||
// if request body is binary request body and seekable, save the current position
|
||||
var curPos int64 = 0
|
||||
isSeekable := false
|
||||
rsc, isBinaryRequest := request.BinaryRequestBody()
|
||||
if rsc != nil && rsc.rc != nil {
|
||||
defer rsc.rc.Close()
|
||||
}
|
||||
if policy.MaximumNumberAttempts != uint(1) {
|
||||
if rsc.Seekable() {
|
||||
isSeekable = true
|
||||
curPos, _ = rsc.Seek(0, io.SeekCurrent)
|
||||
}
|
||||
}
|
||||
|
||||
// some legacy code constructing RetryPolicy instances directly may not have set DeterminePolicyToUse.
|
||||
// In that case, just assume that it doesn't handle eventual consistency.
|
||||
if policy.DeterminePolicyToUse == nil {
|
||||
policy.DeterminePolicyToUse = returnSamePolicy
|
||||
}
|
||||
|
||||
// this determines which policy to use, when the eventual consistency window ends, and what the backoff
|
||||
// scaling factor should be
|
||||
policyToUse, endOfWindowTime, backoffScalingFactor := policy.DeterminePolicyToUse(policy)
|
||||
Debugln(fmt.Sprintf("Retry policy to use: %v", policyToUse))
|
||||
retryStartTime := time.Now()
|
||||
extraHeaders := make(map[string]string)
|
||||
|
||||
if policy.MaximumNumberAttempts == 1 {
|
||||
extraHeaders[requestHeaderOpcClientRetries] = "false"
|
||||
} else {
|
||||
extraHeaders[requestHeaderOpcClientRetries] = "true"
|
||||
}
|
||||
|
||||
// use a one-based counter because it's easier to think about operation retry in terms of attempt numbering
|
||||
for currentOperationAttempt := uint(1); shouldContinueIssuingRequests(currentOperationAttempt, policyToUse.MaximumNumberAttempts); currentOperationAttempt++ {
|
||||
Debugln(fmt.Sprintf("operation attempt #%v", currentOperationAttempt))
|
||||
// rewind body once needed
|
||||
if isSeekable {
|
||||
rsc = NewOCIReadSeekCloser(rsc.rc)
|
||||
rsc.Seek(curPos, io.SeekStart)
|
||||
}
|
||||
response, err = operation(ctx, request, rsc, extraHeaders)
|
||||
|
||||
operationResponse := NewOCIOperationResponseExtended(response, err, currentOperationAttempt, endOfWindowTime, backoffScalingFactor, initialAttemptTime)
|
||||
|
||||
if !policyToUse.ShouldRetryOperation(operationResponse) {
|
||||
// we should NOT retry operation based on response and/or error => return
|
||||
retrierChannel <- retrierResult{response, err}
|
||||
return
|
||||
}
|
||||
|
||||
// if the request body type is stream, requested retry but doesn't resettable, throw error and stop retrying
|
||||
if isBinaryRequest && !isSeekable {
|
||||
retrierChannel <- retrierResult{response, NonSeekableRequestRetryFailure{err}}
|
||||
return
|
||||
}
|
||||
|
||||
duration := policyToUse.NextDuration(operationResponse)
|
||||
//The following condition is kept for backwards compatibility reasons
|
||||
if deadline, ok := ctx.Deadline(); ok && EcContext.timeNowProvider().Add(duration).After(deadline) {
|
||||
// we want to retry the operation, but the policy is telling us to wait for a duration that exceeds
|
||||
// the specified overall deadline for the operation => instead of waiting for however long that
|
||||
// time period is and then aborting, abort now and save the cycles
|
||||
retrierChannel <- retrierResult{response, DeadlineExceededByBackoff}
|
||||
return
|
||||
}
|
||||
Debugln(fmt.Sprintf("waiting %v before retrying operation", duration))
|
||||
// sleep before retrying the operation
|
||||
<-time.After(duration)
|
||||
}
|
||||
retryEndTime := time.Now()
|
||||
Debugln(fmt.Sprintf("Total Latency for this API call is: %v ms", retryEndTime.Sub(retryStartTime).Milliseconds()))
|
||||
retrierChannel <- retrierResult{response, err}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return response, ctx.Err()
|
||||
case result := <-retrierChannel:
|
||||
return result.response, result.err
|
||||
}
|
||||
}
|
||||
92
vendor/github.com/oracle/oci-go-sdk/v65/common/sseReader.go
generated
vendored
Normal file
92
vendor/github.com/oracle/oci-go-sdk/v65/common/sseReader.go
generated
vendored
Normal file
@@ -0,0 +1,92 @@
|
||||
// Copyright (c) 2016, 2018, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
// This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type SseReader struct {
|
||||
HttpBody io.ReadCloser
|
||||
eventScanner bufio.Scanner
|
||||
OnClose func(r *SseReader)
|
||||
}
|
||||
|
||||
// InvalidSSEResponseError returned in the case that a nil response body was given
|
||||
// to NewSSEReader()
|
||||
type InvalidSSEResponseError struct {
|
||||
}
|
||||
|
||||
const InvalidResponseErrorMessage = "invalid response struct given to NewSSEReader"
|
||||
|
||||
func (e InvalidSSEResponseError) Error() string {
|
||||
return InvalidResponseErrorMessage
|
||||
}
|
||||
|
||||
// NewSSEReader returns an SSE Reader given an sse response
|
||||
func NewSSEReader(response *http.Response) (*SseReader, error) {
|
||||
|
||||
if response == nil || response.Body == nil {
|
||||
return nil, InvalidSSEResponseError{}
|
||||
}
|
||||
|
||||
reader := &SseReader{
|
||||
HttpBody: response.Body,
|
||||
eventScanner: *bufio.NewScanner(response.Body),
|
||||
OnClose: func(r *SseReader) { r.HttpBody.Close() }, // Default on close function, ensures body is closed after use
|
||||
}
|
||||
return reader, nil
|
||||
}
|
||||
|
||||
// Take the response in bytes and trim it if necessary
|
||||
func processEvent(e []byte) []byte {
|
||||
e = bytes.TrimPrefix(e, []byte("data: ")) // Text/event-stream always prefixed with 'data: '
|
||||
return e
|
||||
}
|
||||
|
||||
// ReadNextEvent reads the next event in the stream, return it unmarshalled
|
||||
func (r *SseReader) ReadNextEvent() (event []byte, err error) {
|
||||
if r.eventScanner.Scan() {
|
||||
eventBytes := r.eventScanner.Bytes()
|
||||
return processEvent(eventBytes), nil
|
||||
} else {
|
||||
|
||||
// Close out the stream since we are finished reading from it
|
||||
if r.OnClose != nil {
|
||||
r.OnClose(r)
|
||||
}
|
||||
|
||||
err := r.eventScanner.Err()
|
||||
if err == context.Canceled || err == nil {
|
||||
err = io.EOF
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ReadAllEvents reads all events from the response stream, and processes each with given event handler
|
||||
func (r *SseReader) ReadAllEvents(eventHandler func(e []byte)) error {
|
||||
for {
|
||||
|
||||
event, err := r.ReadNextEvent()
|
||||
|
||||
if err != nil {
|
||||
|
||||
if err == io.EOF {
|
||||
err = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Ignore empty events
|
||||
if len(event) > 0 {
|
||||
eventHandler(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
156
vendor/github.com/oracle/oci-go-sdk/v65/common/tls_config_provider.go
generated
vendored
Normal file
156
vendor/github.com/oracle/oci-go-sdk/v65/common/tls_config_provider.go
generated
vendored
Normal file
@@ -0,0 +1,156 @@
|
||||
// Copyright (c) 2016, 2018, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
// This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"os"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// GetTLSConfigTemplateForTransport returns the TLSConfigTemplate to used depending on whether any additional
|
||||
// CA Bundle or client side certs have been configured
|
||||
func GetTLSConfigTemplateForTransport() TLSConfigProvider {
|
||||
certPath := os.Getenv(ociDefaultClientCertsPath)
|
||||
keyPath := os.Getenv(ociDefaultClientCertsPrivateKeyPath)
|
||||
caBundlePath := os.Getenv(ociDefaultCertsPath)
|
||||
if certPath != "" && keyPath != "" {
|
||||
return &DefaultMTLSConfigProvider{
|
||||
caBundlePath: caBundlePath,
|
||||
clientCertPath: certPath,
|
||||
clientKeyPath: keyPath,
|
||||
watchedFilesStatsMap: make(map[string]os.FileInfo),
|
||||
}
|
||||
}
|
||||
return &DefaultTLSConfigProvider{
|
||||
caBundlePath: caBundlePath,
|
||||
}
|
||||
}
|
||||
|
||||
// TLSConfigProvider is an interface the defines a function that creates a new *tls.Config.
|
||||
type TLSConfigProvider interface {
|
||||
NewOrDefault() (*tls.Config, error)
|
||||
WatchedFilesModified() bool
|
||||
}
|
||||
|
||||
// DefaultTLSConfigProvider is a provider that provides a TLS tls.config for the HTTPTransport
|
||||
type DefaultTLSConfigProvider struct {
|
||||
caBundlePath string
|
||||
mux sync.Mutex
|
||||
currentStat os.FileInfo
|
||||
}
|
||||
|
||||
// NewOrDefault returns a default tls.Config which
|
||||
// sets its RootCAs to be a *x509.CertPool from caBundlePath.
|
||||
func (t *DefaultTLSConfigProvider) NewOrDefault() (*tls.Config, error) {
|
||||
if t.caBundlePath == "" {
|
||||
return &tls.Config{}, nil
|
||||
}
|
||||
|
||||
// Keep the current Stat info from the ca bundle in a map
|
||||
Debugf("Getting Initial Stats for file: %s", t.caBundlePath)
|
||||
caBundleStat, err := os.Stat(t.caBundlePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.mux.Lock()
|
||||
defer t.mux.Unlock()
|
||||
t.currentStat = caBundleStat
|
||||
|
||||
rootCAs, err := CertPoolFrom(t.caBundlePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &tls.Config{
|
||||
RootCAs: rootCAs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// WatchedFilesModified returns true if any files in the watchedFilesStatsMap has been modified else returns false
|
||||
func (t *DefaultTLSConfigProvider) WatchedFilesModified() bool {
|
||||
modified := false
|
||||
if t.caBundlePath != "" {
|
||||
newStat, err := os.Stat(t.caBundlePath)
|
||||
if err == nil && (t.currentStat.Size() != newStat.Size() || t.currentStat.ModTime() != newStat.ModTime()) {
|
||||
Logf("Modification detected in cert/ca-bundle file: %s", t.caBundlePath)
|
||||
modified = true
|
||||
t.mux.Lock()
|
||||
defer t.mux.Unlock()
|
||||
t.currentStat = newStat
|
||||
}
|
||||
}
|
||||
return modified
|
||||
}
|
||||
|
||||
// DefaultMTLSConfigProvider is a provider that provides a MTLS tls.config for the HTTPTransport
|
||||
type DefaultMTLSConfigProvider struct {
|
||||
caBundlePath string
|
||||
clientCertPath string
|
||||
clientKeyPath string
|
||||
mux sync.Mutex
|
||||
watchedFilesStatsMap map[string]os.FileInfo
|
||||
}
|
||||
|
||||
// NewOrDefault returns a default tls.Config which sets its RootCAs
|
||||
// to be a *x509.CertPool from caBundlePath and calls
|
||||
// tls.LoadX509KeyPair(clientCertPath, clientKeyPath) to set mtls client certs.
|
||||
func (t *DefaultMTLSConfigProvider) NewOrDefault() (*tls.Config, error) {
|
||||
rootCAs, err := CertPoolFrom(t.caBundlePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cert, err := tls.LoadX509KeyPair(t.clientCertPath, t.clientKeyPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Configure the initial certs file stats, error skipped because we error out before this if the files don't exist
|
||||
t.mux.Lock()
|
||||
defer t.mux.Unlock()
|
||||
t.watchedFilesStatsMap[t.caBundlePath], _ = os.Stat(t.caBundlePath)
|
||||
t.watchedFilesStatsMap[t.clientCertPath], _ = os.Stat(t.clientCertPath)
|
||||
t.watchedFilesStatsMap[t.clientKeyPath], _ = os.Stat(t.clientKeyPath)
|
||||
|
||||
return &tls.Config{
|
||||
RootCAs: rootCAs,
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// WatchedFilesModified returns true if any files in the watchedFilesStatsMap has been modified else returns false
|
||||
func (t *DefaultMTLSConfigProvider) WatchedFilesModified() bool {
|
||||
modified := false
|
||||
|
||||
t.mux.Lock()
|
||||
defer t.mux.Unlock()
|
||||
for k, v := range t.watchedFilesStatsMap {
|
||||
if k != "" {
|
||||
currentStat, err := os.Stat(k)
|
||||
if err == nil && (v.Size() != currentStat.Size() || v.ModTime() != currentStat.ModTime()) {
|
||||
modified = true
|
||||
Logf("Modification detected in cert/ca-bundle file: %s", k)
|
||||
t.watchedFilesStatsMap[k] = currentStat
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return modified
|
||||
}
|
||||
|
||||
// CertPoolFrom creates a new x509.CertPool from a given file.
|
||||
func CertPoolFrom(caBundleFile string) (*x509.CertPool, error) {
|
||||
pemCerts, err := os.ReadFile(caBundleFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
trust := x509.NewCertPool()
|
||||
if !trust.AppendCertsFromPEM(pemCerts) {
|
||||
return nil, fmt.Errorf("creating a new x509.CertPool from %s: no certs added", caBundleFile)
|
||||
}
|
||||
|
||||
return trust, nil
|
||||
}
|
||||
53
vendor/github.com/oracle/oci-go-sdk/v65/common/transport_template_provider.go
generated
vendored
Normal file
53
vendor/github.com/oracle/oci-go-sdk/v65/common/transport_template_provider.go
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright (c) 2016, 2018, 2025, Oracle and/or its affiliates. All rights reserved.
|
||||
// This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TransportTemplateProvider defines a function that creates a new http transport
|
||||
// from a given TLS client config.
|
||||
type TransportTemplateProvider func(tlsClientConfig *tls.Config) (http.RoundTripper, error)
|
||||
|
||||
// NewOrDefault creates a new TransportTemplate
|
||||
// If t is nil, then DefaultTransport is returned
|
||||
func (t TransportTemplateProvider) NewOrDefault(tlsClientConfig *tls.Config) (http.RoundTripper, error) {
|
||||
if t == nil {
|
||||
return DefaultTransport(tlsClientConfig)
|
||||
}
|
||||
return t(tlsClientConfig)
|
||||
}
|
||||
|
||||
// DefaultTransport creates a clone of http.DefaultTransport
|
||||
// and applies the tlsClientConfig on top of it.
|
||||
// The result is never nil, to prevent panics in client code.
|
||||
// Never returns any errors, but needs to return an error
|
||||
// to adhere to TransportTemplate interface.
|
||||
func DefaultTransport(tlsClientConfig *tls.Config) (*http.Transport, error) {
|
||||
transport := CloneHTTPDefaultTransport()
|
||||
if isExpectHeaderDisabled := IsEnvVarFalse(UsingExpectHeaderEnvVar); !isExpectHeaderDisabled {
|
||||
transport.Proxy = http.ProxyFromEnvironment
|
||||
transport.DialContext = (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
DualStack: true,
|
||||
}).DialContext
|
||||
transport.ForceAttemptHTTP2 = true
|
||||
transport.MaxIdleConns = 100
|
||||
transport.IdleConnTimeout = 90 * time.Second
|
||||
transport.TLSHandshakeTimeout = 10 * time.Second
|
||||
transport.ExpectContinueTimeout = 3 * time.Second
|
||||
}
|
||||
transport.TLSClientConfig = tlsClientConfig
|
||||
return transport, nil
|
||||
}
|
||||
|
||||
// CloneHTTPDefaultTransport returns a clone of http.DefaultTransport.
|
||||
func CloneHTTPDefaultTransport() *http.Transport {
|
||||
return http.DefaultTransport.(*http.Transport).Clone()
|
||||
}
|
||||
37
vendor/github.com/oracle/oci-go-sdk/v65/common/version.go
generated
vendored
Normal file
37
vendor/github.com/oracle/oci-go-sdk/v65/common/version.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright (c) 2016, 2018, 2020, Oracle and/or its affiliates. All rights reserved.
|
||||
// This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl or Apache License 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose either license.
|
||||
// Code generated by go generate; DO NOT EDIT
|
||||
|
||||
package common
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
major = "65"
|
||||
minor = "95"
|
||||
patch = "2"
|
||||
tag = ""
|
||||
)
|
||||
|
||||
var once sync.Once
|
||||
var version string
|
||||
|
||||
// Version returns semantic version of the sdk
|
||||
func Version() string {
|
||||
once.Do(func() {
|
||||
ver := fmt.Sprintf("%s.%s.%s", major, minor, patch)
|
||||
verBuilder := bytes.NewBufferString(ver)
|
||||
if tag != "" && tag != "-" {
|
||||
_, err := verBuilder.WriteString(tag)
|
||||
if err != nil {
|
||||
verBuilder = bytes.NewBufferString(ver)
|
||||
}
|
||||
}
|
||||
version = verBuilder.String()
|
||||
})
|
||||
return version
|
||||
}
|
||||
Reference in New Issue
Block a user