fix: add involves edge from task to agent:nomos at creation
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled
ci / web (push) Has been cancelled
Desktop App / Build Linux (amd64) (push) Has been cancelled
Desktop App / Attach to Release (push) Has been cancelled

Plus sync vendor directory for Docker build compatibility.
This commit is contained in:
2026-08-11 22:03:12 +02:00
parent 7d6a3320d4
commit febc153b7f
3384 changed files with 945212 additions and 2 deletions

102
vendor/github.com/oracle/oci-go-sdk/v65/LICENSE.txt generated vendored Normal file
View File

@@ -0,0 +1,102 @@
Copyright (c) 2016, 2023, 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.
 ____________________________
Copyright (c) 2016, 2023 Oracle and/or its affiliates.
The Universal Permissive License (UPL), Version 1.0
Subject to the condition set forth below, permission is hereby granted to any
person obtaining a copy of this software, associated documentation and/or data
(collectively the "Software"), free of charge and under any and all copyright
rights in the Software, and any and all patent rights owned or freely
licensable by each licensor hereunder covering either (i) the unmodified
Software as contributed to or provided by such licensor, or (ii) the Larger
Works (as defined below), to deal in both
(a) the Software, and
(b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
one is included with the Software (each a "Larger Work" to which the Software
is contributed by such licensors),
without restriction, including without limitation the rights to copy, create
derivative works of, display, perform, and distribute the Software and make,
use, sell, offer for sale, import, export, have made, and have sold the
Software and the Larger Work(s), and to sublicense the foregoing rights on
either these or other terms.
This license is subject to the following condition:
The above copyright notice and either this complete permission notice or at
a minimum a reference to the UPL must be included in all copies or
substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
The Apache Software License, Version 2.0
Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); You may not use this product except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0. A copy of the license is also reproduced below. Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of this License; and
You must cause any modified files to carry prominent notices stating that You changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

1
vendor/github.com/oracle/oci-go-sdk/v65/NOTICE.txt generated vendored Normal file
View File

@@ -0,0 +1 @@
Copyright (c) 2016, 2018, 2020, Oracle and/or its affiliates.

View 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

View 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
}

View 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(&region):
case setRegionMetadataFromEnvVar(&region):
case setRegionFromInstanceMetadataService(&region):
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), &regionSchema); 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, &regionJSON)
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, &regionInfo)
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
}

View 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
}

View 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
}

View 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: &timestampFileName,
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)
}

View 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

File diff suppressed because it is too large Load Diff

View 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
View 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()
}
}

View 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
}

View 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",
}

View 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
View 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
}
}

View 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)
}
}
}

View 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
}

View 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()
}

View 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
}