diff --git a/.gitignore b/.gitignore index 673c6cb3b..c353389f9 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,12 @@ bin .vscode/ .idea/ +# VIM +.*.swp + +# Emacs +*~ + # Common backup files *.bak @@ -16,3 +22,5 @@ plugins/grafana-custom-plugins/grafana-sankey-plugin/coverage/ plugins/grafana-custom-plugins/grafana-chord-plugin/node_modules/ plugins/grafana-custom-plugins/grafana-chord-plugin/dist/ plugins/grafana-custom-plugins/grafana-chord-plugin/coverage/ + +.golangci-bin diff --git a/snowflake/.gitignore b/snowflake/.gitignore new file mode 100644 index 000000000..99a84b3a2 --- /dev/null +++ b/snowflake/.gitignore @@ -0,0 +1 @@ +creds diff --git a/snowflake/Makefile b/snowflake/Makefile new file mode 100644 index 000000000..01c001775 --- /dev/null +++ b/snowflake/Makefile @@ -0,0 +1,16 @@ +GO ?= go +BINDIR := $(CURDIR)/bin + +all: bin + +.PHONY: bin +bin: + $(GO) build -o $(BINDIR)/theia-sf antrea.io/theia/snowflake + +.PHONY: test +test: + $(GO) test -v ./... + +.PHONY: clean +clean: + rm -rf bin diff --git a/snowflake/README.md b/snowflake/README.md new file mode 100644 index 000000000..f3cb46fc6 --- /dev/null +++ b/snowflake/README.md @@ -0,0 +1,119 @@ +# Theia with Snowflake + +We are introducing the ability to use Snowflake as the storage and computing +platform for Theia. When using Snowflake, it is no longer necessary to run +ClickHouse DB (flow records are stored in a Snowflake database) or Spark (flow +processing is done by Snowflake virtual warehouses). Using Snowflake for Theia +means that you have to bring your own AWS and Snowflake accounts (you will be +charged for resource usage) and some features available with "standard" Theia +are not available yet with Snowflake. + +## Getting started + +### Prerequisites + +Theia with Snowflake requires Antrea >= v1.9.0 and Theia >= v0.3.0. + +### Install the theia-sf CLI + +Because it is not yet distributed as a release asset, you will need to build the +CLI yourself, which requires Git, Golang and Make. + +```bash +git clone git@github.com:antrea-io/theia.git +cd theia/snowflake +make +``` + +### Configure AWS credentials + +Follow the steps in the [AWS +documentation](https://aws.github.io/aws-sdk-go-v2/docs/configuring-sdk/#specifying-credentials) +to specify credentials that can be used by the AWS Go SDK. Either configure the +~/.aws/credentials file or set the required environment variables. + +You can also export the `AWS_REGION` environment variable, set to your preferred +region. + +### Configure Snowflake credentials + +Export the following environment variables: `SNOWFLAKE_ACCOUNT`, +`SNOWFLAKE_USER`, `SNOWFLAKE_PASSWORD`. + +### Create an S3 bucket to store infrastructure state + +You may skip this step if you already have a bucket that you want to use. + +```bash +./bin/theia-sf create-bucket +``` + +Retrieve the bucket name output by the command. + +### Create a KMS key to encrypt infrastructure state + +You may skip this step if you already have a KMS key that you want to use. If +you choose *not* to use a KMS key, Snowflake credentials will be stored in your +S3 bucket in clear text. + +```bash +./bin/theia-sf create-kms-key +``` + +Retrieve the key ID output by the command. + +### Provision all cloud resources + +```bash +./bin/theia-sf onboard --bucket-name --key-id +``` + +The command output will include a table like this one, with important information: + +```text ++----------------------------+------------------------------------------------------------------+ +| Region | us-west-2 | +| Bucket Name | antrea-flows-93e9ojn80fgn5vwt | +| Bucket Flows Folder | flows | +| Snowflake Database Name | ANTREA_93E9OJN80FGN5VWT | +| Snowflake Schema Name | THEIA | +| Snowflake Flows Table Name | FLOWS | +| SNS Topic ARN | arn:aws:sns:us-west-2:867393676014:antrea-flows-93e9ojn80fgn5vwt | +| SQS Queue ARN | arn:aws:sqs:us-west-2:867393676014:antrea-flows-93e9ojn80fgn5vwt | ++----------------------------+------------------------------------------------------------------+ +``` + +### Configure the Flow Aggregator in your cluster(s) + +```bash +helm repo add antrea https://charts.antrea.io +helm repo update +helm install antrea antrea/antrea -n kube-system --set featureGates.FlowExporter=true +helm install flow-aggregator antrea/flow-aggregator \ + --set s3Uploader.enable=true \ + --set s3Uploader.bucketName= \ + --set s3Uploader.bucketPrefix=flows \ + --set s3Uploader.awsCredentials.aws_access_key_id= \ + --set s3Uploader.awsCredentials.aws_secret_access_key= \ + -n flow-aggregator --create-namespace +``` + +## Clean up + +Follow these steps if you want to delete all resources created by +`theia-sf`. Just like for [onboarding](#getting-started), AWS credentials and +Snowflake credentials are required. + +```bash +# always call offboard with the same arguments as onboard! +./bin/theia-sf offboard --bucket-name --key-id +# if you created a KMS key and you want to schedule it for deletion +./bin/theia-sf delete-kms-key --key-id +# if you created an S3 bucket to store infra state and you want to delete it +./bin/theia-sf delete-bucket --name +``` + +## Running applications + +We are in the process of adding support for applications to Snowflake-powered +Theia, starting with NetworkPolicy recommendation. diff --git a/snowflake/cmd/createBucket.go b/snowflake/cmd/createBucket.go new file mode 100644 index 000000000..59afc6316 --- /dev/null +++ b/snowflake/cmd/createBucket.go @@ -0,0 +1,108 @@ +// Copyright 2022 Antrea Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "context" + "errors" + "fmt" + "time" + + awsconfig "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/s3" + s3types "github.com/aws/aws-sdk-go-v2/service/s3/types" + "github.com/dustinkirkland/golang-petname" + "github.com/spf13/cobra" + + s3client "antrea.io/theia/snowflake/pkg/aws/client/s3" +) + +// createBucketCmd represents the create-bucket command +var createBucketCmd = &cobra.Command{ + Use: "create-bucket", + Short: "Create an AWS S3 bucket", + Long: `This command creates a new S3 bucket in your AWS account. This is +useful if you don't already have a bucket available to store infrastructure +state (such a bucket is required for the "onboard" command). + +Before calling this command, ensure that AWS credentials are available: +https://aws.github.io/aws-sdk-go-v2/docs/configuring-sdk/#specifying-credentials + +To create a bucket with a specific name: +"theia-sf create-bucket --name this-is-the-name-i-want" + +To create a bucket with a random name in a specific non-default region: +"theia-sf create-bucket --region us-east-2"`, + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + region, _ := cmd.Flags().GetString("region") + bucketName, _ := cmd.Flags().GetString("name") + bucketPrefix, _ := cmd.Flags().GetString("prefix") + if bucketName == "" { + suffix := petname.Generate(4, "-") + bucketName = fmt.Sprintf("%s-%s", bucketPrefix, suffix) + } + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + awsCfg, err := awsconfig.LoadDefaultConfig(ctx, awsconfig.WithRegion(region)) + if err != nil { + return fmt.Errorf("unable to load AWS SDK config: %w", err) + + } + s3Client := s3client.GetClient(awsCfg) + if err := createBucket(ctx, s3Client, bucketName, region); err != nil { + return err + } + fmt.Printf("Bucket name: %s\n", bucketName) + return nil + }, +} + +func createBucket(ctx context.Context, s3Client s3client.Interface, name string, region string) error { + logger := logger.WithValues("bucket", name, "region", region) + logger.Info("Checking if S3 bucket exists") + _, err := s3Client.HeadBucket(ctx, &s3.HeadBucketInput{ + Bucket: &name, + }) + var notFoundError *s3types.NotFound + if err != nil && !errors.As(err, ¬FoundError) { + return fmt.Errorf("error when looking for bucket '%s': %w", name, err) + } + if notFoundError == nil { + logger.Info("S3 bucket already exists") + return nil + } + logger.Info("Creating S3 bucket") + if _, err := s3Client.CreateBucket(ctx, &s3.CreateBucketInput{ + Bucket: &name, + ACL: s3types.BucketCannedACLPrivate, + CreateBucketConfiguration: &s3types.CreateBucketConfiguration{ + LocationConstraint: s3types.BucketLocationConstraint(region), + }, + }); err != nil { + return fmt.Errorf("error when creating bucket '%s': %w", name, err) + } + logger.Info("Created S3 bucket") + return nil +} + +func init() { + rootCmd.AddCommand(createBucketCmd) + + createBucketCmd.Flags().String("region", GetEnv("AWS_REGION", defaultRegion), "region where bucket should be created") + createBucketCmd.Flags().String("name", "", "name of bucket to create") + createBucketCmd.Flags().String("prefix", "antrea", "prefix to use for bucket name (with auto-generated suffix)") + createBucketCmd.MarkFlagsMutuallyExclusive("name", "prefix") +} diff --git a/snowflake/cmd/createKmsKey.go b/snowflake/cmd/createKmsKey.go new file mode 100644 index 000000000..bd893ed39 --- /dev/null +++ b/snowflake/cmd/createKmsKey.go @@ -0,0 +1,80 @@ +// Copyright 2022 Antrea Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "context" + "fmt" + "time" + + awsconfig "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/kms" + "github.com/spf13/cobra" + + kmsclient "antrea.io/theia/snowflake/pkg/aws/client/kms" +) + +// createKmsKeyCmd represents the create-kms-key command +var createKmsKeyCmd = &cobra.Command{ + Use: "create-kms-key", + Short: "Create an AWS KMS key", + Long: `This command creates a new KMS key in your AWS account. This key +can be used to encrypt infrastructure state in the backend (S3 bucket). If you +already have a KMS key that you want to use, you won't need this command. + +Before calling this command, ensure that AWS credentials are available: +https://aws.github.io/aws-sdk-go-v2/docs/configuring-sdk/#specifying-credentials + +To create a KMS key and obtain its ID: +"theia-sf create-kms-key"`, + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + region, _ := cmd.Flags().GetString("region") + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + awsCfg, err := awsconfig.LoadDefaultConfig(ctx, awsconfig.WithRegion(region)) + if err != nil { + return fmt.Errorf("unable to load AWS SDK config: %w", err) + + } + kmsClient := kmsclient.GetClient(awsCfg) + keyID, err := createKey(ctx, kmsClient) + if err != nil { + return err + } + fmt.Printf("Key ID: %s\n", keyID) + return nil + }, +} + +func createKey(ctx context.Context, kmsClient kmsclient.Interface) (string, error) { + logger.Info("Creating key") + description := "This key was created by theia-sf; it is used to encrypt infrastructure state" + output, err := kmsClient.CreateKey(ctx, &kms.CreateKeyInput{ + Description: &description, + // we use default parameters for everything else + }) + if err != nil { + return "", fmt.Errorf("error when creating key: %w", err) + } + logger.Info("Created key") + return *output.KeyMetadata.KeyId, nil +} + +func init() { + rootCmd.AddCommand(createKmsKeyCmd) + + createKmsKeyCmd.Flags().String("region", GetEnv("AWS_REGION", defaultRegion), "region where key should be created") +} diff --git a/snowflake/cmd/defaults.go b/snowflake/cmd/defaults.go new file mode 100644 index 000000000..2bd2d9972 --- /dev/null +++ b/snowflake/cmd/defaults.go @@ -0,0 +1,19 @@ +// Copyright 2022 Antrea Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +const ( + defaultRegion = "us-west-2" +) diff --git a/snowflake/cmd/deleteBucket.go b/snowflake/cmd/deleteBucket.go new file mode 100644 index 000000000..7a4ec2fc9 --- /dev/null +++ b/snowflake/cmd/deleteBucket.go @@ -0,0 +1,125 @@ +// Copyright 2022 Antrea Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "context" + "fmt" + "time" + + awsconfig "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/s3" + s3types "github.com/aws/aws-sdk-go-v2/service/s3/types" + "github.com/spf13/cobra" + + s3client "antrea.io/theia/snowflake/pkg/aws/client/s3" +) + +// deleteBucketCmd represents the delete-bucket command +var deleteBucketCmd = &cobra.Command{ + Use: "delete-bucket", + Short: "Delete an AWS S3 bucket", + Long: `This command deletes an existing S3 bucket in your AWS +account. For example: + +"theia-sf delete-bucket --name " + +If the bucket is not empty, you will need to provide the "--force" flag, which +will cause all objects in the bucket to be deleted.`, + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + region, _ := cmd.Flags().GetString("region") + bucketName, _ := cmd.Flags().GetString("name") + force, _ := cmd.Flags().GetBool("force") + ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) + defer cancel() + awsCfg, err := awsconfig.LoadDefaultConfig(ctx, awsconfig.WithRegion(region)) + if err != nil { + return fmt.Errorf("unable to load AWS SDK config: %w", err) + + } + s3Client := s3client.GetClient(awsCfg) + if force { + if err := deleteS3Objects(ctx, s3Client, bucketName); err != nil { + return err + } + } + return deleteBucket(ctx, s3Client, bucketName) + }, +} + +func deleteBucket(ctx context.Context, s3Client s3client.Interface, name string) error { + logger := logger.WithValues("bucket", name) + logger.Info("Deleting S3 bucket") + if _, err := s3Client.DeleteBucket(ctx, &s3.DeleteBucketInput{ + Bucket: &name, + }); err != nil { + return fmt.Errorf("error when deleting bucket '%s': %w", name, err) + } + logger.Info("Deleted S3 bucket") + return nil +} + +func deleteS3Objects(ctx context.Context, s3Client s3client.Interface, bucketName string) error { + logger := logger.WithValues("bucket", bucketName) + prefix := "" + var nextToken *string + logger.Info("Deleting all objects in S3 bucket") + for { + output, err := s3Client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ + Bucket: &bucketName, + ContinuationToken: nextToken, + Prefix: &prefix, + }) + if err != nil { + return fmt.Errorf("error when listing objects: %w", err) + } + keys := make([]s3types.ObjectIdentifier, 0, len(output.Contents)) + for _, obj := range output.Contents { + keys = append(keys, s3types.ObjectIdentifier{ + Key: obj.Key, + }) + } + if len(keys) == 0 { + break + } + logger.Info("Deleting objects", "count", len(keys)) + _, err = s3Client.DeleteObjects(ctx, &s3.DeleteObjectsInput{ + Bucket: &bucketName, + Delete: &s3types.Delete{ + Objects: keys, + }, + }) + if err != nil { + return fmt.Errorf("error when deleting objects: %w", err) + } + + nextToken = output.ContinuationToken + if nextToken == nil { + break + } + } + logger.Info("Deleted all objects in S3 bucket") + return nil +} + +func init() { + rootCmd.AddCommand(deleteBucketCmd) + + deleteBucketCmd.Flags().String("region", GetEnv("AWS_REGION", defaultRegion), "region to use for deleting the bucket") + deleteBucketCmd.Flags().String("name", "", "name of bucket to delete") + deleteBucketCmd.MarkFlagRequired("name") + deleteBucketCmd.Flags().Bool("force", false, "delete all objects if bucket is not empty") +} diff --git a/snowflake/cmd/deleteKmsKey.go b/snowflake/cmd/deleteKmsKey.go new file mode 100644 index 000000000..00551c4c7 --- /dev/null +++ b/snowflake/cmd/deleteKmsKey.go @@ -0,0 +1,79 @@ +// Copyright 2022 Antrea Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "context" + "fmt" + "time" + + awsconfig "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/kms" + "github.com/spf13/cobra" + + kmsclient "antrea.io/theia/snowflake/pkg/aws/client/kms" +) + +// deleteKmsKeyCmd represents the delete-kms-Key command +var deleteKmsKeyCmd = &cobra.Command{ + Use: "delete-kms-key", + Short: "Delete an AWS KMS key", + Long: `This command deletes an existing KMS key in your AWS account. For +example: + +"theia-sf delete-kms-key --key-id " + +Note that the key will not be deleted immediately, which is not supported by the +AWS API. Instead, the deletion will be scheduled after a 30-day waiting period. + +Before deleting the key, ensure that you have deleted (with "theia-sf offboard") +all the infrastructure which depends on this key.`, + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + region, _ := cmd.Flags().GetString("region") + keyID, _ := cmd.Flags().GetString("key-id") + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + awsCfg, err := awsconfig.LoadDefaultConfig(ctx, awsconfig.WithRegion(region)) + if err != nil { + return fmt.Errorf("unable to load AWS SDK config: %w", err) + + } + kmsClient := kmsclient.GetClient(awsCfg) + return scheduleKeyDeletion(ctx, kmsClient, keyID) + }, +} + +func scheduleKeyDeletion(ctx context.Context, kmsClient kmsclient.Interface, keyID string) error { + waitingPeriodDays := int32(30) + logger.Info("Scheduling key deletion", "waiting days", waitingPeriodDays) + output, err := kmsClient.ScheduleKeyDeletion(ctx, &kms.ScheduleKeyDeletionInput{ + KeyId: &keyID, + PendingWindowInDays: &waitingPeriodDays, + }) + if err != nil { + return fmt.Errorf("error when scheduling key deletion: %w", err) + } + logger.Info("Scheduled key deletion", "deletion date", output.DeletionDate.String()) + return nil +} + +func init() { + rootCmd.AddCommand(deleteKmsKeyCmd) + + deleteKmsKeyCmd.Flags().String("region", GetEnv("AWS_REGION", defaultRegion), "region to use for deleting the key") + deleteKmsKeyCmd.Flags().String("key-id", "", "KMS key ID") + deleteKmsKeyCmd.MarkFlagRequired("key-id") +} diff --git a/snowflake/cmd/offboard.go b/snowflake/cmd/offboard.go new file mode 100644 index 000000000..03323475b --- /dev/null +++ b/snowflake/cmd/offboard.go @@ -0,0 +1,91 @@ +// Copyright 2022 Antrea Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "context" + "fmt" + "time" + + "github.com/spf13/cobra" + + "antrea.io/theia/snowflake/pkg/infra" +) + +// offboardCmd represents the offboard command +var offboardCmd = &cobra.Command{ + Use: "offboard", + Short: "Delete cloud resources in Snowflake and AWS", + Long: `Delete all the resources created by the "onboard" command. Note +that all resources will be deleted, including the Snowflake database storing the +Antrea flows. If needed, you can copy the database contents yourself before +running this command. This command has the same prerequisites as the "onboard" +command when it comes to AWS and Snowflake credentials. It is important to call +this command with the same parameters as when "onboard" was called: +"--bucket-name" and, if applicable, "region", "--bucket-prefix", +"--bucket-region", and "--stack-name". + +For example: +"theia-sf offboard --bucket-name "`, + RunE: func(cmd *cobra.Command, args []string) error { + region, _ := cmd.Flags().GetString("region") + stackName, _ := cmd.Flags().GetString("stack-name") + bucketName, _ := cmd.Flags().GetString("bucket-name") + bucketPrefix, _ := cmd.Flags().GetString("bucket-prefix") + bucketRegion, _ := cmd.Flags().GetString("bucket-region") + keyID, _ := cmd.Flags().GetString("key-id") + keyRegion, _ := cmd.Flags().GetString("key-region") + workdir, _ := cmd.Flags().GetString("workdir") + verbose := verbosity >= 2 + ctx, cancel := context.WithTimeout(context.Background(), 300*time.Second) + defer cancel() + if bucketRegion == "" { + var err error + bucketRegion, err = GetBucketRegion(ctx, bucketName, region) + if err != nil { + return err + } + } + stateBackendURL := infra.S3StateBackendURL(bucketName, bucketPrefix, bucketRegion) + var secretsProviderURL string + if keyID != "" { + if keyRegion == "" { + keyRegion = region + } + secretsProviderURL = infra.KmsSecretsProviderURL(keyID, keyRegion) + } + mgr := infra.NewManager(logger, stackName, stateBackendURL, secretsProviderURL, region, "", workdir, verbose) + if err := mgr.Offboard(ctx); err != nil { + return err + } + fmt.Println("SUCCESS!") + fmt.Println("To re-create infrastructure, run 'theia-sf onboard' again") + return nil + }, +} + +func init() { + rootCmd.AddCommand(offboardCmd) + + offboardCmd.Flags().String("region", GetEnv("AWS_REGION", defaultRegion), "region where bucket should be created") + offboardCmd.Flags().String("stack-name", "default", "Name of the infrastructure stack: useful to deploy multiple instances in the same Snowflake account or with the same bucket (e.g., one for dev and one for prod)") + offboardCmd.Flags().String("bucket-name", "", "bucket to store infra state") + offboardCmd.MarkFlagRequired("bucket-name") + offboardCmd.Flags().String("bucket-prefix", "antrea-flows-infra", "prefix to use to store infra state") + offboardCmd.Flags().String("bucket-region", "", "region where infra bucket is defined; if omitted, we will try to get the region from AWS") + offboardCmd.Flags().String("key-id", GetEnv("THEIA_SF_KMS_KEY_ID", ""), "Kms key ID") + offboardCmd.Flags().String("key-region", "", "Kms key region") + offboardCmd.Flags().String("workdir", "", "use provided local workdir (by default a temporary one will be created") +} diff --git a/snowflake/cmd/onboard.go b/snowflake/cmd/onboard.go new file mode 100644 index 000000000..0242ec51e --- /dev/null +++ b/snowflake/cmd/onboard.go @@ -0,0 +1,128 @@ +// Copyright 2022 Antrea Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "context" + "fmt" + "os" + "time" + + "github.com/olekukonko/tablewriter" + "github.com/spf13/cobra" + + "antrea.io/theia/snowflake/pkg/infra" +) + +// onboardCmd represents the onboard command +var onboardCmd = &cobra.Command{ + Use: "onboard", + Short: "Create or update cloud resources in Snowflake and AWS", + Long: `Create or update cloud resources in Snowflake and AWS. You need +to bring your own Snowflake and AWS accounts. + +1. ensure that AWS credentials are available: + https://aws.github.io/aws-sdk-go-v2/docs/configuring-sdk/#specifying-credentials +2. export your Snowflake credentials as environment variables: + SNOWFLAKE_ACCOUNT, SNOWFLAKE_USER, SNOWFLAKE_PASSWORD +3. choose an AWS S3 bucket which will be used to store infrastructure state; + you can create one with "theia-sf create-bucket" if needed +4. choose an AWS KMS key which will be used to encrypt infrastructure state; + you can create one with "theia-sf create-kms-key" if needed +5. provision infrastructure with: + "theia-sf onboard --bucket-name --key-id " + +You can run the "onboard" command multiple times as it is idempotent. When +upgrading this application to a more recent version, it is safe to run "onboard" +again with the same parameters. + +You can delete all the created cloud resources at any time with: +"theia-sf offboard --bucket-name " + +The "onboard" command requires a Snowflake warehouse to run database +migration. By default, it will create a temporary one. You can also bring your +own by using the "--warehouse-name" parameter.`, + RunE: func(cmd *cobra.Command, args []string) error { + region, _ := cmd.Flags().GetString("region") + stackName, _ := cmd.Flags().GetString("stack-name") + bucketName, _ := cmd.Flags().GetString("bucket-name") + bucketPrefix, _ := cmd.Flags().GetString("bucket-prefix") + bucketRegion, _ := cmd.Flags().GetString("bucket-region") + keyID, _ := cmd.Flags().GetString("key-id") + keyRegion, _ := cmd.Flags().GetString("key-region") + warehouseName, _ := cmd.Flags().GetString("warehouse-name") + workdir, _ := cmd.Flags().GetString("workdir") + verbose := verbosity >= 2 + ctx, cancel := context.WithTimeout(context.Background(), 300*time.Second) + defer cancel() + if bucketRegion == "" { + var err error + bucketRegion, err = GetBucketRegion(ctx, bucketName, region) + if err != nil { + return err + } + } + stateBackendURL := infra.S3StateBackendURL(bucketName, bucketPrefix, bucketRegion) + var secretsProviderURL string + if keyID != "" { + if keyRegion == "" { + keyRegion = region + } + secretsProviderURL = infra.KmsSecretsProviderURL(keyID, keyRegion) + } + mgr := infra.NewManager(logger, stackName, stateBackendURL, secretsProviderURL, region, warehouseName, workdir, verbose) + result, err := mgr.Onboard(ctx) + if err != nil { + return err + } + showResults(result) + fmt.Println("SUCCESS!") + fmt.Println("To update infrastructure, run 'theia-sf onboard' again") + fmt.Println("To destroy all infrastructure, run 'theia-sf offboard'") + return nil + }, +} + +func showResults(result *infra.Result) { + table := tablewriter.NewWriter(os.Stdout) + data := [][]string{ + []string{"Region", result.Region}, + []string{"Bucket Name", result.BucketName}, + []string{"Bucket Flows Folder", result.BucketFlowsFolder}, + []string{"Snowflake Database Name", result.DatabaseName}, + []string{"Snowflake Schema Name", result.SchemaName}, + []string{"Snowflake Flows Table Name", result.FlowsTableName}, + []string{"SNS Topic ARN", result.SNSTopicARN}, + []string{"SQS Queue ARN", result.SQSQueueARN}, + } + table.SetAlignment(tablewriter.ALIGN_LEFT) + table.AppendBulk(data) + table.Render() +} + +func init() { + rootCmd.AddCommand(onboardCmd) + + onboardCmd.Flags().String("region", GetEnv("AWS_REGION", defaultRegion), "region where AWS resources will be provisioned") + onboardCmd.Flags().String("stack-name", "default", "name of the infrastructure stack: useful to deploy multiple instances in the same Snowflake account or with the same bucket (e.g., one for dev and one for prod)") + onboardCmd.Flags().String("bucket-name", "", "bucket to store infra state") + onboardCmd.MarkFlagRequired("bucket-name") + onboardCmd.Flags().String("bucket-prefix", "antrea-flows-infra", "prefix to use to store infra state") + onboardCmd.Flags().String("bucket-region", "", "region where infra bucket is defined; if omitted, we will try to get the region from AWS") + onboardCmd.Flags().String("key-id", GetEnv("THEIA_SF_KMS_KEY_ID", ""), "Kms key ID") + onboardCmd.Flags().String("key-region", "", "Kms key region") + onboardCmd.Flags().String("workdir", "", "use provided local workdir (by default a temporary one will be created") + onboardCmd.Flags().String("warehouse-name", "", "Snowflake Virtual Warehouse to use for onboarding queries, by default we will use a temporary one") +} diff --git a/snowflake/cmd/receiveSqsMessage.go b/snowflake/cmd/receiveSqsMessage.go new file mode 100644 index 000000000..7350142f1 --- /dev/null +++ b/snowflake/cmd/receiveSqsMessage.go @@ -0,0 +1,121 @@ +// Copyright 2022 Antrea Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "context" + "fmt" + "time" + + awsarn "github.com/aws/aws-sdk-go-v2/aws/arn" + awsconfig "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/sqs" + "github.com/spf13/cobra" + + sqsclient "antrea.io/theia/snowflake/pkg/aws/client/sqs" +) + +// receiveSqsMessageCmd represents the receive-sqs-message command +var receiveSqsMessageCmd = &cobra.Command{ + Use: "receive-sqs-message", + Short: "Receive a message from an AWS SQS queue", + Long: `This command can be used to receive a message from an SQS queue +in your AWS account. Snowflake data ingestion errors are sent to an SQS queue, +whose ARN is displayed when running the "onboard" command. While ingestion +errors are not expected when using the Antrea Flow Aggregator to export flows, +this command can prove useful if flow records don't get ingested into your +Snowflake account as expected. + +To receive a message without deleting it (message will remain in the queue and +become available to consumers again after a short time interval): +"theia-sf receive-sqs-message --queue-arn " + +To receive a message and delete it from the queue: +"theia-sf receive-sqs-message --queue-arn --delete" + +Note that this command will not block: if no message is available in the queue, +it will return immediately.`, + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + region, _ := cmd.Flags().GetString("region") + sqsQueueARN, _ := cmd.Flags().GetString("queue-arn") + delete, _ := cmd.Flags().GetBool("delete") + arn, err := awsarn.Parse(sqsQueueARN) + if err != nil { + return fmt.Errorf("invalid ARN '%s': %w", sqsQueueARN, err) + } + if region != "" && arn.Region != region { + return fmt.Errorf("region conflict between --region flag and ARN region") + } + region = arn.Region + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + awsCfg, err := awsconfig.LoadDefaultConfig(ctx, awsconfig.WithRegion(region)) + if err != nil { + return fmt.Errorf("unable to load AWS SDK config: %w", err) + + } + sqsClient := sqsclient.GetClient(awsCfg) + return receiveSQSMessage(ctx, sqsClient, arn.Resource, delete) + }, +} + +func receiveSQSMessage(ctx context.Context, sqsClient sqsclient.Interface, queueName string, delete bool) error { + queueURL, err := func() (string, error) { + output, err := sqsClient.GetQueueUrl(ctx, &sqs.GetQueueUrlInput{ + QueueName: &queueName, + }) + if err != nil { + return "", err + } + return *output.QueueUrl, nil + }() + if err != nil { + return fmt.Errorf("error when retrieving SQS queue URL: %v", err) + } + output, err := sqsClient.ReceiveMessage(ctx, &sqs.ReceiveMessageInput{ + QueueUrl: &queueURL, + MaxNumberOfMessages: int32(1), + WaitTimeSeconds: int32(0), + }) + if err != nil { + return fmt.Errorf("error when receiving message from SQS queue: %v", err) + } + if len(output.Messages) == 0 { + return nil + } + message := output.Messages[0] + fmt.Println(*message.Body) + if !delete { + return nil + } + _, err = sqsClient.DeleteMessage(ctx, &sqs.DeleteMessageInput{ + QueueUrl: &queueURL, + ReceiptHandle: message.ReceiptHandle, + }) + if err != nil { + return fmt.Errorf("error when deleting message from SQS queue: %v", err) + } + return nil +} + +func init() { + rootCmd.AddCommand(receiveSqsMessageCmd) + + receiveSqsMessageCmd.Flags().String("region", GetEnv("AWS_REGION", defaultRegion), "region of the SQS queue") + receiveSqsMessageCmd.Flags().String("queue-arn", "", "ARN of the SQS queue") + receiveSqsMessageCmd.MarkFlagRequired("queue-arn") + receiveSqsMessageCmd.Flags().Bool("delete", false, "delete received message from SQS queue") +} diff --git a/snowflake/cmd/root.go b/snowflake/cmd/root.go new file mode 100644 index 000000000..bf8292738 --- /dev/null +++ b/snowflake/cmd/root.go @@ -0,0 +1,83 @@ +// Copyright 2022 Antrea Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "fmt" + "math/rand" + "os" + "time" + + "github.com/go-logr/logr" + "github.com/go-logr/zapr" + "github.com/spf13/cobra" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" +) + +var verbosity int + +var logger logr.Logger + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "theia-sf", + Short: "Manage infrastructure to use Theia with Snowflake backend", + Long: `Theia can use Snowflake as the data store for flows exported by +the Flow Aggregator in each cluster running Antrea. You need to bring your own +Snowflake account and your own AWS account. This CLI application takes care of +configuring cloud resources in Snowflake and AWS to enable Theia. To get +started: + +1. ensure that AWS credentials are available: + https://aws.github.io/aws-sdk-go-v2/docs/configuring-sdk/#specifying-credentials +2. export your Snowflake credentials as environment variables: + SNOWFLAKE_ACCOUNT, SNOWFLAKE_USER, SNOWFLAKE_PASSWORD +3. choose an AWS S3 bucket which will be used to store infrastructure state; + you can create one with "theia-sf create-bucket" if needed +4. choose an AWS KMS key which will be used to encrypt infrastructure state; + you can create one with "theia-sf create-kms-key" if needed +5. provision infrastructure with: + "theia-sf onboard --bucket-name --key-id "`, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + if verbosity < 0 || verbosity >= 128 { + return fmt.Errorf("invalid verbosity level %d: it should be >= 0 and < 128", verbosity) + } + zc := zap.NewProductionConfig() + zc.Level = zap.NewAtomicLevelAt(zapcore.Level(-1 * verbosity)) + zc.DisableStacktrace = true + zapLog, err := zc.Build() + if err != nil { + return fmt.Errorf("cannot initialize Zap logger: %w", err) + panic("Cannot initialize Zap logger") + } + logger = zapr.NewLogger(zapLog) + return nil + }, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + if err := rootCmd.Execute(); err != nil { + os.Exit(1) + } +} + +func init() { + rand.Seed(time.Now().UnixNano()) + + rootCmd.PersistentFlags().IntVarP(&verbosity, "verbosity", "v", 0, "log verbosity") +} diff --git a/snowflake/cmd/utils.go b/snowflake/cmd/utils.go new file mode 100644 index 000000000..81728ebb6 --- /dev/null +++ b/snowflake/cmd/utils.go @@ -0,0 +1,46 @@ +// Copyright 2022 Antrea Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "context" + "fmt" + "os" + + awsconfig "github.com/aws/aws-sdk-go-v2/config" + + s3client "antrea.io/theia/snowflake/pkg/aws/client/s3" +) + +func GetEnv(key string, defaultValue string) string { + if value, ok := os.LookupEnv(key); ok { + return value + } + return defaultValue +} + +func GetBucketRegion(ctx context.Context, bucket string, regionHint string) (string, error) { + awsCfg, err := awsconfig.LoadDefaultConfig(ctx, awsconfig.WithRegion(regionHint)) + if err != nil { + return "", fmt.Errorf("unable to load AWS SDK config: %w", err) + + } + s3Client := s3client.GetClient(awsCfg) + bucketRegion, err := s3client.GetBucketRegion(ctx, s3Client, bucket) + if err != nil { + return "", fmt.Errorf("unable to determine region for infra bucket '%s', make sure the bucket exists and consider providing the region explicitly: %w", bucket, err) + } + return bucketRegion, err +} diff --git a/snowflake/database/migrations.go b/snowflake/database/migrations.go new file mode 100644 index 000000000..52337cc87 --- /dev/null +++ b/snowflake/database/migrations.go @@ -0,0 +1,24 @@ +// Copyright 2022 Antrea Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package database + +import ( + "embed" +) + +//go:embed migrations/*.sql +var Migrations embed.FS + +const MigrationsPath = "migrations" diff --git a/snowflake/database/migrations/000001_create_flows_table.down.sql b/snowflake/database/migrations/000001_create_flows_table.down.sql new file mode 100644 index 000000000..3023dac2f --- /dev/null +++ b/snowflake/database/migrations/000001_create_flows_table.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS flows diff --git a/snowflake/database/migrations/000001_create_flows_table.up.sql b/snowflake/database/migrations/000001_create_flows_table.up.sql new file mode 100644 index 000000000..62a1d7ff9 --- /dev/null +++ b/snowflake/database/migrations/000001_create_flows_table.up.sql @@ -0,0 +1,51 @@ +CREATE TABLE flows ( + flowStartSeconds TIMESTAMP_TZ, + flowEndSeconds TIMESTAMP_TZ, + flowEndSecondsFromSourceNode TIMESTAMP_TZ, + flowEndSecondsFromDestinationNode TIMESTAMP_TZ, + flowEndReason NUMBER(3, 0), + sourceIP STRING(50), + destinationIP STRING(50), + sourceTransportPort NUMBER(5, 0), + destinationTransportPort NUMBER(5, 0), + protocolIdentifier NUMBER(3, 0), + packetTotalCount NUMBER(20, 0), + octetTotalCount NUMBER(20, 0), + packetDeltaCount NUMBER(20, 0), + octetDeltaCount NUMBER(20, 0), + reversePacketTotalCount NUMBER(20, 0), + reverseOctetTotalCount NUMBER(20, 0), + reversePacketDeltaCount NUMBER(20, 0), + reverseOctetDeltaCount NUMBER(20, 0), + sourcePodName STRING(256), + sourcePodNamespace STRING(256), + sourceNodeName STRING(256), + destinationPodName STRING(256), + destinationPodNamespace STRING(256), + destinationNodeName STRING(256), + destinationClusterIP STRING(50), + destinationServicePort NUMBER(5, 0), + destinationServicePortName STRING(256), + ingressNetworkPolicyName STRING(256), + ingressNetworkPolicyNamespace STRING(256), + ingressNetworkPolicyRuleName STRING(256), + ingressNetworkPolicyRuleAction NUMBER(3, 0), + ingressNetworkPolicyType NUMBER(3, 0), + egressNetworkPolicyName STRING(256), + egressNetworkPolicyNamespace STRING(256), + egressNetworkPolicyRuleName STRING(256), + egressNetworkPolicyRuleAction NUMBER(3, 0), + egressNetworkPolicyType NUMBER(3, 0), + tcpState STRING(20), + flowType NUMBER(3, 0), + sourcePodLabels STRING(10000), + destinationPodLabels STRING(10000), + throughput NUMBER(20, 0), + reverseThroughput NUMBER(20, 0), + throughputFromSourceNode NUMBER(20, 0), + throughputFromDestinationNode NUMBER(20, 0), + reverseThroughputFromSourceNode NUMBER(20, 0), + reverseThroughputFromDestinationNode NUMBER(20, 0), + clusterUUID STRING(36), + timeInserted TIMESTAMP_TZ DEFAULT current_timestamp() +) IF NOT EXISTS diff --git a/snowflake/database/migrations/000002_create_pods_view.down.sql b/snowflake/database/migrations/000002_create_pods_view.down.sql new file mode 100644 index 000000000..0db10eca6 --- /dev/null +++ b/snowflake/database/migrations/000002_create_pods_view.down.sql @@ -0,0 +1 @@ +DROP VIEW IF EXISTS pods diff --git a/snowflake/database/migrations/000002_create_pods_view.up.sql b/snowflake/database/migrations/000002_create_pods_view.up.sql new file mode 100644 index 000000000..b6565dff4 --- /dev/null +++ b/snowflake/database/migrations/000002_create_pods_view.up.sql @@ -0,0 +1,39 @@ +CREATE VIEW IF NOT EXISTS pods( + flowStartSeconds, + flowEndSeconds, + packetDeltaCount, + octetDeltaCount, + reversePacketDeltaCount, + reverseOctetDeltaCount, + sourcePodName, + sourcePodNamespace, + sourceTransportPort, + source, + destinationPodName, + destinationPodNamespace, + destinationTransportPort, + destination, + throughput, + reverseThroughput, + flowType, + clusterUUID +) as SELECT + flowStartSeconds, + flowEndSeconds, + packetDeltaCount, + octetDeltaCount, + reversePacketDeltaCount, + reverseOctetDeltaCount, + sourcePodName, + sourcePodNamespace, + sourceTransportPort, + sourcePodNamespace || '/' || sourcePodName, + destinationPodName, + destinationPodNamespace, + destinationTransportPort, + destinationPodNamespace || '/' || destinationPodName, + throughput, + reverseThroughput, + flowtype, + clusterUUID + FROM flows diff --git a/snowflake/database/migrations/000003_create_policies_view.down.sql b/snowflake/database/migrations/000003_create_policies_view.down.sql new file mode 100644 index 000000000..2ac8c8ab1 --- /dev/null +++ b/snowflake/database/migrations/000003_create_policies_view.down.sql @@ -0,0 +1 @@ +DROP VIEW IF EXISTS policies diff --git a/snowflake/database/migrations/000003_create_policies_view.up.sql b/snowflake/database/migrations/000003_create_policies_view.up.sql new file mode 100644 index 000000000..689566c92 --- /dev/null +++ b/snowflake/database/migrations/000003_create_policies_view.up.sql @@ -0,0 +1,24 @@ +CREATE VIEW IF NOT EXISTS policies +as SELECT + flowEndSeconds, + octetDeltaCount, + reverseOctetDeltaCount, + egressNetworkPolicyName, + egressNetworkPolicyNamespace, + egressNetworkPolicyRuleAction, + ingressNetworkPolicyName, + ingressNetworkPolicyNamespace, + ingressNetworkPolicyRuleAction, + sourcePodName, + sourcePodNamespace, + sourceTransportPort, + destinationIP, + destinationPodName, + destinationPodNamespace, + destinationTransportPort, + destinationServicePortName, + destinationServicePort, + throughput, + flowtype, + clusterUUID +FROM flows diff --git a/snowflake/go.mod b/snowflake/go.mod new file mode 100644 index 000000000..c807cd090 --- /dev/null +++ b/snowflake/go.mod @@ -0,0 +1,116 @@ +module antrea.io/theia/snowflake + +go 1.19 + +require ( + github.com/aws/aws-sdk-go-v2 v1.16.15 + github.com/aws/aws-sdk-go-v2/config v1.17.5 + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.5.4 + github.com/aws/aws-sdk-go-v2/service/kms v1.18.10 + github.com/aws/aws-sdk-go-v2/service/s3 v1.27.9 + github.com/aws/aws-sdk-go-v2/service/sqs v1.19.8 + github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0 + github.com/go-logr/logr v1.2.3 + github.com/go-logr/zapr v1.2.3 + github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5 + github.com/pulumi/pulumi-aws/sdk/v5 v5.13.0 + github.com/pulumi/pulumi-command/sdk v0.5.1 + github.com/pulumi/pulumi-random/sdk/v4 v4.8.2 + github.com/pulumi/pulumi-snowflake/sdk v0.13.0 + github.com/pulumi/pulumi/sdk/v3 v3.39.3 + github.com/snowflakedb/gosnowflake v1.6.3 + github.com/spf13/cobra v1.5.0 + go.uber.org/zap v1.23.0 +) + +require ( + github.com/Azure/azure-pipeline-go v0.2.3 // indirect + github.com/Azure/azure-storage-blob-go v0.14.0 // indirect + github.com/Azure/go-autorest/autorest/adal v0.9.16 // indirect + github.com/Microsoft/go-winio v0.5.2 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 // indirect + github.com/acomagu/bufpipe v1.0.3 // indirect + github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect + github.com/apache/arrow/go/arrow v0.0.0-20211013220434-5962184e7a30 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.7 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.12.18 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.15 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.22 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.22 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.12 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.8 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.16 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.15 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.15 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.11.21 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.3 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.16.17 // indirect + github.com/aws/smithy-go v1.13.3 // indirect + github.com/blang/semver v3.5.1+incompatible // indirect + github.com/cheggaaa/pb v1.0.18 // indirect + github.com/djherbis/times v1.2.0 // indirect + github.com/emirpasic/gods v1.12.0 // indirect + github.com/form3tech-oss/jwt-go v3.2.5+incompatible // indirect + github.com/fsnotify/fsnotify v1.4.9 // indirect + github.com/gabriel-vasile/mimetype v1.4.0 // indirect + github.com/go-git/gcfg v1.5.0 // indirect + github.com/go-git/go-billy/v5 v5.3.1 // indirect + github.com/go-git/go-git/v5 v5.4.2 // indirect + github.com/gofrs/uuid v4.0.0+incompatible // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v4 v4.1.0 // indirect + github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/flatbuffers v2.0.0+incompatible // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/imdario/mergo v0.3.12 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect + github.com/klauspost/compress v1.13.6 // indirect + github.com/mattn/go-ieproxy v0.0.1 // indirect + github.com/mattn/go-runewidth v0.0.8 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/go-ps v1.0.0 // indirect + github.com/nxadm/tail v1.4.8 // indirect + github.com/opentracing/basictracer-go v1.0.0 // indirect + github.com/opentracing/opentracing-go v1.1.0 // indirect + github.com/pierrec/lz4/v4 v4.1.8 // indirect + github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pkg/term v1.1.0 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/rogpeppe/go-internal v1.8.1 // indirect + github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94 // indirect + github.com/sergi/go-diff v1.1.0 // indirect + github.com/sirupsen/logrus v1.8.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/texttheater/golang-levenshtein v0.0.0-20191208221605-eb6844b05fc6 // indirect + github.com/tweekmonster/luser v0.0.0-20161003172636-3fa38070dbd7 // indirect + github.com/uber/jaeger-client-go v2.22.1+incompatible // indirect + github.com/uber/jaeger-lib v2.2.0+incompatible // indirect + github.com/xanzy/ssh-agent v0.3.2 // indirect + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/goleak v1.1.12 // indirect + go.uber.org/multierr v1.6.0 // indirect + golang.org/x/crypto v0.0.0-20220824171710-5757bc0c5503 // indirect + golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 // indirect + golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect + golang.org/x/sys v0.0.0-20220823224334-20c2bfdbfe24 // indirect + golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106 // indirect + google.golang.org/grpc v1.45.0 // indirect + google.golang.org/protobuf v1.27.1 // indirect + gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + lukechampine.com/frand v1.4.2 // indirect + sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0 // indirect +) diff --git a/snowflake/go.sum b/snowflake/go.sum new file mode 100644 index 000000000..9338e824b --- /dev/null +++ b/snowflake/go.sum @@ -0,0 +1,570 @@ +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= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +gioui.org v0.0.0-20210308172011-57750fc8a0a6/go.mod h1:RSH6KIUZ0p2xy5zHDxgAM4zumjgTw83q2ge/PI+yyw8= +github.com/Azure/azure-pipeline-go v0.2.3 h1:7U9HBg1JFK3jHl5qmo4CTZKFTVgMwdFHMVtCdfBE21U= +github.com/Azure/azure-pipeline-go v0.2.3/go.mod h1:x841ezTBIMG6O3lAcl8ATHnsOPVl2bqk7S3ta6S6u4k= +github.com/Azure/azure-storage-blob-go v0.14.0 h1:1BCg74AmVdYwO3dlKwtFU1V0wU2PZdREkXvAmZJRUlM= +github.com/Azure/azure-storage-blob-go v0.14.0/go.mod h1:SMqIBi+SuiQH32bvyjngEewEeXoPfKMgWlBDaYf6fck= +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/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +github.com/Azure/go-autorest/autorest/adal v0.9.16 h1:P8An8Z9rH1ldbOLdFpxYorgOt2sywL9V24dAwWHPuGc= +github.com/Azure/go-autorest/autorest/adal v0.9.16/go.mod h1:tGMin8I49Yij6AQ+rvV+Xa/zwxYQB5hmsd6DkfAx2+A= +github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ= +github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= +github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= +github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= +github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= +github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/apache/arrow/go/arrow v0.0.0-20210818145353-234c94e4ce64/go.mod h1:2qMFB56yOP3KzkB3PbYZ4AlUFg3a88F67TIx5lB/WwY= +github.com/apache/arrow/go/arrow v0.0.0-20211013220434-5962184e7a30 h1:HGREIyk0QRPt70R69Gm1JFHDgoiyYpCyuGE8E9k/nf0= +github.com/apache/arrow/go/arrow v0.0.0-20211013220434-5962184e7a30/go.mod h1:Q7yQnSMnLvcXlZ8RV+jwz/6y1rQTqbX6C82SndT52Zs= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/aws/aws-sdk-go-v2 v1.8.0/go.mod h1:xEFuWz+3TYdlPRuo+CqATbeDWIWyaT5uAPwPaWtgse0= +github.com/aws/aws-sdk-go-v2 v1.9.2/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= +github.com/aws/aws-sdk-go-v2 v1.16.14/go.mod h1:s/G+UV29dECbF5rf+RNj1xhlmvoNurGSr+McVSRj59w= +github.com/aws/aws-sdk-go-v2 v1.16.15 h1:2sInOWGE4HV54R90Pj8QgqBBw3Qf1I0husqbqjPZzys= +github.com/aws/aws-sdk-go-v2 v1.16.15/go.mod h1:SwiyXi/1zTUZ6KIAmLK5V5ll8SiURNUYOqTerZPaF9k= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.7 h1:/kxQjtZc7j67TMW/aFJfpsrlvFhsq3lNbX41qN5Tro4= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.7/go.mod h1:KvHyNlxCjo9Y1Fsz+6Ex9OaN2jKijvMxzROxpW5Vctc= +github.com/aws/aws-sdk-go-v2/config v1.6.0/go.mod h1:TNtBVmka80lRPk5+S9ZqVfFszOQAGJJ9KbT3EM3CHNU= +github.com/aws/aws-sdk-go-v2/config v1.8.3/go.mod h1:4AEiLtAb8kLs7vgw2ZV3p2VZ1+hBavOc84hqxVNpCyw= +github.com/aws/aws-sdk-go-v2/config v1.17.5 h1:+NS1BWvprx7nHcIk5o32LrZgifs/7Pm1V2nWjQgZ2H0= +github.com/aws/aws-sdk-go-v2/config v1.17.5/go.mod h1:H0cvPNDO3uExWts/9PDhD/0ne2esu1uaIulwn1vkwxM= +github.com/aws/aws-sdk-go-v2/credentials v1.3.2/go.mod h1:PACKuTJdt6AlXvEq8rFI4eDmoqDFC5DpVKQbWysaDgM= +github.com/aws/aws-sdk-go-v2/credentials v1.4.3/go.mod h1:FNNC6nQZQUuyhq5aE5c7ata8o9e4ECGmS4lAXC7o1mQ= +github.com/aws/aws-sdk-go-v2/credentials v1.12.18 h1:HF62tbhARhgLfvmfwUbL9qZ+dkbZYzbFdxBb3l5gr7Q= +github.com/aws/aws-sdk-go-v2/credentials v1.12.18/go.mod h1:O7n/CPagQ33rfG6h7vR/W02ammuc5CrsSM22cNZp9so= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.4.0/go.mod h1:Mj/U8OpDbcVcoctrYwA2bak8k/HFPdcLzI/vaiXMwuM= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.6.0/go.mod h1:gqlclDEZp4aqJOancXK6TN24aKhT0W0Ae9MHk3wzTMM= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.15 h1:nkQ+aI0OCeYfzrBipL6ja/6VEbUnHQoZHBHtoK+Nzxw= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.15/go.mod h1:Oz2/qWINxIgSmoZT9adpxJy2UhpcOAI3TIyWgYMVSz0= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.4.0/go.mod h1:eHwXu2+uE/T6gpnYWwBwqoeqRf9IXyCcolyOWDRAErQ= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.5.4 h1:TnU1cY51027j/MQeFy7DIgk1UuzJY+wLFYqXceY/fiE= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.5.4/go.mod h1:Ex7XQmbFmgFHrjUX6TN3mApKW5Hglyga+F7wZHTtYhA= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.21/go.mod h1:XsmHMV9c512xgsW01q7H0ut+UQQQpWX8QsFbdLHDwaU= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.22 h1:pE27/u2A7JlwICjOvONQDob8PToShRTkuiUE74ymVWg= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.22/go.mod h1:/vNv5Al0bpiF8YdX2Ov6Xy05VTiXsql94yUqJMYaj0w= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.15/go.mod h1:kjJ4CyD9M3Wq88GYg3IPfj67Rs0Uvz8aXK7MJ8BvE4I= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.16 h1:L5LKGHHXOl4t7+5QZMTl38GIzSAq07XUTRtEquiHGMA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.16/go.mod h1:62dsXI0BqTIGomDl8Hpm33dv0OntGaVblri3ZRParVQ= +github.com/aws/aws-sdk-go-v2/internal/ini v1.2.0/go.mod h1:Q5jATQc+f1MfZp3PDMhn6ry18hGvE0i8yvbXoKbnZaE= +github.com/aws/aws-sdk-go-v2/internal/ini v1.2.4/go.mod h1:ZcBrrI3zBKlhGFNYWvju0I3TR93I7YIgAfy82Fh4lcQ= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.22 h1:nF+E8HfYpOMw6M5oA9efB602VC00IHNQnB5CmFvZPvA= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.22/go.mod h1:tltHVGy977LrSOgRR5aV9+miyno/Gul/uJNPKS7FzP4= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.12 h1:i0Tig01XGhXo/ki1BZUbRMhusGVCScEvaWdlFRWxAKk= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.0.12/go.mod h1:QPoxYMISvteeDH4A89gGWWlCA/Bz6oUDF7hGdPdOPuE= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.2.2/go.mod h1:EASdTcM1lGhUe1/p4gkojHwlGJkeoRjjr1sRCzup3Is= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.3.0/go.mod h1:v8ygadNyATSm6elwJ/4gzJwcFhri9RqS8skgHKiwXPU= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.8 h1:NpixDFjwr1BZg2459mX07NZnVYGGp62Lb6AtVGOLNlo= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.8/go.mod h1:MJUgrBPfGB4yk2uWoImVqd9cklry1hATyJV/7gJ6JTk= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.16 h1:kHc3TqW5kJ9Vfd9YEwywrNrL87DItpvAohlP+OuzABY= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.16/go.mod h1:U/9ZCgIx6x6NTdFRt60qO3gxUxBx4gRi+S/Yc/n+7vc= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.2.2/go.mod h1:NXmNI41bdEsJMrD0v9rUvbGCB5GwdBEpKvUvIY3vTFg= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.2/go.mod h1:72HRZDLMtmVQiLG2tLfQcaWLCssELvGl+Zf2WVxMmR8= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.15 h1:xlf0J6DUgAj/ocvKQxCmad8Bu1lJuRbt5Wu+4G1xw1g= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.15/go.mod h1:ZVJ7ejRl4+tkWMuCwjXoy0jd8fF5u3RCyWjSVjUIvQE= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.5.2/go.mod h1:QuL2Ym8BkrLmN4lUofXYq6000/i5jPjosCNK//t6gak= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.7.2/go.mod h1:np7TMuJNT83O0oDOSF8i4dF3dvGqA6hPYYo6YYkzgRA= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.15 h1:v9f7NY7D19ssE2EM+m9yT1m5zdWHuRAsZaFh24GAkOk= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.13.15/go.mod h1:gXfPo3nMoCbJKTZKDxv3rUhcYJjYT/K++jEqcWHjD/Q= +github.com/aws/aws-sdk-go-v2/service/kms v1.18.10 h1:rl0vxqQ/DFZZMLk9+FLgIuiE/GwMPoI5BeoCkkM2DA4= +github.com/aws/aws-sdk-go-v2/service/kms v1.18.10/go.mod h1:45pB2oUV71tilooilIi3dC1KVWWJHHhc7JnyqByuheo= +github.com/aws/aws-sdk-go-v2/service/s3 v1.12.0/go.mod h1:6J++A5xpo7QDsIeSqPK4UHqMSyPOCopa+zKtqAMhqVQ= +github.com/aws/aws-sdk-go-v2/service/s3 v1.16.1/go.mod h1:CQe/KvWV1AqRc65KqeJjrLzr5X2ijnFTTVzJW0VBRCI= +github.com/aws/aws-sdk-go-v2/service/s3 v1.27.9 h1:imVonvre+AHMcDc3B9bPHHy5ZgjIkkYc/jyDBK8FHFw= +github.com/aws/aws-sdk-go-v2/service/s3 v1.27.9/go.mod h1:0Gfmg8gjPhVPy/IXkLAmyKZbAue+2s11BWKH+oXggmg= +github.com/aws/aws-sdk-go-v2/service/sqs v1.19.8 h1:sgWMD5t0GYBw5QqSr7L5+oFonjdrgvpiGoyb1veOpXI= +github.com/aws/aws-sdk-go-v2/service/sqs v1.19.8/go.mod h1:nMu/p558phDp5xa1USWHcofcWvoaat4Dr46w7ruM1XQ= +github.com/aws/aws-sdk-go-v2/service/sso v1.3.2/go.mod h1:J21I6kF+d/6XHVk7kp/cx9YVD2TMD2TbLwtRGVcinXo= +github.com/aws/aws-sdk-go-v2/service/sso v1.4.2/go.mod h1:NBvT9R1MEF+Ud6ApJKM0G+IkPchKS7p7c2YPKwHmBOk= +github.com/aws/aws-sdk-go-v2/service/sso v1.11.21 h1:7jUFr+7F4MzIjCZzy7ygRtXFQcQ0kAbT0gUvtUeAdyU= +github.com/aws/aws-sdk-go-v2/service/sso v1.11.21/go.mod h1:q8nYq51W3gpZempYsAD83fPRlrOTMCwN+Ahg4BKFTXQ= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.3 h1:UTTPNP3/WzZa7hoHP3Szb/Yl0bM3NoBrf5ABy1OArUM= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.13.3/go.mod h1:+IF75RMJh0+zqTGXGshyEGRsU2ImqWv6UuHGkHl6kEo= +github.com/aws/aws-sdk-go-v2/service/sts v1.6.1/go.mod h1:hLZ/AnkIKHLuPGjEiyghNEdvJ2PP0MgOxcmv9EBJ4xs= +github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g= +github.com/aws/aws-sdk-go-v2/service/sts v1.16.17 h1:LVM2jzEQ8mhb2dhrFl4PJ3sa5+KcKT01dsMk2Ma9/FU= +github.com/aws/aws-sdk-go-v2/service/sts v1.16.17/go.mod h1:bQujK1n0V1D1Gz5uII1jaB1WDvhj4/T3tElsJnVXCR0= +github.com/aws/smithy-go v1.7.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= +github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= +github.com/aws/smithy-go v1.13.2/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= +github.com/aws/smithy-go v1.13.3 h1:l7LYxGuzK6/K+NzJ2mC+VvLUbae0sL3bXU//04MkmnA= +github.com/aws/smithy-go v1.13.3/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/boombuler/barcode v1.0.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cheggaaa/pb v1.0.18 h1:G/DgkKaBP0V5lnBg/vx61nVxxAU+VqU5yMzSc0f2PPE= +github.com/cheggaaa/pb v1.0.18/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +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-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/djherbis/times v1.2.0 h1:xANXjsC/iBqbO00vkWlYwPWgBgEVU6m6AFYg0Pic+Mc= +github.com/djherbis/times v1.2.0/go.mod h1:CGMZlo255K5r4Yw0b9RRfFQpM2y7uOmxg4jm9HsaVf8= +github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0 h1:90Ly+6UfUypEF6vvvW5rQIv9opIL8CbmW9FT20LDQoY= +github.com/dustinkirkland/golang-petname v0.0.0-20191129215211-8e5a1ed0cff0/go.mod h1:V+Qd57rJe8gd4eiGzZyg4h54VLHmYVVw54iMnlAMrF8= +github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +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.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/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/form3tech-oss/jwt-go v3.2.5+incompatible h1:/l4kBbb4/vGSsdtB5nUe8L7B9mImVMaBPw9L/0TBHU8= +github.com/form3tech-oss/jwt-go v3.2.5+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/gabriel-vasile/mimetype v1.3.1/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8= +github.com/gabriel-vasile/mimetype v1.4.0 h1:Cn9dkdYsMIu56tGho+fqzh7XmvY2YyGU0FnbhiOsEro= +github.com/gabriel-vasile/mimetype v1.4.0/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-fonts/dejavu v0.1.0/go.mod h1:4Wt4I4OU2Nq9asgDCteaAaWZOV24E+0/Pwo0gppep4g= +github.com/go-fonts/latin-modern v0.2.0/go.mod h1:rQVLdDMK+mK1xscDwsqM5J8U2jrRa3T0ecnM9pNujks= +github.com/go-fonts/liberation v0.1.1/go.mod h1:K6qoJYypsmfVjWg8KOVDQhLc8UDgIK2HYqyqAO9z7GY= +github.com/go-fonts/stix v0.1.0/go.mod h1:w/c1f0ldAUlJmLBvlbkvVXLAD+tAMqobIIQpmnUIzUY= +github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= +github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= +github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git-fixtures/v4 v4.2.1 h1:n9gGL1Ct/yIw+nfsfr8s4+sbhT+Ncu2SubfXjIWgci8= +github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= +github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4= +github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= +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-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= +github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.1.0 h1:XUgk2Ex5veyVFVeLm0xhusUTQybEbexJXrvPNOKkSY0= +github.com/golang-jwt/jwt/v4 v4.1.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/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.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.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/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/flatbuffers v2.0.0+incompatible h1:dicJ2oXwypfwUGnB2/TYWYEKiuk9eYQlQO/AnOHl5mI= +github.com/google/flatbuffers v2.0.0+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +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.5.0/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.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU= +github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +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/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +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/jung-kurt/gofpdf v1.0.0/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.13.1/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +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/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= +github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= +github.com/mattn/go-colorable v0.1.6 h1:6Su7aK7lXmJ/U79bYtBjLNaha4Fs1Rg9plHpcH+vvnE= +github.com/mattn/go-ieproxy v0.0.1 h1:qiyop7gCflfhwCzGyeT0gro3sF9AIg9HU98JORTkqfI= +github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0= +github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= +github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5 h1:58+kh9C6jJVXYjt8IE48G2eWl6BjwU5Gj0gqY84fy78= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/opentracing/basictracer-go v1.0.0 h1:YyUAhaEfjoWXclZVJ9sGoNct7j4TVk7lZWlQw5UXuoo= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/phpdave11/gofpdf v1.4.2/go.mod h1:zpO6xFn9yxo3YLyMvW8HcKWVdbNqgIfOOp2dXMnm1mY= +github.com/phpdave11/gofpdi v1.0.12/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI= +github.com/pierrec/lz4/v4 v4.1.8 h1:ieHkV+i2BRzngO4Wd/3HGowuZStgq6QkPsD1eolNAO4= +github.com/pierrec/lz4/v4 v4.1.8/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= +github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/term v1.1.0 h1:xIAAdCMh3QIAy+5FrE8Ad8XoDhEU4ufwbaSozViP9kk= +github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw= +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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/pulumi/pulumi-aws/sdk/v5 v5.13.0 h1:OlUQ0httAeL1OOqERcuIBVDqkS/vRMtfnb0zjcfFKoM= +github.com/pulumi/pulumi-aws/sdk/v5 v5.13.0/go.mod h1:Ro2eNbpP/uGWMMvtBDrVph+jdL/G6+IiGB6kj+kDRYM= +github.com/pulumi/pulumi-command/sdk v0.5.1 h1:uUzmiRfKqxl66xonYT7cAfbnPhWFbThvNR++/TKSEDI= +github.com/pulumi/pulumi-command/sdk v0.5.1/go.mod h1:AJfy/5pzH1YV/W2B3/UxsVFQJkJFNFNErp+lXjX748k= +github.com/pulumi/pulumi-random/sdk/v4 v4.8.2 h1:ZlXB3mx1YvAjs+jm59rcpvfl1J7dpLOBOxUb5vEPkZk= +github.com/pulumi/pulumi-random/sdk/v4 v4.8.2/go.mod h1:czSwj+jZnn/VWovMpTLUs/RL/ZS4PFHRdmlXrkvHqeI= +github.com/pulumi/pulumi-snowflake/sdk v0.13.0 h1:PS/CDBY+0pYhJPFyxF+ihoYNl0YvJYmiJuFJvw00yNI= +github.com/pulumi/pulumi-snowflake/sdk v0.13.0/go.mod h1:81MakKxthGNVricIsLYaTBeO3+x+LpyJe+u0xTTxJpQ= +github.com/pulumi/pulumi/sdk/v3 v3.39.3 h1:FQk/fJjwRffehuCyJa/z1ejY7sjdrMji1Z3hq29ODk0= +github.com/pulumi/pulumi/sdk/v3 v3.39.3/go.mod h1:Fw52iyR/4T9xWm7cTcshy4rGEXyPwhXKKEalczKZ8RY= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfFZQK844Gfx8o5WFuvpxWRwnSoipWe/p622j1v06w= +github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94 h1:G04eS0JkAIVZfaJLjla9dNxkJCPiKIGZlw9AfOhzOD0= +github.com/sabhiram/go-gitignore v0.0.0-20180611051255-d3107576ba94/go.mod h1:b18R55ulyQ/h3RaWyloPyER7fWQVZvimKKhnI5OfrJQ= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/snowflakedb/gosnowflake v1.6.3 h1:EJDdDi74YbYt1ty164ge3fMZ0eVZ6KA7b1zmAa/wnRo= +github.com/snowflakedb/gosnowflake v1.6.3/go.mod h1:6hLajn6yxuJ4xUHZegMekpq9rnQbGJ7TMwXjgTmA6lg= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= +github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/texttheater/golang-levenshtein v0.0.0-20191208221605-eb6844b05fc6 h1:9VTskZOIRf2vKF3UL8TuWElry5pgUpV1tFSe/e/0m/E= +github.com/texttheater/golang-levenshtein v0.0.0-20191208221605-eb6844b05fc6/go.mod h1:XDKHRm5ThF8YJjx001LtgelzsoaEcvnA7lVWz9EeX3g= +github.com/tweekmonster/luser v0.0.0-20161003172636-3fa38070dbd7 h1:X9dsIWPuuEJlPX//UmRKophhOKCGXc46RVIGuttks68= +github.com/tweekmonster/luser v0.0.0-20161003172636-3fa38070dbd7/go.mod h1:UxoP3EypF8JfGEjAII8jx1q8rQyDnX8qdTCs/UQBVIE= +github.com/uber/jaeger-client-go v2.22.1+incompatible h1:NHcubEkVbahf9t3p75TOCR83gdUHXjRJvjoBh1yACsM= +github.com/uber/jaeger-client-go v2.22.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v2.2.0+incompatible h1:MxZXOiR2JuoANZ3J6DE/U0kSFv/eJ/GfSYVCjK7dyaw= +github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= +github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= +github.com/xanzy/ssh-agent v0.3.2 h1:eKj4SX2Fe7mui28ZgnFW5fmTz1EIr7ugo5s6wDxdHBM= +github.com/xanzy/ssh-agent v0.3.2/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/yuin/goldmark v1.1.27/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.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= +go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= +go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/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-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220824171710-5757bc0c5503 h1:vJ2V3lFLg+bBhgroYuRfyN583UzVveQmIXjc8T/y3to= +golang.org/x/crypto v0.0.0-20220824171710-5757bc0c5503/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/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-20191002040644-a1355ae1e2c3/go.mod h1:NOZ3BPKG0ec/BKJQgnvsSFpcKLM5xXVWnvZS97DWHgE= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +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/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20210216034530-4410531fe030/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-20190313153728-d0100b6bd8b3/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-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +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.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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191112182307-2180aed22343/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-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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +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-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-20201020160332-67f06af15bc9/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-20180905080454-ebe1bf3edb33/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/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/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-20200828194041-157a740278f4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/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-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/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-20210502180810-71e4cd670f79/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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/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-20210818153620-00dd8d7831e7/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220823224334-20c2bfdbfe24 h1:TyKJRhyo17yWxOMCTHKWrc5rddHORMlnZ/j57umaUd8= +golang.org/x/sys v0.0.0-20220823224334-20c2bfdbfe24/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +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.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 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +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-20190206041539-40960b6deb8e/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-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190927191325-030b2cf1153e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/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-20200130002326-2f3ba24bd6e7/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-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +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 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/gonum v0.9.3 h1:DnoIG+QAMaF5NvxnGe/oKsgKcAc6PcUyl8q0VetfQ8s= +gonum.org/v1/gonum v0.9.3/go.mod h1:TZumC3NeyVQskjXqmyWt4S3bINhy7B4eYwW69EbyX+0= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +gonum.org/v1/plot v0.9.0/go.mod h1:3Pcqqmp6RHvJI72kgb8fThyUnav364FOsdDo2aGW5lY= +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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20210630183607-d20f26d13c79/go.mod h1:yiaVoXHpRzHGyxV3o4DktVWY4mSUErTKaeEOq6C3t3U= +google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106 h1:ErU+UA6wxadoU8nWrsy5MZUVBs75K17zUCsUCIfrXCE= +google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +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.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +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.45.0 h1:NEpgUqV3Z+ZjkqMsxMg11IaDrXY4RY6CQukSGK0uI1M= +google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= +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.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 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/cheggaaa/pb.v1 v1.0.28 h1:n1tBJnnK2r7g9OW2btFH91V92STTUevLXYFb8gy9EMk= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +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.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +lukechampine.com/frand v1.4.2 h1:RzFIpOvkMXuPMBb9maa4ND4wjBn71E1Jpf8BzJHMaVw= +lukechampine.com/frand v1.4.2/go.mod h1:4S/TM2ZgrKejMcKMbeLjISpJMO+/eZ1zu3vYX9dtj3s= +pgregory.net/rapid v0.4.7 h1:MTNRktPuv5FNqOO151TM9mDTa+XHcX6ypYeISDVD14g= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0 h1:ucqkfpjg9WzSUubAO62csmucvxl4/JeW3F4I4909XkM= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/snowflake/main.go b/snowflake/main.go new file mode 100644 index 000000000..337c4ced7 --- /dev/null +++ b/snowflake/main.go @@ -0,0 +1,21 @@ +// Copyright 2022 Antrea Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import "antrea.io/theia/snowflake/cmd" + +func main() { + cmd.Execute() +} diff --git a/snowflake/pkg/aws/client/kms/client.go b/snowflake/pkg/aws/client/kms/client.go new file mode 100644 index 000000000..03b4094ab --- /dev/null +++ b/snowflake/pkg/aws/client/kms/client.go @@ -0,0 +1,24 @@ +// Copyright 2022 Antrea Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kms + +import ( + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/kms" +) + +func GetClient(cfg aws.Config) Interface { + return kms.NewFromConfig(cfg) +} diff --git a/snowflake/pkg/aws/client/kms/interface.go b/snowflake/pkg/aws/client/kms/interface.go new file mode 100644 index 000000000..1e8beb9c5 --- /dev/null +++ b/snowflake/pkg/aws/client/kms/interface.go @@ -0,0 +1,26 @@ +// Copyright 2022 Antrea Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kms + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/service/kms" +) + +type Interface interface { + CreateKey(ctx context.Context, params *kms.CreateKeyInput, optFns ...func(*kms.Options)) (*kms.CreateKeyOutput, error) + ScheduleKeyDeletion(ctx context.Context, params *kms.ScheduleKeyDeletionInput, optFns ...func(*kms.Options)) (*kms.ScheduleKeyDeletionOutput, error) +} diff --git a/snowflake/pkg/aws/client/s3/client.go b/snowflake/pkg/aws/client/s3/client.go new file mode 100644 index 000000000..d0391da5c --- /dev/null +++ b/snowflake/pkg/aws/client/s3/client.go @@ -0,0 +1,24 @@ +// Copyright 2022 Antrea Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package s3 + +import ( + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/s3" +) + +func GetClient(cfg aws.Config) Interface { + return s3.NewFromConfig(cfg) +} diff --git a/snowflake/pkg/aws/client/s3/interface.go b/snowflake/pkg/aws/client/s3/interface.go new file mode 100644 index 000000000..6381beda0 --- /dev/null +++ b/snowflake/pkg/aws/client/s3/interface.go @@ -0,0 +1,35 @@ +// Copyright 2022 Antrea Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package s3 + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/service/s3" +) + +type Interface interface { + HeadBucket(ctx context.Context, params *s3.HeadBucketInput, optFns ...func(*s3.Options)) (*s3.HeadBucketOutput, error) + CreateBucket(ctx context.Context, params *s3.CreateBucketInput, optFns ...func(*s3.Options)) (*s3.CreateBucketOutput, error) + DeleteBucket(ctx context.Context, params *s3.DeleteBucketInput, optFns ...func(*s3.Options)) (*s3.DeleteBucketOutput, error) + + PutBucketLifecycleConfiguration(ctx context.Context, params *s3.PutBucketLifecycleConfigurationInput, optFns ...func(*s3.Options)) (*s3.PutBucketLifecycleConfigurationOutput, error) + + PutBucketNotificationConfiguration(ctx context.Context, params *s3.PutBucketNotificationConfigurationInput, optFns ...func(*s3.Options)) (*s3.PutBucketNotificationConfigurationOutput, error) + GetBucketNotificationConfiguration(ctx context.Context, params *s3.GetBucketNotificationConfigurationInput, optFns ...func(*s3.Options)) (*s3.GetBucketNotificationConfigurationOutput, error) + + ListObjectsV2(ctx context.Context, params *s3.ListObjectsV2Input, optFns ...func(*s3.Options)) (*s3.ListObjectsV2Output, error) + DeleteObjects(ctx context.Context, params *s3.DeleteObjectsInput, optFns ...func(*s3.Options)) (*s3.DeleteObjectsOutput, error) +} diff --git a/snowflake/pkg/aws/client/s3/region.go b/snowflake/pkg/aws/client/s3/region.go new file mode 100644 index 000000000..6481e52c0 --- /dev/null +++ b/snowflake/pkg/aws/client/s3/region.go @@ -0,0 +1,25 @@ +// Copyright 2022 Antrea Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package s3 + +import ( + "context" + + s3manager "github.com/aws/aws-sdk-go-v2/feature/s3/manager" +) + +func GetBucketRegion(ctx context.Context, client Interface, bucket string) (string, error) { + return s3manager.GetBucketRegion(ctx, client, bucket) +} diff --git a/snowflake/pkg/aws/client/sqs/client.go b/snowflake/pkg/aws/client/sqs/client.go new file mode 100644 index 000000000..ae7fc71a2 --- /dev/null +++ b/snowflake/pkg/aws/client/sqs/client.go @@ -0,0 +1,24 @@ +// Copyright 2022 Antrea Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sqs + +import ( + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/sqs" +) + +func GetClient(cfg aws.Config) Interface { + return sqs.NewFromConfig(cfg) +} diff --git a/snowflake/pkg/aws/client/sqs/interface.go b/snowflake/pkg/aws/client/sqs/interface.go new file mode 100644 index 000000000..3dc2e2ac8 --- /dev/null +++ b/snowflake/pkg/aws/client/sqs/interface.go @@ -0,0 +1,28 @@ +// Copyright 2022 Antrea Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package sqs + +import ( + "context" + + "github.com/aws/aws-sdk-go-v2/service/sqs" +) + +type Interface interface { + GetQueueUrl(ctx context.Context, params *sqs.GetQueueUrlInput, optFns ...func(*sqs.Options)) (*sqs.GetQueueUrlOutput, error) + + ReceiveMessage(ctx context.Context, params *sqs.ReceiveMessageInput, optFns ...func(*sqs.Options)) (*sqs.ReceiveMessageOutput, error) + DeleteMessage(ctx context.Context, params *sqs.DeleteMessageInput, optFns ...func(*sqs.Options)) (*sqs.DeleteMessageOutput, error) +} diff --git a/snowflake/pkg/infra/backends.go b/snowflake/pkg/infra/backends.go new file mode 100644 index 000000000..18889d131 --- /dev/null +++ b/snowflake/pkg/infra/backends.go @@ -0,0 +1,23 @@ +// Copyright 2022 Antrea Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package infra + +import ( + "fmt" +) + +func S3StateBackendURL(bucket, prefix, region string) string { + return fmt.Sprintf("s3://%s/%s?region=%s", bucket, prefix, region) +} diff --git a/snowflake/pkg/infra/constants.go b/snowflake/pkg/infra/constants.go new file mode 100644 index 000000000..76038c577 --- /dev/null +++ b/snowflake/pkg/infra/constants.go @@ -0,0 +1,57 @@ +// Copyright 2022 Antrea Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package infra + +const ( + projectName = "theia-infra" + + pulumiVersion = "v3.39.3" + pulumiAWSPluginVersion = "v5.13.0" + pulumiSnowflakePluginVersion = "v0.13.0" + pulumiRandomPluginVersion = "v4.8.2" + pulumiCommandPluginVersion = "v0.5.2" + + migrateSnowflakeVersion = "v0.2.0" + + flowRecordsRetentionDays = 7 + s3BucketNamePrefix = "antrea-flows-" + s3BucketFlowsFolder = "flows" + snsTopicNamePrefix = "antrea-flows-" + sqsQueueNamePrefix = "antrea-flows-" + // how long will Snowpipe error notifications be saved in SQS queue + sqsMessageRetentionSeconds = 7 * 24 * 3600 + + storageIAMRoleNamePrefix = "antrea-sf-storage-iam-role-" + storageIAMPolicyNamePrefix = "antrea-sf-storage-iam-policy-" + storageIntegrationNamePrefix = "ANTREA_FLOWS_STORAGE_INTEGRATION_" + + notificationIAMRoleNamePrefix = "antrea-sf-notification-iam-role-" + notificationIAMPolicyNamePrefix = "antrea-sf-notification-iam-policy" + notificationIntegrationNamePrefix = "ANTREA_FLOWS_NOTIFICATION_INTEGRATION_" + + databaseNamePrefix = "ANTREA_" + + schemaName = "THEIA" + flowRetentionDays = 30 + flowDeletionTaskName = "DELETE_STALE_FLOWS" + udfStageName = "UDFS" + ingestionStageName = "FLOWSTAGE" + autoIngestPipeName = "FLOWPIPE" + + // do not change!!! + flowsTableName = "FLOWS" + + migrationsDir = "migrations" +) diff --git a/snowflake/pkg/infra/manager.go b/snowflake/pkg/infra/manager.go new file mode 100644 index 000000000..2c6cbf33d --- /dev/null +++ b/snowflake/pkg/infra/manager.go @@ -0,0 +1,490 @@ +// Copyright 2022 Antrea Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package infra + +import ( + "archive/tar" + "compress/gzip" + "context" + "database/sql" + "fmt" + "io" + "io/fs" + "net/http" + "os" + "path/filepath" + "runtime" + + "github.com/go-logr/logr" + "github.com/pulumi/pulumi/sdk/v3/go/auto" + "github.com/pulumi/pulumi/sdk/v3/go/auto/optdestroy" + "github.com/pulumi/pulumi/sdk/v3/go/auto/optup" + "github.com/pulumi/pulumi/sdk/v3/go/common/tokens" + "github.com/pulumi/pulumi/sdk/v3/go/common/workspace" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" + + "antrea.io/theia/snowflake/database" + sf "antrea.io/theia/snowflake/pkg/snowflake" +) + +type pulumiPlugin struct { + name string + version string +} + +func createTemporaryWorkdir() (string, error) { + return os.MkdirTemp("", "antrea-pulumi") +} + +func deleteTemporaryWorkdir(d string) { + os.RemoveAll(d) +} + +func writeMigrationsToDisk(fsys fs.FS, migrationsPath string, dest string) error { + if err := os.MkdirAll(dest, 0755); err != nil { + return err + } + entries, err := fs.ReadDir(fsys, migrationsPath) + if err != nil { + return err + } + for _, e := range entries { + if e.IsDir() { + continue + } + if err := func() error { + in, err := fsys.Open(filepath.Join(migrationsPath, e.Name())) + if err != nil { + return err + } + defer in.Close() + + out, err := os.Create(filepath.Join(dest, e.Name())) + if err != nil { + return err + } + defer out.Close() + + _, err = io.Copy(out, in) + if err != nil { + return err + } + return out.Close() + }(); err != nil { + return err + } + } + return nil +} + +func downloadAndUntar(ctx context.Context, logger logr.Logger, url string, dir string) error { + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return err + } + client := http.DefaultClient + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + gzr, err := gzip.NewReader(resp.Body) + if err != nil { + return err + } + defer gzr.Close() + tr := tar.NewReader(gzr) + for { + hdr, err := tr.Next() + if err == io.EOF { + break // End of archive + } + if err != nil { + return err + } + dest := filepath.Join(dir, hdr.Name) + logger.V(4).Info("Untarring", "path", hdr.Name) + if hdr.Typeflag != tar.TypeReg { + continue + } + if err := func() error { + f, err := os.OpenFile(dest, os.O_CREATE|os.O_RDWR, os.FileMode(hdr.Mode)) + if err != nil { + return err + } + defer f.Close() + + // copy over contents + if _, err := io.Copy(f, tr); err != nil { + return err + } + return nil + }(); err != nil { + return err + } + } + return nil +} + +func installPulumiCLI(ctx context.Context, logger logr.Logger, dir string) error { + logger.Info("Downloading and installing Pulumi", "version", pulumiVersion) + cachedVersion, err := os.ReadFile(filepath.Join(dir, ".pulumi-version")) + if err == nil && string(cachedVersion) == pulumiVersion { + logger.Info("Pulumi CLI is already up-to-date") + return nil + } + + operatingSystem := runtime.GOOS + arch := runtime.GOARCH + target := operatingSystem + if arch == "amd64" { + target += "-x64" + } else if arch == "arm64" { + target += "-arm64" + } else { + return fmt.Errorf("arch not supported: %s", arch) + } + supportedTargets := map[string]bool{ + "darwin-arm64": true, + "darwin-x64": true, + "linux-arm64": true, + "linux-x64": true, + "windows-x64": true, + } + if _, ok := supportedTargets[target]; !ok { + return fmt.Errorf("OS / arch combination is not supported: %s / %s", operatingSystem, arch) + } + url := fmt.Sprintf("https://github.com/pulumi/pulumi/releases/download/%s/pulumi-%s-%s.tar.gz", pulumiVersion, pulumiVersion, target) + if err := os.MkdirAll(filepath.Join(dir, "pulumi"), 0755); err != nil { + return err + } + if err := downloadAndUntar(ctx, logger, url, dir); err != nil { + return err + } + + if err := os.WriteFile(filepath.Join(dir, ".pulumi-version"), []byte(pulumiVersion), 0660); err != nil { + logger.Error(err, "Error when writing pulumi version to cache file") + } + logger.Info("Installed Pulumi") + return nil +} + +func installMigrateSnowflakeCLI(ctx context.Context, logger logr.Logger, dir string) error { + logger.Info("Downloading and installing Migrate Snowflake", "version", migrateSnowflakeVersion) + cachedVersion, err := os.ReadFile(filepath.Join(dir, ".migrate-sf-version")) + if err == nil && string(cachedVersion) == migrateSnowflakeVersion { + logger.Info("Migrate Snowflake CLI is already up-to-date") + return nil + } + + operatingSystem := runtime.GOOS + arch := runtime.GOARCH + target := fmt.Sprintf("%s_%s", operatingSystem, arch) + supportedTargets := map[string]bool{ + "darwin_arm64": true, + "darwin_amd64": true, + "linux_arm64": true, + "linux_amd64": true, + "windows_amd64": true, + } + if _, ok := supportedTargets[target]; !ok { + return fmt.Errorf("OS / arch combination is not supported: %s / %s", operatingSystem, arch) + } + url := fmt.Sprintf("https://github.com/antoninbas/migrate-snowflake/releases/download/%s/migrate-snowflake_%s_%s.tar.gz", migrateSnowflakeVersion, migrateSnowflakeVersion, target) + if err := downloadAndUntar(ctx, logger, url, dir); err != nil { + return err + } + + if err := os.WriteFile(filepath.Join(dir, ".migrate-sf-version"), []byte(migrateSnowflakeVersion), 0660); err != nil { + logger.Error(err, "Error when writing Migrate Snowflake version to cache file") + } + logger.Info("Installed Migrate Snowflake") + return nil +} + +type Manager struct { + logger logr.Logger + stackName string + stateBackendURL string + secretsProviderURL string + region string + warehouseName string + workdir string + verbose bool +} + +func NewManager( + logger logr.Logger, + stackName string, + stateBackendURL string, + secretsProviderURL string, + region string, + warehouseName string, + workdir string, + verbose bool, // output Pulumi progress to stdout +) *Manager { + return &Manager{ + logger: logger.WithValues("project", projectName, "stack", stackName), + stackName: stackName, + stateBackendURL: stateBackendURL, + secretsProviderURL: secretsProviderURL, + region: region, + warehouseName: warehouseName, + workdir: workdir, + verbose: verbose, + } +} + +func (m *Manager) setup( + ctx context.Context, + stackName string, + workdir string, + requiredPlugins []pulumiPlugin, + declareFunc func(ctx *pulumi.Context) error, +) (auto.Stack, error) { + logger := m.logger + logger.Info("Creating stack") + secretsProvider := m.secretsProviderURL + passphrase := os.Getenv("PULUMI_CONFIG_PASSPHRASE") + // If KMS is not used to encrypt secrets, setting + // PULUMI_CONFIG_PASSPHRASE is not useful. If neither is set, secrets + // will be in "plain text" in the state backend (S3 bucket). + if secretsProvider == "" && passphrase == "" { + logger.Info("No secrets provider configured and PULUMI_CONFIG_PASSPHRASE env variable empty: secrets will be stored in plain text in backend") + } + opts := []auto.LocalWorkspaceOption{ + auto.WorkDir(workdir), + auto.Project(workspace.Project{ + Name: tokens.PackageName(projectName), + Runtime: workspace.NewProjectRuntimeInfo("go", nil), + Main: workdir, + Backend: &workspace.ProjectBackend{ + URL: m.stateBackendURL, + }, + }), + auto.EnvVars(map[string]string{ + "PULUMI_CONFIG_PASSPHRASE": os.Getenv("PULUMI_CONFIG_PASSPHRASE"), + }), + } + if secretsProvider != "" { + opts = append( + opts, + auto.SecretsProvider(secretsProvider), + auto.Stacks(map[string]workspace.ProjectStack{ + stackName: { + SecretsProvider: secretsProvider, + }, + }), + ) + } + s, err := auto.UpsertStackInlineSource( + ctx, + fmt.Sprintf("%s.%s", projectName, stackName), + projectName, + declareFunc, + opts..., + ) + if err != nil { + return s, err + } + logger.Info("Created stack") + w := s.Workspace() + for _, plugin := range requiredPlugins { + logger.Info("Installing Pulumi plugin", "plugin", plugin.name, "version", plugin.version) + if err := w.InstallPlugin(ctx, plugin.name, plugin.version); err != nil { + return s, fmt.Errorf("failed to install Pulumi plugin %s: %w", plugin.name, err) + } + logger.Info("Installed Pulumi plugin", "plugin", plugin.name) + } + // set stack configuration specifying the AWS region to deploy + s.SetConfig(ctx, "aws:region", auto.ConfigValue{Value: m.region}) + logger.Info("Refreshing stack") + _, err = s.Refresh(ctx) + if err != nil { + return s, err + } + logger.Info("Refreshed stack") + return s, nil +} + +type Result struct { + Region string + BucketName string + BucketFlowsFolder string + DatabaseName string + SchemaName string + FlowsTableName string + SNSTopicARN string + SQSQueueARN string +} + +func (m *Manager) run(ctx context.Context, destroy bool) (*Result, error) { + logger := m.logger + workdir := m.workdir + if workdir == "" { + var err error + workdir, err = createTemporaryWorkdir() + if err != nil { + return nil, err + } + logger.Info("Created temporary workdir", "path", workdir) + defer deleteTemporaryWorkdir(workdir) + } else { + var err error + workdir, err = filepath.Abs(workdir) + if err != nil { + return nil, err + } + } + if err := installPulumiCLI(ctx, logger, workdir); err != nil { + return nil, fmt.Errorf("error when installing Pulumi: %w", err) + } + os.Setenv("PATH", filepath.Join(workdir, "pulumi")) + if err := installMigrateSnowflakeCLI(ctx, logger, workdir); err != nil { + return nil, fmt.Errorf("error when installing Migrate Snowflake: %w", err) + } + + warehouseName := m.warehouseName + if !destroy { + logger.Info("Copying database migrations to disk") + if err := writeMigrationsToDisk(database.Migrations, database.MigrationsPath, filepath.Join(workdir, migrationsDir)); err != nil { + return nil, err + } + logger.Info("Copied database migrations to disk") + + if warehouseName == "" { + dsn, _, err := sf.GetDSN() + if err != nil { + return nil, fmt.Errorf("failed to create DSN: %w", err) + } + + db, err := sql.Open("snowflake", dsn) + if err != nil { + return nil, fmt.Errorf("failed to connect to Snowflake: %w", err) + } + defer db.Close() + temporaryWarehouse := newTemporaryWarehouse(sf.NewClient(db, logger), logger) + warehouseName = temporaryWarehouse.Name() + if err := temporaryWarehouse.Create(ctx); err != nil { + return nil, err + } + defer func() { + if err := temporaryWarehouse.Delete(ctx); err != nil { + logger.Error(err, "Failed to delete temporary warehouse, please do it manually", "name", warehouseName) + } + }() + } + } + + plugins := []pulumiPlugin{ + {name: "aws", version: pulumiAWSPluginVersion}, + {name: "snowflake", version: pulumiSnowflakePluginVersion}, + {name: "random", version: pulumiRandomPluginVersion}, + {name: "command", version: pulumiCommandPluginVersion}, + } + + s, err := m.setup(ctx, m.stackName, workdir, plugins, declareStack(warehouseName)) + if err != nil { + return nil, err + } + + destroyFunc := func() error { + logger.Info("Destroying stack") + var progressStream io.Writer + if m.verbose { + // wire up our destroy to stream progress to stdout + progressStream = os.Stdout + } else { + progressStream = io.Discard + } + if _, err := s.Destroy(ctx, optdestroy.ProgressStreams(progressStream)); err != nil { + return err + } + logger.Info("Destroyed stack") + logger.Info("Removing stack") + if err := s.Workspace().RemoveStack(ctx, s.Name()); err != nil { + return err + } + logger.Info("Removed stack") + return nil + } + + if destroy { + if err := destroyFunc(); err != nil { + return nil, err + } + // return early + return &Result{}, nil + } + + updateFunc := func() (auto.UpResult, error) { + logger.Info("Updating stack") + var progressStream io.Writer + if m.verbose { + // wire up our update to stream progress to stdout + progressStream = os.Stdout + } else { + progressStream = io.Discard + } + res, err := s.Up(ctx, optup.ProgressStreams(progressStream)) + if err != nil { + return res, err + } + logger.Info("Updated stack") + return res, nil + } + + getStackOutputs := func(outs auto.OutputMap) (map[string]string, error) { + result := make(map[string]string) + names := []string{"bucketID", "databaseName", "storageIntegrationName", "notificationIntegrationName", "snsTopicARN", "sqsQueueARN"} + for _, name := range names { + v, ok := outs[name].Value.(string) + if !ok { + return nil, fmt.Errorf("failed to get '%s' from first stack outputs", name) + } + result[name] = v + } + return result, nil + } + + upRes, err := updateFunc() + if err != nil { + return nil, err + } + outs, err := getStackOutputs(upRes.Outputs) + if err != nil { + return nil, err + } + + return &Result{ + Region: m.region, + BucketName: outs["bucketID"], + BucketFlowsFolder: s3BucketFlowsFolder, + DatabaseName: outs["databaseName"], + SchemaName: schemaName, + FlowsTableName: flowsTableName, + SNSTopicARN: outs["snsTopicARN"], + SQSQueueARN: outs["sqsQueueARN"], + }, nil +} + +func (m *Manager) Onboard(ctx context.Context) (*Result, error) { + return m.run(ctx, false) +} + +func (m *Manager) Offboard(ctx context.Context) error { + _, err := m.run(ctx, true) + return err +} diff --git a/snowflake/pkg/infra/secret_providers.go b/snowflake/pkg/infra/secret_providers.go new file mode 100644 index 000000000..2c0a13776 --- /dev/null +++ b/snowflake/pkg/infra/secret_providers.go @@ -0,0 +1,23 @@ +// Copyright 2022 Antrea Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package infra + +import ( + "fmt" +) + +func KmsSecretsProviderURL(keyID string, region string) string { + return fmt.Sprintf("awskms://%s?region=%s", keyID, region) +} diff --git a/snowflake/pkg/infra/stack.go b/snowflake/pkg/infra/stack.go new file mode 100644 index 000000000..556f73ee1 --- /dev/null +++ b/snowflake/pkg/infra/stack.go @@ -0,0 +1,462 @@ +// Copyright 2022 Antrea Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package infra + +import ( + "fmt" + "math/rand" + "os" + "strings" + "time" + + "github.com/pulumi/pulumi-aws/sdk/v5/go/aws" + "github.com/pulumi/pulumi-aws/sdk/v5/go/aws/iam" + "github.com/pulumi/pulumi-aws/sdk/v5/go/aws/s3" + "github.com/pulumi/pulumi-aws/sdk/v5/go/aws/sns" + "github.com/pulumi/pulumi-aws/sdk/v5/go/aws/sqs" + "github.com/pulumi/pulumi-command/sdk/go/command/local" + "github.com/pulumi/pulumi-random/sdk/v4/go/random" + "github.com/pulumi/pulumi-snowflake/sdk/go/snowflake" + "github.com/pulumi/pulumi/sdk/v3/go/pulumi" +) + +func declareSnowflakeIngestion(randomString *random.RandomString, bucket *s3.BucketV2, accountID string) func(ctx *pulumi.Context) (*snowflake.StorageIntegration, *iam.Role, error) { + declareFunc := func(ctx *pulumi.Context) (*snowflake.StorageIntegration, *iam.Role, error) { + storageIntegrationName := randomString.Result.ApplyT(func(suffix string) string { + return fmt.Sprintf("%s%s", storageIntegrationNamePrefix, strings.ToUpper(suffix)) + }).(pulumi.StringOutput) + storageIntegration, err := snowflake.NewStorageIntegration(ctx, "antrea-sf-storage-integration", &snowflake.StorageIntegrationArgs{ + Name: storageIntegrationName, + Type: pulumi.String("EXTERNAL_STAGE"), + Enabled: pulumi.Bool(true), + StorageAllowedLocations: pulumi.StringArray([]pulumi.StringInput{pulumi.Sprintf("s3://%s/%s/", bucket.ID(), s3BucketFlowsFolder)}), + StorageProvider: pulumi.String("S3"), + StorageAwsRoleArn: pulumi.Sprintf("arn:aws:iam::%s:role/%s%s", accountID, storageIAMRoleNamePrefix, randomString.ID()), + }, pulumi.DeleteBeforeReplace(true)) + if err != nil { + return nil, nil, err + } + ctx.Export("storageIntegrationName", storageIntegration.Name) + + storagePolicyDocument := iam.GetPolicyDocumentOutput(ctx, iam.GetPolicyDocumentOutputArgs{ + Statements: iam.GetPolicyDocumentStatementArray{ + iam.GetPolicyDocumentStatementArgs{ + Sid: pulumi.String("1"), + Effect: pulumi.String("Allow"), + Actions: pulumi.ToStringArray([]string{"s3:GetObject", "s3:GetObjectVersion"}), + Resources: pulumi.StringArray([]pulumi.StringInput{pulumi.Sprintf("arn:aws:s3:::%s/%s/*", bucket.ID(), s3BucketFlowsFolder)}), + }, + iam.GetPolicyDocumentStatementArgs{ + Sid: pulumi.String("2"), + Effect: pulumi.String("Allow"), + Actions: pulumi.ToStringArray([]string{"s3:ListBucket"}), + Resources: pulumi.StringArray([]pulumi.StringInput{pulumi.Sprintf("arn:aws:s3:::%s", bucket.ID())}), + Conditions: iam.GetPolicyDocumentStatementConditionArray{ + iam.GetPolicyDocumentStatementConditionArgs{ + Test: pulumi.String("StringLike"), + Variable: pulumi.String("s3:prefix"), + Values: pulumi.StringArray([]pulumi.StringInput{pulumi.Sprintf("%s/*", s3BucketFlowsFolder)}), + }, + }, + }, + iam.GetPolicyDocumentStatementArgs{ + Sid: pulumi.String("3"), + Effect: pulumi.String("Allow"), + Actions: pulumi.ToStringArray([]string{"s3:GetBucketLocation"}), + Resources: pulumi.StringArray([]pulumi.StringInput{pulumi.Sprintf("arn:aws:s3:::%s", bucket.ID())}), + }, + }, + }) + + // For some reason pulumi reports a diff on every refresh, when in fact the resource + // does not need to be updated and is not actually updated during the "up" stage. + // See https://github.com/pulumi/pulumi-aws/issues/2024 + storageIAMPolicy, err := iam.NewPolicy(ctx, "antrea-sf-storage-iam-policy", &iam.PolicyArgs{ + Name: pulumi.Sprintf("%s%s", storageIAMPolicyNamePrefix, randomString.ID()), + Policy: storagePolicyDocument.Json(), + }, pulumi.DeleteBeforeReplace(true)) + if err != nil { + return nil, nil, err + } + + storageAssumeRolePolicyDocument := iam.GetPolicyDocumentOutput(ctx, iam.GetPolicyDocumentOutputArgs{ + Statements: iam.GetPolicyDocumentStatementArray{ + iam.GetPolicyDocumentStatementArgs{ + Sid: pulumi.String("1"), + Actions: pulumi.ToStringArray([]string{"sts:AssumeRole"}), + Principals: iam.GetPolicyDocumentStatementPrincipalArray{ + iam.GetPolicyDocumentStatementPrincipalArgs{ + Type: pulumi.String("AWS"), + Identifiers: pulumi.StringArray([]pulumi.StringInput{storageIntegration.StorageAwsIamUserArn}), + }, + }, + Conditions: iam.GetPolicyDocumentStatementConditionArray{ + iam.GetPolicyDocumentStatementConditionArgs{ + Test: pulumi.String("StringEquals"), + Variable: pulumi.String("sts:ExternalId"), + Values: pulumi.StringArray([]pulumi.StringInput{storageIntegration.StorageAwsExternalId}), + }, + }, + }, + }, + }) + + storageIAMRole, err := iam.NewRole(ctx, "antrea-sf-storage-iam-policy", &iam.RoleArgs{ + Name: pulumi.Sprintf("%s%s", storageIAMRoleNamePrefix, randomString.ID()), + AssumeRolePolicy: storageAssumeRolePolicyDocument.Json(), + }, pulumi.DeleteBeforeReplace(true)) + if err != nil { + return nil, nil, err + } + + _, err = iam.NewRolePolicyAttachment(ctx, "antrea-sf-storage-iam-role-policy-attachment", &iam.RolePolicyAttachmentArgs{ + Role: storageIAMRole.ID(), + PolicyArn: storageIAMPolicy.Arn, + }) + if err != nil { + return nil, nil, err + } + + return storageIntegration, storageIAMRole, nil + } + return declareFunc +} + +func declareSnowflakeErrorNotifications(randomString *random.RandomString, snsTopic *sns.Topic, accountID string) func(ctx *pulumi.Context) (*snowflake.NotificationIntegration, *iam.Role, error) { + declareFunc := func(ctx *pulumi.Context) (*snowflake.NotificationIntegration, *iam.Role, error) { + notificationIntegrationName := randomString.Result.ApplyT(func(suffix string) string { + return fmt.Sprintf("%s%s", notificationIntegrationNamePrefix, strings.ToUpper(suffix)) + }).(pulumi.StringOutput) + notificationIntegration, err := snowflake.NewNotificationIntegration(ctx, "antrea-sf-notification-integration", &snowflake.NotificationIntegrationArgs{ + Name: notificationIntegrationName, + AwsSnsRoleArn: pulumi.Sprintf("arn:aws:iam::%s:role/%s%s", accountID, notificationIAMRoleNamePrefix, randomString.ID()), + AwsSnsTopicArn: snsTopic.ID(), + Direction: pulumi.String("OUTBOUND"), + Enabled: pulumi.Bool(true), + NotificationProvider: pulumi.String("AWS_SNS"), + Type: pulumi.String("QUEUE"), + }, pulumi.DeleteBeforeReplace(true)) + if err != nil { + return nil, nil, err + } + ctx.Export("notificationIntegrationName", notificationIntegration.Name) + + notificationPolicyDocument := iam.GetPolicyDocumentOutput(ctx, iam.GetPolicyDocumentOutputArgs{ + Statements: iam.GetPolicyDocumentStatementArray{ + iam.GetPolicyDocumentStatementArgs{ + Sid: pulumi.String("1"), + Effect: pulumi.String("Allow"), + Actions: pulumi.ToStringArray([]string{"sns:Publish"}), + Resources: pulumi.StringArray([]pulumi.StringInput{snsTopic.ID()}), + }, + }, + }) + + notificationIAMPolicy, err := iam.NewPolicy(ctx, "antrea-sf-notification-iam-policy", &iam.PolicyArgs{ + Name: pulumi.Sprintf("%s%s", notificationIAMPolicyNamePrefix, randomString.ID()), + Policy: notificationPolicyDocument.Json(), + }, pulumi.DeleteBeforeReplace(true)) + if err != nil { + return nil, nil, err + } + + notificationAssumeRolePolicyDocument := iam.GetPolicyDocumentOutput(ctx, iam.GetPolicyDocumentOutputArgs{ + Statements: iam.GetPolicyDocumentStatementArray{ + iam.GetPolicyDocumentStatementArgs{ + Sid: pulumi.String("1"), + Actions: pulumi.ToStringArray([]string{"sts:AssumeRole"}), + Principals: iam.GetPolicyDocumentStatementPrincipalArray{ + iam.GetPolicyDocumentStatementPrincipalArgs{ + Type: pulumi.String("AWS"), + Identifiers: pulumi.StringArray([]pulumi.StringInput{notificationIntegration.AwsSnsIamUserArn}), + }, + }, + Conditions: iam.GetPolicyDocumentStatementConditionArray{ + iam.GetPolicyDocumentStatementConditionArgs{ + Test: pulumi.String("StringEquals"), + Variable: pulumi.String("sts:ExternalId"), + Values: pulumi.StringArray([]pulumi.StringInput{notificationIntegration.AwsSnsExternalId}), + }, + }, + }, + }, + }) + + notificationIAMRole, err := iam.NewRole(ctx, "antrea-sf-notification-iam-policy", &iam.RoleArgs{ + Name: pulumi.Sprintf("%s%s", notificationIAMRoleNamePrefix, randomString.ID()), + AssumeRolePolicy: notificationAssumeRolePolicyDocument.Json(), + }) + if err != nil { + return nil, nil, err + } + + _, err = iam.NewRolePolicyAttachment(ctx, "antrea-sf-notification-iam-role-policy-attachment", &iam.RolePolicyAttachmentArgs{ + Role: notificationIAMRole.ID(), + PolicyArn: notificationIAMPolicy.Arn, + }) + if err != nil { + return nil, nil, err + } + + return notificationIntegration, notificationIAMRole, nil + } + return declareFunc +} + +func declareSnowflakeDatabase( + warehouseName string, + randomString *random.RandomString, + bucket *s3.BucketV2, + storageIntegration *snowflake.StorageIntegration, + storageIAMRole *iam.Role, + notificationIntegration *snowflake.NotificationIntegration, + notificationIAMRole *iam.Role, +) func(ctx *pulumi.Context) (*snowflake.Pipe, error) { + declareFunc := func(ctx *pulumi.Context) (*snowflake.Pipe, error) { + databaseName := randomString.Result.ApplyT(func(suffix string) string { + return fmt.Sprintf("%s%s", databaseNamePrefix, strings.ToUpper(suffix)) + }).(pulumi.StringOutput) + db, err := snowflake.NewDatabase(ctx, "antrea-sf-db", &snowflake.DatabaseArgs{ + Name: databaseName, + }, pulumi.DeleteBeforeReplace(true)) + if err != nil { + return nil, err + } + ctx.Export("databaseName", db.Name) + + schema, err := snowflake.NewSchema(ctx, "antrea-sf-schema", &snowflake.SchemaArgs{ + Database: db.ID(), + Name: pulumi.String(schemaName), + }, pulumi.Parent(db), pulumi.DeleteBeforeReplace(true)) + if err != nil { + return nil, err + } + + _, err = snowflake.NewStage(ctx, "antrea-sf-udf-stage", &snowflake.StageArgs{ + Database: db.ID(), + Schema: schema.Name, + Name: pulumi.String(udfStageName), + }, pulumi.Parent(schema), pulumi.DeleteBeforeReplace(true)) + if err != nil { + return nil, err + } + + // IAMRoles need to be added as dependencies, but integrations are probably redundant + dependencies := []pulumi.Resource{storageIntegration, storageIAMRole, notificationIntegration, notificationIAMRole} + ingestionStage, err := snowflake.NewStage(ctx, "antrea-sf-ingestion-stage", &snowflake.StageArgs{ + Database: db.ID(), + Schema: schema.Name, + Name: pulumi.String(ingestionStageName), + Url: pulumi.Sprintf("s3://%s/%s/", bucket.ID(), s3BucketFlowsFolder), + StorageIntegration: storageIntegration.ID(), + }, pulumi.Parent(schema), pulumi.DependsOn(dependencies), pulumi.DeleteBeforeReplace(true)) + if err != nil { + return nil, err + } + + // ideally this would be a dynamic provider: https://www.pulumi.com/docs/intro/concepts/resources/dynamic-providers/ + // however, dynamic providers are not supported at the moment for Golang + // maybe this is a change that we can make at a future time if support is added + dbMigrations, err := local.NewCommand(ctx, "antrea-sf-run-db-migrations", &local.CommandArgs{ + // we will ignore changes to create because the warehouse name can change + Create: pulumi.Sprintf("./migrate-snowflake -source file://%s -database %s -schema %s -warehouse %s", migrationsDir, db.Name, schema.Name, warehouseName), + Environment: pulumi.StringMap{ + "SNOWFLAKE_ACCOUNT": pulumi.ToSecret(os.Getenv("SNOWFLAKE_ACCOUNT")).(pulumi.StringOutput), + "SNOWFLAKE_USER": pulumi.ToSecret(os.Getenv("SNOWFLAKE_USER")).(pulumi.StringOutput), + "SNOWFLAKE_PASSWORD": pulumi.ToSecret(os.Getenv("SNOWFLAKE_PASSWORD")).(pulumi.StringOutput), + }, + // migrations are idempotent so for now we always run them + Triggers: pulumi.Array([]pulumi.Input{db.ID(), schema.ID(), pulumi.Int(rand.Int())}), + }, pulumi.Parent(schema), pulumi.DeleteBeforeReplace(true), pulumi.ReplaceOnChanges([]string{"environment"}), pulumi.IgnoreChanges([]string{"create"})) + if err != nil { + return nil, err + } + + // a bit of defensive programming: explicit dependency on ingestionStage may not be strictly required + pipe, err := snowflake.NewPipe(ctx, "antrea-sf-auto-ingest-pipe", &snowflake.PipeArgs{ + Database: db.ID(), + Schema: schema.Name, + Name: pulumi.String(autoIngestPipeName), + AutoIngest: pulumi.Bool(true), + ErrorIntegration: notificationIntegration.ID(), + // FQN required for table and stage, see https://github.com/pulumi/pulumi-snowflake/issues/129 + CopyStatement: pulumi.Sprintf("COPY INTO %s.%s.%s FROM @%s.%s.%s FILE_FORMAT = (TYPE = 'CSV')", databaseName, schemaName, flowsTableName, databaseName, schemaName, ingestionStageName), + }, pulumi.Parent(schema), pulumi.DependsOn([]pulumi.Resource{ingestionStage, dbMigrations}), pulumi.DeleteBeforeReplace(true)) + if err != nil { + return nil, err + } + + if flowRetentionDays > 0 { + _, err := snowflake.NewTask(ctx, "antrea-sf-flow-deletion-task", &snowflake.TaskArgs{ + Database: db.ID(), + Schema: schema.Name, + Name: pulumi.String(flowDeletionTaskName), + Schedule: pulumi.String("USING CRON 0 0 * * * UTC"), + SqlStatement: pulumi.Sprintf("DELETE FROM %s WHERE DATEDIFF(day, timeInserted, CURRENT_TIMESTAMP) > %d", flowsTableName, flowRetentionDays), + UserTaskManagedInitialWarehouseSize: pulumi.String("XSMALL"), + Enabled: pulumi.Bool(true), + }, pulumi.Parent(schema), pulumi.DependsOn([]pulumi.Resource{dbMigrations}), pulumi.DeleteBeforeReplace(true)) + if err != nil { + return nil, err + } + } + + return pipe, nil + } + return declareFunc +} + +func declareStack(warehouseName string) func(ctx *pulumi.Context) error { + declareFunc := func(ctx *pulumi.Context) error { + randomString, err := random.NewRandomString(ctx, "antrea-flows-random-pet-suffix", &random.RandomStringArgs{ + Length: pulumi.Int(16), + Lower: pulumi.Bool(true), + Upper: pulumi.Bool(false), + Number: pulumi.Bool(true), + Special: pulumi.Bool(false), + }) + // when disabling auto-naming, it's safer to use DeleteBeforeReplace + // see https://www.pulumi.com/docs/intro/concepts/resources/names/#autonaming + bucket, err := s3.NewBucketV2(ctx, "antrea-flows-bucket", &s3.BucketV2Args{ + Bucket: pulumi.Sprintf("%s%s", s3BucketNamePrefix, randomString.ID()), + ForceDestroy: pulumi.Bool(true), // bucket will be deleted even if not empty + }, pulumi.DeleteBeforeReplace(true)) + if err != nil { + return err + } + ctx.Export("bucketID", bucket.ID()) + + _, err = s3.NewBucketLifecycleConfigurationV2(ctx, "antrea-flows-bucket-lifecycle-configuration", &s3.BucketLifecycleConfigurationV2Args{ + Bucket: bucket.ID(), + Rules: s3.BucketLifecycleConfigurationV2RuleArray{ + &s3.BucketLifecycleConfigurationV2RuleArgs{ + Expiration: &s3.BucketLifecycleConfigurationV2RuleExpirationArgs{ + Days: pulumi.Int(flowRecordsRetentionDays), + }, + Filter: &s3.BucketLifecycleConfigurationV2RuleFilterArgs{ + Prefix: pulumi.Sprintf("%s/", pulumi.String(s3BucketFlowsFolder)), + }, + Id: pulumi.String(s3BucketFlowsFolder), + Status: pulumi.String("Enabled"), + }, + }, + }) + if err != nil { + return err + } + + sqsQueue, err := sqs.NewQueue(ctx, "antrea-flows-sqs-queue", &sqs.QueueArgs{ + Name: pulumi.Sprintf("%s%s", sqsQueueNamePrefix, randomString.ID()), + MessageRetentionSeconds: pulumi.Int(sqsMessageRetentionSeconds), + }, pulumi.DeleteBeforeReplace(true)) + if err != nil { + return err + } + ctx.Export("sqsQueueARN", sqsQueue.Arn) + + snsTopic, err := sns.NewTopic(ctx, "antrea-flows-sns-topic", &sns.TopicArgs{ + Name: pulumi.Sprintf("%s%s", snsTopicNamePrefix, randomString.ID()), + }, pulumi.DeleteBeforeReplace(true)) + if err != nil { + return err + } + ctx.Export("snsTopicARN", snsTopic.Arn) + + sqsQueuePolicyDocument := iam.GetPolicyDocumentOutput(ctx, iam.GetPolicyDocumentOutputArgs{ + Statements: iam.GetPolicyDocumentStatementArray{ + iam.GetPolicyDocumentStatementArgs{ + Sid: pulumi.String("1"), + Effect: pulumi.String("Allow"), + Principals: iam.GetPolicyDocumentStatementPrincipalArray{ + iam.GetPolicyDocumentStatementPrincipalArgs{ + Type: pulumi.String("Service"), + Identifiers: pulumi.ToStringArray([]string{"sns.amazonaws.com"}), + }, + }, + Actions: pulumi.ToStringArray([]string{"sqs:SendMessage"}), + Resources: pulumi.StringArray([]pulumi.StringInput{sqsQueue.Arn}), + Conditions: iam.GetPolicyDocumentStatementConditionArray{ + iam.GetPolicyDocumentStatementConditionArgs{ + Test: pulumi.String("ArnEquals"), + Variable: pulumi.String("aws:SourceArn"), + Values: pulumi.StringArray([]pulumi.StringInput{snsTopic.Arn}), + }, + }, + }, + }, + }) + + _, err = sqs.NewQueuePolicy(ctx, "antrea-flows-sqs-queue-policy", &sqs.QueuePolicyArgs{ + QueueUrl: sqsQueue.ID(), + Policy: sqsQueuePolicyDocument.Json(), + }) + if err != nil { + return err + } + + _, err = sns.NewTopicSubscription(ctx, "antrea-flows-sns-subscription", &sns.TopicSubscriptionArgs{ + Endpoint: sqsQueue.Arn, + Protocol: pulumi.String("sqs"), + Topic: snsTopic.Arn, + }) + if err != nil { + return err + } + + current, err := aws.GetCallerIdentity(ctx, nil, nil) + if err != nil { + return err + } + + storageIntegration, storageIAMRole, err := declareSnowflakeIngestion(randomString, bucket, current.AccountId)(ctx) + if err != nil { + return err + } + + notificationIntegration, notificationIAMRole, err := declareSnowflakeErrorNotifications(randomString, snsTopic, current.AccountId)(ctx) + if err != nil { + return err + } + + pipe, err := declareSnowflakeDatabase(warehouseName, randomString, bucket, storageIntegration, storageIAMRole, notificationIntegration, notificationIAMRole)(ctx) + if err != nil { + return err + } + + _, err = s3.NewBucketNotification(ctx, "antrea-flows-bucket-notification", &s3.BucketNotificationArgs{ + Bucket: bucket.ID(), + Queues: s3.BucketNotificationQueueArray{ + &s3.BucketNotificationQueueArgs{ + QueueArn: pipe.NotificationChannel, + Events: pulumi.StringArray{ + pulumi.String("s3:ObjectCreated:*"), + }, + FilterPrefix: pulumi.Sprintf("%s/", s3BucketFlowsFolder), + Id: pulumi.String("Auto-ingest Snowflake"), + }, + }, + }) + if err != nil { + return err + } + + return nil + } + return declareFunc +} + +func init() { + rand.Seed(time.Now().UnixNano()) +} diff --git a/snowflake/pkg/infra/temporary_warehouse.go b/snowflake/pkg/infra/temporary_warehouse.go new file mode 100644 index 000000000..9b5978a79 --- /dev/null +++ b/snowflake/pkg/infra/temporary_warehouse.go @@ -0,0 +1,69 @@ +// Copyright 2022 Antrea Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package infra + +import ( + "context" + "fmt" + "strings" + + "github.com/dustinkirkland/golang-petname" + "github.com/go-logr/logr" + + sf "antrea.io/theia/snowflake/pkg/snowflake" +) + +type temporaryWarehouse struct { + sfClient sf.Client + logger logr.Logger + warehouseName string +} + +func newTemporaryWarehouse(sfClient sf.Client, logger logr.Logger) *temporaryWarehouse { + return &temporaryWarehouse{ + sfClient: sfClient, + logger: logger, + warehouseName: strings.ToUpper(petname.Generate(3, "_")), + } +} + +func (w *temporaryWarehouse) Name() string { + return w.warehouseName +} + +func (w *temporaryWarehouse) Create(ctx context.Context) error { + warehouseSize := sf.WarehouseSizeType("XSMALL") + autoSuspend := int32(60) // minimum value + intiallySuspended := true + w.logger.Info("Creating Snowflake warehouse", "name", w.warehouseName, "size", warehouseSize) + if err := w.sfClient.CreateWarehouse(ctx, w.warehouseName, sf.WarehouseConfig{ + Size: &warehouseSize, + AutoSuspend: &autoSuspend, + InitiallySuspended: &intiallySuspended, + }); err != nil { + return fmt.Errorf("error when creating Snowflake warehouse: %w", err) + } + w.logger.Info("Created Snowflake warehouse", "name", w.warehouseName) + return nil +} + +func (w *temporaryWarehouse) Delete(ctx context.Context) error { + w.logger.Info("Deleting Snowflake warehouse", "name", w.warehouseName) + if err := w.sfClient.DropWarehouse(ctx, w.warehouseName); err != nil { + return fmt.Errorf("error when deleting Snowflake warehouse: %w", err) + } + w.logger.Info("Deleted Snowflake warehouse", "name", w.warehouseName) + return nil +} diff --git a/snowflake/pkg/snowflake/dsn.go b/snowflake/pkg/snowflake/dsn.go new file mode 100644 index 000000000..4d2a8b150 --- /dev/null +++ b/snowflake/pkg/snowflake/dsn.go @@ -0,0 +1,86 @@ +// Copyright 2022 Antrea Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package snowflake + +import ( + "log" + "os" + "strconv" + + sf "github.com/snowflakedb/gosnowflake" +) + +func SetWarehouse(name string) func(*sf.Config) { + return func(cfg *sf.Config) { + cfg.Warehouse = name + } +} + +func SetDatabase(name string) func(*sf.Config) { + return func(cfg *sf.Config) { + cfg.Database = name + } +} + +func SetSchema(name string) func(*sf.Config) { + return func(cfg *sf.Config) { + cfg.Schema = name + } +} + +// GetDSN constructs a DSN based on the test connection parameters +func GetDSN(options ...func(*sf.Config)) (string, *sf.Config, error) { + env := func(k string, failOnMissing bool) string { + if value := os.Getenv(k); value != "" { + return value + } + if failOnMissing { + log.Fatalf("%v environment variable is not set.", k) + } + return "" + } + + account := env("SNOWFLAKE_ACCOUNT", true) + user := env("SNOWFLAKE_USER", true) + password := env("SNOWFLAKE_PASSWORD", true) + host := env("SNOWFLAKE_HOST", false) + portStr := env("SNOWFLAKE_PORT", false) + protocol := env("SNOWFLAKE_PROTOCOL", false) + + port := 443 // snowflake default port + var err error + if len(portStr) > 0 { + port, err = strconv.Atoi(portStr) + if err != nil { + return "", nil, err + } + } + + cfg := &sf.Config{ + Account: account, + User: user, + Password: password, + Host: host, + Port: port, + Protocol: protocol, + } + + for _, fn := range options { + fn(cfg) + } + + dsn, err := sf.DSN(cfg) + return dsn, cfg, err +} diff --git a/snowflake/pkg/snowflake/snowflake.go b/snowflake/pkg/snowflake/snowflake.go new file mode 100644 index 000000000..014c74c2b --- /dev/null +++ b/snowflake/pkg/snowflake/snowflake.go @@ -0,0 +1,103 @@ +// Copyright 2022 Antrea Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package snowflake + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "github.com/go-logr/logr" +) + +type WarehouseSizeType string + +type ScalingPolicyType string + +const ( + ScalingPolicyStandard ScalingPolicyType = "STANDARD" + ScalingPolicyEconomy ScalingPolicyType = "ECONOMY" +) + +type WarehouseConfig struct { + Size *WarehouseSizeType + MinClusterCount *int32 + MaxClusterCount *int32 + ScalingPolicy *ScalingPolicyType + AutoSuspend *int32 + InitiallySuspended *bool +} + +type Client interface { + CreateWarehouse(ctx context.Context, name string, config WarehouseConfig) error + UseWarehouse(ctx context.Context, name string) error + DropWarehouse(ctx context.Context, name string) error +} + +type client struct { + db *sql.DB + logger logr.Logger +} + +func NewClient(db *sql.DB, logger logr.Logger) *client { + return &client{ + db: db, + logger: logger, + } +} + +func (c *client) CreateWarehouse(ctx context.Context, name string, config WarehouseConfig) error { + query := fmt.Sprintf("CREATE WAREHOUSE %s", name) + properties := make([]string, 0) + if config.Size != nil { + properties = append(properties, fmt.Sprintf("WAREHOUSE_SIZE = %s", *config.Size)) + } + if config.MinClusterCount != nil { + properties = append(properties, fmt.Sprintf("MIN_CLUSTER_COUNT = %d", *config.MinClusterCount)) + } + if config.MaxClusterCount != nil { + properties = append(properties, fmt.Sprintf("MAX_CLUSTER_COUNT = %d", *config.MaxClusterCount)) + } + if config.ScalingPolicy != nil { + properties = append(properties, fmt.Sprintf("SCALING_POLICY = %s", *config.ScalingPolicy)) + } + if config.AutoSuspend != nil { + properties = append(properties, fmt.Sprintf("AUTO_SUSPEND = %d", *config.AutoSuspend)) + } + if config.InitiallySuspended != nil { + properties = append(properties, fmt.Sprintf("INITIALLY_SUSPENDED = %t", *config.InitiallySuspended)) + } + if len(properties) > 0 { + query += " WITH " + strings.Join(properties, " ") + } + c.logger.V(2).Info("Snowflake query", "query", query) + _, err := c.db.ExecContext(ctx, query) + return err +} + +func (c *client) UseWarehouse(ctx context.Context, name string) error { + query := fmt.Sprintf("USE WAREHOUSE %s", name) + c.logger.V(2).Info("Snowflake query", "query", query) + _, err := c.db.ExecContext(ctx, query) + return err +} + +func (c *client) DropWarehouse(ctx context.Context, name string) error { + query := fmt.Sprintf("DROP WAREHOUSE IF EXISTS %s", name) + c.logger.V(2).Info("Snowflake query", "query", query) + _, err := c.db.ExecContext(ctx, query) + return err +}