diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 41ce00d103..ca1ca18d50 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -32,6 +32,7 @@ jobs: component: - datacatalog - flyteadmin + - flyteartifacts # TODO(monorepo): Enable lint flytecopilot # - flytecopilot - flyteidl @@ -69,6 +70,7 @@ jobs: component: - datacatalog - flyteadmin + - flyteartifacts - flytecopilot - flytepropeller name: Docker Build Images @@ -112,6 +114,7 @@ jobs: component: - datacatalog - flyteadmin + - flyteartifacts - flytecopilot - flytepropeller uses: ./.github/workflows/go_generate.yml diff --git a/Dockerfile.flyteartifacts b/Dockerfile.flyteartifacts new file mode 100644 index 0000000000..51303aad69 --- /dev/null +++ b/Dockerfile.flyteartifacts @@ -0,0 +1,42 @@ +FROM --platform=${BUILDPLATFORM} golang:1.19-alpine3.16 as builder + +ARG TARGETARCH +ENV GOARCH "${TARGETARCH}" +ENV GOOS linux + +RUN apk add git openssh-client make curl + +# Create the artifacts directory +RUN mkdir /artifacts + +WORKDIR /go/src/github.com/flyteorg/flyte/flyteartifacts/ + +COPY datacatalog ../datacatalog +COPY flyteadmin ../flyteadmin +COPY flyteartifacts . +COPY flytecopilot ../flytecopilot +COPY flyteidl ../flyteidl +COPY flyteplugins ../flyteplugins +COPY flytepropeller ../flytepropeller +COPY flytestdlib ../flytestdlib + +# This 'linux_compile' target should compile binaries to the /artifacts directory +# The main entrypoint should be compiled to /artifacts/flyteadmin +RUN make linux_compile + +# update the PATH to include the /artifacts directory +ENV PATH="/artifacts:${PATH}" + +# This will eventually move to centurylink/ca-certs:latest for minimum possible image size +FROM alpine:3.16 +LABEL org.opencontainers.image.source https://github.com/flyteorg/flyte/ + +COPY --from=builder /artifacts /bin + +# Ensure the latest CA certs are present to authenticate SSL connections. +RUN apk --update add ca-certificates + +RUN addgroup -S flyte && adduser -S flyte -G flyte +USER flyte + +CMD ["artifacts"] diff --git a/Dockerfile.sandbox-lite b/Dockerfile.sandbox-lite index 095c83b6e1..e5cc60eaaf 100644 --- a/Dockerfile.sandbox-lite +++ b/Dockerfile.sandbox-lite @@ -18,6 +18,7 @@ WORKDIR /app/flyte COPY datacatalog datacatalog COPY flyteadmin flyteadmin +COPY flyteartifacts flyteartifacts COPY flytecopilot flytecopilot COPY flyteidl flyteidl COPY flyteplugins flyteplugins diff --git a/charts/flyte-sandbox/README.md b/charts/flyte-sandbox/README.md index 6e584f51c2..18362fca2e 100644 --- a/charts/flyte-sandbox/README.md +++ b/charts/flyte-sandbox/README.md @@ -11,6 +11,7 @@ A Helm chart for the Flyte local sandbox | file://../flyte-binary | flyte-binary | v0.1.10 | | https://charts.bitnami.com/bitnami | minio | 12.1.1 | | https://charts.bitnami.com/bitnami | postgresql | 12.1.9 | +| https://charts.bitnami.com/bitnami | redis | 18.0.1 | | https://helm.twun.io/ | docker-registry | 2.2.2 | | https://kubernetes.github.io/dashboard/ | kubernetes-dashboard | 6.0.0 | @@ -100,6 +101,13 @@ A Helm chart for the Flyte local sandbox | postgresql.volumePermissions.enabled | bool | `true` | | | postgresql.volumePermissions.image.pullPolicy | string | `"Never"` | | | postgresql.volumePermissions.image.tag | string | `"sandbox"` | | +| redis.auth.enabled | bool | `false` | | +| redis.enabled | bool | `true` | | +| redis.image.pullPolicy | string | `"Never"` | | +| redis.image.tag | string | `"sandbox"` | | +| redis.master.service.nodePorts.redis | int | `30004` | | +| redis.master.service.type | string | `"NodePort"` | | +| redis.replica.replicaCount | int | `0` | | | sandbox.buildkit.enabled | bool | `true` | | | sandbox.buildkit.image.pullPolicy | string | `"Never"` | | | sandbox.buildkit.image.repository | string | `"moby/buildkit"` | | diff --git a/charts/flyte-sandbox/templates/artifacts/deployment.yaml b/charts/flyte-sandbox/templates/artifacts/deployment.yaml new file mode 100644 index 0000000000..99219beec2 --- /dev/null +++ b/charts/flyte-sandbox/templates/artifacts/deployment.yaml @@ -0,0 +1,36 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: artifact-service + name: artifact-service +spec: + replicas: 1 + selector: + matchLabels: + app: artifact-service + template: + metadata: + labels: + app: artifact-service + spec: + containers: + - name: main + image: ghcr.io/unionai/artifacts:sandbox + env: + - name: DATABASE_URL + value: postgresql://postgres:postgres@flyte-sandbox-postgresql.flyte:5432/postgres + - name: REDIS_HOST + value: flyte-sandbox-redis-headless.flyte.svc.cluster.local + - name: REDIS_PORT + value: "6379" + ports: + - name: grpc + containerPort: 50051 + livenessProbe: + initialDelaySeconds: 30 + tcpSocket: + port: grpc + readinessProbe: + tcpSocket: + port: grpc diff --git a/charts/flyte-sandbox/templates/artifacts/service.yaml b/charts/flyte-sandbox/templates/artifacts/service.yaml new file mode 100644 index 0000000000..16d97b8c9f --- /dev/null +++ b/charts/flyte-sandbox/templates/artifacts/service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: artifact-service + labels: + app: artifact-service +spec: + ports: + - name: grpc + port: 50051 + targetPort: 50051 + selector: + app: artifact-service + type: ClusterIP diff --git a/charts/flyte-sandbox/templates/proxy/configmap.yaml b/charts/flyte-sandbox/templates/proxy/configmap.yaml index 362597aa9e..3fecffdb9a 100644 --- a/charts/flyte-sandbox/templates/proxy/configmap.yaml +++ b/charts/flyte-sandbox/templates/proxy/configmap.yaml @@ -108,6 +108,10 @@ data: prefix: "/flyteidl.service.SignalService" route: cluster: flyte_grpc + - match: + prefix: "/flyteidl.artifact.ArtifactRegistry" + route: + cluster: artifact {{- end }} {{- if index .Values "kubernetes-dashboard" "enabled" }} - match: @@ -203,5 +207,19 @@ data: address: {{ .Release.Name }}-minio port_value: 9001 {{- end }} + - name: artifact + connect_timeout: 0.25s + type: STRICT_DNS + lb_policy: ROUND_ROBIN + http2_protocol_options: {} + load_assignment: + cluster_name: artifact + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: artifact-service + port_value: 50051 {{- end }} diff --git a/charts/flyte-sandbox/values.yaml b/charts/flyte-sandbox/values.yaml index 9743bcab33..353d44012e 100644 --- a/charts/flyte-sandbox/values.yaml +++ b/charts/flyte-sandbox/values.yaml @@ -47,6 +47,14 @@ flyte-binary: ephemeralStorage: 0 gpu: 0 memory: 0 + cloudEvents: + enable: true + cloudEventVersion: v2 + type: sandbox + artifacts: + host: localhost + port: 50051 + insecure: true storage: signedURL: stowConfigOverride: diff --git a/cmd/single/config.go b/cmd/single/config.go index adbabe7ae5..9388ec62ce 100644 --- a/cmd/single/config.go +++ b/cmd/single/config.go @@ -13,6 +13,7 @@ type Config struct { Propeller Propeller `json:"propeller" pflag:",Configuration to disable propeller or any of its components."` Admin Admin `json:"admin" pflag:",Configuration to disable FlyteAdmin or any of its components"` DataCatalog DataCatalog `json:"dataCatalog" pflag:",Configuration to disable DataCatalog or any of its components"` + Artifact Artifacts `json:"artifact" pflag:",Configuration to disable Artifact or any of its components"` } type Propeller struct { @@ -20,6 +21,10 @@ type Propeller struct { DisableWebhook bool `json:"disableWebhook" pflag:",Disables webhook only"` } +type Artifacts struct { + Disabled bool `json:"disabled" pflag:",Disables flyteartifacts in the single binary mode"` +} + type Admin struct { Disabled bool `json:"disabled" pflag:",Disables flyteadmin in the single binary mode"` DisableScheduler bool `json:"disableScheduler" pflag:",Disables Native scheduler in the single binary mode"` diff --git a/cmd/single/start.go b/cmd/single/start.go index 8a499dbe92..ef9dd2bc20 100644 --- a/cmd/single/start.go +++ b/cmd/single/start.go @@ -2,6 +2,10 @@ package single import ( "context" + sharedCmd "github.com/flyteorg/flyte/flyteartifacts/cmd/shared" + "github.com/flyteorg/flyte/flyteartifacts/pkg/configuration" + artifactsServer "github.com/flyteorg/flyte/flyteartifacts/pkg/server" + "github.com/flyteorg/flyte/flytestdlib/database" "net/http" metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" ctrlWebhook "sigs.k8s.io/controller-runtime/pkg/webhook" @@ -61,6 +65,40 @@ func startClusterResourceController(ctx context.Context) error { return nil } +func startArtifact(ctx context.Context, cfg Artifacts) error { + if cfg.Disabled { + logger.Infof(ctx, "Artifacts server is disabled. Skipping...") + return nil + } + // Roughly copies main/NewMigrateCmd + logger.Infof(ctx, "Artifacts: running database migrations if any...") + migs := artifactsServer.GetMigrations(ctx) + initializationSql := "create extension if not exists hstore;" + dbConfig := artifactsServer.GetDbConfig() + err := database.Migrate(context.Background(), dbConfig, migs, initializationSql) + if err != nil { + logger.Errorf(ctx, "Failed to run Artifacts database migrations. Error: %v", err) + return err + } + + g, childCtx := errgroup.WithContext(ctx) + + // Rough copy of NewServeCmd + g.Go(func() error { + cfg := configuration.GetApplicationConfig() + serverCfg := &cfg.ArtifactServerConfig + err := sharedCmd.ServeGateway(childCtx, "artifacts", serverCfg, artifactsServer.GrpcRegistrationHook, + artifactsServer.HttpRegistrationHook) + if err != nil { + logger.Errorf(childCtx, "Failed to start Artifacts server. Error: %v", err) + return err + } + return nil + }) + + return g.Wait() +} + func startAdmin(ctx context.Context, cfg Admin) error { logger.Infof(ctx, "Running Database Migrations...") if err := adminServer.Migrate(ctx); err != nil { @@ -202,6 +240,16 @@ var startCmd = &cobra.Command{ }) } + if !cfg.Artifact.Disabled { + g.Go(func() error { + err := startArtifact(childCtx, cfg.Artifact) + if err != nil { + logger.Panicf(childCtx, "Failed to start Artifacts server, err: %v", err) + } + return nil + }) + } + if !cfg.Propeller.Disabled { g.Go(func() error { err := startPropeller(childCtx, cfg.Propeller) diff --git a/docker/sandbox-bundled/Makefile b/docker/sandbox-bundled/Makefile index 9ae4197673..ed92a733c4 100644 --- a/docker/sandbox-bundled/Makefile +++ b/docker/sandbox-bundled/Makefile @@ -1,7 +1,7 @@ define FLYTE_BINARY_BUILD mkdir -p images/tar/$(1) -docker buildx build \ +docker buildx build --ssh default \ --build-arg FLYTECONSOLE_VERSION=$(FLYTECONSOLE_VERSION) \ --platform linux/$(1) \ --tag flyte-binary:sandbox \ @@ -40,9 +40,20 @@ build: flyte manifests --driver docker-container --driver-opt image=moby/buildkit:master \ --buildkitd-flags '--allow-insecure-entitlement security.insecure' \ --platform linux/arm64,linux/amd64 - docker buildx build --builder flyte-sandbox --allow security.insecure --load \ + docker buildx build --ssh default --builder flyte-sandbox --allow security.insecure --load \ --tag flyte-sandbox:latest . +# This is here because we want to be able to push locally, not depend on GH actions +.PHONY: build_push +build_push: flyte manifests + [ -n "$(shell docker buildx ls | awk '/^flyte-sandbox / {print $$1}')" ] || \ + docker buildx create --name flyte-sandbox \ + --driver docker-container --driver-opt image=moby/buildkit:master \ + --buildkitd-flags '--allow-insecure-entitlement security.insecure' \ + --platform linux/arm64,linux/amd64 + docker buildx build --ssh default --builder flyte-sandbox --allow security.insecure --push \ + --tag ghcr.io/flyteorg/flyte-sandbox:a_v0.1.1 . + # Port map # 6443 - k8s API server # 30000 - Docker Registry diff --git a/flyte-single-binary-local.yaml b/flyte-single-binary-local.yaml index eaad8c12e2..deac2fe3c0 100644 --- a/flyte-single-binary-local.yaml +++ b/flyte-single-binary-local.yaml @@ -1,5 +1,7 @@ # This is a sample configuration file for running single-binary Flyte locally against # a sandbox. +# gatepr: revert the local dir to reflect home. +# paths were changed to personal to ensure settings didn't get lost. admin: # This endpoint is used by flytepropeller to talk to admin # and artifacts to talk to admin, @@ -17,7 +19,7 @@ catalog-cache: cluster_resources: standaloneDeployment: false - templatePath: $HOME/.flyte/sandbox/cluster-resource-templates + templatePath: /Users/ytong/.flyte/sandbox/cluster-resource-templates logger: show-source: true @@ -25,14 +27,14 @@ logger: propeller: create-flyteworkflow-crd: true - kube-config: $HOME/.flyte/sandbox/kubeconfig + kube-config: /Users/ytong/.flyte/sandbox/kubeconfig rawoutput-prefix: s3://my-s3-bucket/data server: - kube-config: $HOME/.flyte/sandbox/kubeconfig + kube-config: /Users/ytong/.flyte/sandbox/kubeconfig webhook: - certDir: $HOME/.flyte/webhook-certs + certDir: /Users/ytong/.flyte/webhook-certs localCert: true secretName: flyte-sandbox-webhook-secret serviceName: flyte-sandbox-local @@ -75,6 +77,34 @@ database: port: 30001 dbname: flyte options: "sslmode=disable" + # Point to cloned repo instead. + # +# postgres: +# username: postgres +# password: xxx +# host: 127.0.0.1 +# port: 54328 +# dbname: app +# options: "sslmode=disable" +cloudEvents: + enable: true + cloudEventVersion: v2 + type: sandbox +# For artifact service itself +artifactsServer: + artifactBlobStoreConfig: + type: stow + stow: + kind: s3 + config: + disable_ssl: true + v2_signing: true + endpoint: http://localhost:30002 + auth_type: accesskey + access_key_id: minio + secret_key: miniostorage +artifactsProcessor: + cloudProvider: Sandbox storage: type: stow stow: diff --git a/flyteadmin/flyteadmin_config.yaml b/flyteadmin/flyteadmin_config.yaml index e3d19f7326..6840b406cd 100644 --- a/flyteadmin/flyteadmin_config.yaml +++ b/flyteadmin/flyteadmin_config.yaml @@ -60,6 +60,10 @@ flyteadmin: - "metadata" - "admin" useOffloadedWorkflowClosure: false +artifacts: + host: localhost + port: 50051 + insecure: true database: postgres: port: 30001 @@ -112,6 +116,10 @@ externalEvents: eventsPublisher: topicName: "bar" eventTypes: all +cloudEvents: + enable: true + cloudEventVersion: v2 + type: sandbox Logger: show-source: true level: 5 diff --git a/flyteadmin/pkg/artifacts/config.go b/flyteadmin/pkg/artifacts/config.go new file mode 100644 index 0000000000..c639666ffe --- /dev/null +++ b/flyteadmin/pkg/artifacts/config.go @@ -0,0 +1,8 @@ +package artifacts + +// gatepr: add proper config bits for this +type Config struct { + Host string `json:"host"` + Port int `json:"port"` + Insecure bool `json:"insecure"` +} diff --git a/flyteartifacts/Makefile b/flyteartifacts/Makefile new file mode 100644 index 0000000000..7b5ada6444 --- /dev/null +++ b/flyteartifacts/Makefile @@ -0,0 +1,6 @@ +#TODO delete this comment +.PHONY: linux_compile +linux_compile: export CGO_ENABLED ?= 0 +linux_compile: export GOOS ?= linux +linux_compile: + go build -o /artifacts/flyteadmin -ldflags=$(LD_FLAGS) ./cmd/ diff --git a/flyteartifacts/artifact_config.yaml b/flyteartifacts/artifact_config.yaml new file mode 100644 index 0000000000..c0584c8091 --- /dev/null +++ b/flyteartifacts/artifact_config.yaml @@ -0,0 +1,8 @@ +# This is an (incomplete) configure file for the artifact service, here just as an example. +#artifactsServer: +# database: +# postgres: +# dbname: your pg db +#logger: +# level: 5 +# show-source: true diff --git a/flyteartifacts/cmd/main.go b/flyteartifacts/cmd/main.go new file mode 100644 index 0000000000..afbc88eef6 --- /dev/null +++ b/flyteartifacts/cmd/main.go @@ -0,0 +1,33 @@ +package main + +import ( + "context" + sharedCmd "github.com/flyteorg/flyte/flyteartifacts/cmd/shared" + "github.com/flyteorg/flyte/flyteartifacts/pkg/server" + "github.com/flyteorg/flyte/flytestdlib/contextutils" + "github.com/flyteorg/flyte/flytestdlib/logger" + "github.com/flyteorg/flyte/flytestdlib/promutils/labeled" + "github.com/flyteorg/flyte/flytestdlib/storage" + + _ "net/http/pprof" // Required to serve application. +) + +func main() { + ctx := context.Background() + logger.Infof(ctx, "Beginning Flyte Artifacts Service") + rootCmd := sharedCmd.NewRootCmd("artifacts", server.GrpcRegistrationHook, server.HttpRegistrationHook) + migs := server.GetMigrations(ctx) + initializationSql := "create extension if not exists hstore;" + dbConfig := server.GetDbConfig() + rootCmd.AddCommand(sharedCmd.NewMigrateCmd(migs, dbConfig, initializationSql)) + err := rootCmd.ExecuteContext(ctx) + if err != nil { + panic(err) + } +} + +func init() { + // Set Keys + labeled.SetMetricKeys(contextutils.AppNameKey, contextutils.ProjectKey, + contextutils.DomainKey, storage.FailureTypeLabel) +} diff --git a/flyteartifacts/cmd/shared/migrate.go b/flyteartifacts/cmd/shared/migrate.go new file mode 100644 index 0000000000..843f6e881f --- /dev/null +++ b/flyteartifacts/cmd/shared/migrate.go @@ -0,0 +1,19 @@ +package shared + +import ( + "context" + "github.com/flyteorg/flyte/flytestdlib/database" + "github.com/go-gormigrate/gormigrate/v2" + "github.com/spf13/cobra" +) + +// NewMigrateCmd represents the migrate command +func NewMigrateCmd(migs []*gormigrate.Migration, databaseConfig *database.DbConfig, initializationSql string) *cobra.Command { + return &cobra.Command{ + Use: "migrate", + Short: "This command will run all the migrations for the database", + RunE: func(cmd *cobra.Command, args []string) error { + return database.Migrate(context.Background(), databaseConfig, migs, initializationSql) + }, + } +} diff --git a/flyteartifacts/cmd/shared/root.go b/flyteartifacts/cmd/shared/root.go new file mode 100644 index 0000000000..8bc0390c30 --- /dev/null +++ b/flyteartifacts/cmd/shared/root.go @@ -0,0 +1,91 @@ +package shared + +import ( + "context" + "flag" + "fmt" + "github.com/flyteorg/flyte/flyteartifacts/pkg/configuration" + "github.com/flyteorg/flyte/flytestdlib/config" + "github.com/flyteorg/flyte/flytestdlib/config/viper" + "github.com/flyteorg/flyte/flytestdlib/logger" + "github.com/flyteorg/flyte/flytestdlib/profutils" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "os" +) + +var ( + cfgFile string + configAccessor = viper.NewAccessor(config.Options{}) +) + +// NewRootCmd represents the base command when called without any subcommands +func NewRootCmd(rootUse string, grpcHook GrpcRegistrationHook, httpHook HttpRegistrationHook) *cobra.Command { + rootCmd := &cobra.Command{ + Use: rootUse, + Short: "Short description", + Long: "Long description to be filled in later", + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + err := initConfig(cmd, args) + if err != nil { + return err + } + + go func() { + ctx := context.Background() + metricsCfg := configuration.GetApplicationConfig().ArtifactServerConfig.Metrics + err := profutils.StartProfilingServerWithDefaultHandlers(ctx, + metricsCfg.Port.Port, nil) + if err != nil { + logger.Panicf(ctx, "Failed to Start profiling and metrics server. Error: %v", err) + } + }() + + return nil + }, + } + + initSubCommands(rootCmd, grpcHook, httpHook) + return rootCmd +} + +func initConfig(cmd *cobra.Command, _ []string) error { + configAccessor = viper.NewAccessor(config.Options{ + SearchPaths: []string{cfgFile, ".", "/etc/flyte/config", "$GOPATH/src/github.com/flyteorg/flyte/flyteartifacts"}, + StrictMode: false, + }) + + fmt.Println("Using config file: ", configAccessor.ConfigFilesUsed()) + + // persistent flags were initially bound to the root command so we must bind to the same command to avoid + // overriding those initial ones. We need to traverse up to the root command and initialize pflags for that. + rootCmd := cmd + for rootCmd.Parent() != nil { + rootCmd = rootCmd.Parent() + } + + configAccessor.InitializePflags(rootCmd.PersistentFlags()) + + return configAccessor.UpdateConfig(context.TODO()) +} + +func initSubCommands(rootCmd *cobra.Command, grpcHook GrpcRegistrationHook, httpHook HttpRegistrationHook) { + // allows ` --logtostderr` to work + pflag.CommandLine.AddGoFlagSet(flag.CommandLine) + + // Add persistent flags - persistent flags persist through all sub-commands + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "artifact_config.yaml", "config file (default is ./artifact_config.yaml)") + + rootCmd.AddCommand(viper.GetConfigCommand()) + cfg := configuration.GetApplicationConfig() + rootCmd.AddCommand(NewServeCmd(rootCmd.Use, cfg.ArtifactServerConfig, grpcHook, httpHook)) + + // Allow viper to read the value of the flags + configAccessor.InitializePflags(rootCmd.PersistentFlags()) + + err := flag.CommandLine.Parse([]string{}) + if err != nil { + fmt.Println(err) + os.Exit(-1) + } +} diff --git a/flyteartifacts/cmd/shared/serve.go b/flyteartifacts/cmd/shared/serve.go new file mode 100644 index 0000000000..9a3a3e8411 --- /dev/null +++ b/flyteartifacts/cmd/shared/serve.go @@ -0,0 +1,211 @@ +package shared + +import ( + "context" + "google.golang.org/grpc/reflection" + "net" + "net/http" + + sharedCfg "github.com/flyteorg/flyte/flyteartifacts/pkg/configuration/shared" + "github.com/flyteorg/flyte/flytestdlib/logger" + "github.com/flyteorg/flyte/flytestdlib/promutils" + grpcMiddleware "github.com/grpc-ecosystem/go-grpc-middleware" + grpcPrometheus "github.com/grpc-ecosystem/go-grpc-prometheus" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/health" + "google.golang.org/grpc/health/grpc_health_v1" +) + +func NewServeCmd(commandName string, serverCfg sharedCfg.ServerConfiguration, grpcHook GrpcRegistrationHook, httpHook HttpRegistrationHook) *cobra.Command { + // serveCmd represents the serve command + return &cobra.Command{ + Use: "serve", + Short: "Launches the server", + RunE: func(cmd *cobra.Command, args []string) error { + ctx := context.Background() + return ServeGateway(ctx, commandName, &serverCfg, grpcHook, httpHook) + }, + } +} + +// Creates a new gRPC Server with all the configuration +func newGRPCServer(ctx context.Context, serviceName string, serverCfg *sharedCfg.ServerConfiguration, + grpcHook GrpcRegistrationHook, opts ...grpc.ServerOption) (*grpc.Server, error) { + + scope := promutils.NewScope(serverCfg.Metrics.MetricsScope) + scope = scope.NewSubScope(serviceName) + + var grpcUnaryInterceptors = make([]grpc.UnaryServerInterceptor, 0) + var streamServerInterceptors = make([]grpc.StreamServerInterceptor, 0) + + serverOpts := []grpc.ServerOption{ + grpc.StreamInterceptor(grpcPrometheus.StreamServerInterceptor), + grpc.UnaryInterceptor(grpcMiddleware.ChainUnaryServer( + grpcUnaryInterceptors..., + )), + grpc.ChainStreamInterceptor( + streamServerInterceptors..., + ), + } + + serverOpts = append(serverOpts, opts...) + + grpcServer := grpc.NewServer(serverOpts...) + grpcPrometheus.Register(grpcServer) + if grpcHook != nil { + err := grpcHook(ctx, grpcServer, scope) + if err != nil { + return nil, err + } + } + + healthServer := health.NewServer() + healthServer.SetServingStatus(serviceName, grpc_health_v1.HealthCheckResponse_SERVING) + grpc_health_v1.RegisterHealthServer(grpcServer, healthServer) + if serverCfg.GrpcServerReflection { + reflection.Register(grpcServer) + } + + return grpcServer, nil +} + +// ServeGateway launches the grpc and http servers. +func ServeGateway(ctx context.Context, serviceName string, serverCfg *sharedCfg.ServerConfiguration, grpcHook GrpcRegistrationHook, httpHook HttpRegistrationHook) error { + + if grpcHook != nil { + if err := launchGrpcServer(ctx, serviceName, serverCfg, grpcHook); err != nil { + return err + } + } + + if httpHook != nil { + if err := launchHttpServer(ctx, serverCfg, httpHook); err != nil { + return err + } + } + + for { + <-ctx.Done() + return nil + } +} + +// launchGrpcServer launches grpc server with server config and also registers the grpc hook for the service. +func launchGrpcServer(ctx context.Context, serviceName string, serverCfg *sharedCfg.ServerConfiguration, grpcHook GrpcRegistrationHook) error { + var grpcServer *grpc.Server + if serverCfg.Security.Secure { + tlsCreds, err := credentials.NewServerTLSFromFile(serverCfg.Security.Ssl.CertificateFile, serverCfg.Security.Ssl.KeyFile) + if err != nil { + return err + } + + grpcServer, err = newGRPCServer(ctx, serviceName, serverCfg, grpcHook, grpc.Creds(tlsCreds)) + if err != nil { + return errors.Wrap(err, "failed to create secure GRPC server") + } + } else { + var err error + grpcServer, err = newGRPCServer(ctx, serviceName, serverCfg, grpcHook) + if err != nil { + return errors.Wrap(err, "failed to create GRPC server") + } + } + + return listenAndServe(ctx, grpcServer, serverCfg.GetGrpcHostAddress()) +} + +// listenAndServe on the grpcHost address and serve connections using the grpcServer +func listenAndServe(ctx context.Context, grpcServer *grpc.Server, grpcHost string) error { + conn, err := net.Listen("tcp", grpcHost) + if err != nil { + panic(err) + } + + go func() { + if err := grpcServer.Serve(conn); err != nil { + logger.Fatalf(ctx, "Failed to create GRPC Server, Err: ", err) + } + + logger.Infof(ctx, "Serving GRPC Gateway server on: %v", grpcHost) + }() + return nil +} + +// launchHttpServer launches an http server for converting REST calls to grpc internally +func launchHttpServer(ctx context.Context, cfg *sharedCfg.ServerConfiguration, httpHook HttpRegistrationHook) error { + logger.Infof(ctx, "Starting HTTP/1 Gateway server on %s", cfg.GetHttpHostAddress()) + + httpServer, serverMux, err := newHTTPServer(cfg.Security) + if err != nil { + return err + } + + if err = registerHttpHook(ctx, serverMux, cfg.GetGrpcHostAddress(), uint32(cfg.GrpcMaxResponseStatusBytes), httpHook, promutils.NewScope(cfg.Metrics.MetricsScope)); err != nil { + return err + } + + handler := getHTTPHandler(httpServer, cfg.Security) + + go func() { + err = http.ListenAndServe(cfg.GetHttpHostAddress(), handler) + if err != nil { + logger.Fatalf(ctx, "Failed to Start HTTP Server, Err: %v", err) + } + + logger.Infof(ctx, "Serving HTTP/1 on: %v", cfg.GetHttpHostAddress()) + }() + return nil +} + +// getHTTPHandler gets the http handler for the configured security options +func getHTTPHandler(httpServer *http.ServeMux, _ sharedCfg.ServerSecurityOptions) http.Handler { + // not really used yet (reserved for admin) + var handler http.Handler + handler = httpServer + return handler +} + +// registerHttpHook registers the http hook for a service which multiplexes to the grpc endpoint for that service. +func registerHttpHook(ctx context.Context, gwmux *runtime.ServeMux, grpcHost string, maxResponseStatusBytes uint32, httpHook HttpRegistrationHook, scope promutils.Scope) error { + if httpHook == nil { + return nil + } + grpcOptions := []grpc.DialOption{ + grpc.WithInsecure(), + grpc.WithMaxHeaderListSize(maxResponseStatusBytes), + } + + return httpHook(ctx, gwmux, grpcHost, grpcOptions, scope) +} + +func healthCheckFunc(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) +} + +// newHTTPServer creates a new http sever +func newHTTPServer(_ sharedCfg.ServerSecurityOptions) (*http.ServeMux, *runtime.ServeMux, error) { + // Register the server that will serve HTTP/REST Traffic + mux := http.NewServeMux() + + // Register healthcheck + mux.HandleFunc("/healthcheck", healthCheckFunc) + + var gwmuxOptions = make([]runtime.ServeMuxOption, 0) + // This option means that http requests are served with protobufs, instead of json. We always want this. + gwmuxOptions = append(gwmuxOptions, runtime.WithMarshalerOption("application/octet-stream", &runtime.ProtoMarshaller{})) + + // Create the grpc-gateway server with the options specified + gwmux := runtime.NewServeMux(gwmuxOptions...) + + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + logger.Debugf(ctx, "Running identity interceptor for http endpoint [%s]", r.URL.String()) + + gwmux.ServeHTTP(w, r.WithContext(ctx)) + }) + return mux, gwmux, nil +} diff --git a/flyteartifacts/cmd/shared/types.go b/flyteartifacts/cmd/shared/types.go new file mode 100644 index 0000000000..be1878a6d3 --- /dev/null +++ b/flyteartifacts/cmd/shared/types.go @@ -0,0 +1,11 @@ +package shared + +import ( + "context" + "github.com/flyteorg/flyte/flytestdlib/promutils" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "google.golang.org/grpc" +) + +type GrpcRegistrationHook func(ctx context.Context, server *grpc.Server, scope promutils.Scope) error +type HttpRegistrationHook func(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption, scope promutils.Scope) error diff --git a/flyteartifacts/go.mod b/flyteartifacts/go.mod new file mode 100644 index 0000000000..1fe874539f --- /dev/null +++ b/flyteartifacts/go.mod @@ -0,0 +1,125 @@ +module github.com/flyteorg/flyte/flyteartifacts + +go 1.19 + +require ( + github.com/DATA-DOG/go-sqlmock v1.5.0 + github.com/NYTimes/gizmo v1.3.6 + github.com/cloudevents/sdk-go/binding/format/protobuf/v2 v2.14.0 + github.com/cloudevents/sdk-go/v2 v2.14.0 + github.com/flyteorg/flyte/flyteidl v0.0.0-00010101000000-000000000000 + github.com/flyteorg/flyte/flytestdlib v1.9.5 + github.com/go-gormigrate/gormigrate/v2 v2.1.1 + github.com/golang/protobuf v1.5.3 + github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 + github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 + github.com/grpc-ecosystem/grpc-gateway v1.16.0 + github.com/jackc/pgx/v5 v5.4.3 + github.com/pkg/errors v0.9.1 + github.com/prometheus/client_golang v1.12.1 + github.com/spf13/cobra v1.4.0 + github.com/spf13/pflag v1.0.5 + github.com/stretchr/testify v1.8.4 + google.golang.org/grpc v1.56.1 + google.golang.org/protobuf v1.30.0 + gorm.io/driver/postgres v1.5.3 + gorm.io/gorm v1.25.5 +) + +require ( + cloud.google.com/go v0.110.0 // indirect + cloud.google.com/go/compute v1.19.1 // indirect + cloud.google.com/go/compute/metadata v0.2.3 // indirect + cloud.google.com/go/iam v0.13.0 // indirect + cloud.google.com/go/storage v1.28.1 // indirect + github.com/Azure/azure-sdk-for-go v63.4.0+incompatible // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v0.23.1 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v0.9.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.4.0 // indirect + github.com/Azure/go-autorest v14.2.0+incompatible // indirect + github.com/Azure/go-autorest/autorest v0.11.27 // indirect + github.com/Azure/go-autorest/autorest/adal v0.9.18 // indirect + github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect + github.com/Azure/go-autorest/logger v0.2.1 // indirect + github.com/Azure/go-autorest/tracing v0.6.0 // indirect + github.com/aws/aws-sdk-go v1.44.2 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737 // indirect + github.com/cespare/xxhash v1.1.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/coocood/freecache v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/fatih/color v1.13.0 // indirect + github.com/flyteorg/stow v0.3.7 // indirect + github.com/fsnotify/fsnotify v1.5.1 // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/go-logr/logr v1.2.3 // indirect + github.com/gofrs/uuid v4.2.0+incompatible // indirect + github.com/golang-jwt/jwt/v4 v4.4.1 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect + github.com/googleapis/gax-go/v2 v2.7.1 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.14.1 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.2 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kelseyhightower/envconfig v1.4.0 // indirect + github.com/magiconair/properties v1.8.6 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/ncw/swift v1.0.53 // indirect + github.com/pelletier/go-toml v1.9.4 // indirect + github.com/pelletier/go-toml/v2 v2.0.0-beta.8 // indirect + github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.32.1 // indirect + github.com/prometheus/procfs v0.7.3 // indirect + github.com/sirupsen/logrus v1.8.1 // indirect + github.com/spf13/afero v1.9.2 // indirect + github.com/spf13/cast v1.4.1 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/viper v1.11.0 // indirect + github.com/stretchr/objx v0.5.0 // indirect + github.com/subosito/gotenv v1.2.0 // indirect + go.opencensus.io v0.24.0 // indirect + golang.org/x/crypto v0.13.0 // indirect + golang.org/x/net v0.15.0 // indirect + golang.org/x/oauth2 v0.7.0 // indirect + golang.org/x/sys v0.12.0 // indirect + golang.org/x/text v0.13.0 // indirect + golang.org/x/time v0.3.0 // indirect + golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect + google.golang.org/api v0.114.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect + gopkg.in/ini.v1 v1.66.4 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/driver/sqlite v1.5.4 // indirect + k8s.io/apimachinery v0.24.1 // indirect + k8s.io/client-go v0.24.1 // indirect + k8s.io/klog/v2 v2.90.1 // indirect + k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect +) + +replace ( + github.com/flyteorg/flyte/flyteidl => ../flyteidl + github.com/flyteorg/flyte/flytestdlib => ../flytestdlib + github.com/robfig/cron/v3 => github.com/unionai/cron/v3 v3.0.2-0.20220915080349-5790c370e63a +) diff --git a/flyteartifacts/go.sum b/flyteartifacts/go.sum new file mode 100644 index 0000000000..830d361481 --- /dev/null +++ b/flyteartifacts/go.sum @@ -0,0 +1,1055 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.43.0/go.mod h1:BOSR3VbTLkk6FDC/TcffxP4NF/FFBGA5ku+jvKOP7pg= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= +cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY= +cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= +cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= +cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/iam v0.13.0 h1:+CmB+K0J/33d0zSQ9SlFWUeCCEn5XJA0ZMZ3pHE9u8k= +cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= +cloud.google.com/go/logging v1.0.0/go.mod h1:V1cc3ogwobYzQq5f2R7DS/GvRIrI4FKj01Gs5glwAls= +cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +cloud.google.com/go/storage v1.28.1 h1:F5QDG5ChchaAVQhINh24U99OWHURqrW8OmQcGKXcbgI= +cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= +contrib.go.opencensus.io/exporter/stackdriver v0.13.1/go.mod h1:z2tyTZtPmQ2HvWH4cOmVDgtY+1lomfKdbLnkJvZdc8c= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/azure-sdk-for-go v63.4.0+incompatible h1:fle3M5Q7vr8auaiPffKyUQmLbvYeqpw30bKU6PrWJFo= +github.com/Azure/azure-sdk-for-go v63.4.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= +github.com/Azure/azure-sdk-for-go/sdk/azcore v0.23.1 h1:3CVsSo4mp8NDWO11tHzN/mdo2zP0CtaSK5IcwBjfqRA= +github.com/Azure/azure-sdk-for-go/sdk/azcore v0.23.1/go.mod h1:w5pDIZuawUmY3Bj4tVx3Xb8KS96ToB0j315w9rqpAg0= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.14.0 h1:NVS/4LOQfkBpk+B1VopIzv1ptmYeEskA8w/3K/w7vjo= +github.com/Azure/azure-sdk-for-go/sdk/internal v0.9.2 h1:Px2KVERcYEg2Lv25AqC2hVr0xUWaq94wuEObLIkYzmA= +github.com/Azure/azure-sdk-for-go/sdk/internal v0.9.2/go.mod h1:CdSJQNNzZhCkwDaV27XV1w48ZBPtxe7mlrZAsPNxD5g= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.4.0 h1:0nJeKDmB7a1a8RDMjTltahlPsaNlWjq/LpkZleSwINk= +github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.4.0/go.mod h1:mbwxKc/fW+IkF0GG591MuXw0KuEQBDkeRoZ9vmVJPxg= +github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= +github.com/Azure/go-autorest/autorest v0.11.27 h1:F3R3q42aWytozkV8ihzcgMO4OA4cuqr3bNlsEuF6//A= +github.com/Azure/go-autorest/autorest v0.11.27/go.mod h1:7l8ybrIdUmGqZMTD0sRtAr8NvbHjfofbf8RSP2q7w7U= +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.18 h1:kLnPsRjzZZUF3K5REu/Kc+qMQrvuza2bwSnNdhmzLfQ= +github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= +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/autorest/mocks v0.4.2 h1:PGN4EDXnuQbojHbU0UWoNvmu9AGVwYHG9/fkDYhtAfw= +github.com/Azure/go-autorest/autorest/mocks v0.4.2/go.mod h1:Vy7OitM9Kei0i1Oj+LvyAWMXJHeKH1MVlzFugfVrmyU= +github.com/Azure/go-autorest/autorest/to v0.4.0 h1:oXVqrxakqqV1UZdSazDOPOLvOIz+XA683u8EctwboHk= +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/AzureAD/microsoft-authentication-library-for-go v0.4.0 h1:WVsrXCnHlDDX8ls+tootqRE87/hL9S/g4ewig9RsD/c= +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/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/DataDog/datadog-go v3.4.1+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/opencensus-go-exporter-datadog v0.0.0-20191210083620-6965a1cfed68/go.mod h1:gMGUEe16aZh0QN941HgDjwrdjU4iTthPoz2/AtDRADE= +github.com/NYTimes/gizmo v1.3.6 h1:K+GRagPdAxojsT1TlTQlMkTeOmgfLxSdvuOhdki7GG0= +github.com/NYTimes/gizmo v1.3.6/go.mod h1:8S8QVnITA40p/1jGsUMcPI8R9SSKkoKu+8WF13s9Uhw= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/NYTimes/logrotate v1.0.0/go.mod h1:GxNz1cSw1c6t99PXoZlw+nm90H6cyQyrH66pjVv7x88= +github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/Shopify/sarama v1.26.4/go.mod h1:NbSGBSSndYaIhRcBtY9V0U7AyH+x71bG668AuWys/yU= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go v1.31.3/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go v1.44.2 h1:5VBk5r06bgxgRKVaUtm1/4NT/rtrnH2E4cnAYv5zgQc= +github.com/aws/aws-sdk-go v1.44.2/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737 h1:rRISKWyXfVxvoa702s91Zl5oREZTrR3yv+tXrrX7G/g= +github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927 h1:SKI1/fuSdodxmNNyVBR8d7X/HuLnRpvvFO0AgyQk764= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudevents/sdk-go/binding/format/protobuf/v2 v2.14.0 h1:dEopBSOSjB5fM9r76ufM44AVj9Dnz2IOM0Xs6FVxZRM= +github.com/cloudevents/sdk-go/binding/format/protobuf/v2 v2.14.0/go.mod h1:qDSbb0fgIfFNjZrNTPtS5MOMScAGyQtn1KlSvoOdqYw= +github.com/cloudevents/sdk-go/v2 v2.14.0 h1:Nrob4FwVgi5L4tV9lhjzZcjYqFVyJzsA56CwPaPfv6s= +github.com/cloudevents/sdk-go/v2 v2.14.0/go.mod h1:xDmKfzNjM8gBvjaF8ijFjM1VYOVUEeUfapHMUX1T5To= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coocood/freecache v1.1.1 h1:uukNF7QKCZEdZ9gAV7WQzvh0SbjwdMF6m3x3rxEkaPc= +github.com/coocood/freecache v1.1.1/go.mod h1:OKrEjkGVoxZhyWAJoeFi5BMLUJm2Tit0kpGkIr7NGYY= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +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/dnaeon/go-vcr v1.1.0 h1:ReYa/UBrRyQdant9B4fNHGoCNKw6qh6P0fsdGmZpR7c= +github.com/dnaeon/go-vcr v1.1.0/go.mod h1:M7tiix8f0r6mKKJ3Yq/kqU1OYf3MnfmBWVbPx/yU9ko= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/flyteorg/stow v0.3.7 h1:Cx7j8/Ux6+toD5hp5fy++927V+yAcAttDeQAlUD/864= +github.com/flyteorg/stow v0.3.7/go.mod h1:5dfBitPM004dwaZdoVylVjxFT4GWAgI0ghAndhNUzCo= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gormigrate/gormigrate/v2 v2.1.1 h1:eGS0WTFRV30r103lU8JNXY27KbviRnqqIDobW3EV3iY= +github.com/go-gormigrate/gormigrate/v2 v2.1.1/go.mod h1:L7nJ620PFDKei9QOhJzqA8kRCk+E3UbV2f5gv+1ndLc= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v1.2.0/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-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gofrs/uuid v4.2.0+incompatible h1:yyYWMnhkhrKwwr8gAOcOCYxOOscHgDS9yZgBrnJfGa0= +github.com/gofrs/uuid v4.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt v3.2.1+incompatible h1:73Z+4BJcrTC+KczS6WvTPvRGOp1WmfEP4Q1lOd9Z/+c= +github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ= +github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= +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.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= +github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6cvPr8A= +github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware v1.2.0/go.mod h1:mJzapYve32yjrKlk9GbyCZHuPgZsrbyIbyKhSzOpg6s= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= +github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.14.1 h1:smbxIaZA08n6YuxEX1sDyjV/qkbtUtkH20qLkR9MUR4= +github.com/jackc/pgconn v1.14.1/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= +github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY= +github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= +github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +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/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8= +github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +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.9.8/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= +github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= +github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/ncw/swift v1.0.53 h1:luHjjTNtekIEvHg5KdAFIBaH7bWfNkefwFnpDffSIks= +github.com/ncw/swift v1.0.53/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM= +github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.0.0-beta.8 h1:dy81yyLYJDwMTifq24Oi/IslOslRrDSb3jwDggjz3Z0= +github.com/pelletier/go-toml/v2 v2.0.0-beta.8/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU= +github.com/pierrec/lz4 v2.4.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4 h1:Qj1ukM4GlMWXNdMBuXcXfz/Kw9s1qm0CLY32QxuSImI= +github.com/pkg/browser v0.0.0-20210115035449-ce105d075bb4/go.mod h1:N6UoU20jOqggOuDwUaBQpluzLNDqif3kq9z2wpdYEfQ= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +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_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.4/go.mod h1:oCXIBxdI62A4cR6aTRJCgetEjecSIYzOEaeAn4iYEpM= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= +github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= +github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.9.2 h1:j49Hj62F0n+DaZ1dDCvhABaPNSGNkt32oRFxI33IEMw= +github.com/spf13/afero v1.9.2/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA= +github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q= +github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +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/spf13/viper v1.11.0 h1:7OX/1FS6n7jHD1zGrZTM7WtY13ZELRyosK4k93oPr44= +github.com/spf13/viper v1.11.0/go.mod h1:djo0X/bA5+tYVoCn+C7cAYJGcVn/qYLFTG8gdUsX7Zk= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +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/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.1/go.mod h1:Ap50jQcDJrx6rB6VgeeFPtuPIf3wMRvRfrfYDO6+BmA= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +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/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +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.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.18.1 h1:CSUJ2mjFszzEWt4CdKISEuChVIXGBn3lAPwkRGyVrc4= +go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/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-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200204104054-c9f3fb736b72/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +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-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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-20190222072716-a9d3bda3a223/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-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/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-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/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-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/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-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191010075000-0337d82405ff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= +golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.10.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.25.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.114.0 h1:1xQPji6cO2E2vLiI+C/XiFAnsn1WV3mjaEwGLhi3grE= +google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190708153700-3bdd9d9f5532/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.56.1 h1:z0dNfjIl0VpaZ9iSVjA6daGatAYwPGstTjt5vkRMFkQ= +google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/DataDog/dd-trace-go.v1 v1.22.0/go.mod h1:DVp8HmDh8PuTu2Z0fVVlBsyWaC++fzwVCaGWylTe3tg= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4= +gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/jcmturner/aescts.v1 v1.0.1/go.mod h1:nsR8qBOg+OucoIW+WMhB3GspUQXq9XorLnQb9XtvcOo= +gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q= +gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4= +gopkg.in/jcmturner/gokrb5.v7 v7.5.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM= +gopkg.in/jcmturner/rpc.v1 v1.1.0/go.mod h1:YIdkC4XfD6GXbzje11McwsDuOlZQSb9W4vfLvuNnlv8= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +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.5/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-20200615113413-eeeca48fe776/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= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/postgres v1.5.3 h1:qKGY5CPHOuj47K/VxbCXJfFvIUeqMSXXadqdCY+MbBU= +gorm.io/driver/postgres v1.5.3/go.mod h1:F+LtvlFhZT7UBiA81mC9W6Su3D4WUhSboc/36QZU0gk= +gorm.io/driver/sqlite v1.5.4 h1:IqXwXi8M/ZlPzH/947tn5uik3aYQslP9BVveoax0nV0= +gorm.io/driver/sqlite v1.5.4/go.mod h1:qxAuCol+2r6PannQDpOP1FP6ag3mKi4esLnB/jHed+4= +gorm.io/gorm v1.25.5 h1:zR9lOiiYf09VNh5Q1gphfyia1JpiClIWG9hQaxB/mls= +gorm.io/gorm v1.25.5/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.24.1/go.mod h1:JhoOvNiLXKTPQ60zh2g0ewpA+bnEYf5q44Flhquh4vQ= +k8s.io/apimachinery v0.24.1 h1:ShD4aDxTQKN5zNf8K1RQ2u98ELLdIW7jEnlO9uAMX/I= +k8s.io/apimachinery v0.24.1/go.mod h1:82Bi4sCzVBdpYjyI4jY6aHX+YCUchUIrZrXKedjd2UM= +k8s.io/client-go v0.24.1 h1:w1hNdI9PFrzu3OlovVeTnf4oHDt+FJLd9Ndluvnb42E= +k8s.io/client-go v0.24.1/go.mod h1:f1kIDqcEYmwXS/vTbbhopMUbhKp2JhOeVTfxgaCIlF8= +k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.60.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= +k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20220328201542-3ee0da9b0b42/go.mod h1:Z/45zLw8lUo4wdiUkI+v/ImEGAvu3WatcZl3lPMR4Rk= +k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY= +k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 h1:kDi4JBNAsJWfz1aEXhO8Jg87JJaPNLh5tIzYHgStQ9Y= +sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2/go.mod h1:B+TnT182UBxE84DiCz4CVE26eOSDAeYCpfDnC2kdKMY= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.2.1/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/flyteartifacts/pkg/blob/artifact_blob_store.go b/flyteartifacts/pkg/blob/artifact_blob_store.go new file mode 100644 index 0000000000..36a9c30518 --- /dev/null +++ b/flyteartifacts/pkg/blob/artifact_blob_store.go @@ -0,0 +1,51 @@ +package blob + +import ( + "context" + "fmt" + "github.com/flyteorg/flyte/flyteartifacts/pkg/configuration" + "github.com/flyteorg/flyte/flytestdlib/logger" + "github.com/flyteorg/flyte/flytestdlib/promutils" + "github.com/flyteorg/flyte/flytestdlib/storage" + "github.com/golang/protobuf/ptypes/any" +) + +type ArtifactBlobStore struct { + store *storage.DataStore +} + +// OffloadArtifactCard stores the artifact card in the blob store +func (a *ArtifactBlobStore) OffloadArtifactCard(ctx context.Context, name, version string, card *any.Any) (storage.DataReference, error) { + uri, err := a.store.ConstructReference(ctx, a.store.GetBaseContainerFQN(ctx), name, version) + if err != nil { + return "", fmt.Errorf("failed to construct data reference for [%s/%s] with err: %v", name, version, err) + } + err = a.store.WriteProtobuf(ctx, uri, storage.Options{}, card) + if err != nil { + return "", fmt.Errorf("failed to write protobuf to %s with err: %v", uri, err) + } + return uri, nil +} + +func (a *ArtifactBlobStore) RetrieveArtifactCard(ctx context.Context, uri storage.DataReference) (*any.Any, error) { + card := &any.Any{} + err := a.store.ReadProtobuf(ctx, uri, card) + if err != nil { + return nil, fmt.Errorf("failed to read protobuf from %s with err: %v", uri, err) + } + return nil, nil +} + +func NewArtifactBlobStore(ctx context.Context, scope promutils.Scope) ArtifactBlobStore { + storageCfg := configuration.GetApplicationConfig().ArtifactBlobStoreConfig + logger.Infof(ctx, "Initializing storage client with config [%+v]", storageCfg) + + dataStorageClient, err := storage.NewDataStore(&storageCfg, scope) + if err != nil { + logger.Error(ctx, "Failed to initialize storage config") + panic(err) + } + return ArtifactBlobStore{ + store: dataStorageClient, + } +} diff --git a/flyteartifacts/pkg/configuration/config.go b/flyteartifacts/pkg/configuration/config.go new file mode 100644 index 0000000000..7848789db1 --- /dev/null +++ b/flyteartifacts/pkg/configuration/config.go @@ -0,0 +1,61 @@ +package configuration + +import ( + "github.com/flyteorg/flyte/flyteartifacts/pkg/configuration/shared" + "github.com/flyteorg/flyte/flytestdlib/config" + stdLibDb "github.com/flyteorg/flyte/flytestdlib/database" + stdLibStorage "github.com/flyteorg/flyte/flytestdlib/storage" + + "time" +) + +const artifactsServer = "artifactsServer" + +type ApplicationConfiguration struct { + ArtifactDatabaseConfig stdLibDb.DbConfig `json:"artifactDatabaseConfig" pflag:",Database configuration"` + ArtifactBlobStoreConfig stdLibStorage.Config `json:"artifactBlobStoreConfig" pflag:",Blob store configuration"` + ArtifactServerConfig shared.ServerConfiguration `json:"artifactServerConfig" pflag:",Artifact server configuration"` +} + +var defaultApplicationConfiguration = ApplicationConfiguration{ + ArtifactDatabaseConfig: stdLibDb.DbConfig{ + EnableForeignKeyConstraintWhenMigrating: true, + MaxIdleConnections: 10, + MaxOpenConnections: 100, + ConnMaxLifeTime: config.Duration{Duration: time.Hour}, + Postgres: stdLibDb.PostgresConfig{ + // These values are suitable for local sandbox development + Host: "localhost", + Port: 30001, + DbName: "artifacts", + User: "postgres", + Password: "postgres", + ExtraOptions: "sslmode=disable", + }, + }, + ArtifactBlobStoreConfig: stdLibStorage.Config{ + InitContainer: "flyte-artifacts", + }, + ArtifactServerConfig: shared.ServerConfiguration{ + Metrics: shared.Metrics{ + MetricsScope: "service:", + Port: config.Port{Port: 10254}, + ProfilerEnabled: false, + }, + Port: config.Port{Port: 50051}, + HttpPort: config.Port{Port: 50050}, + GrpcMaxResponseStatusBytes: 320000, + GrpcServerReflection: false, + Security: shared.ServerSecurityOptions{ + Secure: false, + UseAuth: false, + }, + MaxConcurrentStreams: 100, + }, +} + +var ApplicationConfig = config.MustRegisterSection(artifactsServer, &defaultApplicationConfiguration) + +func GetApplicationConfig() *ApplicationConfiguration { + return ApplicationConfig.GetConfig().(*ApplicationConfiguration) +} diff --git a/flyteartifacts/pkg/configuration/processor_config.go b/flyteartifacts/pkg/configuration/processor_config.go new file mode 100644 index 0000000000..e5645336fe --- /dev/null +++ b/flyteartifacts/pkg/configuration/processor_config.go @@ -0,0 +1,32 @@ +package configuration + +import ( + "github.com/flyteorg/flyte/flytestdlib/config" +) + +const artifactsProcessor = "artifactsProcessor" + +// QueueSubscriberConfig is a generic config object for reading from things like SQS +type QueueSubscriberConfig struct { + // The name of the queue onto which workflow notifications will enqueue. + QueueName string `json:"queueName"` + // The account id (according to whichever cloud provider scheme is used) that has permission to read from the above + // queue. + AccountID string `json:"accountId"` +} + +type EventProcessorConfiguration struct { + CloudProvider config.CloudDeployment `json:"cloudProvider" pflag:",Your deployment environment."` + + Region string `json:"region" pflag:",The region if applicable for the cloud provider."` + + Subscriber QueueSubscriberConfig `json:"subscriber" pflag:",The configuration for the subscriber."` +} + +var defaultEventProcessorConfiguration = EventProcessorConfiguration{} + +var EventsProcessorConfig = config.MustRegisterSection(artifactsProcessor, &defaultEventProcessorConfiguration) + +func GetEventsProcessorConfig() *EventProcessorConfiguration { + return EventsProcessorConfig.GetConfig().(*EventProcessorConfiguration) +} diff --git a/flyteartifacts/pkg/configuration/shared/config.go b/flyteartifacts/pkg/configuration/shared/config.go new file mode 100644 index 0000000000..35c9d4ecfb --- /dev/null +++ b/flyteartifacts/pkg/configuration/shared/config.go @@ -0,0 +1,62 @@ +package shared + +import ( + "fmt" + "github.com/flyteorg/flyte/flytestdlib/config" +) + +const sharedServer = "sharedServer" + +type Metrics struct { + MetricsScope string `json:"metricsScope" pflag:",MetricsScope"` + Port config.Port `json:"port" pflag:",Profile port to start listen for pprof and metric handlers on."` + ProfilerEnabled bool `json:"profilerEnabled" pflag:",Enable Profiler on server"` +} + +type SslOptions struct { + CertificateAuthorityFile string `json:"certificateAuthorityFile"` + CertificateFile string `json:"certificateFile"` + KeyFile string `json:"keyFile"` +} + +type ServerSecurityOptions struct { + Secure bool `json:"secure"` + Ssl SslOptions `json:"ssl"` + UseAuth bool `json:"useAuth"` + AllowLocalhostAccess bool `json:"allowLocalhostAccess" pflag:",Whether to permit localhost unauthenticated access to the server"` +} + +type ServerConfiguration struct { + Metrics Metrics `json:"metrics" pflag:",Metrics configuration"` + Port config.Port `json:"port" pflag:",On which grpc port to serve"` + HttpPort config.Port `json:"httpPort" pflag:",On which http port to serve"` + GrpcMaxResponseStatusBytes int32 `json:"grpcMaxResponseStatusBytes" pflag:", specifies the maximum (uncompressed) size of header list that the client is prepared to accept on grpc calls"` + GrpcServerReflection bool `json:"grpcServerReflection" pflag:",Enable GRPC Server Reflection"` + Security ServerSecurityOptions `json:"security"` + MaxConcurrentStreams int `json:"maxConcurrentStreams" pflag:",Limit on the number of concurrent streams to each ServerTransport."` +} + +var sharedServerConfiguration = ServerConfiguration{ + Metrics: Metrics{ + MetricsScope: "service:", + Port: config.Port{Port: 10254}, + ProfilerEnabled: false, + }, + Port: config.Port{Port: 8089}, + HttpPort: config.Port{Port: 8088}, + GrpcMaxResponseStatusBytes: 320000, + GrpcServerReflection: false, + Security: ServerSecurityOptions{ + Secure: false, + UseAuth: false, + }, + MaxConcurrentStreams: 100, +} + +func (s ServerConfiguration) GetGrpcHostAddress() string { + return fmt.Sprintf("0.0.0.0:%s", s.Port.String()) +} + +func (s ServerConfiguration) GetHttpHostAddress() string { + return fmt.Sprintf("0.0.0.0:%s", s.HttpPort.String()) +} diff --git a/flyteartifacts/pkg/db/gorm_models.go b/flyteartifacts/pkg/db/gorm_models.go new file mode 100644 index 0000000000..bc033f186b --- /dev/null +++ b/flyteartifacts/pkg/db/gorm_models.go @@ -0,0 +1,85 @@ +package db + +import ( + "github.com/jackc/pgx/v5/pgtype" + "gorm.io/gorm" +) + +type ArtifactKey struct { + gorm.Model + Project string `gorm:"uniqueIndex:idx_pdn;index:idx_proj;type:varchar(64)"` + Domain string `gorm:"uniqueIndex:idx_pdn;index:idx_dom;type:varchar(64)"` + Name string `gorm:"uniqueIndex:idx_pdn;index:idx_name;type:varchar(255)"` +} + +// WorkflowExecution - The Project/Domain is assumed to always be the same as the Artifact. +// The +type WorkflowExecution struct { + gorm.Model + ExecutionProject string `gorm:"uniqueIndex:idx_we_pdn;index:idx_we_proj;type:varchar(64)"` + ExecutionDomain string `gorm:"uniqueIndex:idx_we_pdn;index:idx_we_dom;type:varchar(64)"` + ExecutionName string `gorm:"uniqueIndex:idx_we_pdn;index:idx_we_name;type:varchar(255)"` + InputArtifacts []Artifact `gorm:"many2many:execution_inputs;"` +} + +type Artifact struct { + gorm.Model + ArtifactKeyID uint `gorm:"not null;uniqueIndex:idx_artifact_version"` + ArtifactKey ArtifactKey `gorm:"foreignKey:ArtifactKeyID;references:ID"` + Version string `gorm:"not null;type:varchar(255);uniqueIndex:idx_artifact_version"` + Partitions pgtype.Hstore `gorm:"type:hstore;index:idx_artifact_partitions"` + + LiteralType []byte `gorm:"not null"` + LiteralValue []byte `gorm:"not null"` + + Description string `gorm:"type:varchar(255)"` + MetadataType string `gorm:"type:varchar(64)"` + OffloadedUserMetadata string `gorm:"type:varchar(255)"` + + WorkflowExecutionID uint `gorm:"index:idx_artifact_wf_exec_id"` + WorkflowExecution WorkflowExecution `gorm:"foreignKey:WorkflowExecutionID;references:ID"` + NodeID string `gorm:"type:varchar(128)"` + + WorkflowProject string `gorm:"type:varchar(64)"` + WorkflowDomain string `gorm:"type:varchar(64)"` + WorkflowName string `gorm:"type:varchar(255)"` + WorkflowVersion string `gorm:"type:varchar(255)"` + TaskProject string `gorm:"type:varchar(64)"` + TaskDomain string `gorm:"type:varchar(64)"` + TaskName string `gorm:"type:varchar(255)"` + TaskVersion string `gorm:"type:varchar(255)"` + // See Admin migration for note. + // Here nullable in the case of workflow output. + RetryAttempt *uint32 + + Principal string `gorm:"type:varchar(256)"` +} + +type TriggerKey struct { + gorm.Model + Project string `gorm:"uniqueIndex:idx_t_pdn;index:idx_t_proj;type:varchar(64)"` + Domain string `gorm:"uniqueIndex:idx_t_pdn;index:idx_t_dom;type:varchar(64)"` + Name string `gorm:"uniqueIndex:idx_t_pdn;index:idx_t_name;type:varchar(255)"` + RunsOn []ArtifactKey `gorm:"many2many:active_trigger_artifact_keys;"` +} + +type LaunchPlanID struct { + Name string `gorm:"not null;index:idx_lp_id;type:varchar(255)"` + Version string `gorm:"not null;type:varchar(255);index:idx_launch_plan_version"` +} + +type Trigger struct { + gorm.Model + TriggerKeyID uint `gorm:"uniqueIndex:idx_trigger_pdnv"` + TriggerKey TriggerKey `gorm:"foreignKey:TriggerKeyID;references:ID"` + Version string `gorm:"not null;type:varchar(255);index:idx_trigger_version;uniqueIndex:idx_trigger_pdnv"` + + // Unlike the one in the TriggerKey table, these are the list of artifact keys as specified by the user + // for this specific version. Currently just the key but can add additional fields in the future. + RunsOn []ArtifactKey `gorm:"many2many:trigger_ids_artifact_keys;"` + + Active bool `gorm:"index:idx_active"` + LaunchPlanID LaunchPlanID `gorm:"embedded"` + LaunchPlanSpec []byte `gorm:"not null"` + LaunchPlanClosure []byte `gorm:"not null"` +} diff --git a/flyteartifacts/pkg/db/gorm_transformers.go b/flyteartifacts/pkg/db/gorm_transformers.go new file mode 100644 index 0000000000..9569896f78 --- /dev/null +++ b/flyteartifacts/pkg/db/gorm_transformers.go @@ -0,0 +1,252 @@ +package db + +import ( + "context" + "fmt" + "github.com/flyteorg/flyte/flyteartifacts/pkg/models" + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/admin" + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/artifact" + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/core" + "github.com/flyteorg/flyte/flytestdlib/logger" + "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/ptypes" + "github.com/jackc/pgx/v5/pgtype" +) + +func PartitionsIdlToHstore(idlPartitions *core.Partitions) pgtype.Hstore { + ctx := context.Background() + if idlPartitions == nil || idlPartitions.GetValue() == nil { + return nil + } + var hstore = make(pgtype.Hstore) + + for k, v := range idlPartitions.GetValue() { + if len(v.GetStaticValue()) == 0 { + logger.Warningf(ctx, "Partition key [%s] missing static value, [%+v]", k, v.GetValue()) + continue + } + sv := v.GetStaticValue() + hstore[k] = &sv + } + return hstore +} + +func HstoreToIdlPartitions(hs pgtype.Hstore) *core.Partitions { + if hs == nil || len(hs) == 0 { + return nil + } + m := make(map[string]*core.LabelValue, len(hs)) + for k, v := range hs { + m[k] = &core.LabelValue{ + Value: &core.LabelValue_StaticValue{ + StaticValue: *v, + }, + } + } + return &core.Partitions{ + Value: m, + } +} + +func ServiceToGormModel(serviceModel models.Artifact) (Artifact, error) { + partitions := PartitionsIdlToHstore(serviceModel.Artifact.GetArtifactId().GetPartitions()) + + ga := Artifact{ + ArtifactKey: ArtifactKey{ + Project: serviceModel.Artifact.ArtifactId.ArtifactKey.Project, + Domain: serviceModel.Artifact.ArtifactId.ArtifactKey.Domain, + Name: serviceModel.Artifact.ArtifactId.ArtifactKey.Name, + }, + Version: serviceModel.Artifact.ArtifactId.Version, + Partitions: partitions, + + LiteralType: serviceModel.LiteralTypeBytes, + LiteralValue: serviceModel.LiteralValueBytes, + Description: serviceModel.Artifact.Spec.ShortDescription, + MetadataType: serviceModel.Artifact.Spec.MetadataType, + OffloadedUserMetadata: serviceModel.OffloadedMetadata, + } + + if serviceModel.Artifact.GetSource().GetWorkflowExecution() != nil { + // artifact and execution project/domains are always the same. + // Note the service model will not have workflow execution if it was an upload + wfExec := WorkflowExecution{ + ExecutionProject: serviceModel.Artifact.ArtifactId.ArtifactKey.Project, + ExecutionDomain: serviceModel.Artifact.ArtifactId.ArtifactKey.Domain, + ExecutionName: serviceModel.Source.WorkflowExecution.Name, + } + ga.WorkflowExecution = wfExec + ga.NodeID = serviceModel.Source.NodeId + } + if serviceModel.GetSource() != nil { + ga.Principal = serviceModel.GetSource().GetPrincipal() + } + + if serviceModel.GetSource().GetTaskId() != nil { + // If task id is there, so should the retry attempt + retry := serviceModel.GetSource().GetRetryAttempt() + ga.RetryAttempt = &retry + ga.TaskProject = serviceModel.GetSource().GetTaskId().Project + ga.TaskDomain = serviceModel.GetSource().GetTaskId().Domain + ga.TaskName = serviceModel.GetSource().GetTaskId().Name + ga.TaskVersion = serviceModel.GetSource().GetTaskId().Version + } + + return ga, nil +} + +func GormToServiceModel(ga Artifact) (models.Artifact, error) { + lt := &core.LiteralType{} + lit := &core.Literal{} + if err := proto.Unmarshal(ga.LiteralType, lt); err != nil { + return models.Artifact{}, err + } + if err := proto.Unmarshal(ga.LiteralValue, lit); err != nil { + return models.Artifact{}, err + } + + a := artifact.Artifact{ + ArtifactId: &core.ArtifactID{ + ArtifactKey: &core.ArtifactKey{ + Project: ga.ArtifactKey.Project, + Domain: ga.ArtifactKey.Domain, + Name: ga.ArtifactKey.Name, + }, + Version: ga.Version, + }, + Spec: &artifact.ArtifactSpec{ + Value: lit, + Type: lt, + ShortDescription: ga.Description, + UserMetadata: nil, + MetadataType: ga.MetadataType, + }, + Tags: nil, + } + p := HstoreToIdlPartitions(ga.Partitions) + if p != nil { + a.ArtifactId.Dimensions = &core.ArtifactID_Partitions{Partitions: p} + } + aSrc := artifact.ArtifactSource{ + NodeId: ga.NodeID, + Principal: ga.Principal, + } + if ga.RetryAttempt != nil { + aSrc.RetryAttempt = *ga.RetryAttempt + } + if ga.WorkflowExecutionID != 0 { + execID := &core.WorkflowExecutionIdentifier{ + Project: ga.ArtifactKey.Project, + Domain: ga.ArtifactKey.Domain, + Name: ga.WorkflowExecution.ExecutionName, + } + aSrc.WorkflowExecution = execID + } + if ga.TaskProject != "" { + aSrc.TaskId = &core.Identifier{ + ResourceType: core.ResourceType_TASK, + Project: ga.TaskProject, + Domain: ga.TaskDomain, + Name: ga.TaskName, + Version: ga.TaskVersion, + } + } + a.Source = &aSrc + + return models.Artifact{ + Artifact: a, + OffloadedMetadata: "", + LiteralTypeBytes: ga.LiteralType, + LiteralValueBytes: ga.LiteralValue, + }, nil +} + +func ServiceToGormTrigger(serviceTrigger models.Trigger) Trigger { + + t := Trigger{ + TriggerKey: TriggerKey{ + Project: serviceTrigger.Project, + Domain: serviceTrigger.Domain, + Name: serviceTrigger.Name, + }, + Version: serviceTrigger.Version, + Active: serviceTrigger.Active, + LaunchPlanID: LaunchPlanID{ + Name: serviceTrigger.LaunchPlan.Id.Name, + Version: serviceTrigger.LaunchPlan.Id.Version, + }, + LaunchPlanSpec: serviceTrigger.SpecBytes, + LaunchPlanClosure: serviceTrigger.ClosureBytes, + } + + var runsOn = make([]ArtifactKey, len(serviceTrigger.RunsOn)) + for i, a := range serviceTrigger.RunsOn { + runsOn[i] = ArtifactKey{ + Project: a.ArtifactKey.Project, + Domain: a.ArtifactKey.Domain, + Name: a.ArtifactKey.Name, + } + } + t.RunsOn = runsOn + + return t +} + +func GormToServiceTrigger(gormTrigger Trigger) (models.Trigger, error) { + spec := &admin.LaunchPlanSpec{} + closure := &admin.LaunchPlanClosure{} + if err := proto.Unmarshal(gormTrigger.LaunchPlanSpec, spec); err != nil { + return models.Trigger{}, err + } + if err := proto.Unmarshal(gormTrigger.LaunchPlanClosure, closure); err != nil { + return models.Trigger{}, err + } + lpID := core.Identifier{ + ResourceType: core.ResourceType_LAUNCH_PLAN, + Project: gormTrigger.TriggerKey.Project, + Domain: gormTrigger.TriggerKey.Domain, + Name: gormTrigger.LaunchPlanID.Name, + Version: gormTrigger.Version, // gormTrigger.LaunchPlanID.Version, + } + t := models.Trigger{ + Project: gormTrigger.TriggerKey.Project, + Domain: gormTrigger.TriggerKey.Domain, + Name: gormTrigger.TriggerKey.Name, + Version: gormTrigger.Version, + Active: gormTrigger.Active, + LaunchPlanID: lpID, + LaunchPlan: &admin.LaunchPlan{ + Id: &lpID, + Spec: spec, + Closure: closure, + }, + SpecBytes: gormTrigger.LaunchPlanSpec, + ClosureBytes: gormTrigger.LaunchPlanClosure, + } + + // TODO: This is a copy/paste of the code in transformers.go. Refactor. + // Basically the DB model only has artifact keys, not whole artifact IDs including partitions + // so pull the artifact IDs again from the spec. + lc := spec.GetEntityMetadata().GetLaunchConditions() + + var err error + idlTrigger := core.Trigger{} + err = ptypes.UnmarshalAny(lc, &idlTrigger) + if err != nil { + logger.Errorf(context.TODO(), "Failed to unmarshal launch conditions to idl, metadata: [%+v]", spec.GetEntityMetadata()) + return models.Trigger{}, err + } + if len(idlTrigger.Triggers) == 0 { + return models.Trigger{}, fmt.Errorf("invalid request to CreateTrigger, launch conditions cannot be empty") + } + var runsOnArtifactIDs = make([]core.ArtifactID, len(idlTrigger.Triggers)) + for i, t := range idlTrigger.Triggers { + runsOnArtifactIDs[i] = *t + runsOnArtifactIDs[i].ArtifactKey.Project = lpID.Project + runsOnArtifactIDs[i].ArtifactKey.Domain = lpID.Domain + } + + t.RunsOn = runsOnArtifactIDs + + return t, nil +} diff --git a/flyteartifacts/pkg/db/metrics.go b/flyteartifacts/pkg/db/metrics.go new file mode 100644 index 0000000000..b275548cd4 --- /dev/null +++ b/flyteartifacts/pkg/db/metrics.go @@ -0,0 +1,34 @@ +package db + +import ( + "time" + + "github.com/flyteorg/flyte/flytestdlib/promutils" +) + +// Common metrics emitted by gormimpl repos. +type gormMetrics struct { + Scope promutils.Scope + CreateDuration promutils.StopWatch + GetDuration promutils.StopWatch + UpdateDuration promutils.StopWatch + SearchDuration promutils.StopWatch + + CreateTriggerDuration promutils.StopWatch +} + +func newMetrics(scope promutils.Scope) gormMetrics { + return gormMetrics{ + Scope: scope, + CreateDuration: scope.MustNewStopWatch( + "create", "time taken to create a new entry", time.Millisecond), + GetDuration: scope.MustNewStopWatch( + "get", "time taken to get an entry", time.Millisecond), + UpdateDuration: scope.MustNewStopWatch( + "update", "time taken to update an entry", time.Millisecond), + SearchDuration: scope.MustNewStopWatch( + "search", "time taken for searching", time.Millisecond), + CreateTriggerDuration: scope.MustNewStopWatch( + "createT", "time taken to create a new trigger", time.Millisecond), + } +} diff --git a/flyteartifacts/pkg/db/migrations.go b/flyteartifacts/pkg/db/migrations.go new file mode 100644 index 0000000000..719d17a8d9 --- /dev/null +++ b/flyteartifacts/pkg/db/migrations.go @@ -0,0 +1,117 @@ +package db + +import ( + "github.com/go-gormigrate/gormigrate/v2" + "github.com/jackc/pgx/v5/pgtype" + "gorm.io/gorm" +) + +var Migrations = []*gormigrate.Migration{ + { + ID: "2023-10-12-inits", + Migrate: func(tx *gorm.DB) error { + type ArtifactKey struct { + gorm.Model + Project string `gorm:"uniqueIndex:idx_pdn;index:idx_proj;type:varchar(64)"` + Domain string `gorm:"uniqueIndex:idx_pdn;index:idx_dom;type:varchar(64)"` + Name string `gorm:"uniqueIndex:idx_pdn;index:idx_name;type:varchar(255)"` + } + type WorkflowExecution struct { + gorm.Model + ExecutionProject string `gorm:"uniqueIndex:idx_we_pdn;index:idx_we_proj;type:varchar(64)"` + ExecutionDomain string `gorm:"uniqueIndex:idx_we_pdn;index:idx_we_dom;type:varchar(64)"` + ExecutionName string `gorm:"uniqueIndex:idx_we_pdn;index:idx_we_name;type:varchar(255)"` + InputArtifacts []Artifact `gorm:"many2many:execution_inputs;"` + } + + type Artifact struct { + gorm.Model + ArtifactKeyID uint `gorm:"not null;uniqueIndex:idx_artifact_version"` + ArtifactKey ArtifactKey `gorm:"foreignKey:ArtifactKeyID;references:ID"` + Version string `gorm:"not null;type:varchar(255);uniqueIndex:idx_artifact_version"` + Partitions pgtype.Hstore `gorm:"type:hstore;index:idx_artifact_partitions"` + + LiteralType []byte `gorm:"not null"` + LiteralValue []byte `gorm:"not null"` + + Description string `gorm:"type:varchar(255)"` + MetadataType string `gorm:"type:varchar(64)"` + OffloadedUserMetadata string `gorm:"type:varchar(255)"` + + WorkflowExecutionID uint `gorm:"index:idx_artifact_wf_exec_id"` + WorkflowExecution WorkflowExecution `gorm:"foreignKey:WorkflowExecutionID;references:ID"` + NodeID string `gorm:"type:varchar(128)"` + + WorkflowProject string `gorm:"type:varchar(64)"` + WorkflowDomain string `gorm:"type:varchar(64)"` + WorkflowName string `gorm:"type:varchar(255)"` + WorkflowVersion string `gorm:"type:varchar(255)"` + TaskProject string `gorm:"type:varchar(64)"` + TaskDomain string `gorm:"type:varchar(64)"` + TaskName string `gorm:"type:varchar(255)"` + TaskVersion string `gorm:"type:varchar(255)"` + // See Admin migration for note. + // Here nullable in the case of workflow output. + RetryAttempt *uint32 + + Principal string `gorm:"type:varchar(256)"` + } + err := tx.AutoMigrate( + &ArtifactKey{}, &Artifact{}, &WorkflowExecution{}, + ) + if err != nil { + return err + } + + tx.Exec("CREATE INDEX idx_gin_artifact_partitions ON artifacts USING GIN (partitions)") + tx.Exec("CREATE INDEX idx_created_at ON artifacts (created_at desc)") + return tx.Error + }, + Rollback: func(tx *gorm.DB) error { + return tx.Migrator().DropTable( + "artifacts", "artifact_keys", + ) + }, + }, + { + ID: "2023-10-22-trigger", + Migrate: func(tx *gorm.DB) error { + type TriggerKey struct { + gorm.Model + Project string `gorm:"uniqueIndex:idx_t_pdn;index:idx_t_proj;type:varchar(64)"` + Domain string `gorm:"uniqueIndex:idx_t_pdn;index:idx_t_dom;type:varchar(64)"` + Name string `gorm:"uniqueIndex:idx_t_pdn;index:idx_t_name;type:varchar(255)"` + RunsOn []ArtifactKey `gorm:"many2many:active_trigger_artifact_keys;"` + } + + type LaunchPlanID struct { + Name string `gorm:"not null;index:idx_lp_id;type:varchar(255)"` + Version string `gorm:"not null;type:varchar(255);index:idx_launch_plan_version"` + } + + type Trigger struct { + gorm.Model + TriggerKeyID uint `gorm:"uniqueIndex:idx_trigger_pdnv"` + TriggerKey TriggerKey `gorm:"foreignKey:TriggerKeyID;references:ID"` + Version string `gorm:"not null;type:varchar(255);index:idx_trigger_version;uniqueIndex:idx_trigger_pdnv"` + + // Unlike the one in the TriggerKey table, these are the list of artifact keys as specified by the user + // for this specific version. Currently just the key but can add additional fields in the future. + RunsOn []ArtifactKey `gorm:"many2many:trigger_ids_artifact_keys;"` + + Active bool `gorm:"index:idx_t_active"` + LaunchPlanID LaunchPlanID `gorm:"embedded"` + LaunchPlanSpec []byte `gorm:"not null"` + LaunchPlanClosure []byte `gorm:"not null"` + } + return tx.AutoMigrate( + &TriggerKey{}, &Trigger{}, + ) + }, + Rollback: func(tx *gorm.DB) error { + return tx.Migrator().DropTable( + "triggers", "trigger_keys", + ) + }, + }, +} diff --git a/flyteartifacts/pkg/db/querying_test.go b/flyteartifacts/pkg/db/querying_test.go new file mode 100644 index 0000000000..fd9ff014e4 --- /dev/null +++ b/flyteartifacts/pkg/db/querying_test.go @@ -0,0 +1,350 @@ +package db + +import ( + "context" + "database/sql" + "fmt" + "github.com/DATA-DOG/go-sqlmock" + "github.com/flyteorg/flyte/flyteartifacts/pkg/models" + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/artifact" + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/core" + "github.com/flyteorg/flyte/flytestdlib/database" + "github.com/flyteorg/flyte/flytestdlib/promutils" + "github.com/stretchr/testify/assert" + "testing" + + "gorm.io/driver/postgres" + "gorm.io/gorm" +) + +func getMockRds(t *testing.T) (*sql.DB, sqlmock.Sqlmock, RDSStorage) { + dbCfg := database.DbConfig{} + + mockDb, mock, err := sqlmock.New() + assert.NoError(t, err) + dialector := postgres.New(postgres.Config{ + Conn: mockDb, + DriverName: "postgres", + }) + db, _ := gorm.Open(dialector, &gorm.Config{}) + + scope := promutils.NewTestScope() + rds := RDSStorage{ + config: dbCfg, + db: db, + metrics: newMetrics(scope), + } + return mockDb, mock, rds +} + +func TestQuery1(t *testing.T) { + ctx := context.Background() + + mockDb, mock, rds := getMockRds(t) + defer func(mockDb *sql.DB) { + err := mockDb.Close() + assert.NoError(t, err) + }(mockDb) + + //rows := sqlmock.NewRows([]string{"Code", "Price"}).AddRow("D43", 100) + //mock.ExpectQuery(`SELECT`).WillReturnRows(rows) + + query := core.ArtifactQuery{ + Identifier: &core.ArtifactQuery_ArtifactId{ + ArtifactId: &core.ArtifactID{ + ArtifactKey: &core.ArtifactKey{ + Project: "pp", + Domain: "dd", + Name: "nn", + }, + Version: "abc", + Dimensions: nil, + }, + }, + } + _, err := rds.GetArtifact(ctx, query) + assert.NoError(t, err) + + //mock.ExpectQuery("SELECT") + mock.ExpectClose() +} + +func TestQuery2_Create(t *testing.T) { + ctx := context.Background() + + mockDb, mock, rds := getMockRds(t) + defer func(mockDb *sql.DB) { + err := mockDb.Close() + assert.NoError(t, err) + }(mockDb) + + a := artifact.Artifact{ + ArtifactId: &core.ArtifactID{ + ArtifactKey: &core.ArtifactKey{ + Project: "pp", + Domain: "dd", + Name: "nn", + }, + Version: "abc", + Dimensions: nil, + }, + Spec: &artifact.ArtifactSpec{ + Value: nil, + Type: nil, + }, + Source: &artifact.ArtifactSource{ + WorkflowExecution: &core.WorkflowExecutionIdentifier{ + Project: "pp", + Domain: "dd", + Name: "artifact_name", + }, + NodeId: "node1", + Principal: "foo", + }, + } + + mock.ExpectBegin() + _, err := rds.CreateArtifact(ctx, models.Artifact{ + Artifact: a, + LiteralTypeBytes: []byte("hello"), + LiteralValueBytes: []byte("world"), + }) + assert.NoError(t, err) + + //mock.ExpectQuery("SELECT") + mock.ExpectClose() +} + +func TestQuery3_Find(t *testing.T) { + ctx := context.Background() + + mockDb, mock, rds := getMockRds(t) + defer func(mockDb *sql.DB) { + err := mockDb.Close() + assert.NoError(t, err) + }(mockDb) + + p := models.PartitionsToIdl(map[string]string{"region": "LAX"}) + + s := artifact.SearchArtifactsRequest{ + ArtifactKey: &core.ArtifactKey{ + Domain: "development", + }, + Partitions: p, + Principal: "", + Version: "", + Options: nil, + } + + res, ct, err := rds.SearchArtifacts(ctx, s) + assert.NoError(t, err) + + fmt.Println(res, ct) + + s = artifact.SearchArtifactsRequest{ + ArtifactKey: &core.ArtifactKey{ + Domain: "development", + }, + Partitions: p, + Principal: "", + Version: "", + Options: &artifact.SearchOptions{ + StrictPartitions: true, + }, + } + + res, ct, err = rds.SearchArtifacts(ctx, s) + assert.NoError(t, err) + + s = artifact.SearchArtifactsRequest{ + ArtifactKey: &core.ArtifactKey{ + Domain: "development", + }, + Principal: "abc", + Version: "vxyz", + Options: &artifact.SearchOptions{ + StrictPartitions: true, + }, + } + + res, ct, err = rds.SearchArtifacts(ctx, s) + assert.NoError(t, err) + + fmt.Println(res, ct) + + mock.ExpectClose() +} + +func TestQuery4_Find(t *testing.T) { + ctx := context.Background() + + mockDb, mock, rds := getMockRds(t) + defer func(mockDb *sql.DB) { + err := mockDb.Close() + assert.NoError(t, err) + }(mockDb) + + s := artifact.SearchArtifactsRequest{ + ArtifactKey: &core.ArtifactKey{ + Domain: "development", + }, + Principal: "", + Version: "", + Options: nil, + } + + res, ct, err := rds.SearchArtifacts(ctx, s) + assert.NoError(t, err) + + fmt.Println(res, ct) + + mock.ExpectClose() +} + +func TestQuery5_Find(t *testing.T) { + ctx := context.Background() + + mockDb, mock, rds := getMockRds(t) + defer func(mockDb *sql.DB) { + err := mockDb.Close() + assert.NoError(t, err) + }(mockDb) + + s := artifact.SearchArtifactsRequest{ + ArtifactKey: &core.ArtifactKey{ + Domain: "development", + }, + Principal: "", + Version: "", + Token: "1", + Options: &artifact.SearchOptions{ + LatestByKey: true, + }, + } + + res, ct, err := rds.SearchArtifacts(ctx, s) + assert.NoError(t, err) + + fmt.Println(res, ct) + + mock.ExpectClose() +} + +func TestLineage1_Input(t *testing.T) { + ctx := context.Background() + + mockDb, mock, rds := getMockRds(t) + defer func(mockDb *sql.DB) { + err := mockDb.Close() + assert.NoError(t, err) + }(mockDb) + + r := artifact.FindByWorkflowExecRequest{ + ExecId: &core.WorkflowExecutionIdentifier{ + Project: "pr", + Domain: "do", + Name: "nnn", + }, + Direction: 0, + } + + res, err := rds.FindByWorkflowExec(ctx, &r) + assert.NoError(t, err) + + fmt.Println(res) + + mock.ExpectClose() +} + +func TestLineage2_Output(t *testing.T) { + ctx := context.Background() + + mockDb, mock, rds := getMockRds(t) + defer func(mockDb *sql.DB) { + err := mockDb.Close() + assert.NoError(t, err) + }(mockDb) + + r := artifact.FindByWorkflowExecRequest{ + ExecId: &core.WorkflowExecutionIdentifier{ + Project: "pr", + Domain: "do", + Name: "nnn", + }, + Direction: 1, + } + + res, err := rds.FindByWorkflowExec(ctx, &r) + assert.NoError(t, err) + + fmt.Println(res) + + mock.ExpectClose() +} + +func TestLineage3_Set(t *testing.T) { + ctx := context.Background() + + mockDb, mock, rds := getMockRds(t) + defer func(mockDb *sql.DB) { + err := mockDb.Close() + assert.NoError(t, err) + }(mockDb) + + ak := core.ArtifactKey{ + Project: "pr", + Domain: "do", + Name: "nnn", + } + ids := []*core.ArtifactID{ + { + ArtifactKey: &ak, + Version: "v1", + }, + { + ArtifactKey: &ak, + Version: "v2", + }, + } + + r := artifact.ExecutionInputsRequest{ + ExecutionId: &core.WorkflowExecutionIdentifier{ + Project: "pr", + Domain: "do", + Name: "nnn", + }, + Inputs: ids, + } + mock.ExpectBegin() + err := rds.SetExecutionInputs(ctx, &r) + assert.NoError(t, err) + mock.ExpectClose() +} + +func TestLineagedd3_Set(t *testing.T) { + mockDb, mock, rds := getMockRds(t) + defer func(mockDb *sql.DB) { + err := mockDb.Close() + assert.NoError(t, err) + }(mockDb) + defer func(mockDb *sql.DB) { + err := mockDb.Close() + assert.NoError(t, err) + }(mockDb) + + type Language struct { + gorm.Model + Name string //`gorm:"uniqueIndex:idx_lang_name"` + } + type User struct { + gorm.Model + Name string + Languages []Language `gorm:"many2many:user_languages;"` + } + var userLanguages []Language + var dbUser User + err := rds.db.Model(&dbUser).Where("name = ?", "chinese").Association("Languages").Find(&userLanguages) + + assert.NoError(t, err) + mock.ExpectClose() +} diff --git a/flyteartifacts/pkg/db/storage.go b/flyteartifacts/pkg/db/storage.go new file mode 100644 index 0000000000..177e9741cb --- /dev/null +++ b/flyteartifacts/pkg/db/storage.go @@ -0,0 +1,561 @@ +package db + +import ( + "context" + "errors" + "fmt" + "github.com/flyteorg/flyte/flyteartifacts/pkg/configuration" + "github.com/flyteorg/flyte/flyteartifacts/pkg/lib" + "github.com/flyteorg/flyte/flyteartifacts/pkg/models" + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/artifact" + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/core" + "github.com/flyteorg/flyte/flytestdlib/database" + "github.com/flyteorg/flyte/flytestdlib/logger" + "github.com/flyteorg/flyte/flytestdlib/promutils" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "gorm.io/gorm" + "strconv" +) + +// RDSStorage should implement StorageInterface +type RDSStorage struct { + config database.DbConfig + db *gorm.DB + metrics gormMetrics +} + +// CreateArtifact helps implement StorageInterface +func (r *RDSStorage) CreateArtifact(ctx context.Context, serviceModel models.Artifact) (models.Artifact, error) { + timer := r.metrics.CreateDuration.Start() + logger.Debugf(ctx, "Attempt create artifact [%s:%s]", + serviceModel.Artifact.ArtifactId.ArtifactKey.Name, serviceModel.Artifact.ArtifactId.Version) + gormModel, err := ServiceToGormModel(serviceModel) + if err != nil { + logger.Errorf(ctx, "Failed to convert service model to gorm model: %+v", err) + return models.Artifact{}, err + } + + // Check to see if the artifact key already exists. + // do the create in a transaction + err = r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + var extantKey ArtifactKey + ak := ArtifactKey{ + Project: serviceModel.Artifact.ArtifactId.ArtifactKey.Project, + Domain: serviceModel.Artifact.ArtifactId.ArtifactKey.Domain, + Name: serviceModel.Artifact.ArtifactId.ArtifactKey.Name, + } + tx.FirstOrCreate(&extantKey, ak) + if err := tx.Error; err != nil { + logger.Errorf(ctx, "Failed to firstorcreate key: %+v", err) + return err + } + gormModel.ArtifactKeyID = extantKey.ID + gormModel.ArtifactKey = ArtifactKey{} // zero out the artifact key + + if serviceModel.GetSource().GetWorkflowExecution() != nil { + var extantWfExec WorkflowExecution + we := WorkflowExecution{ + ExecutionProject: serviceModel.Artifact.ArtifactId.ArtifactKey.Project, + ExecutionDomain: serviceModel.Artifact.ArtifactId.ArtifactKey.Domain, + ExecutionName: serviceModel.Source.WorkflowExecution.Name, + } + tx.FirstOrCreate(&extantWfExec, we) + if err := tx.Error; err != nil { + logger.Errorf(ctx, "Failed to firstorcreate wf exec: %+v", err) + return err + } + gormModel.WorkflowExecutionID = extantWfExec.ID + gormModel.WorkflowExecution = WorkflowExecution{} // zero out the workflow execution + } + + tx.Create(&gormModel) + if tx.Error != nil { + logger.Errorf(ctx, "Failed to create artifact %+v", tx.Error) + return tx.Error + } + getSaved := tx.Preload("ArtifactKey").Preload("WorkflowExecution").First(&gormModel, "id = ?", gormModel.ID) + if getSaved.Error != nil { + logger.Errorf(ctx, "Failed to find artifact that was just saved: %+v", getSaved.Error) + return getSaved.Error + } + return nil + }) + if err != nil { + logger.Errorf(ctx, "Failed transaction upsert on key [%v]: %+v", serviceModel.ArtifactId, err) + return models.Artifact{}, err + } + timer.Stop() + svcModel, err := GormToServiceModel(gormModel) + if err != nil { + // metric + logger.Errorf(ctx, "Failed to convert gorm model to service model: %+v", err) + return models.Artifact{}, err + } + + return svcModel, nil +} + +func (r *RDSStorage) handleUriGet(ctx context.Context, uri string) (models.Artifact, error) { + artifactID, tag, err := lib.ParseFlyteURL(uri) + if err != nil { + logger.Errorf(ctx, "Failed to parse uri [%s]: %+v", uri, err) + return models.Artifact{}, err + } + if tag != "" { + return models.Artifact{}, fmt.Errorf("tag not implemented yet") + } + logger.Debugf(ctx, "Extracted artifact id [%v] from uri [%s], using id handler", artifactID, uri) + return r.handleArtifactIdGet(ctx, artifactID) +} + +func (r *RDSStorage) handleArtifactIdGet(ctx context.Context, artifactID core.ArtifactID) (models.Artifact, error) { + var gotArtifact Artifact + ak := ArtifactKey{ + Project: artifactID.ArtifactKey.Project, + Domain: artifactID.ArtifactKey.Domain, + Name: artifactID.ArtifactKey.Name, + } + db := r.db.Model(&Artifact{}).InnerJoins("ArtifactKey", r.db.Where(&ak)).Joins("WorkflowExecution") + if artifactID.Version != "" { + db = db.Where("version = ?", artifactID.Version) + } + + // @eduardo - not actually sure if this is doing a strict match. + if artifactID.GetPartitions() != nil && len(artifactID.GetPartitions().GetValue()) > 0 { + partitionMap := PartitionsIdlToHstore(artifactID.GetPartitions()) + db = db.Where("partitions = ?", partitionMap) + } else { + // Be strict about partitions. If not specified, that means, we're looking + // for null + db = db.Where("partitions is null") + } + db.Order("created_at desc").Limit(1) + db = db.First(&gotArtifact) + if err := db.Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + logger.Infof(ctx, "Artifact not found: %+v", artifactID) + // todo: return grpc errors at the service layer not here. + return models.Artifact{}, status.Errorf(codes.NotFound, "artifact [%v] not found", artifactID) + } + logger.Errorf(ctx, "Failed to query for artifact: %+v", err) + return models.Artifact{}, err + } + logger.Debugf(ctx, "Found and returning artifact key %v", gotArtifact) + m, err := GormToServiceModel(gotArtifact) + if err != nil { + logger.Errorf(ctx, "Failed to convert gorm model to service model: %+v", err) + return models.Artifact{}, err + } + return m, nil +} + +func (r *RDSStorage) GetArtifact(ctx context.Context, query core.ArtifactQuery) (models.Artifact, error) { + timer := r.metrics.GetDuration.Start() + + var resp models.Artifact + var err error + if query.GetUri() != "" { + logger.Debugf(ctx, "found uri in query: %+v", query.GetUri()) + resp, err = r.handleUriGet(ctx, query.GetUri()) + } else if query.GetArtifactId() != nil { + logger.Debugf(ctx, "found artifact_id in query: %+v", *query.GetArtifactId()) + resp, err = r.handleArtifactIdGet(ctx, *query.GetArtifactId()) + } else if query.GetArtifactTag() != nil { + return models.Artifact{}, fmt.Errorf("artifact tag not implemented yet") + } else { + return models.Artifact{}, fmt.Errorf("query must contain either uri, artifact_id, or artifact_tag") + } + timer.Stop() + return resp, err +} + +func (r *RDSStorage) CreateTrigger(ctx context.Context, trigger models.Trigger) (models.Trigger, error) { + + timer := r.metrics.CreateTriggerDuration.Start() + logger.Debugf(ctx, "Attempt create trigger [%s:%s]", trigger.Name, trigger.Version) + dbTrigger := ServiceToGormTrigger(trigger) + // TODO: Add a check to ensure that the artifact IDs that the trigger is triggering on are unique if more than one. + + // The first transaction just saves artifact keys, in case there are no artifacts + // yet in the database that the trigger depends on. + err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + for _, ak := range dbTrigger.RunsOn { + var extantKey ArtifactKey + tx.FirstOrCreate(&extantKey, ak) + if err := tx.Error; err != nil { + logger.Errorf(ctx, "Failed to firstorcreate key: %+v", err) + return err + } + } + return nil + }) + + if err != nil { + logger.Errorf(ctx, "Failed to pre-save artifact keys [%s]: %+v", trigger.Name, err) + return models.Trigger{}, err + } + + // Check to see if the trigger key already exists. Create if not + err = r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + var extantKey TriggerKey + + tx.FirstOrCreate(&extantKey, dbTrigger.TriggerKey) + if err := tx.Error; err != nil { + logger.Errorf(ctx, "Failed to firstorcreate key: %+v", err) + return err + } + + // Look for all earlier versions of the trigger and mark them inactive + setFalse := tx.Model(&Trigger{}).Where("trigger_key_id = ?", extantKey.ID).Update("active", false) + if tx.Error != nil { + logger.Errorf(ctx, "transaction error marking earlier versions inactive for %s: %+v", dbTrigger.TriggerKey.Name, tx.Error) + return tx.Error + } + if setFalse.Error != nil { + logger.Errorf(ctx, "Failed to mark earlier versions inactive for %s: %+v", dbTrigger.TriggerKey.Name, setFalse.Error) + return setFalse.Error + } + + var artifactKeys []ArtifactKey + + db := r.db.Model(&ArtifactKey{}).Where(&dbTrigger.RunsOn).Find(&artifactKeys) + if db.Error != nil { + logger.Errorf(ctx, "Error %v", db.Error) + return db.Error + } + if len(artifactKeys) != len(dbTrigger.RunsOn) { + logger.Errorf(ctx, "Could not find all artifact keys for trigger: %+v, only found %v", dbTrigger.RunsOn, artifactKeys) + return fmt.Errorf("could not find all artifact keys for trigger") + } + dbTrigger.RunsOn = artifactKeys + + dbTrigger.TriggerKeyID = extantKey.ID + dbTrigger.TriggerKey = TriggerKey{} // zero out the artifact key + // This create should update the join table between individual triggers and artifact keys + tt := tx.Save(&dbTrigger) + if tx.Error != nil || tt.Error != nil { + if tx.Error != nil { + logger.Errorf(ctx, "Transaction error: %v", tx.Error) + return tx.Error + } + logger.Errorf(ctx, "Save query failed with: %v", tt.Error) + return tt.Error + } + var savedTrigger Trigger + tt = tx.Preload("TriggerKey").Preload("RunsOn").First(&savedTrigger, "id = ?", dbTrigger.ID) + if tx.Error != nil || tt.Error != nil { + if tx.Error != nil { + logger.Errorf(ctx, "Transaction error: %v", tx.Error) + return tx.Error + } + logger.Errorf(ctx, "Failed to find trigger that was just saved: %+v", tx.Error) + return tt.Error + } + + // Next update the active_trigger_artifact_keys join table that keeps track of active key relationships + // That is, if you have a trigger_on=[artifactA, artifactB], this table links the trigger's name to those + // artifact names. If you register a new version of the trigger that is just trigger_on=[artifactC], then + // this table should just hold the reference to artifactC. + err := tx.Model(&savedTrigger.TriggerKey).Association("RunsOn").Replace(savedTrigger.RunsOn) + if err != nil { + logger.Errorf(ctx, "Failed to update active_trigger_artifact_keys: %+v", err) + return err + } + + return nil + }) + if err != nil { + if database.IsPgErrorWithCode(err, database.PgDuplicatedKey) { + logger.Infof(ctx, "Duplicate key detected, the current transaction will be cancelled: %s %s", trigger.Name, trigger.Version) + // TODO: Replace with the retrieved Trigger object maybe + // TODO: Add an error handling layer that translates from pg errors to a general service error. + return models.Trigger{}, err + } else { + logger.Errorf(ctx, "Failed transaction upsert on key [%s]: %+v", trigger.Name, err) + return models.Trigger{}, err + } + } + timer.Stop() + + return models.Trigger{}, nil +} + +func (r *RDSStorage) GetLatestTrigger(ctx context.Context, project, domain, name string) (models.Trigger, error) { + var gotTrigger Trigger + tk := TriggerKey{ + Project: project, + Domain: domain, + Name: name, + } + db := r.db.Model(&Trigger{}).InnerJoins("TriggerKey", r.db.Where(&tk)) + db = db.Where("active = true").Order("created_at desc").Limit(1).First(&gotTrigger) + if err := db.Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + logger.Infof(ctx, "Trigger not found: %+v", tk) + return models.Trigger{}, fmt.Errorf("could not find a latest trigger") + } + logger.Errorf(ctx, "Failed to query for triggers: %+v", err) + return models.Trigger{}, err + } + logger.Debugf(ctx, "Found and returning trigger obj %v", gotTrigger) + + m, err := GormToServiceTrigger(gotTrigger) + if err != nil { + logger.Errorf(ctx, "Failed to convert gorm model to service model: %+v", err) + return models.Trigger{}, err + } + return m, nil +} + +// GetTriggersByArtifactKey - Given an artifact key, presumably of a newly created artifact, find active triggers that +// trigger on that key (and potentially others). +func (r *RDSStorage) GetTriggersByArtifactKey(ctx context.Context, key core.ArtifactKey) ([]models.Trigger, error) { + // First find the trigger keys relevant to the artifact key + // then find the most recent, active, version of each trigger key. + + var triggerKey []TriggerKey + db := r.db.Preload("RunsOn"). + Joins("inner join active_trigger_artifact_keys ug on ug.trigger_key_id = trigger_keys.id "). + Joins("inner join artifact_keys g on g.id= ug.artifact_key_id "). + Where("g.project = ? and g.domain = ? and g.name = ?", key.Project, key.Domain, key.Name). + Find(&triggerKey) + + err := db.Error + if err != nil { + logger.Errorf(ctx, "Failed to find triggers for artifact key %v: %+v", key, err) + return nil, err + } + logger.Debugf(ctx, "Found trigger keys: %+v for artifact key %v", triggerKey, key) + if triggerKey == nil || len(triggerKey) == 0 { + logger.Infof(ctx, "No triggers found for artifact key %v", key) + return nil, nil + } + + ts := make([]Trigger, len(triggerKey)) + + var triggerCondition = r.db.Where(&triggerKey[0]) + if len(triggerKey) > 1 { + for _, tk := range triggerKey[1:] { + triggerCondition = triggerCondition.Or(&tk) + } + } + + db = r.db.Preload("RunsOn").Model(&Trigger{}).InnerJoins("TriggerKey", triggerCondition).Where("active = true").Find(&ts) + if err := db.Error; err != nil { + logger.Errorf(ctx, "Failed to query for triggers: %+v", err) + return nil, err + } + logger.Debugf(ctx, "Found (%d) triggers %v", len(ts), ts) + + modelTriggers := make([]models.Trigger, len(ts)) + for i, t := range ts { + st, err := GormToServiceTrigger(t) + if err != nil { + logger.Errorf(ctx, "Failed to convert gorm model to service model: %+v", err) + return nil, err + } + modelTriggers[i] = st + } + + return modelTriggers, nil +} + +func (r *RDSStorage) SearchArtifacts(ctx context.Context, request artifact.SearchArtifactsRequest) ([]models.Artifact, string, error) { + + var offset = 0 + var err error + if request.GetToken() != "" { + offset, err = strconv.Atoi(request.GetToken()) + if err != nil { + logger.Errorf(ctx, "Failed to convert offset to int: %+v", err) + return nil, "", err + } + } + + var db *gorm.DB + if request.GetOptions() != nil && request.GetOptions().LatestByKey { + db = r.db.Select(`distinct on ("ArtifactKey"."project", "ArtifactKey"."domain", "ArtifactKey"."name") "artifacts".*`).Model(&Artifact{}) + } else { + db = r.db.Model(&Artifact{}) + } + + if request.GetArtifactKey() != nil { + gormAk := ArtifactKey{ + Project: request.GetArtifactKey().Project, + Domain: request.GetArtifactKey().Domain, + Name: request.GetArtifactKey().Name, + } + db = db.InnerJoins("ArtifactKey", r.db.Where(&gormAk)) + } + + if request.GetOptions() != nil && request.GetOptions().StrictPartitions { + // strict means if partitions is not specified, then partitions is set to empty + if request.GetPartitions() != nil && len(request.GetPartitions().GetValue()) > 0 { + partitionMap := PartitionsIdlToHstore(request.GetPartitions()) + db = db.Where("partitions = ?", partitionMap) + } else { + db = db.Where("partitions is null or partitions = ''") + } + } else { + // make this not-strict, and make the handleArtifactIdGet one strict. + // this should be what the @> comparison does. + if request.GetPartitions() != nil && len(request.GetPartitions().GetValue()) > 0 { + partitionMap := PartitionsIdlToHstore(request.GetPartitions()) + db = db.Where("partitions @> ?", partitionMap) + } + } + + if request.Principal != "" { + db = db.Where("principal = ?", request.Principal) + } + + if request.Version != "" { + db = db.Where("version = ?", request.Version) + } + + var limit = 10 + if request.Limit != 0 { + limit = int(request.Limit) + } + if request.GetOptions() != nil && request.GetOptions().LatestByKey { + db.Order(`"ArtifactKey"."project", "ArtifactKey"."domain", "ArtifactKey"."name", created_at desc`) + } else { + db.Order("created_at desc") + } + + var results []Artifact + db.Limit(limit).Offset(offset).Find(&results) + + if len(results) == 0 { + logger.Debugf(ctx, "No artifacts found for query: %+v", request) + return nil, "", nil + } + var res = make([]models.Artifact, len(results)) + for i, ga := range results { + a, err := GormToServiceModel(ga) + if err != nil { + logger.Errorf(ctx, "Failed to convert %v: %+v", ga, err) + return nil, "", err + } + res[i] = a + } + + return res, fmt.Sprintf("%d", offset+len(res)), nil +} + +func (r *RDSStorage) SetExecutionInputs(ctx context.Context, req *artifact.ExecutionInputsRequest) error { + err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + + var newInputs = make([]Artifact, len(req.GetInputs())) + for i, a := range req.GetInputs() { + if a.Version == "" { + return fmt.Errorf("version must be specified for artifact %v", a.ArtifactKey) + } + ak := ArtifactKey{ + Project: a.ArtifactKey.Project, + Domain: a.ArtifactKey.Domain, + Name: a.ArtifactKey.Name, + } + var dbArtifact Artifact + r.db.Model(&Artifact{}).InnerJoins("ArtifactKey", r.db.Where(&ak)).Where("version = ?", a.Version).First(&dbArtifact) + newInputs[i] = dbArtifact + } + we := WorkflowExecution{ + ExecutionProject: req.ExecutionId.Project, + ExecutionDomain: req.ExecutionId.Domain, + ExecutionName: req.ExecutionId.Name, + } + var dbWe WorkflowExecution + // Create the execution if it doesn't exist. When we hit this part, we're keeping track of what artifacts + // an execution used as inputs. We need to create because the execution itself might otherwise never exist + // in the artifact db if it doesn't itself produce artifacts. + tx.FirstOrCreate(&dbWe, we) + if err := tx.Error; err != nil { + logger.Errorf(ctx, "Failed to firstorcreate workflow exec %v: %+v", we, err) + return err + } + + err := tx.Model(&dbWe).Association("InputArtifacts").Append(&newInputs) + + if err != nil { + logger.Errorf(ctx, "Failed to update input artifacts: %+v", err) + return err + } + + return nil + }) + + return err +} + +func (r *RDSStorage) findWorkflowInputs(ctx context.Context, we WorkflowExecution) ([]Artifact, error) { + var usedAsInputs []Artifact + var dbWe WorkflowExecution + err := r.db.Preload("InputArtifacts").Where(we).First(&dbWe).Error + if err != nil { + logger.Errorf(ctx, "The requested workflow execution %v does not exist: %+v", we, err) + return nil, err + } + err = r.db.Model(&dbWe).Association("InputArtifacts").Find(&usedAsInputs) + if err != nil { + logger.Errorf(ctx, "Failed to find artifacts used as inputs for workflow execution %v: %+v", we, err) + return nil, err + } + return usedAsInputs, nil +} + +func (r *RDSStorage) FindByWorkflowExec(ctx context.Context, request *artifact.FindByWorkflowExecRequest) ([]models.Artifact, error) { + we := WorkflowExecution{ + ExecutionProject: request.ExecId.Project, + ExecutionDomain: request.ExecId.Domain, + ExecutionName: request.ExecId.Name, + } + var artifacts []Artifact + var err error + if request.Direction == artifact.FindByWorkflowExecRequest_INPUTS { + // What are the artifacts that this workflow execution used as inputs? + artifacts, err = r.findWorkflowInputs(ctx, we) + } else { + // What artifacts were produced as outputs by this workflow execution? + db := r.db.Model(&Artifact{}).InnerJoins("WorkflowExecution", r.db.Where(&we)) + db.Order("created_at desc").Find(&artifacts) + err = db.Error + } + + if err != nil { + logger.Errorf(ctx, "Failed to find artifacts for workflow execution: %+v", err) + return nil, err + } + + if len(artifacts) == 0 { + logger.Debugf(ctx, "No artifacts found for workflow execution: %+v", request) + return nil, nil + } + var res = make([]models.Artifact, len(artifacts)) + for i, ga := range artifacts { + a, err := GormToServiceModel(ga) + if err != nil { + logger.Errorf(ctx, "Failed to convert %v: %+v", ga, err) + return nil, err + } + res[i] = a + } + + return res, nil +} + +func NewStorage(ctx context.Context, scope promutils.Scope) *RDSStorage { + dbCfg := configuration.ApplicationConfig.GetConfig().(*configuration.ApplicationConfiguration).ArtifactDatabaseConfig + logConfig := logger.GetConfig() + + db, err := database.GetDB(ctx, &dbCfg, logConfig) + if err != nil { + logger.Fatal(ctx, err) + } + return &RDSStorage{ + config: dbCfg, + db: db, + metrics: newMetrics(scope), + } +} diff --git a/flyteartifacts/pkg/db/storage_test.go b/flyteartifacts/pkg/db/storage_test.go new file mode 100644 index 0000000000..ef99d160b1 --- /dev/null +++ b/flyteartifacts/pkg/db/storage_test.go @@ -0,0 +1,149 @@ +//go:build local_integration + +// Eduardo - add this build tag to run this test + +package db + +import ( + "context" + "fmt" + "github.com/flyteorg/flyte/flyteartifacts/pkg/models" + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/artifact" + "os" + "testing" + + "github.com/flyteorg/flyte/flytestdlib/config" + "github.com/flyteorg/flyte/flytestdlib/config/viper" + "github.com/flyteorg/flyte/flytestdlib/promutils" + "github.com/stretchr/testify/assert" + + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/core" +) + +func TestBasicWrite(t *testing.T) { + ctx := context.Background() + sandboxCfgFile := os.ExpandEnv("$GOPATH/src/github.com/flyteorg/flyte/flyteartifacts/sandbox.yaml") + configAccessor := viper.NewAccessor(config.Options{ + SearchPaths: []string{sandboxCfgFile}, + StrictMode: false, + }) + err := configAccessor.UpdateConfig(ctx) + + fmt.Println("Local integration testing using: ", configAccessor.ConfigFilesUsed()) + scope := promutils.NewTestScope() + rds := NewStorage(ctx, scope) + + lt := &core.LiteralType{ + Type: &core.LiteralType_Simple{Simple: core.SimpleType_INTEGER}, + } + lit := &core.Literal{ + Value: &core.Literal_Scalar{ + Scalar: &core.Scalar{ + Value: &core.Scalar_Primitive{ + Primitive: &core.Primitive{ + Value: &core.Primitive_Integer{Integer: 15}, + }, + }, + }, + }, + } + + ak := &core.ArtifactKey{ + Project: "demotst", + Domain: "unit", + Name: "artfname 10", + } + source := &artifact.ArtifactSource{ + WorkflowExecution: &core.WorkflowExecutionIdentifier{ + Project: "demotst", + Domain: "unit", + Name: "exectest1", + }, + Principal: "userone", + NodeId: "testnodeid", + TaskId: &core.Identifier{ + ResourceType: core.ResourceType_TASK, + Project: "demotst", + Domain: "unit", + Name: "testtaskname 2", + Version: "testtaskversion", + }, + } + spec := &artifact.ArtifactSpec{ + Value: lit, + Type: lt, + ShortDescription: "", + UserMetadata: nil, + MetadataType: "", + } + partitions := map[string]string{ + "area": "51", + "ds": "2023-05-01", + } + + // Create one + am, err := models.CreateArtifactModelFromRequest(ctx, ak, spec, "abc123/1/n0/1", partitions, "tag", source) + assert.NoError(t, err) + + newModel, err := rds.CreateArtifact(ctx, am) + assert.NoError(t, err) + fmt.Println(newModel) + + // Create another + am, err = models.CreateArtifactModelFromRequest(ctx, ak, spec, "abc123/1/n0/2", partitions, "tag", source) + assert.NoError(t, err) + + newModel, err = rds.CreateArtifact(ctx, am) + assert.NoError(t, err) + fmt.Println(newModel) +} + +func TestBasicRead(t *testing.T) { + ctx := context.Background() + sandboxCfgFile := os.ExpandEnv("$GOPATH/src/github.com/flyteorg/flyte/flyteartifacts/sandbox.yaml") + configAccessor := viper.NewAccessor(config.Options{ + SearchPaths: []string{sandboxCfgFile}, + StrictMode: false, + }) + err := configAccessor.UpdateConfig(ctx) + fmt.Println("Local integration testing using: ", configAccessor.ConfigFilesUsed()) + + scope := promutils.NewTestScope() + rds := NewStorage(ctx, scope) + ak := &core.ArtifactKey{ + Project: "demotst", + Domain: "unit", + Name: "artfname 10", + } + partitions := map[string]string{ + "area": "51", + "ds": "2023-05-01", + } + query := core.ArtifactQuery{ + Identifier: &core.ArtifactQuery_ArtifactId{ + ArtifactId: &core.ArtifactID{ + ArtifactKey: ak, + Version: "abc123/1/n0/1", + }, + }, + } + _, err = rds.GetArtifact(context.Background(), query) + assert.Error(t, err) + + pidl := models.PartitionsToIdl(partitions) + query = core.ArtifactQuery{ + Identifier: &core.ArtifactQuery_ArtifactId{ + ArtifactId: &core.ArtifactID{ + ArtifactKey: ak, + Version: "abc123/1/n0/1", + Dimensions: &core.ArtifactID_Partitions{ + Partitions: pidl, + }, + }, + }, + } + ga, err := rds.GetArtifact(context.Background(), query) + assert.NoError(t, err) + + fmt.Println(ga) +} diff --git a/flyteartifacts/pkg/lib/constants.go b/flyteartifacts/pkg/lib/constants.go new file mode 100644 index 0000000000..845b570525 --- /dev/null +++ b/flyteartifacts/pkg/lib/constants.go @@ -0,0 +1,4 @@ +package lib + +// ArtifactKey - This is used to tag Literals as a tracking bit. +const ArtifactKey = "_ua" diff --git a/flyteartifacts/pkg/lib/string_converter.go b/flyteartifacts/pkg/lib/string_converter.go new file mode 100644 index 0000000000..b85e56ee2a --- /dev/null +++ b/flyteartifacts/pkg/lib/string_converter.go @@ -0,0 +1,58 @@ +package lib + +import ( + "fmt" + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/core" + "strings" + "time" +) + +const DateFormat string = "2006-01-02" + +func RenderLiteral(lit *core.Literal) (string, error) { + if lit == nil { + return "", fmt.Errorf("can't RenderLiteral, input is nil") + } + + switch lit.Value.(type) { + case *core.Literal_Scalar: + scalar := lit.GetScalar() + if scalar.GetPrimitive() == nil { + return "", fmt.Errorf("rendering only works for primitives, got [%v]", scalar) + } + // todo: figure out how to expose more formatting + // todo: maybe add a metric to each one of these, or this whole block. + switch scalar.GetPrimitive().GetValue().(type) { + case *core.Primitive_StringValue: + return scalar.GetPrimitive().GetStringValue(), nil + case *core.Primitive_Integer: + return fmt.Sprintf("%d", scalar.GetPrimitive().GetInteger()), nil + case *core.Primitive_FloatValue: + return fmt.Sprintf("%v", scalar.GetPrimitive().GetFloatValue()), nil + case *core.Primitive_Boolean: + if scalar.GetPrimitive().GetBoolean() { + return "true", nil + } + return "false", nil + case *core.Primitive_Datetime: + // just date for now, not sure if we should support time... + dt := scalar.GetPrimitive().GetDatetime().AsTime() + txt := dt.Format(DateFormat) + return txt, nil + case *core.Primitive_Duration: + dur := scalar.GetPrimitive().GetDuration().AsDuration() + // Found somewhere as iso8601 representation of duration, but there's still lots of + // possibilities for formatting. + txt := "PT" + strings.ToUpper(dur.Truncate(time.Millisecond).String()) + return txt, nil + default: + return "", fmt.Errorf("unknown primitive type [%v]", scalar.GetPrimitive()) + } + case *core.Literal_Collection: + return "", fmt.Errorf("can't RenderLiteral for collections") + case *core.Literal_Map: + return "", fmt.Errorf("can't RenderLiteral for maps") + } + + return "", fmt.Errorf("unknown literal type [%v]", lit) +} diff --git a/flyteartifacts/pkg/lib/string_converter_test.go b/flyteartifacts/pkg/lib/string_converter_test.go new file mode 100644 index 0000000000..ec50b1f46d --- /dev/null +++ b/flyteartifacts/pkg/lib/string_converter_test.go @@ -0,0 +1,24 @@ +package lib + +import ( + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/core" + "github.com/golang/protobuf/ptypes/timestamp" + "github.com/stretchr/testify/assert" + "testing" + "time" +) + +func TestRenderDate(t *testing.T) { + dt := time.Date(2020, 12, 8, 0, 0, 0, 0, time.UTC) + pt := timestamp.Timestamp{ + Seconds: dt.Unix(), + Nanos: 0, + } + lit := core.Literal{ + Value: &core.Literal_Scalar{Scalar: &core.Scalar{Value: &core.Scalar_Primitive{Primitive: &core.Primitive{Value: &core.Primitive_Datetime{Datetime: &pt}}}}}, + } + + txt, err := RenderLiteral(&lit) + assert.NoError(t, err) + assert.Equal(t, "2020-12-08", txt) +} diff --git a/flyteartifacts/pkg/lib/url_parse.go b/flyteartifacts/pkg/lib/url_parse.go new file mode 100644 index 0000000000..41bb5df16a --- /dev/null +++ b/flyteartifacts/pkg/lib/url_parse.go @@ -0,0 +1,71 @@ +package lib + +import ( + "errors" + "github.com/flyteorg/flyte/flyteartifacts/pkg/models" + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/core" + "net/url" + "regexp" + "strings" +) + +var flyteURLNameRe = regexp.MustCompile(`(?P[\w/-]+)(?:(:(?P\w+))?)(?:(@(?P\w+))?)`) + +func ParseFlyteURL(urlStr string) (core.ArtifactID, string, error) { + if len(urlStr) == 0 { + return core.ArtifactID{}, "", errors.New("URL cannot be empty") + } + + parsed, err := url.Parse(urlStr) + if err != nil { + return core.ArtifactID{}, "", err + } + queryValues, err := url.ParseQuery(parsed.RawQuery) + if err != nil { + return core.ArtifactID{}, "", err + } + projectDomainName := strings.Split(strings.Trim(parsed.Path, "/"), "/") + if len(projectDomainName) < 3 { + return core.ArtifactID{}, "", errors.New("invalid URL format") + } + project, domain, name := projectDomainName[0], projectDomainName[1], strings.Join(projectDomainName[2:], "/") + version := "" + tag := "" + queryDict := make(map[string]string) + + if match := flyteURLNameRe.FindStringSubmatch(name); match != nil { + name = match[1] + if match[3] != "" { + tag = match[3] + } + if match[5] != "" { + version = match[5] + } + + if tag != "" && (version != "" || len(queryValues) > 0) { + return core.ArtifactID{}, "", errors.New("cannot specify tag with version or querydict") + } + } + + for key, values := range queryValues { + if len(values) > 0 { + queryDict[key] = values[0] + } + } + + a := core.ArtifactID{ + ArtifactKey: &core.ArtifactKey{ + Project: project, + Domain: domain, + Name: name, + }, + Version: version, + } + + p := models.PartitionsToIdl(queryDict) + a.Dimensions = &core.ArtifactID_Partitions{ + Partitions: p, + } + + return a, tag, nil +} diff --git a/flyteartifacts/pkg/lib/url_parse_test.go b/flyteartifacts/pkg/lib/url_parse_test.go new file mode 100644 index 0000000000..3739b4c2a6 --- /dev/null +++ b/flyteartifacts/pkg/lib/url_parse_test.go @@ -0,0 +1,63 @@ +package lib + +import ( + "context" + "github.com/flyteorg/flyte/flyteartifacts/pkg/models" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestURLParseWithTag(t *testing.T) { + artifactID, tag, err := ParseFlyteURL("flyte://av0.1/project/domain/name:tag") + assert.NoError(t, err) + + assert.Equal(t, "project", artifactID.ArtifactKey.Project) + assert.Equal(t, "domain", artifactID.ArtifactKey.Domain) + assert.Equal(t, "name", artifactID.ArtifactKey.Name) + assert.Equal(t, "", artifactID.Version) + assert.Equal(t, "tag", tag) + assert.Nil(t, artifactID.GetPartitions()) +} + +func TestURLParseWithVersionAndPartitions(t *testing.T) { + artifactID, tag, err := ParseFlyteURL("flyte://av0.1/project/domain/name@version?foo=bar&ham=spam") + expPartitions := map[string]string{"foo": "bar", "ham": "spam"} + assert.NoError(t, err) + + assert.Equal(t, "project", artifactID.ArtifactKey.Project) + assert.Equal(t, "domain", artifactID.ArtifactKey.Domain) + assert.Equal(t, "name", artifactID.ArtifactKey.Name) + assert.Equal(t, "version", artifactID.Version) + assert.Equal(t, "", tag) + p := artifactID.GetPartitions() + mapP := models.PartitionsFromIdl(context.TODO(), p) + assert.Equal(t, expPartitions, mapP) +} + +func TestURLParseFailsWithBothTagAndPartitions(t *testing.T) { + _, _, err := ParseFlyteURL("flyte://av0.1/project/domain/name:tag?foo=bar&ham=spam") + assert.Error(t, err) +} + +func TestURLParseWithBothTagAndVersion(t *testing.T) { + _, _, err := ParseFlyteURL("flyte://av0.1/project/domain/name:tag@version") + assert.Error(t, err) +} + +func TestURLParseNameWithSlashes(t *testing.T) { + artifactID, tag, err := ParseFlyteURL("flyte://av0.1/project/domain/name/with/slashes") + assert.NoError(t, err) + assert.Equal(t, "project", artifactID.ArtifactKey.Project) + assert.Equal(t, "domain", artifactID.ArtifactKey.Domain) + assert.Equal(t, "name/with/slashes", artifactID.ArtifactKey.Name) + assert.Equal(t, "", tag) + + artifactID, tag, err = ParseFlyteURL("flyte://av0.1/project/domain/name/with/slashes?ds=2020-01-01") + assert.Equal(t, "name/with/slashes", artifactID.ArtifactKey.Name) + assert.Equal(t, "project", artifactID.ArtifactKey.Project) + assert.Equal(t, "domain", artifactID.ArtifactKey.Domain) + + expPartitions := map[string]string{"ds": "2020-01-01"} + + assert.Equal(t, expPartitions, models.PartitionsFromIdl(context.TODO(), artifactID.GetPartitions())) +} diff --git a/flyteartifacts/pkg/models/service_models.go b/flyteartifacts/pkg/models/service_models.go new file mode 100644 index 0000000000..ce811b5f8e --- /dev/null +++ b/flyteartifacts/pkg/models/service_models.go @@ -0,0 +1,36 @@ +package models + +import ( + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/admin" + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/artifact" + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/core" +) + +// Artifact is a wrapper object for easier handling of additional fields +type Artifact struct { + artifact.Artifact + OffloadedMetadata string + LiteralTypeBytes []byte + LiteralValueBytes []byte +} + +// Trigger - A trigger is nothing more than a launch plan, so wrap that. +type Trigger struct { + // The launch plan below doesn't have an ID field for the trigger directly (it's nested), so add one here. + Project string + Domain string + Name string + Version string + + LaunchPlanID core.Identifier + + // The trigger as defined in the user code becomes a launch plan as it is the most similar. + *admin.LaunchPlan + + RunsOn []core.ArtifactID + + // Additional meta fields relevant to the trigger. + Active bool + SpecBytes []byte + ClosureBytes []byte +} diff --git a/flyteartifacts/pkg/models/transformers.go b/flyteartifacts/pkg/models/transformers.go new file mode 100644 index 0000000000..64cd433cb5 --- /dev/null +++ b/flyteartifacts/pkg/models/transformers.go @@ -0,0 +1,173 @@ +package models + +import ( + "context" + "fmt" + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/artifact" + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/core" + "github.com/flyteorg/flyte/flytestdlib/logger" + "github.com/golang/protobuf/proto" + "github.com/golang/protobuf/ptypes" +) + +func CreateArtifactModelFromRequest(ctx context.Context, key *core.ArtifactKey, spec *artifact.ArtifactSpec, version string, partitions map[string]string, tag string, source *artifact.ArtifactSource) (Artifact, error) { + if key == nil || spec == nil { + return Artifact{}, fmt.Errorf("key and spec cannot be nil") + } + if len(version) == 0 { + return Artifact{}, fmt.Errorf("version cannot be empty") + } + if spec.Type == nil || spec.Value == nil { + return Artifact{}, fmt.Errorf("spec type and value cannot be nil") + } + + ex := source.GetWorkflowExecution() + if ex == nil { + return Artifact{}, fmt.Errorf("spec execution cannot be nil") + } + if ex.Project != key.Project || ex.Domain != key.Domain { + return Artifact{}, fmt.Errorf("spec execution must match key") + } + + a := artifact.Artifact{ + ArtifactId: &core.ArtifactID{ + ArtifactKey: &core.ArtifactKey{ + Project: key.Project, + Domain: key.Domain, + Name: key.Name, + }, + Version: version, + }, + Spec: spec, + Tags: []string{tag}, + Source: source, + } + + if partitions != nil { + cp := PartitionsToIdl(partitions) + a.ArtifactId.Dimensions = &core.ArtifactID_Partitions{ + Partitions: cp, + } + } + + ltBytes, err := proto.Marshal(spec.Type) + if err != nil { + logger.Errorf(ctx, "Failed to marshal type for artifact: %+v@%s, err: %v", key, version, err) + return Artifact{}, err + } + litBytes, err := proto.Marshal(spec.Value) + if err != nil { + logger.Errorf(ctx, "Failed to marshal literal value for artifact: %+v@%s, err: %v", key, version, err) + return Artifact{}, err + } + + return Artifact{ + Artifact: a, + LiteralTypeBytes: ltBytes, + LiteralValueBytes: litBytes, + }, nil +} + +func PartitionsToIdl(partitions map[string]string) *core.Partitions { + if partitions == nil || len(partitions) == 0 { + return nil + } + + cp := core.Partitions{ + Value: make(map[string]*core.LabelValue), + } + + for k, v := range partitions { + cp.Value[k] = &core.LabelValue{ + Value: &core.LabelValue_StaticValue{ + StaticValue: v, + }, + } + } + + return &cp +} + +func PartitionsFromIdl(ctx context.Context, partitions *core.Partitions) map[string]string { + if partitions == nil { + return nil + } + + p := make(map[string]string, len(partitions.Value)) + for k, v := range partitions.Value { + if len(v.GetStaticValue()) == 0 { + logger.Warningf(ctx, "Partition key [%s] missing static value, [%+v]", k, v.GetValue()) + continue + } + p[k] = v.GetStaticValue() + } + + return p +} + +func CreateTriggerModelFromRequest(ctx context.Context, request *artifact.CreateTriggerRequest) (Trigger, error) { + if request.GetTriggerLaunchPlan().GetSpec() == nil || request.GetTriggerLaunchPlan().GetId() == nil || request.GetTriggerLaunchPlan().GetClosure() == nil { + logger.Errorf(ctx, "Something nil in CreateTrigger, [%+v]", request) + return Trigger{}, fmt.Errorf("invalid request to CreateTrigger, something is nil") + } + + spec := request.GetTriggerLaunchPlan().GetSpec() + if spec.GetEntityMetadata().GetLaunchConditions() == nil { + logger.Errorf(ctx, "Launch conditions cannot be nil in CreateTrigger, [%+v]", request) + return Trigger{}, fmt.Errorf("invalid request to CreateTrigger, launch conditions cannot be nil") + } + + lpID := request.GetTriggerLaunchPlan().GetId() + + lc := spec.GetEntityMetadata().GetLaunchConditions() + + var err error + idlTrigger := core.Trigger{} + err = ptypes.UnmarshalAny(lc, &idlTrigger) + if err != nil { + logger.Errorf(ctx, "Failed to unmarshal launch conditions to idl, metadata: [%+v]", spec.GetEntityMetadata()) + return Trigger{}, err + } + if len(idlTrigger.Triggers) == 0 { + return Trigger{}, fmt.Errorf("invalid request to CreateTrigger, launch conditions cannot be empty") + } + // Create a list of the Artifact IDs referenced by the trigger definition. + // Keep in mind: these are not real IDs, they just contain partial information like the name. + // Always set the referenced artifacts to the project/domain of the launch plan + var runsOnArtifactIDs = make([]core.ArtifactID, len(idlTrigger.Triggers)) + for i, t := range idlTrigger.Triggers { + runsOnArtifactIDs[i] = *t + runsOnArtifactIDs[i].ArtifactKey.Project = lpID.Project + runsOnArtifactIDs[i].ArtifactKey.Domain = lpID.Domain + } + + specBytes, err := proto.Marshal(request.GetTriggerLaunchPlan().GetSpec()) + if err != nil { + logger.Errorf(ctx, "Failed to marshal lp spec for CreateTrigger err: %v", err) + return Trigger{}, err + } + closureBytes, err := proto.Marshal(request.GetTriggerLaunchPlan().GetClosure()) + if err != nil { + logger.Errorf(ctx, "Failed to marshal lp closure for CreateTrigger err: %v", err) + return Trigger{}, err + } + + // Always set the project/domain of the trigger equal to the underlying launch plan + t := Trigger{ + Project: lpID.Project, + Domain: lpID.Domain, + Name: idlTrigger.TriggerId.Name, + // Use LP id for the version because the trigger doesn't come with its own + // version for now... too difficult to update the version of the trigger + // inside the launch conditions object during registration. + Version: lpID.Version, + LaunchPlanID: *lpID, + LaunchPlan: request.GetTriggerLaunchPlan(), + RunsOn: runsOnArtifactIDs, + Active: true, + SpecBytes: specBytes, + ClosureBytes: closureBytes, + } + + return t, nil +} diff --git a/flyteartifacts/pkg/server/interfaces.go b/flyteartifacts/pkg/server/interfaces.go new file mode 100644 index 0000000000..275b2d8343 --- /dev/null +++ b/flyteartifacts/pkg/server/interfaces.go @@ -0,0 +1,40 @@ +package server + +import ( + "context" + "github.com/flyteorg/flyte/flyteartifacts/pkg/models" + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/artifact" + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/core" + stdLibStorage "github.com/flyteorg/flyte/flytestdlib/storage" + "github.com/golang/protobuf/ptypes/any" +) + +type StorageInterface interface { + CreateArtifact(context.Context, models.Artifact) (models.Artifact, error) + + GetArtifact(context.Context, core.ArtifactQuery) (models.Artifact, error) + + CreateTrigger(context.Context, models.Trigger) (models.Trigger, error) + + GetLatestTrigger(ctx context.Context, project, domain, name string) (models.Trigger, error) + + GetTriggersByArtifactKey(ctx context.Context, key core.ArtifactKey) ([]models.Trigger, error) + + // DeleteTrigger(context.Context, models.Trigger) error + + SearchArtifacts(context.Context, artifact.SearchArtifactsRequest) ([]models.Artifact, string, error) + + SetExecutionInputs(ctx context.Context, req *artifact.ExecutionInputsRequest) error + + FindByWorkflowExec(ctx context.Context, request *artifact.FindByWorkflowExecRequest) ([]models.Artifact, error) +} + +type BlobStoreInterface interface { + OffloadArtifactCard(ctx context.Context, name string, version string, userMetadata *any.Any) (stdLibStorage.DataReference, error) + + RetrieveArtifactCard(context.Context, stdLibStorage.DataReference) (*any.Any, error) +} + +type TriggerHandlerInterface interface { + EvaluateNewArtifact(context.Context, *artifact.Artifact) ([]core.WorkflowExecutionIdentifier, error) +} diff --git a/flyteartifacts/pkg/server/metrics.go b/flyteartifacts/pkg/server/metrics.go new file mode 100644 index 0000000000..3ae7d5f8ba --- /dev/null +++ b/flyteartifacts/pkg/server/metrics.go @@ -0,0 +1,66 @@ +package server + +import ( + "github.com/flyteorg/flyte/flytestdlib/promutils" + "github.com/prometheus/client_golang/prometheus" + "time" +) + +type RequestMetrics struct { + scope promutils.Scope + requestDuration promutils.StopWatch + errCount prometheus.Counter + successCount prometheus.Counter +} + +func NewRequestMetrics(scope promutils.Scope, method string) RequestMetrics { + methodScope := scope.NewSubScope(method) + + return RequestMetrics{ + scope: methodScope, + requestDuration: methodScope.MustNewStopWatch("duration", + "recorded response time duration for endpoint", time.Millisecond), + errCount: methodScope.MustNewCounter("errors", "count of errors returned by endpoint"), + successCount: methodScope.MustNewCounter("success", "count of successful responses returned by endpoint"), + } +} + +type endpointMetrics struct { + scope promutils.Scope + + create RequestMetrics + list RequestMetrics + registerConsumer RequestMetrics + registerProducer RequestMetrics + createTrigger RequestMetrics + deleteTrigger RequestMetrics + addTag RequestMetrics + search RequestMetrics +} + +type ServiceMetrics struct { + Scope promutils.Scope + PanicCounter prometheus.Counter + + executionEndpointMetrics endpointMetrics +} + +func InitMetrics(scope promutils.Scope) ServiceMetrics { + return ServiceMetrics{ + Scope: scope, + PanicCounter: scope.MustNewCounter("handler_panic", + "panics encountered while handling requests to the artifact service"), + + executionEndpointMetrics: endpointMetrics{ + scope: scope, + create: NewRequestMetrics(scope, "create_artifact"), + list: NewRequestMetrics(scope, "list_artifacts"), + registerConsumer: NewRequestMetrics(scope, "register_consumer"), + registerProducer: NewRequestMetrics(scope, "register_producer"), + createTrigger: NewRequestMetrics(scope, "create_trigger"), + deleteTrigger: NewRequestMetrics(scope, "delete_trigger"), + addTag: NewRequestMetrics(scope, "add_tag"), + search: NewRequestMetrics(scope, "search"), + }, + } +} diff --git a/flyteartifacts/pkg/server/processor/channel_processor.go b/flyteartifacts/pkg/server/processor/channel_processor.go new file mode 100644 index 0000000000..741c5801f9 --- /dev/null +++ b/flyteartifacts/pkg/server/processor/channel_processor.go @@ -0,0 +1,117 @@ +package processor + +import ( + "bytes" + "context" + pbcloudevents "github.com/cloudevents/sdk-go/binding/format/protobuf/v2" + "github.com/cloudevents/sdk-go/v2/event" + flyteEvents "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/event" + "github.com/flyteorg/flyte/flytestdlib/logger" + "github.com/flyteorg/flyte/flytestdlib/sandboxutils" + "github.com/golang/protobuf/jsonpb" + "github.com/golang/protobuf/proto" + "time" +) + +type SandboxCloudEventsReceiver struct { + subChan <-chan sandboxutils.SandboxMessage + Handler EventsHandlerInterface +} + +func (p *SandboxCloudEventsReceiver) StartProcessing(ctx context.Context) { + for { + logger.Warningf(context.Background(), "Starting SandBox notifications processor") + err := p.run(ctx) + if err != nil { + logger.Errorf(context.Background(), "error with running processor err: [%v], sleeping and restarting", err) + time.Sleep(1000 * 1000 * 1000 * 1) + // metric + continue + } + break + } + logger.Warning(context.Background(), "Sandbox cloud event processor has stopped because context cancelled") +} + +func (p *SandboxCloudEventsReceiver) handleMessage(ctx context.Context, sandboxMsg sandboxutils.SandboxMessage) error { + ce := &event.Event{} + err := pbcloudevents.Protobuf.Unmarshal(sandboxMsg.Raw, ce) + if err != nil { + logger.Errorf(context.Background(), "error with unmarshalling message [%v]", err) + return err + } + logger.Debugf(ctx, "Cloud event received message [%+v]", ce) + // ce data should be a jsonpb Marshaled proto message, one of + // - event.CloudEventTaskExecution + // - event.CloudEventNodeExecution + // - event.CloudEventWorkflowExecution + // - event.CloudEventExecutionStart + ceData := bytes.NewReader(ce.Data()) + unmarshaler := jsonpb.Unmarshaler{} + + // Use the type to determine which proto message to unmarshal to. + var flyteEvent proto.Message + if ce.Type() == "com.flyte.resource.cloudevents.TaskExecution" { + flyteEvent = &flyteEvents.CloudEventTaskExecution{} + err = unmarshaler.Unmarshal(ceData, flyteEvent) + } else if ce.Type() == "com.flyte.resource.cloudevents.WorkflowExecution" { + flyteEvent = &flyteEvents.CloudEventWorkflowExecution{} + err = unmarshaler.Unmarshal(ceData, flyteEvent) + } else if ce.Type() == "com.flyte.resource.cloudevents.NodeExecution" { + flyteEvent = &flyteEvents.CloudEventNodeExecution{} + err = unmarshaler.Unmarshal(ceData, flyteEvent) + } else if ce.Type() == "com.flyte.resource.cloudevents.ExecutionStart" { + flyteEvent = &flyteEvents.CloudEventExecutionStart{} + err = unmarshaler.Unmarshal(ceData, flyteEvent) + } else { + logger.Warningf(ctx, "Ignoring cloud event type [%s]", ce.Type()) + return nil + } + if err != nil { + logger.Errorf(ctx, "error unmarshalling message on topic [%s] [%v]", sandboxMsg.Topic, err) + return err + } + + err = p.Handler.HandleEvent(ctx, ce, flyteEvent) + if err != nil { + logger.Errorf(context.Background(), "error handling event on topic [%s] [%v]", sandboxMsg.Topic, err) + return err + } + return nil +} + +func (p *SandboxCloudEventsReceiver) run(ctx context.Context) error { + for { + select { + case <-ctx.Done(): + logger.Warning(context.Background(), "Context cancelled, stopping processing.") + return nil + + case sandboxMsg := <-p.subChan: + // metric + logger.Debugf(ctx, "received message [%v]", sandboxMsg) + if sandboxMsg.Raw != nil { + err := p.handleMessage(ctx, sandboxMsg) + if err != nil { + // Assuming that handle message will return a fair number of errors + // add metric + logger.Infof(ctx, "error processing sandbox cloud event [%v] with err [%v]", sandboxMsg, err) + } + } else { + logger.Infof(ctx, "sandbox receiver ignoring message [%v]", sandboxMsg) + } + } + } +} + +func (p *SandboxCloudEventsReceiver) StopProcessing() error { + logger.Warning(context.Background(), "StopProcessing called on SandboxCloudEventsReceiver") + return nil +} + +func NewSandboxCloudEventProcessor(eventsHandler EventsHandlerInterface) *SandboxCloudEventsReceiver { + return &SandboxCloudEventsReceiver{ + Handler: eventsHandler, + subChan: sandboxutils.MsgChan, + } +} diff --git a/flyteartifacts/pkg/server/processor/events_handler.go b/flyteartifacts/pkg/server/processor/events_handler.go new file mode 100644 index 0000000000..dd47658f7c --- /dev/null +++ b/flyteartifacts/pkg/server/processor/events_handler.go @@ -0,0 +1,327 @@ +package processor + +import ( + "context" + "fmt" + event2 "github.com/cloudevents/sdk-go/v2/event" + "github.com/flyteorg/flyte/flyteartifacts/pkg/lib" + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/artifact" + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/core" + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/event" + "github.com/flyteorg/flyte/flytestdlib/logger" + "github.com/golang/protobuf/proto" +) + +// ServiceCallHandler will take events and call the grpc endpoints directly. The service should most likely be local. +type ServiceCallHandler struct { + service artifact.ArtifactRegistryServer + created chan<- artifact.Artifact +} + +func (s *ServiceCallHandler) HandleEvent(ctx context.Context, cloudEvent *event2.Event, msg proto.Message) error { + source := cloudEvent.Source() + + switch msgType := msg.(type) { + case *event.CloudEventExecutionStart: + logger.Debugf(ctx, "Handling CloudEventExecutionStart [%v]", msgType.ExecutionId) + return s.HandleEventExecStart(ctx, msgType) + case *event.CloudEventWorkflowExecution: + logger.Debugf(ctx, "Handling CloudEventWorkflowExecution [%v]", msgType.RawEvent.ExecutionId) + return s.HandleEventWorkflowExec(ctx, source, msgType) + case *event.CloudEventTaskExecution: + logger.Debugf(ctx, "Handling CloudEventTaskExecution [%v]", msgType.RawEvent.ParentNodeExecutionId) + return s.HandleEventTaskExec(ctx, source, msgType) + case *event.CloudEventNodeExecution: + logger.Debugf(ctx, "Handling CloudEventNodeExecution [%v]", msgType.RawEvent.Id) + return s.HandleEventNodeExec(ctx, source, msgType) + default: + return fmt.Errorf("HandleEvent found unknown message type [%T]", msgType) + } +} + +func (s *ServiceCallHandler) HandleEventExecStart(ctx context.Context, evt *event.CloudEventExecutionStart) error { + + if len(evt.ArtifactIds) > 0 { + // metric + req := &artifact.ExecutionInputsRequest{ + ExecutionId: evt.ExecutionId, + Inputs: evt.ArtifactIds, + } + _, err := s.service.SetExecutionInputs(ctx, req) + if err != nil { + logger.Errorf(ctx, "failed to set execution inputs for [%v] with error: %v", evt.ExecutionId, err) + return err + } + } + + return nil +} + +// HandleEventWorkflowExec and the task one below are very similar. Can be combined in the future. +func (s *ServiceCallHandler) HandleEventWorkflowExec(ctx context.Context, source string, evt *event.CloudEventWorkflowExecution) error { + + if evt.RawEvent.Phase != core.WorkflowExecution_SUCCEEDED { + logger.Debug(ctx, "Skipping non-successful workflow execution event") + return nil + } + + execID := evt.RawEvent.ExecutionId + if evt.GetOutputData().GetLiterals() == nil || len(evt.OutputData.Literals) == 0 { + logger.Debugf(ctx, "No output data to process for workflow event from [%v]", execID) + } + + for varName, variable := range evt.OutputInterface.Outputs.Variables { + if variable.GetArtifactPartialId() != nil { + logger.Debugf(ctx, "Processing workflow output for %s, artifact name %s, from %v", varName, variable.GetArtifactPartialId().ArtifactKey.Name, execID) + + output := evt.OutputData.Literals[varName] + + // Add a tracking tag to the Literal before saving. + version := fmt.Sprintf("%s/%s", source, varName) + trackingTag := fmt.Sprintf("%s/%s/%s", execID.Project, execID.Domain, version) + if output.Metadata == nil { + output.Metadata = make(map[string]string, 1) + } + output.Metadata[lib.ArtifactKey] = trackingTag + + aSrc := &artifact.ArtifactSource{ + WorkflowExecution: execID, + NodeId: "end-node", + Principal: evt.Principal, + } + + spec := artifact.ArtifactSpec{ + Value: output, + Type: evt.OutputInterface.Outputs.Variables[varName].Type, + } + + partitions, tag, err := getPartitionsAndTag( + ctx, + *variable.GetArtifactPartialId(), + variable, + evt.InputData, + ) + if err != nil { + logger.Errorf(ctx, "failed processing [%s] variable [%v] with error: %v", varName, variable, err) + return err + } + ak := core.ArtifactKey{ + Project: execID.Project, + Domain: execID.Domain, + Name: variable.GetArtifactPartialId().ArtifactKey.Name, + } + + req := artifact.CreateArtifactRequest{ + ArtifactKey: &ak, + Version: version, + Spec: &spec, + Partitions: partitions, + Tag: tag, + Source: aSrc, + } + + resp, err := s.service.CreateArtifact(ctx, &req) + if err != nil { + logger.Errorf(ctx, "failed to create artifact for [%s] with error: %v", varName, err) + return err + } + // metric + select { + case s.created <- *resp.Artifact: + logger.Debugf(ctx, "Sent %v from handle workflow", resp.Artifact.ArtifactId) + default: + // metric + logger.Debugf(ctx, "Channel is full. didn't send %v", resp.Artifact.ArtifactId) + } + logger.Debugf(ctx, "Created wf artifact id [%+v] for key %s", resp.Artifact.ArtifactId, varName) + } + } + + return nil +} + +func getPartitionsAndTag(ctx context.Context, partialID core.ArtifactID, variable *core.Variable, inputData *core.LiteralMap) (map[string]string, string, error) { + if variable == nil || inputData == nil { + return nil, "", fmt.Errorf("variable or input data is nil") + } + + var partitions map[string]string + // todo: consider updating idl to make CreateArtifactRequest just take a full Partitions + // object rather than a mapstrstr @eapolinario @enghabu + if partialID.GetPartitions().GetValue() != nil && len(partialID.GetPartitions().GetValue()) > 0 { + partitions = make(map[string]string, len(partialID.GetPartitions().GetValue())) + for k, lv := range partialID.GetPartitions().GetValue() { + if lv.GetStaticValue() != "" { + partitions[k] = lv.GetStaticValue() + } else if lv.GetInputBinding() != nil { + if lit, ok := inputData.Literals[lv.GetInputBinding().GetVar()]; ok { + // todo: figure out formatting. Maybe we can add formatting directives to the input binding + // @enghabu @eapolinario + renderedStr, err := lib.RenderLiteral(lit) + if err != nil { + logger.Errorf(ctx, "failed to render literal for input [%s] partition [%s] with error: %v", lv.GetInputBinding().GetVar(), k, err) + return nil, "", err + } + partitions[k] = renderedStr + } else { + return nil, "", fmt.Errorf("input binding [%s] not found in input data", lv.GetInputBinding().GetVar()) + } + } else { + return nil, "", fmt.Errorf("unknown binding found in context of a materialized artifact") + } + } + } + + var tag = "" + var err error + if lv := variable.GetArtifactTag().GetValue(); lv != nil { + if lv.GetStaticValue() != "" { + tag = lv.GetStaticValue() + } else if lv.GetInputBinding() != nil { + tag, err = lib.RenderLiteral(inputData.Literals[lv.GetInputBinding().GetVar()]) + if err != nil { + logger.Errorf(ctx, "failed to render input [%s] for tag with error: %v", lv.GetInputBinding().GetVar(), err) + return nil, "", err + } + } else { + return nil, "", fmt.Errorf("triggered binding found in context of a materialized artifact when rendering tag") + } + } + + return partitions, tag, nil +} + +func (s *ServiceCallHandler) HandleEventTaskExec(ctx context.Context, _ string, evt *event.CloudEventTaskExecution) error { + + if evt.RawEvent.Phase != core.TaskExecution_SUCCEEDED { + logger.Debug(ctx, "Skipping non-successful task execution event") + return nil + } + // metric + + return nil +} + +func (s *ServiceCallHandler) HandleEventNodeExec(ctx context.Context, source string, evt *event.CloudEventNodeExecution) error { + if evt.RawEvent.Phase != core.NodeExecution_SUCCEEDED { + logger.Debug(ctx, "Skipping non-successful task execution event") + return nil + } + if evt.RawEvent.Id.NodeId == "end-node" { + logger.Debug(ctx, "Skipping end node for %s", evt.RawEvent.Id.ExecutionId.Name) + return nil + } + // metric + + execID := evt.RawEvent.Id.ExecutionId + if evt.GetOutputData().GetLiterals() == nil || len(evt.OutputData.Literals) == 0 { + logger.Debugf(ctx, "No output data to process for task event from [%s] node %s", execID, evt.RawEvent.Id.NodeId) + } + + if evt.OutputInterface == nil { + if evt.GetOutputData() != nil { + // metric this as error + logger.Errorf(ctx, "No output interface to process for task event from [%s] node %s, but output data is not nil", execID, evt.RawEvent.Id.NodeId) + } + logger.Debugf(ctx, "No output interface to process for task event from [%s] node %s", execID, evt.RawEvent.Id.NodeId) + return nil + } + + if evt.RawEvent.GetTaskNodeMetadata() != nil { + if evt.RawEvent.GetTaskNodeMetadata().CacheStatus == core.CatalogCacheStatus_CACHE_HIT { + logger.Debugf(ctx, "Skipping cache hit for %s", evt.RawEvent.Id) + return nil + } + } + var taskExecID *core.TaskExecutionIdentifier + if taskExecID = evt.GetTaskExecId(); taskExecID == nil { + logger.Debugf(ctx, "No task execution id to process for task event from [%s] node %s", execID, evt.RawEvent.Id.NodeId) + } + + // See note on the cloudevent_publisher side, we'll have to call one of the get data endpoints to get the actual data + // rather than reading them here. But read here for now. + + // Iterate through the output interface. For any outputs that have an artifact ID specified, grab the + // output Literal and construct a Create request and call the service. + for varName, variable := range evt.OutputInterface.Outputs.Variables { + if variable.GetArtifactPartialId() != nil { + logger.Debugf(ctx, "Processing output for %s, artifact name %s, from %v", varName, variable.GetArtifactPartialId().ArtifactKey.Name, execID) + + output := evt.OutputData.Literals[varName] + + // Add a tracking tag to the Literal before saving. + version := fmt.Sprintf("%s/%d/%s", source, taskExecID.RetryAttempt, varName) + trackingTag := fmt.Sprintf("%s/%s/%s", execID.Project, execID.Domain, version) + if output.Metadata == nil { + output.Metadata = make(map[string]string, 1) + } + output.Metadata[lib.ArtifactKey] = trackingTag + + aSrc := &artifact.ArtifactSource{ + WorkflowExecution: execID, + NodeId: evt.RawEvent.Id.NodeId, + Principal: evt.Principal, + } + + if taskExecID != nil { + aSrc.RetryAttempt = taskExecID.RetryAttempt + aSrc.TaskId = taskExecID.TaskId + } + + spec := artifact.ArtifactSpec{ + Value: output, + Type: evt.OutputInterface.Outputs.Variables[varName].Type, + } + + partitions, tag, err := getPartitionsAndTag( + ctx, + *variable.GetArtifactPartialId(), + variable, + evt.InputData, + ) + if err != nil { + logger.Errorf(ctx, "failed processing [%s] variable [%v] with error: %v", varName, variable, err) + return err + } + ak := core.ArtifactKey{ + Project: execID.Project, + Domain: execID.Domain, + Name: variable.GetArtifactPartialId().ArtifactKey.Name, + } + + req := artifact.CreateArtifactRequest{ + ArtifactKey: &ak, + Version: version, + Spec: &spec, + Partitions: partitions, + Tag: tag, + Source: aSrc, + } + + resp, err := s.service.CreateArtifact(ctx, &req) + if err != nil { + logger.Errorf(ctx, "failed to create artifact for [%s] with error: %v", varName, err) + return err + } + // metric + select { + case s.created <- *resp.Artifact: + logger.Debugf(ctx, "Sent %v from handle task", resp.Artifact.ArtifactId) + default: + // metric + logger.Debugf(ctx, "Channel is full. task handler didn't send %v", resp.Artifact.ArtifactId) + } + + logger.Debugf(ctx, "Created artifact id [%+v] for key %s", resp.Artifact.ArtifactId, varName) + } + } + return nil +} + +func NewServiceCallHandler(ctx context.Context, svc artifact.ArtifactRegistryServer, created chan<- artifact.Artifact) EventsHandlerInterface { + logger.Infof(ctx, "Creating new service call handler") + return &ServiceCallHandler{ + service: svc, + created: created, + } +} diff --git a/flyteartifacts/pkg/server/processor/events_handler_test.go b/flyteartifacts/pkg/server/processor/events_handler_test.go new file mode 100644 index 0000000000..61d668215b --- /dev/null +++ b/flyteartifacts/pkg/server/processor/events_handler_test.go @@ -0,0 +1,17 @@ +package processor + +import ( + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/core" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestMetaDataWrite(t *testing.T) { + lit := core.Literal{ + Value: &core.Literal_Scalar{Scalar: &core.Scalar{Value: &core.Scalar_Primitive{Primitive: &core.Primitive{Value: &core.Primitive_StringValue{StringValue: "test"}}}}}, + } + lit.Metadata = make(map[string]string) + + lit.Metadata["test"] = "test" + assert.Equal(t, "test", lit.Metadata["test"]) +} diff --git a/flyteartifacts/pkg/server/processor/http_processor.go b/flyteartifacts/pkg/server/processor/http_processor.go new file mode 100644 index 0000000000..89e216d339 --- /dev/null +++ b/flyteartifacts/pkg/server/processor/http_processor.go @@ -0,0 +1,9 @@ +package processor + +import "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/artifact" + +// HTTPCloudEventProcessor could potentially run another local http service to handle traffic instead of a go channel +// todo: either implement or remote this file. +type HTTPCloudEventProcessor struct { + service *artifact.UnimplementedArtifactRegistryServer +} diff --git a/flyteartifacts/pkg/server/processor/interfaces.go b/flyteartifacts/pkg/server/processor/interfaces.go new file mode 100644 index 0000000000..ff61126adb --- /dev/null +++ b/flyteartifacts/pkg/server/processor/interfaces.go @@ -0,0 +1,22 @@ +package processor + +import ( + "context" + "github.com/cloudevents/sdk-go/v2/event" + "github.com/golang/protobuf/proto" +) + +type EventsHandlerInterface interface { + // HandleEvent The cloudEvent here is the original deserialized event and the proto msg is message + // that's been unmarshalled already from the cloudEvent.Data() field. + HandleEvent(ctx context.Context, cloudEvent *event.Event, msg proto.Message) error +} + +// EventsProcessorInterface is a copy of the notifications processor in admin except that start takes a context +type EventsProcessorInterface interface { + // StartProcessing whatever it is that needs to be processed. + StartProcessing(ctx context.Context) + + // StopProcessing is called when the server is shutting down. + StopProcessing() error +} diff --git a/flyteartifacts/pkg/server/processor/processor.go b/flyteartifacts/pkg/server/processor/processor.go new file mode 100644 index 0000000000..64aec5c149 --- /dev/null +++ b/flyteartifacts/pkg/server/processor/processor.go @@ -0,0 +1,55 @@ +package processor + +import ( + "context" + "github.com/NYTimes/gizmo/pubsub" + gizmoAWS "github.com/NYTimes/gizmo/pubsub/aws" + "github.com/flyteorg/flyte/flyteartifacts/pkg/configuration" + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/artifact" + configCommon "github.com/flyteorg/flyte/flytestdlib/config" + "github.com/flyteorg/flyte/flytestdlib/logger" + "github.com/flyteorg/flyte/flytestdlib/promutils" +) + +func NewBackgroundProcessor(ctx context.Context, processorConfiguration configuration.EventProcessorConfiguration, service artifact.ArtifactRegistryServer, created chan<- artifact.Artifact, scope promutils.Scope) EventsProcessorInterface { + + // TODO: Add retry logic + var sub pubsub.Subscriber + switch processorConfiguration.CloudProvider { + case configCommon.CloudDeploymentAWS: + // TODO: When we start using this, the created channel will also need to be added to the pubsubprocessor + sqsConfig := gizmoAWS.SQSConfig{ + QueueName: processorConfiguration.Subscriber.QueueName, + QueueOwnerAccountID: processorConfiguration.Subscriber.AccountID, + // The AWS configuration type uses SNS to SQS for notifications. + // Gizmo by default will decode the SQS message using Base64 decoding. + // However, the message body of SQS is the SNS message format which isn't Base64 encoded. + // gatepr: Understand this when we do sqs testing + // TODO: + //ConsumeBase64: &enable64decoding, + } + sqsConfig.Region = processorConfiguration.Region + var err error + // gatepr: wrap this in retry + sub, err = gizmoAWS.NewSubscriber(sqsConfig) + if err != nil { + logger.Warnf(context.TODO(), "Failed to initialize new gizmo aws subscriber with config [%+v] and err: %v", sqsConfig, err) + } + + if err != nil { + panic(err) + } + return NewPubSubProcessor(sub, scope) + case configCommon.CloudDeploymentGCP: + panic("Artifacts not implemented for GCP") + case configCommon.CloudDeploymentSandbox: + handler := NewServiceCallHandler(ctx, service, created) + return NewSandboxCloudEventProcessor(handler) + case configCommon.CloudDeploymentLocal: + fallthrough + default: + logger.Infof(context.Background(), + "Not using a background events processor, cfg is [%s]", processorConfiguration) + return nil + } +} diff --git a/flyteartifacts/pkg/server/processor/sqs_processor.go b/flyteartifacts/pkg/server/processor/sqs_processor.go new file mode 100644 index 0000000000..ec82a65436 --- /dev/null +++ b/flyteartifacts/pkg/server/processor/sqs_processor.go @@ -0,0 +1,111 @@ +package processor + +import ( + "context" + "encoding/base64" + "encoding/json" + "github.com/NYTimes/gizmo/pubsub" + "github.com/flyteorg/flyte/flytestdlib/logger" + "github.com/flyteorg/flyte/flytestdlib/promutils" + "time" +) + +type PubSubProcessor struct { + sub pubsub.Subscriber +} + +func (p *PubSubProcessor) StartProcessing(ctx context.Context) { + for { + logger.Warningf(context.Background(), "Starting notifications processor") + err := p.run() + logger.Errorf(context.Background(), "error with running processor err: [%v] ", err) + time.Sleep(1000 * 1000 * 1000 * 5) + } +} + +// todo: i think we can easily add context +func (p *PubSubProcessor) run() error { + var err error + for msg := range p.sub.Start() { + // gatepr: add metrics + // Currently this is safe because Gizmo takes a string and casts it to a byte array. + stringMsg := string(msg.Message()) + + var snsJSONFormat map[string]interface{} + + // Typically, SNS populates SQS. This results in the message body of SQS having the SNS message format. + // The message format is documented here: https://docs.aws.amazon.com/sns/latest/dg/sns-message-and-json-formats.html + // The notification published is stored in the message field after unmarshalling the SQS message. + if err := json.Unmarshal(msg.Message(), &snsJSONFormat); err != nil { + //p.systemMetrics.MessageDecodingError.Inc() + logger.Errorf(context.Background(), "failed to unmarshall JSON message [%s] from processor with err: %v", stringMsg, err) + p.markMessageDone(msg) + continue + } + + var value interface{} + var ok bool + var valueString string + + if value, ok = snsJSONFormat["Message"]; !ok { + logger.Errorf(context.Background(), "failed to retrieve message from unmarshalled JSON object [%s]", stringMsg) + // p.systemMetrics.MessageDataError.Inc() + p.markMessageDone(msg) + continue + } + + if valueString, ok = value.(string); !ok { + // p.systemMetrics.MessageDataError.Inc() + logger.Errorf(context.Background(), "failed to retrieve notification message (in string format) from unmarshalled JSON object for message [%s]", stringMsg) + p.markMessageDone(msg) + continue + } + + // The Publish method for SNS Encodes the notification using Base64 then stringifies it before + // setting that as the message body for SNS. Do the inverse to retrieve the notification. + incomingMessageBytes, err := base64.StdEncoding.DecodeString(valueString) + if err != nil { + logger.Errorf(context.Background(), "failed to Base64 decode from message string [%s] from message [%s] with err: %v", valueString, stringMsg, err) + //p.systemMetrics.MessageDecodingError.Inc() + p.markMessageDone(msg) + continue + } + logger.Infof(context.Background(), "incoming message bytes [%v]", incomingMessageBytes) + // handle message + p.markMessageDone(msg) + } + + // According to https://github.com/NYTimes/gizmo/blob/f2b3deec03175b11cdfb6642245a49722751357f/pubsub/pubsub.go#L36-L39, + // the channel backing the subscriber will just close if there is an error. The call to Err() is needed to identify + // there was an error in the channel or there are no more messages left (resulting in no errors when calling Err()). + if err = p.sub.Err(); err != nil { + //p.systemMetrics.ChannelClosedError.Inc() + logger.Warningf(context.Background(), "The stream for the subscriber channel closed with err: %v", err) + } + + // If there are no errors, nil will be returned. + return err +} + +func (p *PubSubProcessor) markMessageDone(message pubsub.SubscriberMessage) { + if err := message.Done(); err != nil { + //p.systemMetrics.MessageDoneError.Inc() + logger.Errorf(context.Background(), "failed to mark message as Done() in processor with err: %v", err) + } +} + +func (p *PubSubProcessor) StopProcessing() error { + // Note: If the underlying channel is already closed, then Stop() will return an error. + err := p.sub.Stop() + if err != nil { + //p.systemMetrics.StopError.Inc() + logger.Errorf(context.Background(), "Failed to stop the subscriber channel gracefully with err: %v", err) + } + return err +} + +func NewPubSubProcessor(sub pubsub.Subscriber, _ promutils.Scope) *PubSubProcessor { + return &PubSubProcessor{ + sub: sub, + } +} diff --git a/flyteartifacts/pkg/server/server.go b/flyteartifacts/pkg/server/server.go new file mode 100644 index 0000000000..6ae88c312c --- /dev/null +++ b/flyteartifacts/pkg/server/server.go @@ -0,0 +1,159 @@ +package server + +import ( + "context" + "fmt" + "github.com/flyteorg/flyte/flyteartifacts/pkg/blob" + "github.com/flyteorg/flyte/flyteartifacts/pkg/configuration" + "github.com/flyteorg/flyte/flyteartifacts/pkg/db" + "github.com/flyteorg/flyte/flyteartifacts/pkg/server/processor" + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/artifact" + "github.com/flyteorg/flyte/flytestdlib/database" + "github.com/flyteorg/flyte/flytestdlib/logger" + "github.com/flyteorg/flyte/flytestdlib/promutils" + "github.com/go-gormigrate/gormigrate/v2" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/pkg/errors" + "google.golang.org/grpc" + _ "net/http/pprof" // Required to serve application. +) + +type ArtifactService struct { + artifact.UnimplementedArtifactRegistryServer + Metrics ServiceMetrics + Service CoreService + EventConsumer processor.EventsProcessorInterface + TriggerEngine TriggerHandlerInterface + eventsToTrigger chan artifact.Artifact +} + +func (a *ArtifactService) CreateArtifact(ctx context.Context, req *artifact.CreateArtifactRequest) (*artifact.CreateArtifactResponse, error) { + resp, err := a.Service.CreateArtifact(ctx, req) + if err != nil { + return resp, err + } + + // gatepr add go func + execIDs, err := a.TriggerEngine.EvaluateNewArtifact(ctx, resp.Artifact) + if err != nil { + logger.Warnf(ctx, "Failed to evaluate triggers for artifact: %v, err: %v", resp.Artifact, err) + } else { + logger.Infof(ctx, "Triggered %v executions", len(execIDs)) + } + return resp, err +} + +func (a *ArtifactService) GetArtifact(ctx context.Context, req *artifact.GetArtifactRequest) (*artifact.GetArtifactResponse, error) { + return a.Service.GetArtifact(ctx, req) +} + +func (a *ArtifactService) SearchArtifacts(ctx context.Context, req *artifact.SearchArtifactsRequest) (*artifact.SearchArtifactsResponse, error) { + return a.Service.SearchArtifacts(ctx, req) +} + +func (a *ArtifactService) CreateTrigger(ctx context.Context, req *artifact.CreateTriggerRequest) (*artifact.CreateTriggerResponse, error) { + return a.Service.CreateTrigger(ctx, req) +} + +func (a *ArtifactService) DeleteTrigger(ctx context.Context, req *artifact.DeleteTriggerRequest) (*artifact.DeleteTriggerResponse, error) { + return a.Service.DeleteTrigger(ctx, req) +} + +func (a *ArtifactService) AddTag(ctx context.Context, req *artifact.AddTagRequest) (*artifact.AddTagResponse, error) { + return a.Service.AddTag(ctx, req) +} + +func (a *ArtifactService) RegisterProducer(ctx context.Context, req *artifact.RegisterProducerRequest) (*artifact.RegisterResponse, error) { + return a.Service.RegisterProducer(ctx, req) +} + +func (a *ArtifactService) RegisterConsumer(ctx context.Context, req *artifact.RegisterConsumerRequest) (*artifact.RegisterResponse, error) { + return a.Service.RegisterConsumer(ctx, req) +} + +func (a *ArtifactService) SetExecutionInputs(ctx context.Context, req *artifact.ExecutionInputsRequest) (*artifact.ExecutionInputsResponse, error) { + return a.Service.SetExecutionInputs(ctx, req) +} + +func (a *ArtifactService) FindByWorkflowExec(ctx context.Context, req *artifact.FindByWorkflowExecRequest) (*artifact.SearchArtifactsResponse, error) { + return a.Service.FindByWorkflowExec(ctx, req) +} +func (a *ArtifactService) runProcessor(ctx context.Context) { + for { + select { + case art := <-a.eventsToTrigger: + logger.Infof(ctx, "Received artifact: %v", art) + execIDs, err := a.TriggerEngine.EvaluateNewArtifact(ctx, &art) + if err != nil { + logger.Warnf(ctx, "Failed to evaluate triggers for artifact: %v, err: %v", art, err) + } else { + logger.Infof(ctx, "Triggered %v executions", len(execIDs)) + } + case <-ctx.Done(): + logger.Infof(ctx, "Stopping artifact processor") + return + } + } +} + +func NewArtifactService(ctx context.Context, scope promutils.Scope) *ArtifactService { + cfg := configuration.GetApplicationConfig() + fmt.Println(cfg) + eventsCfg := configuration.GetEventsProcessorConfig() + + // channel that the event processor should use to pass created artifacts to, which this artifact server will + // then call the trigger engine with. + createdArtifacts := make(chan artifact.Artifact, 1000) + + storage := db.NewStorage(ctx, scope.NewSubScope("storage:rds")) + blobStore := blob.NewArtifactBlobStore(ctx, scope.NewSubScope("storage:s3")) + coreService := NewCoreService(storage, &blobStore, scope.NewSubScope("server")) + triggerHandler, err := NewTriggerEngine(ctx, storage, &coreService, scope.NewSubScope("triggers")) + if err != nil { + logger.Errorf(ctx, "Failed to create Admin client, stopping server. Error: %v", err) + panic(err) + } + eventsReceiverAndHandler := processor.NewBackgroundProcessor(ctx, *eventsCfg, &coreService, createdArtifacts, scope.NewSubScope("events")) + if eventsReceiverAndHandler != nil { + go func() { + logger.Info(ctx, "Starting Artifact service background processing...") + eventsReceiverAndHandler.StartProcessing(ctx) + }() + } + + as := &ArtifactService{ + Metrics: InitMetrics(scope), + Service: coreService, + EventConsumer: eventsReceiverAndHandler, + TriggerEngine: &triggerHandler, + eventsToTrigger: createdArtifacts, + } + + go as.runProcessor(ctx) + + return as +} + +func HttpRegistrationHook(ctx context.Context, gwmux *runtime.ServeMux, grpcAddress string, grpcConnectionOpts []grpc.DialOption, _ promutils.Scope) error { + err := artifact.RegisterArtifactRegistryHandlerFromEndpoint(ctx, gwmux, grpcAddress, grpcConnectionOpts) + if err != nil { + return errors.Wrap(err, "error registering execution service") + } + return nil +} + +func GrpcRegistrationHook(ctx context.Context, server *grpc.Server, scope promutils.Scope) error { + serviceImpl := NewArtifactService(ctx, scope) + artifact.RegisterArtifactRegistryServer(server, serviceImpl) + + return nil +} + +// GetMigrations should be hidden behind the storage interface in the future. +func GetMigrations(_ context.Context) []*gormigrate.Migration { + return db.Migrations +} +func GetDbConfig() *database.DbConfig { + cfg := configuration.ApplicationConfig.GetConfig().(*configuration.ApplicationConfiguration) + return &cfg.ArtifactDatabaseConfig +} diff --git a/flyteartifacts/pkg/server/service.go b/flyteartifacts/pkg/server/service.go new file mode 100644 index 0000000000..e28124a415 --- /dev/null +++ b/flyteartifacts/pkg/server/service.go @@ -0,0 +1,164 @@ +package server + +import ( + "context" + "fmt" + "github.com/flyteorg/flyte/flyteartifacts/pkg/models" + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/artifact" + "github.com/flyteorg/flyte/flytestdlib/logger" + "github.com/flyteorg/flyte/flytestdlib/promutils" + "github.com/flyteorg/flyte/flytestdlib/storage" +) + +type CoreService struct { + Storage StorageInterface + BlobStore BlobStoreInterface + // SearchHandler SearchHandlerInterface +} + +func (c *CoreService) CreateArtifact(ctx context.Context, request *artifact.CreateArtifactRequest) (*artifact.CreateArtifactResponse, error) { + + // todo: gatepr _ua tracking bit to be installed + if request == nil { + return nil, nil + } + + artifactObj, err := models.CreateArtifactModelFromRequest(ctx, request.ArtifactKey, request.Spec, request.Version, request.Partitions, request.Tag, request.Source) + if err != nil { + logger.Errorf(ctx, "Failed to validate Create request: %v", err) + return nil, err + } + + // Offload the metadata object before storing and add the offload location instead. + if artifactObj.Spec.UserMetadata != nil { + offloadLocation, err := c.BlobStore.OffloadArtifactCard(ctx, + artifactObj.ArtifactId.ArtifactKey.Name, artifactObj.ArtifactId.Version, artifactObj.Spec.UserMetadata) + if err != nil { + logger.Errorf(ctx, "Failed to offload metadata: %v", err) + return nil, err + } + artifactObj.OffloadedMetadata = offloadLocation.String() + } + + created, err := c.Storage.CreateArtifact(ctx, artifactObj) + if err != nil { + logger.Errorf(ctx, "Failed to create artifact: %v", err) + return nil, err + } + + return &artifact.CreateArtifactResponse{Artifact: &created.Artifact}, nil +} + +func (c *CoreService) GetArtifact(ctx context.Context, request *artifact.GetArtifactRequest) (*artifact.GetArtifactResponse, error) { + if request == nil || request.Query == nil { + return nil, fmt.Errorf("request cannot be nil") + } + + getResult, err := c.Storage.GetArtifact(ctx, *request.Query) + if err != nil { + logger.Errorf(ctx, "Failed to get artifact: %v", err) + return nil, err + } + if request.Details && len(getResult.OffloadedMetadata) > 0 { + card, err := c.BlobStore.RetrieveArtifactCard(ctx, storage.DataReference(getResult.OffloadedMetadata)) + if err != nil { + logger.Errorf(ctx, "Failed to retrieve artifact card: %v", err) + return nil, err + } + getResult.Artifact.GetSpec().UserMetadata = card + } + + return &artifact.GetArtifactResponse{Artifact: &getResult.Artifact}, nil +} + +func (c *CoreService) CreateTrigger(ctx context.Context, request *artifact.CreateTriggerRequest) (*artifact.CreateTriggerResponse, error) { + // Create the new trigger object. + // Mark all older versions of the trigger as inactive. + + // trigger handler create trigger(storage layer) + serviceTrigger, err := models.CreateTriggerModelFromRequest(ctx, request) + if err != nil { + logger.Errorf(ctx, "Failed to create a valid Trigger from create request: %v with err %v", request, err) + return nil, err + } + + createdTrigger, err := c.Storage.CreateTrigger(ctx, serviceTrigger) + if err != nil { + logger.Errorf(ctx, "Failed to create trigger: %v", err) + } + logger.Infof(ctx, "Created trigger: %+v", createdTrigger) + + return &artifact.CreateTriggerResponse{}, nil +} + +func (c *CoreService) DeleteTrigger(ctx context.Context, request *artifact.DeleteTriggerRequest) (*artifact.DeleteTriggerResponse, error) { + // Todo: gatepr - This needs to be implemented before merging. + return &artifact.DeleteTriggerResponse{}, nil +} + +func (c *CoreService) AddTag(ctx context.Context, request *artifact.AddTagRequest) (*artifact.AddTagResponse, error) { + // Holding off on implementing for a while. + return &artifact.AddTagResponse{}, nil +} + +func (c *CoreService) RegisterProducer(ctx context.Context, request *artifact.RegisterProducerRequest) (*artifact.RegisterResponse, error) { + // These are lineage endpoints slated for future work + return &artifact.RegisterResponse{}, nil +} + +func (c *CoreService) RegisterConsumer(ctx context.Context, request *artifact.RegisterConsumerRequest) (*artifact.RegisterResponse, error) { + // These are lineage endpoints slated for future work + return &artifact.RegisterResponse{}, nil +} + +func (c *CoreService) SearchArtifacts(ctx context.Context, request *artifact.SearchArtifactsRequest) (*artifact.SearchArtifactsResponse, error) { + logger.Infof(ctx, "SearchArtifactsRequest: %+v", request) + found, continuationToken, err := c.Storage.SearchArtifacts(ctx, *request) + if err != nil { + logger.Errorf(ctx, "Failed search [%+v]: %v", request, err) + return nil, err + } + if len(found) == 0 { + return &artifact.SearchArtifactsResponse{}, fmt.Errorf("no artifacts found") + } + var as []*artifact.Artifact + for _, m := range found { + as = append(as, &m.Artifact) + } + return &artifact.SearchArtifactsResponse{ + Artifacts: as, + Token: continuationToken, + }, nil +} + +func (c *CoreService) SetExecutionInputs(ctx context.Context, req *artifact.ExecutionInputsRequest) (*artifact.ExecutionInputsResponse, error) { + err := c.Storage.SetExecutionInputs(ctx, req) + + return &artifact.ExecutionInputsResponse{}, err +} + +func (c *CoreService) FindByWorkflowExec(ctx context.Context, request *artifact.FindByWorkflowExecRequest) (*artifact.SearchArtifactsResponse, error) { + logger.Infof(ctx, "FindByWorkflowExecRequest: %+v", request) + + res, err := c.Storage.FindByWorkflowExec(ctx, request) + if err != nil { + logger.Errorf(ctx, "Failed to find artifacts by workflow execution: %v", err) + return nil, err + } + var as []*artifact.Artifact + for _, m := range res { + as = append(as, &m.Artifact) + } + resp := artifact.SearchArtifactsResponse{ + Artifacts: as, + } + + return &resp, nil +} + +func NewCoreService(storage StorageInterface, blobStore BlobStoreInterface, _ promutils.Scope) CoreService { + return CoreService{ + Storage: storage, + BlobStore: blobStore, + } +} diff --git a/flyteartifacts/pkg/server/trigger_engine.go b/flyteartifacts/pkg/server/trigger_engine.go new file mode 100644 index 0000000000..68da0645b0 --- /dev/null +++ b/flyteartifacts/pkg/server/trigger_engine.go @@ -0,0 +1,445 @@ +package server + +import ( + "context" + "fmt" + "github.com/flyteorg/flyte/flyteartifacts/pkg/lib" + "github.com/flyteorg/flyte/flyteartifacts/pkg/models" + admin2 "github.com/flyteorg/flyte/flyteidl/clients/go/admin" + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/admin" + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/artifact" + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/core" + "github.com/flyteorg/flyte/flyteidl/gen/pb-go/flyteidl/service" + "github.com/flyteorg/flyte/flytestdlib/logger" + "github.com/flyteorg/flyte/flytestdlib/promutils" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/timestamppb" + "math/rand" + "regexp" + "strconv" + "time" + "unicode/utf8" +) + +type TriggerEngine struct { + service artifact.ArtifactRegistryServer + adminClient service.AdminServiceClient + store StorageInterface + + // needs to be used + scope promutils.Scope +} + +// Evaluate the trigger and launch the workflow if it's true +func (e *TriggerEngine) evaluateAndHandleTrigger(ctx context.Context, trigger models.Trigger, incoming *artifact.Artifact) error { + incomingKey := core.ArtifactKey{ + Project: incoming.GetArtifactId().GetArtifactKey().GetProject(), + Domain: incoming.GetArtifactId().GetArtifactKey().GetDomain(), + Name: incoming.GetArtifactId().GetArtifactKey().GetName(), + } + + var triggeringArtifacts = make([]artifact.Artifact, 0) + + incomingPartitions := map[string]*core.LabelValue{} + + if incoming.GetArtifactId().GetPartitions().GetValue() != nil && len(incoming.GetArtifactId().GetPartitions().GetValue()) > 0 { + for k, p := range incoming.GetArtifactId().GetPartitions().GetValue() { + if len(p.GetStaticValue()) == 0 { + logger.Warningf(ctx, "Trigger %s has non-static partition [%+v]", trigger.Name, incoming.GetArtifactId().GetPartitions().GetValue()) + return fmt.Errorf("trigger %s has non-static partition %s [%+v]", trigger.Name, k, p) + } + incomingPartitions[k] = p + } + } + + // Note the order of this is important. It needs to be the same order as the trigger.RunsOn + // This is because binding data references an index in this array. + for _, triggeringArtifactID := range trigger.RunsOn { + // First check partitions. + // They must either both have no partitions, or both have the same partitions. + var thisIDPartitions = make(map[string]string) + if triggeringArtifactID.GetPartitions().GetValue() != nil { + for k, _ := range triggeringArtifactID.GetPartitions().GetValue() { + thisIDPartitions[k] = "placeholder" + } + } + if len(thisIDPartitions) != len(incomingPartitions) { + return fmt.Errorf("trigger %s has different number of partitions [%v] [%+v]", trigger.Name, incoming.GetArtifactId(), triggeringArtifactID) + } + + // If the lengths match, they must still also have the same keys. + // Build a query map of partitions for this triggering artifact while at it. + queryPartitions := map[string]*core.LabelValue{} + if len(thisIDPartitions) > 0 { + for k, _ := range thisIDPartitions { + if incomingValue, ok := incomingPartitions[k]; !ok { + return fmt.Errorf("trigger %s has different partitions [%v] [%+v]", trigger.Name, incoming.GetArtifactId(), triggeringArtifactID) + } else { + queryPartitions[k] = incomingValue + } + } + } + + // See if it's the same one as incoming. + if triggeringArtifactID.GetArtifactKey().Project == incomingKey.Project && + triggeringArtifactID.GetArtifactKey().Domain == incomingKey.Domain && + triggeringArtifactID.GetArtifactKey().Name == incomingKey.Name { + triggeringArtifacts = append(triggeringArtifacts, *incoming) + continue + } + + // Otherwise, assume it's a different one + // Construct a query and fetch it + var lookupID = core.ArtifactID{ + ArtifactKey: triggeringArtifactID.ArtifactKey, + } + if len(queryPartitions) > 0 { + lookupID.Dimensions = &core.ArtifactID_Partitions{ + Partitions: &core.Partitions{Value: queryPartitions}, + } + } + query := core.ArtifactQuery{ + Identifier: &core.ArtifactQuery_ArtifactId{ + ArtifactId: &lookupID, + }, + } + + resp, err := e.service.GetArtifact(ctx, &artifact.GetArtifactRequest{ + Query: &query, + }) + if err != nil { + return fmt.Errorf("failed to get artifact [%+v]: %w", lookupID, err) + } + + triggeringArtifacts = append(triggeringArtifacts, *resp.Artifact) + + } + + err := e.createExecution( + ctx, + triggeringArtifacts, + trigger.LaunchPlan.Id, + trigger.LaunchPlan.Spec.DefaultInputs, + ) + + return err +} + +func (e *TriggerEngine) generateRandomString(length int) string { + rand.Seed(time.Now().UnixNano()) + charset := "abcdefghijklmnopqrstuvwxyz" + var result string + for i := 0; i < length; i++ { + randomIndex := rand.Intn(len(charset)) + result += string(charset[randomIndex]) + } + + return result +} + +func (e *TriggerEngine) getSpec(_ context.Context, launchPlanID *core.Identifier) admin.ExecutionSpec { + + var spec = admin.ExecutionSpec{ + LaunchPlan: launchPlanID, + Metadata: nil, + NotificationOverrides: nil, + Labels: nil, + Annotations: nil, + SecurityContext: nil, + } + return spec +} + +func (e *TriggerEngine) createExecution(ctx context.Context, triggeringArtifacts []artifact.Artifact, launchPlanID *core.Identifier, defaultInputs *core.ParameterMap) error { + + resolvedInputs, err := e.resolveInputs(ctx, triggeringArtifacts, defaultInputs, launchPlanID) + if err != nil { + return fmt.Errorf("failed to resolve inputs: %w", err) + } + + spec := e.getSpec(ctx, launchPlanID) + + resp, err := e.adminClient.CreateExecution(ctx, &admin.ExecutionCreateRequest{ + Project: launchPlanID.Project, + Domain: launchPlanID.Domain, + Name: e.generateRandomString(12), + Spec: &spec, + Inputs: &core.LiteralMap{Literals: resolvedInputs}, + }) + if err != nil { + return fmt.Errorf("failed to create execution: %w", err) + } + + logger.Infof(ctx, "Created execution %v", resp) + return nil +} + +func (e *TriggerEngine) resolveInputs(ctx context.Context, triggeringArtifacts []artifact.Artifact, defaultInputs *core.ParameterMap, launchPlanID *core.Identifier) (map[string]*core.Literal, error) { + // Process inputs that have defaults separately as these may be used to fill in other inputs + var defaults = map[string]*core.Literal{} + for k, v := range defaultInputs.Parameters { + if v.GetDefault() != nil { + defaults[k] = v.GetDefault() + } + } + + var inputs = map[string]*core.Literal{} + for k, v := range defaultInputs.Parameters { + if v.GetDefault() != nil { + continue + } + if v == nil { + return nil, fmt.Errorf("parameter [%s] is nil", k) + } + + convertedLiteral, err := e.convertParameterToLiteral(ctx, triggeringArtifacts, *v, defaults, launchPlanID) + if err != nil { + logger.Errorf(ctx, "Error converting parameter [%s] [%v] to literal: %v", k, v, err) + return nil, err + } + inputs[k] = &convertedLiteral + } + + for k, v := range inputs { + defaults[k] = v + } + return defaults, nil +} + +func (e *TriggerEngine) convertParameterToLiteral(ctx context.Context, triggeringArtifacts []artifact.Artifact, + value core.Parameter, otherInputs map[string]*core.Literal, launchPlanID *core.Identifier) (core.Literal, error) { + + if value.GetArtifactQuery() != nil { + return e.convertArtifactQueryToLiteral(ctx, triggeringArtifacts, *value.GetArtifactQuery(), otherInputs, value, launchPlanID) + + } else if value.GetArtifactId() != nil { + return core.Literal{}, fmt.Errorf("artifact id not supported yet") + } + return core.Literal{}, fmt.Errorf("trying to convert non artifact Parameter to Literal") + +} + +var DurationRegex = regexp.MustCompile(`P(?P\d+Y)?(?P\d+M)?(?P\d+D)?T?(?P\d+H)?(?P\d+M)?(?P\d+S)?`) + +func ParseDuration(str string) time.Duration { + matches := DurationRegex.FindStringSubmatch(str) + + years := ParseInt64(matches[1]) + months := ParseInt64(matches[2]) + days := ParseInt64(matches[3]) + hours := ParseInt64(matches[4]) + minutes := ParseInt64(matches[5]) + seconds := ParseInt64(matches[6]) + + hour := int64(time.Hour) + minute := int64(time.Minute) + second := int64(time.Second) + return time.Duration(years*24*365*hour + months*30*24*hour + days*24*hour + hours*hour + minutes*minute + seconds*second) +} + +func ParseInt64(value string) int64 { + if len(value) == 0 { + return 0 + } + parsed, err := strconv.Atoi(value[:len(value)-1]) + if err != nil { + return 0 + } + return int64(parsed) +} + +func (e *TriggerEngine) parseStringAsDateAndApplyTransform(ctx context.Context, dateFormat string, dateStr string, transform string) (time.Time, error) { + t, err := time.Parse(dateFormat, dateStr) + if err != nil { + return time.Time{}, fmt.Errorf("failed to parse date [%s] as date : %w", dateStr, err) + } + + if transform != "" { + firstRune, runeSize := utf8.DecodeRuneInString(transform) + op := string(firstRune) + transformStr := transform[runeSize:] + + if op == "-" { + td := ParseDuration(transformStr) + t = t.Add(-td) + logger.Infof(ctx, "Applying transform [%s] new date is [%s] from %s", transform, t, transformStr) + } else { + logger.Warningf(ctx, "Ignoring transform [%s]", transformStr) + } + } + return t, nil +} + +func (e *TriggerEngine) convertArtifactQueryToLiteral(ctx context.Context, triggeringArtifacts []artifact.Artifact, + aq core.ArtifactQuery, otherInputs map[string]*core.Literal, p core.Parameter, launchPlanID *core.Identifier) (core.Literal, error) { + + // if it's a binding and it has a partition key + // this is the only time we need to transform the type. + // and it's only ever going to be a datetime + if bnd := aq.GetBinding(); bnd != nil { + a := triggeringArtifacts[bnd.GetIndex()] + if bnd.GetPartitionKey() != "" { + if partitions := a.GetArtifactId().GetPartitions().GetValue(); partitions != nil { + + if sv, ok := partitions[bnd.GetPartitionKey()]; ok { + dateStr := sv.GetStaticValue() + + t, err := e.parseStringAsDateAndApplyTransform(ctx, lib.DateFormat, dateStr, bnd.GetTransform()) + if err != nil { + return core.Literal{}, fmt.Errorf("failed to parse [%s] transform %s] for [%+v]: %w", dateStr, bnd.GetTransform(), a.GetArtifactId(), err) + } + + // convert time to timestamp + ts := timestamppb.New(t) + + return core.Literal{ + Value: &core.Literal_Scalar{ + Scalar: &core.Scalar{ + Value: &core.Scalar_Primitive{ + Primitive: &core.Primitive{ + Value: &core.Primitive_Datetime{ + Datetime: ts, + }, + }, + }, + }, + }, + }, nil + } + } + return core.Literal{}, fmt.Errorf("partition key [%s] not found in artifact [%+v]", bnd.GetPartitionKey(), a.GetArtifactId()) + } + + // this means it's bound to the whole triggering artifact, not a partition. Just pull out the literal and use it + idlLit := triggeringArtifacts[bnd.GetIndex()].GetSpec().GetValue() + return *idlLit, nil + + // this is a real query - will need to first iterate through the partitions to see if we need to fill in any + } else if queryID := aq.GetArtifactId(); queryID != nil { + searchPartitions := map[string]string{} + if len(queryID.GetPartitions().GetValue()) > 0 { + for partitionKey, lv := range queryID.GetPartitions().GetValue() { + if lv.GetStaticValue() != "" { + searchPartitions[partitionKey] = lv.GetStaticValue() + + } else if lv.GetInputBinding() != nil { + inputVar := lv.GetInputBinding().GetVar() + if val, ok := otherInputs[inputVar]; ok { + strVal, err := lib.RenderLiteral(val) + if err != nil { + return core.Literal{}, fmt.Errorf("failed to render input [%s] for partition [%s] with error: %w", inputVar, partitionKey, err) + } + searchPartitions[partitionKey] = strVal + } else { + return core.Literal{}, fmt.Errorf("input binding [%s] not found in input data", inputVar) + } + + // This is like AnArtifact.query(time_partition=Upstream.time_partition - timedelta(days=1)) or + // AnArtifact.query(region=Upstream.region) + } else if triggerBinding := lv.GetTriggeredBinding(); triggerBinding != nil { + a := triggeringArtifacts[triggerBinding.GetIndex()] + aP := a.GetArtifactId().GetPartitions().GetValue() + var searchValue = aP[triggerBinding.GetPartitionKey()].GetStaticValue() + + if triggerBinding.GetTransform() != "" { + logger.Infof(ctx, "Transform detected [%s] value [%s] with transform [%s], assuming datetime", + triggerBinding.GetPartitionKey(), searchValue, triggerBinding.GetTransform()) + t, err := e.parseStringAsDateAndApplyTransform(ctx, lib.DateFormat, searchValue, triggerBinding.GetTransform()) + if err != nil { + return core.Literal{}, fmt.Errorf("failed to parse [%s] transform %s] for [%+v]: %w", searchValue, triggerBinding.GetTransform(), a.GetArtifactId(), err) + } + logger.Debugf(ctx, "Transformed [%s] to [%s]", aP[triggerBinding.GetPartitionKey()].GetStaticValue(), searchValue) + searchValue = t.Format(lib.DateFormat) + } + searchPartitions[partitionKey] = searchValue + } + } + } + + artifactID := core.ArtifactID{ + ArtifactKey: &core.ArtifactKey{ + Project: launchPlanID.Project, + Domain: launchPlanID.Domain, + Name: queryID.ArtifactKey.Name, + }, + } + if len(searchPartitions) > 0 { + artifactID.Dimensions = &core.ArtifactID_Partitions{ + Partitions: models.PartitionsToIdl(searchPartitions), + } + } + + resp, err := e.service.GetArtifact(ctx, &artifact.GetArtifactRequest{ + Query: &core.ArtifactQuery{ + Identifier: &core.ArtifactQuery_ArtifactId{ + ArtifactId: &artifactID, + }, + }, + }) + if err != nil { + st, ok := status.FromError(err) + if ok && st.Code() == codes.NotFound { + if len(p.GetVar().GetType().GetUnionType().GetVariants()) > 0 { + for _, v := range p.GetVar().GetType().GetUnionType().GetVariants() { + if v.GetSimple() == core.SimpleType_NONE { + return core.Literal{ + Value: &core.Literal_Scalar{ + Scalar: &core.Scalar{ + Value: &core.Scalar_NoneType{ + NoneType: &core.Void{}, + }, + }, + }, + }, nil + } + } + } + } + } + + return *resp.Artifact.Spec.Value, nil + } + + return core.Literal{}, fmt.Errorf("query was neither binding nor artifact id [%v]", aq) +} + +func (e *TriggerEngine) EvaluateNewArtifact(ctx context.Context, artifact *artifact.Artifact) ([]core.WorkflowExecutionIdentifier, error) { + + if artifact.GetArtifactId().GetArtifactKey() == nil { + // metric + return nil, fmt.Errorf("artifact or its key cannot be nil") + } + + triggers, err := e.store.GetTriggersByArtifactKey(ctx, *artifact.ArtifactId.ArtifactKey) + if err != nil { + logger.Errorf(ctx, "Failed to get triggers for artifact [%+v]: %v", artifact.ArtifactId.ArtifactKey, err) + return nil, err + } + + for _, trigger := range triggers { + err := e.evaluateAndHandleTrigger(ctx, trigger, artifact) + if err != nil { + logger.Errorf(ctx, "Failed to evaluate trigger [%s]: %v", trigger.Name, err) + return nil, err + } + } + + // todo: capture return IDs + return nil, nil +} + +func NewTriggerEngine(ctx context.Context, storage StorageInterface, service artifact.ArtifactRegistryServer, scope promutils.Scope) (TriggerEngine, error) { + cfg := admin2.GetConfig(ctx) + clients, err := admin2.NewClientsetBuilder().WithConfig(cfg).Build(ctx) + if err != nil { + return TriggerEngine{}, fmt.Errorf("failed to initialize clientset. Error: %w", err) + } + + return TriggerEngine{ + service: service, + adminClient: clients.AdminClient(), + store: storage, + scope: scope, + }, nil +} diff --git a/flyteartifacts/pkg/server/trigger_engine_test.go b/flyteartifacts/pkg/server/trigger_engine_test.go new file mode 100644 index 0000000000..3c61d6cc46 --- /dev/null +++ b/flyteartifacts/pkg/server/trigger_engine_test.go @@ -0,0 +1,13 @@ +package server + +import ( + "github.com/stretchr/testify/assert" + "testing" + "time" +) + +func TestA(t *testing.T) { + dur := ParseDuration("P1D") + assert.Equal(t, time.Hour*24, dur) + +} diff --git a/flyteartifacts/sandbox.yaml b/flyteartifacts/sandbox.yaml new file mode 100644 index 0000000000..a8268af6bd --- /dev/null +++ b/flyteartifacts/sandbox.yaml @@ -0,0 +1,15 @@ +artifactsServer: + artifactBlobStoreConfig: + type: stow + stow: + kind: s3 + config: + disable_ssl: true + v2_signing: true + endpoint: http://localhost:30002 + auth_type: accesskey + access_key_id: minio + secret_key: miniostorage +logger: + level: 5 + show-source: true