feat: oci support (Beta) (#18646)

Signed-off-by: Blake Pettersson <blake.pettersson@gmail.com>
Co-authored-by: Michael Crenshaw <350466+crenshaw-dev@users.noreply.github.com>
This commit is contained in:
Blake Pettersson 2025-06-06 13:27:02 +02:00 committed by GitHub
parent 109cd6c382
commit 18c4d9d568
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
81 changed files with 5336 additions and 1443 deletions

3
.gitignore vendored
View file

@ -27,3 +27,6 @@ cmd/argocd/argocd
cmd/argocd-application-controller/argocd-application-controller
cmd/argocd-repo-server/argocd-repo-server
cmd/argocd-server/argocd-server
# ignore generated `.argocd-helm-dep-up` marker file; this should not be committed to git
reposerver/repository/testdata/**/.argocd-helm-dep-up

View file

@ -598,6 +598,7 @@ install-test-tools-local:
./hack/install.sh kustomize
./hack/install.sh helm
./hack/install.sh gotestsum
./hack/install.sh oras
# Installs all tools required for running codegen (Linux packages)
.PHONY: install-codegen-tools-local

View file

@ -8,6 +8,7 @@ commit-server: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go
ui: sh -c 'cd ui && ${ARGOCD_E2E_YARN_CMD:-yarn} start'
git-server: test/fixture/testrepos/start-git.sh
helm-registry: test/fixture/testrepos/start-helm-registry.sh
oci-registry: test/fixture/testrepos/start-authenticated-helm-registry.sh
dev-mounter: [[ "$ARGOCD_E2E_TEST" != "true" ]] && go run hack/dev-mounter/main.go --configmap argocd-ssh-known-hosts-cm=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} --configmap argocd-tls-certs-cm=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} --configmap argocd-gpg-keys-cm=${ARGOCD_GPG_DATA_PATH:-/tmp/argocd-local/gpg/source}
applicationset-controller: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/applicationset-controller} FORCE_LOG_COLORS=4 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_SSH_DATA_PATH=${ARGOCD_SSH_DATA_PATH:-/tmp/argocd-local/ssh} ARGOCD_BINARY_NAME=argocd-applicationset-controller $COMMAND --loglevel debug --metrics-addr localhost:12345 --probe-addr localhost:12346 --argocd-repo-server localhost:${ARGOCD_E2E_REPOSERVER_PORT:-8081}"
notification: [ "$BIN_MODE" = 'true' ] && COMMAND=./dist/argocd || COMMAND='go run ./cmd/main.go' && sh -c "GOCOVERDIR=${ARGOCD_COVERAGE_DIR:-/tmp/coverage/notification} FORCE_LOG_COLORS=4 ARGOCD_FAKE_IN_CLUSTER=true ARGOCD_TLS_DATA_PATH=${ARGOCD_TLS_DATA_PATH:-/tmp/argocd-local/tls} ARGOCD_BINARY_NAME=argocd-notifications $COMMAND --loglevel debug --application-namespaces=${ARGOCD_APPLICATION_NAMESPACES:-''} --self-service-notification-enabled=${ARGOCD_NOTIFICATION_CONTROLLER_SELF_SERVICE_NOTIFICATION_ENABLED:-'false'}"

156
assets/swagger.json generated
View file

@ -1704,6 +1704,70 @@
}
}
},
"/api/v1/applications/{name}/revisions/{revision}/ocimetadata": {
"get": {
"tags": [
"ApplicationService"
],
"summary": "Get the chart metadata (description, maintainers, home) for a specific revision of the application",
"operationId": "ApplicationService_GetOCIMetadata",
"parameters": [
{
"type": "string",
"description": "the application's name",
"name": "name",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the revision of the app",
"name": "revision",
"in": "path",
"required": true
},
{
"type": "string",
"description": "the application's namespace.",
"name": "appNamespace",
"in": "query"
},
{
"type": "string",
"name": "project",
"in": "query"
},
{
"type": "integer",
"format": "int32",
"description": "source index (for multi source apps).",
"name": "sourceIndex",
"in": "query"
},
{
"type": "integer",
"format": "int32",
"description": "versionId from historical data (for multi source apps).",
"name": "versionId",
"in": "query"
}
],
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/v1alpha1OCIMetadata"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/runtimeError"
}
}
}
}
},
"/api/v1/applications/{name}/rollback": {
"post": {
"tags": [
@ -3576,6 +3640,49 @@
}
}
},
"/api/v1/repositories/{repo}/oci-tags": {
"get": {
"tags": [
"RepositoryService"
],
"operationId": "RepositoryService_ListOCITags",
"parameters": [
{
"type": "string",
"description": "Repo URL for query",
"name": "repo",
"in": "path",
"required": true
},
{
"type": "boolean",
"description": "Whether to force a cache refresh on repo's connection state.",
"name": "forceRefresh",
"in": "query"
},
{
"type": "string",
"description": "App project for query.",
"name": "appProject",
"in": "query"
}
],
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/repositoryRefs"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/runtimeError"
}
}
}
}
},
"/api/v1/repositories/{repo}/refs": {
"get": {
"tags": [
@ -3758,6 +3865,12 @@
"description": "BearerToken contains the bearer token used for Git auth at the repo server.",
"name": "bearerToken",
"in": "query"
},
{
"type": "boolean",
"description": "Whether https should be disabled for an OCI repo.",
"name": "insecureOciForceHttp",
"in": "query"
}
],
"responses": {
@ -4596,6 +4709,12 @@
"description": "BearerToken contains the bearer token used for Git auth at the repo server.",
"name": "bearerToken",
"in": "query"
},
{
"type": "boolean",
"description": "Whether https should be disabled for an OCI repo.",
"name": "insecureOciForceHttp",
"in": "query"
}
],
"responses": {
@ -8130,7 +8249,7 @@
},
"subType": {
"type": "string",
"title": "SubType holds the key's sub type (e.g. rsa4096)"
"title": "SubType holds the key's subtype (e.g. rsa4096)"
},
"trust": {
"type": "string",
@ -8531,6 +8650,33 @@
}
}
},
"v1alpha1OCIMetadata": {
"type": "object",
"title": "OCIMetadata contains metadata for a specific revision in an OCI repository",
"properties": {
"authors": {
"type": "string"
},
"createdAt": {
"type": "string"
},
"description": {
"type": "string"
},
"docsUrl": {
"type": "string"
},
"imageUrl": {
"type": "string"
},
"sourceUrl": {
"type": "string"
},
"version": {
"type": "string"
}
}
},
"v1alpha1Operation": {
"type": "object",
"title": "Operation contains information about a requested or running operation",
@ -9022,6 +9168,10 @@
"type": "string",
"title": "GithubAppPrivateKey specifies the private key PEM data for authentication via GitHub app"
},
"insecureOCIForceHttp": {
"description": "InsecureOCIForceHttp specifies whether the connection to the repository uses TLS at _all_. If true, no TLS. This flag is applicable for OCI repos only.",
"type": "boolean"
},
"noProxy": {
"type": "string",
"title": "NoProxy specifies a list of targets where the proxy isn't used, applies only in cases where the proxy is applied"
@ -9136,6 +9286,10 @@
"type": "boolean",
"title": "InsecureIgnoreHostKey should not be used anymore, Insecure is favoured\nUsed only for Git repos"
},
"insecureOCIForceHttp": {
"description": "InsecureOCIForceHttp specifies whether the connection to the repository uses TLS at _all_. If true, no TLS. This flag is applicable for OCI repos only.",
"type": "boolean"
},
"name": {
"type": "string",
"title": "Name specifies a name to be used for this repo. Only used with Helm repos"

View file

@ -53,30 +53,33 @@ var (
func NewCommand() *cobra.Command {
var (
parallelismLimit int64
listenPort int
listenHost string
metricsPort int
metricsHost string
otlpAddress string
otlpInsecure bool
otlpHeaders map[string]string
otlpAttrs []string
cacheSrc func() (*reposervercache.Cache, error)
tlsConfigCustomizer tls.ConfigCustomizer
tlsConfigCustomizerSrc func() (tls.ConfigCustomizer, error)
redisClient *redis.Client
disableTLS bool
maxCombinedDirectoryManifestsSize string
cmpTarExcludedGlobs []string
allowOutOfBoundsSymlinks bool
streamedManifestMaxTarSize string
streamedManifestMaxExtractedSize string
helmManifestMaxExtractedSize string
helmRegistryMaxIndexSize string
disableManifestMaxExtractedSize bool
includeHiddenDirectories bool
cmpUseManifestGeneratePaths bool
parallelismLimit int64
listenPort int
listenHost string
metricsPort int
metricsHost string
otlpAddress string
otlpInsecure bool
otlpHeaders map[string]string
otlpAttrs []string
cacheSrc func() (*reposervercache.Cache, error)
tlsConfigCustomizer tls.ConfigCustomizer
tlsConfigCustomizerSrc func() (tls.ConfigCustomizer, error)
redisClient *redis.Client
disableTLS bool
maxCombinedDirectoryManifestsSize string
cmpTarExcludedGlobs []string
allowOutOfBoundsSymlinks bool
streamedManifestMaxTarSize string
streamedManifestMaxExtractedSize string
helmManifestMaxExtractedSize string
helmRegistryMaxIndexSize string
ociManifestMaxExtractedSize string
disableOCIManifestMaxExtractedSize bool
disableManifestMaxExtractedSize bool
includeHiddenDirectories bool
cmpUseManifestGeneratePaths bool
ociMediaTypes []string
)
command := cobra.Command{
Use: cliName,
@ -125,6 +128,9 @@ func NewCommand() *cobra.Command {
helmManifestMaxExtractedSizeQuantity, err := resource.ParseQuantity(helmManifestMaxExtractedSize)
errors.CheckError(err)
ociManifestMaxExtractedSizeQuantity, err := resource.ParseQuantity(ociManifestMaxExtractedSize)
errors.CheckError(err)
helmRegistryMaxIndexSizeQuantity, err := resource.ParseQuantity(helmRegistryMaxIndexSize)
errors.CheckError(err)
@ -144,8 +150,11 @@ func NewCommand() *cobra.Command {
StreamedManifestMaxTarSize: streamedManifestMaxTarSizeQuantity.ToDec().Value(),
HelmManifestMaxExtractedSize: helmManifestMaxExtractedSizeQuantity.ToDec().Value(),
HelmRegistryMaxIndexSize: helmRegistryMaxIndexSizeQuantity.ToDec().Value(),
OCIManifestMaxExtractedSize: ociManifestMaxExtractedSizeQuantity.ToDec().Value(),
DisableOCIManifestMaxExtractedSize: disableOCIManifestMaxExtractedSize,
IncludeHiddenDirectories: includeHiddenDirectories,
CMPUseManifestGeneratePaths: cmpUseManifestGeneratePaths,
OCIMediaTypes: ociMediaTypes,
}, askPassServer)
errors.CheckError(err)
@ -249,9 +258,12 @@ func NewCommand() *cobra.Command {
command.Flags().StringVar(&streamedManifestMaxExtractedSize, "streamed-manifest-max-extracted-size", env.StringFromEnv("ARGOCD_REPO_SERVER_STREAMED_MANIFEST_MAX_EXTRACTED_SIZE", "1G"), "Maximum size of streamed manifest archives when extracted")
command.Flags().StringVar(&helmManifestMaxExtractedSize, "helm-manifest-max-extracted-size", env.StringFromEnv("ARGOCD_REPO_SERVER_HELM_MANIFEST_MAX_EXTRACTED_SIZE", "1G"), "Maximum size of helm manifest archives when extracted")
command.Flags().StringVar(&helmRegistryMaxIndexSize, "helm-registry-max-index-size", env.StringFromEnv("ARGOCD_REPO_SERVER_HELM_MANIFEST_MAX_INDEX_SIZE", "1G"), "Maximum size of registry index file")
command.Flags().StringVar(&ociManifestMaxExtractedSize, "oci-manifest-max-extracted-size", env.StringFromEnv("ARGOCD_REPO_SERVER_OCI_MANIFEST_MAX_EXTRACTED_SIZE", "1G"), "Maximum size of oci manifest archives when extracted")
command.Flags().BoolVar(&disableOCIManifestMaxExtractedSize, "disable-oci-manifest-max-extracted-size", env.ParseBoolFromEnv("ARGOCD_REPO_SERVER_DISABLE_OCI_MANIFEST_MAX_EXTRACTED_SIZE", false), "Disable maximum size of oci manifest archives when extracted")
command.Flags().BoolVar(&disableManifestMaxExtractedSize, "disable-helm-manifest-max-extracted-size", env.ParseBoolFromEnv("ARGOCD_REPO_SERVER_DISABLE_HELM_MANIFEST_MAX_EXTRACTED_SIZE", false), "Disable maximum size of helm manifest archives when extracted")
command.Flags().BoolVar(&includeHiddenDirectories, "include-hidden-directories", env.ParseBoolFromEnv("ARGOCD_REPO_SERVER_INCLUDE_HIDDEN_DIRECTORIES", false), "Include hidden directories from Git")
command.Flags().BoolVar(&cmpUseManifestGeneratePaths, "plugin-use-manifest-generate-paths", env.ParseBoolFromEnv("ARGOCD_REPO_SERVER_PLUGIN_USE_MANIFEST_GENERATE_PATHS", false), "Pass the resources described in argocd.argoproj.io/manifest-generate-paths value to the cmpserver to generate the application manifests.")
command.Flags().StringSliceVar(&ociMediaTypes, "oci-layer-media-types", env.StringsFromEnv("ARGOCD_REPO_SERVER_OCI_LAYER_MEDIA_TYPES", []string{"application/vnd.oci.image.layer.v1.tar", "application/vnd.oci.image.layer.v1.tar+gzip", "application/vnd.cncf.helm.chart.content.v1.tar+gzip"}, ","), "Comma separated list of allowed media types for OCI media types. This only accounts for media types within layers.")
tlsConfigCustomizerSrc = tls.AddTLSFlagsToCmd(&command)
cacheSrc = reposervercache.AddCacheFlagsToCmd(&command, cacheutil.Options{
OnClientCreated: func(client *redis.Client) {

View file

@ -68,6 +68,15 @@ func NewGenRepoSpecCommand() *cobra.Command {
# Add a private Helm OCI-based repository named 'stable' via HTTPS
argocd admin repo generate-spec helm-oci-registry.cn-zhangjiakou.cr.aliyuncs.com --type helm --name stable --enable-oci --username test --password test
# Add a private HTTPS OCI repository named 'stable'
argocd repo generate-spec oci://helm-oci-registry.cn-zhangjiakou.cr.aliyuncs.com --type oci --name stable --username test --password test
# Add a private OCI repository named 'stable' without verifying the server's TLS certificate
argocd repo generate-spec oci://helm-oci-registry.cn-zhangjiakou.cr.aliyuncs.com --type oci --name stable --username test --password test --insecure-skip-server-verification
# Add a private HTTP OCI repository named 'stable'
argocd repo generate-spec oci://helm-oci-registry.cn-zhangjiakou.cr.aliyuncs.com --type oci --name stable --username test --password test --insecure-oci-force-http
`
command := &cobra.Command{
@ -130,6 +139,7 @@ func NewGenRepoSpecCommand() *cobra.Command {
repoOpts.Repo.EnableLFS = repoOpts.EnableLfs
repoOpts.Repo.EnableOCI = repoOpts.EnableOci
repoOpts.Repo.UseAzureWorkloadIdentity = repoOpts.UseAzureWorkloadIdentity
repoOpts.Repo.InsecureOCIForceHttp = repoOpts.InsecureOCIForceHTTP
if repoOpts.Repo.Type == "helm" && repoOpts.Repo.Name == "" {
errors.CheckError(stderrors.New("must specify --name for repos of type 'helm'"))

View file

@ -2154,6 +2154,10 @@ func (c *fakeAppServiceClient) GetApplicationSyncWindows(_ context.Context, _ *a
return nil, nil
}
func (c *fakeAppServiceClient) GetOCIMetadata(_ context.Context, _ *applicationpkg.RevisionMetadataQuery, _ ...grpc.CallOption) (*v1alpha1.OCIMetadata, error) {
return nil, nil
}
func (c *fakeAppServiceClient) RevisionMetadata(_ context.Context, _ *applicationpkg.RevisionMetadataQuery, _ ...grpc.CallOption) (*v1alpha1.RevisionMetadata, error) {
return nil, nil
}

View file

@ -84,6 +84,15 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
# Add a private Helm OCI-based repository named 'stable' via HTTPS
argocd repo add helm-oci-registry.cn-zhangjiakou.cr.aliyuncs.com --type helm --name stable --enable-oci --username test --password test
# Add a private HTTPS OCI repository named 'stable'
argocd repo add oci://helm-oci-registry.cn-zhangjiakou.cr.aliyuncs.com --type oci --name stable --username test --password test
# Add a private OCI repository named 'stable' without verifying the server's TLS certificate
argocd repo add oci://helm-oci-registry.cn-zhangjiakou.cr.aliyuncs.com --type oci --name stable --username test --password test --insecure-skip-server-verification
# Add a private HTTP OCI repository named 'stable'
argocd repo add oci://helm-oci-registry.cn-zhangjiakou.cr.aliyuncs.com --type oci --name stable --username test --password test --insecure-oci-force-http
# Add a private Git repository on GitHub.com via GitHub App
argocd repo add https://git.example.com/repos/repo --github-app-id 1 --github-app-installation-id 2 --github-app-private-key-path test.private-key.pem
@ -187,6 +196,10 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
errors.Fatal(errors.ErrorGeneric, "Must specify --name for repos of type 'helm'")
}
if repoOpts.Repo.Type == "oci" && repoOpts.InsecureOCIForceHTTP {
repoOpts.Repo.InsecureOCIForceHttp = repoOpts.InsecureOCIForceHTTP
}
conn, repoIf := headless.NewClientOrDie(clientOpts, c).NewRepoClientOrDie()
defer utilio.Close(conn)
@ -204,7 +217,7 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
errors.CheckError(err)
// We let the server check access to the repository before adding it. If
// it is a private repo, but we cannot access with with the credentials
// it is a private repo, but we cannot access with the credentials
// that were supplied, we bail out.
//
// Skip validation if we are just adding credentials template, chances
@ -231,6 +244,7 @@ func NewRepoAddCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
GcpServiceAccountKey: repoOpts.Repo.GCPServiceAccountKey,
ForceHttpBasicAuth: repoOpts.Repo.ForceHttpBasicAuth,
UseAzureWorkloadIdentity: repoOpts.Repo.UseAzureWorkloadIdentity,
InsecureOciForceHttp: repoOpts.Repo.InsecureOCIForceHttp,
}
_, err = repoIf.ValidateAccess(ctx, &repoAccessReq)
errors.CheckError(err)

View file

@ -11,6 +11,7 @@ type RepoOptions struct {
Repo appsv1.Repository
Upsert bool
SshPrivateKeyPath string //nolint:revive //FIXME(var-naming)
InsecureOCIForceHTTP bool
InsecureIgnoreHostKey bool
InsecureSkipServerVerification bool
TlsClientCertPath string //nolint:revive //FIXME(var-naming)
@ -29,7 +30,7 @@ type RepoOptions struct {
}
func AddRepoFlags(command *cobra.Command, opts *RepoOptions) {
command.Flags().StringVar(&opts.Repo.Type, "type", common.DefaultRepoType, "type of the repository, \"git\" or \"helm\"")
command.Flags().StringVar(&opts.Repo.Type, "type", common.DefaultRepoType, "type of the repository, \"git\", \"oci\" or \"helm\"")
command.Flags().StringVar(&opts.Repo.Name, "name", "", "name of the repository, mandatory for repositories of type helm")
command.Flags().StringVar(&opts.Repo.Project, "project", "", "project of the repository")
command.Flags().StringVar(&opts.Repo.Username, "username", "", "username to the repository")
@ -41,7 +42,7 @@ func AddRepoFlags(command *cobra.Command, opts *RepoOptions) {
command.Flags().BoolVar(&opts.InsecureIgnoreHostKey, "insecure-ignore-host-key", false, "disables SSH strict host key checking (deprecated, use --insecure-skip-server-verification instead)")
command.Flags().BoolVar(&opts.InsecureSkipServerVerification, "insecure-skip-server-verification", false, "disables server certificate and host key checks")
command.Flags().BoolVar(&opts.EnableLfs, "enable-lfs", false, "enable git-lfs (Large File Support) on this repository")
command.Flags().BoolVar(&opts.EnableOci, "enable-oci", false, "enable helm-oci (Helm OCI-Based Repository)")
command.Flags().BoolVar(&opts.EnableOci, "enable-oci", false, "enable helm-oci (Helm OCI-Based Repository) (only valid for helm type repositories)")
command.Flags().Int64Var(&opts.GithubAppId, "github-app-id", 0, "id of the GitHub Application")
command.Flags().Int64Var(&opts.GithubAppInstallationId, "github-app-installation-id", 0, "installation id of the GitHub Application")
command.Flags().StringVar(&opts.GithubAppPrivateKeyPath, "github-app-private-key-path", "", "private key of the GitHub Application")
@ -51,4 +52,5 @@ func AddRepoFlags(command *cobra.Command, opts *RepoOptions) {
command.Flags().StringVar(&opts.GCPServiceAccountKeyPath, "gcp-service-account-key-path", "", "service account key for the Google Cloud Platform")
command.Flags().BoolVar(&opts.ForceHttpBasicAuth, "force-http-basic-auth", false, "whether to force use of basic auth when connecting repository via HTTP")
command.Flags().BoolVar(&opts.UseAzureWorkloadIdentity, "use-azure-workload-identity", false, "whether to use azure workload identity for authentication")
command.Flags().BoolVar(&opts.InsecureOCIForceHTTP, "insecure-oci-force-http", false, "Use http when accessing an OCI repository")
}

View file

@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"reflect"
"slices"
"strings"
goSync "sync"
"time"
@ -138,6 +139,15 @@ func (m *appStateManager) GetRepoObjs(app *v1alpha1.Application, sources []v1alp
return nil, nil, false, fmt.Errorf("failed to get permitted Helm repositories for project %q: %w", proj.Name, err)
}
ociRepos, err := m.db.ListOCIRepositories(context.Background())
if err != nil {
return nil, nil, false, fmt.Errorf("failed to list OCI repositories: %w", err)
}
permittedOCIRepos, err := argo.GetPermittedRepos(proj, ociRepos)
if err != nil {
return nil, nil, false, fmt.Errorf("failed to get permitted OCI repositories for project %q: %w", proj.Name, err)
}
ts.AddCheckpoint("repo_ms")
helmRepositoryCredentials, err := m.db.GetAllHelmRepositoryCredentials(context.Background())
if err != nil {
@ -148,6 +158,15 @@ func (m *appStateManager) GetRepoObjs(app *v1alpha1.Application, sources []v1alp
return nil, nil, false, fmt.Errorf("failed to get permitted Helm credentials for project %q: %w", proj.Name, err)
}
ociRepositoryCredentials, err := m.db.GetAllOCIRepositoryCredentials(context.Background())
if err != nil {
return nil, nil, false, fmt.Errorf("failed to get OCI credentials: %w", err)
}
permittedOCICredentials, err := argo.GetPermittedReposCredentials(proj, ociRepositoryCredentials)
if err != nil {
return nil, nil, false, fmt.Errorf("failed to get permitted OCI credentials for project %q: %w", proj.Name, err)
}
enabledSourceTypes, err := m.settingsMgr.GetEnabledSourceTypes()
if err != nil {
return nil, nil, false, fmt.Errorf("failed to get enabled source types: %w", err)
@ -276,10 +295,22 @@ func (m *appStateManager) GetRepoObjs(app *v1alpha1.Application, sources []v1alp
atLeastOneRevisionIsNotPossibleToBeUpdated = true
}
repos := permittedHelmRepos
helmRepoCreds := permittedHelmCredentials
// If the source is OCI, there is a potential for an OCI image to be a Helm chart and that said chart in
// turn would have OCI dependencies. To ensure that those dependencies can be resolved, add them to the repos
// list.
if source.IsOCI() {
repos = slices.Clone(permittedHelmRepos)
helmRepoCreds = slices.Clone(permittedHelmCredentials)
repos = append(repos, permittedOCIRepos...)
helmRepoCreds = append(helmRepoCreds, permittedOCICredentials...)
}
log.Debugf("Generating Manifest for source %s revision %s", source, revision)
manifestInfo, err := repoClient.GenerateManifest(context.Background(), &apiclient.ManifestRequest{
Repo: repo,
Repos: permittedHelmRepos,
Repos: repos,
Revision: revision,
NoCache: noCache,
NoRevisionCache: noRevisionCache,
@ -291,7 +322,7 @@ func (m *appStateManager) GetRepoObjs(app *v1alpha1.Application, sources []v1alp
KubeVersion: serverVersion,
ApiVersions: apiVersions,
VerifySignature: verifySignature,
HelmRepoCreds: permittedHelmCredentials,
HelmRepoCreds: helmRepoCreds,
TrackingMethod: trackingMethod,
EnabledSourceTypes: enabledSourceTypes,
HelmOptions: helmOptions,

View file

@ -19,6 +19,7 @@ argocd-repo-server [flags]
--allow-oob-symlinks Allow out-of-bounds symlinks in repositories (not recommended)
--default-cache-expiration duration Cache expiration default (default 24h0m0s)
--disable-helm-manifest-max-extracted-size Disable maximum size of helm manifest archives when extracted
--disable-oci-manifest-max-extracted-size Disable maximum size of oci manifest archives when extracted
--disable-tls Disable TLS on the gRPC endpoint
--helm-manifest-max-extracted-size string Maximum size of helm manifest archives when extracted (default "1G")
--helm-registry-max-index-size string Maximum size of registry index file (default "1G")
@ -29,6 +30,8 @@ argocd-repo-server [flags]
--max-combined-directory-manifests-size string Max combined size of manifest files in a directory-type Application (default "10M")
--metrics-address string Listen on given address for metrics (default "0.0.0.0")
--metrics-port int Start metrics server on given port (default 8084)
--oci-layer-media-types strings Comma separated list of allowed media types for OCI media types. This only accounts for media types within layers. (default [application/vnd.oci.image.layer.v1.tar,application/vnd.oci.image.layer.v1.tar+gzip,application/vnd.cncf.helm.chart.content.v1.tar+gzip])
--oci-manifest-max-extracted-size string Maximum size of oci manifest archives when extracted (default "1G")
--otlp-address string OpenTelemetry collector address to send traces to
--otlp-attrs strings List of OpenTelemetry collector extra attrs when send traces, each attribute is separated by a colon(e.g. key:value)
--otlp-headers stringToString List of OpenTelemetry collector extra headers sent with traces, headers are comma-separated key-value pairs(e.g. key1=value1,key2=value2) (default [])

View file

@ -6,13 +6,14 @@ Argo CD supports several different ways in which Kubernetes manifests can be def
* [Kustomize](kustomize.md) applications
* [Helm](helm.md) charts
* [OCI](oci.md) images
* A directory of YAML, JSON, or [Jsonnet](jsonnet.md) manifests.
* Any [custom config management tool](../operator-manual/config-management-plugins.md) configured as a config management plugin
## Development
Argo CD also supports uploading local manifests directly. Since this is an anti-pattern of the
GitOps paradigm, this should only be done for development purposes. A user with an `override` permission is required
to upload manifests locally (typically an admin). All of the different Kubernetes deployment tools above are supported.
to upload manifests locally (typically an admin). All the different Kubernetes deployment tools mentioned above are supported.
To upload a local application:
```bash

View file

@ -36,6 +36,15 @@ argocd admin repo generate-spec REPOURL [flags]
# Add a private Helm OCI-based repository named 'stable' via HTTPS
argocd admin repo generate-spec helm-oci-registry.cn-zhangjiakou.cr.aliyuncs.com --type helm --name stable --enable-oci --username test --password test
# Add a private HTTPS OCI repository named 'stable'
argocd repo generate-spec oci://helm-oci-registry.cn-zhangjiakou.cr.aliyuncs.com --type oci --name stable --username test --password test
# Add a private OCI repository named 'stable' without verifying the server's TLS certificate
argocd repo generate-spec oci://helm-oci-registry.cn-zhangjiakou.cr.aliyuncs.com --type oci --name stable --username test --password test --insecure-skip-server-verification
# Add a private HTTP OCI repository named 'stable'
argocd repo generate-spec oci://helm-oci-registry.cn-zhangjiakou.cr.aliyuncs.com --type oci --name stable --username test --password test --insecure-oci-force-http
```
### Options
@ -43,7 +52,7 @@ argocd admin repo generate-spec REPOURL [flags]
```
--bearer-token string bearer token to the Git BitBucket Data Center repository
--enable-lfs enable git-lfs (Large File Support) on this repository
--enable-oci enable helm-oci (Helm OCI-Based Repository)
--enable-oci enable helm-oci (Helm OCI-Based Repository) (only valid for helm type repositories)
--force-http-basic-auth whether to force use of basic auth when connecting repository via HTTP
--gcp-service-account-key-path string service account key for the Google Cloud Platform
--github-app-enterprise-base-url string base url to use when using GitHub Enterprise (e.g. https://ghe.example.com/api/v3
@ -52,6 +61,7 @@ argocd admin repo generate-spec REPOURL [flags]
--github-app-private-key-path string private key of the GitHub Application
-h, --help help for generate-spec
--insecure-ignore-host-key disables SSH strict host key checking (deprecated, use --insecure-skip-server-verification instead)
--insecure-oci-force-http Use http when accessing an OCI repository
--insecure-skip-server-verification disables server certificate and host key checks
--name string name of the repository, mandatory for repositories of type helm
--no-proxy string don't access these targets via proxy
@ -62,7 +72,7 @@ argocd admin repo generate-spec REPOURL [flags]
--ssh-private-key-path string path to the private ssh key (e.g. ~/.ssh/id_rsa)
--tls-client-cert-key-path string path to the TLS client cert's key path (must be PEM format)
--tls-client-cert-path string path to the TLS client cert (must be PEM format)
--type string type of the repository, "git" or "helm" (default "git")
--type string type of the repository, "git", "oci" or "helm" (default "git")
--use-azure-workload-identity whether to use azure workload identity for authentication
--username string username to the repository
```

View file

@ -37,6 +37,15 @@ argocd repo add REPOURL [flags]
# Add a private Helm OCI-based repository named 'stable' via HTTPS
argocd repo add helm-oci-registry.cn-zhangjiakou.cr.aliyuncs.com --type helm --name stable --enable-oci --username test --password test
# Add a private HTTPS OCI repository named 'stable'
argocd repo add oci://helm-oci-registry.cn-zhangjiakou.cr.aliyuncs.com --type oci --name stable --username test --password test
# Add a private OCI repository named 'stable' without verifying the server's TLS certificate
argocd repo add oci://helm-oci-registry.cn-zhangjiakou.cr.aliyuncs.com --type oci --name stable --username test --password test --insecure-skip-server-verification
# Add a private HTTP OCI repository named 'stable'
argocd repo add oci://helm-oci-registry.cn-zhangjiakou.cr.aliyuncs.com --type oci --name stable --username test --password test --insecure-oci-force-http
# Add a private Git repository on GitHub.com via GitHub App
argocd repo add https://git.example.com/repos/repo --github-app-id 1 --github-app-installation-id 2 --github-app-private-key-path test.private-key.pem
@ -54,7 +63,7 @@ argocd repo add REPOURL [flags]
```
--bearer-token string bearer token to the Git BitBucket Data Center repository
--enable-lfs enable git-lfs (Large File Support) on this repository
--enable-oci enable helm-oci (Helm OCI-Based Repository)
--enable-oci enable helm-oci (Helm OCI-Based Repository) (only valid for helm type repositories)
--force-http-basic-auth whether to force use of basic auth when connecting repository via HTTP
--gcp-service-account-key-path string service account key for the Google Cloud Platform
--github-app-enterprise-base-url string base url to use when using GitHub Enterprise (e.g. https://ghe.example.com/api/v3
@ -63,6 +72,7 @@ argocd repo add REPOURL [flags]
--github-app-private-key-path string private key of the GitHub Application
-h, --help help for add
--insecure-ignore-host-key disables SSH strict host key checking (deprecated, use --insecure-skip-server-verification instead)
--insecure-oci-force-http Use http when accessing an OCI repository
--insecure-skip-server-verification disables server certificate and host key checks
--name string name of the repository, mandatory for repositories of type helm
--no-proxy string don't access these targets via proxy
@ -72,7 +82,7 @@ argocd repo add REPOURL [flags]
--ssh-private-key-path string path to the private ssh key (e.g. ~/.ssh/id_rsa)
--tls-client-cert-key-path string path to the TLS client cert's key path (must be PEM format)
--tls-client-cert-path string path to the TLS client cert (must be PEM format)
--type string type of the repository, "git" or "helm" (default "git")
--type string type of the repository, "git", "oci" or "helm" (default "git")
--upsert Override an existing repository with the same name even if the spec differs
--use-azure-workload-identity whether to use azure workload identity for authentication
--username string username to the repository

122
docs/user-guide/oci.md Normal file
View file

@ -0,0 +1,122 @@
# OCI
## Declarative
Argo CD supports using OCI (Open Container Initiative) images as an application source.
You can install applications using OCI images through the UI, or in the declarative GitOps way.
Here is an example:
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-custom-image
namespace: argocd
spec:
project: default
source:
path: .
repoURL: oci://registry-1.docker.io/some-user/my-custom-image
targetRevision: 1.16.1
destination:
server: "https://kubernetes.default.svc"
namespace: my-namespace
```
Another example using a public OCI helm chart:
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: nginx
spec:
project: default
source:
path: .
repoURL: oci://registry-1.docker.io/bitnamicharts/nginx
targetRevision: 15.9.0
helm:
valuesObject:
some-value: foo
destination:
name: "in-cluster"
namespace: nginx
```
The key to start using OCI images are the following components in the application spec:
* `repoURL`: Specify the OCI image repository URL using the `oci://` scheme, followed by the registry and image name.
* `targetRevision`: Use this field to specify the desired image tag or digest.
* `path`: Use this field to select a relative path from the expanded image. If you don't want to select a subpath, use `.`.
In the case of OCI Helm charts (an OCI artifact where the `mediaType` is set to `application/vnd.cncf.helm.chart.content.v1.tar+gzip`),
the path should always be set to `.`.
## Usage Guidelines
First off, you'll need to have a repository that is OCI-compliant. As an example, DockerHub, ECR, GHCR and GCR all fit
the bill.
Secondly, Argo CD expects an OCI image to contain a single layer. It also expects an OCI image to have a media type which
is accepted by the Argo CD repository server. By default, Argo CD accepts one of the following media types for the image
layer:
* `application/vnd.oci.image.layer.v1.tar+gzip`
* `application/vnd.cncf.helm.chart.content.v1.tar+gzip`
Custom media types can be configured by setting the `ARGOCD_REPO_SERVER_OCI_LAYER_MEDIA_TYPES` environment variable
in the repo-server deployment.
To create an OCI artifact compatible with Argo CD, there are a multitude of tools to choose from. For this example we'll
use [ORAS](https://oras.land/). Navigate to the directory where your manifests are located and run `oras push`.
```shell
oras push <registry-url>/guestbook:latest .
```
ORAS will take care of packaging the directory to a single layer and setting the `mediaType` to
`application/vnd.oci.image.layer.v1.tar+gzip`.
You can also package your OCI image using a compressed archive.
```shell
# Create a tarball of the directory containing your manifests. If you are not in the current directory, please ensure
# that you are setting the correct parent of the directory (that is what the `-C` flag does).
tar -czvf archive.tar.gz -C manifests .
```
Then, you can push the archive to your OCI registry using ORAS:
```shell
# In the case of tarballs, you currently need to set the media type manually.
oras push <registry-url>/guestbook:latest archive.tar.gz:application/vnd.oci.image.layer.v1.tar+gzip
```
## OCI Metadata Annotations
Argo CD can display standard OCI metadata annotations, providing additional context and information about your OCI
images directly in the Argo CD UI.
### Supported Annotations
Argo CD recognizes and displays the following standard OCI annotations:
* `org.opencontainers.image.title`
* `org.opencontainers.image.description`
* `org.opencontainers.image.version`
* `org.opencontainers.image.revision`
* `org.opencontainers.image.url`
* `org.opencontainers.image.source`
* `org.opencontainers.image.authors`
* `org.opencontainers.image.created`
Using the previous example with ORAS, we can set annotations which Argo CD can make use of:
```shell
oras push -a "org.opencontainers.image.authors=some author" \
-a "org.opencontainers.image.url=http://some-url" \
-a "org.opencontainers.image.version=some-version" \
-a "org.opencontainers.image.source=http://some-source" \
-a "org.opencontainers.image.description=some description" \
<registry-url>/guestbook:latest .
```

6
go.mod
View file

@ -14,6 +14,7 @@ require (
github.com/alicebob/miniredis/v2 v2.35.0
github.com/argoproj/gitops-engine v0.7.1-0.20250420064138-d65e9d92277d
github.com/argoproj/notifications-engine v0.4.1-0.20250309174002-87bf0576a872
github.com/argoproj/pkg v0.13.6
github.com/argoproj/pkg/v2 v2.0.1
github.com/aws/aws-sdk-go v1.55.7
github.com/bmatcuk/doublestar/v4 v4.8.1
@ -68,6 +69,8 @@ require (
github.com/microsoft/azure-devops-go-api/azuredevops/v7 v7.1.1-0.20241014080628-3045bdf43455
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1
github.com/olekukonko/tablewriter v1.0.7
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.1
github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible
github.com/prometheus/client_golang v1.22.0
github.com/prometheus/client_model v0.6.2
@ -186,7 +189,6 @@ require (
github.com/go-openapi/strfmt v0.23.0 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
github.com/go-openapi/validate v0.24.0 // indirect
github.com/gobwas/ws v1.2.1 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/golang/glog v1.2.4 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
@ -233,8 +235,6 @@ require (
github.com/oklog/ulid v1.3.1 // indirect
github.com/olekukonko/errors v0.0.0-20250405072817-4e6d85265da6 // indirect
github.com/olekukonko/ll v0.0.8 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.1 // indirect
github.com/opsgenie/opsgenie-go-sdk-v2 v1.2.23 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pjbgf/sha1cd v0.3.2 // indirect

193
go.sum
View file

@ -23,6 +23,7 @@ cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvf
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/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
@ -90,6 +91,7 @@ github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSC
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/OvyFlash/telegram-bot-api v0.0.0-20241219171906-3f2ca0c14ada h1:5ZtieioZyyfiJsGvjpj3d5Eso/3YjJJhNQ1M8at5U5k=
github.com/OvyFlash/telegram-bot-api v0.0.0-20241219171906-3f2ca0c14ada/go.mod h1:2nRUdsKyWhvezqW/rBGWEQdcTQeTtnbSNd2dgx76WYA=
github.com/PagerDuty/go-pagerduty v1.8.0 h1:MTFqTffIcAervB83U7Bx6HERzLbyaSPL/+oxH3zyluI=
@ -115,12 +117,16 @@ github.com/argoproj/gitops-engine v0.7.1-0.20250420064138-d65e9d92277d h1:NbaCC4
github.com/argoproj/gitops-engine v0.7.1-0.20250420064138-d65e9d92277d/go.mod h1:8bIs7jN5U7iKEWU4fMzZfsYWa8ere+iU1rcTiwAtL3A=
github.com/argoproj/notifications-engine v0.4.1-0.20250309174002-87bf0576a872 h1:ADGAdyN9ty0+RmTT/yn+xV9vwkqvLn9O1ccqeP0Zeas=
github.com/argoproj/notifications-engine v0.4.1-0.20250309174002-87bf0576a872/go.mod h1:d1RazGXWvKRFv9//rg4MRRR7rbvbE7XLgTSMT5fITTE=
github.com/argoproj/pkg v0.13.6 h1:36WPD9MNYECHcO1/R1pj6teYspiK7uMQLCgLGft2abM=
github.com/argoproj/pkg v0.13.6/go.mod h1:I698DoJBKuvNFaixh4vFl2C88cNIT1WS7KCbz5ewyF8=
github.com/argoproj/pkg/v2 v2.0.1 h1:O/gCETzB/3+/hyFL/7d/VM/6pSOIRWIiBOTb2xqAHvc=
github.com/argoproj/pkg/v2 v2.0.1/go.mod h1:sdifF6sUTx9ifs38ZaiNMRJuMpSCBB9GulHfbPgQeRE=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/aws/aws-sdk-go v1.44.39/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE=
github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM=
@ -191,9 +197,15 @@ github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNS
github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA=
github.com/chainguard-dev/git-urls v1.0.2 h1:pSpT7ifrpc5X55n4aTTm7FFUE+ZQHKiqpiwNkJrVcKQ=
github.com/chainguard-dev/git-urls v1.0.2/go.mod h1:rbGgj10OS7UgZlbzdUQIQpT0k/D4+An04HJY7Ol+Y/o=
github.com/chromedp/cdproto v0.0.0-20230802225258-3cf4e6d46a89/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
github.com/chromedp/chromedp v0.9.2/go.mod h1:LkSXJKONWTCHAfQasKFUZI+mxqS4tZqhmtGzzhLsnLs=
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/logex v1.2.1/go.mod h1:JLbx6lG2kDbNRFnfkgvh4eRJRPX1QCoOIWomwysCBrQ=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
@ -202,6 +214,7 @@ github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcju
github.com/codeskyblue/go-sh v0.0.0-20190412065543-76bd3d59ff27/go.mod h1:VQx0hjo2oUeQkQUET7wRwradO6f+fN5jzXgB/zROxxE=
github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOumLqk=
github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@ -225,6 +238,7 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
@ -253,6 +267,7 @@ github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwo
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
@ -298,8 +313,14 @@ github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vb
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.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
github.com/go-logr/logr v0.4.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.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
@ -310,8 +331,11 @@ github.com/go-openapi/analysis v0.23.0 h1:aGday7OWupfMs+LbmLZG4k0MYXIANxcuBTYUC0
github.com/go-openapi/analysis v0.23.0/go.mod h1:9mz9ZWaSlV8TvjQHLl2mUW2PbZtemkE8yA5v22ohupo=
github.com/go-openapi/errors v0.22.0 h1:c4xY/OLxUBSTiepAg3j/MHuAv5mJhnf53LLMWFB+u/w=
github.com/go-openapi/errors v0.22.0/go.mod h1:J3DmZScxCDufmIMsdOuDHxJbdOGC0xtUynjIx092vXE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco=
@ -322,6 +346,7 @@ github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9Z
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMgO/c=
github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58=
@ -338,8 +363,9 @@ github.com/go-playground/webhooks/v6 v6.4.0/go.mod h1:5lBxopx+cAJiBI4+kyRbuHrEi+
github.com/go-redis/cache/v9 v9.0.0 h1:0thdtFo0xJi0/WXbRVu8B066z8OvVymXTJGaXrVWnN0=
github.com/go-redis/cache/v9 v9.0.0/go.mod h1:cMwi1N8ASBOufbIvk7cdXe2PbPjK/WMRL95FFHWsSgI=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho=
@ -371,6 +397,7 @@ github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVI
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc=
github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
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=
@ -389,8 +416,10 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
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/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@ -406,6 +435,7 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-github/v69 v69.2.0 h1:wR+Wi/fN2zdUx9YxSmYE0ktiX9IAR/BeePzeaUUbEHE=
@ -430,6 +460,9 @@ github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hf
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-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
@ -439,6 +472,7 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaU
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.1-0.20241114170450-2d3c2a9cc518 h1:UBg1xk+oAsIVbFuGg6hdfAm7EvCv3EL80vFxJNsslqw=
github.com/google/uuid v1.6.1-0.20241114170450-2d3c2a9cc518/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw=
@ -457,6 +491,7 @@ github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB7
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gosimple/slug v1.15.0 h1:wRZHsRrRcs6b0XnxMUBM6WK1U1Vg5B0R7VkIf1Xzobo=
@ -495,6 +530,7 @@ github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI
github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
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/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
github.com/improbable-eng/grpc-web v0.15.1-0.20230209220825-1d9bbb09a099 h1:k07oXM8RqIaaSEF09Frr/iRMlwx2qvx6vRo2XuPIeW8=
github.com/improbable-eng/grpc-web v0.15.1-0.20230209220825-1d9bbb09a099/go.mod h1:Vkb7Iy2LTlRGIAubpODgfeKPzu8nsh1gO+vvZAiZrcs=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
@ -543,13 +579,17 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.13.5/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
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.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
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.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@ -561,6 +601,7 @@ github.com/ktrysmt/go-bitbucket v0.9.85 h1:WSKYSmpgasEmtnsr+TEhD2UtiZjCZpeTBF5T4
github.com/ktrysmt/go-bitbucket v0.9.85/go.mod h1:ZgvxUOaC6eHrNaC/DbjFvJUXaKpKeDYvfhh4U592jcs=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
@ -592,8 +633,12 @@ github.com/microsoft/azure-devops-go-api/azuredevops/v7 v7.1.1-0.20241014080628-
github.com/microsoft/azure-devops-go-api/azuredevops/v7 v7.1.1-0.20241014080628-3045bdf43455/go.mod h1:mDunUZ1IUJdJIRHvFb+LPBUtxe3AYB5MI6BMXNg8194=
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 h1:lYpkrQH5ajf0OXOcUbGjvZxxijuBwbbmlSxLiuofa+g=
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
github.com/minio/md5-simd v1.1.0/go.mod h1:XpBqgZULrMYD3R+M28PcmP0CkI7PEMzB3U77ZrKZ0Gw=
github.com/minio/minio-go/v7 v7.0.29/go.mod h1:x81+AX5gHSfCSqw7jxRKHvxUXMlE5uKX0Vb75Xk5yYg=
github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
@ -636,6 +681,7 @@ github.com/olekukonko/ll v0.0.8/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIY
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/olekukonko/tablewriter v1.0.7 h1:HCC2e3MM+2g72M81ZcJU11uciw6z/p82aEnm4/ySDGw=
github.com/olekukonko/tablewriter v1.0.7/go.mod h1:H428M+HzoUXC6JU2Abj9IT9ooRmdq9CxuDmKMtrOCMs=
github.com/oliveagle/jsonpath v0.0.0-20180606110733-2e52cf6e6852/go.mod h1:eqOVx5Vwu4gd2mmMZvVZsgIqNSaW3xxRThUJ0k/TPk4=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
@ -650,6 +696,19 @@ github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8Ay
github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo=
github.com/onsi/ginkgo/v2 v2.5.0/go.mod h1:Luc4sArBICYCS8THh8v3i3i5CuSZO+RaQRaJoeNwomw=
github.com/onsi/ginkgo/v2 v2.7.0/go.mod h1:yjiuMwPokqY1XauOgju45q3sJt6VzQ/Fict1LFVcsAo=
github.com/onsi/ginkgo/v2 v2.8.1/go.mod h1:N1/NbDngAFcSLdyZ+/aYTYGSlq9qMCS/cNKGJjy+csc=
github.com/onsi/ginkgo/v2 v2.9.0/go.mod h1:4xkjoL/tZv4SMWeww56BU5kAt19mVB47gTWxmrTcxyk=
github.com/onsi/ginkgo/v2 v2.9.1/go.mod h1:FEcmzVcCHl+4o9bQZVab+4dC9+j+91t2FHSzmGAPfuo=
github.com/onsi/ginkgo/v2 v2.9.2/go.mod h1:WHcJJG2dIlcCqVfBAwUCrJxSPFb6v4azBwgxeMeDuts=
github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k=
github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0=
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
github.com/onsi/ginkgo/v2 v2.13.0/go.mod h1:TE309ZR8s5FsKKpuB1YAQYBzCaAfUgatB/xlT/ETL/o=
github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
github.com/onsi/ginkgo/v2 v2.17.2/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc=
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
github.com/onsi/ginkgo/v2 v2.20.1/go.mod h1:lG9ey2Z29hR41WMVthyJBGUBcBhGOtoPF2VFMvBXFCI=
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg=
github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
@ -664,6 +723,20 @@ github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ
github.com/onsi/gomega v1.24.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg=
github.com/onsi/gomega v1.24.1/go.mod h1:3AOiACssS3/MajrniINInwbfOOtfZvplPzuRSmvt1jM=
github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM=
github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM=
github.com/onsi/gomega v1.27.1/go.mod h1:aHX5xOykVYzWOV4WqQy0sy8BQptgukenXpCXfadcIAw=
github.com/onsi/gomega v1.27.3/go.mod h1:5vG284IBtfDAmDyrK+eGyZmUgUlmi+Wngqo557cZ6Gw=
github.com/onsi/gomega v1.27.4/go.mod h1:riYq/GJKh8hhoM01HN6Vmuy93AarCXCBGpvFDK3q3fQ=
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4=
github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ=
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ=
github.com/onsi/gomega v1.33.0/go.mod h1:+925n5YtiFsLzzafLUHzVMBpvvRAzrydIBiSIxjX3wY=
github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc=
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw=
github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
@ -673,6 +746,7 @@ github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgr
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opsgenie/opsgenie-go-sdk-v2 v1.2.23 h1:EFOD/cRfMeq+PCibHddoRTXu8CTN1m8Oj1Tk6eoz8Dw=
github.com/opsgenie/opsgenie-go-sdk-v2 v1.2.23/go.mod h1:1BK0BG3Mz//zeujilvvu3GJ0jnyZwFdT9XjznoPv6kk=
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible h1:IWzUvJ72xMjmrjR9q3H1PF+jwdN0uNQiR2t1BLNalyo=
github.com/patrickmn/go-cache v2.1.1-0.20191004192108-46f407853014+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
@ -681,6 +755,7 @@ github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
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=
@ -730,11 +805,14 @@ github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6L
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
@ -745,6 +823,7 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
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.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
@ -755,6 +834,7 @@ github.com/slack-go/slack v0.16.0 h1:khp/WCFv+Hb/B/AJaAwvcxKun0hM6grN0bUZ8xG60P8
github.com/slack-go/slack v0.16.0/go.mod h1:hlGi5oXA+Gt+yWTPP0plCdRKmjsDxecdHxYQdlMQKOw=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js=
github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0=
github.com/sony/sonyflake v1.0.0 h1:MpU6Ro7tfXwgn2l5eluf9xQvQJDROTBImNCfRXn/YeM=
@ -762,6 +842,7 @@ github.com/sony/sonyflake v1.0.0/go.mod h1:Jv3cfhf/UFtolOTTRd3q4Nl6ENqM+KfyZ5Pse
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
@ -782,9 +863,12 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
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.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
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.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
@ -869,14 +953,26 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
@ -890,6 +986,7 @@ golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u0
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/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
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=
@ -915,6 +1012,15 @@ golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91
golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
golang.org/x/mod v0.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -957,6 +1063,7 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/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-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
@ -965,9 +1072,22 @@ golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -976,6 +1096,7 @@ golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4Iltr
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-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -991,6 +1112,12 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
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/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -1044,6 +1171,7 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/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-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -1057,10 +1185,26 @@ golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=
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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -1068,9 +1212,21 @@ golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -1084,13 +1240,22 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
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.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -1143,6 +1308,19 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
golang.org/x/tools v0.20.0/go.mod h1:WvitBU7JJf6A4jOdg4S1tviW9bhUxkgeCui/0JHctQg=
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.23.0/go.mod h1:pnu6ufv6vQkll6szChhK3C3L/ruaIv5eBeztNG8wtsI=
golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -1243,6 +1421,9 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
@ -1263,6 +1444,7 @@ gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AW
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
@ -1298,18 +1480,25 @@ k8s.io/component-helpers v0.32.2 h1:2usSAm3zNE5yu5DdAdrKBWLfSYNpU4OPjZywJY5ovP8=
k8s.io/component-helpers v0.32.2/go.mod h1:fvQAoiiOP7jUEUBc9qR0PXiBPuB0I56WTxTkkpcI8g8=
k8s.io/controller-manager v0.32.2 h1:/9XuHWEqofO2Aqa4l7KJGckJUcLVRWfx+qnVkdXoStI=
k8s.io/controller-manager v0.32.2/go.mod h1:o5uo2tLCQhuoMt0RfKcQd0eqaNmSKOKiT+0YELCqXOk=
k8s.io/gengo/v2 v2.0.0-20240826214909-a7b603a56eb7/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU=
k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9 h1:si3PfKm8dDYxgfbeA6orqrtLkvvIeH8UqffFJDl0bz4=
k8s.io/gengo/v2 v2.0.0-20240911193312-2b36238f13e9/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU=
k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y=
k8s.io/klog/v2 v2.5.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec=
k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-aggregator v0.32.2 h1:kg9pke+i2qRbJwX1UKwZV4fsCRvmbaCEFk38R4FqHmw=
k8s.io/kube-aggregator v0.32.2/go.mod h1:rRm+xY1yIFIt3zBc727nG5SBLYywywD87klfIAw+7+c=
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4=
k8s.io/kube-openapi v0.0.0-20250304201544-e5f78fe3ede9 h1:t0huyHnz6HsokckRxAF1bY0cqPFwzINKCL7yltEjZQc=
k8s.io/kube-openapi v0.0.0-20250304201544-e5f78fe3ede9/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
k8s.io/kubectl v0.32.2 h1:TAkag6+XfSBgkqK9I7ZvwtF0WVtUAvK8ZqTt+5zi1Us=
k8s.io/kubectl v0.32.2/go.mod h1:+h/NQFSPxiDZYX/WZaWw9fwYezGLISP0ud8nQKg+3g8=
k8s.io/kubernetes v1.32.2 h1:mShetlA102UpjRVSGzB+5vjJwy8oPy8FMWrkTH5f37o=
k8s.io/kubernetes v1.32.2/go.mod h1:tiIKO63GcdPRBHW2WiUFm3C0eoLczl3f7qi56Dm1W8I=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0=
k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
layeh.com/gopher-json v0.0.0-20190114024228-97fed8db8427 h1:RZkKxMR3jbQxdCEcglq3j7wY3PRJIopAwBlx1RE71X0=
@ -1323,6 +1512,7 @@ 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/controller-runtime v0.20.4 h1:X3c+Odnxz+iPTRobG4tp092+CvBU9UK0t/bRf+n0DGU=
sigs.k8s.io/controller-runtime v0.20.4/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
sigs.k8s.io/kustomize/api v0.18.0 h1:hTzp67k+3NEVInwz5BHyzc9rGxIauoXferXyjv5lWPo=
@ -1332,6 +1522,7 @@ sigs.k8s.io/kustomize/kyaml v0.18.1/go.mod h1:C3L2BFVU1jgcddNBE1TxuVLgS46TjObMwW
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4=
sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI=
sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=

View file

@ -0,0 +1,13 @@
#!/usr/bin/env sh
# Usage: ./add-oras-checksums.sh 1.2.0 # use the desired version
wget "https://github.com/oras-project/oras/releases/download/v$1/oras_$1_checksums.txt"
while IFS="" read -r line || [ -n "$line" ]
do
filename=$(echo "$line" | awk -F ' ' '{print $2}' | sed "s#v$1#$1#")
test "${line#*windows}" == "$line" && echo "$line" | sed "s#v$1#$1#" > "$filename.sha256"
done < oras_$1_checksums.txt
rm oras_$1_checksums.txt

View file

@ -0,0 +1 @@
58a8494f5bce778e21d89d90c82e05bd128e7bb9d84dd190d154f8afdbf30541 oras_1.2.0_darwin_amd64.tar.gz

View file

@ -0,0 +1 @@
fea801b0e02c5342e749ef2860e1faebae03e93ae50e33ed40d227e089cf9435 oras_1.2.0_darwin_arm64.tar.gz

View file

@ -0,0 +1 @@
73d3a292dce1f0985df084ff50b38ab2deacbcb01902ceebc008d5bea2f2dee9 oras_1.2.0_freebsd_amd64.tar.gz

View file

@ -0,0 +1 @@
5b3f1cbb86d869eee68120b9b45b9be983f3738442f87ee5f06b00edd0bab336 oras_1.2.0_linux_amd64.tar.gz

View file

@ -0,0 +1 @@
27df680a39fc2fcedc549cb737891623bc696c9a92a03fd341e9356a35836bae oras_1.2.0_linux_arm64.tar.gz

View file

@ -0,0 +1 @@
054685703cb0c66e51f8143e3bbd3976e217d3d595f0593db43b6dd451e43329 oras_1.2.0_linux_armv7.tar.gz

View file

@ -0,0 +1 @@
af5ee50434c7263b1460978af83e7690570e8999678fae9db6c5dbb38467fed5 oras_1.2.0_linux_ppc64le.tar.gz

View file

@ -0,0 +1 @@
42c7e5f35b72492dd3ee7c68edad0c0da939b39e6ead7f682329015b283e96f8 oras_1.2.0_linux_riscv64.tar.gz

View file

@ -0,0 +1 @@
69d2e842e592f4d990b33d58ff3c298c4e4c0921dd54ad9e65e3b6dd9427a750 oras_1.2.0_linux_s390x.tar.gz

20
hack/installers/install-oras.sh Executable file
View file

@ -0,0 +1,20 @@
#!/bin/bash
set -eux -o pipefail
. "$(dirname "$0")"/../tool-versions.sh
# shellcheck disable=SC2046
# shellcheck disable=SC2128
PROJECT_ROOT=$(cd $(dirname "${BASH_SOURCE}")/../..; pwd)
INSTALL_PATH="${INSTALL_PATH:-$PROJECT_ROOT/dist}"
PATH="${INSTALL_PATH}:${PATH}"
[ -d "$INSTALL_PATH" ] || mkdir -p "$INSTALL_PATH"
# shellcheck disable=SC2154
export TARGET_FILE=oras_${oras_version}_${INSTALL_OS}_${ARCHITECTURE}.tar.gz
# shellcheck disable=SC2154
[ -e "$DOWNLOADS"/"${TARGET_FILE}" ] || curl -sLf --retry 3 -o "${DOWNLOADS}"/"${TARGET_FILE}" "https://github.com/oras-project/oras/releases/download/v${oras_version}/oras_${oras_version}_${INSTALL_OS}_${ARCHITECTURE}.tar.gz"
"$(dirname "$0")"/compare-chksum.sh
tar -C /tmp -xf "${DOWNLOADS}"/"${TARGET_FILE}"
sudo install -m 0755 /tmp/oras "$INSTALL_PATH"/oras

View file

@ -14,3 +14,4 @@
helm3_version=3.17.1
kustomize5_version=5.6.0
protoc_version=29.3
oras_version=1.2.0

View file

@ -161,6 +161,7 @@ nav:
- user-guide/application_sources.md
- user-guide/kustomize.md
- user-guide/helm.md
- user-guide/oci.md
- user-guide/import.md
- user-guide/jsonnet.md
- user-guide/directory.md

View file

@ -2914,182 +2914,184 @@ func init() {
}
var fileDescriptor_df6e82b174b5eaec = []byte{
// 2801 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x5a, 0x4d, 0x8c, 0x1c, 0x47,
0x15, 0xa6, 0x66, 0x76, 0x76, 0x67, 0xdf, 0xec, 0x7a, 0xed, 0x8a, 0xbd, 0x74, 0xc6, 0x1b, 0x33,
0x69, 0xdb, 0xf1, 0x64, 0xed, 0x9d, 0xb1, 0x37, 0x01, 0x92, 0x4d, 0x22, 0x70, 0xd6, 0x8e, 0xb3,
0xb0, 0x76, 0x4c, 0xaf, 0x83, 0x51, 0x38, 0x40, 0xa5, 0xbb, 0x76, 0xa6, 0xd9, 0x9e, 0xee, 0x76,
0x77, 0xcf, 0x84, 0x55, 0xc8, 0x25, 0x88, 0x5b, 0x04, 0x02, 0x82, 0xc4, 0x01, 0x01, 0x4a, 0x14,
0x09, 0x21, 0x10, 0x17, 0x84, 0x90, 0x10, 0x12, 0x1c, 0x40, 0x70, 0x40, 0x8a, 0x40, 0xe2, 0x8c,
0x22, 0xc4, 0x0d, 0xb8, 0xe4, 0x8c, 0x50, 0x55, 0x57, 0x75, 0x57, 0xcf, 0x4f, 0xcf, 0x2c, 0x33,
0x28, 0xbe, 0xf5, 0xab, 0xa9, 0x7a, 0xef, 0x7b, 0xaf, 0x5e, 0xbd, 0xf7, 0xea, 0xd5, 0xc0, 0xb9,
0x90, 0x06, 0x3d, 0x1a, 0x34, 0x89, 0xef, 0x3b, 0xb6, 0x49, 0x22, 0xdb, 0x73, 0xd5, 0xef, 0x86,
0x1f, 0x78, 0x91, 0x87, 0x2b, 0xca, 0x50, 0x75, 0xad, 0xe5, 0x79, 0x2d, 0x87, 0x36, 0x89, 0x6f,
0x37, 0x89, 0xeb, 0x7a, 0x11, 0x1f, 0x0e, 0xe3, 0xa9, 0x55, 0xfd, 0xe0, 0x89, 0xb0, 0x61, 0x7b,
0xfc, 0x57, 0xd3, 0x0b, 0x68, 0xb3, 0x77, 0xa5, 0xd9, 0xa2, 0x2e, 0x0d, 0x48, 0x44, 0x2d, 0x31,
0xe7, 0xf1, 0x74, 0x4e, 0x87, 0x98, 0x6d, 0xdb, 0xa5, 0xc1, 0x61, 0xd3, 0x3f, 0x68, 0xb1, 0x81,
0xb0, 0xd9, 0xa1, 0x11, 0x19, 0xb6, 0x6a, 0xb7, 0x65, 0x47, 0xed, 0xee, 0xcb, 0x0d, 0xd3, 0xeb,
0x34, 0x49, 0xd0, 0xf2, 0xfc, 0xc0, 0xfb, 0x12, 0xff, 0xd8, 0x30, 0xad, 0x66, 0xef, 0xb1, 0x94,
0x81, 0xaa, 0x4b, 0xef, 0x0a, 0x71, 0xfc, 0x36, 0x19, 0xe4, 0x76, 0x7d, 0x0c, 0xb7, 0x80, 0xfa,
0x9e, 0xb0, 0x0d, 0xff, 0xb4, 0x23, 0x2f, 0x38, 0x54, 0x3e, 0x63, 0x36, 0xfa, 0xfb, 0x08, 0x8e,
0x5f, 0x4d, 0xe5, 0x7d, 0xa6, 0x4b, 0x83, 0x43, 0x8c, 0x61, 0xce, 0x25, 0x1d, 0xaa, 0xa1, 0x1a,
0xaa, 0x2f, 0x1a, 0xfc, 0x1b, 0x6b, 0xb0, 0x10, 0xd0, 0xfd, 0x80, 0x86, 0x6d, 0xad, 0xc0, 0x87,
0x25, 0x89, 0xab, 0x50, 0x66, 0xc2, 0xa9, 0x19, 0x85, 0x5a, 0xb1, 0x56, 0xac, 0x2f, 0x1a, 0x09,
0x8d, 0xeb, 0xb0, 0x12, 0xd0, 0xd0, 0xeb, 0x06, 0x26, 0xfd, 0x2c, 0x0d, 0x42, 0xdb, 0x73, 0xb5,
0x39, 0xbe, 0xba, 0x7f, 0x98, 0x71, 0x09, 0xa9, 0x43, 0xcd, 0xc8, 0x0b, 0xb4, 0x12, 0x9f, 0x92,
0xd0, 0x0c, 0x0f, 0x03, 0xae, 0xcd, 0xc7, 0x78, 0xd8, 0x37, 0xd6, 0x61, 0x89, 0xf8, 0xfe, 0x2d,
0xd2, 0xa1, 0xa1, 0x4f, 0x4c, 0xaa, 0x2d, 0xf0, 0xdf, 0x32, 0x63, 0x0c, 0xb3, 0x40, 0xa2, 0x95,
0x39, 0x30, 0x49, 0xea, 0xdb, 0xb0, 0x78, 0xcb, 0xb3, 0xe8, 0x68, 0x75, 0xfb, 0xd9, 0x17, 0x06,
0xd9, 0xeb, 0xbf, 0x43, 0x70, 0xca, 0xa0, 0x3d, 0x9b, 0xe1, 0xbf, 0x49, 0x23, 0x62, 0x91, 0x88,
0xf4, 0x73, 0x2c, 0x24, 0x1c, 0xab, 0x50, 0x0e, 0xc4, 0x64, 0xad, 0xc0, 0xc7, 0x13, 0x7a, 0x40,
0x5a, 0x31, 0x5f, 0x99, 0xd8, 0x84, 0x92, 0xc4, 0x35, 0xa8, 0xc4, 0xb6, 0xdc, 0x71, 0x2d, 0xfa,
0x65, 0x6e, 0xbd, 0x92, 0xa1, 0x0e, 0xe1, 0x35, 0x58, 0xec, 0xc5, 0x76, 0xde, 0xb1, 0xb8, 0x15,
0x4b, 0x46, 0x3a, 0xa0, 0xff, 0x03, 0xc1, 0x19, 0xc5, 0x07, 0x0c, 0xb1, 0x33, 0xd7, 0x7b, 0xd4,
0x8d, 0xc2, 0xd1, 0x0a, 0x5d, 0x82, 0x13, 0x72, 0x13, 0xfb, 0xed, 0x34, 0xf8, 0x03, 0x53, 0x51,
0x1d, 0x94, 0x2a, 0xaa, 0x63, 0x4c, 0x11, 0x49, 0xbf, 0xb8, 0x73, 0x4d, 0xa8, 0xa9, 0x0e, 0x0d,
0x18, 0xaa, 0x94, 0x6f, 0xa8, 0xf9, 0x8c, 0xa1, 0xf4, 0x77, 0x11, 0x68, 0x8a, 0xa2, 0x37, 0x89,
0x6b, 0xef, 0xd3, 0x30, 0x9a, 0x74, 0xcf, 0xd0, 0x0c, 0xf7, 0xac, 0x0e, 0x2b, 0xb1, 0x56, 0xb7,
0xd9, 0x79, 0x64, 0xf1, 0x47, 0x2b, 0xd5, 0x8a, 0xf5, 0xa2, 0xd1, 0x3f, 0xcc, 0xf6, 0x4e, 0xca,
0x0c, 0xb5, 0x79, 0xee, 0xc6, 0xe9, 0x80, 0xfe, 0x30, 0x2c, 0x3e, 0x67, 0x3b, 0x74, 0xbb, 0xdd,
0x75, 0x0f, 0xf0, 0x49, 0x28, 0x99, 0xec, 0x83, 0xeb, 0xb0, 0x64, 0xc4, 0x84, 0xfe, 0x4d, 0x04,
0x0f, 0x8f, 0xd2, 0xfa, 0xae, 0x1d, 0xb5, 0xd9, 0xfa, 0x70, 0x94, 0xfa, 0x66, 0x9b, 0x9a, 0x07,
0x61, 0xb7, 0x23, 0x5d, 0x56, 0xd2, 0xd3, 0xa9, 0xaf, 0xff, 0x18, 0x41, 0x7d, 0x2c, 0xa6, 0xbb,
0x01, 0xf1, 0x7d, 0x1a, 0xe0, 0xe7, 0xa0, 0x74, 0x8f, 0xfd, 0xc0, 0x0f, 0x68, 0x65, 0xb3, 0xd1,
0x50, 0x03, 0xfc, 0x58, 0x2e, 0xcf, 0x7f, 0xc8, 0x88, 0x97, 0xe3, 0x86, 0x34, 0x4f, 0x81, 0xf3,
0x59, 0xcd, 0xf0, 0x49, 0xac, 0xc8, 0xe6, 0xf3, 0x69, 0xcf, 0xce, 0xc3, 0x9c, 0x4f, 0x82, 0x48,
0x3f, 0x05, 0x0f, 0x64, 0x8f, 0x87, 0xef, 0xb9, 0x21, 0xd5, 0x7f, 0x95, 0xf5, 0xa6, 0xed, 0x80,
0x92, 0x88, 0x1a, 0xf4, 0x5e, 0x97, 0x86, 0x11, 0x3e, 0x00, 0x35, 0xe7, 0x70, 0xab, 0x56, 0x36,
0x77, 0x1a, 0x69, 0xd0, 0x6e, 0xc8, 0xa0, 0xcd, 0x3f, 0xbe, 0x60, 0x5a, 0x8d, 0xde, 0x63, 0x0d,
0xff, 0xa0, 0xd5, 0x60, 0x29, 0x20, 0x83, 0x4c, 0xa6, 0x00, 0x55, 0x55, 0x43, 0xe5, 0x8e, 0x57,
0x61, 0xbe, 0xeb, 0x87, 0x34, 0x88, 0xb8, 0x66, 0x65, 0x43, 0x50, 0x6c, 0xff, 0x7a, 0xc4, 0xb1,
0x2d, 0x12, 0xc5, 0xfb, 0x53, 0x36, 0x12, 0x5a, 0xff, 0x75, 0x16, 0xfd, 0x8b, 0xbe, 0xf5, 0x41,
0xa1, 0x57, 0x51, 0x16, 0xb2, 0x28, 0x55, 0x0f, 0x2a, 0x66, 0x3d, 0xe8, 0xe7, 0x59, 0xfc, 0xd7,
0xa8, 0x43, 0x53, 0xfc, 0xc3, 0x9c, 0x59, 0x83, 0x05, 0x93, 0x84, 0x26, 0xb1, 0xa4, 0x14, 0x49,
0xb2, 0x40, 0xe6, 0x07, 0x9e, 0x4f, 0x5a, 0x9c, 0xd3, 0x6d, 0xcf, 0xb1, 0xcd, 0x43, 0x21, 0x6e,
0xf0, 0x87, 0x01, 0xc7, 0x9f, 0xcb, 0x77, 0xfc, 0x52, 0x16, 0xf6, 0x59, 0xa8, 0xec, 0x1d, 0xba,
0xe6, 0x0b, 0x7e, 0x7c, 0xb8, 0x4f, 0x42, 0xc9, 0x8e, 0x68, 0x27, 0xd4, 0x10, 0x3f, 0xd8, 0x31,
0xa1, 0xff, 0xa7, 0x04, 0xab, 0x8a, 0x6e, 0x6c, 0x41, 0x9e, 0x66, 0x79, 0x51, 0x6a, 0x15, 0xe6,
0xad, 0xe0, 0xd0, 0xe8, 0xba, 0xc2, 0x01, 0x04, 0xc5, 0x04, 0xfb, 0x41, 0xd7, 0x8d, 0xe1, 0x97,
0x8d, 0x98, 0xc0, 0xfb, 0x50, 0x0e, 0x23, 0x56, 0x65, 0xb4, 0x0e, 0x39, 0xf0, 0xca, 0xe6, 0xa7,
0xa6, 0xdb, 0x74, 0x06, 0x7d, 0x4f, 0x70, 0x34, 0x12, 0xde, 0xf8, 0x1e, 0x8b, 0x69, 0x71, 0xa0,
0x0b, 0xb5, 0x85, 0x5a, 0xb1, 0x5e, 0xd9, 0xdc, 0x9b, 0x5e, 0xd0, 0x0b, 0x3e, 0xab, 0x90, 0x94,
0x0c, 0x66, 0xa4, 0x52, 0x58, 0x18, 0xed, 0x88, 0xf8, 0x10, 0x8a, 0x6a, 0x20, 0x1d, 0xc0, 0x9f,
0x83, 0x92, 0xed, 0xee, 0x7b, 0xa1, 0xb6, 0xc8, 0xc1, 0x3c, 0x3b, 0x1d, 0x98, 0x1d, 0x77, 0xdf,
0x33, 0x62, 0x86, 0xf8, 0x1e, 0x2c, 0x07, 0x34, 0x0a, 0x0e, 0xa5, 0x15, 0x34, 0xe0, 0x76, 0xfd,
0xf4, 0x74, 0x12, 0x0c, 0x95, 0xa5, 0x91, 0x95, 0x80, 0xb7, 0xa0, 0x12, 0xa6, 0x3e, 0xa6, 0x55,
0xb8, 0x40, 0x2d, 0xc3, 0x48, 0xf1, 0x41, 0x43, 0x9d, 0x3c, 0xe0, 0xdd, 0x4b, 0xf9, 0xde, 0xbd,
0x3c, 0x36, 0xab, 0x1d, 0x9b, 0x20, 0xab, 0xad, 0xf4, 0x67, 0xb5, 0x7f, 0x23, 0x58, 0x1b, 0x08,
0x4e, 0x7b, 0x3e, 0xcd, 0x3d, 0x06, 0x04, 0xe6, 0x42, 0x9f, 0x9a, 0x3c, 0x53, 0x55, 0x36, 0x6f,
0xce, 0x2c, 0x5a, 0x71, 0xb9, 0x9c, 0x75, 0x5e, 0x40, 0x9d, 0x32, 0x2e, 0xfc, 0x00, 0xc1, 0x87,
0x15, 0x99, 0xb7, 0x49, 0x64, 0xb6, 0xf3, 0x94, 0x65, 0xe7, 0x97, 0xcd, 0x11, 0x79, 0x39, 0x26,
0x98, 0x55, 0xf9, 0xc7, 0x9d, 0x43, 0x9f, 0x01, 0x64, 0xbf, 0xa4, 0x03, 0x53, 0x16, 0x4f, 0x3f,
0x41, 0x50, 0x55, 0x63, 0xb8, 0xe7, 0x38, 0x2f, 0x13, 0xf3, 0x20, 0x0f, 0xe4, 0x31, 0x28, 0xd8,
0x16, 0x47, 0x58, 0x34, 0x0a, 0xb6, 0x75, 0xc4, 0x60, 0xd4, 0x0f, 0x77, 0x3e, 0x1f, 0xee, 0x42,
0x16, 0xee, 0xfb, 0x7d, 0x70, 0x65, 0x48, 0xc8, 0x81, 0xbb, 0x06, 0x8b, 0x6e, 0x5f, 0x21, 0x9b,
0x0e, 0x0c, 0x29, 0x60, 0x0b, 0x03, 0x05, 0xac, 0x06, 0x0b, 0xbd, 0xe4, 0x9a, 0xc3, 0x7e, 0x96,
0x24, 0x53, 0xb1, 0x15, 0x78, 0x5d, 0x5f, 0x18, 0x3d, 0x26, 0x18, 0x8a, 0x03, 0xdb, 0x65, 0x25,
0x39, 0x47, 0xc1, 0xbe, 0x8f, 0x7e, 0xb1, 0xc9, 0xa8, 0xfd, 0xd3, 0x02, 0x7c, 0x64, 0x88, 0xda,
0x63, 0xfd, 0xe9, 0xfe, 0xd0, 0x3d, 0xf1, 0xea, 0x85, 0x91, 0x5e, 0x5d, 0x1e, 0xe7, 0xd5, 0x8b,
0xf9, 0xf6, 0x82, 0xac, 0xbd, 0x7e, 0x54, 0x80, 0xda, 0x10, 0x7b, 0x8d, 0x2f, 0x27, 0xee, 0x1b,
0x83, 0xed, 0x7b, 0x81, 0xf0, 0x92, 0xb2, 0x11, 0x13, 0xec, 0x9c, 0x79, 0x81, 0xdf, 0x26, 0x2e,
0xf7, 0x8e, 0xb2, 0x21, 0xa8, 0x29, 0x4d, 0x75, 0x0d, 0x34, 0x69, 0x9e, 0xab, 0x66, 0x1c, 0xa4,
0x02, 0xd2, 0xa1, 0x11, 0x0d, 0xc2, 0x51, 0x21, 0xaa, 0x47, 0x9c, 0x2e, 0x95, 0x21, 0x8a, 0x13,
0xfa, 0x3f, 0x0b, 0xfd, 0x6c, 0x8c, 0xae, 0x7b, 0xff, 0x1b, 0x7a, 0x15, 0xe6, 0x09, 0x47, 0x2b,
0x5c, 0x53, 0x50, 0x03, 0x26, 0x2d, 0xe7, 0x9b, 0x74, 0x31, 0x9b, 0x2f, 0x09, 0x68, 0xc1, 0x08,
0x93, 0x6a, 0xc0, 0x2b, 0x91, 0xf3, 0x99, 0xf4, 0x34, 0xca, 0xfe, 0xc6, 0x48, 0x36, 0xfa, 0xd7,
0x10, 0x9c, 0xce, 0x2e, 0x0b, 0x77, 0xed, 0x30, 0x92, 0xb7, 0x18, 0xbc, 0x0f, 0x0b, 0xb1, 0x2a,
0x71, 0x0d, 0x5a, 0xd9, 0xdc, 0x9d, 0xb6, 0x32, 0xc9, 0xec, 0xad, 0x64, 0xae, 0x3f, 0x09, 0xa7,
0x87, 0x86, 0x63, 0x01, 0xa3, 0x0a, 0x65, 0x59, 0x8d, 0x89, 0xdd, 0x4f, 0x68, 0xfd, 0xed, 0xb9,
0x6c, 0x6e, 0xf4, 0xac, 0x5d, 0xaf, 0x95, 0xd3, 0x98, 0xc8, 0xf7, 0x18, 0xb6, 0x1b, 0x9e, 0xa5,
0xf4, 0x20, 0x24, 0xc9, 0xd6, 0x99, 0x9e, 0x1b, 0x11, 0xdb, 0xa5, 0x81, 0x48, 0xdf, 0xe9, 0x00,
0xdb, 0xe9, 0xd0, 0x76, 0x4d, 0xba, 0x47, 0x4d, 0xcf, 0xb5, 0x42, 0xee, 0x32, 0x45, 0x23, 0x33,
0x86, 0x9f, 0x87, 0x45, 0x4e, 0xdf, 0xb1, 0x3b, 0x71, 0xbe, 0xaa, 0x6c, 0xae, 0x37, 0xe2, 0x66,
0x61, 0x43, 0x6d, 0x16, 0xa6, 0x36, 0xec, 0xd0, 0x88, 0x34, 0x7a, 0x57, 0x1a, 0x6c, 0x85, 0x91,
0x2e, 0x66, 0x58, 0x22, 0x62, 0x3b, 0xbb, 0xb6, 0xcb, 0x2b, 0x64, 0x26, 0x2a, 0x1d, 0x60, 0xde,
0xb8, 0xef, 0x39, 0x8e, 0xf7, 0x8a, 0x3c, 0xe0, 0x31, 0xc5, 0x56, 0x75, 0xdd, 0xc8, 0x76, 0xb8,
0xfc, 0xd8, 0xd7, 0xd2, 0x01, 0xbe, 0xca, 0x76, 0x22, 0x1a, 0x88, 0x93, 0x2d, 0xa8, 0xc4, 0xdf,
0x2b, 0x71, 0xff, 0x4b, 0x06, 0x96, 0xf8, 0x64, 0x2c, 0xa9, 0x27, 0xa3, 0xff, 0xb4, 0x2d, 0x0f,
0x69, 0xe2, 0xf0, 0x76, 0x20, 0xed, 0xd9, 0x5e, 0x97, 0x15, 0x7f, 0xbc, 0x46, 0x92, 0xf4, 0xc0,
0x69, 0x59, 0xc9, 0x3f, 0x2d, 0xc7, 0xb3, 0xa7, 0x85, 0x97, 0xf0, 0x91, 0xd9, 0xde, 0x26, 0x21,
0xd5, 0x4e, 0x70, 0xd6, 0xe9, 0x80, 0xfe, 0x1b, 0x04, 0xe5, 0x5d, 0xaf, 0x75, 0xdd, 0x8d, 0x82,
0x43, 0x7e, 0xd9, 0xf3, 0xdc, 0x88, 0xba, 0xd2, 0x9b, 0x24, 0xc9, 0xb6, 0x28, 0xb2, 0x3b, 0x74,
0x2f, 0x22, 0x1d, 0x5f, 0x94, 0x8a, 0x47, 0xda, 0xa2, 0x64, 0x31, 0x33, 0x9b, 0x43, 0xc2, 0x88,
0x87, 0x9c, 0xb2, 0xc1, 0xbf, 0x99, 0x82, 0xc9, 0x84, 0xbd, 0x28, 0x10, 0xf1, 0x26, 0x33, 0xa6,
0x3a, 0x60, 0x29, 0xc6, 0x26, 0x48, 0xbd, 0x03, 0x0f, 0x26, 0x77, 0x98, 0x3b, 0x34, 0xe8, 0xd8,
0x2e, 0xc9, 0x4f, 0x42, 0x13, 0x74, 0x29, 0x73, 0xae, 0xd0, 0x5e, 0xe6, 0x48, 0xb2, 0x2b, 0xc1,
0x5d, 0xdb, 0xb5, 0xbc, 0x57, 0x72, 0x8e, 0xd6, 0x74, 0x02, 0xff, 0x9c, 0x6d, 0x34, 0x2a, 0x12,
0x93, 0x38, 0xf0, 0x3c, 0x2c, 0xb3, 0x88, 0xd1, 0xa3, 0xe2, 0x07, 0x11, 0x94, 0xf4, 0x51, 0x3d,
0x9f, 0x94, 0x87, 0x91, 0x5d, 0x88, 0x77, 0x61, 0x85, 0x84, 0xa1, 0xdd, 0x72, 0xa9, 0x25, 0x79,
0x15, 0x26, 0xe6, 0xd5, 0xbf, 0x34, 0xee, 0x1e, 0xf0, 0x19, 0x62, 0xbf, 0x25, 0xa9, 0x7f, 0x15,
0xc1, 0xa9, 0xa1, 0x4c, 0x92, 0x73, 0x85, 0x94, 0x3c, 0x52, 0x85, 0x72, 0x68, 0xb6, 0xa9, 0xd5,
0x75, 0x64, 0x5e, 0x4c, 0x68, 0xf6, 0x9b, 0xd5, 0x8d, 0x77, 0x5f, 0xe4, 0xb1, 0x84, 0xc6, 0x67,
0x00, 0x3a, 0xc4, 0xed, 0x12, 0x87, 0x43, 0x98, 0xe3, 0x10, 0x94, 0x11, 0x7d, 0x0d, 0xaa, 0xc3,
0x5c, 0x47, 0xb4, 0xaa, 0xfe, 0x85, 0xe0, 0x98, 0x0c, 0xb9, 0x62, 0x77, 0xeb, 0xb0, 0xa2, 0x98,
0xe1, 0x56, 0xba, 0xd1, 0xfd, 0xc3, 0x63, 0xc2, 0xa9, 0xf4, 0x92, 0x62, 0xf6, 0xad, 0xa0, 0x97,
0xe9, 0xf6, 0x4f, 0x9c, 0x70, 0xd1, 0x8c, 0xca, 0xe0, 0xaf, 0x80, 0x76, 0x93, 0xb8, 0xa4, 0x45,
0xad, 0x44, 0xed, 0xc4, 0xc5, 0xbe, 0xa8, 0xf6, 0x5c, 0xa6, 0xee, 0x70, 0x24, 0x15, 0xa3, 0xbd,
0xbf, 0x2f, 0xfb, 0x37, 0x01, 0x94, 0x77, 0x6d, 0xf7, 0x60, 0xc7, 0xdd, 0xf7, 0x98, 0xc6, 0x91,
0x1d, 0x39, 0xd2, 0xba, 0x31, 0x81, 0x8f, 0x43, 0xb1, 0x1b, 0x38, 0xc2, 0x03, 0xd8, 0x27, 0xae,
0x41, 0xc5, 0xa2, 0xa1, 0x19, 0xd8, 0xbe, 0xd8, 0x7f, 0xde, 0xfb, 0x56, 0x86, 0xd8, 0x3e, 0xd8,
0xa6, 0xe7, 0x6e, 0x3b, 0x24, 0x0c, 0x65, 0x7a, 0x4a, 0x06, 0xf4, 0xa7, 0x61, 0x99, 0xc9, 0x4c,
0xd5, 0xbc, 0x98, 0x55, 0xf3, 0x54, 0x06, 0xbe, 0x84, 0x27, 0x11, 0x13, 0x78, 0x80, 0x55, 0x05,
0x57, 0x7d, 0x5f, 0x30, 0x99, 0xb0, 0x1e, 0x2b, 0x0e, 0xcb, 0xae, 0x43, 0x5b, 0xbe, 0x9b, 0x7f,
0x3d, 0x0b, 0x58, 0x3d, 0x27, 0x34, 0xe8, 0xd9, 0x26, 0xc5, 0xdf, 0x42, 0x30, 0xc7, 0x44, 0xe3,
0x87, 0x46, 0x1d, 0x4b, 0xee, 0xaf, 0xd5, 0xd9, 0xdd, 0xe7, 0x99, 0x34, 0x7d, 0xed, 0xf5, 0xbf,
0xfc, 0xfd, 0xdb, 0x85, 0x55, 0x7c, 0x92, 0x3f, 0xf4, 0xf5, 0xae, 0xa8, 0x8f, 0x6e, 0x21, 0x7e,
0x03, 0x01, 0x16, 0x55, 0x92, 0xf2, 0x14, 0x82, 0x2f, 0x8e, 0x82, 0x38, 0xe4, 0xc9, 0xa4, 0xfa,
0x90, 0x92, 0x55, 0x1a, 0xa6, 0x17, 0x50, 0x96, 0x43, 0xf8, 0x04, 0x0e, 0x60, 0x9d, 0x03, 0x38,
0x87, 0xf5, 0x61, 0x00, 0x9a, 0xaf, 0x32, 0x8b, 0xbe, 0xd6, 0xa4, 0xb1, 0xdc, 0xb7, 0x10, 0x94,
0xee, 0xf2, 0xab, 0xd0, 0x18, 0x23, 0xed, 0xcd, 0xcc, 0x48, 0x5c, 0x1c, 0x47, 0xab, 0x9f, 0xe5,
0x48, 0x1f, 0xc2, 0xa7, 0x25, 0xd2, 0x30, 0x0a, 0x28, 0xe9, 0x64, 0x00, 0x5f, 0x46, 0xf8, 0x1d,
0x04, 0xf3, 0x71, 0x0f, 0x1c, 0x9f, 0x1f, 0x85, 0x32, 0xd3, 0x23, 0xaf, 0xce, 0xae, 0xa1, 0xac,
0x3f, 0xca, 0x31, 0x9e, 0xd5, 0x87, 0x6e, 0xe7, 0x56, 0xa6, 0xdd, 0xfc, 0x26, 0x82, 0xe2, 0x0d,
0x3a, 0xd6, 0xdf, 0x66, 0x08, 0x6e, 0xc0, 0x80, 0x43, 0xb6, 0x1a, 0xbf, 0x8d, 0xe0, 0xc1, 0x1b,
0x34, 0x1a, 0x9e, 0x1e, 0x71, 0x7d, 0x7c, 0xce, 0x12, 0x6e, 0x77, 0x71, 0x82, 0x99, 0x49, 0x5e,
0x68, 0x72, 0x64, 0x8f, 0xe2, 0x0b, 0x79, 0x4e, 0x18, 0x1e, 0xba, 0xe6, 0x2b, 0x02, 0xc7, 0x1f,
0x11, 0x1c, 0xef, 0x7f, 0xf2, 0xc4, 0x7a, 0xdf, 0x1d, 0x65, 0xc8, 0x8b, 0x68, 0xf5, 0xd6, 0xb4,
0x51, 0x36, 0xcb, 0x54, 0xbf, 0xca, 0x91, 0x3f, 0x85, 0x9f, 0xcc, 0x43, 0x9e, 0x34, 0x14, 0x9b,
0xaf, 0xca, 0xcf, 0xd7, 0xf8, 0xf3, 0x3c, 0x87, 0xfd, 0x27, 0x04, 0x27, 0x25, 0xdf, 0xed, 0x36,
0x09, 0xa2, 0x6b, 0x94, 0x55, 0xd8, 0xe1, 0x44, 0xfa, 0x4c, 0x99, 0x35, 0x54, 0x79, 0xfa, 0x75,
0xae, 0xcb, 0x27, 0xf0, 0x33, 0x47, 0xd6, 0xc5, 0x64, 0x6c, 0x2c, 0x01, 0xfb, 0x75, 0x04, 0x4b,
0x37, 0x68, 0x74, 0x33, 0x69, 0x6a, 0x9f, 0x9f, 0xe8, 0xa1, 0xac, 0xba, 0xd6, 0x50, 0xfe, 0x15,
0x20, 0x7f, 0x4a, 0x5c, 0x64, 0x83, 0x83, 0xbb, 0x80, 0xcf, 0xe7, 0x81, 0x4b, 0x1b, 0xe9, 0x6f,
0x21, 0x38, 0xa5, 0x82, 0x48, 0x1f, 0x18, 0x3f, 0x7a, 0xb4, 0x67, 0x3b, 0xf1, 0xf8, 0x37, 0x06,
0xdd, 0x26, 0x47, 0x77, 0x49, 0x1f, 0xee, 0xc0, 0x9d, 0x01, 0x14, 0x5b, 0x68, 0xbd, 0x8e, 0xf0,
0x6f, 0x11, 0xcc, 0xc7, 0x3d, 0xe5, 0xd1, 0x36, 0xca, 0x3c, 0x88, 0xcd, 0x32, 0x1a, 0x88, 0xdd,
0xae, 0x5e, 0x1e, 0x6e, 0x50, 0x75, 0xbd, 0x74, 0xd5, 0x06, 0xb7, 0x72, 0x36, 0x8c, 0xfd, 0x02,
0x01, 0xa4, 0x7d, 0x71, 0xfc, 0x68, 0xbe, 0x1e, 0x4a, 0xef, 0xbc, 0x3a, 0xdb, 0xce, 0xb8, 0xde,
0xe0, 0xfa, 0xd4, 0xab, 0xb5, 0xdc, 0x18, 0xe2, 0x53, 0x73, 0x2b, 0xee, 0xa1, 0xff, 0x10, 0x41,
0x89, 0xb7, 0x23, 0xf1, 0xb9, 0x51, 0x98, 0xd5, 0x6e, 0xe5, 0x2c, 0x4d, 0xff, 0x08, 0x87, 0x5a,
0xdb, 0xcc, 0x0b, 0xc4, 0x5b, 0x68, 0x1d, 0xf7, 0x60, 0x3e, 0x6e, 0x00, 0x8e, 0x76, 0x8f, 0x4c,
0x83, 0xb0, 0x5a, 0xcb, 0x29, 0x0c, 0x62, 0x47, 0x15, 0x39, 0x60, 0x7d, 0x5c, 0x0e, 0x98, 0x63,
0x61, 0x1a, 0x9f, 0xcd, 0x0b, 0xe2, 0xff, 0x07, 0xc3, 0x5c, 0xe4, 0xe8, 0xce, 0xeb, 0xb5, 0x71,
0x79, 0x80, 0x59, 0xe7, 0xbb, 0x08, 0x8e, 0xf7, 0x17, 0xd7, 0xf8, 0xf4, 0xd0, 0x3e, 0x95, 0xc8,
0x49, 0x59, 0x2b, 0x8e, 0x2a, 0xcc, 0xf5, 0x4f, 0x72, 0x14, 0x5b, 0xf8, 0x89, 0xb1, 0x27, 0xe3,
0x96, 0x8c, 0x3a, 0x8c, 0xd1, 0x46, 0xfa, 0xc8, 0xf7, 0x4b, 0x04, 0x4b, 0x92, 0xef, 0x9d, 0x80,
0xd2, 0x7c, 0x58, 0xb3, 0x3b, 0x08, 0x4c, 0x96, 0xfe, 0x34, 0x87, 0xff, 0x31, 0xfc, 0xf8, 0x84,
0xf0, 0x25, 0xec, 0x8d, 0x88, 0x21, 0xfd, 0x3d, 0x82, 0x13, 0x77, 0x63, 0xbf, 0xff, 0x80, 0xf0,
0x6f, 0x73, 0xfc, 0xcf, 0xe0, 0xa7, 0x72, 0xea, 0xbc, 0x71, 0x6a, 0x5c, 0x46, 0xf8, 0x67, 0x08,
0xca, 0xf2, 0x71, 0x08, 0x5f, 0x18, 0x79, 0x30, 0xb2, 0xcf, 0x47, 0xb3, 0x74, 0x66, 0x51, 0xd4,
0xe8, 0xe7, 0x72, 0xd3, 0xa9, 0x90, 0xcf, 0x1c, 0xfa, 0x4d, 0x04, 0x38, 0xb9, 0x33, 0x27, 0xb7,
0x68, 0xfc, 0x48, 0x46, 0xd4, 0xc8, 0xc6, 0x4c, 0xf5, 0xc2, 0xd8, 0x79, 0xd9, 0x54, 0xba, 0x9e,
0x9b, 0x4a, 0xbd, 0x44, 0xfe, 0xd7, 0x11, 0x54, 0x6e, 0xd0, 0xe4, 0x0e, 0x92, 0x63, 0xcb, 0xec,
0xdb, 0x56, 0xb5, 0x3e, 0x7e, 0xa2, 0x40, 0x74, 0x89, 0x23, 0x7a, 0x04, 0xe7, 0x9b, 0x4a, 0x02,
0xf8, 0x1e, 0x82, 0xe5, 0xdb, 0xaa, 0x8b, 0xe2, 0x4b, 0xe3, 0x24, 0x65, 0x22, 0xf9, 0xe4, 0xb8,
0x1e, 0xe3, 0xb8, 0x36, 0xf4, 0x89, 0x70, 0x6d, 0x89, 0x67, 0xa2, 0xef, 0xa3, 0xf8, 0x12, 0xdb,
0xd7, 0xed, 0xfe, 0x5f, 0xed, 0x96, 0xd3, 0x34, 0xd7, 0x1f, 0xe7, 0xf8, 0x1a, 0xf8, 0xd2, 0x24,
0xf8, 0x9a, 0xa2, 0x05, 0x8e, 0xbf, 0x83, 0xe0, 0x04, 0x7f, 0xec, 0x50, 0x19, 0xe3, 0xbc, 0x0e,
0x7f, 0xfa, 0x34, 0x32, 0x41, 0x8a, 0xf9, 0x38, 0x07, 0x75, 0x45, 0x3f, 0x12, 0x28, 0xe6, 0xff,
0xdf, 0x40, 0x70, 0x4c, 0xe6, 0x33, 0xb1, 0xb1, 0x1b, 0xe3, 0x6c, 0x76, 0xd4, 0xfc, 0x27, 0x3c,
0x6d, 0x7d, 0x32, 0x4f, 0x7b, 0x07, 0xc1, 0x82, 0x68, 0xf3, 0xe7, 0x54, 0x09, 0xca, 0x3b, 0x40,
0xb5, 0xaf, 0xbd, 0x21, 0xfa, 0xc0, 0xfa, 0xe7, 0xb9, 0xd8, 0x17, 0x71, 0x33, 0x4f, 0xac, 0xef,
0x59, 0x61, 0xf3, 0x55, 0xd1, 0x84, 0x7d, 0xad, 0xe9, 0x78, 0xad, 0xf0, 0x25, 0x1d, 0xe7, 0xe6,
0x42, 0x36, 0xe7, 0x32, 0xc2, 0x11, 0x2c, 0x32, 0xbf, 0xe0, 0x3d, 0x13, 0x5c, 0xeb, 0xeb, 0xb0,
0x0c, 0xb4, 0x53, 0xaa, 0xd5, 0x81, 0x1e, 0x4c, 0x9a, 0xfc, 0xc4, 0x0d, 0x16, 0x3f, 0x9c, 0x2b,
0x96, 0x0b, 0x7a, 0x03, 0xc1, 0x09, 0xd5, 0xd1, 0x63, 0xf1, 0x13, 0xbb, 0x79, 0x1e, 0x0a, 0x51,
0x4f, 0xe3, 0xf5, 0x89, 0x7c, 0x88, 0xc3, 0x79, 0xf6, 0xb9, 0x3f, 0xbc, 0x77, 0x06, 0xbd, 0xfb,
0xde, 0x19, 0xf4, 0xb7, 0xf7, 0xce, 0xa0, 0x97, 0x9e, 0x98, 0xec, 0x5f, 0xce, 0xa6, 0x63, 0x53,
0x37, 0x52, 0xd9, 0xff, 0x37, 0x00, 0x00, 0xff, 0xff, 0x20, 0x14, 0xaf, 0x73, 0xcb, 0x2d, 0x00,
// 2833 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xcc, 0x5a, 0x4f, 0x8f, 0x1c, 0x47,
0x15, 0xa7, 0x66, 0x76, 0x76, 0x67, 0xdf, 0x78, 0xbd, 0x76, 0xc5, 0x5e, 0x3a, 0xe3, 0x8d, 0x99,
0xb4, 0xbd, 0xf1, 0x64, 0xed, 0x9d, 0xb1, 0x37, 0x01, 0x92, 0x4d, 0x02, 0x38, 0x6b, 0xc7, 0x59,
0x58, 0x3b, 0xa6, 0xd7, 0xc1, 0x28, 0x1c, 0xa0, 0xd2, 0x5d, 0x3b, 0xd3, 0x6c, 0x4f, 0x77, 0xbb,
0xbb, 0x67, 0xc2, 0x2a, 0xe4, 0x12, 0xc4, 0x2d, 0x02, 0x01, 0x41, 0xe2, 0x80, 0x02, 0x4a, 0x14,
0x09, 0x21, 0x10, 0x17, 0x84, 0x90, 0x10, 0x12, 0x1c, 0x82, 0xe0, 0x80, 0x14, 0xc1, 0x17, 0x40,
0x11, 0xe2, 0x06, 0x5c, 0x72, 0x46, 0xa8, 0xaa, 0xab, 0xba, 0xab, 0xe7, 0x4f, 0xcf, 0x2c, 0x33,
0x28, 0xb9, 0xf5, 0xab, 0xa9, 0x7a, 0xef, 0xf7, 0x5e, 0xbd, 0x7a, 0xef, 0xd5, 0xab, 0x81, 0xf3,
0x21, 0x0d, 0x7a, 0x34, 0x68, 0x12, 0xdf, 0x77, 0x6c, 0x93, 0x44, 0xb6, 0xe7, 0xaa, 0xdf, 0x0d,
0x3f, 0xf0, 0x22, 0x0f, 0x57, 0x94, 0xa1, 0xea, 0x6a, 0xcb, 0xf3, 0x5a, 0x0e, 0x6d, 0x12, 0xdf,
0x6e, 0x12, 0xd7, 0xf5, 0x22, 0x3e, 0x1c, 0xc6, 0x53, 0xab, 0xfa, 0xc1, 0x63, 0x61, 0xc3, 0xf6,
0xf8, 0xaf, 0xa6, 0x17, 0xd0, 0x66, 0xef, 0x4a, 0xb3, 0x45, 0x5d, 0x1a, 0x90, 0x88, 0x5a, 0x62,
0xce, 0xa3, 0xe9, 0x9c, 0x0e, 0x31, 0xdb, 0xb6, 0x4b, 0x83, 0xc3, 0xa6, 0x7f, 0xd0, 0x62, 0x03,
0x61, 0xb3, 0x43, 0x23, 0x32, 0x6c, 0xd5, 0x6e, 0xcb, 0x8e, 0xda, 0xdd, 0x17, 0x1b, 0xa6, 0xd7,
0x69, 0x92, 0xa0, 0xe5, 0xf9, 0x81, 0xf7, 0x55, 0xfe, 0xb1, 0x61, 0x5a, 0xcd, 0xde, 0x23, 0x29,
0x03, 0x55, 0x97, 0xde, 0x15, 0xe2, 0xf8, 0x6d, 0x32, 0xc8, 0xed, 0xfa, 0x18, 0x6e, 0x01, 0xf5,
0x3d, 0x61, 0x1b, 0xfe, 0x69, 0x47, 0x5e, 0x70, 0xa8, 0x7c, 0xc6, 0x6c, 0xf4, 0xf7, 0x11, 0x9c,
0xb8, 0x9a, 0xca, 0xfb, 0x7c, 0x97, 0x06, 0x87, 0x18, 0xc3, 0x9c, 0x4b, 0x3a, 0x54, 0x43, 0x35,
0x54, 0x5f, 0x34, 0xf8, 0x37, 0xd6, 0x60, 0x21, 0xa0, 0xfb, 0x01, 0x0d, 0xdb, 0x5a, 0x81, 0x0f,
0x4b, 0x12, 0x57, 0xa1, 0xcc, 0x84, 0x53, 0x33, 0x0a, 0xb5, 0x62, 0xad, 0x58, 0x5f, 0x34, 0x12,
0x1a, 0xd7, 0x61, 0x39, 0xa0, 0xa1, 0xd7, 0x0d, 0x4c, 0xfa, 0x05, 0x1a, 0x84, 0xb6, 0xe7, 0x6a,
0x73, 0x7c, 0x75, 0xff, 0x30, 0xe3, 0x12, 0x52, 0x87, 0x9a, 0x91, 0x17, 0x68, 0x25, 0x3e, 0x25,
0xa1, 0x19, 0x1e, 0x06, 0x5c, 0x9b, 0x8f, 0xf1, 0xb0, 0x6f, 0xac, 0xc3, 0x31, 0xe2, 0xfb, 0xb7,
0x48, 0x87, 0x86, 0x3e, 0x31, 0xa9, 0xb6, 0xc0, 0x7f, 0xcb, 0x8c, 0x31, 0xcc, 0x02, 0x89, 0x56,
0xe6, 0xc0, 0x24, 0xa9, 0x6f, 0xc3, 0xe2, 0x2d, 0xcf, 0xa2, 0xa3, 0xd5, 0xed, 0x67, 0x5f, 0x18,
0x64, 0xaf, 0xbf, 0x83, 0xe0, 0xb4, 0x41, 0x7b, 0x36, 0xc3, 0x7f, 0x93, 0x46, 0xc4, 0x22, 0x11,
0xe9, 0xe7, 0x58, 0x48, 0x38, 0x56, 0xa1, 0x1c, 0x88, 0xc9, 0x5a, 0x81, 0x8f, 0x27, 0xf4, 0x80,
0xb4, 0x62, 0xbe, 0x32, 0xb1, 0x09, 0x25, 0x89, 0x6b, 0x50, 0x89, 0x6d, 0xb9, 0xe3, 0x5a, 0xf4,
0x6b, 0xdc, 0x7a, 0x25, 0x43, 0x1d, 0xc2, 0xab, 0xb0, 0xd8, 0x8b, 0xed, 0xbc, 0x63, 0x71, 0x2b,
0x96, 0x8c, 0x74, 0x40, 0xff, 0x07, 0x82, 0xb3, 0x8a, 0x0f, 0x18, 0x62, 0x67, 0xae, 0xf7, 0xa8,
0x1b, 0x85, 0xa3, 0x15, 0xba, 0x04, 0x27, 0xe5, 0x26, 0xf6, 0xdb, 0x69, 0xf0, 0x07, 0xa6, 0xa2,
0x3a, 0x28, 0x55, 0x54, 0xc7, 0x98, 0x22, 0x92, 0x7e, 0x7e, 0xe7, 0x9a, 0x50, 0x53, 0x1d, 0x1a,
0x30, 0x54, 0x29, 0xdf, 0x50, 0xf3, 0x19, 0x43, 0xe9, 0xef, 0x22, 0xd0, 0x14, 0x45, 0x6f, 0x12,
0xd7, 0xde, 0xa7, 0x61, 0x34, 0xe9, 0x9e, 0xa1, 0x19, 0xee, 0x59, 0x1d, 0x96, 0x63, 0xad, 0x6e,
0xb3, 0xf3, 0xc8, 0xe2, 0x8f, 0x56, 0xaa, 0x15, 0xeb, 0x45, 0xa3, 0x7f, 0x98, 0xed, 0x9d, 0x94,
0x19, 0x6a, 0xf3, 0xdc, 0x8d, 0xd3, 0x01, 0xfd, 0x41, 0x58, 0x7c, 0xc6, 0x76, 0xe8, 0x76, 0xbb,
0xeb, 0x1e, 0xe0, 0x53, 0x50, 0x32, 0xd9, 0x07, 0xd7, 0xe1, 0x98, 0x11, 0x13, 0xfa, 0x77, 0x10,
0x3c, 0x38, 0x4a, 0xeb, 0xbb, 0x76, 0xd4, 0x66, 0xeb, 0xc3, 0x51, 0xea, 0x9b, 0x6d, 0x6a, 0x1e,
0x84, 0xdd, 0x8e, 0x74, 0x59, 0x49, 0x4f, 0xa7, 0xbe, 0xfe, 0x53, 0x04, 0xf5, 0xb1, 0x98, 0xee,
0x06, 0xc4, 0xf7, 0x69, 0x80, 0x9f, 0x81, 0xd2, 0x3d, 0xf6, 0x03, 0x3f, 0xa0, 0x95, 0xcd, 0x46,
0x43, 0x0d, 0xf0, 0x63, 0xb9, 0x3c, 0xfb, 0x11, 0x23, 0x5e, 0x8e, 0x1b, 0xd2, 0x3c, 0x05, 0xce,
0x67, 0x25, 0xc3, 0x27, 0xb1, 0x22, 0x9b, 0xcf, 0xa7, 0x3d, 0x3d, 0x0f, 0x73, 0x3e, 0x09, 0x22,
0xfd, 0x34, 0xdc, 0x97, 0x3d, 0x1e, 0xbe, 0xe7, 0x86, 0x54, 0xff, 0x4d, 0xd6, 0x9b, 0xb6, 0x03,
0x4a, 0x22, 0x6a, 0xd0, 0x7b, 0x5d, 0x1a, 0x46, 0xf8, 0x00, 0xd4, 0x9c, 0xc3, 0xad, 0x5a, 0xd9,
0xdc, 0x69, 0xa4, 0x41, 0xbb, 0x21, 0x83, 0x36, 0xff, 0xf8, 0xb2, 0x69, 0x35, 0x7a, 0x8f, 0x34,
0xfc, 0x83, 0x56, 0x83, 0xa5, 0x80, 0x0c, 0x32, 0x99, 0x02, 0x54, 0x55, 0x0d, 0x95, 0x3b, 0x5e,
0x81, 0xf9, 0xae, 0x1f, 0xd2, 0x20, 0xe2, 0x9a, 0x95, 0x0d, 0x41, 0xb1, 0xfd, 0xeb, 0x11, 0xc7,
0xb6, 0x48, 0x14, 0xef, 0x4f, 0xd9, 0x48, 0x68, 0xfd, 0xb7, 0x59, 0xf4, 0xcf, 0xfb, 0xd6, 0x07,
0x85, 0x5e, 0x45, 0x59, 0xc8, 0xa2, 0x54, 0x3d, 0xa8, 0x98, 0xf5, 0xa0, 0x5f, 0x66, 0xf1, 0x5f,
0xa3, 0x0e, 0x4d, 0xf1, 0x0f, 0x73, 0x66, 0x0d, 0x16, 0x4c, 0x12, 0x9a, 0xc4, 0x92, 0x52, 0x24,
0xc9, 0x02, 0x99, 0x1f, 0x78, 0x3e, 0x69, 0x71, 0x4e, 0xb7, 0x3d, 0xc7, 0x36, 0x0f, 0x85, 0xb8,
0xc1, 0x1f, 0x06, 0x1c, 0x7f, 0x2e, 0xdf, 0xf1, 0x4b, 0x59, 0xd8, 0xe7, 0xa0, 0xb2, 0x77, 0xe8,
0x9a, 0xcf, 0xf9, 0xf1, 0xe1, 0x3e, 0x05, 0x25, 0x3b, 0xa2, 0x9d, 0x50, 0x43, 0xfc, 0x60, 0xc7,
0x84, 0xfe, 0x9f, 0x12, 0xac, 0x28, 0xba, 0xb1, 0x05, 0x79, 0x9a, 0xe5, 0x45, 0xa9, 0x15, 0x98,
0xb7, 0x82, 0x43, 0xa3, 0xeb, 0x0a, 0x07, 0x10, 0x14, 0x13, 0xec, 0x07, 0x5d, 0x37, 0x86, 0x5f,
0x36, 0x62, 0x02, 0xef, 0x43, 0x39, 0x8c, 0x58, 0x95, 0xd1, 0x3a, 0xe4, 0xc0, 0x2b, 0x9b, 0x9f,
0x9d, 0x6e, 0xd3, 0x19, 0xf4, 0x3d, 0xc1, 0xd1, 0x48, 0x78, 0xe3, 0x7b, 0x2c, 0xa6, 0xc5, 0x81,
0x2e, 0xd4, 0x16, 0x6a, 0xc5, 0x7a, 0x65, 0x73, 0x6f, 0x7a, 0x41, 0xcf, 0xf9, 0xac, 0x42, 0x52,
0x32, 0x98, 0x91, 0x4a, 0x61, 0x61, 0xb4, 0x23, 0xe2, 0x43, 0x28, 0xaa, 0x81, 0x74, 0x00, 0x7f,
0x11, 0x4a, 0xb6, 0xbb, 0xef, 0x85, 0xda, 0x22, 0x07, 0xf3, 0xf4, 0x74, 0x60, 0x76, 0xdc, 0x7d,
0xcf, 0x88, 0x19, 0xe2, 0x7b, 0xb0, 0x14, 0xd0, 0x28, 0x38, 0x94, 0x56, 0xd0, 0x80, 0xdb, 0xf5,
0x73, 0xd3, 0x49, 0x30, 0x54, 0x96, 0x46, 0x56, 0x02, 0xde, 0x82, 0x4a, 0x98, 0xfa, 0x98, 0x56,
0xe1, 0x02, 0xb5, 0x0c, 0x23, 0xc5, 0x07, 0x0d, 0x75, 0xf2, 0x80, 0x77, 0x1f, 0xcb, 0xf7, 0xee,
0xa5, 0xb1, 0x59, 0xed, 0xf8, 0x04, 0x59, 0x6d, 0xb9, 0x3f, 0xab, 0xfd, 0x1b, 0xc1, 0xea, 0x40,
0x70, 0xda, 0xf3, 0x69, 0xee, 0x31, 0x20, 0x30, 0x17, 0xfa, 0xd4, 0xe4, 0x99, 0xaa, 0xb2, 0x79,
0x73, 0x66, 0xd1, 0x8a, 0xcb, 0xe5, 0xac, 0xf3, 0x02, 0xea, 0x94, 0x71, 0xe1, 0x47, 0x08, 0x3e,
0xaa, 0xc8, 0xbc, 0x4d, 0x22, 0xb3, 0x9d, 0xa7, 0x2c, 0x3b, 0xbf, 0x6c, 0x8e, 0xc8, 0xcb, 0x31,
0xc1, 0xac, 0xca, 0x3f, 0xee, 0x1c, 0xfa, 0x0c, 0x20, 0xfb, 0x25, 0x1d, 0x98, 0xb2, 0x78, 0xfa,
0x19, 0x82, 0xaa, 0x1a, 0xc3, 0x3d, 0xc7, 0x79, 0x91, 0x98, 0x07, 0x79, 0x20, 0x8f, 0x43, 0xc1,
0xb6, 0x38, 0xc2, 0xa2, 0x51, 0xb0, 0xad, 0x23, 0x06, 0xa3, 0x7e, 0xb8, 0xf3, 0xf9, 0x70, 0x17,
0xb2, 0x70, 0xdf, 0xef, 0x83, 0x2b, 0x43, 0x42, 0x0e, 0xdc, 0x55, 0x58, 0x74, 0xfb, 0x0a, 0xd9,
0x74, 0x60, 0x48, 0x01, 0x5b, 0x18, 0x28, 0x60, 0x35, 0x58, 0xe8, 0x25, 0xd7, 0x1c, 0xf6, 0xb3,
0x24, 0x99, 0x8a, 0xad, 0xc0, 0xeb, 0xfa, 0xc2, 0xe8, 0x31, 0xc1, 0x50, 0x1c, 0xd8, 0x2e, 0x2b,
0xc9, 0x39, 0x0a, 0xf6, 0x7d, 0xf4, 0x8b, 0x4d, 0x46, 0xed, 0x9f, 0x17, 0xe0, 0x63, 0x43, 0xd4,
0x1e, 0xeb, 0x4f, 0x1f, 0x0e, 0xdd, 0x13, 0xaf, 0x5e, 0x18, 0xe9, 0xd5, 0xe5, 0x71, 0x5e, 0xbd,
0x98, 0x6f, 0x2f, 0xc8, 0xda, 0xeb, 0x27, 0x05, 0xa8, 0x0d, 0xb1, 0xd7, 0xf8, 0x72, 0xe2, 0x43,
0x63, 0xb0, 0x7d, 0x2f, 0x10, 0x5e, 0x52, 0x36, 0x62, 0x82, 0x9d, 0x33, 0x2f, 0xf0, 0xdb, 0xc4,
0xe5, 0xde, 0x51, 0x36, 0x04, 0x35, 0xa5, 0xa9, 0xae, 0x81, 0x26, 0xcd, 0x73, 0xd5, 0x8c, 0x83,
0x54, 0x40, 0x3a, 0x34, 0xa2, 0x41, 0x38, 0x2a, 0x44, 0xf5, 0x88, 0xd3, 0xa5, 0x32, 0x44, 0x71,
0x42, 0xff, 0x67, 0xa1, 0x9f, 0x8d, 0xd1, 0x75, 0x3f, 0xfc, 0x86, 0x5e, 0x81, 0x79, 0xc2, 0xd1,
0x0a, 0xd7, 0x14, 0xd4, 0x80, 0x49, 0xcb, 0xf9, 0x26, 0x5d, 0xcc, 0xe6, 0x4b, 0x02, 0x5a, 0x30,
0xc2, 0xa4, 0x1a, 0xf0, 0x4a, 0x64, 0x2d, 0x93, 0x9e, 0x46, 0xd9, 0xdf, 0x18, 0xc9, 0x46, 0xff,
0x26, 0x82, 0x33, 0xd9, 0x65, 0xe1, 0xae, 0x1d, 0x46, 0xf2, 0x16, 0x83, 0xf7, 0x61, 0x21, 0x56,
0x25, 0xae, 0x41, 0x2b, 0x9b, 0xbb, 0xd3, 0x56, 0x26, 0x99, 0xbd, 0x95, 0xcc, 0xf5, 0xc7, 0xe1,
0xcc, 0xd0, 0x70, 0x2c, 0x60, 0x54, 0xa1, 0x2c, 0xab, 0x31, 0xb1, 0xfb, 0x09, 0xad, 0xbf, 0x35,
0x97, 0xcd, 0x8d, 0x9e, 0xb5, 0xeb, 0xb5, 0x72, 0x1a, 0x13, 0xf9, 0x1e, 0xc3, 0x76, 0xc3, 0xb3,
0x94, 0x1e, 0x84, 0x24, 0xd9, 0x3a, 0xd3, 0x73, 0x23, 0x62, 0xbb, 0x34, 0x10, 0xe9, 0x3b, 0x1d,
0x60, 0x3b, 0x1d, 0xda, 0xae, 0x49, 0xf7, 0xa8, 0xe9, 0xb9, 0x56, 0xc8, 0x5d, 0xa6, 0x68, 0x64,
0xc6, 0xf0, 0xb3, 0xb0, 0xc8, 0xe9, 0x3b, 0x76, 0x27, 0xce, 0x57, 0x95, 0xcd, 0xf5, 0x46, 0xdc,
0x2c, 0x6c, 0xa8, 0xcd, 0xc2, 0xd4, 0x86, 0x1d, 0x1a, 0x91, 0x46, 0xef, 0x4a, 0x83, 0xad, 0x30,
0xd2, 0xc5, 0x0c, 0x4b, 0x44, 0x6c, 0x67, 0xd7, 0x76, 0x79, 0x85, 0xcc, 0x44, 0xa5, 0x03, 0xcc,
0x1b, 0xf7, 0x3d, 0xc7, 0xf1, 0x5e, 0x92, 0x07, 0x3c, 0xa6, 0xd8, 0xaa, 0xae, 0x1b, 0xd9, 0x0e,
0x97, 0x1f, 0xfb, 0x5a, 0x3a, 0xc0, 0x57, 0xd9, 0x4e, 0x44, 0x03, 0x71, 0xb2, 0x05, 0x95, 0xf8,
0x7b, 0x25, 0xee, 0x7f, 0xc9, 0xc0, 0x12, 0x9f, 0x8c, 0x63, 0xea, 0xc9, 0xe8, 0x3f, 0x6d, 0x4b,
0x43, 0x9a, 0x38, 0xbc, 0x1d, 0x48, 0x7b, 0xb6, 0xd7, 0x65, 0xc5, 0x1f, 0xaf, 0x91, 0x24, 0x3d,
0x70, 0x5a, 0x96, 0xf3, 0x4f, 0xcb, 0x89, 0xec, 0x69, 0xe1, 0x25, 0x7c, 0x64, 0xb6, 0xb7, 0x49,
0x48, 0xb5, 0x93, 0x9c, 0x75, 0x3a, 0xa0, 0xff, 0x0e, 0x41, 0x79, 0xd7, 0x6b, 0x5d, 0x77, 0xa3,
0xe0, 0x90, 0x5f, 0xf6, 0x3c, 0x37, 0xa2, 0xae, 0xf4, 0x26, 0x49, 0xb2, 0x2d, 0x8a, 0xec, 0x0e,
0xdd, 0x8b, 0x48, 0xc7, 0x17, 0xa5, 0xe2, 0x91, 0xb6, 0x28, 0x59, 0xcc, 0xcc, 0xe6, 0x90, 0x30,
0xe2, 0x21, 0xa7, 0x6c, 0xf0, 0x6f, 0xa6, 0x60, 0x32, 0x61, 0x2f, 0x0a, 0x44, 0xbc, 0xc9, 0x8c,
0xa9, 0x0e, 0x58, 0x8a, 0xb1, 0x09, 0x52, 0xef, 0xc0, 0xfd, 0xc9, 0x1d, 0xe6, 0x0e, 0x0d, 0x3a,
0xb6, 0x4b, 0xf2, 0x93, 0xd0, 0x04, 0x5d, 0xca, 0x9c, 0x2b, 0xb4, 0x97, 0x39, 0x92, 0xec, 0x4a,
0x70, 0xd7, 0x76, 0x2d, 0xef, 0xa5, 0x9c, 0xa3, 0x35, 0x9d, 0xc0, 0xbf, 0x64, 0x1b, 0x8d, 0x8a,
0xc4, 0x24, 0x0e, 0x3c, 0x0b, 0x4b, 0x2c, 0x62, 0xf4, 0xa8, 0xf8, 0x41, 0x04, 0x25, 0x7d, 0x54,
0xcf, 0x27, 0xe5, 0x61, 0x64, 0x17, 0xe2, 0x5d, 0x58, 0x26, 0x61, 0x68, 0xb7, 0x5c, 0x6a, 0x49,
0x5e, 0x85, 0x89, 0x79, 0xf5, 0x2f, 0x8d, 0xbb, 0x07, 0x7c, 0x86, 0xd8, 0x6f, 0x49, 0xea, 0xdf,
0x40, 0x70, 0x7a, 0x28, 0x93, 0xe4, 0x5c, 0x21, 0x25, 0x8f, 0x54, 0xa1, 0x1c, 0x9a, 0x6d, 0x6a,
0x75, 0x1d, 0x99, 0x17, 0x13, 0x9a, 0xfd, 0x66, 0x75, 0xe3, 0xdd, 0x17, 0x79, 0x2c, 0xa1, 0xf1,
0x59, 0x80, 0x0e, 0x71, 0xbb, 0xc4, 0xe1, 0x10, 0xe6, 0x38, 0x04, 0x65, 0x44, 0x5f, 0x85, 0xea,
0x30, 0xd7, 0x11, 0xad, 0xaa, 0x7f, 0x21, 0x38, 0x2e, 0x43, 0xae, 0xd8, 0xdd, 0x3a, 0x2c, 0x2b,
0x66, 0xb8, 0x95, 0x6e, 0x74, 0xff, 0xf0, 0x98, 0x70, 0x2a, 0xbd, 0xa4, 0x98, 0x7d, 0x2b, 0xe8,
0x65, 0xba, 0xfd, 0x13, 0x27, 0x5c, 0x34, 0xa3, 0x32, 0xf8, 0xeb, 0xa0, 0xdd, 0x24, 0x2e, 0x69,
0x51, 0x2b, 0x51, 0x3b, 0x71, 0xb1, 0xaf, 0xa8, 0x3d, 0x97, 0xa9, 0x3b, 0x1c, 0x49, 0xc5, 0x68,
0xef, 0xef, 0xcb, 0xfe, 0x4d, 0x00, 0xe5, 0x5d, 0xdb, 0x3d, 0xd8, 0x71, 0xf7, 0x3d, 0xa6, 0x71,
0x64, 0x47, 0x8e, 0xb4, 0x6e, 0x4c, 0xe0, 0x13, 0x50, 0xec, 0x06, 0x8e, 0xf0, 0x00, 0xf6, 0x89,
0x6b, 0x50, 0xb1, 0x68, 0x68, 0x06, 0xb6, 0x2f, 0xf6, 0x9f, 0xf7, 0xbe, 0x95, 0x21, 0xb6, 0x0f,
0xb6, 0xe9, 0xb9, 0xdb, 0x0e, 0x09, 0x43, 0x99, 0x9e, 0x92, 0x01, 0xfd, 0x49, 0x58, 0x62, 0x32,
0x53, 0x35, 0x2f, 0x66, 0xd5, 0x3c, 0x9d, 0x81, 0x2f, 0xe1, 0x49, 0xc4, 0x04, 0xee, 0x63, 0x55,
0xc1, 0x55, 0xdf, 0x17, 0x4c, 0x26, 0xac, 0xc7, 0x8a, 0xc3, 0xb2, 0xeb, 0xd0, 0x96, 0xef, 0xe6,
0x1b, 0x6b, 0x80, 0xd5, 0x73, 0x42, 0x83, 0x9e, 0x6d, 0x52, 0xfc, 0x5d, 0x04, 0x73, 0x4c, 0x34,
0x7e, 0x60, 0xd4, 0xb1, 0xe4, 0xfe, 0x5a, 0x9d, 0xdd, 0x7d, 0x9e, 0x49, 0xd3, 0x57, 0x5f, 0xfd,
0xeb, 0xdf, 0xbf, 0x57, 0x58, 0xc1, 0xa7, 0xf8, 0x43, 0x5f, 0xef, 0x8a, 0xfa, 0xe8, 0x16, 0xe2,
0xd7, 0x10, 0x60, 0x51, 0x25, 0x29, 0x4f, 0x21, 0xf8, 0xe2, 0x28, 0x88, 0x43, 0x9e, 0x4c, 0xaa,
0x0f, 0x28, 0x59, 0xa5, 0x61, 0x7a, 0x01, 0x65, 0x39, 0x84, 0x4f, 0xe0, 0x00, 0xd6, 0x39, 0x80,
0xf3, 0x58, 0x1f, 0x06, 0xa0, 0xf9, 0x32, 0xb3, 0xe8, 0x2b, 0x4d, 0x1a, 0xcb, 0x7d, 0x13, 0x41,
0xe9, 0x2e, 0xbf, 0x0a, 0x8d, 0x31, 0xd2, 0xde, 0xcc, 0x8c, 0xc4, 0xc5, 0x71, 0xb4, 0xfa, 0x39,
0x8e, 0xf4, 0x01, 0x7c, 0x46, 0x22, 0x0d, 0xa3, 0x80, 0x92, 0x4e, 0x06, 0xf0, 0x65, 0x84, 0xdf,
0x46, 0x30, 0x1f, 0xf7, 0xc0, 0xf1, 0xda, 0x28, 0x94, 0x99, 0x1e, 0x79, 0x75, 0x76, 0x0d, 0x65,
0xfd, 0x61, 0x8e, 0xf1, 0x9c, 0x3e, 0x74, 0x3b, 0xb7, 0x32, 0xed, 0xe6, 0xd7, 0x11, 0x14, 0x6f,
0xd0, 0xb1, 0xfe, 0x36, 0x43, 0x70, 0x03, 0x06, 0x1c, 0xb2, 0xd5, 0xf8, 0x2d, 0x04, 0xf7, 0xdf,
0xa0, 0xd1, 0xf0, 0xf4, 0x88, 0xeb, 0xe3, 0x73, 0x96, 0x70, 0xbb, 0x8b, 0x13, 0xcc, 0x4c, 0xf2,
0x42, 0x93, 0x23, 0x7b, 0x18, 0x5f, 0xc8, 0x73, 0xc2, 0xf0, 0xd0, 0x35, 0x5f, 0x12, 0x38, 0xfe,
0x84, 0xe0, 0x44, 0xff, 0x93, 0x27, 0xd6, 0xfb, 0xee, 0x28, 0x43, 0x5e, 0x44, 0xab, 0xb7, 0xa6,
0x8d, 0xb2, 0x59, 0xa6, 0xfa, 0x55, 0x8e, 0xfc, 0x09, 0xfc, 0x78, 0x1e, 0xf2, 0xa4, 0xa1, 0xd8,
0x7c, 0x59, 0x7e, 0xbe, 0xc2, 0x9f, 0xe7, 0x39, 0xec, 0x3f, 0x23, 0x38, 0x25, 0xf9, 0x6e, 0xb7,
0x49, 0x10, 0x5d, 0xa3, 0xac, 0xc2, 0x0e, 0x27, 0xd2, 0x67, 0xca, 0xac, 0xa1, 0xca, 0xd3, 0xaf,
0x73, 0x5d, 0x3e, 0x8d, 0x9f, 0x3a, 0xb2, 0x2e, 0x26, 0x63, 0x63, 0x09, 0xd8, 0xef, 0x20, 0x38,
0x7e, 0x83, 0x46, 0xcf, 0x6d, 0xef, 0x1c, 0x69, 0x67, 0xa6, 0x74, 0x74, 0x45, 0x9c, 0x7e, 0x8d,
0x2b, 0xf2, 0x29, 0xfc, 0xe4, 0x91, 0x15, 0xf1, 0x4c, 0x3b, 0xd9, 0x97, 0x57, 0x11, 0x1c, 0xbb,
0x41, 0xa3, 0x9b, 0x49, 0x73, 0x7e, 0x6d, 0xa2, 0x07, 0xbf, 0xea, 0x6a, 0x43, 0xf9, 0x77, 0x83,
0xfc, 0x29, 0x71, 0xf5, 0x0d, 0x8e, 0xed, 0x02, 0x5e, 0xcb, 0xc3, 0x96, 0x3e, 0x08, 0xbc, 0x89,
0xe0, 0xb4, 0x0a, 0x22, 0x7d, 0x28, 0xfd, 0xf8, 0xd1, 0x9e, 0x1f, 0xc5, 0x23, 0xe6, 0x18, 0x74,
0x9b, 0x1c, 0xdd, 0x25, 0x7d, 0xf8, 0x41, 0xec, 0x0c, 0xa0, 0xd8, 0x42, 0xeb, 0x75, 0x84, 0x7f,
0x8f, 0x60, 0x3e, 0xee, 0x8d, 0x8f, 0xb6, 0x51, 0xe6, 0x61, 0x6f, 0x96, 0x51, 0x4d, 0x78, 0x6d,
0xf5, 0xf2, 0x70, 0x83, 0xaa, 0xeb, 0xe5, 0xd6, 0x36, 0xb8, 0x95, 0xb3, 0xe1, 0xf8, 0x57, 0x08,
0x20, 0xed, 0xef, 0xe3, 0x87, 0xf3, 0xf5, 0x50, 0xde, 0x00, 0xaa, 0xb3, 0xed, 0xf0, 0xeb, 0x0d,
0xae, 0x4f, 0xbd, 0x5a, 0xcb, 0x8d, 0x85, 0x3e, 0x35, 0xb7, 0xe2, 0xb7, 0x80, 0x1f, 0x23, 0x28,
0xf1, 0xb6, 0x2a, 0x3e, 0x3f, 0x0a, 0xb3, 0xda, 0x75, 0x9d, 0xa5, 0xe9, 0x1f, 0xe2, 0x50, 0x6b,
0x9b, 0x79, 0x09, 0x65, 0x0b, 0xad, 0xe3, 0x1e, 0xcc, 0xc7, 0x8d, 0xcc, 0xd1, 0xee, 0x91, 0x69,
0x74, 0x56, 0x6b, 0x39, 0x05, 0x4e, 0xec, 0xa8, 0x22, 0x97, 0xad, 0x8f, 0xcb, 0x65, 0x73, 0x2c,
0xdd, 0xe0, 0x73, 0x79, 0xc9, 0xe8, 0xff, 0x60, 0x98, 0x8b, 0x1c, 0xdd, 0x9a, 0x5e, 0x1b, 0x97,
0xcf, 0x98, 0x75, 0x7e, 0x80, 0xe0, 0x44, 0xff, 0x25, 0x01, 0x9f, 0x19, 0xda, 0x6f, 0x13, 0xb9,
0x35, 0x6b, 0xc5, 0x51, 0x17, 0x0c, 0xfd, 0x33, 0x1c, 0xc5, 0x16, 0x7e, 0x6c, 0xec, 0xc9, 0xb8,
0x25, 0xa3, 0x0e, 0x63, 0xb4, 0x91, 0x3e, 0x56, 0xfe, 0x1a, 0xc1, 0x31, 0xc9, 0xf7, 0x4e, 0x40,
0x69, 0x3e, 0xac, 0xd9, 0x1d, 0x04, 0x26, 0x4b, 0x7f, 0x92, 0xc3, 0xff, 0x04, 0x7e, 0x74, 0x42,
0xf8, 0x12, 0xf6, 0x46, 0xc4, 0x90, 0xfe, 0x01, 0xc1, 0xc9, 0xbb, 0xb1, 0xdf, 0x7f, 0x40, 0xf8,
0xb7, 0x39, 0xfe, 0xa7, 0xf0, 0x13, 0x39, 0xf5, 0xea, 0x38, 0x35, 0x2e, 0x23, 0xfc, 0x0b, 0x04,
0x65, 0xf9, 0xc8, 0x85, 0x2f, 0x8c, 0x3c, 0x18, 0xd9, 0x67, 0xb0, 0x59, 0x3a, 0xb3, 0x28, 0xce,
0xf4, 0xf3, 0xb9, 0xd9, 0x54, 0xc8, 0x67, 0x0e, 0xfd, 0x3a, 0x02, 0x9c, 0xdc, 0xfd, 0x93, 0x6e,
0x00, 0x7e, 0x28, 0x23, 0x6a, 0x64, 0x83, 0xa9, 0x7a, 0x61, 0xec, 0xbc, 0x6c, 0x2a, 0x5d, 0xcf,
0x4d, 0xa5, 0x5e, 0x22, 0xff, 0x5b, 0x08, 0x2a, 0x37, 0x68, 0x72, 0x97, 0xca, 0xb1, 0x65, 0xf6,
0x8d, 0xae, 0x5a, 0x1f, 0x3f, 0x51, 0x20, 0xba, 0xc4, 0x11, 0x3d, 0x84, 0xf3, 0x4d, 0x25, 0x01,
0xfc, 0x10, 0xc1, 0xd2, 0x6d, 0xd5, 0x45, 0xf1, 0xa5, 0x71, 0x92, 0x32, 0x91, 0x7c, 0x72, 0x5c,
0x8f, 0x70, 0x5c, 0x1b, 0xfa, 0x44, 0xb8, 0xb6, 0xc4, 0x73, 0xd7, 0x1b, 0x28, 0xbe, 0x8c, 0xf7,
0x75, 0xed, 0xff, 0x57, 0xbb, 0xe5, 0x34, 0xff, 0xf5, 0x47, 0x39, 0xbe, 0x06, 0xbe, 0x34, 0x09,
0xbe, 0xa6, 0x68, 0xe5, 0xe3, 0xef, 0x23, 0x38, 0xc9, 0x1f, 0x6d, 0x54, 0xc6, 0x38, 0xef, 0xa5,
0x22, 0x7d, 0xe2, 0x99, 0x20, 0xc5, 0x7c, 0x92, 0x83, 0xba, 0xa2, 0x1f, 0x09, 0x14, 0xf3, 0xff,
0x6f, 0x23, 0x38, 0x2e, 0xf3, 0x99, 0xd8, 0xd8, 0x8d, 0x71, 0x36, 0x3b, 0x6a, 0xfe, 0x13, 0x9e,
0xb6, 0x3e, 0x99, 0xa7, 0xbd, 0x8d, 0x60, 0x41, 0x3c, 0x57, 0xe4, 0x54, 0x09, 0xca, 0x7b, 0x46,
0xb5, 0xaf, 0x4d, 0x23, 0xfa, 0xd9, 0xfa, 0x97, 0xb8, 0xd8, 0xe7, 0x71, 0x33, 0x4f, 0xac, 0xef,
0x59, 0x61, 0xf3, 0x65, 0xd1, 0x4c, 0x7e, 0xa5, 0xe9, 0x78, 0xad, 0xf0, 0x05, 0x1d, 0xe7, 0xe6,
0x42, 0x36, 0xe7, 0x32, 0xc2, 0x11, 0x2c, 0x32, 0xbf, 0xe0, 0xbd, 0x1f, 0x5c, 0xeb, 0xeb, 0x14,
0x0d, 0xb4, 0x85, 0xaa, 0xd5, 0x81, 0x5e, 0x52, 0x9a, 0xfc, 0xc4, 0x4d, 0x1c, 0x3f, 0x98, 0x2b,
0x96, 0x0b, 0x7a, 0x0d, 0xc1, 0x49, 0xd5, 0xd1, 0x63, 0xf1, 0x13, 0xbb, 0x79, 0x1e, 0x0a, 0x51,
0x4f, 0xe3, 0xf5, 0x89, 0x7c, 0x88, 0xc3, 0x79, 0xfa, 0x99, 0x3f, 0xbe, 0x77, 0x16, 0xbd, 0xfb,
0xde, 0x59, 0xf4, 0xb7, 0xf7, 0xce, 0xa2, 0x17, 0x1e, 0x9b, 0xec, 0xdf, 0xda, 0xa6, 0x63, 0x53,
0x37, 0x52, 0xd9, 0xff, 0x37, 0x00, 0x00, 0xff, 0xff, 0x47, 0x2b, 0xad, 0x16, 0x93, 0x2e, 0x00,
0x00,
}
@ -3121,6 +3123,8 @@ type ApplicationServiceClient interface {
RevisionMetadata(ctx context.Context, in *RevisionMetadataQuery, opts ...grpc.CallOption) (*v1alpha1.RevisionMetadata, error)
// Get the chart metadata (description, maintainers, home) for a specific revision of the application
RevisionChartDetails(ctx context.Context, in *RevisionMetadataQuery, opts ...grpc.CallOption) (*v1alpha1.ChartDetails, error)
// Get the chart metadata (description, maintainers, home) for a specific revision of the application
GetOCIMetadata(ctx context.Context, in *RevisionMetadataQuery, opts ...grpc.CallOption) (*v1alpha1.OCIMetadata, error)
// GetManifests returns application manifests
GetManifests(ctx context.Context, in *ApplicationManifestQuery, opts ...grpc.CallOption) (*apiclient.ManifestResponse, error)
// GetManifestsWithFiles returns application manifests using provided files to generate them
@ -3266,6 +3270,15 @@ func (c *applicationServiceClient) RevisionChartDetails(ctx context.Context, in
return out, nil
}
func (c *applicationServiceClient) GetOCIMetadata(ctx context.Context, in *RevisionMetadataQuery, opts ...grpc.CallOption) (*v1alpha1.OCIMetadata, error) {
out := new(v1alpha1.OCIMetadata)
err := c.cc.Invoke(ctx, "/application.ApplicationService/GetOCIMetadata", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *applicationServiceClient) GetManifests(ctx context.Context, in *ApplicationManifestQuery, opts ...grpc.CallOption) (*apiclient.ManifestResponse, error) {
out := new(apiclient.ManifestResponse)
err := c.cc.Invoke(ctx, "/application.ApplicationService/GetManifests", in, out, opts...)
@ -3535,6 +3548,8 @@ type ApplicationServiceServer interface {
RevisionMetadata(context.Context, *RevisionMetadataQuery) (*v1alpha1.RevisionMetadata, error)
// Get the chart metadata (description, maintainers, home) for a specific revision of the application
RevisionChartDetails(context.Context, *RevisionMetadataQuery) (*v1alpha1.ChartDetails, error)
// Get the chart metadata (description, maintainers, home) for a specific revision of the application
GetOCIMetadata(context.Context, *RevisionMetadataQuery) (*v1alpha1.OCIMetadata, error)
// GetManifests returns application manifests
GetManifests(context.Context, *ApplicationManifestQuery) (*apiclient.ManifestResponse, error)
// GetManifestsWithFiles returns application manifests using provided files to generate them
@ -3605,6 +3620,9 @@ func (*UnimplementedApplicationServiceServer) RevisionMetadata(ctx context.Conte
func (*UnimplementedApplicationServiceServer) RevisionChartDetails(ctx context.Context, req *RevisionMetadataQuery) (*v1alpha1.ChartDetails, error) {
return nil, status.Errorf(codes.Unimplemented, "method RevisionChartDetails not implemented")
}
func (*UnimplementedApplicationServiceServer) GetOCIMetadata(ctx context.Context, req *RevisionMetadataQuery) (*v1alpha1.OCIMetadata, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetOCIMetadata not implemented")
}
func (*UnimplementedApplicationServiceServer) GetManifests(ctx context.Context, req *ApplicationManifestQuery) (*apiclient.ManifestResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetManifests not implemented")
}
@ -3817,6 +3835,24 @@ func _ApplicationService_RevisionChartDetails_Handler(srv interface{}, ctx conte
return interceptor(ctx, in, info, handler)
}
func _ApplicationService_GetOCIMetadata_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RevisionMetadataQuery)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ApplicationServiceServer).GetOCIMetadata(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/application.ApplicationService/GetOCIMetadata",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ApplicationServiceServer).GetOCIMetadata(ctx, req.(*RevisionMetadataQuery))
}
return interceptor(ctx, in, info, handler)
}
func _ApplicationService_GetManifests_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ApplicationManifestQuery)
if err := dec(in); err != nil {
@ -4223,6 +4259,10 @@ var _ApplicationService_serviceDesc = grpc.ServiceDesc{
MethodName: "RevisionChartDetails",
Handler: _ApplicationService_RevisionChartDetails_Handler,
},
{
MethodName: "GetOCIMetadata",
Handler: _ApplicationService_GetOCIMetadata_Handler,
},
{
MethodName: "GetManifests",
Handler: _ApplicationService_GetManifests_Handler,

View file

@ -553,6 +553,100 @@ func local_request_ApplicationService_RevisionChartDetails_0(ctx context.Context
}
var (
filter_ApplicationService_GetOCIMetadata_0 = &utilities.DoubleArray{Encoding: map[string]int{"name": 0, "revision": 1}, Base: []int{1, 1, 2, 0, 0}, Check: []int{0, 1, 1, 2, 3}}
)
func request_ApplicationService_GetOCIMetadata_0(ctx context.Context, marshaler runtime.Marshaler, client ApplicationServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq RevisionMetadataQuery
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["name"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name")
}
protoReq.Name, err = runtime.StringP(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err)
}
val, ok = pathParams["revision"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "revision")
}
protoReq.Revision, err = runtime.StringP(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "revision", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ApplicationService_GetOCIMetadata_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.GetOCIMetadata(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_ApplicationService_GetOCIMetadata_0(ctx context.Context, marshaler runtime.Marshaler, server ApplicationServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq RevisionMetadataQuery
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["name"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "name")
}
protoReq.Name, err = runtime.StringP(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "name", err)
}
val, ok = pathParams["revision"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "revision")
}
protoReq.Revision, err = runtime.StringP(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "revision", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_ApplicationService_GetOCIMetadata_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.GetOCIMetadata(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_ApplicationService_GetManifests_0 = &utilities.DoubleArray{Encoding: map[string]int{"name": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}}
)
@ -2184,6 +2278,29 @@ func RegisterApplicationServiceHandlerServer(ctx context.Context, mux *runtime.S
})
mux.Handle("GET", pattern_ApplicationService_GetOCIMetadata_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_ApplicationService_GetOCIMetadata_0(rctx, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_ApplicationService_GetOCIMetadata_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_ApplicationService_GetManifests_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@ -2804,6 +2921,26 @@ func RegisterApplicationServiceHandlerClient(ctx context.Context, mux *runtime.S
})
mux.Handle("GET", pattern_ApplicationService_GetOCIMetadata_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_ApplicationService_GetOCIMetadata_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_ApplicationService_GetOCIMetadata_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_ApplicationService_GetManifests_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@ -3244,6 +3381,8 @@ var (
pattern_ApplicationService_RevisionChartDetails_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6}, []string{"api", "v1", "applications", "name", "revisions", "revision", "chartdetails"}, "", runtime.AssumeColonVerbOpt(true)))
pattern_ApplicationService_GetOCIMetadata_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4, 1, 0, 4, 1, 5, 5, 2, 6}, []string{"api", "v1", "applications", "name", "revisions", "revision", "ocimetadata"}, "", runtime.AssumeColonVerbOpt(true)))
pattern_ApplicationService_GetManifests_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v1", "applications", "name", "manifests"}, "", runtime.AssumeColonVerbOpt(true)))
pattern_ApplicationService_GetManifestsWithFiles_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3}, []string{"api", "v1", "applications", "manifestsWithFiles"}, "", runtime.AssumeColonVerbOpt(true)))
@ -3304,6 +3443,8 @@ var (
forward_ApplicationService_RevisionChartDetails_0 = runtime.ForwardResponseMessage
forward_ApplicationService_GetOCIMetadata_0 = runtime.ForwardResponseMessage
forward_ApplicationService_GetManifests_0 = runtime.ForwardResponseMessage
forward_ApplicationService_GetManifestsWithFiles_0 = runtime.ForwardResponseMessage

View file

@ -399,7 +399,9 @@ type RepoAccessQuery struct {
// Whether to use azure workload identity for authentication
UseAzureWorkloadIdentity bool `protobuf:"varint,20,opt,name=useAzureWorkloadIdentity,proto3" json:"useAzureWorkloadIdentity,omitempty"`
// BearerToken contains the bearer token used for Git auth at the repo server
BearerToken string `protobuf:"bytes,21,opt,name=bearerToken,proto3" json:"bearerToken,omitempty"`
BearerToken string `protobuf:"bytes,21,opt,name=bearerToken,proto3" json:"bearerToken,omitempty"`
// Whether https should be disabled for an OCI repo
InsecureOciForceHttp bool `protobuf:"varint,22,opt,name=insecureOciForceHttp,proto3" json:"insecureOciForceHttp,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
@ -578,6 +580,13 @@ func (m *RepoAccessQuery) GetBearerToken() string {
return ""
}
func (m *RepoAccessQuery) GetInsecureOciForceHttp() bool {
if m != nil {
return m.InsecureOciForceHttp
}
return false
}
type RepoResponse struct {
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
@ -748,92 +757,94 @@ func init() {
}
var fileDescriptor_8d38260443475705 = []byte{
// 1349 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x98, 0xcf, 0x6f, 0x1c, 0x35,
0x14, 0xc7, 0x35, 0x49, 0xb3, 0x4d, 0x5e, 0x9a, 0x76, 0xeb, 0x24, 0x65, 0xd8, 0xa6, 0x69, 0x98,
0x96, 0x2a, 0x8d, 0xda, 0xd9, 0x66, 0x0b, 0xa2, 0x2a, 0x02, 0x69, 0x9b, 0x54, 0x6d, 0x44, 0x44,
0xcb, 0x94, 0x52, 0x09, 0x81, 0x90, 0x33, 0xfb, 0xb2, 0x3b, 0xcd, 0x64, 0xc6, 0xb5, 0xbd, 0xdb,
0x2e, 0x55, 0x2f, 0x1c, 0x10, 0x12, 0x5c, 0x10, 0x02, 0x71, 0x02, 0x0e, 0x48, 0x48, 0x70, 0xe7,
0x6f, 0xe0, 0x88, 0xc4, 0x3f, 0x80, 0x2a, 0xfe, 0x08, 0x6e, 0x20, 0xdb, 0xb3, 0x33, 0xb3, 0xc9,
0xfe, 0x48, 0xd5, 0x34, 0x37, 0xfb, 0xd9, 0xf3, 0xde, 0xc7, 0x5f, 0xbf, 0x67, 0x7b, 0x17, 0x1c,
0x81, 0xbc, 0x85, 0xbc, 0xcc, 0x91, 0xc5, 0x22, 0x90, 0x31, 0x6f, 0xe7, 0x9a, 0x2e, 0xe3, 0xb1,
0x8c, 0x09, 0x64, 0x96, 0xd2, 0x5c, 0x3d, 0x8e, 0xeb, 0x21, 0x96, 0x29, 0x0b, 0xca, 0x34, 0x8a,
0x62, 0x49, 0x65, 0x10, 0x47, 0xc2, 0xcc, 0x2c, 0xad, 0xd7, 0x03, 0xd9, 0x68, 0x6e, 0xb8, 0x7e,
0xbc, 0x5d, 0xa6, 0xbc, 0x1e, 0x33, 0x1e, 0xdf, 0xd7, 0x8d, 0x8b, 0x7e, 0xad, 0xdc, 0xba, 0x5c,
0x66, 0x5b, 0x75, 0xf5, 0xa5, 0x28, 0x53, 0xc6, 0xc2, 0xc0, 0xd7, 0xdf, 0x96, 0x5b, 0xcb, 0x34,
0x64, 0x0d, 0xba, 0x5c, 0xae, 0x63, 0x84, 0x9c, 0x4a, 0xac, 0x25, 0xde, 0xae, 0x0f, 0xf1, 0xa6,
0xb1, 0x86, 0xe2, 0x3b, 0x6d, 0x98, 0xf2, 0x90, 0xc5, 0x55, 0xc6, 0xc4, 0x7b, 0x4d, 0xe4, 0x6d,
0x42, 0xe0, 0x90, 0x9a, 0x64, 0x5b, 0x0b, 0xd6, 0xe2, 0x84, 0xa7, 0xdb, 0xa4, 0x04, 0xe3, 0x1c,
0x5b, 0x81, 0x08, 0xe2, 0xc8, 0x1e, 0xd1, 0xf6, 0xb4, 0x4f, 0x6c, 0x38, 0x4c, 0x19, 0x7b, 0x97,
0x6e, 0xa3, 0x3d, 0xaa, 0x87, 0x3a, 0x5d, 0x32, 0x0f, 0x40, 0x19, 0xbb, 0xcd, 0xe3, 0xfb, 0xe8,
0x4b, 0xfb, 0x90, 0x1e, 0xcc, 0x59, 0x9c, 0x65, 0x38, 0x5c, 0x65, 0x6c, 0x2d, 0xda, 0x8c, 0x55,
0x50, 0xd9, 0x66, 0xd8, 0x09, 0xaa, 0xda, 0xca, 0xc6, 0xa8, 0x6c, 0x24, 0x01, 0x75, 0xdb, 0xf9,
0xd7, 0x82, 0xe9, 0x04, 0x77, 0x15, 0x25, 0x0d, 0xc2, 0x04, 0xba, 0x0e, 0x05, 0x11, 0x37, 0xb9,
0x6f, 0x3c, 0x4c, 0x56, 0x6e, 0xb9, 0x99, 0x3a, 0x6e, 0x47, 0x1d, 0xdd, 0xf8, 0xc4, 0xaf, 0xb9,
0xad, 0xcb, 0x2e, 0xdb, 0xaa, 0xbb, 0x4a, 0x6b, 0x37, 0xa7, 0xb5, 0xdb, 0xd1, 0xda, 0xad, 0x66,
0xc6, 0x3b, 0xda, 0xad, 0x97, 0xb8, 0xcf, 0xaf, 0x76, 0x64, 0xd0, 0x6a, 0x47, 0x77, 0xae, 0x96,
0x2c, 0xc0, 0xa4, 0xf1, 0xb1, 0x16, 0xd5, 0xf0, 0x91, 0x96, 0x63, 0xcc, 0xcb, 0x9b, 0xc8, 0x1c,
0x4c, 0xb4, 0x90, 0x2b, 0x51, 0xd7, 0x6a, 0xf6, 0x98, 0x1e, 0xcf, 0x0c, 0xce, 0x5b, 0x50, 0xec,
0x6c, 0x94, 0x87, 0x82, 0xc5, 0x91, 0x40, 0x72, 0x1e, 0xc6, 0x02, 0x89, 0xdb, 0xc2, 0xb6, 0x16,
0x46, 0x17, 0x27, 0x2b, 0xd3, 0x6e, 0x6e, 0x7b, 0x13, 0x69, 0x3d, 0x33, 0xc3, 0xf1, 0x61, 0x42,
0x7d, 0xde, 0x7f, 0x8f, 0x1d, 0x38, 0xb2, 0x19, 0xab, 0xa5, 0xe2, 0x26, 0x47, 0x61, 0x64, 0x1f,
0xf7, 0xba, 0x6c, 0xc3, 0xd6, 0xe8, 0xfc, 0x37, 0x06, 0xc7, 0x34, 0xa4, 0xef, 0xa3, 0x18, 0x9c,
0x4f, 0x4d, 0x81, 0x3c, 0xca, 0x64, 0x4c, 0xfb, 0x6a, 0x8c, 0x51, 0x21, 0x1e, 0xc6, 0xbc, 0x96,
0x44, 0x48, 0xfb, 0xe4, 0x2c, 0x4c, 0x09, 0xd1, 0xb8, 0xcd, 0x83, 0x16, 0x95, 0xf8, 0x0e, 0xb6,
0x93, 0xa4, 0xea, 0x36, 0x2a, 0x0f, 0x41, 0x24, 0xd0, 0x6f, 0x72, 0xd4, 0x32, 0x8e, 0x7b, 0x69,
0x9f, 0x5c, 0x80, 0xe3, 0x32, 0x14, 0x2b, 0x61, 0x80, 0x91, 0x5c, 0x41, 0x2e, 0x57, 0xa9, 0xa4,
0x76, 0x41, 0x7b, 0xd9, 0x3d, 0x40, 0x96, 0xa0, 0xd8, 0x65, 0x54, 0x21, 0x0f, 0xeb, 0xc9, 0xbb,
0xec, 0x69, 0x0a, 0x4f, 0x74, 0xa7, 0xb0, 0x5e, 0x23, 0x18, 0x9b, 0x5e, 0xdf, 0x1c, 0x4c, 0x60,
0x44, 0x37, 0x42, 0xbc, 0xe5, 0x07, 0xf6, 0xa4, 0xc6, 0xcb, 0x0c, 0xe4, 0x12, 0x4c, 0x9b, 0xcc,
0xad, 0x2a, 0x55, 0xd3, 0x75, 0x1e, 0xd1, 0x0e, 0x7a, 0x0d, 0xa9, 0xbc, 0x4a, 0xcd, 0x6b, 0xab,
0xf6, 0xd4, 0x82, 0xb5, 0x38, 0xea, 0xe5, 0x4d, 0xe4, 0x0a, 0xbc, 0x94, 0x75, 0x23, 0x21, 0x69,
0x18, 0xea, 0xd4, 0x5e, 0x5b, 0xb5, 0x8f, 0xea, 0xd9, 0xfd, 0x86, 0xc9, 0xdb, 0x50, 0x4a, 0x87,
0xae, 0x47, 0x12, 0x39, 0xe3, 0x81, 0xc0, 0x6b, 0x54, 0xe0, 0x5d, 0x1e, 0xda, 0xc7, 0x34, 0xd4,
0x80, 0x19, 0x64, 0x06, 0xc6, 0x18, 0x8f, 0x1f, 0xb5, 0xed, 0xa2, 0x9e, 0x6a, 0x3a, 0xaa, 0x86,
0x58, 0x92, 0x42, 0xc7, 0x4d, 0x0d, 0x25, 0x5d, 0x52, 0x81, 0x99, 0xba, 0xcf, 0xee, 0x20, 0x6f,
0x05, 0x3e, 0x56, 0x7d, 0x3f, 0x6e, 0x46, 0x5a, 0x73, 0xa2, 0xa7, 0xf5, 0x1c, 0x23, 0x2e, 0x10,
0x9d, 0xa3, 0x37, 0xa5, 0x64, 0xd7, 0xa8, 0x08, 0xfc, 0x6a, 0x53, 0x36, 0xec, 0x69, 0x2d, 0x6c,
0x8f, 0x11, 0x72, 0x15, 0xec, 0xa6, 0xc0, 0xea, 0xa7, 0x4d, 0x8e, 0xf7, 0x62, 0xbe, 0x15, 0xc6,
0xb4, 0xb6, 0x56, 0xc3, 0x48, 0x06, 0xb2, 0x6d, 0xcf, 0xe8, 0xaf, 0xfa, 0x8e, 0x2b, 0xad, 0x37,
0x90, 0x72, 0xe4, 0xef, 0xc7, 0x5b, 0x18, 0xd9, 0xb3, 0x1a, 0x2b, 0x6f, 0x72, 0x8e, 0xc2, 0x11,
0x55, 0x00, 0x9d, 0x0a, 0x75, 0x7e, 0xb1, 0xe0, 0xb8, 0x32, 0xac, 0x70, 0xa4, 0x12, 0x3d, 0x7c,
0xd0, 0x44, 0x21, 0xc9, 0x47, 0xb9, 0x9a, 0x98, 0xac, 0xdc, 0x7c, 0xbe, 0xc3, 0xca, 0x4b, 0x6b,
0x3e, 0xa9, 0xae, 0x13, 0x50, 0x68, 0x32, 0x81, 0x5c, 0x26, 0x35, 0x9c, 0xf4, 0x54, 0xe6, 0xf9,
0x1c, 0x6b, 0xe2, 0x56, 0x14, 0xb6, 0x75, 0x69, 0x8d, 0x7b, 0x99, 0xc1, 0x79, 0x60, 0x40, 0xef,
0xb2, 0xda, 0x41, 0x81, 0x56, 0x7e, 0x38, 0x61, 0x62, 0x1a, 0x63, 0xb2, 0xb5, 0xe4, 0x2b, 0x0b,
0x0e, 0xad, 0x07, 0x42, 0x92, 0xd9, 0xfc, 0x71, 0x96, 0x1e, 0x5e, 0xa5, 0xf5, 0xfd, 0xa2, 0x50,
0x41, 0x9c, 0xd3, 0x9f, 0xfd, 0xf5, 0xcf, 0x37, 0x23, 0x27, 0xc8, 0x8c, 0xbe, 0xb4, 0x5b, 0xcb,
0xd9, 0x0d, 0x19, 0xa0, 0xf8, 0x62, 0xc4, 0x22, 0x5f, 0x5a, 0x30, 0x7a, 0x03, 0xfb, 0xd2, 0xec,
0x9b, 0x26, 0xce, 0x19, 0x4d, 0x72, 0x8a, 0x9c, 0xec, 0x45, 0x52, 0x7e, 0xac, 0x7a, 0x4f, 0xc8,
0x77, 0x16, 0x8c, 0xdf, 0x40, 0x79, 0x8f, 0x07, 0x12, 0x5f, 0x3c, 0xd2, 0x79, 0x8d, 0x74, 0x86,
0xbc, 0xd2, 0x41, 0x7a, 0xa8, 0xe2, 0x5e, 0xec, 0x05, 0xf6, 0xad, 0x05, 0x45, 0x25, 0xa8, 0x97,
0x1b, 0x3b, 0x98, 0x1d, 0x9c, 0x1b, 0xb4, 0x83, 0xe4, 0x27, 0x0b, 0x66, 0xd5, 0x34, 0xad, 0xd8,
0xc1, 0xc3, 0x39, 0x1a, 0x6e, 0x8e, 0x94, 0xfa, 0x2b, 0x48, 0x3e, 0x86, 0x71, 0xa3, 0xdc, 0x66,
0x5f, 0xa8, 0x62, 0xb7, 0x79, 0x53, 0x38, 0x8b, 0xda, 0xb1, 0x43, 0x16, 0x06, 0x64, 0x4b, 0x99,
0x2b, 0x97, 0xdb, 0xc6, 0xbd, 0x7a, 0x38, 0x90, 0x97, 0x77, 0xba, 0x4f, 0xdf, 0x7d, 0xa5, 0xb9,
0x5e, 0x43, 0xe9, 0x39, 0xb6, 0xa7, 0x70, 0x54, 0x85, 0xf8, 0xda, 0x82, 0xa9, 0x1b, 0x28, 0xb3,
0x17, 0x1a, 0x39, 0xdd, 0xc3, 0x73, 0xfe, 0xf5, 0x56, 0x72, 0xfa, 0x4f, 0x48, 0x01, 0xde, 0xd4,
0x00, 0xaf, 0x3b, 0x97, 0x7a, 0x03, 0x98, 0x77, 0x94, 0xf6, 0x73, 0xd7, 0x5b, 0xd7, 0x28, 0x35,
0xe3, 0xe1, 0xaa, 0xb5, 0x44, 0x5a, 0x1a, 0xe9, 0x26, 0x86, 0xdb, 0x2b, 0x0d, 0xca, 0x65, 0x5f,
0x99, 0xe7, 0xf3, 0xe6, 0x6c, 0x7a, 0x0a, 0xe1, 0x6a, 0x88, 0x45, 0x72, 0x6e, 0x90, 0x0a, 0x0d,
0x0c, 0xb7, 0x7d, 0x13, 0xe6, 0x7b, 0x0b, 0x0a, 0xe6, 0xe4, 0x27, 0xa7, 0x76, 0x46, 0xec, 0xba,
0x11, 0xf6, 0xb1, 0x66, 0x5f, 0x35, 0x19, 0xe7, 0xf4, 0x2c, 0x87, 0xab, 0xfa, 0xe0, 0x55, 0xc7,
0xda, 0x8f, 0x16, 0x14, 0x3b, 0x08, 0x9d, 0x6f, 0x0f, 0x0e, 0xd2, 0x19, 0x0e, 0x49, 0x7e, 0xb5,
0x60, 0xd6, 0xc4, 0xef, 0xae, 0xdd, 0x03, 0xc4, 0x4c, 0xb2, 0xde, 0x19, 0x50, 0xbd, 0x09, 0xec,
0xcf, 0x16, 0x14, 0xcc, 0xd5, 0xb9, 0x9b, 0xae, 0xeb, 0x4a, 0xdd, 0x47, 0xba, 0x65, 0x93, 0x8d,
0xa5, 0x01, 0x35, 0xa9, 0x51, 0x9e, 0x64, 0xbb, 0xfe, 0x9b, 0x05, 0xc5, 0x0e, 0x4e, 0x7f, 0x39,
0x5f, 0x14, 0xb0, 0xfb, 0x6c, 0xc0, 0xe4, 0x77, 0x0b, 0x66, 0x0d, 0xcb, 0xd0, 0x0c, 0x78, 0x51,
0xc8, 0xaf, 0x69, 0x64, 0xb7, 0x74, 0x6e, 0xd8, 0x0d, 0xd8, 0x05, 0x4e, 0xa1, 0xb0, 0x8a, 0x21,
0xf6, 0xbf, 0xa2, 0xed, 0x9d, 0xe6, 0xf4, 0x88, 0x39, 0x67, 0x5e, 0x01, 0x4b, 0x83, 0x5e, 0x01,
0x6a, 0x27, 0x1b, 0x50, 0x34, 0x21, 0x72, 0xaa, 0x3c, 0x73, 0xb0, 0x33, 0x7b, 0x08, 0x46, 0x04,
0xcc, 0x9a, 0x48, 0x3b, 0x37, 0xe1, 0x99, 0xc3, 0x25, 0xcf, 0x89, 0xa5, 0x3d, 0x3c, 0x27, 0x1e,
0xc3, 0xd1, 0x0f, 0x68, 0x18, 0xa8, 0x4d, 0x35, 0x3f, 0x26, 0xc9, 0xc9, 0x5d, 0x97, 0x44, 0xf6,
0x23, 0x73, 0x40, 0xcc, 0x8a, 0x8e, 0x79, 0xc1, 0x39, 0x3b, 0xe8, 0xc8, 0x6e, 0x25, 0xa1, 0x92,
0xed, 0xfb, 0xdc, 0x82, 0xe9, 0x4e, 0x74, 0xbd, 0xe8, 0xe7, 0x43, 0xb8, 0xa2, 0x11, 0x2a, 0xce,
0xd2, 0xd0, 0x65, 0xef, 0x00, 0xb9, 0x76, 0xfd, 0x8f, 0xa7, 0xf3, 0xd6, 0x9f, 0x4f, 0xe7, 0xad,
0xbf, 0x9f, 0xce, 0x5b, 0x1f, 0xbe, 0xb1, 0xb7, 0xff, 0x8f, 0x7c, 0xfd, 0xb3, 0x34, 0xf7, 0x4f,
0xcf, 0x46, 0x41, 0xff, 0xd5, 0x73, 0xf9, 0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xb8, 0x97, 0xe0,
0xc3, 0xcf, 0x12, 0x00, 0x00,
// 1392 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x58, 0xdf, 0x6e, 0x1b, 0xc5,
0x17, 0xd6, 0x26, 0x8d, 0x9b, 0x4c, 0x9a, 0xd6, 0x9d, 0x24, 0xed, 0xfe, 0xdc, 0x34, 0xcd, 0x6f,
0x5b, 0xa2, 0x34, 0x6a, 0xd7, 0x4d, 0x0a, 0xa2, 0x2a, 0x02, 0xc9, 0x4d, 0x4a, 0x6b, 0x11, 0x91,
0xb2, 0x6d, 0xa9, 0x84, 0x40, 0x68, 0xb2, 0x3e, 0xb1, 0xb7, 0xd9, 0xec, 0x4e, 0x67, 0xc6, 0x6e,
0x4d, 0xd5, 0x1b, 0x84, 0x10, 0x12, 0xdc, 0x20, 0x04, 0xe2, 0x0e, 0x2e, 0x90, 0x90, 0xe0, 0x12,
0x89, 0x67, 0xe0, 0x12, 0x89, 0x17, 0x40, 0x15, 0x0f, 0xc1, 0x25, 0x9a, 0x33, 0xeb, 0xf5, 0x3a,
0xf1, 0x9f, 0x44, 0x4d, 0x73, 0x37, 0x73, 0xce, 0xec, 0xf9, 0xbe, 0xf3, 0xcd, 0x99, 0x33, 0x63,
0x13, 0x47, 0x82, 0x68, 0x80, 0x28, 0x0a, 0xe0, 0xb1, 0x0c, 0x54, 0x2c, 0x9a, 0x99, 0xa1, 0xcb,
0x45, 0xac, 0x62, 0x4a, 0xda, 0x96, 0xc2, 0x4c, 0x35, 0x8e, 0xab, 0x21, 0x14, 0x19, 0x0f, 0x8a,
0x2c, 0x8a, 0x62, 0xc5, 0x54, 0x10, 0x47, 0xd2, 0xac, 0x2c, 0xac, 0x55, 0x03, 0x55, 0xab, 0x6f,
0xb8, 0x7e, 0xbc, 0x5d, 0x64, 0xa2, 0x1a, 0x73, 0x11, 0x3f, 0xc4, 0xc1, 0x65, 0xbf, 0x52, 0x6c,
0x5c, 0x2d, 0xf2, 0xad, 0xaa, 0xfe, 0x52, 0x16, 0x19, 0xe7, 0x61, 0xe0, 0xe3, 0xb7, 0xc5, 0xc6,
0x12, 0x0b, 0x79, 0x8d, 0x2d, 0x15, 0xab, 0x10, 0x81, 0x60, 0x0a, 0x2a, 0x49, 0xb4, 0x9b, 0x03,
0xa2, 0x21, 0xad, 0x81, 0xf4, 0x9d, 0x26, 0x99, 0xf0, 0x80, 0xc7, 0x25, 0xce, 0xe5, 0x7b, 0x75,
0x10, 0x4d, 0x4a, 0xc9, 0x11, 0xbd, 0xc8, 0xb6, 0xe6, 0xac, 0x85, 0x31, 0x0f, 0xc7, 0xb4, 0x40,
0x46, 0x05, 0x34, 0x02, 0x19, 0xc4, 0x91, 0x3d, 0x84, 0xf6, 0x74, 0x4e, 0x6d, 0x72, 0x94, 0x71,
0xfe, 0x2e, 0xdb, 0x06, 0x7b, 0x18, 0x5d, 0xad, 0x29, 0x9d, 0x25, 0x84, 0x71, 0x7e, 0x47, 0xc4,
0x0f, 0xc1, 0x57, 0xf6, 0x11, 0x74, 0x66, 0x2c, 0xce, 0x12, 0x39, 0x5a, 0xe2, 0xbc, 0x1c, 0x6d,
0xc6, 0x1a, 0x54, 0x35, 0x39, 0xb4, 0x40, 0xf5, 0x58, 0xdb, 0x38, 0x53, 0xb5, 0x04, 0x10, 0xc7,
0xce, 0xbf, 0x16, 0x99, 0x4c, 0xe8, 0xae, 0x82, 0x62, 0x41, 0x98, 0x90, 0xae, 0x92, 0x9c, 0x8c,
0xeb, 0xc2, 0x37, 0x11, 0xc6, 0x97, 0xd7, 0xdd, 0xb6, 0x3a, 0x6e, 0x4b, 0x1d, 0x1c, 0x7c, 0xec,
0x57, 0xdc, 0xc6, 0x55, 0x97, 0x6f, 0x55, 0x5d, 0xad, 0xb5, 0x9b, 0xd1, 0xda, 0x6d, 0x69, 0xed,
0x96, 0xda, 0xc6, 0xbb, 0x18, 0xd6, 0x4b, 0xc2, 0x67, 0xb3, 0x1d, 0xea, 0x97, 0xed, 0xf0, 0xce,
0x6c, 0xe9, 0x1c, 0x19, 0x37, 0x31, 0xca, 0x51, 0x05, 0x9e, 0xa0, 0x1c, 0x23, 0x5e, 0xd6, 0x44,
0x67, 0xc8, 0x58, 0x03, 0x84, 0x16, 0xb5, 0x5c, 0xb1, 0x47, 0xd0, 0xdf, 0x36, 0x38, 0x6f, 0x92,
0x7c, 0x6b, 0xa3, 0x3c, 0x90, 0x3c, 0x8e, 0x24, 0xd0, 0x8b, 0x64, 0x24, 0x50, 0xb0, 0x2d, 0x6d,
0x6b, 0x6e, 0x78, 0x61, 0x7c, 0x79, 0xd2, 0xcd, 0x6c, 0x6f, 0x22, 0xad, 0x67, 0x56, 0x38, 0x3e,
0x19, 0xd3, 0x9f, 0xf7, 0xde, 0x63, 0x87, 0x1c, 0xdb, 0x8c, 0x75, 0xaa, 0xb0, 0x29, 0x40, 0x1a,
0xd9, 0x47, 0xbd, 0x0e, 0xdb, 0xa0, 0x1c, 0x9d, 0xdf, 0x72, 0xe4, 0x04, 0x92, 0xf4, 0x7d, 0x90,
0xfd, 0xeb, 0xa9, 0x2e, 0x41, 0x44, 0x6d, 0x19, 0xd3, 0xb9, 0xf6, 0x71, 0x26, 0xe5, 0xe3, 0x58,
0x54, 0x12, 0x84, 0x74, 0x4e, 0x2f, 0x90, 0x09, 0x29, 0x6b, 0x77, 0x44, 0xd0, 0x60, 0x0a, 0xde,
0x81, 0x66, 0x52, 0x54, 0x9d, 0x46, 0x1d, 0x21, 0x88, 0x24, 0xf8, 0x75, 0x01, 0x28, 0xe3, 0xa8,
0x97, 0xce, 0xe9, 0x25, 0x72, 0x52, 0x85, 0x72, 0x25, 0x0c, 0x20, 0x52, 0x2b, 0x20, 0xd4, 0x2a,
0x53, 0xcc, 0xce, 0x61, 0x94, 0xdd, 0x0e, 0xba, 0x48, 0xf2, 0x1d, 0x46, 0x0d, 0x79, 0x14, 0x17,
0xef, 0xb2, 0xa7, 0x25, 0x3c, 0xd6, 0x59, 0xc2, 0x98, 0x23, 0x31, 0x36, 0xcc, 0x6f, 0x86, 0x8c,
0x41, 0xc4, 0x36, 0x42, 0x58, 0xf7, 0x03, 0x7b, 0x1c, 0xe9, 0xb5, 0x0d, 0xf4, 0x0a, 0x99, 0x34,
0x95, 0x5b, 0xd2, 0xaa, 0xa6, 0x79, 0x1e, 0xc3, 0x00, 0xdd, 0x5c, 0xba, 0xae, 0x52, 0x73, 0x79,
0xd5, 0x9e, 0x98, 0xb3, 0x16, 0x86, 0xbd, 0xac, 0x89, 0x5e, 0x23, 0xa7, 0xdb, 0xd3, 0x48, 0x2a,
0x16, 0x86, 0x58, 0xda, 0xe5, 0x55, 0xfb, 0x38, 0xae, 0xee, 0xe5, 0xa6, 0x6f, 0x91, 0x42, 0xea,
0xba, 0x19, 0x29, 0x10, 0x5c, 0x04, 0x12, 0x6e, 0x30, 0x09, 0xf7, 0x45, 0x68, 0x9f, 0x40, 0x52,
0x7d, 0x56, 0xd0, 0x29, 0x32, 0xc2, 0x45, 0xfc, 0xa4, 0x69, 0xe7, 0x71, 0xa9, 0x99, 0xe8, 0x33,
0xc4, 0x93, 0x12, 0x3a, 0x69, 0xce, 0x50, 0x32, 0xa5, 0xcb, 0x64, 0xaa, 0xea, 0xf3, 0xbb, 0x20,
0x1a, 0x81, 0x0f, 0x25, 0xdf, 0x8f, 0xeb, 0x11, 0x6a, 0x4e, 0x71, 0x59, 0x57, 0x1f, 0x75, 0x09,
0xc5, 0x1a, 0xbd, 0xad, 0x14, 0xbf, 0xc1, 0x64, 0xe0, 0x97, 0xea, 0xaa, 0x66, 0x4f, 0xa2, 0xb0,
0x5d, 0x3c, 0xf4, 0x3a, 0xb1, 0xeb, 0x12, 0x4a, 0x9f, 0xd4, 0x05, 0x3c, 0x88, 0xc5, 0x56, 0x18,
0xb3, 0x4a, 0xb9, 0x02, 0x91, 0x0a, 0x54, 0xd3, 0x9e, 0xc2, 0xaf, 0x7a, 0xfa, 0xb5, 0xd6, 0x1b,
0xc0, 0x04, 0x88, 0x7b, 0xf1, 0x16, 0x44, 0xf6, 0x34, 0xd2, 0xca, 0x9a, 0x74, 0x06, 0xad, 0x5a,
0x5b, 0xf7, 0x83, 0xb7, 0x5b, 0xf0, 0xf6, 0x29, 0x8c, 0xdc, 0xd5, 0xe7, 0x1c, 0x27, 0xc7, 0xf4,
0xa1, 0x69, 0x9d, 0x6a, 0xe7, 0x67, 0x8b, 0x9c, 0xd4, 0x86, 0x15, 0x01, 0x4c, 0x81, 0x07, 0x8f,
0xea, 0x20, 0x15, 0xfd, 0x30, 0x73, 0x8e, 0xc6, 0x97, 0x6f, 0xbf, 0x58, 0x83, 0xf3, 0xd2, 0x3e,
0x91, 0x9c, 0xc8, 0x53, 0x24, 0x57, 0xe7, 0x12, 0x84, 0x4a, 0xce, 0x7d, 0x32, 0xd3, 0xd5, 0xea,
0x0b, 0xa8, 0xc8, 0xf5, 0x28, 0x6c, 0xe2, 0x71, 0x1c, 0xf5, 0xda, 0x06, 0xe7, 0x91, 0x21, 0x7a,
0x9f, 0x57, 0x0e, 0x8b, 0xe8, 0xf2, 0x67, 0xa7, 0x0d, 0xa6, 0x31, 0x26, 0xe5, 0x40, 0xbf, 0xb2,
0xc8, 0x91, 0xb5, 0x40, 0x2a, 0x3a, 0x9d, 0x6d, 0x81, 0x69, 0xc3, 0x2b, 0xac, 0x1d, 0x14, 0x0b,
0x0d, 0xe2, 0x9c, 0xfb, 0xf4, 0xaf, 0x7f, 0xbe, 0x19, 0x3a, 0x45, 0xa7, 0xf0, 0xa2, 0x6f, 0x2c,
0xb5, 0x6f, 0xd5, 0x00, 0xe4, 0x17, 0x43, 0x16, 0xfd, 0xd2, 0x22, 0xc3, 0xb7, 0xa0, 0x27, 0x9b,
0x03, 0xd3, 0xc4, 0x39, 0x8f, 0x4c, 0xce, 0xd2, 0x33, 0xdd, 0x98, 0x14, 0x9f, 0xea, 0xd9, 0x33,
0xfa, 0x9d, 0x45, 0x46, 0x6f, 0x81, 0x7a, 0x20, 0x02, 0x05, 0x2f, 0x9f, 0xd2, 0x45, 0xa4, 0x74,
0x9e, 0xfe, 0xbf, 0x45, 0xe9, 0xb1, 0xc6, 0xbd, 0xdc, 0x8d, 0xd8, 0xb7, 0x16, 0xc9, 0x6b, 0x41,
0xbd, 0x8c, 0xef, 0x70, 0x76, 0x70, 0xa6, 0xdf, 0x0e, 0xd2, 0x1f, 0x2d, 0x32, 0xad, 0x97, 0xa1,
0x62, 0x87, 0x4f, 0xce, 0x41, 0x72, 0x33, 0xb4, 0xd0, 0x5b, 0x41, 0xfa, 0x11, 0x19, 0x35, 0xca,
0x6d, 0xf6, 0x24, 0x95, 0xef, 0x34, 0x6f, 0x4a, 0x67, 0x01, 0x03, 0x3b, 0x74, 0xae, 0x4f, 0xb5,
0x14, 0x85, 0x0e, 0x59, 0x21, 0xe3, 0x3a, 0xfc, 0xfa, 0x4a, 0xf9, 0x1e, 0xab, 0xee, 0x03, 0xe1,
0x12, 0x22, 0xcc, 0xd3, 0x0b, 0xfd, 0x10, 0x62, 0x3f, 0xb8, 0xac, 0x74, 0xd8, 0x6d, 0x93, 0x84,
0x7e, 0xd2, 0xd0, 0xff, 0xed, 0x84, 0x48, 0x5f, 0xa4, 0x85, 0x99, 0x6e, 0xae, 0xb4, 0x5b, 0xee,
0x29, 0x29, 0xa6, 0x21, 0xbe, 0xb6, 0xc8, 0xc4, 0x2d, 0x50, 0xed, 0xb7, 0x23, 0x3d, 0xd7, 0x25,
0x72, 0xf6, 0x5d, 0x59, 0x70, 0x7a, 0x2f, 0x48, 0x09, 0xbc, 0x81, 0x04, 0x5e, 0x73, 0xae, 0x74,
0x27, 0x60, 0x5e, 0x78, 0x18, 0xe7, 0xbe, 0xb7, 0x86, 0x54, 0x2a, 0x26, 0xc2, 0x75, 0x6b, 0x91,
0x36, 0x90, 0xd2, 0x6d, 0x08, 0xb7, 0x57, 0x6a, 0x4c, 0xa8, 0x9e, 0x52, 0xcf, 0x66, 0xcd, 0xed,
0xe5, 0x29, 0x09, 0x17, 0x49, 0x2c, 0xd0, 0xf9, 0x7e, 0x2a, 0xd4, 0x20, 0xdc, 0xf6, 0x0d, 0xcc,
0xf7, 0x16, 0xc9, 0x99, 0xfb, 0x85, 0x9e, 0xdd, 0x89, 0xd8, 0x71, 0xef, 0x1c, 0x60, 0x67, 0x78,
0xc5, 0xd4, 0xb5, 0xd3, 0xf5, 0xd0, 0x5d, 0xc7, 0xf6, 0xae, 0x9b, 0xe7, 0x0f, 0x16, 0xc9, 0xb7,
0x28, 0xb4, 0xbe, 0x3d, 0x3c, 0x92, 0xce, 0x60, 0x92, 0xf4, 0x17, 0x8b, 0x4c, 0x1b, 0xfc, 0xce,
0x0e, 0x71, 0x88, 0x34, 0x93, 0xaa, 0x77, 0xfa, 0xf4, 0x88, 0x84, 0xec, 0x4f, 0x16, 0xc9, 0x99,
0x0b, 0x7a, 0x37, 0xbb, 0x8e, 0x8b, 0xfb, 0x00, 0xd9, 0x2d, 0x99, 0x6a, 0x2c, 0xf4, 0x39, 0x93,
0x48, 0xe5, 0x59, 0x7b, 0xd7, 0x7f, 0xb5, 0x48, 0xbe, 0x45, 0xa7, 0xb7, 0x9c, 0x2f, 0x8b, 0xb0,
0xbb, 0x3f, 0xc2, 0xf4, 0x77, 0x8b, 0x4c, 0x1b, 0x2e, 0x03, 0x2b, 0xe0, 0x65, 0x51, 0x7e, 0x15,
0x29, 0xbb, 0x85, 0xf9, 0x41, 0xf7, 0x6c, 0x07, 0x71, 0x46, 0x72, 0xab, 0x10, 0x42, 0xef, 0x87,
0x80, 0xbd, 0xd3, 0x9c, 0xb6, 0x98, 0x79, 0xf3, 0xd6, 0x58, 0xec, 0xf7, 0xd6, 0xd0, 0x3b, 0x59,
0x23, 0x79, 0x03, 0x91, 0x51, 0x65, 0xdf, 0x60, 0xe7, 0xf7, 0x00, 0x46, 0x25, 0x99, 0x36, 0x48,
0x3b, 0x37, 0x61, 0xdf, 0x70, 0xc9, 0xa3, 0x65, 0x71, 0x0f, 0x8f, 0x96, 0xa7, 0xe4, 0xf8, 0xfb,
0x2c, 0x0c, 0xf4, 0xa6, 0x9a, 0x9f, 0xb9, 0xf4, 0xcc, 0xae, 0x4b, 0xa2, 0xfd, 0xf3, 0xb7, 0x0f,
0xe6, 0x32, 0x62, 0x5e, 0x72, 0xfa, 0xde, 0x95, 0x8d, 0x04, 0x2a, 0xd9, 0xbe, 0xcf, 0x2d, 0x32,
0xd9, 0x42, 0xc7, 0xa4, 0x5f, 0x8c, 0xc2, 0x35, 0xa4, 0xb0, 0xec, 0x2c, 0x0e, 0x4c, 0x7b, 0x07,
0x91, 0x1b, 0x37, 0xff, 0x78, 0x3e, 0x6b, 0xfd, 0xf9, 0x7c, 0xd6, 0xfa, 0xfb, 0xf9, 0xac, 0xf5,
0xc1, 0xeb, 0x7b, 0xfb, 0x67, 0xcb, 0xc7, 0x1f, 0xcc, 0x99, 0xff, 0xa0, 0x36, 0x72, 0xf8, 0x27,
0xd4, 0xd5, 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0xbc, 0x0b, 0xd2, 0x3f, 0x69, 0x13, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
@ -859,6 +870,7 @@ type RepositoryServiceClient interface {
// ListWriteRepositories gets a list of all configured write repositories
ListWriteRepositories(ctx context.Context, in *RepoQuery, opts ...grpc.CallOption) (*v1alpha1.RepositoryList, error)
ListRefs(ctx context.Context, in *RepoQuery, opts ...grpc.CallOption) (*apiclient.Refs, error)
ListOCITags(ctx context.Context, in *RepoQuery, opts ...grpc.CallOption) (*apiclient.Refs, error)
// ListApps returns list of apps in the repo
ListApps(ctx context.Context, in *RepoAppsQuery, opts ...grpc.CallOption) (*RepoAppsResponse, error)
// GetAppDetails returns application details by given path
@ -952,6 +964,15 @@ func (c *repositoryServiceClient) ListRefs(ctx context.Context, in *RepoQuery, o
return out, nil
}
func (c *repositoryServiceClient) ListOCITags(ctx context.Context, in *RepoQuery, opts ...grpc.CallOption) (*apiclient.Refs, error) {
out := new(apiclient.Refs)
err := c.cc.Invoke(ctx, "/repository.RepositoryService/ListOCITags", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *repositoryServiceClient) ListApps(ctx context.Context, in *RepoAppsQuery, opts ...grpc.CallOption) (*RepoAppsResponse, error) {
out := new(RepoAppsResponse)
err := c.cc.Invoke(ctx, "/repository.RepositoryService/ListApps", in, out, opts...)
@ -1094,6 +1115,7 @@ type RepositoryServiceServer interface {
// ListWriteRepositories gets a list of all configured write repositories
ListWriteRepositories(context.Context, *RepoQuery) (*v1alpha1.RepositoryList, error)
ListRefs(context.Context, *RepoQuery) (*apiclient.Refs, error)
ListOCITags(context.Context, *RepoQuery) (*apiclient.Refs, error)
// ListApps returns list of apps in the repo
ListApps(context.Context, *RepoAppsQuery) (*RepoAppsResponse, error)
// GetAppDetails returns application details by given path
@ -1146,6 +1168,9 @@ func (*UnimplementedRepositoryServiceServer) ListWriteRepositories(ctx context.C
func (*UnimplementedRepositoryServiceServer) ListRefs(ctx context.Context, req *RepoQuery) (*apiclient.Refs, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRefs not implemented")
}
func (*UnimplementedRepositoryServiceServer) ListOCITags(ctx context.Context, req *RepoQuery) (*apiclient.Refs, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListOCITags not implemented")
}
func (*UnimplementedRepositoryServiceServer) ListApps(ctx context.Context, req *RepoAppsQuery) (*RepoAppsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListApps not implemented")
}
@ -1301,6 +1326,24 @@ func _RepositoryService_ListRefs_Handler(srv interface{}, ctx context.Context, d
return interceptor(ctx, in, info, handler)
}
func _RepositoryService_ListOCITags_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RepoQuery)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RepositoryServiceServer).ListOCITags(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/repository.RepositoryService/ListOCITags",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RepositoryServiceServer).ListOCITags(ctx, req.(*RepoQuery))
}
return interceptor(ctx, in, info, handler)
}
func _RepositoryService_ListApps_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RepoAppsQuery)
if err := dec(in); err != nil {
@ -1581,6 +1624,10 @@ var _RepositoryService_serviceDesc = grpc.ServiceDesc{
MethodName: "ListRefs",
Handler: _RepositoryService_ListRefs_Handler,
},
{
MethodName: "ListOCITags",
Handler: _RepositoryService_ListOCITags_Handler,
},
{
MethodName: "ListApps",
Handler: _RepositoryService_ListApps_Handler,
@ -1917,6 +1964,18 @@ func (m *RepoAccessQuery) MarshalToSizedBuffer(dAtA []byte) (int, error) {
i -= len(m.XXX_unrecognized)
copy(dAtA[i:], m.XXX_unrecognized)
}
if m.InsecureOciForceHttp {
i--
if m.InsecureOciForceHttp {
dAtA[i] = 1
} else {
dAtA[i] = 0
}
i--
dAtA[i] = 0x1
i--
dAtA[i] = 0xb0
}
if len(m.BearerToken) > 0 {
i -= len(m.BearerToken)
copy(dAtA[i:], m.BearerToken)
@ -2415,6 +2474,9 @@ func (m *RepoAccessQuery) Size() (n int) {
if l > 0 {
n += 2 + l + sovRepository(uint64(l))
}
if m.InsecureOciForceHttp {
n += 3
}
if m.XXX_unrecognized != nil {
n += len(m.XXX_unrecognized)
}
@ -3775,6 +3837,26 @@ func (m *RepoAccessQuery) Unmarshal(dAtA []byte) error {
}
m.BearerToken = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 22:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field InsecureOciForceHttp", wireType)
}
var v int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowRepository
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
v |= int(b&0x7F) << shift
if b < 0x80 {
break
}
}
m.InsecureOciForceHttp = bool(v != 0)
default:
iNdEx = preIndex
skippy, err := skipRepository(dAtA[iNdEx:])

View file

@ -357,6 +357,78 @@ func local_request_RepositoryService_ListRefs_0(ctx context.Context, marshaler r
}
var (
filter_RepositoryService_ListOCITags_0 = &utilities.DoubleArray{Encoding: map[string]int{"repo": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}}
)
func request_RepositoryService_ListOCITags_0(ctx context.Context, marshaler runtime.Marshaler, client RepositoryServiceClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq RepoQuery
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["repo"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "repo")
}
protoReq.Repo, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "repo", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_RepositoryService_ListOCITags_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.ListOCITags(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_RepositoryService_ListOCITags_0(ctx context.Context, marshaler runtime.Marshaler, server RepositoryServiceServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq RepoQuery
var metadata runtime.ServerMetadata
var (
val string
ok bool
err error
_ = err
)
val, ok = pathParams["repo"]
if !ok {
return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "repo")
}
protoReq.Repo, err = runtime.String(val)
if err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "repo", err)
}
if err := req.ParseForm(); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_RepositoryService_ListOCITags_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.ListOCITags(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_RepositoryService_ListApps_0 = &utilities.DoubleArray{Encoding: map[string]int{"repo": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}}
)
@ -1473,6 +1545,29 @@ func RegisterRepositoryServiceHandlerServer(ctx context.Context, mux *runtime.Se
})
mux.Handle("GET", pattern_RepositoryService_ListOCITags_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
var stream runtime.ServerTransportStream
ctx = grpc.NewContextWithServerTransportStream(ctx, &stream)
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := local_request_RepositoryService_ListOCITags_0(rctx, inboundMarshaler, server, req, pathParams)
md.HeaderMD, md.TrailerMD = metadata.Join(md.HeaderMD, stream.Header()), metadata.Join(md.TrailerMD, stream.Trailer())
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_RepositoryService_ListOCITags_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_RepositoryService_ListApps_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@ -1956,6 +2051,26 @@ func RegisterRepositoryServiceHandlerClient(ctx context.Context, mux *runtime.Se
})
mux.Handle("GET", pattern_RepositoryService_ListOCITags_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
rctx, err := runtime.AnnotateContext(ctx, mux, req)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
resp, md, err := request_RepositoryService_ListOCITags_0(rctx, inboundMarshaler, client, req, pathParams)
ctx = runtime.NewServerMetadataContext(ctx, md)
if err != nil {
runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err)
return
}
forward_RepositoryService_ListOCITags_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_RepositoryService_ListApps_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
ctx, cancel := context.WithCancel(req.Context())
defer cancel()
@ -2252,6 +2367,8 @@ var (
pattern_RepositoryService_ListRefs_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v1", "repositories", "repo", "refs"}, "", runtime.AssumeColonVerbOpt(true)))
pattern_RepositoryService_ListOCITags_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v1", "repositories", "repo", "oci-tags"}, "", runtime.AssumeColonVerbOpt(true)))
pattern_RepositoryService_ListApps_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v1", "repositories", "repo", "apps"}, "", runtime.AssumeColonVerbOpt(true)))
pattern_RepositoryService_GetAppDetails_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 1, 0, 4, 1, 5, 3, 2, 4}, []string{"api", "v1", "repositories", "source.repoURL", "appdetails"}, "", runtime.AssumeColonVerbOpt(true)))
@ -2294,6 +2411,8 @@ var (
forward_RepositoryService_ListRefs_0 = runtime.ForwardResponseMessage
forward_RepositoryService_ListOCITags_0 = runtime.ForwardResponseMessage
forward_RepositoryService_ListApps_0 = runtime.ForwardResponseMessage
forward_RepositoryService_GetAppDetails_0 = runtime.ForwardResponseMessage

File diff suppressed because it is too large Load diff

View file

@ -1097,7 +1097,7 @@ message GnuPGPublicKey {
// Trust holds the level of trust assigned to this key
optional string trust = 4;
// SubType holds the key's sub type (e.g. rsa4096)
// SubType holds the key's subtype (e.g. rsa4096)
optional string subType = 5;
// KeyData holds the raw key data, in base64 encoded format
@ -1377,6 +1377,23 @@ message NestedMergeGenerator {
repeated string mergeKeys = 2;
}
// OCIMetadata contains metadata for a specific revision in an OCI repository
message OCIMetadata {
optional string createdAt = 1;
optional string authors = 2;
optional string imageUrl = 3;
optional string docsUrl = 4;
optional string sourceUrl = 5;
optional string version = 6;
optional string description = 7;
}
// Operation contains information about a requested or running operation
message Operation {
// Sync contains parameters for the operation
@ -1744,6 +1761,9 @@ message RepoCreds {
// BearerToken contains the bearer token used for Git BitBucket Data Center auth at the repo server
optional string bearerToken = 25;
// InsecureOCIForceHttp specifies whether the connection to the repository uses TLS at _all_. If true, no TLS. This flag is applicable for OCI repos only.
optional bool insecureOCIForceHttp = 26;
}
// RepositoryList is a collection of Repositories.
@ -1830,6 +1850,9 @@ message Repository {
// BearerToken contains the bearer token used for Git BitBucket Data Center auth at the repo server
optional string bearerToken = 25;
// InsecureOCIForceHttp specifies whether the connection to the repository uses TLS at _all_. If true, no TLS. This flag is applicable for OCI repos only.
optional bool insecureOCIForceHttp = 26;
}
// A RepositoryCertificate is either SSH known hosts entry or TLS certificate

View file

@ -5,6 +5,8 @@ import (
"net/url"
"strings"
"github.com/argoproj/argo-cd/v3/util/oci"
"github.com/argoproj/argo-cd/v3/common"
"github.com/argoproj/argo-cd/v3/util/cert"
"github.com/argoproj/argo-cd/v3/util/git"
@ -53,6 +55,8 @@ type RepoCreds struct {
UseAzureWorkloadIdentity bool `json:"useAzureWorkloadIdentity,omitempty" protobuf:"bytes,24,opt,name=useAzureWorkloadIdentity"`
// BearerToken contains the bearer token used for Git BitBucket Data Center auth at the repo server
BearerToken string `json:"bearerToken,omitempty" protobuf:"bytes,25,opt,name=bearerToken"`
// InsecureOCIForceHttp specifies whether the connection to the repository uses TLS at _all_. If true, no TLS. This flag is applicable for OCI repos only.
InsecureOCIForceHttp bool `json:"insecureOCIForceHttp,omitempty" protobuf:"bytes,26,opt,name=insecureOCIForceHttp"` //nolint:revive //FIXME(var-naming)
}
// Repository is a repository holding application configurations
@ -108,11 +112,13 @@ type Repository struct {
UseAzureWorkloadIdentity bool `json:"useAzureWorkloadIdentity,omitempty" protobuf:"bytes,24,opt,name=useAzureWorkloadIdentity"`
// BearerToken contains the bearer token used for Git BitBucket Data Center auth at the repo server
BearerToken string `json:"bearerToken,omitempty" protobuf:"bytes,25,opt,name=bearerToken"`
// InsecureOCIForceHttp specifies whether the connection to the repository uses TLS at _all_. If true, no TLS. This flag is applicable for OCI repos only.
InsecureOCIForceHttp bool `json:"insecureOCIForceHttp,omitempty" protobuf:"bytes,26,opt,name=insecureOCIForceHttp"` //nolint:revive //FIXME(var-naming)
}
// IsInsecure returns true if the repository has been configured to skip server verification
// IsInsecure returns true if the repository has been configured to skip server verification or set to HTTP only
func (repo *Repository) IsInsecure() bool {
return repo.InsecureIgnoreHostKey || repo.Insecure
return repo.InsecureIgnoreHostKey || repo.Insecure || repo.InsecureOCIForceHttp
}
// IsLFSEnabled returns true if LFS support is enabled on repository
@ -161,6 +167,7 @@ func (repo *Repository) CopyCredentialsFromRepo(source *Repository) {
if repo.GCPServiceAccountKey == "" {
repo.GCPServiceAccountKey = source.GCPServiceAccountKey
}
repo.InsecureOCIForceHttp = source.InsecureOCIForceHttp
repo.ForceHttpBasicAuth = source.ForceHttpBasicAuth
repo.UseAzureWorkloadIdentity = source.UseAzureWorkloadIdentity
}
@ -208,6 +215,12 @@ func (repo *Repository) CopyCredentialsFrom(source *RepoCreds) {
if repo.NoProxy == "" {
repo.NoProxy = source.NoProxy
}
if repo.Type == "" {
repo.Type = source.Type
}
repo.EnableOCI = source.EnableOCI
repo.InsecureOCIForceHttp = source.InsecureOCIForceHttp
repo.ForceHttpBasicAuth = source.ForceHttpBasicAuth
repo.UseAzureWorkloadIdentity = source.UseAzureWorkloadIdentity
}
@ -236,7 +249,7 @@ func (repo *Repository) GetGitCreds(store git.CredsStore) git.Creds {
return git.NopCreds{}
}
// GetHelmCreds returns the credentials from a repository configuration used to authenticate at a Helm repository
// GetHelmCreds returns the credentials from a repository configuration used to authenticate a Helm repository
func (repo *Repository) GetHelmCreds() helm.Creds {
if repo.UseAzureWorkloadIdentity {
return helm.NewAzureWorkloadIdentityCreds(
@ -259,6 +272,19 @@ func (repo *Repository) GetHelmCreds() helm.Creds {
}
}
// GetOCICreds returns the credentials from a repository configuration used to authenticate an OCI repository
func (repo *Repository) GetOCICreds() oci.Creds {
return oci.Creds{
Username: repo.Username,
Password: repo.Password,
CAPath: getCAPath(repo.Repo),
CertData: []byte(repo.TLSClientCertData),
KeyData: []byte(repo.TLSClientCertKey),
InsecureSkipVerify: repo.Insecure,
InsecureHTTPOnly: repo.InsecureOCIForceHttp,
}
}
func getCAPath(repoURL string) string {
// For git ssh protocol url without ssh://, url.Parse() will fail to parse.
// However, no warn log is output since ssh scheme url is a possible format.
@ -402,7 +428,7 @@ type GnuPGPublicKey struct {
Owner string `json:"owner,omitempty" protobuf:"bytes,3,opt,name=owner"`
// Trust holds the level of trust assigned to this key
Trust string `json:"trust,omitempty" protobuf:"bytes,4,opt,name=trust"`
// SubType holds the key's sub type (e.g. rsa4096)
// SubType holds the key's subtype (e.g. rsa4096)
SubType string `json:"subType,omitempty" protobuf:"bytes,5,opt,name=subType"`
// KeyData holds the raw key data, in base64 encoded format
KeyData string `json:"keyData,omitempty" protobuf:"bytes,6,opt,name=keyData"`

View file

@ -302,6 +302,10 @@ func (source *ApplicationSource) AllowsConcurrentProcessing() bool {
return true
}
func (source *ApplicationSource) IsOCI() bool {
return strings.HasPrefix(source.RepoURL, "oci://")
}
// IsRef returns true when the application source is of type Ref
func (source *ApplicationSource) IsRef() bool {
return source.Ref != ""
@ -1582,6 +1586,17 @@ type RevisionMetadata struct {
SignatureInfo string `json:"signatureInfo,omitempty" protobuf:"bytes,5,opt,name=signatureInfo"`
}
// OCIMetadata contains metadata for a specific revision in an OCI repository
type OCIMetadata struct {
CreatedAt string `json:"createdAt,omitempty" protobuf:"bytes,1,opt,name=createdAt"`
Authors string `json:"authors,omitempty" protobuf:"bytes,2,opt,name=authors"`
ImageURL string `json:"imageUrl,omitempty" protobuf:"bytes,3,opt,name=imageUrl"`
DocsURL string `json:"docsUrl,omitempty" protobuf:"bytes,4,opt,name=docsUrl"`
SourceURL string `json:"sourceUrl,omitempty" protobuf:"bytes,5,opt,name=sourceUrl"`
Version string `json:"version,omitempty" protobuf:"bytes,6,opt,name=version"`
Description string `json:"description,omitempty" protobuf:"bytes,7,opt,name=description"`
}
// ChartDetails contains helm chart metadata for a specific version
type ChartDetails struct {
Description string `json:"description,omitempty" protobuf:"bytes,1,opt,name=description"`

View file

@ -2776,6 +2776,22 @@ func (in *NestedMergeGenerator) DeepCopy() *NestedMergeGenerator {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *OCIMetadata) DeepCopyInto(out *OCIMetadata) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OCIMetadata.
func (in *OCIMetadata) DeepCopy() *OCIMetadata {
if in == nil {
return nil
}
out := new(OCIMetadata)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Operation) DeepCopyInto(out *Operation) {
*out = *in

View file

@ -478,6 +478,79 @@ func (_c *RepoServerServiceClient_GetHelmCharts_Call) RunAndReturn(run func(ctx
return _c
}
// GetOCIMetadata provides a mock function for the type RepoServerServiceClient
func (_mock *RepoServerServiceClient) GetOCIMetadata(ctx context.Context, in *apiclient.RepoServerRevisionChartDetailsRequest, opts ...grpc.CallOption) (*v1alpha1.OCIMetadata, error) {
// grpc.CallOption
_va := make([]interface{}, len(opts))
for _i := range opts {
_va[_i] = opts[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, in)
_ca = append(_ca, _va...)
ret := _mock.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for GetOCIMetadata")
}
var r0 *v1alpha1.OCIMetadata
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, *apiclient.RepoServerRevisionChartDetailsRequest, ...grpc.CallOption) (*v1alpha1.OCIMetadata, error)); ok {
return returnFunc(ctx, in, opts...)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, *apiclient.RepoServerRevisionChartDetailsRequest, ...grpc.CallOption) *v1alpha1.OCIMetadata); ok {
r0 = returnFunc(ctx, in, opts...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1alpha1.OCIMetadata)
}
}
if returnFunc, ok := ret.Get(1).(func(context.Context, *apiclient.RepoServerRevisionChartDetailsRequest, ...grpc.CallOption) error); ok {
r1 = returnFunc(ctx, in, opts...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// RepoServerServiceClient_GetOCIMetadata_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetOCIMetadata'
type RepoServerServiceClient_GetOCIMetadata_Call struct {
*mock.Call
}
// GetOCIMetadata is a helper method to define mock.On call
// - ctx
// - in
// - opts
func (_e *RepoServerServiceClient_Expecter) GetOCIMetadata(ctx interface{}, in interface{}, opts ...interface{}) *RepoServerServiceClient_GetOCIMetadata_Call {
return &RepoServerServiceClient_GetOCIMetadata_Call{Call: _e.mock.On("GetOCIMetadata",
append([]interface{}{ctx, in}, opts...)...)}
}
func (_c *RepoServerServiceClient_GetOCIMetadata_Call) Run(run func(ctx context.Context, in *apiclient.RepoServerRevisionChartDetailsRequest, opts ...grpc.CallOption)) *RepoServerServiceClient_GetOCIMetadata_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]grpc.CallOption, len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(grpc.CallOption)
}
}
run(args[0].(context.Context), args[1].(*apiclient.RepoServerRevisionChartDetailsRequest), variadicArgs...)
})
return _c
}
func (_c *RepoServerServiceClient_GetOCIMetadata_Call) Return(oCIMetadata *v1alpha1.OCIMetadata, err error) *RepoServerServiceClient_GetOCIMetadata_Call {
_c.Call.Return(oCIMetadata, err)
return _c
}
func (_c *RepoServerServiceClient_GetOCIMetadata_Call) RunAndReturn(run func(ctx context.Context, in *apiclient.RepoServerRevisionChartDetailsRequest, opts ...grpc.CallOption) (*v1alpha1.OCIMetadata, error)) *RepoServerServiceClient_GetOCIMetadata_Call {
_c.Call.Return(run)
return _c
}
// GetRevisionChartDetails provides a mock function for the type RepoServerServiceClient
func (_mock *RepoServerServiceClient) GetRevisionChartDetails(ctx context.Context, in *apiclient.RepoServerRevisionChartDetailsRequest, opts ...grpc.CallOption) (*v1alpha1.ChartDetails, error) {
// grpc.CallOption
@ -697,6 +770,79 @@ func (_c *RepoServerServiceClient_ListApps_Call) RunAndReturn(run func(ctx conte
return _c
}
// ListOCITags provides a mock function for the type RepoServerServiceClient
func (_mock *RepoServerServiceClient) ListOCITags(ctx context.Context, in *apiclient.ListRefsRequest, opts ...grpc.CallOption) (*apiclient.Refs, error) {
// grpc.CallOption
_va := make([]interface{}, len(opts))
for _i := range opts {
_va[_i] = opts[_i]
}
var _ca []interface{}
_ca = append(_ca, ctx, in)
_ca = append(_ca, _va...)
ret := _mock.Called(_ca...)
if len(ret) == 0 {
panic("no return value specified for ListOCITags")
}
var r0 *apiclient.Refs
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context, *apiclient.ListRefsRequest, ...grpc.CallOption) (*apiclient.Refs, error)); ok {
return returnFunc(ctx, in, opts...)
}
if returnFunc, ok := ret.Get(0).(func(context.Context, *apiclient.ListRefsRequest, ...grpc.CallOption) *apiclient.Refs); ok {
r0 = returnFunc(ctx, in, opts...)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*apiclient.Refs)
}
}
if returnFunc, ok := ret.Get(1).(func(context.Context, *apiclient.ListRefsRequest, ...grpc.CallOption) error); ok {
r1 = returnFunc(ctx, in, opts...)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// RepoServerServiceClient_ListOCITags_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListOCITags'
type RepoServerServiceClient_ListOCITags_Call struct {
*mock.Call
}
// ListOCITags is a helper method to define mock.On call
// - ctx
// - in
// - opts
func (_e *RepoServerServiceClient_Expecter) ListOCITags(ctx interface{}, in interface{}, opts ...interface{}) *RepoServerServiceClient_ListOCITags_Call {
return &RepoServerServiceClient_ListOCITags_Call{Call: _e.mock.On("ListOCITags",
append([]interface{}{ctx, in}, opts...)...)}
}
func (_c *RepoServerServiceClient_ListOCITags_Call) Run(run func(ctx context.Context, in *apiclient.ListRefsRequest, opts ...grpc.CallOption)) *RepoServerServiceClient_ListOCITags_Call {
_c.Call.Run(func(args mock.Arguments) {
variadicArgs := make([]grpc.CallOption, len(args)-2)
for i, a := range args[2:] {
if a != nil {
variadicArgs[i] = a.(grpc.CallOption)
}
}
run(args[0].(context.Context), args[1].(*apiclient.ListRefsRequest), variadicArgs...)
})
return _c
}
func (_c *RepoServerServiceClient_ListOCITags_Call) Return(refs *apiclient.Refs, err error) *RepoServerServiceClient_ListOCITags_Call {
_c.Call.Return(refs, err)
return _c
}
func (_c *RepoServerServiceClient_ListOCITags_Call) RunAndReturn(run func(ctx context.Context, in *apiclient.ListRefsRequest, opts ...grpc.CallOption) (*apiclient.Refs, error)) *RepoServerServiceClient_ListOCITags_Call {
_c.Call.Return(run)
return _c
}
// ListPlugins provides a mock function for the type RepoServerServiceClient
func (_mock *RepoServerServiceClient) ListPlugins(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*apiclient.PluginList, error) {
// grpc.CallOption

View file

@ -2478,156 +2478,158 @@ func init() {
}
var fileDescriptor_dd8723cfcc820480 = []byte{
// 2377 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x1a, 0x4d, 0x73, 0x1c, 0x47,
0x55, 0xfb, 0x25, 0xed, 0x3e, 0x59, 0x5f, 0x6d, 0x5b, 0x1e, 0xaf, 0x6d, 0xa1, 0x0c, 0xd8, 0xe5,
0xd8, 0xc9, 0xaa, 0x6c, 0x57, 0x62, 0x70, 0x42, 0x28, 0x45, 0xb6, 0x25, 0xc7, 0x96, 0x2d, 0xc6,
0x4e, 0x28, 0x83, 0x81, 0xea, 0x9d, 0x6d, 0xed, 0x76, 0x34, 0x1f, 0xed, 0x99, 0x1e, 0x05, 0xb9,
0x8a, 0x0b, 0x50, 0x5c, 0xb8, 0x73, 0xe0, 0xca, 0x6f, 0xa0, 0x38, 0x72, 0xa0, 0x28, 0x38, 0x52,
0x5c, 0xb8, 0x50, 0x05, 0xe5, 0x5f, 0x42, 0xf5, 0xc7, 0x7c, 0xee, 0xec, 0x4a, 0x61, 0x65, 0x05,
0xb8, 0x48, 0xd3, 0xaf, 0x5f, 0xbf, 0xf7, 0xfa, 0x7d, 0xf5, 0x7b, 0xdd, 0x0b, 0x57, 0x02, 0xc2,
0xfc, 0x90, 0x04, 0xfb, 0x24, 0x58, 0x93, 0x9f, 0x94, 0xfb, 0xc1, 0x41, 0xe6, 0xb3, 0xc3, 0x02,
0x9f, 0xfb, 0x08, 0x52, 0x48, 0xfb, 0x51, 0x9f, 0xf2, 0x41, 0xd4, 0xed, 0xd8, 0xbe, 0xbb, 0x86,
0x83, 0xbe, 0xcf, 0x02, 0xff, 0x73, 0xf9, 0xf1, 0xae, 0xdd, 0x5b, 0xdb, 0xbf, 0xb5, 0xc6, 0xf6,
0xfa, 0x6b, 0x98, 0xd1, 0x70, 0x0d, 0x33, 0xe6, 0x50, 0x1b, 0x73, 0xea, 0x7b, 0x6b, 0xfb, 0x37,
0xb0, 0xc3, 0x06, 0xf8, 0xc6, 0x5a, 0x9f, 0x78, 0x24, 0xc0, 0x9c, 0xf4, 0x14, 0xe5, 0xf6, 0x85,
0xbe, 0xef, 0xf7, 0x1d, 0xb2, 0x26, 0x47, 0xdd, 0x68, 0x77, 0x8d, 0xb8, 0x8c, 0x6b, 0xb6, 0xe6,
0x3f, 0xe6, 0x60, 0x61, 0x1b, 0x7b, 0x74, 0x97, 0x84, 0xdc, 0x22, 0x2f, 0x23, 0x12, 0x72, 0xf4,
0x02, 0xea, 0x42, 0x18, 0xa3, 0xb2, 0x5a, 0xb9, 0x3a, 0x7b, 0x73, 0xab, 0x93, 0x4a, 0xd3, 0x89,
0xa5, 0x91, 0x1f, 0x3f, 0xb6, 0x7b, 0x9d, 0xfd, 0x5b, 0x1d, 0xb6, 0xd7, 0xef, 0x08, 0x69, 0x3a,
0x19, 0x69, 0x3a, 0xb1, 0x34, 0x1d, 0x2b, 0xd9, 0x96, 0x25, 0xa9, 0xa2, 0x36, 0x34, 0x03, 0xb2,
0x4f, 0x43, 0xea, 0x7b, 0x46, 0x75, 0xb5, 0x72, 0xb5, 0x65, 0x25, 0x63, 0x64, 0xc0, 0x8c, 0xe7,
0x6f, 0x60, 0x7b, 0x40, 0x8c, 0xda, 0x6a, 0xe5, 0x6a, 0xd3, 0x8a, 0x87, 0x68, 0x15, 0x66, 0x31,
0x63, 0x8f, 0x70, 0x97, 0x38, 0x0f, 0xc9, 0x81, 0x51, 0x97, 0x0b, 0xb3, 0x20, 0xb1, 0x16, 0x33,
0xf6, 0x18, 0xbb, 0xc4, 0x68, 0xc8, 0xd9, 0x78, 0x88, 0x2e, 0x42, 0xcb, 0xc3, 0x2e, 0x09, 0x19,
0xb6, 0x89, 0xd1, 0x94, 0x73, 0x29, 0x00, 0xfd, 0x14, 0x96, 0x32, 0x82, 0x3f, 0xf5, 0xa3, 0xc0,
0x26, 0x06, 0xc8, 0xad, 0x3f, 0x99, 0x6c, 0xeb, 0xeb, 0x45, 0xb2, 0xd6, 0x30, 0x27, 0xf4, 0x23,
0x68, 0x48, 0xcb, 0x1b, 0xb3, 0xab, 0xb5, 0x63, 0xd5, 0xb6, 0x22, 0x8b, 0x3c, 0x98, 0x61, 0x4e,
0xd4, 0xa7, 0x5e, 0x68, 0x9c, 0x92, 0x1c, 0x9e, 0x4d, 0xc6, 0x61, 0xc3, 0xf7, 0x76, 0x69, 0x7f,
0x1b, 0x7b, 0xb8, 0x4f, 0x5c, 0xe2, 0xf1, 0x1d, 0x49, 0xdc, 0x8a, 0x99, 0xa0, 0x57, 0xb0, 0xb8,
0x17, 0x85, 0xdc, 0x77, 0xe9, 0x2b, 0xf2, 0x84, 0x89, 0xb5, 0xa1, 0x31, 0x27, 0xb5, 0xf9, 0x78,
0x32, 0xc6, 0x0f, 0x0b, 0x54, 0xad, 0x21, 0x3e, 0xc2, 0x49, 0xf6, 0xa2, 0x2e, 0xf9, 0x8c, 0x04,
0xd2, 0xbb, 0xe6, 0x95, 0x93, 0x64, 0x40, 0xca, 0x8d, 0xa8, 0x1e, 0x85, 0xc6, 0xc2, 0x6a, 0x4d,
0xb9, 0x51, 0x02, 0x42, 0x57, 0x61, 0x61, 0x9f, 0x04, 0x74, 0xf7, 0xe0, 0x29, 0xed, 0x7b, 0x98,
0x47, 0x01, 0x31, 0x16, 0xa5, 0x2b, 0x16, 0xc1, 0xc8, 0x85, 0xb9, 0x01, 0x71, 0x5c, 0xa1, 0xf2,
0x8d, 0x80, 0xf4, 0x42, 0x63, 0x49, 0xea, 0x77, 0x73, 0x72, 0x0b, 0x4a, 0x72, 0x56, 0x9e, 0xba,
0x10, 0xcc, 0xf3, 0x2d, 0x1d, 0x29, 0x2a, 0x46, 0x90, 0x12, 0xac, 0x00, 0x46, 0x57, 0x60, 0x9e,
0x07, 0xd8, 0xde, 0xa3, 0x5e, 0x7f, 0x9b, 0xf0, 0x81, 0xdf, 0x33, 0x4e, 0x4b, 0x4d, 0x14, 0xa0,
0xc8, 0x06, 0x44, 0x3c, 0xdc, 0x75, 0x48, 0x4f, 0xf9, 0xe2, 0xb3, 0x03, 0x46, 0x42, 0xe3, 0x8c,
0xdc, 0xc5, 0xad, 0x4e, 0x26, 0x43, 0x15, 0x12, 0x44, 0xe7, 0xde, 0xd0, 0xaa, 0x7b, 0x1e, 0x0f,
0x0e, 0xac, 0x12, 0x72, 0x68, 0x0f, 0x66, 0xc5, 0x3e, 0x62, 0x57, 0x38, 0x2b, 0x5d, 0xe1, 0xc1,
0x64, 0x3a, 0xda, 0x4a, 0x09, 0x5a, 0x59, 0xea, 0xa8, 0x03, 0x68, 0x80, 0xc3, 0xed, 0xc8, 0xe1,
0x94, 0x39, 0x44, 0x89, 0x11, 0x1a, 0xcb, 0x52, 0x4d, 0x25, 0x33, 0xe8, 0x21, 0x40, 0x40, 0x76,
0x63, 0xbc, 0x73, 0x72, 0xe7, 0xd7, 0xc7, 0xed, 0xdc, 0x4a, 0xb0, 0xd5, 0x8e, 0x33, 0xcb, 0x05,
0x73, 0xb1, 0x0d, 0x62, 0x73, 0x1d, 0xed, 0x32, 0xac, 0x0d, 0xe9, 0x62, 0x25, 0x33, 0xc2, 0x17,
0x35, 0x54, 0x26, 0xad, 0xf3, 0xca, 0x5b, 0x33, 0x20, 0xb4, 0x05, 0x5f, 0xc3, 0x9e, 0xe7, 0x73,
0xb9, 0xfd, 0x58, 0x94, 0x4d, 0x9d, 0xde, 0x77, 0x30, 0x1f, 0x84, 0x46, 0x5b, 0xae, 0x3a, 0x0c,
0x4d, 0xb8, 0x04, 0xf5, 0x42, 0x8e, 0x1d, 0x47, 0x22, 0x3d, 0xb8, 0x6b, 0x5c, 0x50, 0x2e, 0x91,
0x87, 0xb6, 0xef, 0xc1, 0xb9, 0x11, 0xc6, 0x45, 0x8b, 0x50, 0xdb, 0x23, 0x07, 0xf2, 0x50, 0x68,
0x59, 0xe2, 0x13, 0x9d, 0x81, 0xc6, 0x3e, 0x76, 0x22, 0x22, 0xd3, 0x78, 0xd3, 0x52, 0x83, 0x3b,
0xd5, 0x6f, 0x56, 0xda, 0xbf, 0xac, 0xc0, 0x42, 0x41, 0x55, 0x25, 0xeb, 0x7f, 0x98, 0x5d, 0x7f,
0x0c, 0x81, 0xb3, 0xfb, 0x0c, 0x07, 0x7d, 0xc2, 0x33, 0x82, 0x98, 0x7f, 0xab, 0x80, 0x51, 0xb0,
0xe1, 0xf7, 0x28, 0x1f, 0xdc, 0xa7, 0x0e, 0x09, 0xd1, 0x6d, 0x98, 0x09, 0x14, 0x4c, 0x1f, 0x75,
0x17, 0xc6, 0x98, 0x7e, 0x6b, 0xca, 0x8a, 0xb1, 0xd1, 0x47, 0xd0, 0x74, 0x09, 0xc7, 0x3d, 0xcc,
0xb1, 0x96, 0x7d, 0xb5, 0x6c, 0xa5, 0xe0, 0xb2, 0xad, 0xf1, 0xb6, 0xa6, 0xac, 0x64, 0x0d, 0x7a,
0x0f, 0x1a, 0xf6, 0x20, 0xf2, 0xf6, 0xe4, 0x21, 0x37, 0x7b, 0xf3, 0xd2, 0xa8, 0xc5, 0x1b, 0x02,
0x69, 0x6b, 0xca, 0x52, 0xd8, 0x1f, 0x4f, 0x43, 0x9d, 0xe1, 0x80, 0x9b, 0xf7, 0xe1, 0x4c, 0x19,
0x0b, 0x71, 0xb2, 0xda, 0x03, 0x62, 0xef, 0x85, 0x91, 0xab, 0xd5, 0x9c, 0x8c, 0x11, 0x82, 0x7a,
0x48, 0x5f, 0x29, 0x55, 0xd7, 0x2c, 0xf9, 0x6d, 0xbe, 0x0d, 0x4b, 0x43, 0xdc, 0x84, 0x51, 0x95,
0x6c, 0x82, 0xc2, 0x29, 0xcd, 0xda, 0x8c, 0xe0, 0xec, 0x33, 0xa9, 0x8b, 0xe4, 0x78, 0x39, 0x89,
0x5a, 0xc1, 0xdc, 0x82, 0xe5, 0x22, 0xdb, 0x90, 0xf9, 0x5e, 0x48, 0x44, 0xb0, 0xc9, 0x7c, 0x4c,
0x49, 0x2f, 0x9d, 0x95, 0x52, 0x34, 0xad, 0x92, 0x19, 0xf3, 0xb7, 0x55, 0x58, 0xb6, 0x48, 0xe8,
0x3b, 0xfb, 0x24, 0x4e, 0x96, 0x27, 0x53, 0xee, 0xfc, 0x00, 0x6a, 0x98, 0x31, 0xed, 0x26, 0x0f,
0x8e, 0xad, 0xa0, 0xb0, 0x04, 0x55, 0xf4, 0x0e, 0x2c, 0x61, 0xb7, 0x4b, 0xfb, 0x91, 0x1f, 0x85,
0xf1, 0xb6, 0xa4, 0x53, 0xb5, 0xac, 0xe1, 0x09, 0x91, 0x70, 0x42, 0x19, 0x91, 0x0f, 0xbc, 0x1e,
0xf9, 0x89, 0xac, 0xa1, 0x6a, 0x56, 0x16, 0x64, 0xda, 0x70, 0x6e, 0x48, 0x49, 0x5a, 0xe1, 0xd9,
0xb2, 0xad, 0x52, 0x28, 0xdb, 0x4a, 0xc5, 0xa8, 0x8e, 0x10, 0xc3, 0x7c, 0x5d, 0x81, 0xc5, 0x34,
0xb8, 0x34, 0xf9, 0x8b, 0xd0, 0x72, 0x35, 0x2c, 0x34, 0x2a, 0x32, 0x67, 0xa6, 0x80, 0x7c, 0x05,
0x57, 0x2d, 0x56, 0x70, 0xcb, 0x30, 0xad, 0x0a, 0x6c, 0xbd, 0x75, 0x3d, 0xca, 0x89, 0x5c, 0x2f,
0x88, 0xbc, 0x02, 0x10, 0x26, 0x19, 0xce, 0x98, 0x96, 0xb3, 0x19, 0x08, 0x32, 0xe1, 0x94, 0x3a,
0xef, 0x2d, 0x12, 0x46, 0x0e, 0x37, 0x66, 0x24, 0x46, 0x0e, 0x26, 0xe3, 0xcd, 0x77, 0x5d, 0xec,
0xf5, 0x42, 0xa3, 0x29, 0x45, 0x4e, 0xc6, 0xa6, 0x0f, 0x0b, 0x8f, 0xa8, 0xd8, 0xdf, 0x6e, 0x78,
0x32, 0xa1, 0xf2, 0x3e, 0xd4, 0x05, 0x33, 0x21, 0x54, 0x37, 0xc0, 0x9e, 0x3d, 0x20, 0xb1, 0x1e,
0x93, 0xb1, 0x48, 0x02, 0x1c, 0xf7, 0x43, 0xa3, 0x2a, 0xe1, 0xf2, 0xdb, 0xfc, 0x7d, 0x55, 0x49,
0xba, 0xce, 0x58, 0xf8, 0xd5, 0x37, 0x00, 0xe5, 0x25, 0x49, 0x6d, 0xb8, 0x24, 0x29, 0x88, 0xfc,
0x65, 0x4a, 0x92, 0x63, 0x3a, 0xe4, 0xcc, 0x08, 0x66, 0xd6, 0x19, 0x13, 0x82, 0xa0, 0x1b, 0x50,
0xc7, 0x8c, 0x29, 0x85, 0x17, 0xf2, 0xb9, 0x46, 0x11, 0xff, 0xb5, 0x48, 0x12, 0xb5, 0x7d, 0x1b,
0x5a, 0x09, 0xe8, 0x30, 0xb6, 0xad, 0x2c, 0xdb, 0x55, 0x00, 0x55, 0x73, 0x3f, 0xf0, 0x76, 0x7d,
0x61, 0x52, 0x11, 0x08, 0x7a, 0xa9, 0xfc, 0x36, 0xef, 0xc4, 0x18, 0x52, 0xb6, 0x77, 0xa0, 0x41,
0x39, 0x71, 0x63, 0xe1, 0x96, 0xb3, 0xc2, 0xa5, 0x84, 0x2c, 0x85, 0x64, 0xfe, 0xb9, 0x09, 0xe7,
0x85, 0xc5, 0x9e, 0xca, 0x10, 0x5a, 0x67, 0xec, 0x2e, 0xe1, 0x98, 0x3a, 0xe1, 0x77, 0x23, 0x12,
0x1c, 0xbc, 0x61, 0xc7, 0xe8, 0xc3, 0xb4, 0x8a, 0x40, 0x9d, 0x2d, 0x8f, 0xbd, 0xfd, 0xd2, 0xe4,
0xd3, 0x9e, 0xab, 0xf6, 0x66, 0x7a, 0xae, 0xb2, 0x1e, 0xa8, 0x7e, 0x42, 0x3d, 0xd0, 0xe8, 0x36,
0x38, 0xd3, 0x5c, 0x4f, 0xe7, 0x9b, 0xeb, 0x92, 0xd6, 0x62, 0xe6, 0xa8, 0xad, 0x45, 0xb3, 0xb4,
0xb5, 0x70, 0x4b, 0xe3, 0xb8, 0x25, 0xd5, 0xfd, 0xed, 0xac, 0x07, 0x8e, 0xf4, 0xb5, 0x49, 0x9a,
0x0c, 0x78, 0xa3, 0x4d, 0xc6, 0xa7, 0xb9, 0xa6, 0x41, 0xb5, 0xed, 0xef, 0x1d, 0x6d, 0x4f, 0x63,
0xda, 0x87, 0xff, 0xbb, 0xd2, 0xfb, 0x17, 0xb2, 0xe2, 0x62, 0x7e, 0xaa, 0x83, 0xe4, 0xb0, 0x17,
0xe7, 0x90, 0x38, 0x76, 0x75, 0xd2, 0x12, 0xdf, 0xe8, 0x3a, 0xd4, 0x85, 0x92, 0x75, 0x49, 0x7c,
0x2e, 0xab, 0x4f, 0x61, 0x89, 0x75, 0xc6, 0x9e, 0x32, 0x62, 0x5b, 0x12, 0x09, 0xdd, 0x81, 0x56,
0xe2, 0xf8, 0x3a, 0xb2, 0x2e, 0x66, 0x57, 0x24, 0x71, 0x12, 0x2f, 0x4b, 0xd1, 0xc5, 0xda, 0x1e,
0x0d, 0x88, 0x2d, 0x0b, 0xc6, 0xc6, 0xf0, 0xda, 0xbb, 0xf1, 0x64, 0xb2, 0x36, 0x41, 0x47, 0x37,
0x60, 0x5a, 0xdd, 0x73, 0xc8, 0x08, 0x9a, 0xbd, 0x79, 0x7e, 0x38, 0x99, 0xc6, 0xab, 0x34, 0xa2,
0xf9, 0xa7, 0x0a, 0xbc, 0x95, 0x3a, 0x44, 0x1c, 0x4d, 0x71, 0xcd, 0xfe, 0xd5, 0x9f, 0xb8, 0x57,
0x60, 0x5e, 0x36, 0x09, 0xe9, 0x75, 0x87, 0xba, 0x79, 0x2b, 0x40, 0xcd, 0xdf, 0x55, 0xe0, 0xf2,
0xf0, 0x3e, 0x36, 0x06, 0x38, 0xe0, 0x89, 0x79, 0x4f, 0x62, 0x2f, 0xf1, 0x81, 0x57, 0x4d, 0x0f,
0xbc, 0xdc, 0xfe, 0x6a, 0xf9, 0xfd, 0x99, 0x7f, 0xa8, 0xc2, 0x6c, 0xc6, 0x81, 0xca, 0x0e, 0x4c,
0x51, 0x0c, 0x4a, 0xbf, 0x95, 0x6d, 0xa1, 0x3c, 0x14, 0x5a, 0x56, 0x06, 0x82, 0xf6, 0x00, 0x18,
0x0e, 0xb0, 0x4b, 0x38, 0x09, 0x44, 0x26, 0x17, 0x11, 0xff, 0x70, 0xf2, 0xec, 0xb2, 0x13, 0xd3,
0xb4, 0x32, 0xe4, 0x45, 0x35, 0x2b, 0x59, 0x87, 0x3a, 0x7f, 0xeb, 0x11, 0xfa, 0x02, 0xe6, 0x77,
0xa9, 0x43, 0x76, 0x52, 0x41, 0xa6, 0xa5, 0x20, 0x4f, 0x26, 0x17, 0xe4, 0x7e, 0x96, 0xae, 0x55,
0x60, 0x63, 0x5e, 0x83, 0xc5, 0x62, 0x3c, 0x09, 0x21, 0xa9, 0x8b, 0xfb, 0x89, 0xb6, 0xf4, 0xc8,
0x44, 0xb0, 0x58, 0x8c, 0x1f, 0xf3, 0x9f, 0x55, 0x38, 0x9b, 0x90, 0x5b, 0xf7, 0x3c, 0x3f, 0xf2,
0x6c, 0x79, 0x75, 0x58, 0x6a, 0x8b, 0x33, 0xd0, 0xe0, 0x94, 0x3b, 0x49, 0xe1, 0x23, 0x07, 0xe2,
0xec, 0xe2, 0xbe, 0xef, 0x70, 0xca, 0xb4, 0x81, 0xe3, 0xa1, 0xb2, 0xfd, 0xcb, 0x88, 0x06, 0xa4,
0x27, 0x33, 0x41, 0xd3, 0x4a, 0xc6, 0x62, 0x4e, 0x54, 0x35, 0xb2, 0xc4, 0x57, 0xca, 0x4c, 0xc6,
0xd2, 0xef, 0x7d, 0xc7, 0x21, 0xb6, 0x50, 0x47, 0xa6, 0x09, 0x28, 0x40, 0x65, 0x73, 0xc1, 0x03,
0xea, 0xf5, 0x75, 0x0b, 0xa0, 0x47, 0x42, 0x4e, 0x1c, 0x04, 0xf8, 0x40, 0x57, 0xfe, 0x6a, 0x80,
0x3e, 0x84, 0x9a, 0x8b, 0x99, 0x3e, 0xe8, 0xae, 0xe5, 0xb2, 0x43, 0x99, 0x06, 0x3a, 0xdb, 0x98,
0xa9, 0x93, 0x40, 0x2c, 0x6b, 0xbf, 0x0f, 0xcd, 0x18, 0xf0, 0xa5, 0x4a, 0xc2, 0xcf, 0x61, 0x2e,
0x97, 0x7c, 0xd0, 0x73, 0x58, 0x4e, 0x3d, 0x2a, 0xcb, 0x50, 0x17, 0x81, 0x6f, 0x1d, 0x2a, 0x99,
0x35, 0x82, 0x80, 0xf9, 0x12, 0x96, 0x84, 0xcb, 0xc8, 0xc0, 0x3f, 0xa1, 0xd6, 0xe6, 0x03, 0x68,
0x25, 0x2c, 0x4b, 0x7d, 0xa6, 0x0d, 0xcd, 0xfd, 0xf8, 0x4a, 0x57, 0xf5, 0x36, 0xc9, 0xd8, 0x5c,
0x07, 0x94, 0x95, 0x57, 0x9f, 0x40, 0xd7, 0xf3, 0x45, 0xf1, 0xd9, 0xe2, 0x71, 0x23, 0xd1, 0xe3,
0x9a, 0xf8, 0xef, 0x55, 0x58, 0xd8, 0xa4, 0xf2, 0x8e, 0xe4, 0x84, 0x92, 0xdc, 0x35, 0x58, 0x0c,
0xa3, 0xae, 0xeb, 0xf7, 0x22, 0x87, 0xe8, 0xa2, 0x40, 0x9f, 0xf4, 0x43, 0xf0, 0x71, 0xc9, 0x4f,
0x28, 0x8b, 0x61, 0x3e, 0xd0, 0xdd, 0xaf, 0xfc, 0x46, 0x1f, 0xc2, 0xf9, 0xc7, 0xe4, 0x0b, 0xbd,
0x9f, 0x4d, 0xc7, 0xef, 0x76, 0xa9, 0xd7, 0x8f, 0x99, 0x34, 0x24, 0x93, 0xd1, 0x08, 0x65, 0xa5,
0xe2, 0x74, 0x79, 0xa9, 0x98, 0x74, 0xd0, 0x1b, 0xbe, 0xeb, 0x52, 0xae, 0x2b, 0xca, 0x1c, 0xcc,
0xfc, 0x79, 0x05, 0x16, 0x53, 0xcd, 0x6a, 0xdb, 0xdc, 0x56, 0x31, 0xa4, 0x2c, 0x73, 0x39, 0x6b,
0x99, 0x22, 0xea, 0x7f, 0x1e, 0x3e, 0xa7, 0xb2, 0xe1, 0xf3, 0xab, 0x2a, 0x9c, 0xdd, 0xa4, 0x3c,
0x4e, 0x5c, 0xf4, 0x7f, 0xcd, 0xca, 0x25, 0x36, 0xa9, 0x1f, 0xcd, 0x26, 0x8d, 0x12, 0x9b, 0x74,
0x60, 0xb9, 0xa8, 0x0c, 0x6d, 0x98, 0x33, 0xd0, 0x60, 0xf2, 0xd2, 0x59, 0xdd, 0x2b, 0xa8, 0x81,
0xf9, 0xb3, 0x19, 0xb8, 0xf4, 0x29, 0xeb, 0x61, 0x9e, 0xdc, 0x19, 0xdd, 0xf7, 0x03, 0x79, 0xeb,
0x7c, 0x32, 0x5a, 0x2c, 0xbc, 0x0c, 0x56, 0xc7, 0xbe, 0x0c, 0xd6, 0xc6, 0xbc, 0x0c, 0xd6, 0x8f,
0xf4, 0x32, 0xd8, 0x38, 0xb1, 0x97, 0xc1, 0xe1, 0x5e, 0x6b, 0xba, 0xb4, 0xd7, 0x7a, 0x9e, 0xeb,
0x47, 0x66, 0x64, 0xd8, 0x7c, 0x2b, 0x1b, 0x36, 0x63, 0xad, 0x33, 0xf6, 0x49, 0xa3, 0xf0, 0xa0,
0xd6, 0x3c, 0xf4, 0x41, 0xad, 0x35, 0xfc, 0xa0, 0x56, 0xfe, 0x26, 0x03, 0x23, 0xdf, 0x64, 0xae,
0xc0, 0x7c, 0x78, 0xe0, 0xd9, 0xa4, 0x97, 0xdc, 0x24, 0xce, 0xaa, 0x6d, 0xe7, 0xa1, 0xb9, 0x88,
0x38, 0x55, 0x88, 0x88, 0xc4, 0x53, 0xe7, 0x32, 0x9e, 0x5a, 0x16, 0x27, 0xf3, 0x23, 0xdb, 0xdc,
0xc2, 0x73, 0xc9, 0x42, 0xe9, 0x73, 0xc9, 0x7f, 0x4d, 0xb3, 0xf5, 0x19, 0xac, 0x8c, 0xb2, 0xb2,
0x0e, 0x5e, 0x03, 0x66, 0xec, 0x01, 0xf6, 0xfa, 0xf2, 0x5a, 0x50, 0x76, 0xff, 0x7a, 0x38, 0xae,
0x3b, 0xb8, 0xf9, 0x47, 0x80, 0xa5, 0xb4, 0xea, 0x17, 0x7f, 0xa9, 0x4d, 0xd0, 0x13, 0x58, 0x8c,
0x9f, 0x97, 0xe2, 0x8b, 0x5c, 0x34, 0xee, 0xed, 0xa4, 0x7d, 0xb1, 0x7c, 0x52, 0x89, 0x66, 0x4e,
0x21, 0x1b, 0xce, 0x17, 0x09, 0xa6, 0xcf, 0x34, 0xdf, 0x18, 0x43, 0x39, 0xc1, 0x3a, 0x8c, 0xc5,
0xd5, 0x0a, 0x7a, 0x0e, 0xf3, 0xf9, 0xc7, 0x04, 0x94, 0x2b, 0x83, 0x4a, 0xdf, 0x37, 0xda, 0xe6,
0x38, 0x94, 0x44, 0xfe, 0x17, 0xc2, 0x0d, 0x72, 0xf7, 0xe6, 0xc8, 0xcc, 0xdf, 0x08, 0x94, 0xbd,
0x3c, 0xb4, 0xbf, 0x3e, 0x16, 0x27, 0xa1, 0xfe, 0x01, 0x34, 0xe3, 0xbb, 0xe4, 0xbc, 0x9a, 0x0b,
0x37, 0xcc, 0xed, 0xc5, 0x3c, 0xbd, 0xdd, 0xd0, 0x9c, 0x42, 0x1f, 0xa9, 0xc5, 0xeb, 0x8c, 0x95,
0x2c, 0xce, 0xdc, 0xa0, 0xb6, 0x4f, 0x97, 0xdc, 0x5a, 0x9a, 0x53, 0xe8, 0x3b, 0x30, 0x2b, 0xbe,
0x76, 0xf4, 0xf3, 0xfe, 0x72, 0x47, 0xfd, 0x9a, 0xa4, 0x13, 0xff, 0x9a, 0xa4, 0x73, 0xcf, 0x65,
0xfc, 0xa0, 0x5d, 0x72, 0xad, 0xa8, 0x09, 0xbc, 0x80, 0xb9, 0x4d, 0xc2, 0xd3, 0x5b, 0x00, 0x74,
0xf9, 0x48, 0x77, 0x25, 0x6d, 0xb3, 0x88, 0x36, 0x7c, 0x91, 0x60, 0x4e, 0xa1, 0x5f, 0x57, 0xe0,
0xf4, 0x26, 0xe1, 0xc5, 0xbe, 0x1a, 0xbd, 0x5b, 0xce, 0x64, 0x44, 0xff, 0xdd, 0x7e, 0x3c, 0x69,
0x4c, 0xe6, 0xc9, 0x9a, 0x53, 0xe8, 0x37, 0x15, 0x38, 0x97, 0x11, 0x2c, 0xdb, 0x28, 0xa3, 0x1b,
0xe3, 0x85, 0x2b, 0x69, 0xaa, 0xdb, 0x9f, 0x4c, 0xf8, 0xab, 0x8d, 0x0c, 0x49, 0x73, 0x0a, 0xed,
0x48, 0x9b, 0xa4, 0x75, 0x31, 0xba, 0x54, 0x5a, 0x00, 0x27, 0xdc, 0x57, 0x46, 0x4d, 0x27, 0x76,
0xf8, 0x04, 0x66, 0x37, 0x09, 0x8f, 0x0b, 0xb4, 0xbc, 0xa7, 0x15, 0x6a, 0xe7, 0x7c, 0xa8, 0x16,
0x6b, 0x3a, 0xe9, 0x31, 0x4b, 0x8a, 0x56, 0xa6, 0x08, 0xc9, 0xc7, 0x6a, 0x69, 0xb5, 0x96, 0xf7,
0x98, 0xf2, 0x1a, 0xc6, 0x9c, 0x42, 0x2f, 0x61, 0xb9, 0x3c, 0x55, 0xa2, 0xb7, 0x8f, 0x7c, 0x68,
0xb6, 0xaf, 0x1d, 0x05, 0x35, 0x66, 0xf9, 0xf1, 0xfa, 0x5f, 0x5e, 0xaf, 0x54, 0xfe, 0xfa, 0x7a,
0xa5, 0xf2, 0xaf, 0xd7, 0x2b, 0x95, 0xef, 0xdf, 0x3a, 0xe4, 0xd7, 0x5d, 0x99, 0x1f, 0x8c, 0x61,
0x46, 0x6d, 0x87, 0x12, 0x8f, 0x77, 0xa7, 0x65, 0xbc, 0xdd, 0xfa, 0x77, 0x00, 0x00, 0x00, 0xff,
0xff, 0x17, 0x3e, 0x16, 0x5a, 0x4f, 0x26, 0x00, 0x00,
// 2407 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x1a, 0xcb, 0x72, 0x1c, 0x49,
0x71, 0x9e, 0xd2, 0x4c, 0xea, 0x5d, 0xb6, 0xe5, 0xf6, 0xd8, 0x16, 0xda, 0x06, 0x3b, 0xbc, 0xf6,
0xee, 0x28, 0x6c, 0xc7, 0xae, 0xc1, 0xbb, 0x2c, 0xa1, 0x95, 0x6d, 0x49, 0x6b, 0xcb, 0x16, 0x6d,
0xef, 0x12, 0x06, 0x03, 0x51, 0xd3, 0x53, 0xea, 0xe9, 0x55, 0x3f, 0xca, 0xdd, 0xd5, 0x5a, 0xe4,
0x08, 0x2e, 0x40, 0x70, 0xe1, 0xc2, 0x69, 0x0f, 0x5c, 0xf9, 0x06, 0x82, 0x23, 0x27, 0x02, 0x8e,
0x04, 0x17, 0x2e, 0x44, 0x40, 0xf8, 0x4b, 0x88, 0x7a, 0xf4, 0x73, 0x7a, 0x46, 0x5a, 0x8f, 0xad,
0x05, 0x2e, 0x52, 0x57, 0x56, 0x56, 0x66, 0x56, 0x56, 0x66, 0x56, 0x66, 0xd6, 0xc0, 0xe5, 0x80,
0x50, 0x3f, 0x24, 0xc1, 0x01, 0x09, 0xd6, 0xc4, 0xa7, 0xcd, 0xfc, 0xe0, 0x30, 0xf3, 0xd9, 0xa5,
0x81, 0xcf, 0x7c, 0x04, 0x29, 0xa4, 0xf3, 0xc0, 0xb2, 0xd9, 0x20, 0xea, 0x75, 0x4d, 0xdf, 0x5d,
0xc3, 0x81, 0xe5, 0xd3, 0xc0, 0xff, 0x5c, 0x7c, 0xbc, 0x6b, 0xf6, 0xd7, 0x0e, 0x6e, 0xae, 0xd1,
0x7d, 0x6b, 0x0d, 0x53, 0x3b, 0x5c, 0xc3, 0x94, 0x3a, 0xb6, 0x89, 0x99, 0xed, 0x7b, 0x6b, 0x07,
0xd7, 0xb1, 0x43, 0x07, 0xf8, 0xfa, 0x9a, 0x45, 0x3c, 0x12, 0x60, 0x46, 0xfa, 0x92, 0x72, 0xe7,
0xbc, 0xe5, 0xfb, 0x96, 0x43, 0xd6, 0xc4, 0xa8, 0x17, 0xed, 0xad, 0x11, 0x97, 0x32, 0xc5, 0x56,
0xff, 0xe7, 0x1c, 0x2c, 0xec, 0x60, 0xcf, 0xde, 0x23, 0x21, 0x33, 0xc8, 0xf3, 0x88, 0x84, 0x0c,
0x3d, 0x83, 0x06, 0x17, 0x46, 0xab, 0xae, 0x56, 0xaf, 0xcc, 0xdc, 0xd8, 0xea, 0xa6, 0xd2, 0x74,
0x63, 0x69, 0xc4, 0xc7, 0x4f, 0xcd, 0x7e, 0xf7, 0xe0, 0x66, 0x97, 0xee, 0x5b, 0x5d, 0x2e, 0x4d,
0x37, 0x23, 0x4d, 0x37, 0x96, 0xa6, 0x6b, 0x24, 0xdb, 0x32, 0x04, 0x55, 0xd4, 0x81, 0x56, 0x40,
0x0e, 0xec, 0xd0, 0xf6, 0x3d, 0xad, 0xb6, 0x5a, 0xbd, 0xd2, 0x36, 0x92, 0x31, 0xd2, 0x60, 0xda,
0xf3, 0x37, 0xb0, 0x39, 0x20, 0x5a, 0x7d, 0xb5, 0x7a, 0xa5, 0x65, 0xc4, 0x43, 0xb4, 0x0a, 0x33,
0x98, 0xd2, 0x07, 0xb8, 0x47, 0x9c, 0xfb, 0xe4, 0x50, 0x6b, 0x88, 0x85, 0x59, 0x10, 0x5f, 0x8b,
0x29, 0x7d, 0x88, 0x5d, 0xa2, 0x35, 0xc5, 0x6c, 0x3c, 0x44, 0x17, 0xa0, 0xed, 0x61, 0x97, 0x84,
0x14, 0x9b, 0x44, 0x6b, 0x89, 0xb9, 0x14, 0x80, 0x7e, 0x0e, 0x4b, 0x19, 0xc1, 0x1f, 0xfb, 0x51,
0x60, 0x12, 0x0d, 0xc4, 0xd6, 0x1f, 0x4d, 0xb6, 0xf5, 0xf5, 0x22, 0x59, 0x63, 0x98, 0x13, 0xfa,
0x09, 0x34, 0xc5, 0xc9, 0x6b, 0x33, 0xab, 0xf5, 0xd7, 0xaa, 0x6d, 0x49, 0x16, 0x79, 0x30, 0x4d,
0x9d, 0xc8, 0xb2, 0xbd, 0x50, 0x9b, 0x15, 0x1c, 0x9e, 0x4c, 0xc6, 0x61, 0xc3, 0xf7, 0xf6, 0x6c,
0x6b, 0x07, 0x7b, 0xd8, 0x22, 0x2e, 0xf1, 0xd8, 0xae, 0x20, 0x6e, 0xc4, 0x4c, 0xd0, 0x0b, 0x58,
0xdc, 0x8f, 0x42, 0xe6, 0xbb, 0xf6, 0x0b, 0xf2, 0x88, 0xf2, 0xb5, 0xa1, 0x36, 0x27, 0xb4, 0xf9,
0x70, 0x32, 0xc6, 0xf7, 0x0b, 0x54, 0x8d, 0x21, 0x3e, 0xdc, 0x48, 0xf6, 0xa3, 0x1e, 0xf9, 0x8c,
0x04, 0xc2, 0xba, 0xe6, 0xa5, 0x91, 0x64, 0x40, 0xd2, 0x8c, 0x6c, 0x35, 0x0a, 0xb5, 0x85, 0xd5,
0xba, 0x34, 0xa3, 0x04, 0x84, 0xae, 0xc0, 0xc2, 0x01, 0x09, 0xec, 0xbd, 0xc3, 0xc7, 0xb6, 0xe5,
0x61, 0x16, 0x05, 0x44, 0x5b, 0x14, 0xa6, 0x58, 0x04, 0x23, 0x17, 0xe6, 0x06, 0xc4, 0x71, 0xb9,
0xca, 0x37, 0x02, 0xd2, 0x0f, 0xb5, 0x25, 0xa1, 0xdf, 0xcd, 0xc9, 0x4f, 0x50, 0x90, 0x33, 0xf2,
0xd4, 0xb9, 0x60, 0x9e, 0x6f, 0x28, 0x4f, 0x91, 0x3e, 0x82, 0xa4, 0x60, 0x05, 0x30, 0xba, 0x0c,
0xf3, 0x2c, 0xc0, 0xe6, 0xbe, 0xed, 0x59, 0x3b, 0x84, 0x0d, 0xfc, 0xbe, 0x76, 0x4a, 0x68, 0xa2,
0x00, 0x45, 0x26, 0x20, 0xe2, 0xe1, 0x9e, 0x43, 0xfa, 0xd2, 0x16, 0x9f, 0x1c, 0x52, 0x12, 0x6a,
0xa7, 0xc5, 0x2e, 0x6e, 0x76, 0x33, 0x11, 0xaa, 0x10, 0x20, 0xba, 0x77, 0x87, 0x56, 0xdd, 0xf5,
0x58, 0x70, 0x68, 0x94, 0x90, 0x43, 0xfb, 0x30, 0xc3, 0xf7, 0x11, 0x9b, 0xc2, 0x19, 0x61, 0x0a,
0xdb, 0x93, 0xe9, 0x68, 0x2b, 0x25, 0x68, 0x64, 0xa9, 0xa3, 0x2e, 0xa0, 0x01, 0x0e, 0x77, 0x22,
0x87, 0xd9, 0xd4, 0x21, 0x52, 0x8c, 0x50, 0x5b, 0x16, 0x6a, 0x2a, 0x99, 0x41, 0xf7, 0x01, 0x02,
0xb2, 0x17, 0xe3, 0x9d, 0x15, 0x3b, 0xbf, 0x36, 0x6e, 0xe7, 0x46, 0x82, 0x2d, 0x77, 0x9c, 0x59,
0xce, 0x99, 0xf3, 0x6d, 0x10, 0x93, 0x29, 0x6f, 0x17, 0x6e, 0xad, 0x09, 0x13, 0x2b, 0x99, 0xe1,
0xb6, 0xa8, 0xa0, 0x22, 0x68, 0x9d, 0x93, 0xd6, 0x9a, 0x01, 0xa1, 0x2d, 0xf8, 0x06, 0xf6, 0x3c,
0x9f, 0x89, 0xed, 0xc7, 0xa2, 0x6c, 0xaa, 0xf0, 0xbe, 0x8b, 0xd9, 0x20, 0xd4, 0x3a, 0x62, 0xd5,
0x51, 0x68, 0xdc, 0x24, 0x6c, 0x2f, 0x64, 0xd8, 0x71, 0x04, 0xd2, 0xf6, 0x1d, 0xed, 0xbc, 0x34,
0x89, 0x3c, 0xb4, 0x73, 0x17, 0xce, 0x8e, 0x38, 0x5c, 0xb4, 0x08, 0xf5, 0x7d, 0x72, 0x28, 0x2e,
0x85, 0xb6, 0xc1, 0x3f, 0xd1, 0x69, 0x68, 0x1e, 0x60, 0x27, 0x22, 0x22, 0x8c, 0xb7, 0x0c, 0x39,
0xb8, 0x5d, 0xfb, 0x76, 0xb5, 0xf3, 0xeb, 0x2a, 0x2c, 0x14, 0x54, 0x55, 0xb2, 0xfe, 0xc7, 0xd9,
0xf5, 0xaf, 0xc1, 0x71, 0xf6, 0x9e, 0xe0, 0xc0, 0x22, 0x2c, 0x23, 0x88, 0xfe, 0xf7, 0x2a, 0x68,
0x85, 0x33, 0xfc, 0x81, 0xcd, 0x06, 0xf7, 0x6c, 0x87, 0x84, 0xe8, 0x16, 0x4c, 0x07, 0x12, 0xa6,
0xae, 0xba, 0xf3, 0x63, 0x8e, 0x7e, 0xab, 0x62, 0xc4, 0xd8, 0xe8, 0x23, 0x68, 0xb9, 0x84, 0xe1,
0x3e, 0x66, 0x58, 0xc9, 0xbe, 0x5a, 0xb6, 0x92, 0x73, 0xd9, 0x51, 0x78, 0x5b, 0x15, 0x23, 0x59,
0x83, 0xde, 0x83, 0xa6, 0x39, 0x88, 0xbc, 0x7d, 0x71, 0xc9, 0xcd, 0xdc, 0xb8, 0x38, 0x6a, 0xf1,
0x06, 0x47, 0xda, 0xaa, 0x18, 0x12, 0xfb, 0xe3, 0x29, 0x68, 0x50, 0x1c, 0x30, 0xfd, 0x1e, 0x9c,
0x2e, 0x63, 0xc1, 0x6f, 0x56, 0x73, 0x40, 0xcc, 0xfd, 0x30, 0x72, 0x95, 0x9a, 0x93, 0x31, 0x42,
0xd0, 0x08, 0xed, 0x17, 0x52, 0xd5, 0x75, 0x43, 0x7c, 0xeb, 0x6f, 0xc3, 0xd2, 0x10, 0x37, 0x7e,
0xa8, 0x52, 0x36, 0x4e, 0x61, 0x56, 0xb1, 0xd6, 0x23, 0x38, 0xf3, 0x44, 0xe8, 0x22, 0xb9, 0x5e,
0x4e, 0x22, 0x57, 0xd0, 0xb7, 0x60, 0xb9, 0xc8, 0x36, 0xa4, 0xbe, 0x17, 0x12, 0xee, 0x6c, 0x22,
0x1e, 0xdb, 0xa4, 0x9f, 0xce, 0x0a, 0x29, 0x5a, 0x46, 0xc9, 0x8c, 0xfe, 0xfb, 0x1a, 0x2c, 0x1b,
0x24, 0xf4, 0x9d, 0x03, 0x12, 0x07, 0xcb, 0x93, 0x49, 0x77, 0x7e, 0x04, 0x75, 0x4c, 0xa9, 0x32,
0x93, 0xed, 0xd7, 0x96, 0x50, 0x18, 0x9c, 0x2a, 0x7a, 0x07, 0x96, 0xb0, 0xdb, 0xb3, 0xad, 0xc8,
0x8f, 0xc2, 0x78, 0x5b, 0xc2, 0xa8, 0xda, 0xc6, 0xf0, 0x04, 0x0f, 0x38, 0xa1, 0xf0, 0xc8, 0x6d,
0xaf, 0x4f, 0x7e, 0x26, 0x72, 0xa8, 0xba, 0x91, 0x05, 0xe9, 0x26, 0x9c, 0x1d, 0x52, 0x92, 0x52,
0x78, 0x36, 0x6d, 0xab, 0x16, 0xd2, 0xb6, 0x52, 0x31, 0x6a, 0x23, 0xc4, 0xd0, 0x5f, 0x56, 0x61,
0x31, 0x75, 0x2e, 0x45, 0xfe, 0x02, 0xb4, 0x5d, 0x05, 0x0b, 0xb5, 0xaa, 0x88, 0x99, 0x29, 0x20,
0x9f, 0xc1, 0xd5, 0x8a, 0x19, 0xdc, 0x32, 0x4c, 0xc9, 0x04, 0x5b, 0x6d, 0x5d, 0x8d, 0x72, 0x22,
0x37, 0x0a, 0x22, 0xaf, 0x00, 0x84, 0x49, 0x84, 0xd3, 0xa6, 0xc4, 0x6c, 0x06, 0x82, 0x74, 0x98,
0x95, 0xf7, 0xbd, 0x41, 0xc2, 0xc8, 0x61, 0xda, 0xb4, 0xc0, 0xc8, 0xc1, 0x84, 0xbf, 0xf9, 0xae,
0x8b, 0xbd, 0x7e, 0xa8, 0xb5, 0x84, 0xc8, 0xc9, 0x58, 0xf7, 0x61, 0xe1, 0x81, 0xcd, 0xf7, 0xb7,
0x17, 0x9e, 0x8c, 0xab, 0xbc, 0x0f, 0x0d, 0xce, 0x8c, 0x0b, 0xd5, 0x0b, 0xb0, 0x67, 0x0e, 0x48,
0xac, 0xc7, 0x64, 0xcc, 0x83, 0x00, 0xc3, 0x56, 0xa8, 0xd5, 0x04, 0x5c, 0x7c, 0xeb, 0x7f, 0xac,
0x49, 0x49, 0xd7, 0x29, 0x0d, 0xbf, 0xfe, 0x02, 0xa0, 0x3c, 0x25, 0xa9, 0x0f, 0xa7, 0x24, 0x05,
0x91, 0xbf, 0x4a, 0x4a, 0xf2, 0x9a, 0x2e, 0x39, 0x3d, 0x82, 0xe9, 0x75, 0x4a, 0xb9, 0x20, 0xe8,
0x3a, 0x34, 0x30, 0xa5, 0x52, 0xe1, 0x85, 0x78, 0xae, 0x50, 0xf8, 0x7f, 0x25, 0x92, 0x40, 0xed,
0xdc, 0x82, 0x76, 0x02, 0x3a, 0x8a, 0x6d, 0x3b, 0xcb, 0x76, 0x15, 0x40, 0xe6, 0xdc, 0xdb, 0xde,
0x9e, 0xcf, 0x8f, 0x94, 0x3b, 0x82, 0x5a, 0x2a, 0xbe, 0xf5, 0xdb, 0x31, 0x86, 0x90, 0xed, 0x1d,
0x68, 0xda, 0x8c, 0xb8, 0xb1, 0x70, 0xcb, 0x59, 0xe1, 0x52, 0x42, 0x86, 0x44, 0xd2, 0xff, 0xd2,
0x82, 0x73, 0xfc, 0xc4, 0x1e, 0x0b, 0x17, 0x5a, 0xa7, 0xf4, 0x0e, 0x61, 0xd8, 0x76, 0xc2, 0xef,
0x47, 0x24, 0x38, 0x7c, 0xc3, 0x86, 0x61, 0xc1, 0x94, 0xf4, 0x40, 0x15, 0x2d, 0x5f, 0x7b, 0xf9,
0xa5, 0xc8, 0xa7, 0x35, 0x57, 0xfd, 0xcd, 0xd4, 0x5c, 0x65, 0x35, 0x50, 0xe3, 0x84, 0x6a, 0xa0,
0xd1, 0x65, 0x70, 0xa6, 0xb8, 0x9e, 0xca, 0x17, 0xd7, 0x25, 0xa5, 0xc5, 0xf4, 0x71, 0x4b, 0x8b,
0x56, 0x69, 0x69, 0xe1, 0x96, 0xfa, 0x71, 0x5b, 0xa8, 0xfb, 0xbb, 0x59, 0x0b, 0x1c, 0x69, 0x6b,
0x93, 0x14, 0x19, 0xf0, 0x46, 0x8b, 0x8c, 0x4f, 0x73, 0x45, 0x83, 0x2c, 0xdb, 0xdf, 0x3b, 0xde,
0x9e, 0xc6, 0x94, 0x0f, 0xff, 0x77, 0xa9, 0xf7, 0xaf, 0x44, 0xc6, 0x45, 0xfd, 0x54, 0x07, 0xc9,
0x65, 0xcf, 0xef, 0x21, 0x7e, 0xed, 0xaa, 0xa0, 0xc5, 0xbf, 0xd1, 0x35, 0x68, 0x70, 0x25, 0xab,
0x94, 0xf8, 0x6c, 0x56, 0x9f, 0xfc, 0x24, 0xd6, 0x29, 0x7d, 0x4c, 0x89, 0x69, 0x08, 0x24, 0x74,
0x1b, 0xda, 0x89, 0xe1, 0x2b, 0xcf, 0xba, 0x90, 0x5d, 0x91, 0xf8, 0x49, 0xbc, 0x2c, 0x45, 0xe7,
0x6b, 0xfb, 0x76, 0x40, 0x4c, 0x91, 0x30, 0x36, 0x87, 0xd7, 0xde, 0x89, 0x27, 0x93, 0xb5, 0x09,
0x3a, 0xba, 0x0e, 0x53, 0xb2, 0xcf, 0x21, 0x3c, 0x68, 0xe6, 0xc6, 0xb9, 0xe1, 0x60, 0x1a, 0xaf,
0x52, 0x88, 0xfa, 0x9f, 0xab, 0xf0, 0x56, 0x6a, 0x10, 0xb1, 0x37, 0xc5, 0x39, 0xfb, 0xd7, 0x7f,
0xe3, 0x5e, 0x86, 0x79, 0x51, 0x24, 0xa4, 0xed, 0x0e, 0xd9, 0x79, 0x2b, 0x40, 0xf5, 0x3f, 0x54,
0xe1, 0xd2, 0xf0, 0x3e, 0x36, 0x06, 0x38, 0x60, 0xc9, 0xf1, 0x9e, 0xc4, 0x5e, 0xe2, 0x0b, 0xaf,
0x96, 0x5e, 0x78, 0xb9, 0xfd, 0xd5, 0xf3, 0xfb, 0xd3, 0xff, 0x54, 0x83, 0x99, 0x8c, 0x01, 0x95,
0x5d, 0x98, 0x3c, 0x19, 0x14, 0x76, 0x2b, 0xca, 0x42, 0x71, 0x29, 0xb4, 0x8d, 0x0c, 0x04, 0xed,
0x03, 0x50, 0x1c, 0x60, 0x97, 0x30, 0x12, 0xf0, 0x48, 0xce, 0x3d, 0xfe, 0xfe, 0xe4, 0xd1, 0x65,
0x37, 0xa6, 0x69, 0x64, 0xc8, 0xf3, 0x6c, 0x56, 0xb0, 0x0e, 0x55, 0xfc, 0x56, 0x23, 0xf4, 0x05,
0xcc, 0xef, 0xd9, 0x0e, 0xd9, 0x4d, 0x05, 0x99, 0x12, 0x82, 0x3c, 0x9a, 0x5c, 0x90, 0x7b, 0x59,
0xba, 0x46, 0x81, 0x8d, 0x7e, 0x15, 0x16, 0x8b, 0xfe, 0xc4, 0x85, 0xb4, 0x5d, 0x6c, 0x25, 0xda,
0x52, 0x23, 0x1d, 0xc1, 0x62, 0xd1, 0x7f, 0xf4, 0x7f, 0xd5, 0xe0, 0x4c, 0x42, 0x6e, 0xdd, 0xf3,
0xfc, 0xc8, 0x33, 0x45, 0xeb, 0xb0, 0xf4, 0x2c, 0x4e, 0x43, 0x93, 0xd9, 0xcc, 0x49, 0x12, 0x1f,
0x31, 0xe0, 0x77, 0x17, 0xf3, 0x7d, 0x87, 0xd9, 0x54, 0x1d, 0x70, 0x3c, 0x94, 0x67, 0xff, 0x3c,
0xb2, 0x03, 0xd2, 0x17, 0x91, 0xa0, 0x65, 0x24, 0x63, 0x3e, 0xc7, 0xb3, 0x1a, 0x91, 0xe2, 0x4b,
0x65, 0x26, 0x63, 0x61, 0xf7, 0xbe, 0xe3, 0x10, 0x93, 0xab, 0x23, 0x53, 0x04, 0x14, 0xa0, 0xa2,
0xb8, 0x60, 0x81, 0xed, 0x59, 0xaa, 0x04, 0x50, 0x23, 0x2e, 0x27, 0x0e, 0x02, 0x7c, 0xa8, 0x32,
0x7f, 0x39, 0x40, 0x1f, 0x42, 0xdd, 0xc5, 0x54, 0x5d, 0x74, 0x57, 0x73, 0xd1, 0xa1, 0x4c, 0x03,
0xdd, 0x1d, 0x4c, 0xe5, 0x4d, 0xc0, 0x97, 0x75, 0xde, 0x87, 0x56, 0x0c, 0xf8, 0x4a, 0x29, 0xe1,
0xe7, 0x30, 0x97, 0x0b, 0x3e, 0xe8, 0x29, 0x2c, 0xa7, 0x16, 0x95, 0x65, 0xa8, 0x92, 0xc0, 0xb7,
0x8e, 0x94, 0xcc, 0x18, 0x41, 0x40, 0x7f, 0x0e, 0x4b, 0xdc, 0x64, 0x84, 0xe3, 0x9f, 0x50, 0x69,
0xf3, 0x01, 0xb4, 0x13, 0x96, 0xa5, 0x36, 0xd3, 0x81, 0xd6, 0x41, 0xdc, 0xd2, 0x95, 0xb5, 0x4d,
0x32, 0xd6, 0xd7, 0x01, 0x65, 0xe5, 0x55, 0x37, 0xd0, 0xb5, 0x7c, 0x52, 0x7c, 0xa6, 0x78, 0xdd,
0x08, 0xf4, 0x38, 0x27, 0xfe, 0x47, 0x0d, 0x16, 0x36, 0x6d, 0xd1, 0x23, 0x39, 0xa1, 0x20, 0x77,
0x15, 0x16, 0xc3, 0xa8, 0xe7, 0xfa, 0xfd, 0xc8, 0x21, 0x2a, 0x29, 0x50, 0x37, 0xfd, 0x10, 0x7c,
0x5c, 0xf0, 0xe3, 0xca, 0xa2, 0x98, 0x0d, 0x54, 0xf5, 0x2b, 0xbe, 0xd1, 0x87, 0x70, 0xee, 0x21,
0xf9, 0x42, 0xed, 0x67, 0xd3, 0xf1, 0x7b, 0x3d, 0xdb, 0xb3, 0x62, 0x26, 0x4d, 0xc1, 0x64, 0x34,
0x42, 0x59, 0xaa, 0x38, 0x55, 0x9e, 0x2a, 0x26, 0x15, 0xf4, 0x86, 0xef, 0xba, 0x36, 0x53, 0x19,
0x65, 0x0e, 0xa6, 0xff, 0xb2, 0x0a, 0x8b, 0xa9, 0x66, 0xd5, 0xd9, 0xdc, 0x92, 0x3e, 0x24, 0x4f,
0xe6, 0x52, 0xf6, 0x64, 0x8a, 0xa8, 0xaf, 0xee, 0x3e, 0xb3, 0x59, 0xf7, 0xf9, 0x4d, 0x0d, 0xce,
0x6c, 0xda, 0x2c, 0x0e, 0x5c, 0xf6, 0xff, 0xda, 0x29, 0x97, 0x9c, 0x49, 0xe3, 0x78, 0x67, 0xd2,
0x2c, 0x39, 0x93, 0x2e, 0x2c, 0x17, 0x95, 0xa1, 0x0e, 0xe6, 0x34, 0x34, 0xa9, 0x68, 0x3a, 0xcb,
0xbe, 0x82, 0x1c, 0xe8, 0xbf, 0x98, 0x86, 0x8b, 0x9f, 0xd2, 0x3e, 0x66, 0x49, 0xcf, 0xe8, 0x9e,
0x1f, 0x88, 0xae, 0xf3, 0xc9, 0x68, 0xb1, 0xf0, 0x32, 0x58, 0x1b, 0xfb, 0x32, 0x58, 0x1f, 0xf3,
0x32, 0xd8, 0x38, 0xd6, 0xcb, 0x60, 0xf3, 0xc4, 0x5e, 0x06, 0x87, 0x6b, 0xad, 0xa9, 0xd2, 0x5a,
0xeb, 0x69, 0xae, 0x1e, 0x99, 0x16, 0x6e, 0xf3, 0x9d, 0xac, 0xdb, 0x8c, 0x3d, 0x9d, 0xb1, 0x4f,
0x1a, 0x85, 0x07, 0xb5, 0xd6, 0x91, 0x0f, 0x6a, 0xed, 0xe1, 0x07, 0xb5, 0xf2, 0x37, 0x19, 0x18,
0xf9, 0x26, 0x73, 0x19, 0xe6, 0xc3, 0x43, 0xcf, 0x24, 0xfd, 0xa4, 0x93, 0x38, 0x23, 0xb7, 0x9d,
0x87, 0xe6, 0x3c, 0x62, 0xb6, 0xe0, 0x11, 0x89, 0xa5, 0xce, 0x65, 0x2c, 0xb5, 0xcc, 0x4f, 0xe6,
0x47, 0x96, 0xb9, 0x85, 0xe7, 0x92, 0x85, 0xd2, 0xe7, 0x92, 0xff, 0x9a, 0x62, 0xeb, 0x33, 0x58,
0x19, 0x75, 0xca, 0xca, 0x79, 0x35, 0x98, 0x36, 0x07, 0xd8, 0xb3, 0x44, 0x5b, 0x50, 0x54, 0xff,
0x6a, 0x38, 0xae, 0x3a, 0xb8, 0xf1, 0xe5, 0x2c, 0x2c, 0xa5, 0x59, 0x3f, 0xff, 0x6b, 0x9b, 0x04,
0x3d, 0x82, 0xc5, 0xf8, 0x79, 0x29, 0x6e, 0xe4, 0xa2, 0x71, 0x6f, 0x27, 0x9d, 0x0b, 0xe5, 0x93,
0x52, 0x34, 0xbd, 0x82, 0x4c, 0x38, 0x57, 0x24, 0x98, 0x3e, 0xd3, 0x7c, 0x6b, 0x0c, 0xe5, 0x04,
0xeb, 0x28, 0x16, 0x57, 0xaa, 0xe8, 0x29, 0xcc, 0xe7, 0x1f, 0x13, 0x50, 0x2e, 0x0d, 0x2a, 0x7d,
0xdf, 0xe8, 0xe8, 0xe3, 0x50, 0x12, 0xf9, 0x9f, 0x71, 0x33, 0xc8, 0xf5, 0xcd, 0x91, 0x9e, 0xef,
0x08, 0x94, 0xbd, 0x3c, 0x74, 0xbe, 0x39, 0x16, 0x27, 0xa1, 0xfe, 0x01, 0xb4, 0xe2, 0x5e, 0x72,
0x5e, 0xcd, 0x85, 0x0e, 0x73, 0x67, 0x31, 0x4f, 0x6f, 0x2f, 0xd4, 0x2b, 0xe8, 0x23, 0x98, 0xe1,
0x68, 0x8f, 0x36, 0xb6, 0x9f, 0x60, 0xeb, 0x95, 0xd6, 0xb7, 0xe2, 0x5e, 0xeb, 0xf0, 0xe2, 0x4c,
0x07, 0xb6, 0x73, 0xaa, 0xa4, 0xeb, 0xa9, 0x57, 0xd0, 0xf7, 0x24, 0xff, 0x5d, 0xf5, 0xf3, 0x80,
0xe5, 0xae, 0xfc, 0x35, 0x4a, 0x37, 0xfe, 0x35, 0x4a, 0xf7, 0xae, 0x4b, 0xd9, 0x61, 0xa7, 0xa4,
0x2d, 0xa9, 0x08, 0x3c, 0x83, 0xb9, 0x4d, 0xc2, 0xd2, 0x2e, 0x02, 0xba, 0x74, 0xac, 0x5e, 0x4b,
0x47, 0x2f, 0xa2, 0x0d, 0x37, 0x22, 0xf4, 0x0a, 0xfa, 0xb2, 0x0a, 0xa7, 0x36, 0x09, 0x2b, 0xd6,
0xe5, 0xe8, 0xdd, 0x72, 0x26, 0x23, 0xea, 0xf7, 0xce, 0xc3, 0x49, 0x7d, 0x3a, 0x4f, 0x56, 0xaf,
0xa0, 0xdf, 0x56, 0x61, 0x7e, 0x93, 0xf0, 0x73, 0x4b, 0x64, 0xba, 0x3e, 0x5e, 0xa6, 0x92, 0x5a,
0xbc, 0x33, 0x61, 0x0f, 0x2c, 0xc3, 0x5d, 0xaf, 0xa0, 0xdf, 0x55, 0xe1, 0x6c, 0x46, 0x57, 0x59,
0x7e, 0xaf, 0x22, 0xdb, 0x27, 0x13, 0xfe, 0x10, 0x25, 0x43, 0x52, 0xaf, 0xa0, 0x5d, 0x61, 0x26,
0x69, 0xaa, 0x8f, 0x2e, 0x96, 0xe6, 0xf4, 0x09, 0xf7, 0x95, 0x51, 0xd3, 0x89, 0x69, 0x7c, 0x02,
0x33, 0x9b, 0x84, 0xc5, 0x39, 0x67, 0xde, 0xf8, 0x0b, 0xe5, 0x40, 0x3e, 0xfa, 0x14, 0xd3, 0x54,
0x61, 0xc4, 0x4b, 0x92, 0x56, 0x26, 0xaf, 0xca, 0x87, 0x9f, 0xd2, 0x04, 0x34, 0x6f, 0xc4, 0xe5,
0x69, 0x99, 0x5e, 0x41, 0xcf, 0x61, 0xb9, 0x3c, 0xfa, 0xa3, 0xb7, 0x8f, 0x9d, 0x07, 0x74, 0xae,
0x1e, 0x07, 0x35, 0x66, 0xf9, 0xf1, 0xfa, 0x5f, 0x5f, 0xae, 0x54, 0xff, 0xf6, 0x72, 0xa5, 0xfa,
0xef, 0x97, 0x2b, 0xd5, 0x1f, 0xde, 0x3c, 0xe2, 0x07, 0x6b, 0x99, 0xdf, 0xc0, 0x61, 0x6a, 0x9b,
0x8e, 0x4d, 0x3c, 0xd6, 0x9b, 0x12, 0x21, 0xe0, 0xe6, 0x7f, 0x02, 0x00, 0x00, 0xff, 0xff, 0xc9,
0x9b, 0xb9, 0x62, 0x22, 0x27, 0x00, 0x00,
}
// Reference imports to suppress errors if they are not otherwise used.
@ -2652,6 +2654,8 @@ type RepoServerServiceClient interface {
ResolveRevision(ctx context.Context, in *ResolveRevisionRequest, opts ...grpc.CallOption) (*ResolveRevisionResponse, error)
// Returns a list of refs (e.g. branches and tags) in the repo
ListRefs(ctx context.Context, in *ListRefsRequest, opts ...grpc.CallOption) (*Refs, error)
// Returns a list of oci tags in the repo
ListOCITags(ctx context.Context, in *ListRefsRequest, opts ...grpc.CallOption) (*Refs, error)
// ListApps returns a list of apps in the repo
ListApps(ctx context.Context, in *ListAppsRequest, opts ...grpc.CallOption) (*AppList, error)
// ListPlugins returns a list of cmp v2 plugins running as sidecar to reposerver
@ -2660,6 +2664,8 @@ type RepoServerServiceClient interface {
GetAppDetails(ctx context.Context, in *RepoServerAppDetailsQuery, opts ...grpc.CallOption) (*RepoAppDetailsResponse, error)
// Get the meta-data (author, date, tags, message) for a specific revision of the repo
GetRevisionMetadata(ctx context.Context, in *RepoServerRevisionMetadataRequest, opts ...grpc.CallOption) (*v1alpha1.RevisionMetadata, error)
// Get the meta-data (author, date, tags, message) for a specific revision of the OCI image
GetOCIMetadata(ctx context.Context, in *RepoServerRevisionChartDetailsRequest, opts ...grpc.CallOption) (*v1alpha1.OCIMetadata, error)
// Get the chart details (author, date, tags, message) for a specific revision of the repo
GetRevisionChartDetails(ctx context.Context, in *RepoServerRevisionChartDetailsRequest, opts ...grpc.CallOption) (*v1alpha1.ChartDetails, error)
// GetHelmCharts returns list of helm charts in the specified repository
@ -2750,6 +2756,15 @@ func (c *repoServerServiceClient) ListRefs(ctx context.Context, in *ListRefsRequ
return out, nil
}
func (c *repoServerServiceClient) ListOCITags(ctx context.Context, in *ListRefsRequest, opts ...grpc.CallOption) (*Refs, error) {
out := new(Refs)
err := c.cc.Invoke(ctx, "/repository.RepoServerService/ListOCITags", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *repoServerServiceClient) ListApps(ctx context.Context, in *ListAppsRequest, opts ...grpc.CallOption) (*AppList, error) {
out := new(AppList)
err := c.cc.Invoke(ctx, "/repository.RepoServerService/ListApps", in, out, opts...)
@ -2786,6 +2801,15 @@ func (c *repoServerServiceClient) GetRevisionMetadata(ctx context.Context, in *R
return out, nil
}
func (c *repoServerServiceClient) GetOCIMetadata(ctx context.Context, in *RepoServerRevisionChartDetailsRequest, opts ...grpc.CallOption) (*v1alpha1.OCIMetadata, error) {
out := new(v1alpha1.OCIMetadata)
err := c.cc.Invoke(ctx, "/repository.RepoServerService/GetOCIMetadata", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *repoServerServiceClient) GetRevisionChartDetails(ctx context.Context, in *RepoServerRevisionChartDetailsRequest, opts ...grpc.CallOption) (*v1alpha1.ChartDetails, error) {
out := new(v1alpha1.ChartDetails)
err := c.cc.Invoke(ctx, "/repository.RepoServerService/GetRevisionChartDetails", in, out, opts...)
@ -2843,6 +2867,8 @@ type RepoServerServiceServer interface {
ResolveRevision(context.Context, *ResolveRevisionRequest) (*ResolveRevisionResponse, error)
// Returns a list of refs (e.g. branches and tags) in the repo
ListRefs(context.Context, *ListRefsRequest) (*Refs, error)
// Returns a list of oci tags in the repo
ListOCITags(context.Context, *ListRefsRequest) (*Refs, error)
// ListApps returns a list of apps in the repo
ListApps(context.Context, *ListAppsRequest) (*AppList, error)
// ListPlugins returns a list of cmp v2 plugins running as sidecar to reposerver
@ -2851,6 +2877,8 @@ type RepoServerServiceServer interface {
GetAppDetails(context.Context, *RepoServerAppDetailsQuery) (*RepoAppDetailsResponse, error)
// Get the meta-data (author, date, tags, message) for a specific revision of the repo
GetRevisionMetadata(context.Context, *RepoServerRevisionMetadataRequest) (*v1alpha1.RevisionMetadata, error)
// Get the meta-data (author, date, tags, message) for a specific revision of the OCI image
GetOCIMetadata(context.Context, *RepoServerRevisionChartDetailsRequest) (*v1alpha1.OCIMetadata, error)
// Get the chart details (author, date, tags, message) for a specific revision of the repo
GetRevisionChartDetails(context.Context, *RepoServerRevisionChartDetailsRequest) (*v1alpha1.ChartDetails, error)
// GetHelmCharts returns list of helm charts in the specified repository
@ -2882,6 +2910,9 @@ func (*UnimplementedRepoServerServiceServer) ResolveRevision(ctx context.Context
func (*UnimplementedRepoServerServiceServer) ListRefs(ctx context.Context, req *ListRefsRequest) (*Refs, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListRefs not implemented")
}
func (*UnimplementedRepoServerServiceServer) ListOCITags(ctx context.Context, req *ListRefsRequest) (*Refs, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListOCITags not implemented")
}
func (*UnimplementedRepoServerServiceServer) ListApps(ctx context.Context, req *ListAppsRequest) (*AppList, error) {
return nil, status.Errorf(codes.Unimplemented, "method ListApps not implemented")
}
@ -2894,6 +2925,9 @@ func (*UnimplementedRepoServerServiceServer) GetAppDetails(ctx context.Context,
func (*UnimplementedRepoServerServiceServer) GetRevisionMetadata(ctx context.Context, req *RepoServerRevisionMetadataRequest) (*v1alpha1.RevisionMetadata, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRevisionMetadata not implemented")
}
func (*UnimplementedRepoServerServiceServer) GetOCIMetadata(ctx context.Context, req *RepoServerRevisionChartDetailsRequest) (*v1alpha1.OCIMetadata, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetOCIMetadata not implemented")
}
func (*UnimplementedRepoServerServiceServer) GetRevisionChartDetails(ctx context.Context, req *RepoServerRevisionChartDetailsRequest) (*v1alpha1.ChartDetails, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetRevisionChartDetails not implemented")
}
@ -3012,6 +3046,24 @@ func _RepoServerService_ListRefs_Handler(srv interface{}, ctx context.Context, d
return interceptor(ctx, in, info, handler)
}
func _RepoServerService_ListOCITags_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListRefsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RepoServerServiceServer).ListOCITags(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/repository.RepoServerService/ListOCITags",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RepoServerServiceServer).ListOCITags(ctx, req.(*ListRefsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _RepoServerService_ListApps_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListAppsRequest)
if err := dec(in); err != nil {
@ -3084,6 +3136,24 @@ func _RepoServerService_GetRevisionMetadata_Handler(srv interface{}, ctx context
return interceptor(ctx, in, info, handler)
}
func _RepoServerService_GetOCIMetadata_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RepoServerRevisionChartDetailsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(RepoServerServiceServer).GetOCIMetadata(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/repository.RepoServerService/GetOCIMetadata",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(RepoServerServiceServer).GetOCIMetadata(ctx, req.(*RepoServerRevisionChartDetailsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _RepoServerService_GetRevisionChartDetails_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(RepoServerRevisionChartDetailsRequest)
if err := dec(in); err != nil {
@ -3194,6 +3264,10 @@ var _RepoServerService_serviceDesc = grpc.ServiceDesc{
MethodName: "ListRefs",
Handler: _RepoServerService_ListRefs_Handler,
},
{
MethodName: "ListOCITags",
Handler: _RepoServerService_ListOCITags_Handler,
},
{
MethodName: "ListApps",
Handler: _RepoServerService_ListApps_Handler,
@ -3210,6 +3284,10 @@ var _RepoServerService_serviceDesc = grpc.ServiceDesc{
MethodName: "GetRevisionMetadata",
Handler: _RepoServerService_GetRevisionMetadata_Handler,
},
{
MethodName: "GetOCIMetadata",
Handler: _RepoServerService_GetOCIMetadata_Handler,
},
{
MethodName: "GetRevisionChartDetails",
Handler: _RepoServerService_GetRevisionChartDetails_Handler,

View file

@ -164,6 +164,10 @@ func helmIndexRefsKey(repo string) string {
return "helm-index|" + repo
}
func ociTagsKey(repo string) string {
return "oci-tags|" + repo
}
// SetHelmIndex stores helm repository index.yaml content to cache
func (c *Cache) SetHelmIndex(repo string, indexData []byte) error {
if indexData == nil {
@ -181,6 +185,23 @@ func (c *Cache) GetHelmIndex(repo string, indexData *[]byte) error {
return c.cache.GetItem(helmIndexRefsKey(repo), indexData)
}
// SetOCITags stores oci image tags to cache
func (c *Cache) SetOCITags(repo string, indexData []byte) error {
if indexData == nil {
// Logged as warning upstream
return errors.New("oci index data is nil, skipping cache")
}
return c.cache.SetItem(
ociTagsKey(repo),
indexData,
&cacheutil.CacheActionOpts{Expiration: c.revisionCacheExpiration})
}
// GetOCITags retrieves oci image tags from cache
func (c *Cache) GetOCITags(repo string, indexData *[]byte) error {
return c.cache.GetItem(ociTagsKey(repo), indexData)
}
func gitRefsKey(repo string) string {
return "git-refs|" + repo
}

View file

@ -17,6 +17,11 @@ import (
"time"
"github.com/TomOnTime/utfutil"
imagev1 "github.com/opencontainers/image-spec/specs-go/v1"
"sigs.k8s.io/yaml"
"github.com/argoproj/argo-cd/v3/util/oci"
"github.com/argoproj/gitops-engine/pkg/utils/kube"
textutils "github.com/argoproj/gitops-engine/pkg/utils/text"
"github.com/argoproj/pkg/v2/sync"
@ -36,7 +41,6 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
kubeyaml "k8s.io/apimachinery/pkg/util/yaml"
"sigs.k8s.io/yaml"
pluginclient "github.com/argoproj/argo-cd/v3/cmpserver/apiclient"
"github.com/argoproj/argo-cd/v3/common"
@ -77,11 +81,13 @@ type Service struct {
rootDir string
gitRepoPaths utilio.TempPaths
chartPaths utilio.TempPaths
ociPaths utilio.TempPaths
gitRepoInitializer func(rootPath string) goio.Closer
repoLock *repositoryLock
cache *cache.Cache
parallelismLimitSemaphore *semaphore.Weighted
metricsServer *metrics.MetricsServer
newOCIClient func(repoURL string, creds oci.Creds, proxy string, noProxy string, mediaTypes []string, opts ...oci.ClientOpts) (oci.Client, error)
newGitClient func(rawRepoURL string, root string, creds git.Creds, insecure bool, enableLfs bool, proxy string, noProxy string, opts ...git.ClientOpts) (git.Client, error)
newHelmClient func(repoURL string, creds helm.Creds, enableOci bool, proxy string, noProxy string, opts ...helm.ClientOpts) helm.Client
initConstants RepoServerInitConstants
@ -90,6 +96,7 @@ type Service struct {
}
type RepoServerInitConstants struct {
OCIMediaTypes []string
ParallelismLimit int64
PauseGenerationAfterFailedGenerationAttempts int
PauseGenerationOnFailureForMinutes int
@ -102,6 +109,8 @@ type RepoServerInitConstants struct {
StreamedManifestMaxTarSize int64
HelmManifestMaxExtractedSize int64
HelmRegistryMaxIndexSize int64
OCIManifestMaxExtractedSize int64
DisableOCIManifestMaxExtractedSize bool
DisableHelmManifestMaxExtractedSize bool
IncludeHiddenDirectories bool
CMPUseManifestGeneratePaths bool
@ -118,12 +127,14 @@ func NewService(metricsServer *metrics.MetricsServer, cache *cache.Cache, initCo
repoLock := NewRepositoryLock()
gitRandomizedPaths := utilio.NewRandomizedTempPaths(rootDir)
helmRandomizedPaths := utilio.NewRandomizedTempPaths(rootDir)
ociRandomizedPaths := utilio.NewRandomizedTempPaths(rootDir)
return &Service{
parallelismLimitSemaphore: parallelismLimitSemaphore,
repoLock: repoLock,
cache: cache,
metricsServer: metricsServer,
newGitClient: git.NewClientExt,
newOCIClient: oci.NewClient,
newHelmClient: func(repoURL string, creds helm.Creds, enableOci bool, proxy string, noProxy string, opts ...helm.ClientOpts) helm.Client {
return helm.NewClientWithLock(repoURL, creds, sync.NewKeyLock(), enableOci, proxy, noProxy, opts...)
},
@ -132,6 +143,7 @@ func NewService(metricsServer *metrics.MetricsServer, cache *cache.Cache, initCo
gitCredsStore: gitCredsStore,
gitRepoPaths: gitRandomizedPaths,
chartPaths: helmRandomizedPaths,
ociPaths: ociRandomizedPaths,
gitRepoInitializer: directoryPermissionInitializer,
rootDir: rootDir,
}
@ -172,6 +184,28 @@ func (s *Service) Init() error {
return os.Chmod(s.rootDir, 0o300)
}
// ListOCITags List a subset of the refs (currently, branches and tags) of a git repo
func (s *Service) ListOCITags(ctx context.Context, q *apiclient.ListRefsRequest) (*apiclient.Refs, error) {
ociClient, err := s.newOCIClient(q.Repo.Repo, q.Repo.GetOCICreds(), q.Repo.Proxy, q.Repo.NoProxy, s.initConstants.OCIMediaTypes, oci.WithIndexCache(s.cache), oci.WithImagePaths(s.ociPaths), oci.WithManifestMaxExtractedSize(s.initConstants.OCIManifestMaxExtractedSize), oci.WithDisableManifestMaxExtractedSize(s.initConstants.DisableOCIManifestMaxExtractedSize))
if err != nil {
return nil, fmt.Errorf("error creating oci client: %w", err)
}
s.metricsServer.IncPendingRepoRequest(q.Repo.Repo)
defer s.metricsServer.DecPendingRepoRequest(q.Repo.Repo)
tags, err := ociClient.GetTags(ctx, false)
if err != nil {
return nil, err
}
res := apiclient.Refs{
Tags: tags,
}
return &res, nil
}
// ListRefs List a subset of the refs (currently, branches and tags) of a git repo
func (s *Service) ListRefs(_ context.Context, q *apiclient.ListRefsRequest) (*apiclient.Refs, error) {
gitClient, err := s.newClient(q.Repo)
@ -293,22 +327,25 @@ func (s *Service) runRepoOperation(
sanitizer.AddRegexReplacement(getRepoSanitizerRegex(s.rootDir), "<path to cached source>")
}
var ociClient oci.Client
var gitClient git.Client
var helmClient helm.Client
var err error
gitClientOpts := git.WithCache(s.cache, !settings.noRevisionCache && !settings.noCache)
revision = textutils.FirstNonEmpty(revision, source.TargetRevision)
unresolvedRevision := revision
if source.IsHelm() {
switch {
case source.IsOCI():
ociClient, revision, err = s.newOCIClientResolveRevision(ctx, repo, revision, settings.noCache || settings.noRevisionCache)
case source.IsHelm():
helmClient, revision, err = s.newHelmClientResolveRevision(repo, revision, source.Chart, settings.noCache || settings.noRevisionCache)
if err != nil {
return err
}
} else {
default:
gitClient, revision, err = s.newClientResolveRevision(repo, revision, gitClientOpts)
if err != nil {
return err
}
}
if err != nil {
return err
}
repoRefs, err := resolveReferencedSources(hasMultipleSources, source.Helm, refSources, s.newClientResolveRevision, gitClientOpts)
@ -333,7 +370,46 @@ func (s *Service) runRepoOperation(
defer settings.sem.Release(1)
}
if source.IsHelm() {
if source.IsOCI() {
if settings.noCache {
err = ociClient.CleanCache(revision)
if err != nil {
return err
}
}
ociPath, closer, err := ociClient.Extract(ctx, revision)
if err != nil {
return err
}
defer utilio.Close(closer)
if !s.initConstants.AllowOutOfBoundsSymlinks {
err := apppathutil.CheckOutOfBoundsSymlinks(ociPath)
if err != nil {
oobError := &apppathutil.OutOfBoundsSymlinkError{}
if errors.As(err, &oobError) {
log.WithFields(log.Fields{
common.SecurityField: common.SecurityHigh,
"repo": repo.Repo,
"digest": revision,
"file": oobError.File,
}).Warn("oci image contains out-of-bounds symlink")
return fmt.Errorf("oci image contains out-of-bounds symlinks. file: %s", oobError.File)
}
return err
}
}
appPath, err := apppathutil.Path(ociPath, source.Path)
if err != nil {
return err
}
return operation(ociPath, revision, revision, func() (*operationContext, error) {
return &operationContext{appPath, ""}, nil
})
} else if source.IsHelm() {
if settings.noCache {
err = helmClient.CleanChartCache(source.Chart, revision)
if err != nil {
@ -962,7 +1038,7 @@ func getHelmRepos(appPath string, repositories []*v1alpha1.Repository, helmRepoC
reposByName := make(map[string]*v1alpha1.Repository)
reposByURL := make(map[string]*v1alpha1.Repository)
for _, repo := range repositories {
reposByURL[repo.Repo] = repo
reposByURL[strings.TrimPrefix(repo.Repo, "oci://")] = repo
if repo.Name != "" {
reposByName[repo.Name] = repo
}
@ -992,10 +1068,11 @@ func getHelmRepos(appPath string, repositories []*v1alpha1.Repository, helmRepoC
for _, cred := range repositories {
// if the repo is OCI, don't match the repository URL exactly, but only as a dependent repository prefix just like in the getRepoCredential function
// see https://github.com/argoproj/argo-cd/issues/12436
if _, err := url.Parse("oci://" + dep.Repo); err == nil && cred.EnableOCI && strings.HasPrefix(dep.Repo, cred.Repo) {
if _, err := url.Parse("oci://" + dep.Repo); err == nil && (cred.EnableOCI && (strings.HasPrefix(dep.Repo, cred.Repo) || strings.HasPrefix(cred.Repo, dep.Repo)) || (cred.Type == "oci" && (strings.HasPrefix("oci://"+dep.Repo, cred.Repo) || strings.HasPrefix(cred.Repo, "oci://"+dep.Repo)))) {
repo.Username = cred.Username
repo.Password = cred.Password
repo.UseAzureWorkloadIdentity = cred.UseAzureWorkloadIdentity
repo.EnableOCI = true
break
}
}
@ -1335,8 +1412,12 @@ func getReferencedSource(rawValueFile string, refSources map[string]*v1alpha1.Re
func getRepoCredential(repoCredentials []*v1alpha1.RepoCreds, repoURL string) *v1alpha1.RepoCreds {
for _, cred := range repoCredentials {
url := strings.TrimPrefix(repoURL, ociPrefix)
if strings.HasPrefix(url, cred.URL) {
if cred.Type != "oci" {
if strings.HasPrefix(strings.TrimPrefix(repoURL, ociPrefix), cred.URL) {
return cred
}
} else if strings.HasPrefix(ociPrefix+repoURL, cred.URL) {
cred.EnableOCI = true
return cred
}
}
@ -1685,18 +1766,32 @@ func getObjsFromYAMLOrJSON(logCtx *log.Entry, manifestPath string, filename stri
if err != nil {
return status.Errorf(codes.FailedPrecondition, "Failed to open %q", manifestPath)
}
defer func() {
closeReader := func(reader goio.ReadCloser) {
err := reader.Close()
if err != nil {
logCtx.Errorf("failed to close %q - potential memory leak", manifestPath)
}
}()
}
defer closeReader(reader)
if strings.HasSuffix(filename, ".json") {
var obj unstructured.Unstructured
decoder := json.NewDecoder(reader)
err = decoder.Decode(&obj)
if err != nil {
return status.Errorf(codes.FailedPrecondition, "Failed to unmarshal %q: %v", filename, err)
decoderErr := decoder.Decode(&obj)
if decoderErr != nil {
// Check to see if the file is potentially an OCI manifest
reader, err := utfutil.OpenFile(manifestPath, utfutil.UTF8)
if err != nil {
return status.Errorf(codes.FailedPrecondition, "Failed to open %q", manifestPath)
}
defer closeReader(reader)
manifest := imagev1.Manifest{}
decoder := json.NewDecoder(reader)
err = decoder.Decode(&manifest)
if err != nil {
// Not an OCI manifest, return original error
return status.Errorf(codes.FailedPrecondition, "Failed to unmarshal %q: %v", filename, decoderErr)
}
}
if decoder.More() {
return status.Errorf(codes.FailedPrecondition, "Found multiple objects in %q. Only single objects are allowed in JSON files.", filename)
@ -2262,7 +2357,7 @@ func (s *Service) GetRevisionMetadata(_ context.Context, q *apiclient.RepoServer
if err == nil {
// The logic here is that if a signature check on metadata is requested,
// but there is none in the cache, we handle as if we have a cache miss
// and re-generate the meta data. Otherwise, if there is signature info
// and re-generate the metadata. Otherwise, if there is signature info
// in the metadata, but none was requested, we remove it from the data
// that we return.
if !q.CheckSignature || metadata.SignatureInfo != "" {
@ -2329,6 +2424,31 @@ func (s *Service) GetRevisionMetadata(_ context.Context, q *apiclient.RepoServer
return metadata, nil
}
func (s *Service) GetOCIMetadata(ctx context.Context, q *apiclient.RepoServerRevisionChartDetailsRequest) (*v1alpha1.OCIMetadata, error) {
client, err := s.newOCIClient(q.Repo.Repo, q.Repo.GetOCICreds(), q.Repo.Proxy, q.Repo.NoProxy, s.initConstants.OCIMediaTypes, oci.WithIndexCache(s.cache), oci.WithImagePaths(s.ociPaths), oci.WithManifestMaxExtractedSize(s.initConstants.OCIManifestMaxExtractedSize), oci.WithDisableManifestMaxExtractedSize(s.initConstants.DisableOCIManifestMaxExtractedSize))
if err != nil {
return nil, fmt.Errorf("failed to initialize oci client: %w", err)
}
metadata, err := client.DigestMetadata(ctx, q.Revision)
if err != nil {
return nil, fmt.Errorf("failed to extract digest metadata for revision %q: %w", q.Revision, err)
}
a := metadata.Annotations
return &v1alpha1.OCIMetadata{
CreatedAt: a["org.opencontainers.image.created"],
Authors: a["org.opencontainers.image.authors"],
// TODO: add this field at a later stage
// ImageURL: a["org.opencontainers.image.url"],
DocsURL: a["org.opencontainers.image.documentation"],
SourceURL: a["org.opencontainers.image.source"],
Version: a["org.opencontainers.image.version"],
Description: a["org.opencontainers.image.description"],
}, nil
}
// GetRevisionChartDetails returns the helm chart details of a given version
func (s *Service) GetRevisionChartDetails(_ context.Context, q *apiclient.RepoServerRevisionChartDetailsRequest) (*v1alpha1.ChartDetails, error) {
details, err := s.cache.GetRevisionChartDetails(q.Repo.Repo, q.Name, q.Revision)
@ -2398,6 +2518,20 @@ func (s *Service) newClientResolveRevision(repo *v1alpha1.Repository, revision s
return gitClient, commitSHA, nil
}
func (s *Service) newOCIClientResolveRevision(ctx context.Context, repo *v1alpha1.Repository, revision string, noRevisionCache bool) (oci.Client, string, error) {
ociClient, err := s.newOCIClient(repo.Repo, repo.GetOCICreds(), repo.Proxy, repo.NoProxy, s.initConstants.OCIMediaTypes, oci.WithIndexCache(s.cache), oci.WithImagePaths(s.ociPaths), oci.WithManifestMaxExtractedSize(s.initConstants.OCIManifestMaxExtractedSize), oci.WithDisableManifestMaxExtractedSize(s.initConstants.DisableOCIManifestMaxExtractedSize))
if err != nil {
return nil, "", fmt.Errorf("failed to initialize oci client: %w", err)
}
digest, err := ociClient.ResolveRevision(ctx, revision, noRevisionCache)
if err != nil {
return nil, "", fmt.Errorf("failed to resolve revision %q: %w", revision, err)
}
return ociClient, digest, nil
}
func (s *Service) newHelmClientResolveRevision(repo *v1alpha1.Repository, revision string, chart string, noRevisionCache bool) (helm.Client, string, error) {
enableOCI := repo.EnableOCI || helm.IsHelmOciRepo(repo.Repo)
helmClient := s.newHelmClient(repo.Repo, repo.GetHelmCreds(), enableOCI, repo.Proxy, repo.NoProxy, helm.WithIndexCache(s.cache), helm.WithChartPaths(s.chartPaths))
@ -2567,7 +2701,7 @@ func (s *Service) GetHelmCharts(_ context.Context, q *apiclient.HelmChartsReques
return &res, nil
}
func (s *Service) TestRepository(_ context.Context, q *apiclient.TestRepositoryRequest) (*apiclient.TestRepositoryResponse, error) {
func (s *Service) TestRepository(ctx context.Context, q *apiclient.TestRepositoryRequest) (*apiclient.TestRepositoryResponse, error) {
repo := q.Repo
// per Type doc, "git" should be assumed if empty or absent
if repo.Type == "" {
@ -2577,6 +2711,14 @@ func (s *Service) TestRepository(_ context.Context, q *apiclient.TestRepositoryR
"git": func() error {
return git.TestRepo(repo.Repo, repo.GetGitCreds(s.gitCredsStore), repo.IsInsecure(), repo.IsLFSEnabled(), repo.Proxy, repo.NoProxy)
},
"oci": func() error {
client, err := oci.NewClient(repo.Repo, repo.GetOCICreds(), repo.Proxy, repo.NoProxy, s.initConstants.OCIMediaTypes)
if err != nil {
return err
}
_, err = client.TestRepo(ctx)
return err
},
"helm": func() error {
if repo.EnableOCI {
if !helm.IsHelmOciRepo(repo.Repo) {
@ -2599,12 +2741,23 @@ func (s *Service) TestRepository(_ context.Context, q *apiclient.TestRepositoryR
}
// ResolveRevision resolves the revision/ambiguousRevision specified in the ResolveRevisionRequest request into a concrete revision.
func (s *Service) ResolveRevision(_ context.Context, q *apiclient.ResolveRevisionRequest) (*apiclient.ResolveRevisionResponse, error) {
func (s *Service) ResolveRevision(ctx context.Context, q *apiclient.ResolveRevisionRequest) (*apiclient.ResolveRevisionResponse, error) {
repo := q.Repo
app := q.App
ambiguousRevision := q.AmbiguousRevision
var revision string
source := app.Spec.GetSourcePtrByIndex(int(q.SourceIndex))
if source.IsOCI() {
_, revision, err := s.newOCIClientResolveRevision(ctx, repo, ambiguousRevision, true)
if err != nil {
return &apiclient.ResolveRevisionResponse{Revision: "", AmbiguousRevision: ""}, err
}
return &apiclient.ResolveRevisionResponse{
Revision: revision,
AmbiguousRevision: fmt.Sprintf("%v (%v)", ambiguousRevision, revision),
}, nil
}
if source.IsHelm() {
_, revision, err := s.newHelmClientResolveRevision(repo, ambiguousRevision, source.Chart, true)
if err != nil {
@ -2619,7 +2772,7 @@ func (s *Service) ResolveRevision(_ context.Context, q *apiclient.ResolveRevisio
if err != nil {
return &apiclient.ResolveRevisionResponse{Revision: "", AmbiguousRevision: ""}, err
}
revision, err = gitClient.LsRemote(ambiguousRevision)
revision, err := gitClient.LsRemote(ambiguousRevision)
if err != nil {
s.metricsServer.IncGitLsRemoteFail(gitClient.Root(), revision)
return &apiclient.ResolveRevisionResponse{Revision: "", AmbiguousRevision: ""}, err

View file

@ -319,6 +319,10 @@ service RepoServerService {
rpc ListRefs(ListRefsRequest) returns (Refs) {
}
// Returns a list of oci tags in the repo
rpc ListOCITags(ListRefsRequest) returns (Refs) {
}
// ListApps returns a list of apps in the repo
rpc ListApps(ListAppsRequest) returns (AppList) {
}
@ -334,6 +338,10 @@ service RepoServerService {
// Get the meta-data (author, date, tags, message) for a specific revision of the repo
rpc GetRevisionMetadata(RepoServerRevisionMetadataRequest) returns (github.com.argoproj.argo_cd.v3.pkg.apis.application.v1alpha1.RevisionMetadata) {
}
// Get the meta-data (author, date, tags, message) for a specific revision of the OCI image
rpc GetOCIMetadata(RepoServerRevisionChartDetailsRequest) returns (github.com.argoproj.argo_cd.v3.pkg.apis.application.v1alpha1.OCIMetadata) {
}
// Get the chart details (author, date, tags, message) for a specific revision of the repo
rpc GetRevisionChartDetails(RepoServerRevisionChartDetailsRequest) returns (github.com.argoproj.argo_cd.v3.pkg.apis.application.v1alpha1.ChartDetails) {

View file

@ -2848,7 +2848,7 @@ func Test_findManifests(t *testing.T) {
})
}
func TestTestRepoOCI(t *testing.T) {
func TestTestRepoHelmOCI(t *testing.T) {
service := newService(t, ".")
_, err := service.TestRepository(t.Context(), &apiclient.TestRepositoryRequest{
Repo: &v1alpha1.Repository{
@ -3205,7 +3205,7 @@ func Test_populateHelmAppDetails_values_symlinks(t *testing.T) {
})
}
func TestGetHelmRepos_OCIDependenciesWithHelmRepo(t *testing.T) {
func TestGetHelmRepos_OCIHelmDependenciesWithHelmRepo(t *testing.T) {
src := v1alpha1.ApplicationSource{Path: "."}
q := apiclient.ManifestRequest{Repos: []*v1alpha1.Repository{}, ApplicationSource: &src, HelmRepoCreds: []*v1alpha1.RepoCreds{
{URL: "example.com", Username: "test", Password: "test", EnableOCI: true},
@ -3220,7 +3220,7 @@ func TestGetHelmRepos_OCIDependenciesWithHelmRepo(t *testing.T) {
assert.Equal(t, "example.com/myrepo", helmRepos[0].Repo)
}
func TestGetHelmRepos_OCIDependenciesWithRepo(t *testing.T) {
func TestGetHelmRepos_OCIHelmDependenciesWithRepo(t *testing.T) {
src := v1alpha1.ApplicationSource{Path: "."}
q := apiclient.ManifestRequest{Repos: []*v1alpha1.Repository{{Repo: "example.com", Username: "test", Password: "test", EnableOCI: true}}, ApplicationSource: &src, HelmRepoCreds: []*v1alpha1.RepoCreds{}}
@ -3233,6 +3233,34 @@ func TestGetHelmRepos_OCIDependenciesWithRepo(t *testing.T) {
assert.Equal(t, "example.com/myrepo", helmRepos[0].Repo)
}
func TestGetHelmRepos_OCIDependenciesWithHelmRepo(t *testing.T) {
src := v1alpha1.ApplicationSource{Path: "."}
q := apiclient.ManifestRequest{Repos: []*v1alpha1.Repository{}, ApplicationSource: &src, HelmRepoCreds: []*v1alpha1.RepoCreds{
{URL: "oci://example.com", Username: "test", Password: "test", Type: "oci"},
}}
helmRepos, err := getHelmRepos("./testdata/oci-dependencies", q.Repos, q.HelmRepoCreds)
require.NoError(t, err)
assert.Len(t, helmRepos, 1)
assert.Equal(t, "test", helmRepos[0].GetUsername())
assert.True(t, helmRepos[0].EnableOci)
assert.Equal(t, "example.com/myrepo", helmRepos[0].Repo)
}
func TestGetHelmRepos_OCIDependenciesWithRepo(t *testing.T) {
src := v1alpha1.ApplicationSource{Path: "."}
q := apiclient.ManifestRequest{Repos: []*v1alpha1.Repository{{Repo: "oci://example.com", Username: "test", Password: "test", Type: "oci"}}, ApplicationSource: &src, HelmRepoCreds: []*v1alpha1.RepoCreds{}}
helmRepos, err := getHelmRepos("./testdata/oci-dependencies", q.Repos, q.HelmRepoCreds)
require.NoError(t, err)
assert.Len(t, helmRepos, 1)
assert.Equal(t, "test", helmRepos[0].GetUsername())
assert.True(t, helmRepos[0].EnableOci)
assert.Equal(t, "example.com/myrepo", helmRepos[0].Repo)
}
func TestGetHelmRepo_NamedRepos(t *testing.T) {
src := v1alpha1.ApplicationSource{Path: "."}
q := apiclient.ManifestRequest{Repo: &v1alpha1.Repository{}, ApplicationSource: &src, Repos: []*v1alpha1.Repository{{

View file

@ -7,6 +7,7 @@ import (
"fmt"
"math"
"reflect"
"slices"
"sort"
"strconv"
"strings"
@ -409,6 +410,8 @@ func (s *Server) queryRepoServer(ctx context.Context, proj *v1alpha1.AppProject,
client apiclient.RepoServerServiceClient,
helmRepos []*v1alpha1.Repository,
helmCreds []*v1alpha1.RepoCreds,
ociRepos []*v1alpha1.Repository,
ociCreds []*v1alpha1.RepoCreds,
helmOptions *v1alpha1.HelmOptions,
enabledSourceTypes map[string]bool,
) error,
@ -444,7 +447,24 @@ func (s *Server) queryRepoServer(ctx context.Context, proj *v1alpha1.AppProject,
if err != nil {
return fmt.Errorf("error getting settings enabled source types: %w", err)
}
return action(client, permittedHelmRepos, permittedHelmCredentials, helmOptions, enabledSourceTypes)
ociRepos, err := s.db.ListOCIRepositories(context.Background())
if err != nil {
return fmt.Errorf("failed to list OCI repositories: %w", err)
}
permittedOCIRepos, err := argo.GetPermittedRepos(proj, ociRepos)
if err != nil {
return fmt.Errorf("failed to get permitted OCI repositories for project %q: %w", proj.Name, err)
}
ociRepositoryCredentials, err := s.db.GetAllOCIRepositoryCredentials(context.Background())
if err != nil {
return fmt.Errorf("failed to get OCI credentials: %w", err)
}
permittedOCICredentials, err := argo.GetPermittedReposCredentials(proj, ociRepositoryCredentials)
if err != nil {
return fmt.Errorf("failed to get permitted OCI credentials for project %q: %w", proj.Name, err)
}
return action(client, permittedHelmRepos, permittedHelmCredentials, permittedOCIRepos, permittedOCICredentials, helmOptions, enabledSourceTypes)
}
// GetManifests returns application manifests
@ -463,7 +483,7 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
manifestInfos := make([]*apiclient.ManifestResponse, 0)
err = s.queryRepoServer(ctx, proj, func(
client apiclient.RepoServerServiceClient, helmRepos []*v1alpha1.Repository, helmCreds []*v1alpha1.RepoCreds, helmOptions *v1alpha1.HelmOptions, enableGenerateManifests map[string]bool,
client apiclient.RepoServerServiceClient, helmRepos []*v1alpha1.Repository, helmCreds []*v1alpha1.RepoCreds, ociRepos []*v1alpha1.Repository, ociCreds []*v1alpha1.RepoCreds, helmOptions *v1alpha1.HelmOptions, enableGenerateManifests map[string]bool,
) error {
appInstanceLabelKey, err := s.settingsMgr.GetAppInstanceLabelKey()
if err != nil {
@ -534,6 +554,18 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
return fmt.Errorf("error getting trackingMethod from settings: %w", err)
}
repos := helmRepos
helmRepoCreds := helmCreds
// If the source is OCI, there is a potential for an OCI image to be a Helm chart and that said chart in
// turn would have OCI dependencies. To ensure that those dependencies can be resolved, add them to the repos
// list.
if source.IsOCI() {
repos = slices.Clone(helmRepos)
helmRepoCreds = slices.Clone(helmCreds)
repos = append(repos, ociRepos...)
helmRepoCreds = append(helmRepoCreds, ociCreds...)
}
manifestInfo, err := client.GenerateManifest(ctx, &apiclient.ManifestRequest{
Repo: repo,
Revision: source.TargetRevision,
@ -541,11 +573,11 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
AppName: a.InstanceName(s.ns),
Namespace: a.Spec.Destination.Namespace,
ApplicationSource: &source,
Repos: helmRepos,
Repos: repos,
KustomizeOptions: kustomizeOptions,
KubeVersion: serverVersion,
ApiVersions: argo.APIResourcesToStrings(apiResources, true),
HelmRepoCreds: helmCreds,
HelmRepoCreds: helmRepoCreds,
HelmOptions: helmOptions,
TrackingMethod: trackingMethod,
EnabledSourceTypes: enableGenerateManifests,
@ -611,7 +643,7 @@ func (s *Server) GetManifestsWithFiles(stream application.ApplicationService_Get
var manifestInfo *apiclient.ManifestResponse
err = s.queryRepoServer(ctx, proj, func(
client apiclient.RepoServerServiceClient, helmRepos []*v1alpha1.Repository, helmCreds []*v1alpha1.RepoCreds, helmOptions *v1alpha1.HelmOptions, enableGenerateManifests map[string]bool,
client apiclient.RepoServerServiceClient, helmRepos []*v1alpha1.Repository, helmCreds []*v1alpha1.RepoCreds, _ []*v1alpha1.Repository, _ []*v1alpha1.RepoCreds, helmOptions *v1alpha1.HelmOptions, enableGenerateManifests map[string]bool,
) error {
appInstanceLabelKey, err := s.settingsMgr.GetAppInstanceLabelKey()
if err != nil {
@ -775,6 +807,8 @@ func (s *Server) Get(ctx context.Context, q *application.ApplicationQuery) (*v1a
client apiclient.RepoServerServiceClient,
helmRepos []*v1alpha1.Repository,
_ []*v1alpha1.RepoCreds,
_ []*v1alpha1.Repository,
_ []*v1alpha1.RepoCreds,
helmOptions *v1alpha1.HelmOptions,
enabledSourceTypes map[string]bool,
) error {
@ -1607,6 +1641,34 @@ func (s *Server) RevisionChartDetails(ctx context.Context, q *application.Revisi
})
}
func (s *Server) GetOCIMetadata(ctx context.Context, q *application.RevisionMetadataQuery) (*v1alpha1.OCIMetadata, error) {
a, proj, err := s.getApplicationEnforceRBACInformer(ctx, rbac.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
if err != nil {
return nil, err
}
source, err := getAppSourceBySourceIndexAndVersionId(a, q.SourceIndex, q.VersionId)
if err != nil {
return nil, fmt.Errorf("error getting app source by source index and version ID: %w", err)
}
repo, err := s.db.GetRepository(ctx, source.RepoURL, proj.Name)
if err != nil {
return nil, fmt.Errorf("error getting repository by URL: %w", err)
}
conn, repoClient, err := s.repoClientset.NewRepoServerClient()
if err != nil {
return nil, fmt.Errorf("error creating repo server client: %w", err)
}
defer utilio.Close(conn)
return repoClient.GetOCIMetadata(ctx, &apiclient.RepoServerRevisionChartDetailsRequest{
Repo: repo,
Name: source.Chart,
Revision: q.GetRevision(),
})
}
// getAppSourceBySourceIndexAndVersionId returns the source for a specific source index and version ID. Source index and
// version ID are optional. If the source index is not specified, it defaults to 0. If the version ID is not specified,
// we use the source(s) currently configured for the app. If the version ID is specified, we find the source for that

View file

@ -367,6 +367,11 @@ service ApplicationService {
option (google.api.http).get = "/api/v1/applications/{name}/revisions/{revision}/chartdetails";
}
// Get the chart metadata (description, maintainers, home) for a specific revision of the application
rpc GetOCIMetadata (RevisionMetadataQuery) returns (github.com.argoproj.argo_cd.v3.pkg.apis.application.v1alpha1.OCIMetadata) {
option (google.api.http).get = "/api/v1/applications/{name}/revisions/{revision}/ocimetadata";
}
// GetManifests returns application manifests
rpc GetManifests (ApplicationManifestQuery) returns (repository.ManifestResponse) {
option (google.api.http).get = "/api/v1/applications/{name}/manifests";

View file

@ -238,6 +238,27 @@ func (s *Server) prepareRepoList(ctx context.Context, resourceType string, repos
return items, nil
}
func (s *Server) ListOCITags(ctx context.Context, q *repositorypkg.RepoQuery) (*apiclient.Refs, error) {
repo, err := s.getRepo(ctx, q.Repo, q.GetAppProject())
if err != nil {
return nil, err
}
if err := s.enf.EnforceErr(ctx.Value("claims"), rbac.ResourceRepositories, rbac.ActionGet, createRBACObject(repo.Project, repo.Repo)); err != nil {
return nil, err
}
conn, repoClient, err := s.repoClientset.NewRepoServerClient()
if err != nil {
return nil, err
}
defer utilio.Close(conn)
return repoClient.ListOCITags(ctx, &apiclient.ListRefsRequest{
Repo: repo,
})
}
func (s *Server) ListRefs(ctx context.Context, q *repositorypkg.RepoQuery) (*apiclient.Refs, error) {
repo, err := s.getRepo(ctx, q.Repo, q.GetAppProject())
if err != nil {
@ -683,6 +704,7 @@ func (s *Server) ValidateAccess(ctx context.Context, q *repositorypkg.RepoAccess
GitHubAppEnterpriseBaseURL: q.GithubAppEnterpriseBaseUrl,
Proxy: q.Proxy,
GCPServiceAccountKey: q.GcpServiceAccountKey,
InsecureOCIForceHttp: q.InsecureOciForceHttp,
}
// If repo does not have credentials, check if there are credentials stored

View file

@ -93,6 +93,8 @@ message RepoAccessQuery {
bool useAzureWorkloadIdentity = 20;
// BearerToken contains the bearer token used for Git auth at the repo server
string bearerToken = 21;
// Whether https should be disabled for an OCI repo
bool insecureOciForceHttp = 22;
}
message RepoResponse {}
@ -144,6 +146,10 @@ service RepositoryService {
option (google.api.http).get = "/api/v1/repositories/{repo}/refs";
}
rpc ListOCITags(RepoQuery) returns (Refs) {
option (google.api.http).get = "/api/v1/repositories/{repo}/oci-tags";
}
// ListApps returns list of apps in the repo
rpc ListApps(RepoAppsQuery) returns (RepoAppsResponse) {
option (google.api.http).get = "/api/v1/repositories/{repo}/apps";

View file

@ -243,7 +243,13 @@ func (a *Actions) prepareCreateAppArgs(args []string) []string {
if a.context.drySourceRevision != "" || a.context.drySourcePath != "" || a.context.syncSourcePath != "" || a.context.syncSourceBranch != "" || a.context.hydrateToBranch != "" {
args = append(args, "--dry-source-repo", fixture.RepoURL(a.context.repoURLType))
} else {
args = append(args, "--repo", fixture.RepoURL(a.context.repoURLType))
var repo string
if a.context.ociRegistryPath != "" && a.context.repoURLType == fixture.RepoURLTypeOCI {
repo = fmt.Sprintf("%s/%s", a.context.ociRegistry, a.context.ociRegistryPath)
} else {
repo = fixture.RepoURL(a.context.repoURLType)
}
args = append(args, "--repo", repo)
}
if a.context.destName != "" && a.context.isDestServerInferred && !slices.Contains(args, "--dest-server") {

View file

@ -17,10 +17,12 @@ import (
// Context implements the "given" part of given/when/then
type Context struct {
t *testing.T
path string
chart string
repoURLType fixture.RepoURLType
t *testing.T
path string
chart string
ociRegistry string
ociRegistryPath string
repoURLType fixture.RepoURLType
// seconds
timeout int
name string
@ -187,11 +189,26 @@ func (c *Context) HelmOCIRepoAdded(name string) *Context {
return c
}
func (c *Context) PushImageToOCIRegistry(pathName, tag string) *Context {
repos.PushImageToOCIRegistry(c.t, pathName, tag)
return c
}
func (c *Context) PushImageToAuthenticatedOCIRegistry(pathName, tag string) *Context {
repos.PushImageToAuthenticatedOCIRegistry(c.t, pathName, tag)
return c
}
func (c *Context) PushChartToOCIRegistry(chartPathName, chartName, chartVersion string) *Context {
repos.PushChartToOCIRegistry(c.t, chartPathName, chartName, chartVersion)
return c
}
func (c *Context) PushChartToAuthenticatedOCIRegistry(chartPathName, chartName, chartVersion string) *Context {
repos.PushChartToAuthenticatedOCIRegistry(c.t, chartPathName, chartName, chartVersion)
return c
}
func (c *Context) HTTPSCredentialsUserPassAdded() *Context {
repos.AddHTTPSCredentialsUserPass(c.t)
return c
@ -217,6 +234,21 @@ func (c *Context) SSHCredentialsAdded() *Context {
return c
}
func (c *Context) OCIRepoAdded(name, imagePath string) *Context {
repos.AddOCIRepo(c.t, name, imagePath)
return c
}
func (c *Context) AuthenticatedOCIRepoAdded(name, imagePath string) *Context {
repos.AddAuthenticatedOCIRepo(c.t, name, imagePath)
return c
}
func (c *Context) OCIRegistry(registry string) *Context {
c.ociRegistry = registry
return c
}
func (c *Context) ProjectSpec(spec v1alpha1.AppProjectSpec) *Context {
c.t.Helper()
require.NoError(c.t, fixture.SetProjectSpec(c.project, spec))
@ -282,6 +314,11 @@ func (c *Context) Chart(chart string) *Context {
return c
}
func (c *Context) OCIRegistryPath(ociPath string) *Context {
c.ociRegistryPath = ociPath
return c
}
func (c *Context) Revision(revision string) *Context {
c.revision = revision
return c

View file

@ -124,6 +124,7 @@ const (
RepoURLTypeHelm = "helm"
RepoURLTypeHelmParent = "helm-par"
RepoURLTypeHelmOCI = "helm-oci"
RepoURLTypeOCI = "oci"
GitUsername = "admin"
GitPassword = "password"
GitBearerToken = "test"
@ -131,6 +132,10 @@ const (
GithubAppInstallationID = "7893789433789"
GpgGoodKeyID = "D56C4FCA57A46444"
HelmOCIRegistryURL = "localhost:5000/myrepo"
HelmAuthenticatedOCIRegistryURL = "localhost:5001/myrepo"
OCIRegistryURL = "oci://localhost:5000/my-oci-repo"
OCIHostURL = "oci://localhost:5000"
AuthenticatedOCIHostURL = "oci://localhost:5001"
)
// TestNamespace returns the namespace where Argo CD E2E test instance will be
@ -345,6 +350,8 @@ func RepoURL(urlType RepoURLType) string {
// When Helm Repo has sub repos, this is the parent repo URL
case RepoURLTypeHelmParent:
return GetEnvWithDefault(EnvRepoURLTypeHelm, "https://localhost:9444/argo-e2e/testdata.git/helm-repo")
case RepoURLTypeOCI:
return OCIRegistryURL
case RepoURLTypeHelmOCI:
return HelmOCIRegistryURL
default:

View file

@ -4,6 +4,7 @@ import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/require"
@ -95,6 +96,34 @@ func AddHelmRepo(t *testing.T, name string) {
errors.NewHandler(t).FailOnErr(fixture.RunCli(args...))
}
func AddOCIRepo(t *testing.T, name, imagePath string) {
t.Helper()
args := []string{
"repo",
"add",
fmt.Sprintf("%s/%s", fixture.OCIHostURL, imagePath),
"--type", "oci",
"--name", name,
"--insecure-oci-force-http",
}
errors.NewHandler(t).FailOnErr(fixture.RunCli(args...))
}
func AddAuthenticatedOCIRepo(t *testing.T, name, imagePath string) {
t.Helper()
args := []string{
"repo",
"add",
fmt.Sprintf("%s/%s", fixture.AuthenticatedOCIHostURL, imagePath),
"--username", fixture.GitUsername,
"--password", fixture.GitPassword,
"--type", "oci",
"--name", name,
"--insecure-oci-force-http",
}
errors.NewHandler(t).FailOnErr(fixture.RunCli(args...))
}
func AddHelmOCIRepo(t *testing.T, name string) {
t.Helper()
args := []string{
@ -198,3 +227,74 @@ func PushChartToOCIRegistry(t *testing.T, chartPathName, chartName, chartVersion
"oci://"+fixture.HelmOCIRegistryURL,
))
}
// PushChartToAuthenticatedOCIRegistry adds a helm chart to helm OCI registry
func PushChartToAuthenticatedOCIRegistry(t *testing.T, chartPathName, chartName, chartVersion string) {
t.Helper()
// create empty temp directory to extract chart from the registry
tempDest, err1 := os.MkdirTemp("", "helm")
require.NoError(t, err1)
defer func() { _ = os.RemoveAll(tempDest) }()
chartAbsPath, err2 := filepath.Abs("./testdata/" + chartPathName)
require.NoError(t, err2)
t.Setenv("HELM_EXPERIMENTAL_OCI", "1")
errors.NewHandler(t).FailOnErr(fixture.Run("", "helm", "dependency", "build", chartAbsPath))
errors.NewHandler(t).FailOnErr(fixture.Run("", "helm", "package", chartAbsPath, "--destination", tempDest))
_ = os.RemoveAll(fmt.Sprintf("%s/%s", chartAbsPath, "charts"))
errors.NewHandler(t).FailOnErr(fixture.Run(
"",
"helm",
"registry",
"login",
"--username", fixture.GitUsername,
"--password", fixture.GitPassword,
"localhost:5001",
))
errors.NewHandler(t).FailOnErr(fixture.Run(
"",
"helm",
"push",
fmt.Sprintf("%s/%s-%s.tgz", tempDest, chartName, chartVersion),
"oci://"+fixture.HelmAuthenticatedOCIRegistryURL,
))
errors.NewHandler(t).FailOnErr(fixture.Run(
"",
"helm",
"registry",
"logout",
"localhost:5001",
))
}
// PushImageToOCIRegistry adds a helm chart to helm OCI registry
func PushImageToOCIRegistry(t *testing.T, pathName, tag string) {
t.Helper()
imagePath := "./testdata/" + pathName
errors.NewHandler(t).FailOnErr(fixture.Run(
imagePath,
"oras",
"push",
fmt.Sprintf("%s:%s", fmt.Sprintf("%s/%s", strings.TrimPrefix(fixture.OCIHostURL, "oci://"), pathName), tag),
".",
))
}
// PushImageToAuthenticatedOCIRegistry adds a helm chart to helm OCI registry
func PushImageToAuthenticatedOCIRegistry(t *testing.T, pathName, tag string) {
t.Helper()
imagePath := "./testdata/" + pathName
errors.NewHandler(t).FailOnErr(fixture.Run(
imagePath,
"oras",
"push",
fmt.Sprintf("%s:%s", fmt.Sprintf("%s/%s", strings.TrimPrefix(fixture.AuthenticatedOCIHostURL, "oci://"), pathName), tag),
".",
))
}

78
test/e2e/oci_test.go Normal file
View file

@ -0,0 +1,78 @@
package e2e
import (
"testing"
"github.com/argoproj/gitops-engine/pkg/health"
. "github.com/argoproj/gitops-engine/pkg/sync/common"
. "github.com/argoproj/argo-cd/v3/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/v3/test/e2e/fixture"
. "github.com/argoproj/argo-cd/v3/test/e2e/fixture/app"
)
func TestOCIImage(t *testing.T) {
Given(t).
RepoURLType(fixture.RepoURLTypeOCI).
PushImageToOCIRegistry("guestbook", "1.0.0").
OCIRepoAdded("guestbook", "guestbook").
Revision("1.0.0").
OCIRegistry(fixture.OCIHostURL).
OCIRegistryPath("guestbook").
Path(".").
When().
CreateApp().
Then().
Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
Expect(Success("")).
When().
Sync().
Then().
Expect(Success("")).
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(SyncStatusIs(SyncStatusCodeSynced)).
Expect(HealthIs(health.HealthStatusHealthy))
}
func TestOCIWithOCIHelmRegistryDependencies(t *testing.T) {
Given(t).
RepoURLType(fixture.RepoURLTypeOCI).
PushChartToOCIRegistry("helm-values", "helm-values", "1.0.0").
PushImageToOCIRegistry("helm-oci-with-dependencies", "1.0.0").
OCIRegistry(fixture.OCIHostURL).
OCIRepoAdded("helm-oci-with-dependencies", "helm-oci-with-dependencies").
OCIRegistryPath("helm-oci-with-dependencies").
Revision("1.0.0").
Path(".").
When().
CreateApp().
Then().
When().
Sync().
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(HealthIs(health.HealthStatusHealthy)).
Expect(SyncStatusIs(SyncStatusCodeSynced))
}
func TestOCIWithAuthedOCIHelmRegistryDeps(t *testing.T) {
Given(t).
RepoURLType(fixture.RepoURLTypeOCI).
PushChartToAuthenticatedOCIRegistry("helm-values", "helm-values", "1.0.0").
PushImageToOCIRegistry("helm-oci-authed-with-dependencies", "1.0.0").
OCIRepoAdded("helm-oci-authed-with-dependencies", "helm-oci-authed-with-dependencies").
AuthenticatedOCIRepoAdded("helm-values", "myrepo/helm-values").
OCIRegistry(fixture.OCIHostURL).
OCIRegistryPath("helm-oci-authed-with-dependencies").
Revision("1.0.0").
Path(".").
When().
CreateApp().
Then().
When().
Sync().
Then().
Expect(OperationPhaseIs(OperationSucceeded)).
Expect(HealthIs(health.HealthStatusHealthy)).
Expect(SyncStatusIs(SyncStatusCodeSynced))
}

View file

@ -0,0 +1,7 @@
apiVersion: v2
name: helm-oci-with-dependencies
version: 1.0.0
dependencies:
- name: helm-values
repository: "oci://localhost:5001/myrepo"
version: 1.0.0

View file

@ -0,0 +1 @@
admin:$2y$10$OGusddU7nDipgwoCaOLYee/1GRqniRGHllm0ZEkyFQ1E4D8j064MG

View file

@ -0,0 +1,5 @@
#!/usr/bin/env bash
export HELM_EXPERIMENTAL_OCI=1
docker run -p 5001:5000 --rm --name authed-registry -v $(pwd)/test/fixture/testrepos/.oci-htpasswd:/etc/docker/registry/auth.htpasswd \
-e REGISTRY_AUTH="{htpasswd: {realm: localhost, path: /etc/docker/registry/auth.htpasswd}}" \
registry

View file

@ -88,9 +88,9 @@ const AutoSyncFormField = ReactFormField((props: {fieldApi: FieldApi; className:
function normalizeAppSource(app: models.Application, type: string): boolean {
const source = getAppDefaultSource(app);
const repoType = (source.hasOwnProperty('chart') && 'helm') || 'git';
const repoType = source.repoURL.startsWith('oci://') ? 'oci' : (source.hasOwnProperty('chart') && 'helm') || 'git';
if (repoType !== type) {
if (type === 'git') {
if (type === 'git' || type === 'oci') {
source.path = source.chart;
delete source.chart;
source.targetRevision = 'HEAD';
@ -306,7 +306,9 @@ export const ApplicationCreatePanel = (props: {
</div>
);
const repoType = (api.getFormState().values.spec.source.hasOwnProperty('chart') && 'helm') || 'git';
const repoType = api.getFormState().values.spec.source.repoURL.startsWith('oci://')
? 'oci'
: (api.getFormState().values.spec.source.hasOwnProperty('chart') && 'helm') || 'git';
const sourcePanel = () => (
<div className='white-box'>
<p>SOURCE</p>
@ -338,7 +340,7 @@ export const ApplicationCreatePanel = (props: {
</p>
)}
qeId='application-create-dropdown-source-repository'
items={['git', 'helm'].map((type: 'git' | 'helm') => ({
items={['git', 'helm', 'oci'].map((type: 'git' | 'helm' | 'oci') => ({
title: type.toUpperCase(),
action: () => {
if (repoType !== type) {
@ -354,9 +356,9 @@ export const ApplicationCreatePanel = (props: {
</div>
</div>
</div>
{(repoType === 'git' && (
{(repoType === 'oci' && (
<React.Fragment>
<RevisionFormField formApi={api} helpIconTop={'2.5em'} repoURL={app.spec.source.repoURL} />
<RevisionFormField formApi={api} helpIconTop={'2.5em'} repoURL={app.spec.source.repoURL} repoType={repoType} />
<div className='argo-form-row'>
<DataLoader
input={{repoURL: app.spec.source.repoURL, revision: app.spec.source.targetRevision}}
@ -384,46 +386,77 @@ export const ApplicationCreatePanel = (props: {
</DataLoader>
</div>
</React.Fragment>
)) || (
<DataLoader
input={{repoURL: app.spec.source.repoURL}}
load={async src =>
(src.repoURL && services.repos.charts(src.repoURL).catch(() => new Array<models.HelmChart>())) ||
new Array<models.HelmChart>()
}>
{(charts: models.HelmChart[]) => {
const selectedChart = charts.find(chart => chart.name === api.getFormState().values.spec.source.chart);
return (
<div className='row argo-form-row'>
<div className='columns small-10'>
)) ||
(repoType === 'git' && (
<React.Fragment>
<RevisionFormField formApi={api} helpIconTop={'2.5em'} repoURL={app.spec.source.repoURL} repoType={repoType} />
<div className='argo-form-row'>
<DataLoader
input={{repoURL: app.spec.source.repoURL, revision: app.spec.source.targetRevision}}
load={async src =>
(src.repoURL &&
services.repos
.apps(src.repoURL, src.revision, app.metadata.name, app.spec.project)
.then(apps => Array.from(new Set(apps.map(item => item.path))).sort())
.catch(() => new Array<string>())) ||
new Array<string>()
}>
{(apps: string[]) => (
<FormField
formApi={api}
label='Chart'
field='spec.source.chart'
label='Path'
qeId='application-create-field-path'
field='spec.source.path'
component={AutocompleteField}
componentProps={{
items: charts.map(chart => chart.name),
items: apps,
filterSuggestions: true
}}
/>
)}
</DataLoader>
</div>
</React.Fragment>
)) || (
<DataLoader
input={{repoURL: app.spec.source.repoURL}}
load={async src =>
(src.repoURL && services.repos.charts(src.repoURL).catch(() => new Array<models.HelmChart>())) ||
new Array<models.HelmChart>()
}>
{(charts: models.HelmChart[]) => {
const selectedChart = charts.find(chart => chart.name === api.getFormState().values.spec.source.chart);
return (
<div className='row argo-form-row'>
<div className='columns small-10'>
<FormField
formApi={api}
label='Chart'
field='spec.source.chart'
component={AutocompleteField}
componentProps={{
items: charts.map(chart => chart.name),
filterSuggestions: true
}}
/>
</div>
<div className='columns small-2'>
<FormField
formApi={api}
field='spec.source.targetRevision'
component={AutocompleteField}
componentProps={{
items: (selectedChart && selectedChart.versions) || [],
filterSuggestions: true
}}
/>
<RevisionHelpIcon type='helm' />
</div>
</div>
<div className='columns small-2'>
<FormField
formApi={api}
field='spec.source.targetRevision'
component={AutocompleteField}
componentProps={{
items: (selectedChart && selectedChart.versions) || [],
filterSuggestions: true
}}
/>
<RevisionHelpIcon type='helm' />
</div>
</div>
);
}}
</DataLoader>
)}
);
}}
</DataLoader>
)}
</div>
);
const destinationPanel = () => (

View file

@ -1,10 +1,34 @@
import {DataLoader} from 'argo-ui';
import * as React from 'react';
import {Timestamp} from '../../../shared/components/timestamp';
import {ApplicationSource, RevisionMetadata, ChartDetails} from '../../../shared/models';
import {Timestamp} from '../../../shared/components';
import {ApplicationSource, RevisionMetadata, ChartDetails, OCIMetadata} from '../../../shared/models';
import {services} from '../../../shared/services';
export const RevisionMetadataRows = (props: {applicationName: string; applicationNamespace: string; source: ApplicationSource; index: number; versionId: number | null}) => {
if (props?.source?.repoURL?.startsWith('oci://')) {
return (
<DataLoader
input={props}
load={input => services.applications.ociMetadata(input.applicationName, input.applicationNamespace, input.source.targetRevision, input.index, input.versionId)}>
{(m: OCIMetadata) => (
<div>
{m.description && (
<div className='row'>
<div className='columns small-3'>Description:</div>
<div className='columns small-9'>{m.description}</div>
</div>
)}
{m.authors && m.authors.length > 0 && (
<div className='row'>
<div className='columns small-3'>Maintainers:</div>
<div className='columns small-9'>{m.authors}</div>
</div>
)}
</div>
)}
</DataLoader>
);
}
if (props?.source?.chart) {
return (
<DataLoader

View file

@ -25,7 +25,7 @@ import * as AppUtils from '../utils';
import {ApplicationResourceList} from './application-resource-list';
import {Filters, FiltersProps} from './application-resource-filter';
import {getAppDefaultSource, getAppCurrentVersion, urlPattern} from '../utils';
import {ChartDetails, ResourceStatus} from '../../../shared/models';
import {ChartDetails, OCIMetadata, ResourceStatus} from '../../../shared/models';
import {ApplicationsDetailsAppDropdown} from './application-details-app-dropdown';
import {useSidebarTarget} from '../../../sidebar/sidebar';
@ -226,6 +226,82 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{app
)
);
const getContentForOci = (
aRevision: string,
aSourceIndex: number | null,
aVersionId: number | null,
indx: number,
aSource: models.ApplicationSource,
sourceHeader?: JSX.Element
) => {
const showChartNonMetadataInfo = (aRevision: string, aRepoUrl: string) => {
return (
<>
<div className='row white-box__details-row'>
<div className='columns small-3'>Revision:</div>
<div className='columns small-9'>{aRevision}</div>
</div>
<div className='row white-box__details-row'>
<div className='columns small-3'>OCI Image:</div>
<div className='columns small-9'>{aRepoUrl}</div>
</div>
</>
);
};
return (
<DataLoader
key={indx}
input={application}
load={input => services.applications.ociMetadata(input.metadata.name, input.metadata.namespace, aRevision, aSourceIndex, aVersionId)}>
{(m: OCIMetadata) => {
return m ? (
<div className='white-box' style={{marginTop: '1.5em'}}>
{sourceHeader && sourceHeader}
<div className='white-box__details'>
{showChartNonMetadataInfo(aRevision, aSource.repoURL)}
{m.description && (
<div className='row white-box__details-row'>
<div className='columns small-3'>Description:</div>
<div className='columns small-9'>{m.description}</div>
</div>
)}
{m.authors && m.authors.length > 0 && (
<div className='row white-box__details-row'>
<div className='columns small-3'>Maintainers:</div>
<div className='columns small-9'>{m.authors}</div>
</div>
)}
</div>
</div>
) : (
<div key={indx} className='white-box' style={{marginTop: '1.5em'}}>
<div>Source {indx + 1}</div>
<div className='white-box__details'>
{showChartNonMetadataInfo(aRevision, aSource.repoURL)}
<div className='row white-box__details-row'>
<div className='columns small-3'>Helm Chart:</div>
<div className='columns small-9'>
{aSource.chart}&nbsp;
{
<a
title={sources[indx].chart}
onClick={e => {
e.stopPropagation();
window.open(aSource.repoURL);
}}>
<i className='fa fa-external-link-alt' />
</a>
}
</div>
</div>
</div>
</div>
);
}}
</DataLoader>
);
};
const getContentForChart = (
aRevision: string,
aSourceIndex: number | null,
@ -398,7 +474,9 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{app
const sources: models.ApplicationSource[] = application.spec.sources;
if (sources?.length > 0 && revisions) {
revisions.forEach((rev, indx) => {
if (sources[indx].chart) {
if (sources[indx].repoURL.startsWith('oci://')) {
cont.push(getContentForOci(rev, indx, getAppCurrentVersion(application), indx, sources[indx], <div>Source {indx + 1}</div>));
} else if (sources[indx].chart) {
cont.push(getContentForChart(rev, indx, getAppCurrentVersion(application), indx, sources[indx], <div>Source {indx + 1}</div>));
} else {
cont.push(getContentForNonChart(rev, indx, getAppCurrentVersion(application), indx, sources[indx], <div>Source {indx + 1}</div>));
@ -406,7 +484,9 @@ export class ApplicationDetails extends React.Component<RouteComponentProps<{app
});
return <>{cont}</>;
} else if (application.spec.source) {
if (source.chart) {
if (source.repoURL.startsWith('oci://')) {
cont.push(getContentForOci(revision, null, getAppCurrentVersion(application), 0, source));
} else if (source.chart) {
cont.push(getContentForChart(revision, null, null, 0, source));
} else {
cont.push(getContentForNonChart(revision, null, getAppCurrentVersion(application), 0, source));

View file

@ -23,9 +23,10 @@ const appTypes = new Array<{field: string; type: models.AppSourceType}>(
function normalizeAppSource(app: models.Application, type: string): boolean {
const source = app.spec.source;
// eslint-disable-next-line no-prototype-builtins
const repoType = (source.hasOwnProperty('chart') && 'helm') || 'git';
const repoType = source.repoURL.startsWith('oci://') ? 'oci' : (source.hasOwnProperty('chart') && 'helm') || 'git';
if (repoType !== type) {
if (type === 'git') {
if (type === 'git' || type === 'oci') {
source.path = source.chart;
delete source.chart;
source.targetRevision = 'HEAD';
@ -157,8 +158,9 @@ export const SourcePanel = (props: {
}}
getApi={props.getFormApi}>
{api => {
// eslint-disable-next-line no-prototype-builtins
const repoType = (api.getFormState().values.spec?.source?.hasOwnProperty('chart') && 'helm') || 'git';
const repoType = api.getFormState().values.spec?.source?.repoURL.startsWith('oci://')
? 'oci'
: (api.getFormState().values.spec?.source?.chart && 'helm') || 'git';
const repoInfo = reposInfo.find(info => info.repo === api.getFormState().values.spec?.source?.repoURL);
if (repoInfo) {
normalizeAppSource(appInEdit, repoInfo.type || 'git');
@ -189,7 +191,7 @@ export const SourcePanel = (props: {
{repoType.toUpperCase()} <i className='fa fa-caret-down' />
</p>
)}
items={['git', 'helm'].map((type: 'git' | 'helm') => ({
items={['git', 'helm', 'oci'].map((type: 'git' | 'helm' | 'oci') => ({
title: type.toUpperCase(),
action: () => {
if (repoType !== type) {
@ -210,9 +212,14 @@ export const SourcePanel = (props: {
<FormField formApi={api} label='Name' field={'spec.source.name'} component={Text}></FormField>
</div>
</div>
{(repoType === 'git' && (
{(repoType === 'oci' && (
<React.Fragment>
<RevisionFormField formApi={api} helpIconTop={'2.5em'} repoURL={api.getFormState().values.spec?.source?.repoURL} />
<RevisionFormField
formApi={api}
helpIconTop={'2.5em'}
repoURL={api.getFormState().values.spec?.source?.repoURL}
repoType={repoType}
/>
<div className='argo-form-row'>
<DataLoader
input={{
@ -245,45 +252,86 @@ export const SourcePanel = (props: {
<FormField formApi={api} label='Ref' field={'spec.source.ref'} component={Text}></FormField>
</div>
</React.Fragment>
)) || (
<DataLoader
input={{repoURL: api.getFormState().values.spec.source.repoURL}}
load={async src =>
(src.repoURL && services.repos.charts(src.repoURL).catch(() => new Array<models.HelmChart>())) ||
new Array<models.HelmChart>()
}>
{(charts: models.HelmChart[]) => {
const selectedChart = charts.find(chart => chart.name === api.getFormState().values.spec?.source?.chart);
return (
<div className='row argo-form-row'>
<div className='columns small-10'>
)) ||
(repoType === 'git' && (
<React.Fragment>
<RevisionFormField
formApi={api}
helpIconTop={'2.5em'}
repoURL={api.getFormState().values.spec?.source?.repoURL}
repoType={repoType}
/>
<div className='argo-form-row'>
<DataLoader
input={{
repoURL: api.getFormState().values.spec?.source?.repoURL,
revision: api.getFormState().values.spec?.source?.targetRevision
}}
load={async src =>
(src.repoURL &&
(await services.repos
.apps(src.repoURL, src.revision, appInEdit.metadata.name, props.appCurrent.spec.project)
.then(apps => Array.from(new Set(apps.map(item => item.path))).sort())
.catch(() => new Array<string>()))) ||
new Array<string>()
}>
{(apps: string[]) => (
<FormField
formApi={api}
label='Chart'
field='spec.source.chart'
label='Path'
field='spec.source.path'
component={AutocompleteField}
componentProps={{
items: charts.map(chart => chart.name),
items: apps,
filterSuggestions: true
}}
/>
)}
</DataLoader>
</div>
<div className='argo-form-row'>
<FormField formApi={api} label='Ref' field={'spec.source.ref'} component={Text}></FormField>
</div>
</React.Fragment>
)) || (
<DataLoader
input={{repoURL: api.getFormState().values.spec.source.repoURL}}
load={async src =>
(src.repoURL && services.repos.charts(src.repoURL).catch(() => new Array<models.HelmChart>())) ||
new Array<models.HelmChart>()
}>
{(charts: models.HelmChart[]) => {
const selectedChart = charts.find(chart => chart.name === api.getFormState().values.spec?.source?.chart);
return (
<div className='row argo-form-row'>
<div className='columns small-10'>
<FormField
formApi={api}
label='Chart'
field='spec.source.chart'
component={AutocompleteField}
componentProps={{
items: charts.map(chart => chart.name),
filterSuggestions: true
}}
/>
</div>
<div className='columns small-2'>
<FormField
formApi={api}
field='spec.source.targetRevision'
component={AutocompleteField}
componentProps={{
items: (selectedChart && selectedChart.versions) || []
}}
/>
<RevisionHelpIcon type='helm' />
</div>
</div>
<div className='columns small-2'>
<FormField
formApi={api}
field='spec.source.targetRevision'
component={AutocompleteField}
componentProps={{
items: (selectedChart && selectedChart.versions) || []
}}
/>
<RevisionHelpIcon type='helm' />
</div>
</div>
);
}}
</DataLoader>
)}
);
}}
</DataLoader>
)}
</div>
);

View file

@ -152,7 +152,7 @@ export const ApplicationStatusPanel = ({application, showDiff, showOperation, sh
const errors = cntByCategory.get('error');
const source = getAppDefaultSource(application);
const hasMultipleSources = application.spec.sources?.length > 0;
const revisionType = source?.repoURL?.startsWith('oci://') ? 'oci' : source?.chart ? 'helm' : 'git';
return (
<div className='application-status-panel row'>
<div className='application-status-panel__item'>
@ -233,7 +233,7 @@ export const ApplicationStatusPanel = ({application, showDiff, showOperation, sh
<RevisionMetadataPanel
appName={application.metadata.name}
appNamespace={application.metadata.namespace}
type={source?.chart && 'helm'}
type={revisionType}
revision={revision}
versionId={utils.getAppCurrentVersion(application)}
/>
@ -276,7 +276,7 @@ export const ApplicationStatusPanel = ({application, showDiff, showOperation, sh
<RevisionMetadataPanel
appName={application.metadata.name}
appNamespace={application.metadata.namespace}
type={source?.chart && 'helm'}
type={revisionType}
revision={operationStateRevision}
versionId={utils.getAppCurrentVersion(application)}
/>

View file

@ -4,9 +4,52 @@ import {Timestamp} from '../../../shared/components/timestamp';
import {services} from '../../../shared/services';
export const RevisionMetadataPanel = (props: {appName: string; appNamespace: string; type: string; revision: string; versionId: number}) => {
if (props.type === 'helm') {
if (props.type === 'helm' || props.type === 'oci') {
return <React.Fragment />;
}
if (props.type === 'oci') {
return (
<DataLoader load={() => services.applications.ociMetadata(props.appName, props.appNamespace, props.revision, 0, props.versionId)} errorRenderer={() => <div />}>
{m => (
<Tooltip
popperOptions={{
modifiers: {
preventOverflow: {
enabled: false
},
hide: {
enabled: false
},
flip: {
enabled: false
}
}
}}
content={
<span>
{m.authors && <React.Fragment>Authored by {m.authors}</React.Fragment>}
<br />
{m.createdAt && <Timestamp date={m.createdAt} />}
<br />
<br />
{m.description}
</span>
}
placement='bottom'
allowHTML={true}>
<div className='application-status-panel__item-name'>
{m.authors && (
<div className='application-status-panel__item__row'>
<div>Author:</div>
<div>{m.authors}</div>
</div>
)}
</div>
</Tooltip>
)}
</DataLoader>
);
}
return (
<DataLoader
key={props.revision}

View file

@ -67,6 +67,8 @@ export const ApplicationSummary = (props: ApplicationSummaryProps) => {
const hasMultipleSources = app.spec.sources && app.spec.sources.length > 0;
const repoType = source.repoURL.startsWith('oci://') ? 'oci' : (source.hasOwnProperty('chart') && 'helm') || 'git';
const attributes = [
{
title: 'PROJECT',
@ -235,7 +237,7 @@ export const ApplicationSummary = (props: ApplicationSummaryProps) => {
hasMultipleSources ? (
helpTip('TARGET REVISION is not editable for applications with multiple sources. You can edit them in the "Manifest" tab.')
) : (
<RevisionFormField helpIconTop={'0'} hideLabel={true} formApi={formApi} repoURL={source.repoURL} />
<RevisionFormField helpIconTop={'0'} hideLabel={true} formApi={formApi} repoURL={source.repoURL} repoType={repoType} />
)
},
{

View file

@ -11,6 +11,7 @@ interface RevisionFormFieldProps {
hideLabel?: boolean;
repoURL: string;
fieldValue?: string;
repoType?: string;
}
export class RevisionFormField extends React.PureComponent<RevisionFormFieldProps, {filterType: string}> {
@ -34,7 +35,12 @@ export class RevisionFormField extends React.PureComponent<RevisionFormFieldProp
<DataLoader
input={{repoURL: this.props.repoURL, filterType: selectedFilter}}
load={async (src: any): Promise<string[]> => {
if (src.repoURL) {
if (this.props.repoType === 'oci' && src.repoURL) {
return services.repos
.ociTags(src.repoURL)
.then(revisionsRes => ['HEAD'].concat(revisionsRes.tags || []))
.catch(() => []);
} else if (src.repoURL) {
return services.repos
.revisions(src.repoURL)
.then(revisionsRes =>
@ -63,20 +69,22 @@ export class RevisionFormField extends React.PureComponent<RevisionFormFieldProp
</React.Fragment>
</div>
<div style={{paddingTop: extraPadding}} className='columns small-2'>
<DropDownMenu
anchor={() => (
<p>
{this.state.filterType} <i className='fa fa-caret-down' />
</p>
)}
qeId='application-create-dropdown-revision'
items={['Branches', 'Tags'].map((type: 'Branches' | 'Tags') => ({
title: type,
action: () => {
this.setFilter(type);
}
}))}
/>
{this.props.repoType !== 'oci' && (
<DropDownMenu
anchor={() => (
<p>
{this.state.filterType} <i className='fa fa-caret-down' />
</p>
)}
qeId='application-create-dropdown-revision'
items={['Branches', 'Tags'].map((type: 'Branches' | 'Tags') => ({
title: type,
action: () => {
this.setFilter(type);
}
}))}
/>
)}
</div>
</div>
);

View file

@ -841,7 +841,12 @@ export function syncStatusMessage(app: appModels.Application) {
if (source.chart) {
message += ' (' + revision + ')';
} else if (revision.length >= 7 && !revision.startsWith(source.targetRevision)) {
message += ' (' + revision.substr(0, 7) + ')';
if (source.repoURL.startsWith('oci://')) {
// Show "sha256: " plus the first 7 actual characters of the digest.
message += ' (' + revision.substring(0, 14) + ')';
} else {
message += ' (' + revision.substring(0, 7) + ')';
}
}
}

View file

@ -95,7 +95,8 @@ export const RepoDetails = (props: {repo: models.Repository; save?: (params: New
project: repo.project || '',
enableOCI: repo.enableOCI || false,
forceHttpBasicAuth: repo.forceHttpBasicAuth || false,
useAzureWorkloadIdentity: repo.useAzureWorkloadIdentity || false
useAzureWorkloadIdentity: repo.useAzureWorkloadIdentity || false,
insecureOCIForceHttp: repo.insecureOCIForceHttp || false
};
return (

View file

@ -43,6 +43,7 @@ export interface NewHTTPSRepoParams {
project?: string;
forceHttpBasicAuth?: boolean;
enableOCI: boolean;
insecureOCIForceHttp: boolean;
// write should be true if saving as a write credential.
write: boolean;
useAzureWorkloadIdentity: boolean;
@ -88,6 +89,7 @@ interface NewSSHRepoCredsParams {
interface NewHTTPSRepoCredsParams {
url: string;
type: string;
username: string;
password: string;
bearerToken: string;
@ -97,6 +99,7 @@ interface NewHTTPSRepoCredsParams {
noProxy: string;
forceHttpBasicAuth: boolean;
enableOCI: boolean;
insecureOCIForceHttp: boolean;
// write should be true if saving as a write credential.
write: boolean;
useAzureWorkloadIdentity: boolean;
@ -220,7 +223,8 @@ export class ReposList extends React.Component<
return {
url:
(!validURLValues.url && 'Repository URL is required') ||
(this.credsTemplate && !this.isHTTPOrHTTPSUrl(validURLValues.url) && !validURLValues.enableOCI && 'Not a valid HTTP/HTTPS URL'),
(this.credsTemplate && !this.isHTTPOrHTTPSUrl(validURLValues.url) && !validURLValues.enableOCI && params.type != 'oci' && 'Not a valid HTTP/HTTPS URL') ||
(this.credsTemplate && !this.isOCIUrl(validURLValues.url) && params.type == 'oci' && 'Not a valid OCI URL'),
name: validURLValues.type === 'helm' && !validURLValues.name && 'Name is required',
username: !validURLValues.username && validURLValues.password && 'Username is required if password is given.',
password: !validURLValues.password && validURLValues.username && 'Password is required if username is given.',
@ -291,7 +295,7 @@ export class ReposList extends React.Component<
return (params: FormValues) => this.connectSSHRepo(params as NewSSHRepoParams);
case ConnectionMethod.HTTPS:
return (params: FormValues) => {
params.url = params.enableOCI ? this.stripProtocol(params.url) : params.url;
params.url = params.enableOCI && params.type != 'oci' ? this.stripProtocol(params.url) : params.url;
return this.connectHTTPSRepo(params as NewHTTPSRepoParams);
};
case ConnectionMethod.GITHUBAPP:
@ -756,7 +760,13 @@ export class ReposList extends React.Component<
<div className='white-box'>
<p>CONNECT REPO USING HTTP/HTTPS</p>
<div className='argo-form-row'>
<FormField formApi={formApi} label='Type' field='type' component={FormSelect} componentProps={{options: ['git', 'helm']}} />
<FormField
formApi={formApi}
label='Type'
field='type'
component={FormSelect}
componentProps={{options: ['git', 'helm', 'oci']}}
/>
</div>
{(formApi.getFormState().values.type === 'helm' || formApi.getFormState().values.type === 'git') && (
<div className='argo-form-row'>
@ -833,7 +843,11 @@ export class ReposList extends React.Component<
<FormField formApi={formApi} label='NoProxy (optional)' field='noProxy' component={Text} />
</div>
<div className='argo-form-row'>
<FormField formApi={formApi} label='Enable OCI' field='enableOCI' component={CheckboxField} />
{formApi.getFormState().values.type !== 'oci' ? (
<FormField formApi={formApi} label='Enable OCI' field='enableOCI' component={CheckboxField} />
) : (
<FormField formApi={formApi} label='Insecure HTTP Only' field='insecureOCIForceHttp' component={CheckboxField} />
)}
</div>
<div className='argo-form-row'>
<FormField
@ -977,6 +991,15 @@ export class ReposList extends React.Component<
}
}
// Whether url is an oci url (simple version)
private isOCIUrl(url: string) {
if (url.match(/^oci:\/\/.*$/gi)) {
return true;
} else {
return false;
}
}
private stripProtocol(url: string) {
return url.replace('https://', '').replace('oci://', '');
}
@ -1040,7 +1063,8 @@ export class ReposList extends React.Component<
// Connect a new repository or create a repository credentials for HTTPS repositories
private async connectHTTPSRepo(params: NewHTTPSRepoParams) {
if (this.credsTemplate) {
this.createHTTPSCreds({
await this.createHTTPSCreds({
type: params.type,
url: params.url,
username: params.username,
password: params.password,
@ -1052,7 +1076,8 @@ export class ReposList extends React.Component<
forceHttpBasicAuth: params.forceHttpBasicAuth,
enableOCI: params.enableOCI,
write: params.write,
useAzureWorkloadIdentity: params.useAzureWorkloadIdentity
useAzureWorkloadIdentity: params.useAzureWorkloadIdentity,
insecureOCIForceHttp: params.insecureOCIForceHttp
});
} else {
this.setState({connecting: true});

View file

@ -97,6 +97,15 @@ export interface RevisionMetadata {
signatureInfo?: string;
}
export interface OCIMetadata {
createdAt: string;
authors: string;
docsUrl: string;
sourceUrl: string;
version: string;
description: string;
}
export interface ChartDetails {
description?: string;
maintainers?: string[];
@ -612,6 +621,7 @@ export interface Repository {
enableLfs?: boolean;
githubAppId?: string;
forceHttpBasicAuth?: boolean;
insecureOCIForceHttp?: boolean;
enableOCI: boolean;
useAzureWorkloadIdentity: boolean;
}

View file

@ -55,6 +55,17 @@ export class ApplicationsService {
.then(res => res.body as models.ApplicationSyncWindowState);
}
public ociMetadata(name: string, appNamespace: string, revision: string, sourceIndex: number, versionId: number): Promise<models.OCIMetadata> {
let r = requests.get(`/applications/${name}/revisions/${revision || 'HEAD'}/ocimetadata`).query({appNamespace});
if (sourceIndex !== null) {
r = r.query({sourceIndex});
}
if (versionId !== null) {
r = r.query({versionId});
}
return r.then(res => res.body as models.OCIMetadata);
}
public revisionMetadata(name: string, appNamespace: string, revision: string, sourceIndex: number | null, versionId: number | null): Promise<models.RevisionMetadata> {
let r = requests.get(`/applications/${name}/revisions/${revision || 'HEAD'}/metadata`).query({appNamespace});
if (sourceIndex !== null) {

View file

@ -18,6 +18,7 @@ export interface HTTPSQuery {
forceHttpBasicAuth?: boolean;
enableOCI: boolean;
useAzureWorkloadIdentity: boolean;
insecureOCIForceHttp: boolean;
}
export interface SSHQuery {
@ -107,7 +108,8 @@ export class RepositoriesService {
project: q.project,
forceHttpBasicAuth: q.forceHttpBasicAuth,
enableOCI: q.enableOCI,
useAzureWorkloadIdentity: q.useAzureWorkloadIdentity
useAzureWorkloadIdentity: q.useAzureWorkloadIdentity,
insecureOCIForceHttp: q.insecureOCIForceHttp
})
.then(res => res.body as models.Repository);
}
@ -131,7 +133,8 @@ export class RepositoriesService {
project: q.project,
forceHttpBasicAuth: q.forceHttpBasicAuth,
enableOCI: q.enableOCI,
useAzureWorkloadIdentity: q.useAzureWorkloadIdentity
useAzureWorkloadIdentity: q.useAzureWorkloadIdentity,
insecureOCIForceHttp: q.insecureOCIForceHttp
})
.then(res => res.body as models.Repository);
}
@ -155,7 +158,8 @@ export class RepositoriesService {
project: q.project,
forceHttpBasicAuth: q.forceHttpBasicAuth,
enableOCI: q.enableOCI,
useAzureWorkloadIdentity: q.useAzureWorkloadIdentity
useAzureWorkloadIdentity: q.useAzureWorkloadIdentity,
insecureOCIForceHttp: q.insecureOCIForceHttp
})
.then(res => res.body as models.Repository);
}
@ -179,7 +183,8 @@ export class RepositoriesService {
project: q.project,
forceHttpBasicAuth: q.forceHttpBasicAuth,
enableOCI: q.enableOCI,
useAzureWorkloadIdentity: q.useAzureWorkloadIdentity
useAzureWorkloadIdentity: q.useAzureWorkloadIdentity,
insecureOCIForceHttp: q.insecureOCIForceHttp
})
.then(res => res.body as models.Repository);
}
@ -310,6 +315,10 @@ export class RepositoriesService {
return requests.get(`/repositories/${encodeURIComponent(repo)}/refs`).then(res => res.body as models.RefsInfo);
}
public async ociTags(repo: string): Promise<models.RefsInfo> {
return requests.get(`/repositories/${encodeURIComponent(repo)}/oci-tags`).then(res => res.body as models.RefsInfo);
}
public apps(repo: string, revision: string, appName: string, appProject: string): Promise<models.AppInfo[]> {
return requests
.get(`/repositories/${encodeURIComponent(repo)}/apps`)

View file

@ -8,8 +8,11 @@ export interface HTTPSCreds {
bearerToken: string;
tlsClientCertData: string;
tlsClientCertKey: string;
type: string;
proxy: string;
noProxy: string;
enableOCI: boolean;
insecureOCIForceHttp: boolean;
}
export interface SSHCreds {

View file

@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"regexp"
"slices"
"sort"
"strings"
"time"
@ -260,11 +261,14 @@ func RefreshApp(appIf v1alpha1.ApplicationInterface, name string, refreshType ar
return nil, err
}
func TestRepoWithKnownType(ctx context.Context, repoClient apiclient.RepoServerServiceClient, repo *argoappv1.Repository, isHelm bool, isHelmOci bool) error {
func TestRepoWithKnownType(ctx context.Context, repoClient apiclient.RepoServerServiceClient, repo *argoappv1.Repository, isHelm bool, isHelmOci bool, isOCI bool) error {
repo = repo.DeepCopy()
if isHelm {
switch {
case isHelm:
repo.Type = "helm"
} else {
case isOCI:
repo.Type = "oci"
case repo.Type != "oci":
repo.Type = "git"
}
repo.EnableOCI = repo.EnableOCI || isHelmOci
@ -322,10 +326,26 @@ func ValidateRepo(
if err != nil {
return nil, fmt.Errorf("error getting helm repo creds: %w", err)
}
ociRepos, err := db.ListOCIRepositories(ctx)
if err != nil {
return nil, fmt.Errorf("failed to list oci repositories: %w", err)
}
permittedOCIRepos, err := GetPermittedRepos(proj, ociRepos)
if err != nil {
return nil, fmt.Errorf("failed to get permitted oci repositories for project %q: %w", proj.Name, err)
}
permittedHelmCredentials, err := GetPermittedReposCredentials(proj, helmRepositoryCredentials)
if err != nil {
return nil, fmt.Errorf("error getting permitted repo creds: %w", err)
}
ociRepositoryCredentials, err := db.GetAllOCIRepositoryCredentials(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get OCI credentials: %w", err)
}
permittedOCICredentials, err := GetPermittedReposCredentials(proj, ociRepositoryCredentials)
if err != nil {
return nil, fmt.Errorf("failed to get permitted OCI credentials for project %q: %w", proj.Name, err)
}
destCluster, err := GetDestinationCluster(ctx, spec.Destination, db)
if err != nil {
@ -360,11 +380,13 @@ func ValidateRepo(
app.Spec.GetSources(),
repoClient,
permittedHelmRepos,
permittedOCIRepos,
helmOptions,
destCluster,
apiGroups,
proj,
permittedHelmCredentials,
permittedOCICredentials,
enabledSourceTypes,
settingsMgr)
if err != nil {
@ -381,11 +403,13 @@ func validateRepo(ctx context.Context,
sources []argoappv1.ApplicationSource,
repoClient apiclient.RepoServerServiceClient,
permittedHelmRepos []*argoappv1.Repository,
permittedOCIRepos []*argoappv1.Repository,
helmOptions *argoappv1.HelmOptions,
cluster *argoappv1.Cluster,
apiGroups []kube.APIResourceInfo,
proj *argoappv1.AppProject,
permittedHelmCredentials []*argoappv1.RepoCreds,
permittedOCICredentials []*argoappv1.RepoCreds,
enabledSourceTypes map[string]bool,
settingsMgr *settings.SettingsManager,
) ([]argoappv1.ApplicationCondition, error) {
@ -397,7 +421,7 @@ func validateRepo(ctx context.Context,
if err != nil {
return nil, err
}
if err := TestRepoWithKnownType(ctx, repoClient, repo, source.IsHelm(), source.IsHelmOci()); err != nil {
if err := TestRepoWithKnownType(ctx, repoClient, repo, source.IsHelm(), source.IsHelmOci(), source.IsOCI()); err != nil {
errMessage = fmt.Sprintf("repositories not accessible: %v: %v", repo.StringForLogging(), err)
}
repoAccessible := false
@ -437,6 +461,7 @@ func validateRepo(ctx context.Context,
ctx,
db,
permittedHelmRepos,
permittedOCIRepos,
helmOptions,
app,
proj,
@ -446,6 +471,7 @@ func validateRepo(ctx context.Context,
cluster.ServerVersion,
APIResourcesToStrings(apiGroups, true),
permittedHelmCredentials,
permittedOCICredentials,
enabledSourceTypes,
settingsMgr,
refSources)...)
@ -715,6 +741,7 @@ func verifyGenerateManifests(
ctx context.Context,
db db.ArgoDB,
helmRepos argoappv1.Repositories,
ociRepos argoappv1.Repositories,
helmOptions *argoappv1.HelmOptions,
app *argoappv1.Application,
proj *argoappv1.AppProject,
@ -723,6 +750,7 @@ func verifyGenerateManifests(
kubeVersion string,
apiVersions []string,
repositoryCredentials []*argoappv1.RepoCreds,
ociRepositoryCredentials []*argoappv1.RepoCreds,
enableGenerateManifests map[string]bool,
settingsMgr *settings.SettingsManager,
refSources argoappv1.RefTargetRevisionMapping,
@ -787,6 +815,18 @@ func verifyGenerateManifests(
verifySignature = true
}
repos := helmRepos
helmRepoCreds := repositoryCredentials
// If the source is OCI, there is a potential for an OCI image to be a Helm chart and that said chart in
// turn would have OCI dependencies. To ensure that those dependencies can be resolved, add them to the repos
// list.
if source.IsOCI() {
repos = slices.Clone(helmRepos)
helmRepoCreds = slices.Clone(repositoryCredentials)
repos = append(repos, ociRepos...)
helmRepoCreds = append(helmRepoCreds, ociRepositoryCredentials...)
}
req := apiclient.ManifestRequest{
Repo: &argoappv1.Repository{
Repo: source.RepoURL,
@ -796,7 +836,7 @@ func verifyGenerateManifests(
NoProxy: repoRes.NoProxy,
},
VerifySignature: verifySignature,
Repos: helmRepos,
Repos: repos,
Revision: source.TargetRevision,
AppName: app.Name,
Namespace: app.Spec.Destination.Namespace,
@ -806,7 +846,7 @@ func verifyGenerateManifests(
KubeVersion: kubeVersion,
ApiVersions: apiVersions,
HelmOptions: helmOptions,
HelmRepoCreds: repositoryCredentials,
HelmRepoCreds: helmRepoCreds,
TrackingMethod: trackingMethod,
EnabledSourceTypes: enableGenerateManifests,
NoRevisionCache: true,

View file

@ -411,8 +411,10 @@ func TestValidateRepo(t *testing.T) {
db.On("GetRepository", t.Context(), app.Spec.Source.RepoURL, "").Return(repo, nil)
db.On("ListHelmRepositories", t.Context()).Return(helmRepos, nil)
db.On("ListOCIRepositories", t.Context()).Return([]*argoappv1.Repository{}, nil)
db.On("GetCluster", t.Context(), app.Spec.Destination.Server).Return(cluster, nil)
db.On("GetAllHelmRepositoryCredentials", t.Context()).Return(nil, nil)
db.On("GetAllOCIRepositoryCredentials", t.Context()).Return([]*argoappv1.RepoCreds{}, nil)
var receivedRequest *apiclient.ManifestRequest

View file

@ -106,10 +106,15 @@ type ArgoDB interface {
RemoveRepoCertificates(ctx context.Context, selector *CertificateListSelector) (*appv1.RepositoryCertificateList, error)
// GetAllHelmRepositoryCredentials gets all repo credentials
GetAllHelmRepositoryCredentials(ctx context.Context) ([]*appv1.RepoCreds, error)
// GetAllOCIRepositoryCredentials gets all repo credentials
GetAllOCIRepositoryCredentials(ctx context.Context) ([]*appv1.RepoCreds, error)
// ListHelmRepositories lists repositories
ListHelmRepositories(ctx context.Context) ([]*appv1.Repository, error)
// ListOCIRepositories lists repositories
ListOCIRepositories(ctx context.Context) ([]*appv1.Repository, error)
// ListConfiguredGPGPublicKeys returns all GPG public key IDs that are configured
ListConfiguredGPGPublicKeys(ctx context.Context) (map[string]*appv1.GnuPGPublicKey, error)
// AddGPGPublicKey adds one or more GPG public keys to the configuration

112
util/db/mocks/ArgoDB.go generated
View file

@ -781,6 +781,62 @@ func (_c *ArgoDB_GetAllHelmRepositoryCredentials_Call) RunAndReturn(run func(ctx
return _c
}
// GetAllOCIRepositoryCredentials provides a mock function for the type ArgoDB
func (_mock *ArgoDB) GetAllOCIRepositoryCredentials(ctx context.Context) ([]*v1alpha1.RepoCreds, error) {
ret := _mock.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for GetAllOCIRepositoryCredentials")
}
var r0 []*v1alpha1.RepoCreds
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context) ([]*v1alpha1.RepoCreds, error)); ok {
return returnFunc(ctx)
}
if returnFunc, ok := ret.Get(0).(func(context.Context) []*v1alpha1.RepoCreds); ok {
r0 = returnFunc(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*v1alpha1.RepoCreds)
}
}
if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = returnFunc(ctx)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ArgoDB_GetAllOCIRepositoryCredentials_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetAllOCIRepositoryCredentials'
type ArgoDB_GetAllOCIRepositoryCredentials_Call struct {
*mock.Call
}
// GetAllOCIRepositoryCredentials is a helper method to define mock.On call
// - ctx
func (_e *ArgoDB_Expecter) GetAllOCIRepositoryCredentials(ctx interface{}) *ArgoDB_GetAllOCIRepositoryCredentials_Call {
return &ArgoDB_GetAllOCIRepositoryCredentials_Call{Call: _e.mock.On("GetAllOCIRepositoryCredentials", ctx)}
}
func (_c *ArgoDB_GetAllOCIRepositoryCredentials_Call) Run(run func(ctx context.Context)) *ArgoDB_GetAllOCIRepositoryCredentials_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *ArgoDB_GetAllOCIRepositoryCredentials_Call) Return(repoCredss []*v1alpha1.RepoCreds, err error) *ArgoDB_GetAllOCIRepositoryCredentials_Call {
_c.Call.Return(repoCredss, err)
return _c
}
func (_c *ArgoDB_GetAllOCIRepositoryCredentials_Call) RunAndReturn(run func(ctx context.Context) ([]*v1alpha1.RepoCreds, error)) *ArgoDB_GetAllOCIRepositoryCredentials_Call {
_c.Call.Return(run)
return _c
}
// GetApplicationControllerReplicas provides a mock function for the type ArgoDB
func (_mock *ArgoDB) GetApplicationControllerReplicas() int {
ret := _mock.Called()
@ -1506,6 +1562,62 @@ func (_c *ArgoDB_ListHelmRepositories_Call) RunAndReturn(run func(ctx context.Co
return _c
}
// ListOCIRepositories provides a mock function for the type ArgoDB
func (_mock *ArgoDB) ListOCIRepositories(ctx context.Context) ([]*v1alpha1.Repository, error) {
ret := _mock.Called(ctx)
if len(ret) == 0 {
panic("no return value specified for ListOCIRepositories")
}
var r0 []*v1alpha1.Repository
var r1 error
if returnFunc, ok := ret.Get(0).(func(context.Context) ([]*v1alpha1.Repository, error)); ok {
return returnFunc(ctx)
}
if returnFunc, ok := ret.Get(0).(func(context.Context) []*v1alpha1.Repository); ok {
r0 = returnFunc(ctx)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*v1alpha1.Repository)
}
}
if returnFunc, ok := ret.Get(1).(func(context.Context) error); ok {
r1 = returnFunc(ctx)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ArgoDB_ListOCIRepositories_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListOCIRepositories'
type ArgoDB_ListOCIRepositories_Call struct {
*mock.Call
}
// ListOCIRepositories is a helper method to define mock.On call
// - ctx
func (_e *ArgoDB_Expecter) ListOCIRepositories(ctx interface{}) *ArgoDB_ListOCIRepositories_Call {
return &ArgoDB_ListOCIRepositories_Call{Call: _e.mock.On("ListOCIRepositories", ctx)}
}
func (_c *ArgoDB_ListOCIRepositories_Call) Run(run func(ctx context.Context)) *ArgoDB_ListOCIRepositories_Call {
_c.Call.Run(func(args mock.Arguments) {
run(args[0].(context.Context))
})
return _c
}
func (_c *ArgoDB_ListOCIRepositories_Call) Return(repositorys []*v1alpha1.Repository, err error) *ArgoDB_ListOCIRepositories_Call {
_c.Call.Return(repositorys, err)
return _c
}
func (_c *ArgoDB_ListOCIRepositories_Call) RunAndReturn(run func(ctx context.Context) ([]*v1alpha1.Repository, error)) *ArgoDB_ListOCIRepositories_Call {
_c.Call.Return(run)
return _c
}
// ListRepoCertificates provides a mock function for the type ArgoDB
func (_mock *ArgoDB) ListRepoCertificates(ctx context.Context, selector *db.CertificateListSelector) (*v1alpha1.RepositoryCertificateList, error) {
ret := _mock.Called(ctx, selector)

View file

@ -6,6 +6,7 @@ import (
"hash/fnv"
corev1 "k8s.io/api/core/v1"
"k8s.io/utils/ptr"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc/codes"
@ -47,6 +48,7 @@ type repositoryBackend interface {
RepoCredsExists(ctx context.Context, repoURL string) (bool, error)
GetAllHelmRepoCreds(ctx context.Context) ([]*v1alpha1.RepoCreds, error)
GetAllOCIRepoCreds(ctx context.Context) ([]*v1alpha1.RepoCreds, error)
}
func (db *db) CreateRepository(ctx context.Context, r *v1alpha1.Repository) (*v1alpha1.Repository, error) {
@ -185,6 +187,18 @@ func (db *db) listRepositories(ctx context.Context, repoType *string, writeCreds
return repositories, nil
}
func (db *db) ListOCIRepositories(ctx context.Context) ([]*v1alpha1.Repository, error) {
var result []*v1alpha1.Repository
repos, err := db.listRepositories(ctx, ptr.To("oci"), false)
if err != nil {
return nil, fmt.Errorf("failed to list OCI repositories: %w", err)
}
result = append(result, v1alpha1.Repositories(repos).Filter(func(r *v1alpha1.Repository) bool {
return r.Type == "oci"
})...)
return result, nil
}
// UpdateRepository updates a repository
func (db *db) UpdateRepository(ctx context.Context, r *v1alpha1.Repository) (*v1alpha1.Repository, error) {
secretsBackend := db.repoBackend()
@ -309,6 +323,16 @@ func (db *db) GetAllHelmRepositoryCredentials(ctx context.Context) ([]*v1alpha1.
return secretRepoCreds, nil
}
// GetAllOCIRepositoryCredentials retrieves all repository credentials
func (db *db) GetAllOCIRepositoryCredentials(ctx context.Context) ([]*v1alpha1.RepoCreds, error) {
secretRepoCreds, err := db.repoBackend().GetAllOCIRepoCreds(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get all Helm repo creds: %w", err)
}
return secretRepoCreds, nil
}
// CreateRepositoryCredentials creates a repository credential set
func (db *db) CreateRepositoryCredentials(ctx context.Context, r *v1alpha1.RepoCreds) (*v1alpha1.RepoCreds, error) {
secretBackend := db.repoBackend()

View file

@ -300,6 +300,28 @@ func (s *secretsRepositoryBackend) GetAllHelmRepoCreds(_ context.Context) ([]*ap
return helmRepoCreds, nil
}
func (s *secretsRepositoryBackend) GetAllOCIRepoCreds(_ context.Context) ([]*appsv1.RepoCreds, error) {
var ociRepoCreds []*appsv1.RepoCreds
secrets, err := s.db.listSecretsByType(common.LabelValueSecretTypeRepoCreds)
if err != nil {
return nil, err
}
for _, secret := range secrets {
if strings.EqualFold(string(secret.Data["type"]), "oci") {
repoCreds, err := s.secretToRepoCred(secret)
if err != nil {
return nil, err
}
ociRepoCreds = append(ociRepoCreds, repoCreds)
}
}
return ociRepoCreds, nil
}
func secretToRepository(secret *corev1.Secret) (*appsv1.Repository, error) {
repository := &appsv1.Repository{
Name: string(secret.Data["name"]),
@ -343,6 +365,12 @@ func secretToRepository(secret *corev1.Secret) (*appsv1.Repository, error) {
}
repository.EnableOCI = enableOCI
insecureOCIForceHTTP, err := boolOrFalse(secret, "insecureOCIForceHttp")
if err != nil {
return repository, err
}
repository.InsecureOCIForceHttp = insecureOCIForceHTTP
githubAppID, err := intOrZero(secret, "githubAppID")
if err != nil {
return repository, err
@ -383,6 +411,7 @@ func (s *secretsRepositoryBackend) repositoryToSecret(repository *appsv1.Reposit
updateSecretString(secret, "bearerToken", repository.BearerToken)
updateSecretString(secret, "sshPrivateKey", repository.SSHPrivateKey)
updateSecretBool(secret, "enableOCI", repository.EnableOCI)
updateSecretBool(secret, "insecureOCIForceHttp", repository.InsecureOCIForceHttp)
updateSecretString(secret, "tlsClientCertData", repository.TLSClientCertData)
updateSecretString(secret, "tlsClientCertKey", repository.TLSClientCertKey)
updateSecretString(secret, "type", repository.Type)
@ -424,6 +453,12 @@ func (s *secretsRepositoryBackend) secretToRepoCred(secret *corev1.Secret) (*app
}
repository.EnableOCI = enableOCI
insecureOCIForceHTTP, err := boolOrFalse(secret, "insecureOCIForceHttp")
if err != nil {
return repository, err
}
repository.InsecureOCIForceHttp = insecureOCIForceHTTP
githubAppID, err := intOrZero(secret, "githubAppID")
if err != nil {
return repository, err
@ -462,6 +497,7 @@ func repoCredsToSecret(repoCreds *appsv1.RepoCreds, secret *corev1.Secret) {
updateSecretString(secret, "bearerToken", repoCreds.BearerToken)
updateSecretString(secret, "sshPrivateKey", repoCreds.SSHPrivateKey)
updateSecretBool(secret, "enableOCI", repoCreds.EnableOCI)
updateSecretBool(secret, "insecureOCIForceHttp", repoCreds.InsecureOCIForceHttp)
updateSecretString(secret, "tlsClientCertData", repoCreds.TLSClientCertData)
updateSecretString(secret, "tlsClientCertKey", repoCreds.TLSClientCertKey)
updateSecretString(secret, "type", repoCreds.Type)

View file

@ -30,12 +30,26 @@ func Tgz(srcPath string, inclusions []string, exclusions []string, writers ...io
return 0, fmt.Errorf("error inspecting srcPath %q: %w", srcPath, err)
}
mw := io.MultiWriter(writers...)
gzw := gzip.NewWriter(mw)
gzw := gzip.NewWriter(io.MultiWriter(writers...))
defer gzw.Close()
tw := tar.NewWriter(gzw)
return writeFile(srcPath, inclusions, exclusions, gzw)
}
// Tar will iterate over all files found in srcPath archiving with Tar. Will invoke every given writer while generating the tar.
// This is useful to generate checksums. Will exclude files matching the exclusions
// list blob if exclusions is not nil. Will include only the files matching the
// inclusions list if inclusions is not nil.
func Tar(srcPath string, inclusions []string, exclusions []string, writers ...io.Writer) (int, error) {
if _, err := os.Stat(srcPath); err != nil {
return 0, fmt.Errorf("error inspecting srcPath %q: %w", srcPath, err)
}
return writeFile(srcPath, inclusions, exclusions, io.MultiWriter(writers...))
}
func writeFile(srcPath string, inclusions []string, exclusions []string, writer io.Writer) (int, error) {
tw := tar.NewWriter(writer)
defer tw.Close()
t := &tgz{
@ -56,7 +70,7 @@ func Tgz(srcPath string, inclusions []string, exclusions []string, writers ...io
// Callers must make sure dstPath is:
// - a full path
// - points to an empty directory or
// - points to a non existing directory
// - points to a non-existing directory
func Untgz(dstPath string, r io.Reader, maxSize int64, preserveFileMode bool) error {
if !filepath.IsAbs(dstPath) {
return fmt.Errorf("dstPath points to a relative path: %s", dstPath)
@ -67,9 +81,29 @@ func Untgz(dstPath string, r io.Reader, maxSize int64, preserveFileMode bool) er
return fmt.Errorf("error reading file: %w", err)
}
defer gzr.Close()
return untar(dstPath, io.LimitReader(gzr, maxSize), preserveFileMode)
}
lr := io.LimitReader(gzr, maxSize)
tr := tar.NewReader(lr)
// Untar will loop over the tar reader creating the file structure at dstPath.
// Callers must make sure dstPath is:
// - a full path
// - points to an empty directory or
// - points to a non-existing directory
func Untar(dstPath string, r io.Reader, maxSize int64, preserveFileMode bool) error {
if !filepath.IsAbs(dstPath) {
return fmt.Errorf("dstPath points to a relative path: %s", dstPath)
}
return untar(dstPath, io.LimitReader(r, maxSize), preserveFileMode)
}
// untar will loop over the tar reader creating the file structure at dstPath.
// Callers must make sure dstPath is:
// - a full path
// - points to an empty directory or
// - points to a non existing directory
func untar(dstPath string, r io.Reader, preserveFileMode bool) error {
tr := tar.NewReader(r)
for {
header, err := tr.Next()
@ -79,7 +113,7 @@ func Untgz(dstPath string, r io.Reader, maxSize int64, preserveFileMode bool) er
}
return fmt.Errorf("error while iterating on tar reader: %w", err)
}
if header == nil || header.Name == "." {
if header == nil || header.Name == "." || header.Name == "./" {
continue
}

583
util/oci/client.go Normal file
View file

@ -0,0 +1,583 @@
package oci
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"errors"
"fmt"
"io"
"io/fs"
"math"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"slices"
"strings"
"time"
securejoin "github.com/cyphar/filepath-securejoin"
imagev1 "github.com/opencontainers/image-spec/specs-go/v1"
"oras.land/oras-go/v2/content/oci"
"github.com/argoproj/argo-cd/v3/util/versions"
"github.com/argoproj/pkg/sync"
log "github.com/sirupsen/logrus"
"github.com/argoproj/argo-cd/v3/util/cache"
utilio "github.com/argoproj/argo-cd/v3/util/io"
"github.com/argoproj/argo-cd/v3/util/io/files"
"github.com/argoproj/argo-cd/v3/util/proxy"
"oras.land/oras-go/v2"
"oras.land/oras-go/v2/content/file"
"oras.land/oras-go/v2/registry/remote"
"oras.land/oras-go/v2/registry/remote/auth"
)
var (
globalLock = sync.NewKeyLock()
indexLock = sync.NewKeyLock()
)
var _ Client = &nativeOCIClient{}
type tagsCache interface {
SetOCITags(repo string, indexData []byte) error
GetOCITags(repo string, indexData *[]byte) error
}
// Client is a generic OCI client interface that provides methods for interacting with an OCI (Open Container Initiative) registry.
type Client interface {
// ResolveRevision resolves a tag, digest, or semantic version constraint to a concrete digest.
// If noCache is true, the resolution bypasses the local tags cache and queries the remote registry.
// If the revision is already a digest, it is returned as-is.
ResolveRevision(ctx context.Context, revision string, noCache bool) (string, error)
// DigestMetadata retrieves an OCI manifest for a given digest.
DigestMetadata(ctx context.Context, digest string) (*imagev1.Manifest, error)
// CleanCache is invoked on a hard-refresh or when the manifest cache has expired. This removes the OCI image from
// the cached path, which is looked up by the specified revision.
CleanCache(revision string) error
// Extract retrieves and unpacks the contents of an OCI image identified by the specified revision.
// If successful, the extracted contents are extracted to a randomized tempdir.
Extract(ctx context.Context, revision string) (string, utilio.Closer, error)
// TestRepo verifies the connectivity and accessibility of the repository.
TestRepo(ctx context.Context) (bool, error)
// GetTags retrieves the list of tags for the repository.
GetTags(ctx context.Context, noCache bool) ([]string, error)
}
type Creds struct {
Username string
Password string
CAPath string
CertData []byte
KeyData []byte
InsecureSkipVerify bool
InsecureHTTPOnly bool
}
type ClientOpts func(c *nativeOCIClient)
func WithIndexCache(indexCache tagsCache) ClientOpts {
return func(c *nativeOCIClient) {
c.tagsCache = indexCache
}
}
func WithImagePaths(repoCachePaths utilio.TempPaths) ClientOpts {
return func(c *nativeOCIClient) {
c.repoCachePaths = repoCachePaths
}
}
func WithManifestMaxExtractedSize(manifestMaxExtractedSize int64) ClientOpts {
return func(c *nativeOCIClient) {
c.manifestMaxExtractedSize = manifestMaxExtractedSize
}
}
func WithDisableManifestMaxExtractedSize(disableManifestMaxExtractedSize bool) ClientOpts {
return func(c *nativeOCIClient) {
c.disableManifestMaxExtractedSize = disableManifestMaxExtractedSize
}
}
func NewClient(repoURL string, creds Creds, proxy, noProxy string, layerMediaTypes []string, opts ...ClientOpts) (Client, error) {
return NewClientWithLock(repoURL, creds, globalLock, proxy, noProxy, layerMediaTypes, opts...)
}
func NewClientWithLock(repoURL string, creds Creds, repoLock sync.KeyLock, proxyURL, noProxy string, layerMediaTypes []string, opts ...ClientOpts) (Client, error) {
ociRepo := strings.TrimPrefix(repoURL, "oci://")
repo, err := remote.NewRepository(ociRepo)
if err != nil {
return nil, fmt.Errorf("failed to initialize repository: %w", err)
}
repo.PlainHTTP = creds.InsecureHTTPOnly
var tlsConf *tls.Config
if !repo.PlainHTTP {
tlsConf, err = newTLSConfig(creds)
if err != nil {
return nil, fmt.Errorf("failed setup tlsConfig: %w", err)
}
}
client := &http.Client{
Transport: &http.Transport{
Proxy: proxy.GetCallback(proxyURL, noProxy),
TLSClientConfig: tlsConf,
DisableKeepAlives: true,
},
/*
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return errors.New("redirects are not allowed")
},
*/
}
repo.Client = &auth.Client{
Client: client,
Cache: nil,
Credential: auth.StaticCredential(repo.Reference.Registry, auth.Credential{
Username: creds.Username,
Password: creds.Password,
}),
}
parsed, err := url.Parse(repoURL)
if err != nil {
return nil, fmt.Errorf("failed to parse oci repo url: %w", err)
}
reg, err := remote.NewRegistry(parsed.Host)
if err != nil {
return nil, fmt.Errorf("failed to setup registry config: %w", err)
}
reg.PlainHTTP = repo.PlainHTTP
reg.Client = repo.Client
return newClientWithLock(ociRepo, repoLock, repo, func(ctx context.Context, last string) ([]string, error) {
var t []string
err := repo.Tags(ctx, last, func(tags []string) error {
t = append(t, tags...)
return nil
})
return t, err
}, reg.Ping, layerMediaTypes, opts...), nil
}
func newClientWithLock(repoURL string, repoLock sync.KeyLock, repo oras.ReadOnlyTarget, tagsFunc func(context.Context, string) ([]string, error), pingFunc func(ctx context.Context) error, layerMediaTypes []string, opts ...ClientOpts) Client {
c := &nativeOCIClient{
repoURL: repoURL,
repoLock: repoLock,
repo: repo,
tagsFunc: tagsFunc,
pingFunc: pingFunc,
allowedMediaTypes: layerMediaTypes,
}
for i := range opts {
opts[i](c)
}
return c
}
// nativeOCIClient implements Client interface using oras-go
type nativeOCIClient struct {
repoURL string
repo oras.ReadOnlyTarget
tagsFunc func(context.Context, string) ([]string, error)
repoLock sync.KeyLock
tagsCache tagsCache
repoCachePaths utilio.TempPaths
allowedMediaTypes []string
manifestMaxExtractedSize int64
disableManifestMaxExtractedSize bool
pingFunc func(ctx context.Context) error
}
// TestRepo verifies that the remote OCI repo can be connected to.
func (c *nativeOCIClient) TestRepo(ctx context.Context) (bool, error) {
err := c.pingFunc(ctx)
return err == nil, err
}
func (c *nativeOCIClient) Extract(ctx context.Context, digest string) (string, utilio.Closer, error) {
cachedPath, err := c.getCachedPath(digest)
if err != nil {
return "", nil, fmt.Errorf("error getting oci path for digest %s: %w", digest, err)
}
c.repoLock.Lock(cachedPath)
defer c.repoLock.Unlock(cachedPath)
exists, err := fileExists(cachedPath)
if err != nil {
return "", nil, err
}
if !exists {
ociManifest, err := getOCIManifest(ctx, digest, c.repo)
if err != nil {
return "", nil, err
}
if len(ociManifest.Layers) != 1 {
return "", nil, fmt.Errorf("expected only a single oci layer, got %d", len(ociManifest.Layers))
}
if !slices.Contains(c.allowedMediaTypes, ociManifest.Layers[0].MediaType) {
return "", nil, fmt.Errorf("oci layer media type %s is not in the list of allowed media types", ociManifest.Layers[0].MediaType)
}
err = saveCompressedImageToPath(ctx, digest, c.repo, cachedPath)
if err != nil {
return "", nil, fmt.Errorf("could not save oci digest %s: %w", digest, err)
}
}
maxSize := c.manifestMaxExtractedSize
if c.disableManifestMaxExtractedSize {
maxSize = math.MaxInt64
}
manifestsDir, err := extractContentToManifestsDir(ctx, cachedPath, digest, maxSize)
if err != nil {
return manifestsDir, nil, fmt.Errorf("cannot extract contents of oci image with revision %s: %w", digest, err)
}
return manifestsDir, utilio.NewCloser(func() error {
return os.RemoveAll(manifestsDir)
}), nil
}
func (c *nativeOCIClient) getCachedPath(version string) (string, error) {
keyData, err := json.Marshal(map[string]string{"url": c.repoURL, "version": version})
if err != nil {
return "", err
}
return c.repoCachePaths.GetPath(string(keyData))
}
func (c *nativeOCIClient) CleanCache(revision string) error {
cachePath, err := c.getCachedPath(revision)
if err != nil {
return fmt.Errorf("error cleaning oci path for revision %s: %w", revision, err)
}
return os.RemoveAll(cachePath)
}
// DigestMetadata extracts the OCI manifest for a given revision and returns it to the caller.
func (c *nativeOCIClient) DigestMetadata(ctx context.Context, digest string) (*imagev1.Manifest, error) {
path, err := c.getCachedPath(digest)
if err != nil {
return nil, fmt.Errorf("error fetching oci metadata path for digest %s: %w", digest, err)
}
repo, err := oci.NewFromTar(ctx, path)
if err != nil {
return nil, fmt.Errorf("error extracting oci image for digest %s: %w", digest, err)
}
return getOCIManifest(ctx, digest, repo)
}
func (c *nativeOCIClient) ResolveRevision(ctx context.Context, revision string, noCache bool) (string, error) {
digest, err := c.resolveDigest(ctx, revision) // Lookup explicit revision
if err != nil {
tags, err := c.GetTags(ctx, noCache)
if err != nil {
return "", fmt.Errorf("error fetching tags: %w", err)
}
// Look to see if revision is a semver constraint
version, err := versions.MaxVersion(revision, tags)
if err != nil {
return "", fmt.Errorf("no version for constraints: %w", err)
}
// Look up the digest for the resolved version
return c.resolveDigest(ctx, version)
}
return digest, nil
}
func (c *nativeOCIClient) GetTags(ctx context.Context, noCache bool) ([]string, error) {
indexLock.Lock(c.repoURL)
defer indexLock.Unlock(c.repoURL)
var data []byte
if !noCache && c.tagsCache != nil {
if err := c.tagsCache.GetOCITags(c.repoURL, &data); err != nil && !errors.Is(err, cache.ErrCacheMiss) {
log.Warnf("Failed to load index cache for repo: %s: %s", c.repoLock, err)
}
}
var tags []string
if len(data) == 0 {
start := time.Now()
result, err := c.tagsFunc(ctx, "")
if err != nil {
return nil, fmt.Errorf("failed to get tags: %w", err)
}
for _, tag := range result {
// By convention: Change underscore (_) back to plus (+) to get valid SemVer
convertedTag := strings.ReplaceAll(tag, "_", "+")
tags = append(tags, convertedTag)
}
log.WithFields(
log.Fields{"seconds": time.Since(start).Seconds(), "repo": c.repoURL},
).Info("took to get tags")
if c.tagsCache != nil {
if err := c.tagsCache.SetOCITags(c.repoURL, data); err != nil {
log.Warnf("Failed to store tags list cache for repo: %s: %s", c.repoURL, err)
}
}
} else if err := json.Unmarshal(data, &tags); err != nil {
return nil, fmt.Errorf("failed to decode tags: %w", err)
}
return tags, nil
}
// resolveDigest resolves a digest from a tag.
func (c *nativeOCIClient) resolveDigest(ctx context.Context, revision string) (string, error) {
descriptor, err := c.repo.Resolve(ctx, revision)
if err != nil {
return "", fmt.Errorf("cannot get digest for revision %s: %w", revision, err)
}
return descriptor.Digest.String(), nil
}
func newTLSConfig(creds Creds) (*tls.Config, error) {
tlsConfig := &tls.Config{InsecureSkipVerify: creds.InsecureSkipVerify}
if creds.CAPath != "" {
caData, err := os.ReadFile(creds.CAPath)
if err != nil {
return nil, err
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caData)
tlsConfig.RootCAs = caCertPool
}
// If a client cert & key is provided then configure TLS config accordingly.
if len(creds.CertData) > 0 && len(creds.KeyData) > 0 {
cert, err := tls.X509KeyPair(creds.CertData, creds.KeyData)
if err != nil {
return nil, err
}
tlsConfig.Certificates = []tls.Certificate{cert}
}
//nolint:staticcheck
tlsConfig.BuildNameToCertificate()
return tlsConfig, nil
}
func fileExists(filePath string) (bool, error) {
if _, err := os.Stat(filePath); err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
return true, nil
}
func isCompressedLayer(mediaType string) bool {
return strings.HasSuffix(mediaType, "tar+gzip") || strings.HasSuffix(mediaType, "tar")
}
func createTarFile(from, to string) error {
f, err := os.Create(to)
if err != nil {
return err
}
if _, err = files.Tar(from, nil, nil, f); err != nil {
_ = os.RemoveAll(to)
}
return f.Close()
}
// saveCompressedImageToPath downloads a remote OCI image on a given digest and stores it as a TAR file in cachedPath.
func saveCompressedImageToPath(ctx context.Context, digest string, repo oras.ReadOnlyTarget, cachedPath string) error {
tempDir, err := files.CreateTempDir(os.TempDir())
if err != nil {
return err
}
defer os.RemoveAll(tempDir)
store, err := oci.New(tempDir)
if err != nil {
return err
}
// Copy remote repo at the given digest to the scratch dir.
if _, err = oras.Copy(ctx, repo, digest, store, digest, oras.DefaultCopyOptions); err != nil {
return err
}
// Remove redundant ingest folder; this is an artifact from the oras.Copy call above
if err = os.RemoveAll(path.Join(tempDir, "ingest")); err != nil {
return err
}
// Save contents to tar file
return createTarFile(tempDir, cachedPath)
}
// extractContentToManifestsDir looks up a locally stored OCI image, and extracts the embedded compressed layer which contains
// K8s manifests to a temporary directory
func extractContentToManifestsDir(ctx context.Context, cachedPath, digest string, maxSize int64) (string, error) {
manifestsDir, err := files.CreateTempDir(os.TempDir())
if err != nil {
return manifestsDir, err
}
ociReadOnlyStore, err := oci.NewFromTar(ctx, cachedPath)
if err != nil {
return manifestsDir, err
}
tempDir, err := files.CreateTempDir(os.TempDir())
if err != nil {
return manifestsDir, err
}
defer os.RemoveAll(tempDir)
fs, err := newCompressedLayerFileStore(manifestsDir, tempDir, maxSize)
if err != nil {
return manifestsDir, err
}
defer fs.Close()
// copies the whole artifact to the tempdir, here compressedLayerFileStore.Push will be called
_, err = oras.Copy(ctx, ociReadOnlyStore, digest, fs, digest, oras.DefaultCopyOptions)
return manifestsDir, err
}
type compressedLayerExtracterStore struct {
*file.Store
dest string
maxSize int64
}
func newCompressedLayerFileStore(dest, tempDir string, maxSize int64) (*compressedLayerExtracterStore, error) {
f, err := file.New(tempDir)
if err != nil {
return nil, err
}
return &compressedLayerExtracterStore{f, dest, maxSize}, nil
}
func isHelmOCI(mediaType string) bool {
return mediaType == "application/vnd.cncf.helm.chart.content.v1.tar+gzip"
}
// Push looks in all the layers of an OCI image. Once it finds a layer that is compressed, it extracts the layer to a tempDir
// and then renames the temp dir to the directory where the repo-server expects to find k8s manifests.
func (s *compressedLayerExtracterStore) Push(ctx context.Context, desc imagev1.Descriptor, content io.Reader) error {
if isCompressedLayer(desc.MediaType) {
srcDir, err := files.CreateTempDir(os.TempDir())
if err != nil {
return err
}
defer os.RemoveAll(srcDir)
if strings.HasSuffix(desc.MediaType, "tar+gzip") {
err = files.Untgz(srcDir, content, s.maxSize, false)
} else {
err = files.Untar(srcDir, content, s.maxSize, false)
}
if err != nil {
return fmt.Errorf("could not decompress layer: %w", err)
}
if isHelmOCI(desc.MediaType) {
infos, err := os.ReadDir(srcDir)
if err != nil {
return err
}
// For a Helm chart we expect a single directory
if len(infos) != 1 || !infos[0].IsDir() {
return fmt.Errorf("expected 1 directory, found %v", len(infos))
}
// For Helm charts, we will move the contents of the unpacked directory to the root of its final destination
srcDir, err = securejoin.SecureJoin(srcDir, infos[0].Name())
if err != nil {
return err
}
}
return filepath.WalkDir(srcDir, func(path string, d fs.DirEntry, _ error) error {
if path != srcDir {
// Calculate the relative path from srcDir
relPath, err := filepath.Rel(srcDir, path)
if err != nil {
return err
}
dstPath, err := securejoin.SecureJoin(s.dest, relPath)
if err != nil {
return err
}
// Move the file by renaming it
if d.IsDir() {
info, err := d.Info()
if err != nil {
return err
}
return os.MkdirAll(dstPath, info.Mode())
}
return os.Rename(path, dstPath)
}
return nil
})
}
return s.Store.Push(ctx, desc, content)
}
func getOCIManifest(ctx context.Context, digest string, repo oras.ReadOnlyTarget) (*imagev1.Manifest, error) {
desc, err := repo.Resolve(ctx, digest)
if err != nil {
return nil, fmt.Errorf("error resolving oci repo from digest, %w", err)
}
rc, err := repo.Fetch(ctx, desc)
if err != nil {
return nil, fmt.Errorf("error fetching oci manifest for digest %s: %w", digest, err)
}
manifest := imagev1.Manifest{}
decoder := json.NewDecoder(rc)
if err = decoder.Decode(&manifest); err != nil {
return nil, fmt.Errorf("error decoding oci manifest for digest %s: %w", digest, err)
}
return &manifest, nil
}

438
util/oci/client_test.go Normal file
View file

@ -0,0 +1,438 @@
package oci
import (
"archive/tar"
"bytes"
"compress/gzip"
"context"
"encoding/json"
"errors"
"io"
"os"
"path/filepath"
"testing"
"github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go"
imagev1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/stretchr/testify/require"
"oras.land/oras-go/v2"
"oras.land/oras-go/v2/content"
"oras.land/oras-go/v2/content/memory"
utilio "github.com/argoproj/argo-cd/v3/util/io"
"github.com/argoproj/argo-cd/v3/util/io/files"
)
type layerConf struct {
desc imagev1.Descriptor
bytes []byte
}
func generateManifest(t *testing.T, store *memory.Store, layerDescs ...layerConf) string {
t.Helper()
configBlob := []byte("Hello config")
configDesc := content.NewDescriptorFromBytes(imagev1.MediaTypeImageConfig, configBlob)
var layers []imagev1.Descriptor
for _, layer := range layerDescs {
layers = append(layers, layer.desc)
}
manifestBlob, err := json.Marshal(imagev1.Manifest{
Config: configDesc,
Layers: layers,
Versioned: specs.Versioned{SchemaVersion: 2},
})
require.NoError(t, err)
manifestDesc := content.NewDescriptorFromBytes(imagev1.MediaTypeImageManifest, manifestBlob)
for _, layer := range layerDescs {
require.NoError(t, store.Push(t.Context(), layer.desc, bytes.NewReader(layer.bytes)))
}
require.NoError(t, store.Push(t.Context(), configDesc, bytes.NewReader(configBlob)))
require.NoError(t, store.Push(t.Context(), manifestDesc, bytes.NewReader(manifestBlob)))
require.NoError(t, store.Tag(t.Context(), manifestDesc, manifestDesc.Digest.String()))
return manifestDesc.Digest.String()
}
func createGzippedTarWithContent(t *testing.T, filename, content string) []byte {
t.Helper()
var buf bytes.Buffer
gzw := gzip.NewWriter(&buf)
tw := tar.NewWriter(gzw)
require.NoError(t, tw.WriteHeader(&tar.Header{
Name: filename,
Mode: 0o644,
Size: int64(len(content)),
}))
_, err := tw.Write([]byte(content))
require.NoError(t, err)
require.NoError(t, tw.Close())
require.NoError(t, gzw.Close())
return buf.Bytes()
}
func addFileToDirectory(t *testing.T, dir, filename, content string) {
t.Helper()
filePath := filepath.Join(dir, filename)
err := os.WriteFile(filePath, []byte(content), 0o644)
require.NoError(t, err)
}
func Test_nativeOCIClient_Extract(t *testing.T) {
cacheDir := utilio.NewRandomizedTempPaths(t.TempDir())
type fields struct {
repoURL string
tagsFunc func(context.Context, string) (tags []string, err error)
allowedMediaTypes []string
}
type args struct {
manifestMaxExtractedSize int64
disableManifestMaxExtractedSize bool
digestFunc func(*memory.Store) string
postValidationFunc func(string, string, Client, fields, args)
}
tests := []struct {
name string
fields fields
args args
expectedError error
}{
{
name: "extraction fails due to size limit",
fields: fields{
allowedMediaTypes: []string{imagev1.MediaTypeImageLayerGzip},
},
args: args{
digestFunc: func(store *memory.Store) string {
layerBlob := createGzippedTarWithContent(t, "some-path", "some content")
return generateManifest(t, store, layerConf{content.NewDescriptorFromBytes(imagev1.MediaTypeImageLayerGzip, layerBlob), layerBlob})
},
manifestMaxExtractedSize: 10,
disableManifestMaxExtractedSize: false,
},
expectedError: errors.New("cannot extract contents of oci image with revision sha256:1b6dfd71e2b35c2f35dffc39007c2276f3c0e235cbae4c39cba74bd406174e22: failed to perform \"Push\" on destination: could not decompress layer: error while iterating on tar reader: unexpected EOF"),
},
{
name: "extraction fails due to multiple layers",
fields: fields{
allowedMediaTypes: []string{imagev1.MediaTypeImageLayerGzip},
},
args: args{
digestFunc: func(store *memory.Store) string {
layerBlob := createGzippedTarWithContent(t, "some-path", "some content")
otherLayerBlob := createGzippedTarWithContent(t, "some-other-path", "some other content")
return generateManifest(t, store, layerConf{content.NewDescriptorFromBytes(imagev1.MediaTypeImageLayerGzip, layerBlob), layerBlob}, layerConf{content.NewDescriptorFromBytes(imagev1.MediaTypeImageLayerGzip, otherLayerBlob), otherLayerBlob})
},
manifestMaxExtractedSize: 1000,
disableManifestMaxExtractedSize: false,
},
expectedError: errors.New("expected only a single oci layer, got 2"),
},
{
name: "extraction fails due to invalid media type",
fields: fields{
allowedMediaTypes: []string{"application/vnd.different.media.type"},
},
args: args{
digestFunc: func(store *memory.Store) string {
layerBlob := "Hello layer"
return generateManifest(t, store, layerConf{content.NewDescriptorFromBytes(imagev1.MediaTypeImageLayerGzip, []byte(layerBlob)), []byte(layerBlob)})
},
manifestMaxExtractedSize: 1000,
disableManifestMaxExtractedSize: false,
},
expectedError: errors.New("oci layer media type application/vnd.oci.image.layer.v1.tar+gzip is not in the list of allowed media types"),
},
{
name: "extraction fails due to non-existent digest",
fields: fields{
allowedMediaTypes: []string{"application/vnd.cncf.helm.chart.content.v1.tar+gzip"},
},
args: args{
digestFunc: func(_ *memory.Store) string {
return "sha256:nonexistentdigest"
},
manifestMaxExtractedSize: 1000,
disableManifestMaxExtractedSize: false,
},
expectedError: errors.New("error resolving oci repo from digest, sha256:nonexistentdigest: not found"),
},
{
name: "extraction with helm chart",
fields: fields{
allowedMediaTypes: []string{"application/vnd.cncf.helm.chart.content.v1.tar+gzip"},
},
args: args{
digestFunc: func(store *memory.Store) string {
chartDir := t.TempDir()
chartName := "mychart"
parent := filepath.Join(chartDir, "parent")
require.NoError(t, os.Mkdir(parent, 0o755))
chartPath := filepath.Join(parent, chartName)
require.NoError(t, os.Mkdir(chartPath, 0o755))
addFileToDirectory(t, chartPath, "Chart.yaml", "some content")
temp, err := os.CreateTemp(t.TempDir(), "")
require.NoError(t, err)
defer temp.Close()
_, err = files.Tgz(parent, nil, nil, temp)
require.NoError(t, err)
_, err = temp.Seek(0, io.SeekStart)
require.NoError(t, err)
all, err := io.ReadAll(temp)
require.NoError(t, err)
return generateManifest(t, store, layerConf{content.NewDescriptorFromBytes("application/vnd.cncf.helm.chart.content.v1.tar+gzip", all), all})
},
postValidationFunc: func(_, path string, _ Client, _ fields, _ args) {
tempDir, err := files.CreateTempDir(os.TempDir())
defer os.RemoveAll(tempDir)
require.NoError(t, err)
chartDir, err := os.ReadDir(path)
require.NoError(t, err)
require.Len(t, chartDir, 1)
require.Equal(t, "Chart.yaml", chartDir[0].Name())
chartYaml, err := os.Open(filepath.Join(path, chartDir[0].Name()))
require.NoError(t, err)
contents, err := io.ReadAll(chartYaml)
require.NoError(t, err)
require.Equal(t, "some content", string(contents))
},
manifestMaxExtractedSize: 10000,
disableManifestMaxExtractedSize: false,
},
},
{
name: "extraction with standard gzip layer",
fields: fields{
allowedMediaTypes: []string{imagev1.MediaTypeImageLayerGzip},
},
args: args{
digestFunc: func(store *memory.Store) string {
layerBlob := createGzippedTarWithContent(t, "foo.yaml", "some content")
return generateManifest(t, store, layerConf{content.NewDescriptorFromBytes(imagev1.MediaTypeImageLayerGzip, layerBlob), layerBlob})
},
postValidationFunc: func(_, path string, _ Client, _ fields, _ args) {
manifestDir, err := os.ReadDir(path)
require.NoError(t, err)
require.Len(t, manifestDir, 1)
require.Equal(t, "foo.yaml", manifestDir[0].Name())
f, err := os.Open(filepath.Join(path, manifestDir[0].Name()))
require.NoError(t, err)
contents, err := io.ReadAll(f)
require.NoError(t, err)
require.Equal(t, "some content", string(contents))
},
manifestMaxExtractedSize: 1000,
disableManifestMaxExtractedSize: false,
},
},
{
name: "extraction with standard gzip layer using cache",
fields: fields{
allowedMediaTypes: []string{imagev1.MediaTypeImageLayerGzip},
},
args: args{
digestFunc: func(store *memory.Store) string {
layerBlob := createGzippedTarWithContent(t, "foo.yaml", "some content")
return generateManifest(t, store, layerConf{content.NewDescriptorFromBytes(imagev1.MediaTypeImageLayerGzip, layerBlob), layerBlob})
},
manifestMaxExtractedSize: 1000,
disableManifestMaxExtractedSize: false,
postValidationFunc: func(sha string, _ string, _ Client, fields fields, args args) {
store := memory.New()
c := newClientWithLock(fields.repoURL, globalLock, store, fields.tagsFunc, func(_ context.Context) error {
return nil
}, fields.allowedMediaTypes, WithImagePaths(cacheDir), WithManifestMaxExtractedSize(args.manifestMaxExtractedSize), WithDisableManifestMaxExtractedSize(args.disableManifestMaxExtractedSize))
_, gotCloser, err := c.Extract(t.Context(), sha)
require.NoError(t, err)
require.NoError(t, gotCloser.Close())
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
store := memory.New()
sha := tt.args.digestFunc(store)
c := newClientWithLock(tt.fields.repoURL, globalLock, store, tt.fields.tagsFunc, func(_ context.Context) error {
return nil
}, tt.fields.allowedMediaTypes, WithImagePaths(cacheDir), WithManifestMaxExtractedSize(tt.args.manifestMaxExtractedSize), WithDisableManifestMaxExtractedSize(tt.args.disableManifestMaxExtractedSize))
path, gotCloser, err := c.Extract(t.Context(), sha)
if tt.expectedError != nil {
require.EqualError(t, err, tt.expectedError.Error())
return
}
require.NoError(t, err)
require.NotEmpty(t, path)
require.NotNil(t, gotCloser)
exists, err := fileExists(path)
require.True(t, exists)
require.NoError(t, err)
if tt.args.postValidationFunc != nil {
tt.args.postValidationFunc(sha, path, c, tt.fields, tt.args)
}
require.NoError(t, gotCloser.Close())
exists, err = fileExists(path)
require.False(t, exists)
require.NoError(t, err)
})
}
}
func Test_nativeOCIClient_ResolveRevision(t *testing.T) {
store := memory.New()
data := []byte("")
descriptor := imagev1.Descriptor{
MediaType: "",
Digest: digest.FromBytes(data),
}
require.NoError(t, store.Push(t.Context(), descriptor, bytes.NewReader(data)))
require.NoError(t, store.Tag(t.Context(), descriptor, "latest"))
require.NoError(t, store.Tag(t.Context(), descriptor, "1.2.0"))
require.NoError(t, store.Tag(t.Context(), descriptor, "v1.2.0"))
require.NoError(t, store.Tag(t.Context(), descriptor, descriptor.Digest.String()))
type fields struct {
repoURL string
repo oras.ReadOnlyTarget
tagsFunc func(context.Context, string) (tags []string, err error)
allowedMediaTypes []string
}
tests := []struct {
name string
fields fields
revision string
noCache bool
expectedDigest string
expectedError error
}{
{
name: "resolve semantic version constraint",
revision: "^1.0.0",
fields: fields{repo: store, tagsFunc: func(context.Context, string) (tags []string, err error) {
return []string{"1.0.0", "1.1.0", "1.2.0", "2.0.0"}, nil
}},
expectedDigest: descriptor.Digest.String(),
},
{
name: "resolve exact version",
revision: "1.2.0",
fields: fields{repo: store, tagsFunc: func(context.Context, string) (tags []string, err error) {
return []string{"1.0.0", "1.1.0", "1.2.0", "2.0.0"}, nil
}},
expectedDigest: descriptor.Digest.String(),
},
{
name: "resolve digest directly",
revision: descriptor.Digest.String(),
fields: fields{repo: store, tagsFunc: func(context.Context, string) (tags []string, err error) {
return []string{}, errors.New("this should not be invoked")
}},
expectedDigest: descriptor.Digest.String(),
},
{
name: "no matching version for constraint",
revision: "^3.0.0",
fields: fields{repo: store, tagsFunc: func(context.Context, string) (tags []string, err error) {
return []string{"1.0.0", "1.1.0", "1.2.0", "2.0.0"}, nil
}},
expectedError: errors.New("no version for constraints: version matching constraint not found in 4 tags"),
},
{
name: "error fetching tags",
revision: "^1.0.0",
fields: fields{repo: store, tagsFunc: func(context.Context, string) (tags []string, err error) {
return []string{}, errors.New("some random error")
}},
expectedError: errors.New("error fetching tags: failed to get tags: some random error"),
},
{
name: "error resolving digest",
revision: "sha256:abc123",
fields: fields{repo: store, tagsFunc: func(context.Context, string) (tags []string, err error) {
return []string{"1.0.0", "1.1.0", "1.2.0", "2.0.0"}, nil
}},
expectedError: errors.New("no version for constraints: failed to determine semver constraint: improper constraint: sha256:abc123"),
},
{
name: "resolve latest tag",
revision: "latest",
fields: fields{repo: store, tagsFunc: func(context.Context, string) (tags []string, err error) {
return []string{"1.0.0", "1.1.0", "1.2.0", "2.0.0", "latest"}, nil
}},
expectedDigest: descriptor.Digest.String(),
},
{
name: "resolve with complex semver constraint",
revision: ">=1.0.0 <2.0.0",
fields: fields{repo: store, tagsFunc: func(context.Context, string) (tags []string, err error) {
return []string{"0.9.0", "1.0.0", "1.1.0", "1.2.0", "2.0.0", "2.1.0"}, nil
}},
expectedDigest: descriptor.Digest.String(),
},
{
name: "resolve with only non-semver tags",
revision: "^1.0.0",
fields: fields{repo: store, tagsFunc: func(context.Context, string) (tags []string, err error) {
return []string{"latest", "stable", "prod", "dev"}, nil
}},
expectedError: errors.New("no version for constraints: version matching constraint not found in 4 tags"),
},
{
name: "resolve explicit tag",
revision: "v1.2.0",
fields: fields{repo: store, tagsFunc: func(context.Context, string) (tags []string, err error) {
return []string{}, errors.New("this should not be invoked")
}},
expectedError: nil,
expectedDigest: descriptor.Digest.String(),
},
{
name: "resolve with empty tag list",
revision: "^1.0.0",
fields: fields{repo: store, tagsFunc: func(context.Context, string) (tags []string, err error) {
return []string{}, nil
}},
expectedError: errors.New("no version for constraints: version matching constraint not found in 0 tags"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := newClientWithLock(tt.fields.repoURL, globalLock, tt.fields.repo, tt.fields.tagsFunc, func(_ context.Context) error {
return nil
}, tt.fields.allowedMediaTypes)
got, err := c.ResolveRevision(t.Context(), tt.revision, tt.noCache)
if tt.expectedError != nil {
require.EqualError(t, err, tt.expectedError.Error())
return
}
require.NoError(t, err)
if got != tt.expectedDigest {
t.Errorf("ResolveRevision() got = %v, expectedDigest %v", got, tt.expectedDigest)
}
})
}
}