chore(deps): bump github.com/Azure/azure-sdk-for-go/sdk/storage/azblob (#3114)

Bumps [github.com/Azure/azure-sdk-for-go/sdk/storage/azblob](https://github.com/Azure/azure-sdk-for-go) from 1.3.1 to 1.3.2.
- [Release notes](https://github.com/Azure/azure-sdk-for-go/releases)
- [Changelog](https://github.com/Azure/azure-sdk-for-go/blob/main/documentation/release.md)
- [Commits](https://github.com/Azure/azure-sdk-for-go/compare/sdk/azcore/v1.3.1...sdk/storage/azblob/v1.3.2)

---
updated-dependencies:
- dependency-name: github.com/Azure/azure-sdk-for-go/sdk/storage/azblob
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
dependabot[bot] 2024-04-19 10:33:38 -07:00 committed by GitHub
parent 8ee87b542d
commit 4a516dbae3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
38 changed files with 366 additions and 101 deletions

4
go.mod
View File

@ -4,7 +4,7 @@ go 1.22
require (
cloud.google.com/go/storage v1.40.0
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.1
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2
github.com/aws/aws-sdk-go-v2 v1.26.1
github.com/aws/aws-sdk-go-v2/config v1.27.11
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.15
@ -151,7 +151,7 @@ require (
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 // indirect
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.2 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 // indirect
github.com/Microsoft/hcsshim v0.11.4 // indirect
github.com/aws/aws-sdk-go-v2/service/ecr v1.24.5 // indirect

8
go.sum
View File

@ -17,16 +17,16 @@ github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0 h1:59M
github.com/AdamKorcz/go-118-fuzz-build v0.0.0-20230306123547-8075edf89bb0/go.mod h1:OahwfttHWG6eJ0clwcfBAHoDI6X/LV/15hx/wlMZSrU=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU=
github.com/Azure/azure-sdk-for-go v68.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.2 h1:c4k2FIYIh4xtwqrQwV0Ct1v5+ehlNXj5NI/MWVsiTkQ=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.2/go.mod h1:5FDJtLEO/GxwNgUxbwrY3LP0pEoThTQJtk2oysdXHxM=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 h1:sO0/P7g68FrryJzljemN+6GTssUXdANk6aJ7T1ZxnsQ=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 h1:LqbJ/WzJUwBf8UiaSzgX7aMclParm9/5Vgp+TY51uBQ=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.5.0 h1:AifHbc4mg0x9zW52WOpKbsHaDKuRhlI7TVl47thgQ70=
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.5.0/go.mod h1:T5RfihdXtBDxt1Ch2wobif3TvzTdumDy29kahv6AV9A=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.1 h1:fXPMAmuh0gDuRDey0atC8cXBuKIlqCzCkL8sm1n9Ov0=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.1/go.mod h1:SUZc9YRRHfx2+FAQKNDGrssXehqLpxmwRv2mC/5ntj4=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2 h1:YUUxeiOWgdAQE3pXt2H7QXzZs0q8UBjgRbl56qo8GYM=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2/go.mod h1:dmXQgZuiSubAecswZE+Sm8jkvEa7kQgTPVRvwL/nd0E=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=

View File

@ -1,5 +1,46 @@
# Release History
## 1.11.1 (2024-04-02)
### Bugs Fixed
* Pollers that use the `Location` header won't consider `http.StatusRequestTimeout` a terminal failure.
* `runtime.Poller[T].Result` won't consider non-terminal error responses as terminal.
## 1.11.0 (2024-04-01)
### Features Added
* Added `StatusCodes` to `arm/policy.RegistrationOptions` to allow supporting non-standard HTTP status codes during registration.
* Added field `InsecureAllowCredentialWithHTTP` to `azcore.ClientOptions` and dependent authentication pipeline policies.
* Added type `MultipartContent` to the `streaming` package to support multipart/form payloads with custom Content-Type and file name.
### Bugs Fixed
* `runtime.SetMultipartFormData` won't try to stringify `[]byte` values.
* Pollers that use the `Location` header won't consider `http.StatusTooManyRequests` a terminal failure.
### Other Changes
* Update dependencies.
## 1.10.0 (2024-02-29)
### Features Added
* Added logging event `log.EventResponseError` that will contain the contents of `ResponseError.Error()` whenever an `azcore.ResponseError` is created.
* Added `runtime.NewResponseErrorWithErrorCode` for creating an `azcore.ResponseError` with a caller-supplied error code.
* Added type `MatchConditions` for use in conditional requests.
### Bugs Fixed
* Fixed a potential race condition between `NullValue` and `IsNullValue`.
* `runtime.EncodeQueryParams` will escape semicolons before calling `url.ParseQuery`.
### Other Changes
* Update dependencies.
## 1.9.2 (2024-02-06)
### Bugs Fixed

View File

@ -23,7 +23,7 @@ pr:
- sdk/azcore/
- eng/
stages:
- template: /eng/pipelines/templates/jobs/archetype-sdk-client.yml
extends:
template: /eng/pipelines/templates/jobs/archetype-sdk-client.yml
parameters:
ServiceDirectory: azcore

View File

@ -8,6 +8,7 @@ package azcore
import (
"reflect"
"sync"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared"
@ -41,13 +42,28 @@ func NewSASCredential(sas string) *SASCredential {
}
// holds sentinel values used to send nulls
var nullables map[reflect.Type]interface{} = map[reflect.Type]interface{}{}
var nullables map[reflect.Type]any = map[reflect.Type]any{}
var nullablesMu sync.RWMutex
// NullValue is used to send an explicit 'null' within a request.
// This is typically used in JSON-MERGE-PATCH operations to delete a value.
func NullValue[T any]() T {
t := shared.TypeOfT[T]()
nullablesMu.RLock()
v, found := nullables[t]
nullablesMu.RUnlock()
if found {
// return the sentinel object
return v.(T)
}
// promote to exclusive lock and check again (double-checked locking pattern)
nullablesMu.Lock()
defer nullablesMu.Unlock()
v, found = nullables[t]
if !found {
var o reflect.Value
if k := t.Kind(); k == reflect.Map {
@ -72,6 +88,9 @@ func NullValue[T any]() T {
func IsNullValue[T any](v T) bool {
// see if our map has a sentinel object for this *T
t := reflect.TypeOf(v)
nullablesMu.RLock()
defer nullablesMu.RUnlock()
if o, found := nullables[t]; found {
o1 := reflect.ValueOf(o)
v1 := reflect.ValueOf(v)

View File

@ -46,3 +46,12 @@ func (e ETag) WeakEquals(other ETag) bool {
func (e ETag) IsWeak() bool {
return len(e) >= 4 && strings.HasPrefix(string(e), "W/\"") && strings.HasSuffix(string(e), "\"")
}
// MatchConditions specifies HTTP options for conditional requests.
type MatchConditions struct {
// Optionally limit requests to resources that have a matching ETag.
IfMatch *ETag
// Optionally limit requests to resources that do not match the ETag.
IfNoneMatch *ETag
}

View File

@ -51,15 +51,15 @@ type Request struct {
values opValues
}
type opValues map[reflect.Type]interface{}
type opValues map[reflect.Type]any
// Set adds/changes a value
func (ov opValues) set(value interface{}) {
func (ov opValues) set(value any) {
ov[reflect.TypeOf(value)] = value
}
// Get looks for a value set by SetValue first
func (ov opValues) get(value interface{}) bool {
func (ov opValues) get(value any) bool {
v, ok := ov[reflect.ValueOf(value).Elem().Type()]
if ok {
reflect.ValueOf(value).Elem().Set(reflect.ValueOf(v))
@ -108,7 +108,7 @@ func (req *Request) Next() (*http.Response, error) {
}
// SetOperationValue adds/changes a mutable key/value associated with a single operation.
func (req *Request) SetOperationValue(value interface{}) {
func (req *Request) SetOperationValue(value any) {
if req.values == nil {
req.values = opValues{}
}
@ -116,7 +116,7 @@ func (req *Request) SetOperationValue(value interface{}) {
}
// OperationValue looks for a value set by SetOperationValue().
func (req *Request) OperationValue(value interface{}) bool {
func (req *Request) OperationValue(value any) bool {
if req.values == nil {
return false
}

View File

@ -13,6 +13,7 @@ import (
"net/http"
"regexp"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/log"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared"
"github.com/Azure/azure-sdk-for-go/sdk/internal/exported"
)
@ -20,36 +21,45 @@ import (
// NewResponseError creates a new *ResponseError from the provided HTTP response.
// Exported as runtime.NewResponseError().
func NewResponseError(resp *http.Response) error {
respErr := &ResponseError{
StatusCode: resp.StatusCode,
RawResponse: resp,
}
// prefer the error code in the response header
if ec := resp.Header.Get(shared.HeaderXMSErrorCode); ec != "" {
respErr.ErrorCode = ec
return respErr
return NewResponseErrorWithErrorCode(resp, ec)
}
// if we didn't get x-ms-error-code, check in the response body
body, err := exported.Payload(resp, nil)
if err != nil {
// since we're not returning the ResponseError in this
// case we also don't want to write it to the log.
return err
}
var errorCode string
if len(body) > 0 {
if code := extractErrorCodeJSON(body); code != "" {
respErr.ErrorCode = code
} else if code := extractErrorCodeXML(body); code != "" {
respErr.ErrorCode = code
if fromJSON := extractErrorCodeJSON(body); fromJSON != "" {
errorCode = fromJSON
} else if fromXML := extractErrorCodeXML(body); fromXML != "" {
errorCode = fromXML
}
}
return NewResponseErrorWithErrorCode(resp, errorCode)
}
// NewResponseErrorWithErrorCode creates an *azcore.ResponseError from the provided HTTP response and errorCode.
// Exported as runtime.NewResponseErrorWithErrorCode().
func NewResponseErrorWithErrorCode(resp *http.Response, errorCode string) error {
respErr := &ResponseError{
ErrorCode: errorCode,
StatusCode: resp.StatusCode,
RawResponse: resp,
}
log.Write(log.EventResponseError, respErr.Error())
return respErr
}
func extractErrorCodeJSON(body []byte) string {
var rawObj map[string]interface{}
var rawObj map[string]any
if err := json.Unmarshal(body, &rawObj); err != nil {
// not a JSON object
return ""
@ -58,7 +68,7 @@ func extractErrorCodeJSON(body []byte) string {
// check if this is a wrapped error, i.e. { "error": { ... } }
// if so then unwrap it
if wrapped, ok := rawObj["error"]; ok {
unwrapped, ok := wrapped.(map[string]interface{})
unwrapped, ok := wrapped.(map[string]any)
if !ok {
return ""
}

View File

@ -17,22 +17,34 @@ type Event = log.Event
const (
EventRequest = azlog.EventRequest
EventResponse = azlog.EventResponse
EventResponseError = azlog.EventResponseError
EventRetryPolicy = azlog.EventRetryPolicy
EventLRO = azlog.EventLRO
)
// Write invokes the underlying listener with the specified event and message.
// If the event shouldn't be logged or there is no listener then Write does nothing.
func Write(cls log.Event, msg string) {
log.Write(cls, msg)
}
func Writef(cls log.Event, format string, a ...interface{}) {
// Writef invokes the underlying listener with the specified event and formatted message.
// If the event shouldn't be logged or there is no listener then Writef does nothing.
func Writef(cls log.Event, format string, a ...any) {
log.Writef(cls, format, a...)
}
// SetListener will set the Logger to write to the specified listener.
func SetListener(lst func(Event, string)) {
log.SetListener(lst)
}
// Should returns true if the specified log event should be written to the log.
// By default all log events will be logged. Call SetEvents() to limit
// the log events for logging.
// If no listener has been set this will return false.
// Calling this method is useful when the message to log is computationally expensive
// and you want to avoid the overhead if its log event is not enabled.
func Should(cls log.Event) bool {
return log.Should(cls)
}

View File

@ -27,7 +27,7 @@ func Applicable(resp *http.Response) bool {
}
// CanResume returns true if the token can rehydrate this poller type.
func CanResume(token map[string]interface{}) bool {
func CanResume(token map[string]any) bool {
_, ok := token["asyncURL"]
return ok
}

View File

@ -29,7 +29,7 @@ func Applicable(resp *http.Response) bool {
}
// CanResume returns true if the token can rehydrate this poller type.
func CanResume(token map[string]interface{}) bool {
func CanResume(token map[string]any) bool {
t, ok := token["type"]
if !ok {
return false

View File

@ -26,7 +26,7 @@ func Applicable(resp *http.Response) bool {
}
// CanResume returns true if the token can rehydrate this poller type.
func CanResume(token map[string]interface{}) bool {
func CanResume(token map[string]any) bool {
_, ok := token["fakeURL"]
return ok
}

View File

@ -28,7 +28,7 @@ func Applicable(resp *http.Response) bool {
}
// CanResume returns true if the token can rehydrate this poller type.
func CanResume(token map[string]interface{}) bool {
func CanResume(token map[string]any) bool {
t, ok := token["type"]
if !ok {
return false
@ -103,6 +103,10 @@ func (p *Poller[T]) Poll(ctx context.Context) (*http.Response, error) {
} else if resp.StatusCode > 199 && resp.StatusCode < 300 {
// any 2xx other than a 202 indicates success
p.CurState = poller.StatusSucceeded
} else if pollers.IsNonTerminalHTTPStatusCode(resp) {
// the request timed out or is being throttled.
// DO NOT include this as a terminal failure. preserve
// the existing state and return the response.
} else {
p.CurState = poller.StatusFailed
}

View File

@ -25,7 +25,7 @@ func Applicable(resp *http.Response) bool {
}
// CanResume returns true if the token can rehydrate this poller type.
func CanResume(token map[string]interface{}) bool {
func CanResume(token map[string]any) bool {
_, ok := token["oplocURL"]
return ok
}

View File

@ -74,7 +74,7 @@ func ExtractToken(token string) ([]byte, error) {
// IsTokenValid returns an error if the specified token isn't applicable for generic type T.
func IsTokenValid[T any](token string) error {
raw := map[string]interface{}{}
raw := map[string]any{}
if err := json.Unmarshal([]byte(token), &raw); err != nil {
return err
}
@ -185,3 +185,16 @@ func ResultHelper[T any](resp *http.Response, failed bool, out *T) error {
}
return nil
}
// IsNonTerminalHTTPStatusCode returns true if the HTTP status code should be
// considered non-terminal thus eligible for retry.
func IsNonTerminalHTTPStatusCode(resp *http.Response) bool {
return exported.HasStatusCode(resp,
http.StatusRequestTimeout, // 408
http.StatusTooManyRequests, // 429
http.StatusInternalServerError, // 500
http.StatusBadGateway, // 502
http.StatusServiceUnavailable, // 503
http.StatusGatewayTimeout, // 504
)
}

View File

@ -40,5 +40,5 @@ const (
Module = "azcore"
// Version is the semantic version (see http://semver.org) of this module.
Version = "v1.9.2"
Version = "v1.11.1"
)

View File

@ -23,6 +23,11 @@ const (
// This includes information like the HTTP status code, headers, and request URL.
EventResponse Event = "Response"
// EventResponseError entries contain information about HTTP responses that returned
// an *azcore.ResponseError (i.e. responses with a non 2xx HTTP status code).
// This includes the contents of ResponseError.Error().
EventResponseError Event = "ResponseError"
// EventRetryPolicy entries contain information specific to the retry policy in use.
EventRetryPolicy Event = "Retry"

View File

@ -39,6 +39,11 @@ type ClientOptions struct {
// Cloud specifies a cloud for the client. The default is Azure Public Cloud.
Cloud cloud.Configuration
// InsecureAllowCredentialWithHTTP enables authenticated requests over HTTP.
// By default, authenticated requests to an HTTP endpoint are rejected by the client.
// WARNING: setting this to true will allow sending the credential in clear text. Use with caution.
InsecureAllowCredentialWithHTTP bool
// Logging configures the built-in logging policy.
Logging LogOptions
@ -147,6 +152,11 @@ type BearerTokenOptions struct {
// When this field isn't set, the policy follows its default behavior of authorizing every request with a bearer token from
// its given credential.
AuthorizationHandler AuthorizationHandler
// InsecureAllowCredentialWithHTTP enables authenticated requests over HTTP.
// By default, authenticated requests to an HTTP endpoint are rejected by the client.
// WARNING: setting this to true will allow sending the bearer token in clear text. Use with caution.
InsecureAllowCredentialWithHTTP bool
}
// AuthorizationHandler allows SDK developers to insert custom logic that runs when BearerTokenPolicy must authorize a request.

View File

@ -14,6 +14,14 @@ import (
// NewResponseError creates an *azcore.ResponseError from the provided HTTP response.
// Call this when a service request returns a non-successful status code.
// The error code will be extracted from the *http.Response, either from the x-ms-error-code
// header (preferred) or attempted to be parsed from the response body.
func NewResponseError(resp *http.Response) error {
return exported.NewResponseError(resp)
}
// NewResponseErrorWithErrorCode creates an *azcore.ResponseError from the provided HTTP response and errorCode.
// Use this variant when the error code is in a non-standard location.
func NewResponseErrorWithErrorCode(resp *http.Response, errorCode string) error {
return exported.NewResponseErrorWithErrorCode(resp, errorCode)
}

View File

@ -24,6 +24,7 @@ type BearerTokenPolicy struct {
authzHandler policy.AuthorizationHandler
cred exported.TokenCredential
scopes []string
allowHTTP bool
}
type acquiringResourceState struct {
@ -55,6 +56,7 @@ func NewBearerTokenPolicy(cred exported.TokenCredential, scopes []string, opts *
cred: cred,
scopes: scopes,
mainResource: temporal.NewResource(acquire),
allowHTTP: opts.InsecureAllowCredentialWithHTTP,
}
}
@ -80,7 +82,7 @@ func (b *BearerTokenPolicy) Do(req *policy.Request) (*http.Response, error) {
return req.Next()
}
if err := checkHTTPSForAuth(req); err != nil {
if err := checkHTTPSForAuth(req, b.allowHTTP); err != nil {
return nil, err
}
@ -113,8 +115,8 @@ func (b *BearerTokenPolicy) Do(req *policy.Request) (*http.Response, error) {
return res, err
}
func checkHTTPSForAuth(req *policy.Request) error {
if strings.ToLower(req.Raw().URL.Scheme) != "https" {
func checkHTTPSForAuth(req *policy.Request, allowHTTP bool) error {
if strings.ToLower(req.Raw().URL.Scheme) != "https" && !allowHTTP {
return errorinfo.NonRetriableError(errors.New("authenticated requests are not permitted for non TLS protected (https) endpoints"))
}
return nil

View File

@ -15,10 +15,16 @@ type KeyCredentialPolicy struct {
cred *exported.KeyCredential
header string
prefix string
allowHTTP bool
}
// KeyCredentialPolicyOptions contains the optional values configuring [KeyCredentialPolicy].
type KeyCredentialPolicyOptions struct {
// InsecureAllowCredentialWithHTTP enables authenticated requests over HTTP.
// By default, authenticated requests to an HTTP endpoint are rejected by the client.
// WARNING: setting this to true will allow sending the authentication key in clear text. Use with caution.
InsecureAllowCredentialWithHTTP bool
// Prefix is used if the key requires a prefix before it's inserted into the HTTP request.
Prefix string
}
@ -35,6 +41,7 @@ func NewKeyCredentialPolicy(cred *exported.KeyCredential, header string, options
cred: cred,
header: header,
prefix: options.Prefix,
allowHTTP: options.InsecureAllowCredentialWithHTTP,
}
}
@ -44,7 +51,7 @@ func (k *KeyCredentialPolicy) Do(req *policy.Request) (*http.Response, error) {
// this prevents a panic that might be hard to diagnose and allows testing
// against http endpoints that don't require authentication.
if k.cred != nil {
if err := checkHTTPSForAuth(req); err != nil {
if err := checkHTTPSForAuth(req, k.allowHTTP); err != nil {
return nil, err
}
val := exported.KeyCredentialGet(k.cred)

View File

@ -14,11 +14,15 @@ import (
type SASCredentialPolicy struct {
cred *exported.SASCredential
header string
allowHTTP bool
}
// SASCredentialPolicyOptions contains the optional values configuring [SASCredentialPolicy].
type SASCredentialPolicyOptions struct {
// placeholder for future optional values
// InsecureAllowCredentialWithHTTP enables authenticated requests over HTTP.
// By default, authenticated requests to an HTTP endpoint are rejected by the client.
// WARNING: setting this to true will allow sending the authentication key in clear text. Use with caution.
InsecureAllowCredentialWithHTTP bool
}
// NewSASCredentialPolicy creates a new instance of [SASCredentialPolicy].
@ -26,9 +30,13 @@ type SASCredentialPolicyOptions struct {
// - header is the name of the HTTP request header in which the shared access signature is placed
// - options contains optional configuration, pass nil to accept the default values
func NewSASCredentialPolicy(cred *exported.SASCredential, header string, options *SASCredentialPolicyOptions) *SASCredentialPolicy {
if options == nil {
options = &SASCredentialPolicyOptions{}
}
return &SASCredentialPolicy{
cred: cred,
header: header,
allowHTTP: options.InsecureAllowCredentialWithHTTP,
}
}
@ -38,7 +46,7 @@ func (k *SASCredentialPolicy) Do(req *policy.Request) (*http.Response, error) {
// this prevents a panic that might be hard to diagnose and allows testing
// against http endpoints that don't require authentication.
if k.cred != nil {
if err := checkHTTPSForAuth(req); err != nil {
if err := checkHTTPSForAuth(req, k.allowHTTP); err != nil {
return nil, err
}
req.Raw().Header.Add(k.header, exported.SASCredentialGet(k.cred))

View File

@ -154,7 +154,7 @@ func NewPollerFromResumeToken[T any](token string, pl exported.Pipeline, options
if err != nil {
return nil, err
}
var asJSON map[string]interface{}
var asJSON map[string]any
if err := json.Unmarshal(raw, &asJSON); err != nil {
return nil, err
}
@ -240,7 +240,7 @@ func (p *Poller[T]) PollUntilDone(ctx context.Context, options *PollUntilDoneOpt
}
start := time.Now()
logPollUntilDoneExit := func(v interface{}) {
logPollUntilDoneExit := func(v any) {
log.Writef(log.EventLRO, "END PollUntilDone() for %T: %v, total time: %s", p.op, v, time.Since(start))
}
log.Writef(log.EventLRO, "BEGIN PollUntilDone() for %T", p.op)
@ -334,6 +334,11 @@ func (p *Poller[T]) Result(ctx context.Context) (res T, err error) {
err = p.op.Result(ctx, p.result)
var respErr *exported.ResponseError
if errors.As(err, &respErr) {
if pollers.IsNonTerminalHTTPStatusCode(respErr.RawResponse) {
// the request failed in a non-terminal way.
// don't cache the error or mark the Poller as done
return
}
// the LRO failed. record the error
p.err = err
} else if err != nil {

View File

@ -11,9 +11,11 @@ import (
"context"
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"io"
"mime/multipart"
"net/textproto"
"net/url"
"path"
"strings"
@ -21,6 +23,7 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/exported"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/internal/shared"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming"
)
// Base64Encoding is usesd to specify which base-64 encoder/decoder to use when
@ -42,12 +45,19 @@ func NewRequest(ctx context.Context, httpMethod string, endpoint string) (*polic
}
// EncodeQueryParams will parse and encode any query parameters in the specified URL.
// Any semicolons will automatically be escaped.
func EncodeQueryParams(u string) (string, error) {
before, after, found := strings.Cut(u, "?")
if !found {
return u, nil
}
qp, err := url.ParseQuery(after)
// starting in Go 1.17, url.ParseQuery will reject semicolons in query params.
// so, we must escape them first. note that this assumes that semicolons aren't
// being used as query param separators which is per the current RFC.
// for more info:
// https://github.com/golang/go/issues/25192
// https://github.com/golang/go/issues/50034
qp, err := url.ParseQuery(strings.ReplaceAll(after, ";", "%3B"))
if err != nil {
return "", err
}
@ -102,7 +112,7 @@ func MarshalAsByteArray(req *policy.Request, v []byte, format Base64Encoding) er
}
// MarshalAsJSON calls json.Marshal() to get the JSON encoding of v then calls SetBody.
func MarshalAsJSON(req *policy.Request, v interface{}) error {
func MarshalAsJSON(req *policy.Request, v any) error {
b, err := json.Marshal(v)
if err != nil {
return fmt.Errorf("error marshalling type %T: %s", v, err)
@ -112,7 +122,7 @@ func MarshalAsJSON(req *policy.Request, v interface{}) error {
}
// MarshalAsXML calls xml.Marshal() to get the XML encoding of v then calls SetBody.
func MarshalAsXML(req *policy.Request, v interface{}) error {
func MarshalAsXML(req *policy.Request, v any) error {
b, err := xml.Marshal(v)
if err != nil {
return fmt.Errorf("error marshalling type %T: %s", v, err)
@ -122,10 +132,10 @@ func MarshalAsXML(req *policy.Request, v interface{}) error {
return req.SetBody(exported.NopCloser(bytes.NewReader(b)), shared.ContentTypeAppXML)
}
// SetMultipartFormData writes the specified keys/values as multi-part form
// fields with the specified value. File content must be specified as a ReadSeekCloser.
// All other values are treated as string values.
func SetMultipartFormData(req *policy.Request, formData map[string]interface{}) error {
// SetMultipartFormData writes the specified keys/values as multi-part form fields with the specified value.
// File content must be specified as an [io.ReadSeekCloser] or [streaming.MultipartContent].
// Byte slices will be treated as JSON. All other values are treated as string values.
func SetMultipartFormData(req *policy.Request, formData map[string]any) error {
body := bytes.Buffer{}
writer := multipart.NewWriter(&body)
@ -141,6 +151,60 @@ func SetMultipartFormData(req *policy.Request, formData map[string]interface{})
return nil
}
quoteEscaper := strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
writeMultipartContent := func(fieldname string, mpc streaming.MultipartContent) error {
if mpc.Body == nil {
return errors.New("streaming.MultipartContent.Body cannot be nil")
}
// use fieldname for the file name when unspecified
filename := fieldname
if mpc.ContentType == "" && mpc.Filename == "" {
return writeContent(fieldname, filename, mpc.Body)
}
if mpc.Filename != "" {
filename = mpc.Filename
}
// this is pretty much copied from multipart.Writer.CreateFormFile
// but lets us set the caller provided Content-Type and filename
h := make(textproto.MIMEHeader)
h.Set("Content-Disposition",
fmt.Sprintf(`form-data; name="%s"; filename="%s"`,
quoteEscaper.Replace(fieldname), quoteEscaper.Replace(filename)))
contentType := "application/octet-stream"
if mpc.ContentType != "" {
contentType = mpc.ContentType
}
h.Set("Content-Type", contentType)
fd, err := writer.CreatePart(h)
if err != nil {
return err
}
// copy the data to the form file
if _, err = io.Copy(fd, mpc.Body); err != nil {
return err
}
return nil
}
// the same as multipart.Writer.WriteField but lets us specify the Content-Type
writeField := func(fieldname, contentType string, value string) error {
h := make(textproto.MIMEHeader)
h.Set("Content-Disposition",
fmt.Sprintf(`form-data; name="%s"`, quoteEscaper.Replace(fieldname)))
h.Set("Content-Type", contentType)
fd, err := writer.CreatePart(h)
if err != nil {
return err
}
if _, err = fd.Write([]byte(value)); err != nil {
return err
}
return nil
}
for k, v := range formData {
if rsc, ok := v.(io.ReadSeekCloser); ok {
if err := writeContent(k, k, rsc); err != nil {
@ -154,13 +218,35 @@ func SetMultipartFormData(req *policy.Request, formData map[string]interface{})
}
}
continue
} else if mpc, ok := v.(streaming.MultipartContent); ok {
if err := writeMultipartContent(k, mpc); err != nil {
return err
}
continue
} else if mpcs, ok := v.([]streaming.MultipartContent); ok {
for _, mpc := range mpcs {
if err := writeMultipartContent(k, mpc); err != nil {
return err
}
}
continue
}
var content string
contentType := shared.ContentTypeTextPlain
switch tt := v.(type) {
case []byte:
// JSON, don't quote it
content = string(tt)
contentType = shared.ContentTypeAppJSON
case string:
content = tt
default:
// ensure the value is in string format
s, ok := v.(string)
if !ok {
s = fmt.Sprintf("%v", v)
content = fmt.Sprintf("%v", v)
}
if err := writer.WriteField(k, s); err != nil {
if err := writeField(k, contentType, content); err != nil {
return err
}
}

View File

@ -40,7 +40,7 @@ func UnmarshalAsByteArray(resp *http.Response, v *[]byte, format Base64Encoding)
}
// UnmarshalAsJSON calls json.Unmarshal() to unmarshal the received payload into the value pointed to by v.
func UnmarshalAsJSON(resp *http.Response, v interface{}) error {
func UnmarshalAsJSON(resp *http.Response, v any) error {
payload, err := Payload(resp)
if err != nil {
return err
@ -61,7 +61,7 @@ func UnmarshalAsJSON(resp *http.Response, v interface{}) error {
}
// UnmarshalAsXML calls xml.Unmarshal() to unmarshal the received payload into the value pointed to by v.
func UnmarshalAsXML(resp *http.Response, v interface{}) error {
func UnmarshalAsXML(resp *http.Response, v any) error {
payload, err := Payload(resp)
if err != nil {
return err

View File

@ -73,3 +73,17 @@ func (p *progress) Seek(offset int64, whence int) (int64, error) {
func (p *progress) Close() error {
return p.rc.Close()
}
// MultipartContent contains streaming content used in multipart/form payloads.
type MultipartContent struct {
// Body contains the required content body.
Body io.ReadSeekCloser
// ContentType optionally specifies the HTTP Content-Type for this Body.
// The default value is application/octet-stream.
ContentType string
// Filename optionally specifies the filename for this Body.
// The default value is the field name for the multipart/form section.
Filename string
}

View File

@ -1,5 +1,14 @@
# Release History
## 1.3.2 (2024-04-09)
### Bugs Fixed
* Fixed an issue where GetSASURL() was providing HTTPS SAS, instead of the default http+https SAS. Fixes [#22448](https://github.com/Azure/azure-sdk-for-go/issues/22448)
### Other Changes
* Integrate `InsecureAllowCredentialWithHTTP` client options.
* Update dependencies.
## 1.3.1 (2024-02-28)
### Bugs Fixed

View File

@ -9,19 +9,19 @@ package appendblob
import (
"context"
"errors"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas"
"io"
"os"
"time"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/base"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/exported"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/generated"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/shared"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas"
)
// ClientOptions contains the optional parameters when creating a Client.
@ -36,8 +36,8 @@ type Client base.CompositeClient[generated.BlobClient, generated.AppendBlobClien
// - options - client options; pass nil to accept the default values
func NewClient(blobURL string, cred azcore.TokenCredential, options *ClientOptions) (*Client, error) {
audience := base.GetAudience((*base.ClientOptions)(options))
authPolicy := shared.NewStorageChallengePolicy(cred, audience)
conOptions := shared.GetClientOptions(options)
authPolicy := shared.NewStorageChallengePolicy(cred, audience, conOptions.InsecureAllowCredentialWithHTTP)
plOpts := runtime.PipelineOptions{PerRetry: []policy.Policy{authPolicy}}
azClient, err := azcore.NewClient(exported.ModuleName, exported.ModuleVersion, plOpts, &conOptions.ClientOptions)

View File

@ -37,8 +37,8 @@ type Client base.Client[generated.BlobClient]
// - options - client options; pass nil to accept the default values
func NewClient(blobURL string, cred azcore.TokenCredential, options *ClientOptions) (*Client, error) {
audience := base.GetAudience((*base.ClientOptions)(options))
authPolicy := shared.NewStorageChallengePolicy(cred, audience)
conOptions := shared.GetClientOptions(options)
authPolicy := shared.NewStorageChallengePolicy(cred, audience, conOptions.InsecureAllowCredentialWithHTTP)
plOpts := runtime.PipelineOptions{PerRetry: []policy.Policy{authPolicy}}
azClient, err := azcore.NewClient(exported.ModuleName, exported.ModuleVersion, plOpts, &conOptions.ClientOptions)

View File

@ -11,9 +11,6 @@ import (
"context"
"encoding/base64"
"errors"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/bloberror"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas"
"io"
"math"
"os"
@ -22,16 +19,19 @@ import (
"time"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
"github.com/Azure/azure-sdk-for-go/sdk/internal/log"
"github.com/Azure/azure-sdk-for-go/sdk/internal/uuid"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/bloberror"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/base"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/exported"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/generated"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/shared"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas"
)
// ClientOptions contains the optional parameters when creating a Client.
@ -46,8 +46,8 @@ type Client base.CompositeClient[generated.BlobClient, generated.BlockBlobClient
// - options - client options; pass nil to accept the default values
func NewClient(blobURL string, cred azcore.TokenCredential, options *ClientOptions) (*Client, error) {
audience := base.GetAudience((*base.ClientOptions)(options))
authPolicy := shared.NewStorageChallengePolicy(cred, audience)
conOptions := shared.GetClientOptions(options)
authPolicy := shared.NewStorageChallengePolicy(cred, audience, conOptions.InsecureAllowCredentialWithHTTP)
plOpts := runtime.PipelineOptions{PerRetry: []policy.Policy{authPolicy}}
azClient, err := azcore.NewClient(exported.ModuleName, exported.ModuleVersion, plOpts, &conOptions.ClientOptions)

View File

@ -21,8 +21,8 @@ pr:
- sdk/storage/azblob
stages:
- template: /eng/pipelines/templates/jobs/archetype-sdk-client.yml
extends:
template: /eng/pipelines/templates/jobs/archetype-sdk-client.yml
parameters:
ServiceDirectory: 'storage/azblob'
RunLiveTests: true

View File

@ -11,8 +11,6 @@ import (
"context"
"errors"
"fmt"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/bloberror"
"net/http"
"net/url"
"time"
@ -20,8 +18,10 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/appendblob"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/bloberror"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blockblob"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/base"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/exported"
@ -43,8 +43,8 @@ type Client base.Client[generated.ContainerClient]
// - options - client options; pass nil to accept the default values
func NewClient(containerURL string, cred azcore.TokenCredential, options *ClientOptions) (*Client, error) {
audience := base.GetAudience((*base.ClientOptions)(options))
authPolicy := shared.NewStorageChallengePolicy(cred, audience)
conOptions := shared.GetClientOptions(options)
authPolicy := shared.NewStorageChallengePolicy(cred, audience, conOptions.InsecureAllowCredentialWithHTTP)
plOpts := runtime.PipelineOptions{PerRetry: []policy.Policy{authPolicy}}
azClient, err := azcore.NewClient(exported.ModuleName, exported.ModuleVersion, plOpts, &conOptions.ClientOptions)
@ -348,7 +348,6 @@ func (c *Client) GetSASURL(permissions sas.ContainerPermissions, expiry time.Tim
// Containers do not have snapshots, nor versions.
qps, err := sas.BlobSignatureValues{
Version: sas.Version,
Protocol: sas.ProtocolHTTPS,
ContainerName: urlParts.ContainerName,
Permissions: permissions.String(),
StartTime: st,
@ -371,7 +370,8 @@ func (c *Client) NewBatchBuilder() (*BatchBuilder, error) {
switch cred := c.credential().(type) {
case *azcore.TokenCredential:
authPolicy = shared.NewStorageChallengePolicy(*cred, base.GetAudience(c.getClientOptions()))
conOptions := c.getClientOptions()
authPolicy = shared.NewStorageChallengePolicy(*cred, base.GetAudience(conOptions), conOptions.InsecureAllowCredentialWithHTTP)
case *SharedKeyCredential:
authPolicy = exported.NewSharedKeyCredPolicy(cred)
case nil:

View File

@ -8,5 +8,5 @@ package exported
const (
ModuleName = "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
ModuleVersion = "v1.3.1"
ModuleVersion = "v1.3.2"
)

View File

@ -8,11 +8,12 @@ package shared
import (
"errors"
"net/http"
"strings"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
"net/http"
"strings"
)
type storageAuthorizer struct {
@ -20,13 +21,14 @@ type storageAuthorizer struct {
tenantID string
}
func NewStorageChallengePolicy(cred azcore.TokenCredential, audience string) policy.Policy {
func NewStorageChallengePolicy(cred azcore.TokenCredential, audience string, allowHTTP bool) policy.Policy {
s := storageAuthorizer{scopes: []string{audience}}
return runtime.NewBearerTokenPolicy(cred, []string{audience}, &policy.BearerTokenOptions{
AuthorizationHandler: policy.AuthorizationHandler{
OnRequest: s.onRequest,
OnChallenge: s.onChallenge,
},
InsecureAllowCredentialWithHTTP: allowHTTP,
})
}

View File

@ -8,7 +8,6 @@ package pageblob
import (
"context"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas"
"io"
"net/http"
"net/url"
@ -23,6 +22,7 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/exported"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/generated"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/shared"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas"
)
// ClientOptions contains the optional parameters when creating a Client.
@ -37,8 +37,8 @@ type Client base.CompositeClient[generated.BlobClient, generated.PageBlobClient]
// - options - client options; pass nil to accept the default values
func NewClient(blobURL string, cred azcore.TokenCredential, options *ClientOptions) (*Client, error) {
audience := base.GetAudience((*base.ClientOptions)(options))
authPolicy := shared.NewStorageChallengePolicy(cred, audience)
conOptions := shared.GetClientOptions(options)
authPolicy := shared.NewStorageChallengePolicy(cred, audience, conOptions.InsecureAllowCredentialWithHTTP)
plOpts := runtime.PipelineOptions{PerRetry: []policy.Policy{authPolicy}}
azClient, err := azcore.NewClient(exported.ModuleName, exported.ModuleVersion, plOpts, &conOptions.ClientOptions)

View File

@ -8,6 +8,7 @@ package sas
import (
"errors"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/generated"
"net"
"net/url"
"strings"
@ -23,7 +24,7 @@ const (
var (
// Version is the default version encoded in the SAS token.
Version = "2021-12-02"
Version = generated.ServiceVersion
)
// TimeFormats ISO 8601 format.

View File

@ -11,9 +11,6 @@ import (
"context"
"errors"
"fmt"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/bloberror"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/base"
"net/http"
"strings"
"time"
@ -21,8 +18,11 @@ import (
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/runtime"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/streaming"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/bloberror"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/base"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/exported"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/generated"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/internal/shared"
@ -41,8 +41,8 @@ type Client base.Client[generated.ServiceClient]
// - options - client options; pass nil to accept the default values
func NewClient(serviceURL string, cred azcore.TokenCredential, options *ClientOptions) (*Client, error) {
audience := base.GetAudience((*base.ClientOptions)(options))
authPolicy := shared.NewStorageChallengePolicy(cred, audience)
conOptions := shared.GetClientOptions(options)
authPolicy := shared.NewStorageChallengePolicy(cred, audience, conOptions.InsecureAllowCredentialWithHTTP)
plOpts := runtime.PipelineOptions{PerRetry: []policy.Policy{authPolicy}}
azClient, err := azcore.NewClient(exported.ModuleName, exported.ModuleVersion, plOpts, &conOptions.ClientOptions)
@ -280,7 +280,6 @@ func (s *Client) GetSASURL(resources sas.AccountResourceTypes, permissions sas.A
st := o.format()
qps, err := sas.AccountSignatureValues{
Version: sas.Version,
Protocol: sas.ProtocolHTTPS,
Permissions: permissions.String(),
ResourceTypes: resources.String(),
StartTime: st,
@ -320,7 +319,8 @@ func (s *Client) NewBatchBuilder() (*BatchBuilder, error) {
switch cred := s.credential().(type) {
case *azcore.TokenCredential:
authPolicy = shared.NewStorageChallengePolicy(*cred, base.GetAudience(s.getClientOptions()))
conOptions := s.getClientOptions()
authPolicy = shared.NewStorageChallengePolicy(*cred, base.GetAudience(conOptions), conOptions.InsecureAllowCredentialWithHTTP)
case *SharedKeyCredential:
authPolicy = exported.NewSharedKeyCredPolicy(cred)
case nil:

4
vendor/modules.txt vendored
View File

@ -33,7 +33,7 @@ github.com/AdamKorcz/go-118-fuzz-build/testing
## explicit
github.com/Azure/azure-sdk-for-go/services/preview/containerregistry/runtime/2019-08-15-preview/containerregistry
github.com/Azure/azure-sdk-for-go/version
# github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.2
# github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1
## explicit; go 1.18
github.com/Azure/azure-sdk-for-go/sdk/azcore
github.com/Azure/azure-sdk-for-go/sdk/azcore/cloud
@ -61,7 +61,7 @@ github.com/Azure/azure-sdk-for-go/sdk/internal/log
github.com/Azure/azure-sdk-for-go/sdk/internal/poller
github.com/Azure/azure-sdk-for-go/sdk/internal/temporal
github.com/Azure/azure-sdk-for-go/sdk/internal/uuid
# github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.1
# github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.2
## explicit; go 1.18
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/appendblob