diff --git a/.gitignore b/.gitignore index 8f765f09..d2394b0b 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ tests.xml vendor/ .vscode/ +.terragrunt-cache diff --git a/azure/config.go b/azure/config.go index 7bf21f1a..92905824 100644 --- a/azure/config.go +++ b/azure/config.go @@ -2,56 +2,92 @@ package azure import ( "errors" + "fmt" + "github.com/Azure/azure-sdk-for-go/sdk/azidentity" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" "net/url" + "strconv" - az "github.com/Azure/azure-sdk-for-go/storage" - "github.com/graymeta/stow" + "github.com/flyteorg/stow" ) -// ConfigAccount and ConfigKey are the supported configuration items for -// Azure blob storage. +// ConfigAccount should be the name of your storage account in the Azure portal +// ConfigKey should be an access key +// ConfigDomainSuffix the domain suffix to use for storage account communication. The default is the Azure Public cloud +// ConfigUploadConcurrency the upload concurrency to use when uploading. Default is 4. +// ConfigBaseUrlDepreciated Kept for backwards compatability, use ConfigDomainSuffix instead const ( - ConfigAccount = "account" - ConfigKey = "key" + ConfigAccount = "account" + ConfigKey = "key" + ConfigDomainSuffix = "domain_suffix" + ConfigUploadConcurrency = "upload_concurrency" + ConfigBaseUrlDepreciated = "base_url" ) +// Removed configuration values, will cause failures if used. +const ( + ConfigUseHttpsRemoved = "use_https" + ConfigApiVersionRemoved = "api_version" +) + +var removedConfigKeys = []string{ConfigUseHttpsRemoved, ConfigApiVersionRemoved} + // Kind is the kind of Location this package provides. const Kind = "azure" +// defaultDomainSuffix is the domain suffix for the Azure Public Cloud +const defaultDomainSuffix = "core.windows.net" + +// defaultUploadConcurrency is the default upload concurrency +const defaultUploadConcurrency = 4 + func init() { validatefn := func(config stow.Config) error { _, ok := config.Config(ConfigAccount) if !ok { return errors.New("missing account id") } - _, ok = config.Config(ConfigKey) - if !ok { - return errors.New("missing auth key") + for _, removedConfigKey := range removedConfigKeys { + _, ok = config.Config(removedConfigKey) + if ok { + return fmt.Errorf("removed config option used [%s]", removedConfigKey) + } } return nil } makefn := func(config stow.Config) (stow.Location, error) { - _, ok := config.Config(ConfigAccount) + acctName, ok := config.Config(ConfigAccount) if !ok { return nil, errors.New("missing account id") } - _, ok = config.Config(ConfigKey) - if !ok { - return nil, errors.New("missing auth key") + + var uploadConcurrency int + var err error + uploadConcurrencyStr, ok := config.Config(ConfigUploadConcurrency) + if !ok || len(uploadConcurrencyStr) == 0 { + uploadConcurrency = defaultUploadConcurrency + } else { + uploadConcurrency, err = strconv.Atoi(uploadConcurrencyStr) + if err != nil { + return nil, fmt.Errorf("invalid upload concurrency [%v]", uploadConcurrency) + } } l := &location{ - config: config, + accountName: acctName, + uploadConcurrency: uploadConcurrency, } - var err error - l.client, err = newBlobStorageClient(l.config) + + l.client, l.preSigner, err = makeAccountClient(config) if err != nil { return nil, err } + // test the connection _, _, err = l.Containers("", stow.CursorStart, 1) if err != nil { return nil, err } + return l, nil } kindfn := func(u *url.URL) bool { @@ -60,19 +96,69 @@ func init() { stow.Register(Kind, makefn, kindfn, validatefn) } -func newBlobStorageClient(cfg stow.Config) (*az.BlobStorageClient, error) { - acc, ok := cfg.Config(ConfigAccount) +// makeAccountClient is a factory function for producing client instances +func makeAccountClient(cfg stow.Config) (*azblob.Client, RequestPreSigner, error) { + accountName, ok := cfg.Config(ConfigAccount) if !ok { - return nil, errors.New("missing account id") + return nil, nil, errors.New("missing account id") } + + domainSuffix := resolveAzureDomainSuffix(cfg) + serviceUrl := fmt.Sprintf("https://%s.blob.%s", accountName, domainSuffix) + key, ok := cfg.Config(ConfigKey) - if !ok { - return nil, errors.New("missing auth key") + if ok && key != "" { + return newSharedKeyClient(accountName, key, serviceUrl) } - basicClient, err := az.NewBasicClient(acc, key) + return newDefaultAzureIdentityClient(serviceUrl) +} + +// newSharedKeyClient creates client objects for working with a storage account +// using shared keys. +func newSharedKeyClient(accountName, key, serviceUrl string) (*azblob.Client, RequestPreSigner, error) { + sharedKeyCred, err := azblob.NewSharedKeyCredential(accountName, key) if err != nil { - return nil, errors.New("bad credentials") + return nil, nil, err + } + client, err := azblob.NewClientWithSharedKeyCredential( + serviceUrl, + sharedKeyCred, + nil) + if err != nil { + return nil, nil, err + } + preSigner, err := NewSharedKeyRequestPreSigner(accountName, key) + if err != nil { + return nil, nil, err + } + return client, preSigner, nil +} + +// newDefaultAzureIdentityClient creates client objects for working with a storage +// account using Azure AD auth, resolved using the default Azure credential chain. +func newDefaultAzureIdentityClient(serviceUrl string) (*azblob.Client, RequestPreSigner, error) { + cred, err := azidentity.NewDefaultAzureCredential(nil) + if err != nil { + return nil, nil, err + } + client, err := azblob.NewClient(serviceUrl, cred, nil) + if err != nil { + return nil, nil, err + } + preSigner, err := NewDelegatedKeyPreSigner(client.ServiceClient()) + return client, preSigner, nil +} + +// resolveAzureDomainSuffix returns the Azure domain suffix to use +func resolveAzureDomainSuffix(cfg stow.Config) string { + domainSuffix, ok := cfg.Config(ConfigDomainSuffix) + if ok && domainSuffix != "" { + return domainSuffix + } + + domainSuffix, ok = cfg.Config(ConfigBaseUrlDepreciated) + if ok && domainSuffix != "" { + return domainSuffix } - client := basicClient.GetBlobService() - return &client, err + return defaultDomainSuffix } diff --git a/azure/container.go b/azure/container.go index 82a4c387..40624f03 100644 --- a/azure/container.go +++ b/azure/container.go @@ -1,25 +1,31 @@ package azure import ( + "context" + "fmt" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blockblob" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas" "io" + "os" + "strconv" "strings" "time" - az "github.com/Azure/azure-sdk-for-go/storage" - "github.com/graymeta/stow" + azcontainer "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container" + "github.com/flyteorg/stow" "github.com/pkg/errors" ) -// The maximum size of an object that can be Put in a single request -const maxPutSize = 256 * 1024 * 1024 - // timeFormat is the time format for azure. var timeFormat = "Mon, 2 Jan 2006 15:04:05 MST" type container struct { - id string - properties az.ContainerProperties - client *az.BlobStorageClient + id string + properties *BlobProps + client *azcontainer.Client + preSigner RequestPreSigner + uploadConcurrency int } var _ stow.Container = (*container)(nil) @@ -32,123 +38,183 @@ func (c *container) Name() string { return c.id } -func (c *container) Item(id string) (stow.Item, error) { - blob := c.client.GetContainerReference(c.id).GetBlobReference(id) - err := blob.GetProperties(nil) - if err != nil { - if strings.Contains(err.Error(), "404") { - return nil, stow.ErrNotFound +func (c *container) PreSignRequest(ctx context.Context, method stow.ClientMethod, key string, + params stow.PresignRequestParams) (response stow.PresignResponse, err error) { + containerName := c.id + blobName := key + var requestHeaders map[string]string + permissions := sas.BlobPermissions{} + switch method { + case stow.ClientMethodGet: + permissions.Read = true + case stow.ClientMethodPut: + permissions.Add = true + permissions.Write = true + + requestHeaders = map[string]string{"Content-Length": strconv.Itoa(len(params.ContentMD5)), "Content-MD5": params.ContentMD5} + requestHeaders["x-ms-blob-type"] = "BlockBlob" // https://learn.microsoft.com/en-us/rest/api/storageservices/put-blob?tabs=microsoft-entra-id#remarks + + if params.AddContentMD5Metadata { + requestHeaders[fmt.Sprintf("x-ms-meta-%s", stow.FlyteContentMD5)] = params.ContentMD5 } - return nil, err } - item := &item{ - id: id, - container: c, - client: c.client, - properties: blob.Properties, + + sasQueryParams, err := c.preSigner(ctx, sas.BlobSignatureValues{ + Protocol: sas.ProtocolHTTPS, + StartTime: time.Now().UTC().Add(-1 * clockSkewBuffer), + ExpiryTime: time.Now().UTC().Add(params.ExpiresIn + clockSkewBuffer), + ContainerName: containerName, + BlobName: blobName, + Permissions: permissions.String(), + }) + + if err != nil { + return stow.PresignResponse{}, err } - etag := cleanEtag(item.properties.Etag) // Etags returned from this method include quotes. Strip them. - item.properties.Etag = etag // Assign the corrected string value to the field. + // Create the SAS URL for the resource you wish to access, and append the SAS query parameters. + qp := sasQueryParams.Encode() - return item, nil + return stow.PresignResponse{Url: fmt.Sprintf("%s/%s?%s", c.client.URL(), blobName, qp), RequiredRequestHeaders: requestHeaders}, nil +} + +func (c *container) Item(id string) (stow.Item, error) { + cleanedId := strings.Replace(id, " ", "+", -1) + items, _, err := c.Items(cleanedId, "", 1) + if err != nil { + return nil, err + } else if len(items) == 0 { + return nil, stow.ErrNotFound + } else { + return items[0], nil + } + + // Why not use this code? Because the Azure SDK/API will return metadata with the + // first character of each key upper-cased. Unfortunately, the MSFT position is + // that this is a golang upstream problem and they are not attempting to fix it. + // + // See: https://github.com/Azure/azure-sdk-for-go/issues/16791#issuecomment-1011518946 + // + // The workaround used here is to just use their listing API, which doesn't rely on + // retrieving metadata as HTTP headers. + // + // Another reasonable alternative would be to just lower case all metadata keys, as is + // the case with s3: https://github.com/aws/aws-sdk-go/issues/445 + // + //ctx := context.Background() + //blobClient := c.client.NewBlobClient(id) + //resp, err := blobClient.GetProperties(ctx, nil) + //if err != nil { + // if strings.Contains(err.Error(), "404") { + // return nil, stow.ErrNotFound + // } + // return nil, err + //} + //item := &item{ + // id: id, + // container: c, + // client: blobClient, + // metadata: makeStowCompatMetadataMap(resp.Metadata), + // properties: &BlobProps{ + // ETag: *resp.ETag, + // LastModified: *resp.LastModified, + // ContentLength: *resp.ContentLength, + // }, + //} + // + //return item, nil } func (c *container) Items(prefix, cursor string, count int) ([]stow.Item, string, error) { - params := az.ListBlobsParameters{ - Prefix: prefix, - MaxResults: uint(count), + ctx := context.Background() + options := azcontainer.ListBlobsFlatOptions{ + Prefix: &prefix, + MaxResults: to.Ptr(int32(count)), + Include: azcontainer.ListBlobsInclude{Metadata: true}, } if cursor != "" { - params.Marker = cursor + options.Marker = &cursor } - listblobs, err := c.client.GetContainerReference(c.id).ListBlobs(params) + + listResp, err := c.client.NewListBlobsFlatPager(&options).NextPage(ctx) if err != nil { return nil, "", err } - items := make([]stow.Item, len(listblobs.Blobs)) - for i, blob := range listblobs.Blobs { - - // Clean Etag just in case. - blob.Properties.Etag = cleanEtag(blob.Properties.Etag) - + items := make([]stow.Item, len(listResp.Segment.BlobItems)) + for i, blob := range listResp.Segment.BlobItems { items[i] = &item{ - id: blob.Name, - container: c, - client: c.client, - properties: blob.Properties, + id: *blob.Name, + container: c, + client: c.client.NewBlobClient(*blob.Name), + metadata: makeStowCompatMetadataMap(blob.Metadata), + properties: &BlobProps{ + ETag: *blob.Properties.ETag, + LastModified: *blob.Properties.LastModified, + ContentLength: *blob.Properties.ContentLength, + }, } } - return items, listblobs.NextMarker, nil + return items, *listResp.NextMarker, nil } func (c *container) Put(name string, r io.Reader, size int64, metadata map[string]interface{}) (stow.Item, error) { - mdParsed, err := prepMetadata(metadata) + ctx := context.Background() + mdParsed, err := makeAzureCompatMetadataMap(metadata) if err != nil { return nil, errors.Wrap(err, "unable to create or update Item, preparing metadata") } name = strings.Replace(name, " ", "+", -1) - - if size > maxPutSize { - // Do a multipart upload - err := c.multipartUpload(name, r, size) + blockClient := c.client.NewBlockBlobClient(name) + var blobProps = &BlobProps{ContentLength: size} + f, match := r.(*os.File) + if match { + resp, err := blockClient.UploadFile(ctx, f, &blockblob.UploadFileOptions{ + Concurrency: uint16(c.uploadConcurrency), + Metadata: mdParsed, + }) if err != nil { - return nil, errors.Wrap(err, "multipart upload") + return nil, errors.Wrap(err, "file upload") } + blobProps.ETag = *resp.ETag + blobProps.LastModified = *resp.LastModified } else { - err = c.client.GetContainerReference(c.id).GetBlobReference(name).CreateBlockBlobFromReader(r, nil) + resp, err := blockClient.UploadStream(ctx, r, &blockblob.UploadStreamOptions{ + Concurrency: c.uploadConcurrency, + Metadata: mdParsed, + }) if err != nil { - return nil, errors.Wrap(err, "unable to create or update Item") + return nil, errors.Wrap(err, "stream upload") } - } - - err = c.SetItemMetadata(name, mdParsed) - if err != nil { - return nil, errors.Wrap(err, "unable to create or update item, setting Item metadata") + blobProps.ETag = *resp.ETag + blobProps.LastModified = *resp.LastModified } item := &item{ - id: name, - container: c, - client: c.client, - properties: az.BlobProperties{ - LastModified: az.TimeRFC1123(time.Now()), - Etag: "", - ContentLength: size, - }, + id: name, + container: c, + metadata: metadata, + client: c.client.NewBlobClient(name), + properties: blobProps, } return item, nil } func (c *container) SetItemMetadata(itemName string, md map[string]string) error { - blob := c.client.GetContainerReference(c.id).GetBlobReference(itemName) - blob.Metadata = md - return blob.SetMetadata(nil) -} - -func parseMetadata(md map[string]string) (map[string]interface{}, error) { - rtnMap := make(map[string]interface{}, len(md)) - for key, value := range md { - rtnMap[key] = value - } - return rtnMap, nil -} - -func prepMetadata(md map[string]interface{}) (map[string]string, error) { - rtnMap := make(map[string]string, len(md)) - for key, value := range md { - str, ok := value.(string) - if !ok { - return nil, errors.Errorf(`value of key '%s' in metadata must be of type string`, key) - } - rtnMap[key] = str + ctx := context.Background() + azCompatMap := make(map[string]*string, len(md)) + for k, v := range md { + azCompatMap[k] = &v } - return rtnMap, nil + _, err := c.client.NewBlobClient(itemName).SetMetadata( + ctx, azCompatMap, nil) + return err } func (c *container) RemoveItem(id string) error { - return c.client.GetContainerReference(c.id).GetBlobReference(id).Delete(nil) + ctx := context.Background() + _, err := c.client.NewBlobClient(id).Delete(ctx, nil) + return err } // Remove quotation marks from beginning and end. This includes quotations that diff --git a/azure/doc.go b/azure/doc.go index 21d038ea..510b8741 100644 --- a/azure/doc.go +++ b/azure/doc.go @@ -1,22 +1,39 @@ /* Package azure provides an abstraction for the Microsoft Azure Storage service. In this package, an Azure Resource of type "Storage account" is represented by a Stow Location and an Azure blob is represented by a Stow Item. -Usage and Credentials +# Usage and Credentials -Two peices of information are needed to access an Azure Resorce of type "Storage account": the Resource Name (found in the "All Resources" tab of the Azure Portal console) and the Access Key (found in the "Access Keys" tab in the "Settings" pane of a Resource's details page). - -stow.Dial requires both a string value of the particular Stow Location Kind ("azure") and a stow.Config instance. The stow.Config instance requires two entries with the specific key value attributes: +stow.Dial requires both a string value of the particular Stow Location Kind ("azure") and a stow.Config instance. The list below outlines all configuration values: - a key of azure.ConfigAccount with a value of the Azure Resource Name -- a key of azure.ConfigKey with a value of the Azure Access Key +- a key of azure.ConfigKey with a value of the Azure Access Key (only used with shared key authentication) +- an optional key of azure.ConfigDomainSuffix to specify the Azure API domain. Defaults to `core.windows.net` (public Azure) +- an optional key of azure.ConfigUploadConcurrency to specify the upload concurrency to use. Defaults to `4`. + +## Authentication Methods + +There are two ways to authenticate to a Microsoft Azure Storage account: + +- Shared Key Authentication ([Discouraged by MSFT](https://tinyurl.com/3umn4hpn)) +- Azure AD Authentication (requires Role Assignments) + +### Shared Key Authentication -Location +To perform shared key authentication, the configuration must include the `azure.ConfigKey` property and the storage account must not prevent the use of shared keys ([MSFT reference here](https://tinyurl.com/3dxkzh99)) + +### Azure AD Authentication + +Azure AD authentication is resolved using the Default credential, which performs a hunt for credentials +in the environment in a cross-SDK and cross-platform way. The documentation for the resolution process +[can be found here](https://tinyurl.com/5ajy83c9). + +# Location There are azure.location methods which allow the retrieval of a single Azure Storage Service. A stow.Item representation of an Azure Storage Blob can also be retrieved based on the Object's URL (ItemByURL). Additional azure.location methods provide capabilities to create and remove Azure Storage Containers. -Container +# Container Methods of an azure.container allow the retrieval of an Azure Storage Container's: @@ -28,7 +45,7 @@ Additional azure.container methods allow Stow to : - remove a Blob (RemoveItem) - update or create a Blob (Put) -Item +# Item Methods of azure.Item allow the retrieval of an Azure Storage Container's: - name (ID or name) @@ -38,9 +55,5 @@ Methods of azure.Item allow the retrieval of an Azure Storage Container's: - last modified date - Etag - content - -Caveats - -At this point in time, the upload limit of a blob is about 60MB. This is an implementation restraint. */ package azure diff --git a/azure/item.go b/azure/item.go index 8aa8cf54..38f0a8a1 100644 --- a/azure/item.go +++ b/azure/item.go @@ -1,21 +1,21 @@ package azure import ( + "context" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/blob" "io" "net/url" "sync" "time" - az "github.com/Azure/azure-sdk-for-go/storage" - "github.com/graymeta/stow" - "github.com/pkg/errors" + "github.com/flyteorg/stow" ) type item struct { id string container *container - client *az.BlobStorageClient - properties az.BlobProperties + client *blob.Client + properties *BlobProps url url.URL metadata map[string]interface{} infoOnce sync.Once @@ -36,8 +36,7 @@ func (i *item) Name() string { } func (i *item) URL() *url.URL { - u := i.client.GetContainerReference(i.container.id).GetBlobReference(i.id).GetURL() - url, _ := url.Parse(u) + url, _ := url.Parse(i.client.URL()) url.Scheme = "azure" return url } @@ -47,64 +46,40 @@ func (i *item) Size() (int64, error) { } func (i *item) Open() (io.ReadCloser, error) { - return i.client.GetContainerReference(i.container.id).GetBlobReference(i.id).Get(nil) + ctx := context.Background() + dlResp, err := i.client.DownloadStream(ctx, nil) + if err != nil { + return nil, err + } + return dlResp.Body, nil } func (i *item) ETag() (string, error) { - return i.properties.Etag, nil + return cleanEtag(string(i.properties.ETag)), nil } func (i *item) LastMod() (time.Time, error) { - return time.Time(i.properties.LastModified), nil + return i.properties.LastModified, nil } func (i *item) Metadata() (map[string]interface{}, error) { - err := i.ensureInfo() - if err != nil { - return nil, errors.Wrap(err, "retrieving metadata") - } - return i.metadata, nil } -func (i *item) ensureInfo() error { - if i.metadata == nil { - i.infoOnce.Do(func() { - blob := i.client.GetContainerReference(i.container.Name()).GetBlobReference(i.Name()) - infoErr := blob.GetMetadata(nil) - if infoErr != nil { - i.infoErr = infoErr - return - } - - mdParsed, infoErr := parseMetadata(blob.Metadata) - if infoErr != nil { - i.infoErr = infoErr - return - } - i.metadata = mdParsed - }) - } - - return i.infoErr -} - -func (i *item) getInfo() (stow.Item, error) { - itemInfo, err := i.container.Item(i.ID()) - if err != nil { - return nil, err - } - return itemInfo, nil -} - // OpenRange opens the item for reading starting at byte start and ending // at byte end. func (i *item) OpenRange(start, end uint64) (io.ReadCloser, error) { - opts := &az.GetBlobRangeOptions{ - Range: &az.BlobRange{ - Start: start, - End: end, + ctx := context.Background() + resp, err := i.client.DownloadStream(ctx, &blob.DownloadStreamOptions{ + Range: blob.HTTPRange{ + Offset: int64(start), + Count: int64(end) - int64(start) + 1, }, + }) + + if err != nil { + return nil, err } - return i.client.GetContainerReference(i.container.id).GetBlobReference(i.id).GetRange(opts) + + return resp.Body, nil } diff --git a/azure/location.go b/azure/location.go index c6dd50a0..44a52fb8 100644 --- a/azure/location.go +++ b/azure/location.go @@ -1,64 +1,104 @@ package azure import ( + "context" "errors" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "net/http" + + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" + azcontainer "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/container" "net/url" "strings" "time" - az "github.com/Azure/azure-sdk-for-go/storage" - "github.com/graymeta/stow" + "github.com/flyteorg/stow" ) type location struct { - config stow.Config - client *az.BlobStorageClient + accountName string + uploadConcurrency int + client *azblob.Client + preSigner RequestPreSigner } func (l *location) Close() error { return nil // nothing to close } +var publicAccessTypeContainer = azcontainer.PublicAccessTypeContainer + +// CreateContainer follows the contract from stow.Location, with one notable opinion. +// Attempts to create an already-existing container will not produce an error. func (l *location) CreateContainer(name string) (stow.Container, error) { - err := l.client.GetContainerReference(name).Create(&az.CreateContainerOptions{Access: az.ContainerAccessTypeBlob}) + ctx := context.Background() + resp, err := l.client.CreateContainer( + ctx, + name, + &azblob.CreateContainerOptions{Access: &publicAccessTypeContainer}) + if err != nil { - if strings.Contains(err.Error(), "ErrorCode=ContainerAlreadyExists") { + var tErr *azcore.ResponseError + ok := errors.As(err, &tErr) + // Note: StatusConflict (409) is used for both "already exists" + // and "deleting" failures. + if ok && + tErr.StatusCode == http.StatusConflict && + tErr.ErrorCode == "ContainerAlreadyExists" { return l.Container(name) } return nil, err } + container := &container{ id: name, - properties: az.ContainerProperties{ - LastModified: time.Now().Format(timeFormat), + properties: &BlobProps{ + ETag: *resp.ETag, + LastModified: *resp.LastModified, }, - client: l.client, + client: l.client.ServiceClient().NewContainerClient(name), + preSigner: l.preSigner, + uploadConcurrency: l.uploadConcurrency, } + // TK: What is this here for? Presumably to wait for the container to + // really be available. If that's the case, a validation mechanism is + // a much better path if you want this to always work. time.Sleep(time.Second * 3) return container, nil } func (l *location) Containers(prefix, cursor string, count int) ([]stow.Container, string, error) { - params := az.ListContainersParameters{ - MaxResults: uint(count), - Prefix: prefix, + ctx := context.Background() + params := azblob.ListContainersOptions{ + MaxResults: to.Ptr(int32(count)), + Prefix: &prefix, } if cursor != stow.CursorStart { - params.Marker = cursor + params.Marker = &cursor } - response, err := l.client.ListContainers(params) + + pager := l.client.NewListContainersPager(¶ms) + resp, err := pager.NextPage(ctx) if err != nil { - return nil, "", err + return nil, cursor, err } - containers := make([]stow.Container, len(response.Containers)) - for i, azureContainer := range response.Containers { - containers[i] = &container{ - id: azureContainer.Name, - properties: azureContainer.Properties, - client: l.client, + + stowContainers := make([]stow.Container, len(resp.ContainerItems)) + for i, azContainer := range resp.ContainerItems { + stowContainers[i] = &container{ + id: *azContainer.Name, + properties: &BlobProps{ + ETag: *azContainer.Properties.ETag, + LastModified: *azContainer.Properties.LastModified, + }, + client: l.client.ServiceClient().NewContainerClient(*azContainer.Name), + preSigner: l.preSigner, + uploadConcurrency: l.uploadConcurrency, } } - return containers, response.NextMarker, nil + + return stowContainers, *resp.NextMarker, nil } func (l *location) Container(id string) (stow.Container, error) { @@ -87,15 +127,12 @@ func (l *location) ItemByURL(url *url.URL) (stow.Item, error) { if url.Scheme != "azure" { return nil, errors.New("not valid azure URL") } - location := strings.Split(url.Host, ".")[0] - a, ok := l.config.Config(ConfigAccount) - if !ok { - // shouldn't really happen - return nil, errors.New("missing " + ConfigAccount + " config") - } - if a != location { + + locationAccountPart := strings.Split(url.Host, ".")[0] + if locationAccountPart != l.accountName { return nil, errors.New("wrong azure URL") } + path := strings.TrimLeft(url.Path, "/") params := strings.SplitN(path, "/", 2) if len(params) != 2 { @@ -109,5 +146,7 @@ func (l *location) ItemByURL(url *url.URL) (stow.Item, error) { } func (l *location) RemoveContainer(id string) error { - return l.client.GetContainerReference(id).Delete(nil) + ctx := context.Background() + _, err := l.client.DeleteContainer(ctx, id, &azblob.DeleteContainerOptions{}) + return err } diff --git a/azure/multipart.go b/azure/multipart.go deleted file mode 100644 index 13bed938..00000000 --- a/azure/multipart.go +++ /dev/null @@ -1,96 +0,0 @@ -package azure - -import ( - "encoding/base64" - "encoding/binary" - "errors" - "io" - - az "github.com/Azure/azure-sdk-for-go/storage" -) - -// constants related to multi-part uploads -const ( - startChunkSize = 4 * 1024 * 1024 - maxChunkSize = 100 * 1024 * 1024 - maxParts = 50000 -) - -// errMultiPartUploadTooBig is the error returned when a file is just too big to upload -var errMultiPartUploadTooBig = errors.New("size exceeds maximum capacity for a single multi-part upload") - -// encodedBlockID returns the base64 encoded block id as expected by azure -func encodedBlockID(id uint64) string { - bytesID := make([]byte, 8) - binary.LittleEndian.PutUint64(bytesID, id) - return base64.StdEncoding.EncodeToString(bytesID) -} - -// determineChunkSize determines the chunk size for a multi-part upload. -func determineChunkSize(size int64) (int64, error) { - var chunkSize = int64(startChunkSize) - - for { - parts := size / chunkSize - rem := size % chunkSize - - if rem != 0 { - parts++ - } - - if parts <= maxParts { - break - } - - if chunkSize == maxChunkSize { - return 0, errMultiPartUploadTooBig - } - - chunkSize *= 2 - if chunkSize > maxChunkSize { - chunkSize = maxChunkSize - } - } - - return chunkSize, nil -} - -// multipartUpload performs a multi-part upload by chunking the data, putting each chunk, then -// assembling the chunks into a blob -func (c *container) multipartUpload(name string, r io.Reader, size int64) error { - chunkSize, err := determineChunkSize(size) - if err != nil { - return err - } - var buf = make([]byte, chunkSize) - - var blocks []az.Block - var rawID uint64 - blob := c.client.GetContainerReference(c.id).GetBlobReference(name) - - // TODO: upload the parts in parallel - for { - n, err := r.Read(buf) - if err != nil { - if err == io.EOF { - break - } - return err - } - - blockID := encodedBlockID(rawID) - chunk := buf[:n] - - if err := blob.PutBlock(blockID, chunk, nil); err != nil { - return err - } - - blocks = append(blocks, az.Block{ - ID: blockID, - Status: az.BlockStatusLatest, - }) - rawID++ - } - - return blob.PutBlockList(blocks, nil) -} diff --git a/azure/multipart_test.go b/azure/multipart_test.go deleted file mode 100644 index de1dce67..00000000 --- a/azure/multipart_test.go +++ /dev/null @@ -1,103 +0,0 @@ -package azure - -import ( - "crypto/md5" - "fmt" - "io" - "os" - "testing" - - "github.com/cheekybits/is" - "github.com/graymeta/stow" -) - -func TestChunkSize(t *testing.T) { - is := is.New(t) - - // 10 bytes, should fit in a single chunk - sz, err := determineChunkSize(10) - is.NoErr(err) - is.Equal(sz, startChunkSize) - - // Scale up the chunk size - sz, err = determineChunkSize(maxParts*startChunkSize + 1) - is.NoErr(err) - is.Equal(sz, startChunkSize*2) - - // Maximum size - sz, err = determineChunkSize(maxParts * maxChunkSize) - is.NoErr(err) - is.Equal(sz, maxChunkSize) - - // Add one byte, we shouldn't be able to upload this - sz, err = determineChunkSize(maxParts*maxChunkSize + 1) - is.Err(err) - is.Equal(err, errMultiPartUploadTooBig) -} - -func TestEncodeBlockID(t *testing.T) { - is := is.New(t) - - is.Equal(encodedBlockID(10), "CgAAAAAAAAA=") - is.Equal(encodedBlockID(600), "WAIAAAAAAAA=") -} - -func TestMultipartUpload(t *testing.T) { - is := is.New(t) - - if azureaccount == "" || azurekey == "" { - t.Skip("skipping test because missing either AZUREACCOUNT or AZUREKEY") - } - cfg := stow.ConfigMap{"account": azureaccount, "key": azurekey} - - bigfile := os.Getenv("BIG_FILE_TO_UPLOAD") - if bigfile == "" { - t.Skip("skipping test because BIG_FILE_TO_UPLOAD was not set") - } - - location, err := stow.Dial("azure", cfg) - is.NoErr(err) - is.OK(location) - - defer location.Close() - - cont, err := location.CreateContainer("bigfiletest") - is.NoErr(err) - is.OK(cont) - - defer func() { - is.NoErr(location.RemoveContainer(cont.ID())) - }() - - f, err := os.Open(bigfile) - is.NoErr(err) - defer f.Close() - - fi, err := f.Stat() - is.NoErr(err) - - name := "bigfile/thebigfile" - azc, ok := cont.(*container) - is.OK(ok) - is.NoErr(azc.multipartUpload(name, f, fi.Size())) - - item, err := cont.Item(name) - is.NoErr(err) - - defer cont.RemoveItem(name) - - r, err := item.Open() - is.NoErr(err) - defer r.Close() - - hashNew := md5.New() - _, err = io.Copy(hashNew, r) - is.NoErr(err) - - f.Seek(0, 0) - hashOld := md5.New() - _, err = io.Copy(hashOld, f) - is.NoErr(err) - - is.Equal(fmt.Sprintf("%x", hashOld.Sum(nil)), fmt.Sprintf("%x", hashNew.Sum(nil))) -} diff --git a/azure/sdktools.go b/azure/sdktools.go new file mode 100644 index 00000000..af0290fa --- /dev/null +++ b/azure/sdktools.go @@ -0,0 +1,96 @@ +package azure + +import ( + "context" + "github.com/Azure/azure-sdk-for-go/sdk/azcore" + "github.com/Azure/azure-sdk-for-go/sdk/azcore/to" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/sas" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob/service" + "github.com/pkg/errors" + "time" +) + +// BlobProps are the only blob properties required for stow. +type BlobProps struct { + ETag azcore.ETag + LastModified time.Time + ContentLength int64 +} + +// RequestPreSigner is a facade for pre-signing blob requests, regardless of +// how authentication was performed. +type RequestPreSigner func(ctx context.Context, values sas.BlobSignatureValues) (sas.QueryParameters, error) + +// NewSharedKeyRequestPreSigner will create a RequestPreSigner when a shared key +// is used as the authentication method. +func NewSharedKeyRequestPreSigner(accountName, key string) (RequestPreSigner, error) { + cred, err := azblob.NewSharedKeyCredential(accountName, key) + if err != nil { + return nil, err + } + return func(_ context.Context, values sas.BlobSignatureValues) (sas.QueryParameters, error) { + return values.SignWithSharedKey(cred) + }, nil +} + +// Azure recommends a 15 minute buffer for SAS tokens (and most tokens) in order to account +// for clock skew between their systems and clients. From their docs... +// +// > remember that you may observe up to 15 minutes of clock skew in either direction +// > on any request +// +// https://learn.microsoft.com/en-us/azure/storage/common/storage-sas-overview +var clockSkewBuffer = time.Minute * 15 + +// NewDelegatedKeyPreSigner will create a RequestPreSigner that worked with delegated +// credentials, necessary when identity-based authentication (AD auth) is the used with +// the SDK. +func NewDelegatedKeyPreSigner(serviceClient *service.Client) (RequestPreSigner, error) { + return func(ctx context.Context, values sas.BlobSignatureValues) (sas.QueryParameters, error) { + // Create the delegate key with a time buffer, since the blob key's lifetime + // must fit within the delegate key's lifetime. + delegateCredsStartTime := values.StartTime.UTC().Add(-1 * clockSkewBuffer) + delegateCredsEndTime := values.ExpiryTime.UTC().Add(clockSkewBuffer) + + udc, err := serviceClient.GetUserDelegationCredential( + ctx, + service.KeyInfo{ + Start: to.Ptr(delegateCredsStartTime.Format(sas.TimeFormat)), + Expiry: to.Ptr(delegateCredsEndTime.Format(sas.TimeFormat)), + }, + nil) + + if err != nil { + return sas.QueryParameters{}, err + } + return values.SignWithUserDelegation(udc) + }, nil +} + +// makeAzureCompatMetadataMap converts a stow-style metadata map into an Azure compatible +// metadata map. +func makeAzureCompatMetadataMap(md map[string]interface{}) (map[string]*string, error) { + azcompatMap := make(map[string]*string, len(md)) + for k, v := range md { + vStr, ok := v.(string) + if ok { + azcompatMap[k] = &vStr + } else { + // TODO: This is debatable. A fmt.Sprintf("%v", v) would be more flexible. However + // this is inline with the previous behavior + return nil, errors.Errorf(`value of key '%s' in metadata must be of type string`, k) + } + } + return azcompatMap, nil +} + +// makeStowCompatMetadataMap converts an Azure SDK metadata map into one that is stow +// compatible. +func makeStowCompatMetadataMap(azureMap map[string]*string) map[string]interface{} { + stowMap := make(map[string]interface{}, len(azureMap)) + for k, v := range azureMap { + stowMap[k] = *v + } + return stowMap +} diff --git a/azure/stow_test.go b/azure/stow_test.go index b09e98aa..80f9cc7f 100644 --- a/azure/stow_test.go +++ b/azure/stow_test.go @@ -2,27 +2,71 @@ package azure import ( "fmt" + "net/http" "os" "reflect" + "strconv" + "strings" "testing" "github.com/cheekybits/is" - "github.com/graymeta/stow" - "github.com/graymeta/stow/test" + "github.com/flyteorg/stow" + "github.com/flyteorg/stow/test" ) var ( - azureaccount = os.Getenv("AZUREACCOUNT") - azurekey = os.Getenv("AZUREKEY") + azureaccount = os.Getenv("AZUREACCOUNT") + azurekey = os.Getenv("AZUREKEY") + azureBigFileTestSize = os.Getenv("AZUREBIGFILETESTSIZEMB") ) -func TestStow(t *testing.T) { - if azureaccount == "" || azurekey == "" { - t.Skip("skipping test because missing either AZUREACCOUNT or AZUREKEY") +func presignedRequestPreparer(method stow.ClientMethod, r *http.Request) error { + if method == stow.ClientMethodPut { + r.Header.Set("x-ms-blob-type", "BlockBlob") + } + return nil +} + +func TestStowWithSharedKeyAuth(t *testing.T) { + if azureaccount == "" { + t.Skip("skipping test because missing AZUREACCOUNT") + } + if azurekey == "" { + t.Skip("skipping test because missing AZUREKEY") } cfg := stow.ConfigMap{"account": azureaccount, "key": azurekey} test.All(t, "azure", cfg) + test.ContainerPreSignRequest(t, "azure", cfg, presignedRequestPreparer) + test.ExistingContainerDoesNotProduceAnError(t, "azure", cfg) +} + +func TestStowWithDefaultADAuth(t *testing.T) { + if azureaccount == "" { + t.Skip("skipping test because missing AZUREACCOUNT") + } + + cfg := stow.ConfigMap{"account": azureaccount} + test.All(t, "azure", cfg) + test.ContainerPreSignRequest(t, "azure", cfg, presignedRequestPreparer) + test.ExistingContainerDoesNotProduceAnError(t, "azure", cfg) +} + +func TestBigFileUpload(t *testing.T) { + if azureBigFileTestSize == "" { + t.Skip("skipping test because missing AZUREBIGFILETESTSIZEMB is not set") + } + if azureaccount == "" { + t.Skip("skipping test because missing AZUREACCOUNT") + } + + fileSize, err := strconv.ParseInt(azureBigFileTestSize, 10, 64) + if err != nil { + t.Fatalf("Invalid value for AZUREBIGFILETESTSIZEMB: %s", azureBigFileTestSize) + } + + cfg := stow.ConfigMap{"account": azureaccount} + test.BigFileUpload(t, "azure", cfg, fileSize*1000*1000) } func TestEtagCleanup(t *testing.T) { @@ -46,35 +90,56 @@ func TestEtagCleanup(t *testing.T) { } } -func TestPrepMetadataSuccess(t *testing.T) { +func TestMetaMapRoundTrip(t *testing.T) { is := is.New(t) - m := make(map[string]string) - m["one"] = "two" - m["3"] = "4" - m["ninety-nine"] = "100" + stowMap := make(map[string]interface{}) + stowMap["one"] = "two" + stowMap["3"] = "4" + stowMap["ninety-nine"] = "100" - m2 := make(map[string]interface{}) - for key, value := range m { - m2[key] = value + expectedAzureMap := make(map[string]*string) + for key, value := range stowMap { + vStr, _ := value.(string) + expectedAzureMap[key] = &vStr } //returns map[string]interface - returnedMap, err := prepMetadata(m2) + azureCompatMap, err := makeAzureCompatMetadataMap(stowMap) is.NoErr(err) - if !reflect.DeepEqual(returnedMap, m) { - t.Errorf("Expected map (%+v) and returned map (%+v) are not equal.", m, returnedMap) + if !reflect.DeepEqual(azureCompatMap, expectedAzureMap) { + t.Errorf("Expected map (%+v) and returned map (%+v) are not equal.", expectedAzureMap, azureCompatMap) + } + + convertedStowMap := makeStowCompatMetadataMap(azureCompatMap) + if !reflect.DeepEqual(stowMap, convertedStowMap) { + t.Errorf("Expected map (%+v) and returned map (%+v) are not equal.", stowMap, convertedStowMap) } } -func TestPrepMetadataFailureWithNonStringValues(t *testing.T) { +func TestMakeAzureCompatMetadataMapFailureWithNonStringValues(t *testing.T) { is := is.New(t) m := make(map[string]interface{}) m["float"] = 8.9 m["number"] = 9 - _, err := prepMetadata(m) + _, err := makeAzureCompatMetadataMap(m) is.Err(err) } + +func TestRemovedConfigOptionsCausesFailure(t *testing.T) { + is := is.New(t) + for _, removedKey := range removedConfigKeys { + cfg := stow.ConfigMap{"account": "ignore"} + cfg[removedKey] = "anything" + err := stow.Validate("azure", cfg) + is.Err(err) + if !strings.Contains(err.Error(), "removed config option used") || + !strings.Contains(err.Error(), removedKey) { + is.Failf("Unexpected error message: %s", err.Error()) + } + + } +} diff --git a/b2/config.go b/b2/config.go index 1d9c2c49..7bd97ebb 100644 --- a/b2/config.go +++ b/b2/config.go @@ -4,7 +4,7 @@ import ( "errors" "net/url" - "github.com/graymeta/stow" + "github.com/flyteorg/stow" "gopkg.in/kothar/go-backblaze.v0" ) diff --git a/b2/container.go b/b2/container.go index 852258cb..417e3387 100644 --- a/b2/container.go +++ b/b2/container.go @@ -1,11 +1,13 @@ package b2 import ( + "context" + "fmt" "io" "strings" "time" - "github.com/graymeta/stow" + "github.com/flyteorg/stow" "github.com/pkg/errors" "gopkg.in/kothar/go-backblaze.v0" ) @@ -24,6 +26,11 @@ func (c *container) ID() string { return c.bucket.Name } +func (c *container) PreSignRequest(_ context.Context, _ stow.ClientMethod, _ string, + _ stow.PresignRequestParams) (url stow.PresignResponse, err error) { + return stow.PresignResponse{}, fmt.Errorf("unsupported") +} + // Name returns the name of the bucket func (c *container) Name() string { return c.bucket.Name diff --git a/b2/item.go b/b2/item.go index 1e949448..b854f6f8 100644 --- a/b2/item.go +++ b/b2/item.go @@ -6,7 +6,7 @@ import ( "sync" "time" - "github.com/graymeta/stow" + "github.com/flyteorg/stow" "github.com/pkg/errors" "gopkg.in/kothar/go-backblaze.v0" diff --git a/b2/location.go b/b2/location.go index b9f45023..a99eb50b 100644 --- a/b2/location.go +++ b/b2/location.go @@ -5,7 +5,7 @@ import ( "net/url" "strings" - "github.com/graymeta/stow" + "github.com/flyteorg/stow" "gopkg.in/kothar/go-backblaze.v0" ) diff --git a/b2/stow_test.go b/b2/stow_test.go index 006d3817..5a5d383e 100644 --- a/b2/stow_test.go +++ b/b2/stow_test.go @@ -8,8 +8,8 @@ import ( "time" isi "github.com/cheekybits/is" - "github.com/graymeta/stow" - "github.com/graymeta/stow/test" + "github.com/flyteorg/stow" + "github.com/flyteorg/stow/test" ) func TestStow(t *testing.T) { diff --git a/clientmethod_enumer.go b/clientmethod_enumer.go new file mode 100644 index 00000000..f966d591 --- /dev/null +++ b/clientmethod_enumer.go @@ -0,0 +1,68 @@ +// Code generated by "enumer --type=ClientMethod --trimprefix=ClientMethod -json"; DO NOT EDIT. + +// +package stow + +import ( + "encoding/json" + "fmt" +) + +const _ClientMethodName = "GetPut" + +var _ClientMethodIndex = [...]uint8{0, 3, 6} + +func (i ClientMethod) String() string { + if i < 0 || i >= ClientMethod(len(_ClientMethodIndex)-1) { + return fmt.Sprintf("ClientMethod(%d)", i) + } + return _ClientMethodName[_ClientMethodIndex[i]:_ClientMethodIndex[i+1]] +} + +var _ClientMethodValues = []ClientMethod{0, 1} + +var _ClientMethodNameToValueMap = map[string]ClientMethod{ + _ClientMethodName[0:3]: 0, + _ClientMethodName[3:6]: 1, +} + +// ClientMethodString retrieves an enum value from the enum constants string name. +// Throws an error if the param is not part of the enum. +func ClientMethodString(s string) (ClientMethod, error) { + if val, ok := _ClientMethodNameToValueMap[s]; ok { + return val, nil + } + return 0, fmt.Errorf("%s does not belong to ClientMethod values", s) +} + +// ClientMethodValues returns all values of the enum +func ClientMethodValues() []ClientMethod { + return _ClientMethodValues +} + +// IsAClientMethod returns "true" if the value is listed in the enum definition. "false" otherwise +func (i ClientMethod) IsAClientMethod() bool { + for _, v := range _ClientMethodValues { + if i == v { + return true + } + } + return false +} + +// MarshalJSON implements the json.Marshaler interface for ClientMethod +func (i ClientMethod) MarshalJSON() ([]byte, error) { + return json.Marshal(i.String()) +} + +// UnmarshalJSON implements the json.Unmarshaler interface for ClientMethod +func (i *ClientMethod) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return fmt.Errorf("ClientMethod should be a string, got %s", data) + } + + var err error + *i, err = ClientMethodString(s) + return err +} diff --git a/go.mod b/go.mod index a756f26b..f0732945 100644 --- a/go.mod +++ b/go.mod @@ -1,27 +1,59 @@ -module github.com/graymeta/stow +module github.com/flyteorg/stow -go 1.14 +go 1.18 require ( - cloud.google.com/go v0.38.0 - github.com/Azure/azure-sdk-for-go v32.5.0+incompatible - github.com/Azure/go-autorest/autorest v0.9.0 // indirect - github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect - github.com/aws/aws-sdk-go v1.23.4 + cloud.google.com/go/storage v1.22.0 + github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.0 + github.com/aws/aws-sdk-go v1.44.2 github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 - github.com/dnaeon/go-vcr v1.1.0 // indirect - github.com/google/readahead v0.0.0-20161222183148-eaceba169032 // indirect - github.com/hashicorp/go-multierror v1.0.0 + github.com/hashicorp/go-multierror v1.1.1 + github.com/ncw/swift v1.0.53 + github.com/pkg/errors v0.9.1 + github.com/pkg/sftp v1.13.4 + github.com/stretchr/testify v1.9.0 + golang.org/x/crypto v0.25.0 + golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 + google.golang.org/api v0.76.0 + gopkg.in/kothar/go-backblaze.v0 v0.0.0-20210124194846-35409b867216 +) + +require ( + cloud.google.com/go/iam v0.3.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/golang-jwt/jwt/v5 v5.2.1 // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/googleapis/gax-go/v2 v2.3.0 // indirect + github.com/googleapis/go-type-adapters v1.0.0 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kr/fs v0.1.0 // indirect - github.com/ncw/swift v1.0.49 - github.com/pkg/errors v0.8.1 - github.com/pkg/sftp v1.10.0 - github.com/pquerna/ffjson v0.0.0-20190813045741-dac163c6c0a9 // indirect - github.com/satori/go.uuid v1.2.0 // indirect - github.com/stretchr/testify v1.4.0 - golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 - golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 // indirect - golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 - google.golang.org/api v0.8.0 - gopkg.in/kothar/go-backblaze.v0 v0.0.0-20190520213052-702d4e7eb465 + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + go.opencensus.io v0.23.0 // indirect + golang.org/x/sys v0.22.0 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.28.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +require ( + cloud.google.com/go v0.101.0 // indirect + cloud.google.com/go/compute v1.6.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0 + github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 + github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 // indirect + github.com/golang/glog v1.0.0 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/google/go-cmp v0.5.8 // indirect + github.com/google/readahead v0.0.0-20161222183148-eaceba169032 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7 // indirect + golang.org/x/net v0.27.0 // indirect + google.golang.org/genproto v0.0.0-20220426171045-31bebdecfb46 // indirect + google.golang.org/grpc v1.46.0 // indirect ) diff --git a/go.sum b/go.sum index 85bd08ca..cdb190d0 100644 --- a/go.sum +++ b/go.sum @@ -1,152 +1,703 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -github.com/Azure/azure-sdk-for-go v32.5.0+incompatible h1:Hn/DsObfmw0M7dMGS/c0MlVrJuGFzHzOpBWL89acR68= -github.com/Azure/azure-sdk-for-go v32.5.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= -github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.9.0 h1:MRvx8gncNaXJqOoLmhNjUAKh33JJF8LyxPhomEtOsjs= -github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= -github.com/Azure/go-autorest/autorest/adal v0.5.0 h1:q2gDruN08/guU9vAjuPWff0+QIrpH6ediguzdAzXAUU= -github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= -github.com/Azure/go-autorest/autorest/date v0.1.0 h1:YGrhWfrgtFs84+h0o46rJrlmsZtyZRg470CqAXTZaGM= -github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= -github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/mocks v0.2.0 h1:Ww5g4zThfD/6cLb4z6xxgeyDa7QDkizMkJKe0ysZXp0= -github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= -github.com/Azure/go-autorest/autorest/to v0.4.0/go.mod h1:fE8iZBn7LQR7zH/9XU2NcPR4o9jEImooCeWJcYV/zLE= -github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY= -github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= -github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= -github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= +cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= +cloud.google.com/go v0.101.0 h1:g+LL+JvpvdyGtcaD2xw2mSByE/6F9s471eJSoaysM84= +cloud.google.com/go v0.101.0/go.mod h1:hEiddgDb77jDQ+I80tURYNJEnuwPzFU8awCFFRLKjW0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= +cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= +cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= +cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= +cloud.google.com/go/compute v1.6.1 h1:2sMmt8prCn7DPaG4Pmh0N3Inmc8cT8ae5k1M6VJ9Wqc= +cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/iam v0.3.0 h1:exkAomrVUuzx9kWFI1wm3KI0uoDeUFPB4kKGzx6x+Gc= +cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.22.0 h1:NUV0NNp9nkBuW66BFRLuMgldN60C57ET3dhbwLIYio8= +cloud.google.com/go/storage v1.22.0/go.mod h1:GbaLEoMqbVm6sx3Z0R++gSiBlgMv6yUi2q1DeGFKQgE= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0 h1:GJHeeA2N7xrG3q30L2UXDyuWRzDM900/65j70wcM4Ww= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.13.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0 h1:PiSrjRPpkQNjrM8H0WwKMnZUdu1RGMtd/LdGKUrOo+c= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.0 h1:Be6KInmFEKV81c0pOAEbRYehLMwmmGI1exuFj248AMk= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.4.0/go.mod h1:WCPBHsOXfBVnivScjs2ypRfimjEW0qPVLGgJkZlrIOA= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/aws/aws-sdk-go v1.23.4 h1:F6f/iQRhuSfrpUdy80q29898H0NYN27pX+95tkJ+BIY= -github.com/aws/aws-sdk-go v1.23.4/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/aws/aws-sdk-go v1.44.2 h1:5VBk5r06bgxgRKVaUtm1/4NT/rtrnH2E4cnAYv5zgQc= +github.com/aws/aws-sdk-go v1.44.2/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 h1:SKI1/fuSdodxmNNyVBR8d7X/HuLnRpvvFO0AgyQk764= github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c= -github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0 h1:nfP3RFugxnNRyKgeWd4oI1nYvXpxrx8ck8ZrcizshdQ= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= +github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/readahead v0.0.0-20161222183148-eaceba169032 h1:6Be3nkuJFyRfCgr6qTIzmRp8y9QwDIbqy/nYr9WDPos= github.com/google/readahead v0.0.0-20161222183148-eaceba169032/go.mod h1:qYysrqQXuV4tzsizt4oOQ6mrBZQ0xnQXP3ylXX8Jk5Y= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= +github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= +github.com/googleapis/gax-go/v2 v2.3.0 h1:nRJtk3y8Fm770D42QV6T90ZnvFZyk7agSo3Q+Z9p3WI= +github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= +github.com/googleapis/go-type-adapters v1.0.0 h1:9XdMn+d/G57qq1s8dNc5IesGCXHf6V2HZ2JwRxfA2tA= +github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= -github.com/ncw/swift v1.0.49 h1:eQaKIjSt/PXLKfYgzg01nevmO+CMXfXGRhB1gOhDs7E= -github.com/ncw/swift v1.0.49/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.10.0 h1:DGA1KlA9esU6WcicH+P8PxFZOl15O6GYtab1cIJdOlE= -github.com/pkg/sftp v1.10.0/go.mod h1:NxmoDg/QLVWluQDUYG7XBZTLUpKeFa8e3aMf1BfjyHk= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/ncw/swift v1.0.53 h1:luHjjTNtekIEvHg5KdAFIBaH7bWfNkefwFnpDffSIks= +github.com/ncw/swift v1.0.53/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.4 h1:Lb0RYJCmgUcBgZosfoi9Y9sbl6+LJgOIgk/2Y4YjMFg= +github.com/pkg/sftp v1.13.4/go.mod h1:LzqnAvaD5TWeNBsZpfKxSYn1MbjWwOsCIAFFJbpIsK8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pquerna/ffjson v0.0.0-20190813045741-dac163c6c0a9 h1:kyf9snWXHvQc+yxE9imhdI8YAm4oKeZISlaAR+x73zs= -github.com/pquerna/ffjson v0.0.0-20190813045741-dac163c6c0a9/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M= -github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7 h1:xoIK0ctDddBMnc74udxJYBqlo9Ylnsp1waqjLsnef20= +github.com/pquerna/ffjson v0.0.0-20190930134022-aa0246cd15f7/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -go.opencensus.io v0.21.0 h1:mU6zScU4U1YAFPHEHYk+3JC4SY7JxgkqS10ZOSyksNg= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5 h1:OSnWWcOd/CtWQC2cYSBgbTSJv3ciqd8r54ySIW2y3RE= +golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b h1:ag/x1USPSsqHud38I9BAC88qdNLDHHtQ4mlgQIZPPNA= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f h1:GGU+dLjvlC3qDwqYgL6UgRmHXhOOgns0bZu2Ty5mm6U= +golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.8.0 h1:VGGbLNyPF7dvYHhcUGYBBGCRDDK0RRJAI6KCvo0CL+E= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= +google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= +google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= +google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= +google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= +google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= +google.golang.org/api v0.76.0 h1:UkZl25bR1FHNqtK/EKs3vCdpZtUO6gea3YElTwc8pQg= +google.golang.org/api v0.76.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873 h1:nfPFGzJkUDX6uBmpN/pSw7MbOAWegH5QDQuoXFHedLg= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= +google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/genproto v0.0.0-20220405205423-9d709892a2bf/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= +google.golang.org/genproto v0.0.0-20220426171045-31bebdecfb46 h1:G1IeWbjrqEq9ChWxEuRPJu6laA67+XgTFHVSAvepr38= +google.golang.org/genproto v0.0.0-20220426171045-31bebdecfb46/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1 h1:Hz2g2wirWK7H0qIIhGIqRGTuMwTE8HEKFnDZZ7lm9NU= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +google.golang.org/grpc v1.46.0 h1:oCjezcn6g6A75TGoKYBPgKmVBLexhYLM6MebdrPApP8= +google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/kothar/go-backblaze.v0 v0.0.0-20190520213052-702d4e7eb465 h1:DKgyTtKkmpZZesLue2fz/LxEhzBDUWg4N8u/BVRJqlA= -gopkg.in/kothar/go-backblaze.v0 v0.0.0-20190520213052-702d4e7eb465/go.mod h1:zJ2QpyDCYo1KvLXlmdnFlQAyF/Qfth0fB8239Qg7BIE= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/kothar/go-backblaze.v0 v0.0.0-20210124194846-35409b867216 h1:2TSTkQ8PMvGOD5eeqqRVv6Z9+BYI+bowK97RCr3W+9M= +gopkg.in/kothar/go-backblaze.v0 v0.0.0-20210124194846-35409b867216/go.mod h1:zJ2QpyDCYo1KvLXlmdnFlQAyF/Qfth0fB8239Qg7BIE= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/google/config.go b/google/config.go index 1ad699a5..d854bd0a 100644 --- a/google/config.go +++ b/google/config.go @@ -10,11 +10,12 @@ import ( "golang.org/x/oauth2/google" "google.golang.org/api/option" - "github.com/graymeta/stow" + "github.com/flyteorg/stow" ) // Kind represents the name of the location/storage type. const Kind = "google" +const Protocol = "gs" const ( // The service account json blob diff --git a/google/container.go b/google/container.go index 3caec3b9..bad274c6 100644 --- a/google/container.go +++ b/google/container.go @@ -2,15 +2,21 @@ package google import ( "context" + "fmt" "io" + "net/http" + "strconv" + "time" "cloud.google.com/go/storage" "github.com/pkg/errors" "google.golang.org/api/iterator" - "github.com/graymeta/stow" + "github.com/flyteorg/stow" ) +const googleMetadataPrefix = "x-goog-meta-" + type Container struct { // Name is needed to retrieve items. name string @@ -33,10 +39,38 @@ func (c *Container) Name() string { } // Bucket returns the google bucket attributes -func (c *Container) Bucket() *storage.BucketHandle{ +func (c *Container) Bucket() *storage.BucketHandle { return c.client.Bucket(c.name) } +func (c *Container) PreSignRequest(_ context.Context, clientMethod stow.ClientMethod, id string, + params stow.PresignRequestParams) (response stow.PresignResponse, err error) { + headers := make([]string, 0, 3) + var requestHeaders map[string]string + if len(params.HttpMethod) == 0 { + switch clientMethod { + case stow.ClientMethodGet: + params.HttpMethod = http.MethodGet + case stow.ClientMethodPut: + params.HttpMethod = http.MethodPut + requestHeaders = map[string]string{"Content-Length": strconv.Itoa(len(params.ContentMD5)), "Content-MD5": params.ContentMD5} + if params.AddContentMD5Metadata { + headers = append(headers, fmt.Sprintf("%s%s: %s", googleMetadataPrefix, stow.FlyteContentMD5, params.ContentMD5)) + requestHeaders[fmt.Sprintf("%s%s", googleMetadataPrefix, stow.FlyteContentMD5)] = params.ContentMD5 + } + } + } + + url, error := c.Bucket().SignedURL(id, &storage.SignedURLOptions{ + Method: params.HttpMethod, + Expires: time.Now().Add(params.ExpiresIn), + MD5: params.ContentMD5, + Headers: headers, + }) + + return stow.PresignResponse{Url: url, RequiredRequestHeaders: requestHeaders}, error +} + // Item returns a stow.Item instance of a container based on the // name of the container func (c *Container) Item(id string) (stow.Item, error) { @@ -94,24 +128,29 @@ func (c *Container) Put(name string, r io.Reader, size int64, metadata map[strin } w := obj.NewWriter(c.ctx) + w.ObjectAttrs.Metadata = merge(w.ObjectAttrs.Metadata, mdPrepped) if _, err := io.Copy(w, r); err != nil { return nil, err } - w.Close() - - attr, err := obj.Update(c.ctx, storage.ObjectAttrsToUpdate{Metadata: mdPrepped}) - if err != nil { + if err = w.Close(); err != nil { return nil, err } - return c.convertToStowItem(attr) + return c.convertToStowItem(w.Attrs()) } -func (c *Container) convertToStowItem(attr *storage.ObjectAttrs) (stow.Item, error) { - u, err := prepUrl(attr.MediaLink) - if err != nil { - return nil, err +func merge(metadata ...map[string]string) map[string]string { + res := map[string]string{} + for _, mt := range metadata { + for k, v := range mt { + res[k] = v + } } + return res +} + +func (c *Container) convertToStowItem(attr *storage.ObjectAttrs) (stow.Item, error) { + u := prepUrl(attr) mdParsed, err := parseMetadata(attr.Metadata) if err != nil { diff --git a/google/item.go b/google/item.go index a6b7e946..6c3952ef 100644 --- a/google/item.go +++ b/google/item.go @@ -75,15 +75,21 @@ func (i *Item) StorageObject() *storage.ObjectAttrs { return i.object } -// prepUrl takes a MediaLink string and returns a url -func prepUrl(str string) (*url.URL, error) { - u, err := url.Parse(str) - if err != nil { - return nil, err +// prepUrl takes ObjectAttrs and returns a url constructed from the bucket and name. +// We don't use attr.MediaLink because it is in the format +// `google://storage.googleapis.com/download/storage/v1/b/some-bucket/...` instead of +// `gs://some-bucket/...`. +// Otherwise, when using stow to list a bucket `gs://...`, the resulting objects +// `google://storage.googleapis.com/download/storage/v1/b/...` could not be found +// using the same stow client. +func prepUrl(attr *storage.ObjectAttrs) *url.URL { + u := url.URL{ + Scheme: Protocol, + Host: attr.Bucket, + Path: attr.Name, } - u.Scheme = "google" // Discard the query string u.RawQuery = "" - return u, nil + return &u } diff --git a/google/location.go b/google/location.go index eacdb39a..da3d13f5 100644 --- a/google/location.go +++ b/google/location.go @@ -10,7 +10,7 @@ import ( "google.golang.org/api/googleapi" "google.golang.org/api/iterator" - "github.com/graymeta/stow" + "github.com/flyteorg/stow" ) // A Location contains a client + the configurations used to create the client. @@ -113,22 +113,34 @@ func (l *Location) RemoveContainer(id string) error { // ItemByURL retrieves a stow.Item by parsing the URL, in this // case an item is an object. func (l *Location) ItemByURL(url *url.URL) (stow.Item, error) { - if url.Scheme != Kind { - return nil, errors.New("not valid google storage URL") - } + if url.Scheme == Kind { + // Url in the form: google://storage.googleapis.com/download/storage/v1/b/some-bucket + pieces := strings.SplitN(url.Path, "/", 8) - // /download/storage/v1/b/stowtesttoudhratik/o/a_first%2Fthe%20item - pieces := strings.SplitN(url.Path, "/", 8) + c, err := l.Container(pieces[5]) + if err != nil { + return nil, stow.ErrNotFound + } - c, err := l.Container(pieces[5]) - if err != nil { - return nil, stow.ErrNotFound - } + i, err := c.Item(pieces[7]) + if err != nil { + return nil, stow.ErrNotFound + } + return i, nil - i, err := c.Item(pieces[7]) - if err != nil { - return nil, stow.ErrNotFound - } + } else if url.Scheme == Protocol { + // Url in the form: gs://some-bucket + c, err := l.Container(url.Host) + if err != nil { + return nil, stow.ErrNotFound + } - return i, nil + i, err := c.Item(url.Path) + if err != nil { + return nil, stow.ErrNotFound + } + return i, nil + } else { + return nil, errors.New("not valid google storage URL") + } } diff --git a/google/stow_test.go b/google/stow_test.go index 945bcf6e..98cffbf6 100644 --- a/google/stow_test.go +++ b/google/stow_test.go @@ -8,8 +8,8 @@ import ( "github.com/cheekybits/is" - "github.com/graymeta/stow" - "github.com/graymeta/stow/test" + "github.com/flyteorg/stow" + "github.com/flyteorg/stow/test" ) func TestStow(t *testing.T) { diff --git a/local/container.go b/local/container.go index 6b11feef..b583991d 100644 --- a/local/container.go +++ b/local/container.go @@ -1,14 +1,16 @@ package local import ( + "context" "errors" + "fmt" "io" "net/url" "os" "path/filepath" "strings" - "github.com/graymeta/stow" + "github.com/flyteorg/stow" ) type container struct { @@ -31,6 +33,11 @@ func (c *container) URL() *url.URL { } } +func (c *container) PreSignRequest(_ context.Context, _ stow.ClientMethod, _ string, + _ stow.PresignRequestParams) (response stow.PresignResponse, err error) { + return stow.PresignResponse{}, fmt.Errorf("unsupported") +} + func (c *container) CreateItem(name string) (stow.Item, io.WriteCloser, error) { path := filepath.Join(c.path, filepath.FromSlash(name)) item := &item{ diff --git a/local/container_test.go b/local/container_test.go index 6af32a09..615d992b 100644 --- a/local/container_test.go +++ b/local/container_test.go @@ -6,8 +6,8 @@ import ( "testing" "github.com/cheekybits/is" - "github.com/graymeta/stow" - "github.com/graymeta/stow/local" + "github.com/flyteorg/stow" + "github.com/flyteorg/stow/local" ) func TestItemsPaging(t *testing.T) { diff --git a/local/item_test.go b/local/item_test.go index ff817d33..c7480a23 100644 --- a/local/item_test.go +++ b/local/item_test.go @@ -10,8 +10,8 @@ import ( "testing" "github.com/cheekybits/is" - "github.com/graymeta/stow" - "github.com/graymeta/stow/local" + "github.com/flyteorg/stow" + "github.com/flyteorg/stow/local" ) func TestItemReader(t *testing.T) { diff --git a/local/local.go b/local/local.go index 519dac51..d024940b 100644 --- a/local/local.go +++ b/local/local.go @@ -5,7 +5,7 @@ import ( "net/url" "os" - "github.com/graymeta/stow" + "github.com/flyteorg/stow" ) // ConfigKeys are the supported configuration items for diff --git a/local/location.go b/local/location.go index 580f5ab3..ab91bd74 100644 --- a/local/location.go +++ b/local/location.go @@ -6,7 +6,7 @@ import ( "os" "path/filepath" - "github.com/graymeta/stow" + "github.com/flyteorg/stow" ) type location struct { diff --git a/local/location_test.go b/local/location_test.go index 0d87b04a..d9872071 100644 --- a/local/location_test.go +++ b/local/location_test.go @@ -6,8 +6,8 @@ import ( "testing" "github.com/cheekybits/is" - "github.com/graymeta/stow" - "github.com/graymeta/stow/local" + "github.com/flyteorg/stow" + "github.com/flyteorg/stow/local" ) func TestContainers(t *testing.T) { diff --git a/local/stow_test.go b/local/stow_test.go index b79ec79a..6c59d341 100644 --- a/local/stow_test.go +++ b/local/stow_test.go @@ -6,8 +6,8 @@ import ( "testing" "github.com/cheekybits/is" - "github.com/graymeta/stow" - "github.com/graymeta/stow/test" + "github.com/flyteorg/stow" + "github.com/flyteorg/stow/test" ) func TestStow(t *testing.T) { diff --git a/local/util_test.go b/local/util_test.go index 187048af..aa7e63de 100644 --- a/local/util_test.go +++ b/local/util_test.go @@ -8,8 +8,8 @@ import ( "testing" "github.com/cheekybits/is" - "github.com/graymeta/stow" - "github.com/graymeta/stow/local" + "github.com/flyteorg/stow" + "github.com/flyteorg/stow/local" ) func setup() (string, func() error, error) { diff --git a/location_test.go b/location_test.go index 1ef4fd8e..a52cb915 100644 --- a/location_test.go +++ b/location_test.go @@ -3,7 +3,7 @@ package stow_test import ( "net/url" - "github.com/graymeta/stow" + "github.com/flyteorg/stow" ) func init() { diff --git a/oracle/config.go b/oracle/config.go index 655ea85d..2c6dd1cf 100644 --- a/oracle/config.go +++ b/oracle/config.go @@ -7,7 +7,7 @@ import ( "net/url" "strings" - "github.com/graymeta/stow" + "github.com/flyteorg/stow" "github.com/ncw/swift" ) diff --git a/oracle/container.go b/oracle/container.go index 5c5aa41f..2c504246 100644 --- a/oracle/container.go +++ b/oracle/container.go @@ -1,10 +1,12 @@ package oracle import ( + "context" + "fmt" "io" "strings" - "github.com/graymeta/stow" + "github.com/flyteorg/stow" "github.com/ncw/swift" "github.com/pkg/errors" ) @@ -16,6 +18,11 @@ type container struct { var _ stow.Container = (*container)(nil) +func (c *container) PreSignRequest(_ context.Context, _ stow.ClientMethod, _ string, + _ stow.PresignRequestParams) (response stow.PresignResponse, err error) { + return stow.PresignResponse{}, fmt.Errorf("unsupported") +} + // ID returns a string value representing a unique container, in this case it's // the Container's name. func (c *container) ID() string { diff --git a/oracle/item.go b/oracle/item.go index 96efbd59..03101530 100644 --- a/oracle/item.go +++ b/oracle/item.go @@ -7,7 +7,7 @@ import ( "sync" "time" - "github.com/graymeta/stow" + "github.com/flyteorg/stow" "github.com/ncw/swift" ) diff --git a/oracle/location.go b/oracle/location.go index d3027b56..2f432ff4 100644 --- a/oracle/location.go +++ b/oracle/location.go @@ -5,7 +5,7 @@ import ( "net/url" "strings" - "github.com/graymeta/stow" + "github.com/flyteorg/stow" "github.com/ncw/swift" ) diff --git a/oracle/stow_test.go b/oracle/stow_test.go index 83fafe4a..c3d79c0d 100644 --- a/oracle/stow_test.go +++ b/oracle/stow_test.go @@ -8,8 +8,8 @@ import ( "testing" "github.com/cheekybits/is" - "github.com/graymeta/stow" - "github.com/graymeta/stow/test" + "github.com/flyteorg/stow" + "github.com/flyteorg/stow/test" ) var cfgUnmetered = stow.ConfigMap{ diff --git a/s3/config.go b/s3/config.go index 2d6baf92..5f24bf2d 100644 --- a/s3/config.go +++ b/s3/config.go @@ -9,7 +9,7 @@ import ( "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/s3" - "github.com/graymeta/stow" + "github.com/flyteorg/stow" "github.com/pkg/errors" ) @@ -33,7 +33,7 @@ const ( // ConfigToken is an optional argument which is required when providing // credentials with temporary access. - // ConfigToken = "token" + ConfigToken = "token" // ConfigRegion represents the region/availability zone of the session. ConfigRegion = "region" @@ -127,7 +127,7 @@ func newS3Client(config stow.Config, region string) (client *s3.S3, endpoint str authType, _ := config.Config(ConfigAuthType) accessKeyID, _ := config.Config(ConfigAccessKeyID) secretKey, _ := config.Config(ConfigSecretKey) - // token, _ := config.Config(ConfigToken) + token, _ := config.Config(ConfigToken) if authType == "" { authType = authTypeAccessKey @@ -150,7 +150,7 @@ func newS3Client(config stow.Config, region string) (client *s3.S3, endpoint str } if authType == authTypeAccessKey { - awsConfig.WithCredentials(credentials.NewStaticCredentials(accessKeyID, secretKey, "")) + awsConfig.WithCredentials(credentials.NewStaticCredentials(accessKeyID, secretKey, token)) } endpoint, ok := config.Config(ConfigEndpoint) diff --git a/s3/container.go b/s3/container.go index c172ea02..a865971c 100644 --- a/s3/container.go +++ b/s3/container.go @@ -1,14 +1,19 @@ package s3 import ( + "context" + "fmt" "io" + "strconv" "strings" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/s3" "github.com/aws/aws-sdk-go/service/s3/s3manager" - "github.com/graymeta/stow" + "github.com/flyteorg/stow" "github.com/pkg/errors" ) @@ -23,6 +28,46 @@ type container struct { customEndpoint string } +func (c *container) PreSignRequest(ctx context.Context, clientMethod stow.ClientMethod, id string, + params stow.PresignRequestParams) (response stow.PresignResponse, err error) { + + var req *request.Request + var requestHeaders map[string]string + switch clientMethod { + case stow.ClientMethodGet: + req, _ = c.client.GetObjectRequest(&s3.GetObjectInput{ + Bucket: aws.String(c.name), + Key: aws.String(id), + }) + case stow.ClientMethodPut: + var contentMD5 *string + if len(params.ContentMD5) > 0 { + contentMD5 = aws.String(params.ContentMD5) + } + + metadata := make(map[string]*string) + requestHeaders = map[string]string{"Content-Length": strconv.Itoa(len(params.ContentMD5)), "Content-MD5": params.ContentMD5} + if params.AddContentMD5Metadata { + metadata[stow.FlyteContentMD5] = aws.String(params.ContentMD5) + requestHeaders[fmt.Sprintf("x-amz-meta-%s", stow.FlyteContentMD5)] = params.ContentMD5 + } + + req, _ = c.client.PutObjectRequest(&s3.PutObjectInput{ + Bucket: aws.String(c.name), + Key: aws.String(id), + ContentMD5: contentMD5, + Metadata: metadata, + }) + default: + return stow.PresignResponse{}, fmt.Errorf("unsupported client method [%v]", clientMethod.String()) + } + + req.SetContext(ctx) + url, err := req.Presign(params.ExpiresIn) + + return stow.PresignResponse{Url: url, RequiredRequestHeaders: requestHeaders}, err +} + // ID returns a string value which represents the name of the container. func (c *container) ID() string { return c.name @@ -130,8 +175,9 @@ func (c *container) Put(name string, r io.Reader, size int64, metadata map[strin Key: aws.String(name), Bucket: aws.String(c.name), }) + var etag string - if i.ETag != nil && err == nil { + if err == nil && i.ETag != nil { etag = cleanEtag(*i.ETag) } diff --git a/s3/item.go b/s3/item.go index e0e83cb2..6eeaf400 100644 --- a/s3/item.go +++ b/s3/item.go @@ -10,7 +10,7 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/s3" - "github.com/graymeta/stow" + "github.com/flyteorg/stow" "github.com/pkg/errors" ) diff --git a/s3/location.go b/s3/location.go index 8d1f060d..848c48b7 100644 --- a/s3/location.go +++ b/s3/location.go @@ -10,7 +10,7 @@ import ( "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/s3" "github.com/aws/aws-sdk-go/service/s3/s3manager" - "github.com/graymeta/stow" + "github.com/flyteorg/stow" "github.com/pkg/errors" ) diff --git a/s3/stow_iam_test.go b/s3/stow_iam_test.go index 7f412b5f..cd2b4800 100644 --- a/s3/stow_iam_test.go +++ b/s3/stow_iam_test.go @@ -1,3 +1,4 @@ +//go:build iam // +build iam package s3 @@ -6,8 +7,8 @@ import ( "os" "testing" - "github.com/graymeta/stow" - "github.com/graymeta/stow/test" + "github.com/flyteorg/stow" + "github.com/flyteorg/stow/test" ) func TestStowIAM(t *testing.T) { diff --git a/s3/stow_test.go b/s3/stow_test.go index b4b28db0..189cfb3b 100644 --- a/s3/stow_test.go +++ b/s3/stow_test.go @@ -2,6 +2,7 @@ package s3 import ( + "context" "encoding/json" "fmt" "net/http" @@ -11,12 +12,15 @@ import ( "reflect" "strings" "testing" + "time" + + "github.com/stretchr/testify/assert" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/s3" "github.com/cheekybits/is" - "github.com/graymeta/stow" - "github.com/graymeta/stow/test" + "github.com/flyteorg/stow" + "github.com/flyteorg/stow/test" ) func TestStow(t *testing.T) { @@ -37,6 +41,36 @@ func TestStow(t *testing.T) { test.All(t, "s3", config) } +func TestPreSignedURL(t *testing.T) { + is := is.New(t) + accessKeyId := os.Getenv("S3ACCESSKEYID") + secretKey := os.Getenv("S3SECRETKEY") + region := os.Getenv("S3REGION") + + if accessKeyId == "" || secretKey == "" || region == "" { + t.Skip("skipping test because missing one or more of S3ACCESSKEYID S3SECRETKEY S3REGION") + } + + config := stow.ConfigMap{ + "access_key_id": accessKeyId, + "secret_key": secretKey, + "region": region, + } + + location, err := stow.Dial("s3", config) + is.NoErr(err) + + container, err := location.Container("flyte-demo") + ctx := context.Background() + res, err := container.PreSignRequest(ctx, stow.ClientMethodPut, "blah/bloh/fileon", stow.PresignRequestParams{ + ExpiresIn: time.Hour, + }) + + is.NoErr(err) + t.Log(res) + assert.NotEmpty(t, res) +} + func TestEtagCleanup(t *testing.T) { etagValue := "9c51403a2255f766891a1382288dece4" permutations := []string{ diff --git a/s3/v2signer.go b/s3/v2signer.go index 4b82b8d3..46a3dd54 100644 --- a/s3/v2signer.go +++ b/s3/v2signer.go @@ -231,4 +231,4 @@ const logSignInfoMsg = `DEBUG: Request Signature: func (v2 *signer) logSigningInfo() { msg := fmt.Sprintf(logSignInfoMsg, v2.stringToSign, v2.signature) v2.Logger.Log(msg) -} \ No newline at end of file +} diff --git a/sftp/config.go b/sftp/config.go index 5fa3e387..a5563c2b 100644 --- a/sftp/config.go +++ b/sftp/config.go @@ -5,7 +5,7 @@ import ( "net/url" "strconv" - "github.com/graymeta/stow" + "github.com/flyteorg/stow" "github.com/pkg/errors" "github.com/pkg/sftp" "golang.org/x/crypto/ssh" diff --git a/sftp/container.go b/sftp/container.go index f1b96224..baea7ede 100644 --- a/sftp/container.go +++ b/sftp/container.go @@ -1,13 +1,15 @@ package sftp import ( + "context" "errors" + "fmt" "io" "os" "path/filepath" "strings" - "github.com/graymeta/stow" + "github.com/flyteorg/stow" ) type container struct { @@ -25,6 +27,11 @@ func (c *container) Name() string { return c.name } +func (c *container) PreSignRequest(_ context.Context, _ stow.ClientMethod, _ string, + _ stow.PresignRequestParams) (response stow.PresignResponse, err error) { + return stow.PresignResponse{}, fmt.Errorf("unsupported") +} + // Item returns a stow.Item instance of a container based on the name of the // container and the file. func (c *container) Item(id string) (stow.Item, error) { diff --git a/sftp/item.go b/sftp/item.go index d7817555..aff4a087 100644 --- a/sftp/item.go +++ b/sftp/item.go @@ -8,7 +8,7 @@ import ( "path/filepath" "time" - "github.com/graymeta/stow/local" + "github.com/flyteorg/stow/local" ) type item struct { diff --git a/sftp/location.go b/sftp/location.go index 8a81f477..34d7e5b7 100644 --- a/sftp/location.go +++ b/sftp/location.go @@ -7,7 +7,7 @@ import ( "sort" "strings" - "github.com/graymeta/stow" + "github.com/flyteorg/stow" "github.com/hashicorp/go-multierror" "github.com/pkg/errors" "github.com/pkg/sftp" diff --git a/sftp/stow_test.go b/sftp/stow_test.go index c7038955..6d95e8b1 100644 --- a/sftp/stow_test.go +++ b/sftp/stow_test.go @@ -8,8 +8,8 @@ import ( "testing" "time" - "github.com/graymeta/stow" - "github.com/graymeta/stow/test" + "github.com/flyteorg/stow" + "github.com/flyteorg/stow/test" "github.com/stretchr/testify/require" ) diff --git a/stow.go b/stow.go index 2f02e2b5..e74cee75 100644 --- a/stow.go +++ b/stow.go @@ -1,6 +1,7 @@ package stow import ( + "context" "errors" "io" "net/url" @@ -45,6 +46,21 @@ var ( NoPrefix = "" ) +// HttpMethod defines an alias type for string to represent http Methods. These are defined in RFC 7231 section 4.3. +type HttpMethod = string + +//go:generate enumer --type=ClientMethod --trimprefix=ClientMethod -json + +// ClientMethod defines common client methods across storage providers +type ClientMethod int + +const ( + ClientMethodGet ClientMethod = iota + ClientMethodPut +) + +const FlyteContentMD5 = "flyteContentMD5" + // IsCursorEnd checks whether the cursor indicates there are no // more items or not. func IsCursorEnd(cursor string) bool { @@ -77,6 +93,19 @@ type Location interface { ItemByURL(url *url.URL) (Item, error) } +type PresignRequestParams struct { + ExpiresIn time.Duration + ContentMD5 string + ExtraParams map[string]interface{} + HttpMethod HttpMethod + AddContentMD5Metadata bool +} + +type PresignResponse struct { + Url string + RequiredRequestHeaders map[string]string +} + // Container represents a container. type Container interface { // ID gets a unique string describing this Container. @@ -88,7 +117,7 @@ type Container interface { // Items gets a page of items with the specified // prefix for this Container. // The specified cursor is a pointer to the start of - // the items to get. It it obtained from a previous + // the items to get. It should be obtained from a previous // call to this method, or should be CursorStart for the // first page. // count is the number of items to return per page. @@ -100,6 +129,8 @@ type Container interface { // Put creates a new Item with the specified name, and contents // read from the reader. Put(name string, r io.Reader, size int64, metadata map[string]interface{}) (Item, error) + // PreSignRequest generates a pre-signed url for the given id (key after bucket/container) and a given clientMethod. + PreSignRequest(ctx context.Context, clientMethod ClientMethod, id string, params PresignRequestParams) (response PresignResponse, err error) } // Item represents an item inside a Container. diff --git a/stow_test.go b/stow_test.go index 5f382f43..bebb1faa 100644 --- a/stow_test.go +++ b/stow_test.go @@ -6,7 +6,7 @@ import ( "testing" "github.com/cheekybits/is" - "github.com/graymeta/stow" + "github.com/flyteorg/stow" ) func TestKindByURL(t *testing.T) { diff --git a/swift/config.go b/swift/config.go index 431a64e4..30294507 100644 --- a/swift/config.go +++ b/swift/config.go @@ -5,7 +5,7 @@ import ( "net/http" "net/url" - "github.com/graymeta/stow" + "github.com/flyteorg/stow" "github.com/ncw/swift" ) diff --git a/swift/container.go b/swift/container.go index ab766d57..241e9d83 100644 --- a/swift/container.go +++ b/swift/container.go @@ -1,12 +1,14 @@ package swift import ( + "context" + "fmt" "io" "strings" "github.com/pkg/errors" - "github.com/graymeta/stow" + "github.com/flyteorg/stow" "github.com/ncw/swift" ) @@ -25,6 +27,11 @@ func (c *container) Name() string { return c.id } +func (c *container) PreSignRequest(_ context.Context, _ stow.ClientMethod, _ string, + _ stow.PresignRequestParams) (response stow.PresignResponse, err error) { + return stow.PresignResponse{}, fmt.Errorf("unsupported") +} + func (c *container) Item(id string) (stow.Item, error) { return c.getItem(id) } diff --git a/swift/item.go b/swift/item.go index c5f1896a..e07c5b10 100644 --- a/swift/item.go +++ b/swift/item.go @@ -7,7 +7,7 @@ import ( "sync" "time" - "github.com/graymeta/stow" + "github.com/flyteorg/stow" "github.com/ncw/swift" ) diff --git a/swift/location.go b/swift/location.go index 84a37459..0faf391d 100644 --- a/swift/location.go +++ b/swift/location.go @@ -5,7 +5,7 @@ import ( "net/url" "strings" - "github.com/graymeta/stow" + "github.com/flyteorg/stow" "github.com/ncw/swift" ) diff --git a/swift/stow_test.go b/swift/stow_test.go index aab9a99a..cb157453 100644 --- a/swift/stow_test.go +++ b/swift/stow_test.go @@ -6,8 +6,8 @@ import ( "testing" "github.com/cheekybits/is" - "github.com/graymeta/stow" - "github.com/graymeta/stow/test" + "github.com/flyteorg/stow" + "github.com/flyteorg/stow/test" ) func TestStow(t *testing.T) { diff --git a/test/fixtures/azuresa/.terraform-version b/test/fixtures/azuresa/.terraform-version new file mode 100644 index 00000000..e516bb9d --- /dev/null +++ b/test/fixtures/azuresa/.terraform-version @@ -0,0 +1 @@ +1.4.5 diff --git a/test/fixtures/azuresa/.terraform.lock.hcl b/test/fixtures/azuresa/.terraform.lock.hcl new file mode 100644 index 00000000..3d8084e2 --- /dev/null +++ b/test/fixtures/azuresa/.terraform.lock.hcl @@ -0,0 +1,41 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/azurerm" { + version = "3.72.0" + constraints = ">= 3.42.0, < 4.0.0" + hashes = [ + "h1:QMrnPgehMq1vQEEHcuRs30if7lft3I0/CLY88YHtQBM=", + "zh:0750326f82dc0765cd9dc0e142b4c325be7918beeacc0b887510274f15d76311", + "zh:10ba452905de646181bfbbb9555c7b8fb96138ddc4bb42227521c402c3b12213", + "zh:25c8198603cffa0920e6ae39a87a5bb4af75bbe1fba36156e8077ae50261a7ca", + "zh:5c294fff683c2fc292f502da43f41bf4b68a20bf60a2e92723768a0ce7fe2c7a", + "zh:84449a0e7d5bd4a3fda9a4c9ad287c4c7ebcc5ede406d3ab7593f073d40abdfc", + "zh:89f3fc2b3e84e45776fce547ed9fa3dbdba65fe243094fe308c5cef273b4d980", + "zh:a8cdfc816fbf14a230c3bb4ccdf70d19069186de78008e49dc9dfaa8aaf0208e", + "zh:d6e1d86f2d6d0e09d3961f10f9e26e24a25d39e98ecaf93d5cb089ddb4fea5b6", + "zh:e74f0e6c3904da8ff10bdb90be1fd8b20f1d3f14d62d24ffb76b61c623ee0e3c", + "zh:e9fb32ef48450b8109e30e47280053ca7d5307190bf6f516e1bebaf556dc8d81", + "zh:ee8c9bb7aa318a3d8a313eb032b3fc1a332114fe112723ba7b0c8cb4a5947476", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.5.1" + hashes = [ + "h1:sZ7MTSD4FLekNN2wSNFGpM+5slfvpm5A/NLVZiB7CO0=", + "zh:04e3fbd610cb52c1017d282531364b9c53ef72b6bc533acb2a90671957324a64", + "zh:119197103301ebaf7efb91df8f0b6e0dd31e6ff943d231af35ee1831c599188d", + "zh:4d2b219d09abf3b1bb4df93d399ed156cadd61f44ad3baf5cf2954df2fba0831", + "zh:6130bdde527587bbe2dcaa7150363e96dbc5250ea20154176d82bc69df5d4ce3", + "zh:6cc326cd4000f724d3086ee05587e7710f032f94fc9af35e96a386a1c6f2214f", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:b6d88e1d28cf2dfa24e9fdcc3efc77adcdc1c3c3b5c7ce503a423efbdd6de57b", + "zh:ba74c592622ecbcef9dc2a4d81ed321c4e44cddf7da799faa324da9bf52a22b2", + "zh:c7c5cde98fe4ef1143bd1b3ec5dc04baf0d4cc3ca2c5c7d40d17c0e9b2076865", + "zh:dac4bad52c940cd0dfc27893507c1e92393846b024c5a9db159a93c534a3da03", + "zh:de8febe2a2acd9ac454b844a4106ed295ae9520ef54dc8ed2faf29f12716b602", + "zh:eab0d0495e7e711cca367f7d4df6e322e6c562fc52151ec931176115b83ed014", + ] +} diff --git a/test/fixtures/azuresa/.terragrunt-version b/test/fixtures/azuresa/.terragrunt-version new file mode 100644 index 00000000..9b1bb851 --- /dev/null +++ b/test/fixtures/azuresa/.terragrunt-version @@ -0,0 +1 @@ +0.37.1 diff --git a/test/fixtures/azuresa/README.md b/test/fixtures/azuresa/README.md new file mode 100644 index 00000000..830ba99e --- /dev/null +++ b/test/fixtures/azuresa/README.md @@ -0,0 +1,49 @@ +# Fixture: azuresa + +--- + +This fixture sets up an Azure Storage Account which can be used to test the azure implementation. + +## Pre-reqs + +#### Workstation +- `tgenv` (or install the version of terragrunt in `.terragrunt-version`) +- `tfenv` (or, install the version of terraform in `.terraform-version`) +- `jq` (needed for instructions below) + +#### Azure Account + +You will need an identity & resource group to use this module. The identity must have sufficient access +to the resource group to both create resources and assign roles. This is commonly done with either the +`Owner` role or a combination of the `Contributor` role and the `User Access Administrator` role. + +## Using it + + +#### Set up + +```shell +export AZ_RG_NAME="YOUR_RG_NAME" +terragrunt apply +``` + +#### Capturing the fixture values for testing + +```shell +export AZUREACCOUNT="$(terragrunt output -json | jq -r '.sa_name.value')" +export AZUREKEY="$(terragrunt output -json | jq -r '.primary_sa_key.value')" +``` + +#### Running the Azure tests + +```shell +go test -v ../../../... -run TestStowWithSharedKeyAuth -count=1 +go test -v ../../../... -run TestStowWithDefaultADAuth -count=1 +AZUREBIGFILETESTSIZEMB=10 go test -v ../../../... -run TestBigFileUpload -count=1 +``` + +#### Tearing down + +```shell +terragrunt destroy +``` \ No newline at end of file diff --git a/test/fixtures/azuresa/main.tf b/test/fixtures/azuresa/main.tf new file mode 100644 index 00000000..6a846100 --- /dev/null +++ b/test/fixtures/azuresa/main.tf @@ -0,0 +1,58 @@ +terraform { + required_providers { + azurerm = { + version = ">=3.42.0, < 4.0.0" + } + } +} + +provider "azurerm" { + # Required for when we disable shared access keys. + storage_use_azuread = true + features {} +} + +resource "random_string" "deploy_token" { + length = 8 + special = false + upper = false + lower = true +} + +data "azurerm_client_config" "current" {} + +data "azurerm_resource_group" "rg" { + name = var.resource_group_name +} + +locals { + deploy_token = random_string.deploy_token.result + location = var.location == null ? data.azurerm_resource_group.rg.location : var.location +} + +resource "azurerm_storage_account" "sa" { + name = local.deploy_token + location = local.location + resource_group_name = data.azurerm_resource_group.rg.name + account_tier = "Standard" + account_replication_type = "LRS" + enable_https_traffic_only = true + min_tls_version = "TLS1_2" + shared_access_key_enabled = true // needed to test both auth styles + tags = var.tags + account_kind = "StorageV2" + public_network_access_enabled = true +} + +locals { + sa_roles_for_test_user = [ + "Contributor", "Storage Blob Data Owner" + ] +} + +resource "azurerm_role_assignment" "role_assignment" { + for_each = { for i, v in local.sa_roles_for_test_user: v => v} + scope = azurerm_storage_account.sa.id + role_definition_name = each.value + principal_id = data.azurerm_client_config.current.object_id +} \ No newline at end of file diff --git a/test/fixtures/azuresa/outputs.tf b/test/fixtures/azuresa/outputs.tf new file mode 100644 index 00000000..13e6b46a --- /dev/null +++ b/test/fixtures/azuresa/outputs.tf @@ -0,0 +1,9 @@ +output "sa_name" { + value = azurerm_storage_account.sa.name +} + +output "primary_sa_key" { + value = azurerm_storage_account.sa.primary_access_key + sensitive = true +} + diff --git a/test/fixtures/azuresa/terragrunt.hcl b/test/fixtures/azuresa/terragrunt.hcl new file mode 100644 index 00000000..7712c3e2 --- /dev/null +++ b/test/fixtures/azuresa/terragrunt.hcl @@ -0,0 +1,7 @@ +terraform { + source = ".//." +} + +inputs = { + resource_group_name = get_env("AZ_RG_NAME") +} \ No newline at end of file diff --git a/test/fixtures/azuresa/vars.tf b/test/fixtures/azuresa/vars.tf new file mode 100644 index 00000000..87177b17 --- /dev/null +++ b/test/fixtures/azuresa/vars.tf @@ -0,0 +1,16 @@ +variable "resource_group_name" { + type = string + description = "The associated resource group id for module resources" +} + +variable "location" { + type = string + description = "The location for the resources (defaults to the RG resource group)" + default = null +} + +variable "tags" { + type = map(string) + description = "Common tags to be added to all resources which can take tags" + default = {} +} \ No newline at end of file diff --git a/test/test.go b/test/test.go index ae3e0f4d..3e0eea9e 100644 --- a/test/test.go +++ b/test/test.go @@ -2,6 +2,7 @@ package test import ( "bytes" + "context" "errors" "fmt" "io" @@ -17,8 +18,10 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" + "github.com/cheekybits/is" - "github.com/graymeta/stow" + "github.com/flyteorg/stow" ) // All runs a generic suite of tests for Stow storage @@ -28,20 +31,13 @@ import ( // via stow.Register. // Locations should be empty. func All(t *testing.T, kind string, config stow.Config) { - is := is.New(t) + is, location := initTest(t, kind, config) isWindows := false if runtime.GOOS == "windows" { isWindows = true } - err := stow.Validate(kind, config) - is.NoErr(err) - - location, err := stow.Dial(kind, config) - is.NoErr(err) - is.OK(location) - // testing for file descriptors won't work on Windows var startFDs int var fdsStart []byte @@ -289,6 +285,115 @@ func All(t *testing.T, kind string, config stow.Config) { is.Equal(found, 3) // should find three items } +type PresignedRequestPreparer func(method stow.ClientMethod, r *http.Request) error + +func ContainerPreSignRequest( + t *testing.T, kind string, config stow.Config, requestPreparer PresignedRequestPreparer, +) { + is, location := initTest(t, kind, config) + + testContainer, err := location.CreateContainer("stowtest" + randName(10)) + is.NoErr(err) + + defer func() { + location.RemoveContainer(testContainer.ID()) + }() + + u, err := testContainer.PreSignRequest( + context.Background(), + stow.ClientMethodPut, + "presigned-put.txt", + stow.PresignRequestParams{ + ExpiresIn: time.Minute * 5, + }, + ) + + is.NoErr(err) + assert.NoError(t, err) + client := &http.Client{} + content := []byte("stowtest") + req, err := http.NewRequest("PUT", u.Url, bytes.NewReader(content)) + is.NoErr(err) + req.ContentLength = int64(len(content)) + req.Header.Set("Content-Type", "text/plain") + err = requestPreparer(stow.ClientMethodPut, req) + is.NoErr(err) + + resp, err := client.Do(req) + is.NoErr(err) + if resp.StatusCode != 201 && resp.StatusCode != 200 { + io.Copy(os.Stdout, resp.Body) + } + is.Equal(201, resp.StatusCode) + + u, err = testContainer.PreSignRequest( + context.TODO(), + stow.ClientMethodGet, + "presigned-put.txt", + stow.PresignRequestParams{ + ExpiresIn: time.Minute * 5, + }, + ) + + is.NoErr(err) + req, err = http.NewRequest("GET", u.Url, nil) + is.NoErr(err) + err = requestPreparer(stow.ClientMethodGet, req) + is.NoErr(err) + + resp, err = client.Do(req) + is.NoErr(err) + is.Equal(200, resp.StatusCode) + respBytes, err := io.ReadAll(resp.Body) + is.NoErr(err) + is.Equal(content, respBytes) +} + +func BigFileUpload(t *testing.T, kind string, config stow.Config, fileSize int64) { + is, location := initTest(t, kind, config) + + testContainer, err := location.CreateContainer("uploadtest" + randName(10)) + is.NoErr(err) + + defer func() { + location.RemoveContainer(testContainer.ID()) + }() + + f, err := os.CreateTemp(".", "temp-file") + is.NoErr(err) + defer func() { + os.Remove(f.Name()) + }() + f.Truncate(fileSize) + item, err := testContainer.Put("test-upload-file", f, fileSize, map[string]interface{}{}) + is.NoErr(err) + itemSize, err := item.Size() + is.NoErr(err) + is.Equal(fileSize, itemSize) +} + +func ExistingContainerDoesNotProduceAnError(t *testing.T, kind string, config stow.Config) { + is, location := initTest(t, kind, config) + testContainer, err := location.CreateContainer("stowtest-" + randName(10)) + is.NoErr(err) + defer func() { location.RemoveContainer(testContainer.Name()) }() + dupAttempt, err := location.CreateContainer(testContainer.Name()) + is.NoErr(err) + is.Equal(testContainer.Name(), dupAttempt.Name()) +} + +func initTest(t *testing.T, kind string, config stow.Config) (is.I, stow.Location) { + is := is.New(t) + + err := stow.Validate(kind, config) + is.NoErr(err) + + location, err := stow.Dial(kind, config) + is.NoErr(err) + is.OK(location) + return is, location +} + func totalNetFDs(t *testing.T) (int, []byte) { lsof, err := exec.Command("lsof", "-b", "-n", "-p", strconv.Itoa(os.Getpid())).Output() if err != nil { @@ -408,10 +513,9 @@ func checkMetadata(t *testing.T, is is.I, item stow.Item, md map[string]interfac is.Failf("error retrieving item metadata: %v", err) } - t.Logf("Item metadata: %v", itemMD) - t.Logf("Expected item metadata: %v", md) - if !reflect.DeepEqual(itemMD, md) { + t.Logf("Item metadata: %v", itemMD) + t.Logf("Expected item metadata: %v", md) return errors.New("metadata mismatch") } return nil