mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-03-23 16:58:47 +01:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98203002ba | ||
|
|
a316a413ae | ||
|
|
c8f21e383a | ||
|
|
e2df7315fb | ||
|
|
9e4a0f523e | ||
|
|
7c21486b31 | ||
|
|
caab433303 | ||
|
|
d6cb4e903f | ||
|
|
710ee9a218 | ||
|
|
e0ff56d89f | ||
|
|
91bb155ed2 | ||
|
|
7ff0ccc34e | ||
|
|
b17323b41e | ||
|
|
1701f7edd3 | ||
|
|
ed5d9c3874 | ||
|
|
14f681e3ee | ||
|
|
8281b18831 | ||
|
|
5bbb51ab42 | ||
|
|
6e181d72b3 | ||
|
|
c415d3f3d5 | ||
|
|
759bd2888d | ||
|
|
2a410f3441 | ||
|
|
689a73a729 | ||
|
|
fbb6b20418 | ||
|
|
f05a6b7906 | ||
|
|
7aac4ba0f0 | ||
|
|
7cac5a8946 | ||
|
|
ba62a0a86d | ||
|
|
3fa66ec42c | ||
|
|
cc672e7609 | ||
|
|
cce1e97434 | ||
|
|
f43122d3cd | ||
|
|
f9436641a6 | ||
|
|
7561cc1cdd |
2
.github/workflows/ci-build.yaml
vendored
2
.github/workflows/ci-build.yaml
vendored
@@ -428,7 +428,7 @@ jobs:
|
||||
run: |
|
||||
docker pull ghcr.io/dexidp/dex:v2.37.0
|
||||
docker pull argoproj/argo-cd-ci-builder:v1.0.0
|
||||
docker pull redis:7.0.11-alpine
|
||||
docker pull redis:7.0.15-alpine
|
||||
- name: Create target directory for binaries in the build-process
|
||||
run: |
|
||||
mkdir -p dist
|
||||
|
||||
4
.github/workflows/image-reuse.yaml
vendored
4
.github/workflows/image-reuse.yaml
vendored
@@ -74,9 +74,7 @@ jobs:
|
||||
go-version: ${{ inputs.go-version }}
|
||||
|
||||
- name: Install cosign
|
||||
uses: sigstore/cosign-installer@11086d25041f77fe8fe7b9ea4e48e3b9192b8f19 # v3.1.2
|
||||
with:
|
||||
cosign-release: 'v2.0.2'
|
||||
uses: sigstore/cosign-installer@e1523de7571e31dbe865fd2e80c5c7c23ae71eb4 # v3.4.0
|
||||
|
||||
- uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0
|
||||
- uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
|
||||
|
||||
2
.github/workflows/image.yaml
vendored
2
.github/workflows/image.yaml
vendored
@@ -86,7 +86,7 @@ jobs:
|
||||
packages: write # for uploading attestations. (https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#known-issues)
|
||||
if: ${{ github.repository == 'argoproj/argo-cd' && github.event_name == 'push' }}
|
||||
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.7.0
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.10.0
|
||||
with:
|
||||
image: ghcr.io/argoproj/argo-cd/argocd
|
||||
digest: ${{ needs.build-and-publish.outputs.image-digest }}
|
||||
|
||||
6
.github/workflows/release.yaml
vendored
6
.github/workflows/release.yaml
vendored
@@ -38,7 +38,7 @@ jobs:
|
||||
packages: write # for uploading attestations. (https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#known-issues)
|
||||
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
|
||||
if: github.repository == 'argoproj/argo-cd'
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.9.0
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.10.0
|
||||
with:
|
||||
image: quay.io/argoproj/argocd
|
||||
digest: ${{ needs.argocd-image.outputs.image-digest }}
|
||||
@@ -120,7 +120,7 @@ jobs:
|
||||
contents: write # Needed for release uploads
|
||||
if: github.repository == 'argoproj/argo-cd'
|
||||
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.9.0
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.10.0
|
||||
with:
|
||||
base64-subjects: "${{ needs.goreleaser.outputs.hashes }}"
|
||||
provenance-name: "argocd-cli.intoto.jsonl"
|
||||
@@ -204,7 +204,7 @@ jobs:
|
||||
contents: write # Needed for release uploads
|
||||
if: github.repository == 'argoproj/argo-cd'
|
||||
# Must be refernced by a tag. https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/container/README.md#referencing-the-slsa-generator
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.9.0
|
||||
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.10.0
|
||||
with:
|
||||
base64-subjects: "${{ needs.generate-sbom.outputs.hashes }}"
|
||||
provenance-name: "argocd-sbom.intoto.jsonl"
|
||||
|
||||
@@ -6,7 +6,7 @@ ARG BASE_IMAGE=docker.io/library/ubuntu:22.04@sha256:0bced47fffa3361afa981854fca
|
||||
####################################################################################################
|
||||
FROM docker.io/library/golang:1.21.3@sha256:02d7116222536a5cf0fcf631f90b507758b669648e0f20186d2dc94a9b419a9b AS builder
|
||||
|
||||
RUN echo 'deb http://deb.debian.org/debian buster-backports main' >> /etc/apt/sources.list
|
||||
RUN echo 'deb http://archive.debian.org/debian buster-backports main' >> /etc/apt/sources.list
|
||||
|
||||
RUN apt-get update && apt-get install --no-install-recommends -y \
|
||||
openssh-server \
|
||||
|
||||
@@ -50,6 +50,7 @@ import (
|
||||
argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned"
|
||||
argoutil "github.com/argoproj/argo-cd/v2/util/argo"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application"
|
||||
)
|
||||
@@ -623,7 +624,7 @@ func (r *ApplicationSetReconciler) createOrUpdateInCluster(ctx context.Context,
|
||||
},
|
||||
}
|
||||
|
||||
action, err := utils.CreateOrUpdate(ctx, appLog, r.Client, applicationSet.Spec.IgnoreApplicationDifferences, found, func() error {
|
||||
action, err := utils.CreateOrUpdate(ctx, appLog, r.Client, applicationSet.Spec.IgnoreApplicationDifferences, normalizers.IgnoreNormalizerOpts{}, found, func() error {
|
||||
// Copy only the Application/ObjectMeta fields that are significant, from the generatedApp
|
||||
found.Spec = generatedApp.Spec
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo"
|
||||
argodiff "github.com/argoproj/argo-cd/v2/util/argo/diff"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
|
||||
)
|
||||
|
||||
// CreateOrUpdate overrides "sigs.k8s.io/controller-runtime" function
|
||||
@@ -35,7 +36,7 @@ import (
|
||||
// The MutateFn is called regardless of creating or updating an object.
|
||||
//
|
||||
// It returns the executed operation and an error.
|
||||
func CreateOrUpdate(ctx context.Context, logCtx *log.Entry, c client.Client, ignoreAppDifferences argov1alpha1.ApplicationSetIgnoreDifferences, obj *argov1alpha1.Application, f controllerutil.MutateFn) (controllerutil.OperationResult, error) {
|
||||
func CreateOrUpdate(ctx context.Context, logCtx *log.Entry, c client.Client, ignoreAppDifferences argov1alpha1.ApplicationSetIgnoreDifferences, ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts, obj *argov1alpha1.Application, f controllerutil.MutateFn) (controllerutil.OperationResult, error) {
|
||||
|
||||
key := client.ObjectKeyFromObject(obj)
|
||||
if err := c.Get(ctx, key, obj); err != nil {
|
||||
@@ -60,7 +61,7 @@ func CreateOrUpdate(ctx context.Context, logCtx *log.Entry, c client.Client, ign
|
||||
|
||||
// Apply ignoreApplicationDifferences rules to remove ignored fields from both the live and the desired state. This
|
||||
// prevents those differences from appearing in the diff and therefore in the patch.
|
||||
err := applyIgnoreDifferences(ignoreAppDifferences, normalizedLive, obj)
|
||||
err := applyIgnoreDifferences(ignoreAppDifferences, normalizedLive, obj, ignoreNormalizerOpts)
|
||||
if err != nil {
|
||||
return controllerutil.OperationResultNone, fmt.Errorf("failed to apply ignore differences: %w", err)
|
||||
}
|
||||
@@ -134,14 +135,14 @@ func mutate(f controllerutil.MutateFn, key client.ObjectKey, obj client.Object)
|
||||
}
|
||||
|
||||
// applyIgnoreDifferences applies the ignore differences rules to the found application. It modifies the applications in place.
|
||||
func applyIgnoreDifferences(applicationSetIgnoreDifferences argov1alpha1.ApplicationSetIgnoreDifferences, found *argov1alpha1.Application, generatedApp *argov1alpha1.Application) error {
|
||||
func applyIgnoreDifferences(applicationSetIgnoreDifferences argov1alpha1.ApplicationSetIgnoreDifferences, found *argov1alpha1.Application, generatedApp *argov1alpha1.Application, ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts) error {
|
||||
if len(applicationSetIgnoreDifferences) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
generatedAppCopy := generatedApp.DeepCopy()
|
||||
diffConfig, err := argodiff.NewDiffConfigBuilder().
|
||||
WithDiffSettings(applicationSetIgnoreDifferences.ToApplicationIgnoreDifferences(), nil, false).
|
||||
WithDiffSettings(applicationSetIgnoreDifferences.ToApplicationIgnoreDifferences(), nil, false, ignoreNormalizerOpts).
|
||||
WithNoCache().
|
||||
Build()
|
||||
if err != nil {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
|
||||
)
|
||||
|
||||
func Test_applyIgnoreDifferences(t *testing.T) {
|
||||
@@ -222,7 +223,7 @@ spec:
|
||||
generatedApp := v1alpha1.Application{TypeMeta: appMeta}
|
||||
err = yaml.Unmarshal([]byte(tc.generatedApp), &generatedApp)
|
||||
require.NoError(t, err, tc.generatedApp)
|
||||
err = applyIgnoreDifferences(tc.ignoreDifferences, &foundApp, &generatedApp)
|
||||
err = applyIgnoreDifferences(tc.ignoreDifferences, &foundApp, &generatedApp, normalizers.IgnoreNormalizerOpts{})
|
||||
require.NoError(t, err)
|
||||
yamlFound, err := yaml.Marshal(tc.foundApp)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -5089,7 +5089,7 @@
|
||||
}
|
||||
},
|
||||
"runtimeRawExtension": {
|
||||
"description": "RawExtension is used to hold extensions in external versions.\n\nTo use this, make a field which has RawExtension as its type in your external, versioned\nstruct, and Object in your internal struct. You also need to register your\nvarious plugin types.\n\n// Internal package:\ntype MyAPIObject struct {\n\truntime.TypeMeta `json:\",inline\"`\n\tMyPlugin runtime.Object `json:\"myPlugin\"`\n}\ntype PluginA struct {\n\tAOption string `json:\"aOption\"`\n}\n\n// External package:\ntype MyAPIObject struct {\n\truntime.TypeMeta `json:\",inline\"`\n\tMyPlugin runtime.RawExtension `json:\"myPlugin\"`\n}\ntype PluginA struct {\n\tAOption string `json:\"aOption\"`\n}\n\n// On the wire, the JSON will look something like this:\n{\n\t\"kind\":\"MyAPIObject\",\n\t\"apiVersion\":\"v1\",\n\t\"myPlugin\": {\n\t\t\"kind\":\"PluginA\",\n\t\t\"aOption\":\"foo\",\n\t},\n}\n\nSo what happens? Decode first uses json or yaml to unmarshal the serialized data into\nyour external MyAPIObject. That causes the raw JSON to be stored, but not unpacked.\nThe next step is to copy (using pkg/conversion) into the internal struct. The runtime\npackage's DefaultScheme has conversion functions installed which will unpack the\nJSON stored in RawExtension, turning it into the correct object type, and storing it\nin the Object. (TODO: In the case where the object is of an unknown type, a\nruntime.Unknown object will be created and stored.)\n\n+k8s:deepcopy-gen=true\n+protobuf=true\n+k8s:openapi-gen=true",
|
||||
"description": "RawExtension is used to hold extensions in external versions.\n\nTo use this, make a field which has RawExtension as its type in your external, versioned\nstruct, and Object in your internal struct. You also need to register your\nvarious plugin types.\n\n// Internal package:\n\n\ttype MyAPIObject struct {\n\t\truntime.TypeMeta `json:\",inline\"`\n\t\tMyPlugin runtime.Object `json:\"myPlugin\"`\n\t}\n\n\ttype PluginA struct {\n\t\tAOption string `json:\"aOption\"`\n\t}\n\n// External package:\n\n\ttype MyAPIObject struct {\n\t\truntime.TypeMeta `json:\",inline\"`\n\t\tMyPlugin runtime.RawExtension `json:\"myPlugin\"`\n\t}\n\n\ttype PluginA struct {\n\t\tAOption string `json:\"aOption\"`\n\t}\n\n// On the wire, the JSON will look something like this:\n\n\t{\n\t\t\"kind\":\"MyAPIObject\",\n\t\t\"apiVersion\":\"v1\",\n\t\t\"myPlugin\": {\n\t\t\t\"kind\":\"PluginA\",\n\t\t\t\"aOption\":\"foo\",\n\t\t},\n\t}\n\nSo what happens? Decode first uses json or yaml to unmarshal the serialized data into\nyour external MyAPIObject. That causes the raw JSON to be stored, but not unpacked.\nThe next step is to copy (using pkg/conversion) into the internal struct. The runtime\npackage's DefaultScheme has conversion functions installed which will unpack the\nJSON stored in RawExtension, turning it into the correct object type, and storing it\nin the Object. (TODO: In the case where the object is of an unknown type, a\nruntime.Unknown object will be created and stored.)\n\n+k8s:deepcopy-gen=true\n+protobuf=true\n+k8s:openapi-gen=true",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"raw": {
|
||||
@@ -5571,8 +5571,8 @@
|
||||
}
|
||||
},
|
||||
"v1ObjectReference": {
|
||||
"description": "ObjectReference contains enough information to let you inspect or modify the referred object.\n---\nNew uses of this type are discouraged because of difficulty describing its usage when embedded in APIs.\n 1. Ignored fields. It includes many fields which are not generally honored. For instance, ResourceVersion and FieldPath are both very rarely valid in actual usage.\n 2. Invalid usage help. It is impossible to add specific help for individual usage. In most embedded usages, there are particular\n restrictions like, \"must refer only to types A and B\" or \"UID not honored\" or \"name must be restricted\".\n Those cannot be well described when embedded.\n 3. Inconsistent validation. Because the usages are different, the validation rules are different by usage, which makes it hard for users to predict what will happen.\n 4. The fields are both imprecise and overly precise. Kind is not a precise mapping to a URL. This can produce ambiguity\n during interpretation and require a REST mapping. In most cases, the dependency is on the group,resource tuple\n and the version of the actual struct is irrelevant.\n 5. We cannot easily change it. Because this type is embedded in many locations, updates to this type\n will affect numerous schemas. Don't make new APIs embed an underspecified API type they do not control.\n\nInstead of using this type, create a locally provided and used type that is well-focused on your reference.\nFor example, ServiceReferences for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 .\n+k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n+structType=atomic",
|
||||
"type": "object",
|
||||
"title": "ObjectReference contains enough information to let you inspect or modify the referred object.\n---\nNew uses of this type are discouraged because of difficulty describing its usage when embedded in APIs.\n 1. Ignored fields. It includes many fields which are not generally honored. For instance, ResourceVersion and FieldPath are both very rarely valid in actual usage.\n 2. Invalid usage help. It is impossible to add specific help for individual usage. In most embedded usages, there are particular\n restrictions like, \"must refer only to types A and B\" or \"UID not honored\" or \"name must be restricted\".\n Those cannot be well described when embedded.\n 3. Inconsistent validation. Because the usages are different, the validation rules are different by usage, which makes it hard for users to predict what will happen.\n 4. The fields are both imprecise and overly precise. Kind is not a precise mapping to a URL. This can produce ambiguity\n during interpretation and require a REST mapping. In most cases, the dependency is on the group,resource tuple\n and the version of the actual struct is irrelevant.\n 5. We cannot easily change it. Because this type is embedded in many locations, updates to this type\n will affect numerous schemas. Don't make new APIs embed an underspecified API type they do not control.\nInstead of using this type, create a locally provided and used type that is well-focused on your reference.\nFor example, ServiceReferences for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 .\n+k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object\n+structType=atomic",
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"type": "string",
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
appclientset "github.com/argoproj/argo-cd/v2/pkg/client/clientset/versioned"
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
|
||||
cacheutil "github.com/argoproj/argo-cd/v2/util/cache"
|
||||
appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate"
|
||||
"github.com/argoproj/argo-cd/v2/util/cli"
|
||||
@@ -68,6 +69,7 @@ func NewCommand() *cobra.Command {
|
||||
persistResourceHealth bool
|
||||
shardingAlgorithm string
|
||||
enableDynamicClusterDistribution bool
|
||||
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts
|
||||
)
|
||||
var command = cobra.Command{
|
||||
Use: cliName,
|
||||
@@ -160,6 +162,7 @@ func NewCommand() *cobra.Command {
|
||||
persistResourceHealth,
|
||||
clusterFilter,
|
||||
applicationNamespaces,
|
||||
ignoreNormalizerOpts,
|
||||
)
|
||||
errors.CheckError(err)
|
||||
cacheutil.CollectMetrics(redisClient, appController.GetMetricsServer())
|
||||
@@ -206,6 +209,7 @@ func NewCommand() *cobra.Command {
|
||||
command.Flags().BoolVar(&persistResourceHealth, "persist-resource-health", env.ParseBoolFromEnv("ARGOCD_APPLICATION_CONTROLLER_PERSIST_RESOURCE_HEALTH", true), "Enables storing the managed resources health in the Application CRD")
|
||||
command.Flags().StringVar(&shardingAlgorithm, "sharding-method", env.StringFromEnv(common.EnvControllerShardingAlgorithm, common.DefaultShardingAlgorithm), "Enables choice of sharding method. Supported sharding methods are : [legacy, round-robin] ")
|
||||
command.Flags().BoolVar(&enableDynamicClusterDistribution, "dynamic-cluster-distribution-enabled", env.ParseBoolFromEnv(common.EnvEnableDynamicClusterDistribution, false), "Enables dynamic cluster distribution.")
|
||||
command.Flags().DurationVar(&ignoreNormalizerOpts.JQExecutionTimeout, "ignore-normalizer-jq-execution-timeout-seconds", env.ParseDurationFromEnv("ARGOCD_IGNORE_NORMALIZER_JQ_TIMEOUT", 0*time.Second, 0, math.MaxInt64), "Set ignore normalizer JQ execution timeout")
|
||||
cacheSource = appstatecache.AddCacheFlagsToCmd(&command, func(client *redis.Client) {
|
||||
redisClient = client
|
||||
})
|
||||
|
||||
@@ -66,6 +66,7 @@ func NewCommand() *cobra.Command {
|
||||
streamedManifestMaxTarSize string
|
||||
streamedManifestMaxExtractedSize string
|
||||
helmManifestMaxExtractedSize string
|
||||
helmRegistryMaxIndexSize string
|
||||
disableManifestMaxExtractedSize bool
|
||||
)
|
||||
var command = cobra.Command{
|
||||
@@ -108,6 +109,9 @@ func NewCommand() *cobra.Command {
|
||||
helmManifestMaxExtractedSizeQuantity, err := resource.ParseQuantity(helmManifestMaxExtractedSize)
|
||||
errors.CheckError(err)
|
||||
|
||||
helmRegistryMaxIndexSizeQuantity, err := resource.ParseQuantity(helmRegistryMaxIndexSize)
|
||||
errors.CheckError(err)
|
||||
|
||||
askPassServer := askpass.NewServer()
|
||||
metricsServer := metrics.NewMetricsServer()
|
||||
cacheutil.CollectMetrics(redisClient, metricsServer)
|
||||
@@ -123,6 +127,7 @@ func NewCommand() *cobra.Command {
|
||||
StreamedManifestMaxExtractedSize: streamedManifestMaxExtractedSizeQuantity.ToDec().Value(),
|
||||
StreamedManifestMaxTarSize: streamedManifestMaxTarSizeQuantity.ToDec().Value(),
|
||||
HelmManifestMaxExtractedSize: helmManifestMaxExtractedSizeQuantity.ToDec().Value(),
|
||||
HelmRegistryMaxIndexSize: helmRegistryMaxIndexSizeQuantity.ToDec().Value(),
|
||||
}, askPassServer)
|
||||
errors.CheckError(err)
|
||||
|
||||
@@ -204,6 +209,7 @@ func NewCommand() *cobra.Command {
|
||||
command.Flags().StringVar(&streamedManifestMaxTarSize, "streamed-manifest-max-tar-size", env.StringFromEnv("ARGOCD_REPO_SERVER_STREAMED_MANIFEST_MAX_TAR_SIZE", "100M"), "Maximum size of streamed manifest archives")
|
||||
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().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")
|
||||
tlsConfigCustomizerSrc = tls.AddTLSFlagsToCmd(&command)
|
||||
cacheSrc = reposervercache.AddCacheFlagsToCmd(&command, func(client *redis.Client) {
|
||||
|
||||
@@ -164,6 +164,11 @@ func NewCommand() *cobra.Command {
|
||||
baseHRef = rootPath
|
||||
}
|
||||
|
||||
var contentTypesList []string
|
||||
if contentTypes != "" {
|
||||
contentTypesList = strings.Split(contentTypes, ";")
|
||||
}
|
||||
|
||||
argoCDOpts := server.ArgoCDServerOpts{
|
||||
Insecure: insecure,
|
||||
ListenPort: listenPort,
|
||||
@@ -179,7 +184,7 @@ func NewCommand() *cobra.Command {
|
||||
DexServerAddr: dexServerAddress,
|
||||
DexTLSConfig: dexTlsConfig,
|
||||
DisableAuth: disableAuth,
|
||||
ContentTypes: strings.Split(contentTypes, ";"),
|
||||
ContentTypes: contentTypesList,
|
||||
EnableGZip: enableGZip,
|
||||
TLSConfigCustomizer: tlsConfigCustomizer,
|
||||
Cache: cache,
|
||||
@@ -227,7 +232,7 @@ func NewCommand() *cobra.Command {
|
||||
command.Flags().StringVar(&repoServerAddress, "repo-server", env.StringFromEnv("ARGOCD_SERVER_REPO_SERVER", common.DefaultRepoServerAddr), "Repo server address")
|
||||
command.Flags().StringVar(&dexServerAddress, "dex-server", env.StringFromEnv("ARGOCD_SERVER_DEX_SERVER", common.DefaultDexServerAddr), "Dex server address")
|
||||
command.Flags().BoolVar(&disableAuth, "disable-auth", env.ParseBoolFromEnv("ARGOCD_SERVER_DISABLE_AUTH", false), "Disable client authentication")
|
||||
command.Flags().StringVar(&contentTypes, "api-content-types", "application/json", "Semicolon separated list of allowed content types for non GET api requests. Any content type is allowed if empty.")
|
||||
command.Flags().StringVar(&contentTypes, "api-content-types", env.StringFromEnv("ARGOCD_API_CONTENT_TYPES", "application/json", env.StringFromEnvOpts{AllowEmpty: true}), "Semicolon separated list of allowed content types for non GET api requests. Any content type is allowed if empty.")
|
||||
command.Flags().BoolVar(&enableGZip, "enable-gzip", env.ParseBoolFromEnv("ARGOCD_SERVER_ENABLE_GZIP", true), "Enable GZIP compression")
|
||||
command.AddCommand(cli.NewVersionCmd(cliName))
|
||||
command.Flags().StringVar(&listenHost, "address", env.StringFromEnv("ARGOCD_SERVER_LISTEN_ADDRESS", common.DefaultAddressAPIServer), "Listen on given address")
|
||||
|
||||
@@ -30,6 +30,7 @@ import (
|
||||
appinformers "github.com/argoproj/argo-cd/v2/pkg/client/informers/externalversions"
|
||||
reposerverclient "github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
|
||||
cacheutil "github.com/argoproj/argo-cd/v2/util/cache"
|
||||
appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate"
|
||||
"github.com/argoproj/argo-cd/v2/util/cli"
|
||||
@@ -228,11 +229,12 @@ func diffReconcileResults(res1 reconcileResults, res2 reconcileResults) error {
|
||||
|
||||
func NewReconcileCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var (
|
||||
clientConfig clientcmd.ClientConfig
|
||||
selector string
|
||||
repoServerAddress string
|
||||
outputFormat string
|
||||
refresh bool
|
||||
clientConfig clientcmd.ClientConfig
|
||||
selector string
|
||||
repoServerAddress string
|
||||
outputFormat string
|
||||
refresh bool
|
||||
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts
|
||||
)
|
||||
|
||||
var command = &cobra.Command{
|
||||
@@ -270,7 +272,7 @@ func NewReconcileCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
|
||||
|
||||
appClientset := appclientset.NewForConfigOrDie(cfg)
|
||||
kubeClientset := kubernetes.NewForConfigOrDie(cfg)
|
||||
result, err = reconcileApplications(ctx, kubeClientset, appClientset, namespace, repoServerClient, selector, newLiveStateCache)
|
||||
result, err = reconcileApplications(ctx, kubeClientset, appClientset, namespace, repoServerClient, selector, newLiveStateCache, ignoreNormalizerOpts)
|
||||
errors.CheckError(err)
|
||||
} else {
|
||||
appClientset := appclientset.NewForConfigOrDie(cfg)
|
||||
@@ -285,6 +287,7 @@ func NewReconcileCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command
|
||||
command.Flags().StringVar(&selector, "l", "", "Label selector")
|
||||
command.Flags().StringVar(&outputFormat, "o", "yaml", "Output format (yaml|json)")
|
||||
command.Flags().BoolVar(&refresh, "refresh", false, "If set to true then recalculates apps reconciliation")
|
||||
command.Flags().DurationVar(&ignoreNormalizerOpts.JQExecutionTimeout, "ignore-normalizer-jq-execution-timeout", normalizers.DefaultJQExecutionTimeout, "Set ignore normalizer JQ execution timeout")
|
||||
|
||||
return command
|
||||
}
|
||||
@@ -334,6 +337,7 @@ func reconcileApplications(
|
||||
repoServerClient reposerverclient.Clientset,
|
||||
selector string,
|
||||
createLiveStateCache func(argoDB db.ArgoDB, appInformer kubecache.SharedIndexInformer, settingsMgr *settings.SettingsManager, server *metrics.MetricsServer) cache.LiveStateCache,
|
||||
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts,
|
||||
) ([]appReconcileResult, error) {
|
||||
settingsMgr := settings.NewSettingsManager(ctx, kubeClientset, namespace)
|
||||
argoDB := db.NewDB(namespace, settingsMgr, kubeClientset)
|
||||
@@ -374,7 +378,7 @@ func reconcileApplications(
|
||||
)
|
||||
|
||||
appStateManager := controller.NewAppStateManager(
|
||||
argoDB, appClientset, repoServerClient, namespace, kubeutil.NewKubectl(), settingsMgr, stateCache, projInformer, server, cache, time.Second, argo.NewResourceTracking(), false)
|
||||
argoDB, appClientset, repoServerClient, namespace, kubeutil.NewKubectl(), settingsMgr, stateCache, projInformer, server, cache, time.Second, argo.NewResourceTracking(), false, ignoreNormalizerOpts)
|
||||
|
||||
appsList, err := appClientset.ArgoprojV1alpha1().Applications(namespace).List(ctx, v1.ListOptions{LabelSelector: selector})
|
||||
if err != nil {
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
argocdclient "github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/apiclient/mocks"
|
||||
"github.com/argoproj/argo-cd/v2/test"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
|
||||
"github.com/argoproj/argo-cd/v2/util/db"
|
||||
"github.com/argoproj/argo-cd/v2/util/settings"
|
||||
)
|
||||
@@ -113,6 +114,7 @@ func TestGetReconcileResults_Refresh(t *testing.T) {
|
||||
func(argoDB db.ArgoDB, appInformer cache.SharedIndexInformer, settingsMgr *settings.SettingsManager, server *metrics.MetricsServer) statecache.LiveStateCache {
|
||||
return &liveStateCache
|
||||
},
|
||||
normalizers.IgnoreNormalizerOpts{},
|
||||
)
|
||||
|
||||
if !assert.NoError(t, err) {
|
||||
|
||||
@@ -432,7 +432,7 @@ argocd admin settings resource-overrides ignore-differences ./deploy.yaml --argo
|
||||
// configurations. This requires access to live resources which is not the
|
||||
// purpose of this command. This will just apply jsonPointers and
|
||||
// jqPathExpressions configurations.
|
||||
normalizer, err := normalizers.NewIgnoreNormalizer(nil, overrides)
|
||||
normalizer, err := normalizers.NewIgnoreNormalizer(nil, overrides, normalizers.IgnoreNormalizerOpts{})
|
||||
errors.CheckError(err)
|
||||
|
||||
normalizedRes := res.DeepCopy()
|
||||
@@ -457,6 +457,9 @@ argocd admin settings resource-overrides ignore-differences ./deploy.yaml --argo
|
||||
}
|
||||
|
||||
func NewResourceIgnoreResourceUpdatesCommand(cmdCtx commandContext) *cobra.Command {
|
||||
var (
|
||||
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "ignore-resource-updates RESOURCE_YAML_PATH",
|
||||
Short: "Renders fields excluded from resource updates",
|
||||
@@ -478,7 +481,7 @@ argocd admin settings resource-overrides ignore-resource-updates ./deploy.yaml -
|
||||
return
|
||||
}
|
||||
|
||||
normalizer, err := normalizers.NewIgnoreNormalizer(nil, overrides)
|
||||
normalizer, err := normalizers.NewIgnoreNormalizer(nil, overrides, ignoreNormalizerOpts)
|
||||
errors.CheckError(err)
|
||||
|
||||
normalizedRes := res.DeepCopy()
|
||||
@@ -499,6 +502,7 @@ argocd admin settings resource-overrides ignore-resource-updates ./deploy.yaml -
|
||||
})
|
||||
},
|
||||
}
|
||||
command.Flags().DurationVar(&ignoreNormalizerOpts.JQExecutionTimeout, "ignore-normalizer-jq-execution-timeout", normalizers.DefaultJQExecutionTimeout, "Set ignore normalizer JQ execution timeout")
|
||||
return command
|
||||
}
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/repository"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo"
|
||||
argodiff "github.com/argoproj/argo-cd/v2/util/argo/diff"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
|
||||
"github.com/argoproj/argo-cd/v2/util/cli"
|
||||
"github.com/argoproj/argo-cd/v2/util/errors"
|
||||
"github.com/argoproj/argo-cd/v2/util/git"
|
||||
@@ -964,14 +965,15 @@ type objKeyLiveTarget struct {
|
||||
// NewApplicationDiffCommand returns a new instance of an `argocd app diff` command
|
||||
func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Command {
|
||||
var (
|
||||
refresh bool
|
||||
hardRefresh bool
|
||||
exitCode bool
|
||||
local string
|
||||
revision string
|
||||
localRepoRoot string
|
||||
serverSideGenerate bool
|
||||
localIncludes []string
|
||||
refresh bool
|
||||
hardRefresh bool
|
||||
exitCode bool
|
||||
local string
|
||||
revision string
|
||||
localRepoRoot string
|
||||
serverSideGenerate bool
|
||||
localIncludes []string
|
||||
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts
|
||||
)
|
||||
shortDesc := "Perform a diff against the target and live state."
|
||||
var command = &cobra.Command{
|
||||
@@ -1031,13 +1033,14 @@ func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
defer argoio.Close(conn)
|
||||
cluster, err := clusterIf.Get(ctx, &clusterpkg.ClusterQuery{Name: app.Spec.Destination.Name, Server: app.Spec.Destination.Server})
|
||||
errors.CheckError(err)
|
||||
|
||||
diffOption.local = local
|
||||
diffOption.localRepoRoot = localRepoRoot
|
||||
diffOption.cluster = cluster
|
||||
}
|
||||
}
|
||||
proj := getProject(c, clientOpts, ctx, app.Spec.Project)
|
||||
foundDiffs := findandPrintDiff(ctx, app, proj.Project, resources, argoSettings, diffOption)
|
||||
foundDiffs := findandPrintDiff(ctx, app, proj.Project, resources, argoSettings, diffOption, ignoreNormalizerOpts)
|
||||
if foundDiffs && exitCode {
|
||||
os.Exit(1)
|
||||
}
|
||||
@@ -1051,6 +1054,7 @@ func NewApplicationDiffCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
command.Flags().StringVar(&localRepoRoot, "local-repo-root", "/", "Path to the repository root. Used together with --local allows setting the repository root")
|
||||
command.Flags().BoolVar(&serverSideGenerate, "server-side-generate", false, "Used with --local, this will send your manifests to the server for diffing")
|
||||
command.Flags().StringArrayVar(&localIncludes, "local-include", []string{"*.yaml", "*.yml", "*.json"}, "Used with --server-side-generate, specify patterns of filenames to send. Matching is based on filename and not path.")
|
||||
command.Flags().DurationVar(&ignoreNormalizerOpts.JQExecutionTimeout, "ignore-normalizer-jq-execution-timeout", normalizers.DefaultJQExecutionTimeout, "Set ignore normalizer JQ execution timeout")
|
||||
return command
|
||||
}
|
||||
|
||||
@@ -1065,7 +1069,7 @@ type DifferenceOption struct {
|
||||
}
|
||||
|
||||
// findandPrintDiff ... Prints difference between application current state and state stored in git or locally, returns boolean as true if difference is found else returns false
|
||||
func findandPrintDiff(ctx context.Context, app *argoappv1.Application, proj *argoappv1.AppProject, resources *application.ManagedResourcesResponse, argoSettings *settings.Settings, diffOptions *DifferenceOption) bool {
|
||||
func findandPrintDiff(ctx context.Context, app *argoappv1.Application, proj *argoappv1.AppProject, resources *application.ManagedResourcesResponse, argoSettings *settings.Settings, diffOptions *DifferenceOption, ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts) bool {
|
||||
var foundDiffs bool
|
||||
liveObjs, err := cmdutil.LiveObjects(resources.Items)
|
||||
errors.CheckError(err)
|
||||
@@ -1120,7 +1124,7 @@ func findandPrintDiff(ctx context.Context, app *argoappv1.Application, proj *arg
|
||||
// compareOptions in the protobuf
|
||||
ignoreAggregatedRoles := false
|
||||
diffConfig, err := argodiff.NewDiffConfigBuilder().
|
||||
WithDiffSettings(app.Spec.IgnoreDifferences, overrides, ignoreAggregatedRoles).
|
||||
WithDiffSettings(app.Spec.IgnoreDifferences, overrides, ignoreAggregatedRoles, ignoreNormalizerOpts).
|
||||
WithTracking(argoSettings.AppLabelKey, argoSettings.TrackingMethod).
|
||||
WithNoCache().
|
||||
Build()
|
||||
@@ -1613,6 +1617,8 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
diffChangesConfirm bool
|
||||
projects []string
|
||||
output string
|
||||
appNamespace string
|
||||
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts
|
||||
)
|
||||
var command = &cobra.Command{
|
||||
Use: "sync [APPNAME... | -l selector | --project project-name]",
|
||||
@@ -1837,7 +1843,7 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
fmt.Printf("====== Previewing differences between live and desired state of application %s ======\n", appQualifiedName)
|
||||
|
||||
proj := getProject(c, clientOpts, ctx, app.Spec.Project)
|
||||
foundDiffs = findandPrintDiff(ctx, app, proj.Project, resources, argoSettings, diffOption)
|
||||
foundDiffs = findandPrintDiff(ctx, app, proj.Project, resources, argoSettings, diffOption, ignoreNormalizerOpts)
|
||||
if foundDiffs {
|
||||
if !diffChangesConfirm {
|
||||
yesno := cli.AskToProceed(fmt.Sprintf("Please review changes to application %s shown above. Do you want to continue the sync process? (y/n): ", appQualifiedName))
|
||||
@@ -1895,6 +1901,8 @@ func NewApplicationSyncCommand(clientOpts *argocdclient.ClientOptions) *cobra.Co
|
||||
command.Flags().BoolVar(&diffChanges, "preview-changes", false, "Preview difference against the target and live state before syncing app and wait for user confirmation")
|
||||
command.Flags().StringArrayVar(&projects, "project", []string{}, "Sync apps that belong to the specified projects. This option may be specified repeatedly.")
|
||||
command.Flags().StringVarP(&output, "output", "o", "wide", "Output format. One of: json|yaml|wide|tree|tree=detailed")
|
||||
command.Flags().StringVarP(&appNamespace, "app-namespace", "N", "", "Only sync an application in namespace")
|
||||
command.Flags().DurationVar(&ignoreNormalizerOpts.JQExecutionTimeout, "ignore-normalizer-jq-execution-timeout", normalizers.DefaultJQExecutionTimeout, "Set ignore normalizer JQ execution timeout")
|
||||
return command
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo"
|
||||
argodiff "github.com/argoproj/argo-cd/v2/util/argo/diff"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
|
||||
"github.com/argoproj/argo-cd/v2/util/env"
|
||||
|
||||
appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate"
|
||||
@@ -127,6 +128,7 @@ type ApplicationController struct {
|
||||
clusterFilter func(cluster *appv1.Cluster) bool
|
||||
projByNameCache sync.Map
|
||||
applicationNamespaces []string
|
||||
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts
|
||||
}
|
||||
|
||||
// NewApplicationController creates new instance of ApplicationController.
|
||||
@@ -148,6 +150,7 @@ func NewApplicationController(
|
||||
persistResourceHealth bool,
|
||||
clusterFilter func(cluster *appv1.Cluster) bool,
|
||||
applicationNamespaces []string,
|
||||
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts,
|
||||
) (*ApplicationController, error) {
|
||||
log.Infof("appResyncPeriod=%v, appHardResyncPeriod=%v", appResyncPeriod, appHardResyncPeriod)
|
||||
db := db.NewDB(namespace, settingsMgr, kubeClientset)
|
||||
@@ -173,6 +176,7 @@ func NewApplicationController(
|
||||
clusterFilter: clusterFilter,
|
||||
projByNameCache: sync.Map{},
|
||||
applicationNamespaces: applicationNamespaces,
|
||||
ignoreNormalizerOpts: ignoreNormalizerOpts,
|
||||
}
|
||||
if kubectlParallelismLimit > 0 {
|
||||
ctrl.kubectlSemaphore = semaphore.NewWeighted(kubectlParallelismLimit)
|
||||
@@ -247,7 +251,7 @@ func NewApplicationController(
|
||||
}
|
||||
}
|
||||
stateCache := statecache.NewLiveStateCache(db, appInformer, ctrl.settingsMgr, kubectl, ctrl.metricsServer, ctrl.handleObjectUpdated, clusterFilter, argo.NewResourceTracking())
|
||||
appStateManager := NewAppStateManager(db, applicationClientset, repoClientset, namespace, kubectl, ctrl.settingsMgr, stateCache, projInformer, ctrl.metricsServer, argoCache, ctrl.statusRefreshTimeout, argo.NewResourceTracking(), persistResourceHealth)
|
||||
appStateManager := NewAppStateManager(db, applicationClientset, repoClientset, namespace, kubectl, ctrl.settingsMgr, stateCache, projInformer, ctrl.metricsServer, argoCache, ctrl.statusRefreshTimeout, argo.NewResourceTracking(), persistResourceHealth, ignoreNormalizerOpts)
|
||||
ctrl.appInformer = appInformer
|
||||
ctrl.appLister = appLister
|
||||
ctrl.projInformer = projInformer
|
||||
@@ -698,7 +702,7 @@ func (ctrl *ApplicationController) hideSecretData(app *appv1.Application, compar
|
||||
return nil, fmt.Errorf("error getting cluster cache: %s", err)
|
||||
}
|
||||
diffConfig, err := argodiff.NewDiffConfigBuilder().
|
||||
WithDiffSettings(app.Spec.IgnoreDifferences, resourceOverrides, compareOptions.IgnoreAggregatedRoles).
|
||||
WithDiffSettings(app.Spec.IgnoreDifferences, resourceOverrides, compareOptions.IgnoreAggregatedRoles, ctrl.ignoreNormalizerOpts).
|
||||
WithTracking(appLabelKey, trackingMethod).
|
||||
WithNoCache().
|
||||
WithLogger(logutils.NewLogrusLogger(logutils.NewWithCurrentConfig())).
|
||||
|
||||
@@ -38,6 +38,7 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
mockrepoclient "github.com/argoproj/argo-cd/v2/reposerver/apiclient/mocks"
|
||||
"github.com/argoproj/argo-cd/v2/test"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
|
||||
cacheutil "github.com/argoproj/argo-cd/v2/util/cache"
|
||||
appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate"
|
||||
"github.com/argoproj/argo-cd/v2/util/settings"
|
||||
@@ -123,6 +124,7 @@ func newFakeController(data *fakeData) *ApplicationController {
|
||||
true,
|
||||
nil,
|
||||
data.applicationNamespaces,
|
||||
normalizers.IgnoreNormalizerOpts{},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
20
controller/cache/cache.go
vendored
20
controller/cache/cache.go
vendored
@@ -32,6 +32,7 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application"
|
||||
appv1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
|
||||
"github.com/argoproj/argo-cd/v2/util/db"
|
||||
"github.com/argoproj/argo-cd/v2/util/env"
|
||||
logutils "github.com/argoproj/argo-cd/v2/util/log"
|
||||
@@ -196,14 +197,15 @@ type cacheSettings struct {
|
||||
}
|
||||
|
||||
type liveStateCache struct {
|
||||
db db.ArgoDB
|
||||
appInformer cache.SharedIndexInformer
|
||||
onObjectUpdated ObjectUpdatedHandler
|
||||
kubectl kube.Kubectl
|
||||
settingsMgr *settings.SettingsManager
|
||||
metricsServer *metrics.MetricsServer
|
||||
clusterFilter func(cluster *appv1.Cluster) bool
|
||||
resourceTracking argo.ResourceTracking
|
||||
db db.ArgoDB
|
||||
appInformer cache.SharedIndexInformer
|
||||
onObjectUpdated ObjectUpdatedHandler
|
||||
kubectl kube.Kubectl
|
||||
settingsMgr *settings.SettingsManager
|
||||
metricsServer *metrics.MetricsServer
|
||||
clusterFilter func(cluster *appv1.Cluster) bool
|
||||
resourceTracking argo.ResourceTracking
|
||||
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts
|
||||
|
||||
clusters map[string]clustercache.ClusterCache
|
||||
cacheSettings cacheSettings
|
||||
@@ -486,7 +488,7 @@ func (c *liveStateCache) getCluster(server string) (clustercache.ClusterCache, e
|
||||
gvk := un.GroupVersionKind()
|
||||
|
||||
if cacheSettings.ignoreResourceUpdatesEnabled && shouldHashManifest(appName, gvk) {
|
||||
hash, err := generateManifestHash(un, nil, cacheSettings.resourceOverrides)
|
||||
hash, err := generateManifestHash(un, nil, cacheSettings.resourceOverrides, c.ignoreNormalizerOpts)
|
||||
if err != nil {
|
||||
log.Errorf("Failed to generate manifest hash: %v", err)
|
||||
} else {
|
||||
|
||||
4
controller/cache/info.go
vendored
4
controller/cache/info.go
vendored
@@ -390,8 +390,8 @@ func populateHostNodeInfo(un *unstructured.Unstructured, res *ResourceInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
func generateManifestHash(un *unstructured.Unstructured, ignores []v1alpha1.ResourceIgnoreDifferences, overrides map[string]v1alpha1.ResourceOverride) (string, error) {
|
||||
normalizer, err := normalizers.NewIgnoreNormalizer(ignores, overrides)
|
||||
func generateManifestHash(un *unstructured.Unstructured, ignores []v1alpha1.ResourceIgnoreDifferences, overrides map[string]v1alpha1.ResourceOverride, opts normalizers.IgnoreNormalizerOpts) (string, error) {
|
||||
normalizer, err := normalizers.NewIgnoreNormalizer(ignores, overrides, opts)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error creating normalizer: %w", err)
|
||||
}
|
||||
|
||||
3
controller/cache/info_test.go
vendored
3
controller/cache/info_test.go
vendored
@@ -16,6 +16,7 @@ import (
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
|
||||
)
|
||||
|
||||
func strToUnstructured(jsonStr string) *unstructured.Unstructured {
|
||||
@@ -749,7 +750,7 @@ func TestManifestHash(t *testing.T) {
|
||||
|
||||
expected := hash(data)
|
||||
|
||||
hash, err := generateManifestHash(manifest, ignores, nil)
|
||||
hash, err := generateManifestHash(manifest, ignores, nil, normalizers.IgnoreNormalizerOpts{})
|
||||
assert.Equal(t, expected, hash)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
|
||||
@@ -4,11 +4,12 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/diff"
|
||||
"github.com/argoproj/gitops-engine/pkg/health"
|
||||
"github.com/argoproj/gitops-engine/pkg/sync"
|
||||
@@ -32,6 +33,7 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo"
|
||||
argodiff "github.com/argoproj/argo-cd/v2/util/argo/diff"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
|
||||
appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate"
|
||||
"github.com/argoproj/argo-cd/v2/util/db"
|
||||
"github.com/argoproj/argo-cd/v2/util/gpg"
|
||||
@@ -105,6 +107,7 @@ type appStateManager struct {
|
||||
statusRefreshTimeout time.Duration
|
||||
resourceTracking argo.ResourceTracking
|
||||
persistResourceHealth bool
|
||||
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts
|
||||
}
|
||||
|
||||
// getRepoObjs will generate the manifests for the given application delegating the
|
||||
@@ -565,7 +568,7 @@ func (m *appStateManager) CompareAppState(app *v1alpha1.Application, project *v1
|
||||
useDiffCache := useDiffCache(noCache, manifestInfos, sources, app, manifestRevisions, m.statusRefreshTimeout, logCtx)
|
||||
|
||||
diffConfigBuilder := argodiff.NewDiffConfigBuilder().
|
||||
WithDiffSettings(app.Spec.IgnoreDifferences, resourceOverrides, compareOptions.IgnoreAggregatedRoles).
|
||||
WithDiffSettings(app.Spec.IgnoreDifferences, resourceOverrides, compareOptions.IgnoreAggregatedRoles, m.ignoreNormalizerOpts).
|
||||
WithTracking(appLabelKey, string(trackingMethod))
|
||||
|
||||
if useDiffCache {
|
||||
@@ -871,6 +874,7 @@ func NewAppStateManager(
|
||||
statusRefreshTimeout time.Duration,
|
||||
resourceTracking argo.ResourceTracking,
|
||||
persistResourceHealth bool,
|
||||
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts,
|
||||
) AppStateManager {
|
||||
return &appStateManager{
|
||||
liveStateCache: liveStateCache,
|
||||
@@ -886,6 +890,7 @@ func NewAppStateManager(
|
||||
statusRefreshTimeout: statusRefreshTimeout,
|
||||
resourceTracking: resourceTracking,
|
||||
persistResourceHealth: persistResourceHealth,
|
||||
ignoreNormalizerOpts: ignoreNormalizerOpts,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
@@ -10,6 +9,7 @@ import (
|
||||
"time"
|
||||
|
||||
cdcommon "github.com/argoproj/argo-cd/v2/common"
|
||||
"k8s.io/apimachinery/pkg/util/strategicpatch"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/sync"
|
||||
"github.com/argoproj/gitops-engine/pkg/sync/common"
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/managedfields"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/kubectl/pkg/util/openapi"
|
||||
|
||||
"github.com/argoproj/argo-cd/v2/controller/metrics"
|
||||
@@ -370,11 +371,10 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha
|
||||
}
|
||||
}
|
||||
|
||||
// normalizeTargetResources will apply the diff normalization in all live and target resources.
|
||||
// Then it calculates the merge patch between the normalized live and the current live resources.
|
||||
// Finally it applies the merge patch in the normalized target resources. This is done to ensure
|
||||
// that target resources have the same ignored diff fields values from live ones to avoid them to
|
||||
// be applied in the cluster. Returns the list of normalized target resources.
|
||||
// normalizeTargetResources modifies target resources to ensure ignored fields are not touched during synchronization:
|
||||
// - applies normalization to the target resources based on the live resources
|
||||
// - copies ignored fields from the matching live resources: apply normalizer to the live resource,
|
||||
// calculates the patch performed by normalizer and applies the patch to the target resource
|
||||
func normalizeTargetResources(cr *comparisonResult) ([]*unstructured.Unstructured, error) {
|
||||
// normalize live and target resources
|
||||
normalized, err := diff.Normalize(cr.reconciliationResult.Live, cr.reconciliationResult.Target, cr.diffConfig)
|
||||
@@ -393,94 +393,35 @@ func normalizeTargetResources(cr *comparisonResult) ([]*unstructured.Unstructure
|
||||
patchedTargets = append(patchedTargets, originalTarget)
|
||||
continue
|
||||
}
|
||||
// calculate targetPatch between normalized and target resource
|
||||
targetPatch, err := getMergePatch(normalizedTarget, originalTarget)
|
||||
|
||||
var lookupPatchMeta *strategicpatch.PatchMetaFromStruct
|
||||
versionedObject, err := scheme.Scheme.New(normalizedTarget.GroupVersionKind())
|
||||
if err == nil {
|
||||
meta, err := strategicpatch.NewPatchMetaFromStruct(versionedObject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
lookupPatchMeta = &meta
|
||||
}
|
||||
|
||||
livePatch, err := getMergePatch(normalized.Lives[idx], live, lookupPatchMeta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// check if there is a patch to apply. An empty patch is identified by a '{}' string.
|
||||
if len(targetPatch) > 2 {
|
||||
livePatch, err := getMergePatch(normalized.Lives[idx], live)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// generate a minimal patch that uses the fields from targetPatch (template)
|
||||
// with livePatch values
|
||||
patch, err := compilePatch(targetPatch, livePatch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
normalizedTarget, err = applyMergePatch(normalizedTarget, patch)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// if there is no patch just use the original target
|
||||
normalizedTarget = originalTarget
|
||||
normalizedTarget, err = applyMergePatch(normalizedTarget, livePatch, versionedObject)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
patchedTargets = append(patchedTargets, normalizedTarget)
|
||||
}
|
||||
return patchedTargets, nil
|
||||
}
|
||||
|
||||
// compilePatch will generate a patch using the fields from templatePatch with
|
||||
// the values from valuePatch.
|
||||
func compilePatch(templatePatch, valuePatch []byte) ([]byte, error) {
|
||||
templateMap := make(map[string]interface{})
|
||||
err := json.Unmarshal(templatePatch, &templateMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
valueMap := make(map[string]interface{})
|
||||
err = json.Unmarshal(valuePatch, &valueMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resultMap := intersectMap(templateMap, valueMap)
|
||||
return json.Marshal(resultMap)
|
||||
}
|
||||
|
||||
// intersectMap will return map with the fields intersection from the 2 provided
|
||||
// maps populated with the valueMap values.
|
||||
func intersectMap(templateMap, valueMap map[string]interface{}) map[string]interface{} {
|
||||
result := make(map[string]interface{})
|
||||
for k, v := range templateMap {
|
||||
if innerTMap, ok := v.(map[string]interface{}); ok {
|
||||
if innerVMap, ok := valueMap[k].(map[string]interface{}); ok {
|
||||
result[k] = intersectMap(innerTMap, innerVMap)
|
||||
}
|
||||
} else if innerTSlice, ok := v.([]interface{}); ok {
|
||||
if innerVSlice, ok := valueMap[k].([]interface{}); ok {
|
||||
items := []interface{}{}
|
||||
for idx, innerTSliceValue := range innerTSlice {
|
||||
if idx < len(innerVSlice) {
|
||||
if tSliceValueMap, ok := innerTSliceValue.(map[string]interface{}); ok {
|
||||
if vSliceValueMap, ok := innerVSlice[idx].(map[string]interface{}); ok {
|
||||
item := intersectMap(tSliceValueMap, vSliceValueMap)
|
||||
items = append(items, item)
|
||||
}
|
||||
} else {
|
||||
items = append(items, innerVSlice[idx])
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(items) > 0 {
|
||||
result[k] = items
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if _, ok := valueMap[k]; ok {
|
||||
result[k] = valueMap[k]
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// getMergePatch calculates and returns the patch between the original and the
|
||||
// modified unstructures.
|
||||
func getMergePatch(original, modified *unstructured.Unstructured) ([]byte, error) {
|
||||
func getMergePatch(original, modified *unstructured.Unstructured, lookupPatchMeta *strategicpatch.PatchMetaFromStruct) ([]byte, error) {
|
||||
originalJSON, err := original.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -489,20 +430,30 @@ func getMergePatch(original, modified *unstructured.Unstructured) ([]byte, error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if lookupPatchMeta != nil {
|
||||
return strategicpatch.CreateThreeWayMergePatch(modifiedJSON, modifiedJSON, originalJSON, lookupPatchMeta, true)
|
||||
}
|
||||
|
||||
return jsonpatch.CreateMergePatch(originalJSON, modifiedJSON)
|
||||
}
|
||||
|
||||
// applyMergePatch will apply the given patch in the obj and return the patched
|
||||
// unstructure.
|
||||
func applyMergePatch(obj *unstructured.Unstructured, patch []byte) (*unstructured.Unstructured, error) {
|
||||
func applyMergePatch(obj *unstructured.Unstructured, patch []byte, versionedObject interface{}) (*unstructured.Unstructured, error) {
|
||||
originalJSON, err := obj.MarshalJSON()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
patchedJSON, err := jsonpatch.MergePatch(originalJSON, patch)
|
||||
var patchedJSON []byte
|
||||
if versionedObject == nil {
|
||||
patchedJSON, err = jsonpatch.MergePatch(originalJSON, patch)
|
||||
} else {
|
||||
patchedJSON, err = strategicpatch.StrategicMergePatch(originalJSON, patch, versionedObject)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
patchedObj := &unstructured.Unstructured{}
|
||||
_, _, err = unstructured.UnstructuredJSONScheme.Decode(patchedJSON, nil, patchedObj)
|
||||
if err != nil {
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/reposerver/apiclient"
|
||||
"github.com/argoproj/argo-cd/v2/test"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/diff"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
|
||||
)
|
||||
|
||||
func TestPersistRevisionHistory(t *testing.T) {
|
||||
@@ -261,7 +262,7 @@ func TestNormalizeTargetResources(t *testing.T) {
|
||||
setup := func(t *testing.T, ignores []v1alpha1.ResourceIgnoreDifferences) *fixture {
|
||||
t.Helper()
|
||||
dc, err := diff.NewDiffConfigBuilder().
|
||||
WithDiffSettings(ignores, nil, true).
|
||||
WithDiffSettings(ignores, nil, true, normalizers.IgnoreNormalizerOpts{}).
|
||||
WithNoCache().
|
||||
Build()
|
||||
require.NoError(t, err)
|
||||
@@ -386,3 +387,207 @@ func TestNormalizeTargetResources(t *testing.T) {
|
||||
assert.Equal(t, 2, len(containers))
|
||||
})
|
||||
}
|
||||
|
||||
func TestNormalizeTargetResourcesWithList(t *testing.T) {
|
||||
type fixture struct {
|
||||
comparisonResult *comparisonResult
|
||||
}
|
||||
setupHttpProxy := func(t *testing.T, ignores []v1alpha1.ResourceIgnoreDifferences) *fixture {
|
||||
t.Helper()
|
||||
dc, err := diff.NewDiffConfigBuilder().
|
||||
WithDiffSettings(ignores, nil, true, normalizers.IgnoreNormalizerOpts{}).
|
||||
WithNoCache().
|
||||
Build()
|
||||
require.NoError(t, err)
|
||||
live := test.YamlToUnstructured(testdata.LiveHTTPProxy)
|
||||
target := test.YamlToUnstructured(testdata.TargetHTTPProxy)
|
||||
return &fixture{
|
||||
&comparisonResult{
|
||||
reconciliationResult: sync.ReconciliationResult{
|
||||
Live: []*unstructured.Unstructured{live},
|
||||
Target: []*unstructured.Unstructured{target},
|
||||
},
|
||||
diffConfig: dc,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("will properly ignore nested fields within arrays", func(t *testing.T) {
|
||||
// given
|
||||
ignores := []v1alpha1.ResourceIgnoreDifferences{
|
||||
{
|
||||
Group: "projectcontour.io",
|
||||
Kind: "HTTPProxy",
|
||||
JQPathExpressions: []string{".spec.routes[]"},
|
||||
//JSONPointers: []string{"/spec/routes"},
|
||||
},
|
||||
}
|
||||
f := setupHttpProxy(t, ignores)
|
||||
target := test.YamlToUnstructured(testdata.TargetHTTPProxy)
|
||||
f.comparisonResult.reconciliationResult.Target = []*unstructured.Unstructured{target}
|
||||
|
||||
// when
|
||||
patchedTargets, err := normalizeTargetResources(f.comparisonResult)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(f.comparisonResult.reconciliationResult.Live))
|
||||
require.Equal(t, 1, len(f.comparisonResult.reconciliationResult.Target))
|
||||
require.Equal(t, 1, len(patchedTargets))
|
||||
|
||||
// live should have 1 entry
|
||||
require.Equal(t, 1, len(dig[[]any](f.comparisonResult.reconciliationResult.Live[0].Object, []interface{}{"spec", "routes", 0, "rateLimitPolicy", "global", "descriptors"})))
|
||||
// assert some arbitrary field to show `entries[0]` is not an empty object
|
||||
require.Equal(t, "sample-header", dig[string](f.comparisonResult.reconciliationResult.Live[0].Object, []interface{}{"spec", "routes", 0, "rateLimitPolicy", "global", "descriptors", 0, "entries", 0, "requestHeader", "headerName"}))
|
||||
|
||||
// target has 2 entries
|
||||
require.Equal(t, 2, len(dig[[]any](f.comparisonResult.reconciliationResult.Target[0].Object, []interface{}{"spec", "routes", 0, "rateLimitPolicy", "global", "descriptors", 0, "entries"})))
|
||||
// assert some arbitrary field to show `entries[0]` is not an empty object
|
||||
require.Equal(t, "sample-header", dig[string](f.comparisonResult.reconciliationResult.Target[0].Object, []interface{}{"spec", "routes", 0, "rateLimitPolicy", "global", "descriptors", 0, "entries", 0, "requestHeaderValueMatch", "headers", 0, "name"}))
|
||||
|
||||
// It should be *1* entries in the array
|
||||
require.Equal(t, 1, len(dig[[]any](patchedTargets[0].Object, []interface{}{"spec", "routes", 0, "rateLimitPolicy", "global", "descriptors"})))
|
||||
// and it should NOT equal an empty object
|
||||
require.Len(t, dig[any](patchedTargets[0].Object, []interface{}{"spec", "routes", 0, "rateLimitPolicy", "global", "descriptors", 0, "entries", 0}), 1)
|
||||
|
||||
})
|
||||
t.Run("will correctly set array entries if new entries have been added", func(t *testing.T) {
|
||||
// given
|
||||
ignores := []v1alpha1.ResourceIgnoreDifferences{
|
||||
{
|
||||
Group: "apps",
|
||||
Kind: "Deployment",
|
||||
JQPathExpressions: []string{".spec.template.spec.containers[].env[] | select(.name == \"SOME_ENV_VAR\")"},
|
||||
},
|
||||
}
|
||||
f := setupHttpProxy(t, ignores)
|
||||
live := test.YamlToUnstructured(testdata.LiveDeploymentEnvVarsYaml)
|
||||
target := test.YamlToUnstructured(testdata.TargetDeploymentEnvVarsYaml)
|
||||
f.comparisonResult.reconciliationResult.Live = []*unstructured.Unstructured{live}
|
||||
f.comparisonResult.reconciliationResult.Target = []*unstructured.Unstructured{target}
|
||||
|
||||
// when
|
||||
targets, err := normalizeTargetResources(f.comparisonResult)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(targets))
|
||||
containers, ok, err := unstructured.NestedSlice(targets[0].Object, "spec", "template", "spec", "containers")
|
||||
require.NoError(t, err)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, 1, len(containers))
|
||||
|
||||
ports := containers[0].(map[string]interface{})["ports"].([]interface{})
|
||||
assert.Equal(t, 1, len(ports))
|
||||
|
||||
env := containers[0].(map[string]interface{})["env"].([]interface{})
|
||||
assert.Equal(t, 3, len(env))
|
||||
|
||||
first := env[0]
|
||||
second := env[1]
|
||||
third := env[2]
|
||||
|
||||
// Currently the defined order at this time is the insertion order of the target manifest.
|
||||
assert.Equal(t, "SOME_ENV_VAR", first.(map[string]interface{})["name"])
|
||||
assert.Equal(t, "some_value", first.(map[string]interface{})["value"])
|
||||
|
||||
assert.Equal(t, "SOME_OTHER_ENV_VAR", second.(map[string]interface{})["name"])
|
||||
assert.Equal(t, "some_other_value", second.(map[string]interface{})["value"])
|
||||
|
||||
assert.Equal(t, "YET_ANOTHER_ENV_VAR", third.(map[string]interface{})["name"])
|
||||
assert.Equal(t, "yet_another_value", third.(map[string]interface{})["value"])
|
||||
})
|
||||
|
||||
t.Run("ignore-deployment-image-replicas-changes-additive", func(t *testing.T) {
|
||||
// given
|
||||
|
||||
ignores := []v1alpha1.ResourceIgnoreDifferences{
|
||||
{
|
||||
Group: "apps",
|
||||
Kind: "Deployment",
|
||||
JSONPointers: []string{"/spec/replicas"},
|
||||
}, {
|
||||
Group: "apps",
|
||||
Kind: "Deployment",
|
||||
JQPathExpressions: []string{".spec.template.spec.containers[].image"},
|
||||
},
|
||||
}
|
||||
f := setupHttpProxy(t, ignores)
|
||||
live := test.YamlToUnstructured(testdata.MinimalImageReplicaDeploymentYaml)
|
||||
target := test.YamlToUnstructured(testdata.AdditionalImageReplicaDeploymentYaml)
|
||||
f.comparisonResult.reconciliationResult.Live = []*unstructured.Unstructured{live}
|
||||
f.comparisonResult.reconciliationResult.Target = []*unstructured.Unstructured{target}
|
||||
|
||||
// when
|
||||
targets, err := normalizeTargetResources(f.comparisonResult)
|
||||
|
||||
// then
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 1, len(targets))
|
||||
metadata, ok, err := unstructured.NestedMap(targets[0].Object, "metadata")
|
||||
require.NoError(t, err)
|
||||
require.True(t, ok)
|
||||
labels, ok := metadata["labels"].(map[string]interface{})
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, 2, len(labels))
|
||||
assert.Equal(t, "web", labels["appProcess"])
|
||||
|
||||
spec, ok, err := unstructured.NestedMap(targets[0].Object, "spec")
|
||||
require.NoError(t, err)
|
||||
require.True(t, ok)
|
||||
|
||||
assert.Equal(t, int64(1), spec["replicas"])
|
||||
|
||||
template, ok := spec["template"].(map[string]interface{})
|
||||
require.True(t, ok)
|
||||
|
||||
tMetadata, ok := template["metadata"].(map[string]interface{})
|
||||
require.True(t, ok)
|
||||
tLabels, ok := tMetadata["labels"].(map[string]interface{})
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, 2, len(tLabels))
|
||||
assert.Equal(t, "web", tLabels["appProcess"])
|
||||
|
||||
tSpec, ok := template["spec"].(map[string]interface{})
|
||||
require.True(t, ok)
|
||||
containers, ok, err := unstructured.NestedSlice(tSpec, "containers")
|
||||
require.NoError(t, err)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, 1, len(containers))
|
||||
|
||||
first := containers[0].(map[string]interface{})
|
||||
assert.Equal(t, "alpine:3", first["image"])
|
||||
|
||||
resources, ok := first["resources"].(map[string]interface{})
|
||||
require.True(t, ok)
|
||||
requests, ok := resources["requests"].(map[string]interface{})
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "400m", requests["cpu"])
|
||||
|
||||
env, ok, err := unstructured.NestedSlice(first, "env")
|
||||
require.NoError(t, err)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, 1, len(env))
|
||||
|
||||
env0 := env[0].(map[string]interface{})
|
||||
assert.Equal(t, "EV", env0["name"])
|
||||
assert.Equal(t, "here", env0["value"])
|
||||
})
|
||||
}
|
||||
|
||||
func dig[T any](obj interface{}, path []interface{}) T {
|
||||
i := obj
|
||||
|
||||
for _, segment := range path {
|
||||
switch segment.(type) {
|
||||
case int:
|
||||
i = i.([]interface{})[segment.(int)]
|
||||
case string:
|
||||
i = i.(map[string]interface{})[segment.(string)]
|
||||
default:
|
||||
panic("invalid path for object")
|
||||
}
|
||||
}
|
||||
|
||||
return i.(T)
|
||||
}
|
||||
|
||||
28
controller/testdata/additional-image-replicas-deployment.yaml
vendored
Normal file
28
controller/testdata/additional-image-replicas-deployment.yaml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: client
|
||||
appProcess: web
|
||||
name: client
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: client
|
||||
strategy: {}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: client
|
||||
appProcess: web
|
||||
spec:
|
||||
containers:
|
||||
- image: alpine:2
|
||||
name: alpine
|
||||
resources:
|
||||
requests:
|
||||
cpu: 400m
|
||||
env:
|
||||
- name: EV
|
||||
value: here
|
||||
18
controller/testdata/data.go
vendored
18
controller/testdata/data.go
vendored
@@ -11,4 +11,22 @@ var (
|
||||
|
||||
//go:embed target-deployment-new-entries.yaml
|
||||
TargetDeploymentNewEntries string
|
||||
|
||||
//go:embed live-httpproxy.yaml
|
||||
LiveHTTPProxy string
|
||||
|
||||
//go:embed target-httpproxy.yaml
|
||||
TargetHTTPProxy string
|
||||
|
||||
//go:embed live-deployment-env-vars.yaml
|
||||
LiveDeploymentEnvVarsYaml string
|
||||
|
||||
//go:embed target-deployment-env-vars.yaml
|
||||
TargetDeploymentEnvVarsYaml string
|
||||
|
||||
//go:embed minimal-image-replicas-deployment.yaml
|
||||
MinimalImageReplicaDeploymentYaml string
|
||||
|
||||
//go:embed additional-image-replicas-deployment.yaml
|
||||
AdditionalImageReplicaDeploymentYaml string
|
||||
)
|
||||
|
||||
177
controller/testdata/live-deployment-env-vars.yaml
vendored
Normal file
177
controller/testdata/live-deployment-env-vars.yaml
vendored
Normal file
@@ -0,0 +1,177 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
argocd.argoproj.io/tracking-id: 'guestbook:apps/Deployment:default/kustomize-guestbook-ui'
|
||||
deployment.kubernetes.io/revision: '9'
|
||||
iksm-version: '2.0'
|
||||
kubectl.kubernetes.io/last-applied-configuration: >
|
||||
{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{"argocd.argoproj.io/tracking-id":"guestbook:apps/Deployment:default/kustomize-guestbook-ui","iksm-version":"2.0"},"name":"kustomize-guestbook-ui","namespace":"default"},"spec":{"replicas":4,"revisionHistoryLimit":3,"selector":{"matchLabels":{"app":"guestbook-ui"}},"template":{"metadata":{"labels":{"app":"guestbook-ui"}},"spec":{"containers":[{"env":[{"name":"SOME_ENV_VAR","value":"some_value"}],"image":"gcr.io/heptio-images/ks-guestbook-demo:0.1","name":"guestbook-ui","ports":[{"containerPort":80}],"resources":{"requests":{"cpu":"50m","memory":"100Mi"}}}]}}}}
|
||||
creationTimestamp: '2022-01-05T15:45:21Z'
|
||||
generation: 119
|
||||
managedFields:
|
||||
- apiVersion: apps/v1
|
||||
fieldsType: FieldsV1
|
||||
fieldsV1:
|
||||
'f:metadata':
|
||||
'f:annotations':
|
||||
'f:iksm-version': {}
|
||||
manager: janitor
|
||||
operation: Apply
|
||||
time: '2022-01-06T18:21:04Z'
|
||||
- apiVersion: apps/v1
|
||||
fieldsType: FieldsV1
|
||||
fieldsV1:
|
||||
'f:metadata':
|
||||
'f:annotations':
|
||||
.: {}
|
||||
'f:argocd.argoproj.io/tracking-id': {}
|
||||
'f:kubectl.kubernetes.io/last-applied-configuration': {}
|
||||
'f:spec':
|
||||
'f:progressDeadlineSeconds': {}
|
||||
'f:replicas': {}
|
||||
'f:revisionHistoryLimit': {}
|
||||
'f:selector': {}
|
||||
'f:strategy':
|
||||
'f:rollingUpdate':
|
||||
.: {}
|
||||
'f:maxSurge': {}
|
||||
'f:maxUnavailable': {}
|
||||
'f:type': {}
|
||||
'f:template':
|
||||
'f:metadata':
|
||||
'f:labels':
|
||||
.: {}
|
||||
'f:app': {}
|
||||
'f:spec':
|
||||
'f:containers':
|
||||
'k:{"name":"guestbook-ui"}':
|
||||
.: {}
|
||||
'f:env':
|
||||
.: {}
|
||||
'k:{"name":"SOME_ENV_VAR"}':
|
||||
.: {}
|
||||
'f:name': {}
|
||||
'f:value': {}
|
||||
'f:image': {}
|
||||
'f:imagePullPolicy': {}
|
||||
'f:name': {}
|
||||
'f:ports':
|
||||
.: {}
|
||||
'k:{"containerPort":80,"protocol":"TCP"}':
|
||||
.: {}
|
||||
'f:containerPort': {}
|
||||
'f:protocol': {}
|
||||
'f:resources':
|
||||
.: {}
|
||||
'f:requests':
|
||||
.: {}
|
||||
'f:cpu': {}
|
||||
'f:memory': {}
|
||||
'f:terminationMessagePath': {}
|
||||
'f:terminationMessagePolicy': {}
|
||||
'f:dnsPolicy': {}
|
||||
'f:restartPolicy': {}
|
||||
'f:schedulerName': {}
|
||||
'f:securityContext': {}
|
||||
'f:terminationGracePeriodSeconds': {}
|
||||
manager: argocd
|
||||
operation: Update
|
||||
time: '2022-01-06T15:04:15Z'
|
||||
- apiVersion: apps/v1
|
||||
fieldsType: FieldsV1
|
||||
fieldsV1:
|
||||
'f:metadata':
|
||||
'f:annotations':
|
||||
'f:deployment.kubernetes.io/revision': {}
|
||||
'f:status':
|
||||
'f:availableReplicas': {}
|
||||
'f:conditions':
|
||||
.: {}
|
||||
'k:{"type":"Available"}':
|
||||
.: {}
|
||||
'f:lastTransitionTime': {}
|
||||
'f:lastUpdateTime': {}
|
||||
'f:message': {}
|
||||
'f:reason': {}
|
||||
'f:status': {}
|
||||
'f:type': {}
|
||||
'k:{"type":"Progressing"}':
|
||||
.: {}
|
||||
'f:lastTransitionTime': {}
|
||||
'f:lastUpdateTime': {}
|
||||
'f:message': {}
|
||||
'f:reason': {}
|
||||
'f:status': {}
|
||||
'f:type': {}
|
||||
'f:observedGeneration': {}
|
||||
'f:readyReplicas': {}
|
||||
'f:replicas': {}
|
||||
'f:updatedReplicas': {}
|
||||
manager: kube-controller-manager
|
||||
operation: Update
|
||||
time: '2022-01-06T18:15:14Z'
|
||||
name: kustomize-guestbook-ui
|
||||
namespace: default
|
||||
resourceVersion: '8289211'
|
||||
uid: ef253575-ce44-4c5e-84ad-16e81d0df6eb
|
||||
spec:
|
||||
progressDeadlineSeconds: 600
|
||||
replicas: 4
|
||||
revisionHistoryLimit: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: guestbook-ui
|
||||
strategy:
|
||||
rollingUpdate:
|
||||
maxSurge: 25%
|
||||
maxUnavailable: 25%
|
||||
type: RollingUpdate
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
app: guestbook-ui
|
||||
spec:
|
||||
containers:
|
||||
- env:
|
||||
- name: SOME_ENV_VAR
|
||||
value: some_value
|
||||
image: 'gcr.io/heptio-images/ks-guestbook-demo:0.1'
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: guestbook-ui
|
||||
ports:
|
||||
- containerPort: 80
|
||||
protocol: TCP
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 100Mi
|
||||
terminationMessagePath: /dev/termination-log
|
||||
terminationMessagePolicy: File
|
||||
dnsPolicy: ClusterFirst
|
||||
restartPolicy: Always
|
||||
schedulerName: default-scheduler
|
||||
securityContext: {}
|
||||
terminationGracePeriodSeconds: 30
|
||||
status:
|
||||
availableReplicas: 4
|
||||
conditions:
|
||||
- lastTransitionTime: '2022-01-05T22:20:37Z'
|
||||
lastUpdateTime: '2022-01-05T22:43:47Z'
|
||||
message: >-
|
||||
ReplicaSet "kustomize-guestbook-ui-6549d54677" has successfully
|
||||
progressed.
|
||||
reason: NewReplicaSetAvailable
|
||||
status: 'True'
|
||||
type: Progressing
|
||||
- lastTransitionTime: '2022-01-06T18:15:14Z'
|
||||
lastUpdateTime: '2022-01-06T18:15:14Z'
|
||||
message: Deployment has minimum availability.
|
||||
reason: MinimumReplicasAvailable
|
||||
status: 'True'
|
||||
type: Available
|
||||
observedGeneration: 119
|
||||
readyReplicas: 4
|
||||
replicas: 4
|
||||
updatedReplicas: 4
|
||||
14
controller/testdata/live-httpproxy.yaml
vendored
Normal file
14
controller/testdata/live-httpproxy.yaml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
apiVersion: projectcontour.io/v1
|
||||
kind: HTTPProxy
|
||||
metadata:
|
||||
name: my-http-proxy
|
||||
namespace: default
|
||||
spec:
|
||||
routes:
|
||||
- rateLimitPolicy:
|
||||
global:
|
||||
descriptors:
|
||||
- entries:
|
||||
- requestHeader:
|
||||
descriptorKey: sample-key
|
||||
headerName: sample-header
|
||||
21
controller/testdata/minimal-image-replicas-deployment.yaml
vendored
Normal file
21
controller/testdata/minimal-image-replicas-deployment.yaml
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
labels:
|
||||
app: client
|
||||
name: client
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: client
|
||||
strategy: {}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: client
|
||||
spec:
|
||||
containers:
|
||||
- image: alpine:3
|
||||
name: alpine
|
||||
resources: {}
|
||||
35
controller/testdata/target-deployment-env-vars.yaml
vendored
Normal file
35
controller/testdata/target-deployment-env-vars.yaml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
annotations:
|
||||
argocd.argoproj.io/tracking-id: 'guestbook:apps/Deployment:default/kustomize-guestbook-ui'
|
||||
iksm-version: '1.0'
|
||||
name: kustomize-guestbook-ui
|
||||
namespace: default
|
||||
spec:
|
||||
replicas: 1
|
||||
revisionHistoryLimit: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: guestbook-ui
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: guestbook-ui
|
||||
spec:
|
||||
containers:
|
||||
- env:
|
||||
- name: SOME_OTHER_ENV_VAR
|
||||
value: some_other_value
|
||||
- name: YET_ANOTHER_ENV_VAR
|
||||
value: yet_another_value
|
||||
- name: SOME_ENV_VAR
|
||||
value: different_value!
|
||||
image: 'gcr.io/heptio-images/ks-guestbook-demo:0.1'
|
||||
name: guestbook-ui
|
||||
ports:
|
||||
- containerPort: 80
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 100Mi
|
||||
23
controller/testdata/target-httpproxy.yaml
vendored
Normal file
23
controller/testdata/target-httpproxy.yaml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
apiVersion: projectcontour.io/v1
|
||||
kind: HTTPProxy
|
||||
metadata:
|
||||
name: my-http-proxy
|
||||
namespace: default
|
||||
spec:
|
||||
routes:
|
||||
- rateLimitPolicy:
|
||||
global:
|
||||
descriptors:
|
||||
- entries:
|
||||
- requestHeaderValueMatch:
|
||||
headers:
|
||||
- contains: sample-key
|
||||
name: sample-header
|
||||
value: third
|
||||
- requestHeader:
|
||||
descriptorKey: sample-key
|
||||
headerName: sample-header
|
||||
- entries:
|
||||
- requestHeader:
|
||||
descriptorKey: sample-key
|
||||
headerName: sample-header
|
||||
@@ -72,6 +72,9 @@ data:
|
||||
server.rootpath: ""
|
||||
# Directory path that contains additional static assets
|
||||
server.staticassets: "/shared/app"
|
||||
# Semicolon-separated list of content types allowed on non-GET requests. Set an empty string to allow all. Be aware
|
||||
# that allowing content types besides application/json may make your API more vulnerable to CSRF attacks.
|
||||
server.api.content.types: "application/json"
|
||||
|
||||
# Set the logging format. One of: text|json (default "text")
|
||||
server.log.format: "text"
|
||||
|
||||
@@ -15,59 +15,60 @@ argocd-application-controller [flags]
|
||||
### Options
|
||||
|
||||
```
|
||||
--app-hard-resync int Time period in seconds for application hard resync.
|
||||
--app-resync int Time period in seconds for application resync. (default 180)
|
||||
--app-state-cache-expiration duration Cache expiration for app state (default 1h0m0s)
|
||||
--application-namespaces strings List of additional namespaces that applications are allowed to be reconciled from
|
||||
--as string Username to impersonate for the operation
|
||||
--as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups.
|
||||
--as-uid string UID to impersonate for the operation
|
||||
--certificate-authority string Path to a cert file for the certificate authority
|
||||
--client-certificate string Path to a client certificate file for TLS
|
||||
--client-key string Path to a client key file for TLS
|
||||
--cluster string The name of the kubeconfig cluster to use
|
||||
--context string The name of the kubeconfig context to use
|
||||
--default-cache-expiration duration Cache expiration default (default 24h0m0s)
|
||||
--dynamic-cluster-distribution-enabled Enables dynamic cluster distribution.
|
||||
--gloglevel int Set the glog logging level
|
||||
-h, --help help for argocd-application-controller
|
||||
--insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
|
||||
--kubeconfig string Path to a kube config. Only required if out-of-cluster
|
||||
--kubectl-parallelism-limit int Number of allowed concurrent kubectl fork/execs. Any value less than 1 means no limit. (default 20)
|
||||
--logformat string Set the logging format. One of: text|json (default "text")
|
||||
--loglevel string Set the logging level. One of: debug|info|warn|error (default "info")
|
||||
--metrics-application-labels strings List of Application labels that will be added to the argocd_application_labels metric
|
||||
--metrics-cache-expiration duration Prometheus metrics cache expiration (disabled by default. e.g. 24h0m0s)
|
||||
--metrics-port int Start metrics server on given port (default 8082)
|
||||
-n, --namespace string If present, the namespace scope for this CLI request
|
||||
--operation-processors int Number of application operation processors (default 10)
|
||||
--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)
|
||||
--password string Password for basic authentication to the API server
|
||||
--persist-resource-health Enables storing the managed resources health in the Application CRD (default true)
|
||||
--proxy-url string If provided, this URL will be used to connect via proxy
|
||||
--redis string Redis server hostname and port (e.g. argocd-redis:6379).
|
||||
--redis-ca-certificate string Path to Redis server CA certificate (e.g. /etc/certs/redis/ca.crt). If not specified, system trusted CAs will be used for server certificate validation.
|
||||
--redis-client-certificate string Path to Redis client certificate (e.g. /etc/certs/redis/client.crt).
|
||||
--redis-client-key string Path to Redis client key (e.g. /etc/certs/redis/client.crt).
|
||||
--redis-compress string Enable compression for data sent to Redis with the required compression algorithm. (possible values: gzip, none) (default "gzip")
|
||||
--redis-insecure-skip-tls-verify Skip Redis server certificate validation.
|
||||
--redis-use-tls Use TLS when connecting to Redis.
|
||||
--redisdb int Redis database.
|
||||
--repo-server string Repo server address. (default "argocd-repo-server:8081")
|
||||
--repo-server-plaintext Disable TLS on connections to repo server
|
||||
--repo-server-strict-tls Whether to use strict validation of the TLS cert presented by the repo server
|
||||
--repo-server-timeout-seconds int Repo server RPC call timeout seconds. (default 60)
|
||||
--request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0")
|
||||
--self-heal-timeout-seconds int Specifies timeout between application self heal attempts (default 5)
|
||||
--sentinel stringArray Redis sentinel hostname and port (e.g. argocd-redis-ha-announce-0:6379).
|
||||
--sentinelmaster string Redis sentinel master group name. (default "master")
|
||||
--server string The address and port of the Kubernetes API server
|
||||
--sharding-method string Enables choice of sharding method. Supported sharding methods are : [legacy, round-robin] (default "legacy")
|
||||
--status-processors int Number of application status processors (default 20)
|
||||
--tls-server-name string If provided, this name will be used to validate server certificate. If this is not provided, hostname used to contact the server is used.
|
||||
--token string Bearer token for authentication to the API server
|
||||
--user string The name of the kubeconfig user to use
|
||||
--username string Username for basic authentication to the API server
|
||||
--app-hard-resync int Time period in seconds for application hard resync.
|
||||
--app-resync int Time period in seconds for application resync. (default 180)
|
||||
--app-state-cache-expiration duration Cache expiration for app state (default 1h0m0s)
|
||||
--application-namespaces strings List of additional namespaces that applications are allowed to be reconciled from
|
||||
--as string Username to impersonate for the operation
|
||||
--as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups.
|
||||
--as-uid string UID to impersonate for the operation
|
||||
--certificate-authority string Path to a cert file for the certificate authority
|
||||
--client-certificate string Path to a client certificate file for TLS
|
||||
--client-key string Path to a client key file for TLS
|
||||
--cluster string The name of the kubeconfig cluster to use
|
||||
--context string The name of the kubeconfig context to use
|
||||
--default-cache-expiration duration Cache expiration default (default 24h0m0s)
|
||||
--dynamic-cluster-distribution-enabled Enables dynamic cluster distribution.
|
||||
--gloglevel int Set the glog logging level
|
||||
-h, --help help for argocd-application-controller
|
||||
--ignore-normalizer-jq-execution-timeout-seconds duration Set ignore normalizer JQ execution timeout
|
||||
--insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
|
||||
--kubeconfig string Path to a kube config. Only required if out-of-cluster
|
||||
--kubectl-parallelism-limit int Number of allowed concurrent kubectl fork/execs. Any value less than 1 means no limit. (default 20)
|
||||
--logformat string Set the logging format. One of: text|json (default "text")
|
||||
--loglevel string Set the logging level. One of: debug|info|warn|error (default "info")
|
||||
--metrics-application-labels strings List of Application labels that will be added to the argocd_application_labels metric
|
||||
--metrics-cache-expiration duration Prometheus metrics cache expiration (disabled by default. e.g. 24h0m0s)
|
||||
--metrics-port int Start metrics server on given port (default 8082)
|
||||
-n, --namespace string If present, the namespace scope for this CLI request
|
||||
--operation-processors int Number of application operation processors (default 10)
|
||||
--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)
|
||||
--password string Password for basic authentication to the API server
|
||||
--persist-resource-health Enables storing the managed resources health in the Application CRD (default true)
|
||||
--proxy-url string If provided, this URL will be used to connect via proxy
|
||||
--redis string Redis server hostname and port (e.g. argocd-redis:6379).
|
||||
--redis-ca-certificate string Path to Redis server CA certificate (e.g. /etc/certs/redis/ca.crt). If not specified, system trusted CAs will be used for server certificate validation.
|
||||
--redis-client-certificate string Path to Redis client certificate (e.g. /etc/certs/redis/client.crt).
|
||||
--redis-client-key string Path to Redis client key (e.g. /etc/certs/redis/client.crt).
|
||||
--redis-compress string Enable compression for data sent to Redis with the required compression algorithm. (possible values: gzip, none) (default "gzip")
|
||||
--redis-insecure-skip-tls-verify Skip Redis server certificate validation.
|
||||
--redis-use-tls Use TLS when connecting to Redis.
|
||||
--redisdb int Redis database.
|
||||
--repo-server string Repo server address. (default "argocd-repo-server:8081")
|
||||
--repo-server-plaintext Disable TLS on connections to repo server
|
||||
--repo-server-strict-tls Whether to use strict validation of the TLS cert presented by the repo server
|
||||
--repo-server-timeout-seconds int Repo server RPC call timeout seconds. (default 60)
|
||||
--request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0")
|
||||
--self-heal-timeout-seconds int Specifies timeout between application self heal attempts (default 5)
|
||||
--sentinel stringArray Redis sentinel hostname and port (e.g. argocd-redis-ha-announce-0:6379).
|
||||
--sentinelmaster string Redis sentinel master group name. (default "master")
|
||||
--server string The address and port of the Kubernetes API server
|
||||
--sharding-method string Enables choice of sharding method. Supported sharding methods are : [legacy, round-robin] (default "legacy")
|
||||
--status-processors int Number of application status processors (default 20)
|
||||
--tls-server-name string If provided, this name will be used to validate server certificate. If this is not provided, hostname used to contact the server is used.
|
||||
--token string Bearer token for authentication to the API server
|
||||
--user string The name of the kubeconfig user to use
|
||||
--username string Username for basic authentication to the API server
|
||||
```
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ argocd-repo-server [flags]
|
||||
--disable-helm-manifest-max-extracted-size Disable maximum size of helm 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")
|
||||
-h, --help help for argocd-repo-server
|
||||
--logformat string Set the logging format. One of: text|json (default "text")
|
||||
--loglevel string Set the logging level. One of: debug|info|warn|error (default "info")
|
||||
|
||||
@@ -11,30 +11,31 @@ argocd admin app get-reconcile-results PATH [flags]
|
||||
### Options
|
||||
|
||||
```
|
||||
--as string Username to impersonate for the operation
|
||||
--as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups.
|
||||
--as-uid string UID to impersonate for the operation
|
||||
--certificate-authority string Path to a cert file for the certificate authority
|
||||
--client-certificate string Path to a client certificate file for TLS
|
||||
--client-key string Path to a client key file for TLS
|
||||
--cluster string The name of the kubeconfig cluster to use
|
||||
--context string The name of the kubeconfig context to use
|
||||
-h, --help help for get-reconcile-results
|
||||
--insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
|
||||
--kubeconfig string Path to a kube config. Only required if out-of-cluster
|
||||
--l string Label selector
|
||||
-n, --namespace string If present, the namespace scope for this CLI request
|
||||
--o string Output format (yaml|json) (default "yaml")
|
||||
--password string Password for basic authentication to the API server
|
||||
--proxy-url string If provided, this URL will be used to connect via proxy
|
||||
--refresh If set to true then recalculates apps reconciliation
|
||||
--repo-server string Repo server address.
|
||||
--request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0")
|
||||
--server string The address and port of the Kubernetes API server
|
||||
--tls-server-name string If provided, this name will be used to validate server certificate. If this is not provided, hostname used to contact the server is used.
|
||||
--token string Bearer token for authentication to the API server
|
||||
--user string The name of the kubeconfig user to use
|
||||
--username string Username for basic authentication to the API server
|
||||
--as string Username to impersonate for the operation
|
||||
--as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups.
|
||||
--as-uid string UID to impersonate for the operation
|
||||
--certificate-authority string Path to a cert file for the certificate authority
|
||||
--client-certificate string Path to a client certificate file for TLS
|
||||
--client-key string Path to a client key file for TLS
|
||||
--cluster string The name of the kubeconfig cluster to use
|
||||
--context string The name of the kubeconfig context to use
|
||||
-h, --help help for get-reconcile-results
|
||||
--ignore-normalizer-jq-execution-timeout duration Set ignore normalizer JQ execution timeout (default 1s)
|
||||
--insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
|
||||
--kubeconfig string Path to a kube config. Only required if out-of-cluster
|
||||
--l string Label selector
|
||||
-n, --namespace string If present, the namespace scope for this CLI request
|
||||
--o string Output format (yaml|json) (default "yaml")
|
||||
--password string Password for basic authentication to the API server
|
||||
--proxy-url string If provided, this URL will be used to connect via proxy
|
||||
--refresh If set to true then recalculates apps reconciliation
|
||||
--repo-server string Repo server address.
|
||||
--request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0")
|
||||
--server string The address and port of the Kubernetes API server
|
||||
--tls-server-name string If provided, this name will be used to validate server certificate. If this is not provided, hostname used to contact the server is used.
|
||||
--token string Bearer token for authentication to the API server
|
||||
--user string The name of the kubeconfig user to use
|
||||
--username string Username for basic authentication to the API server
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -22,7 +22,8 @@ argocd admin settings resource-overrides ignore-resource-updates ./deploy.yaml -
|
||||
### Options
|
||||
|
||||
```
|
||||
-h, --help help for ignore-resource-updates
|
||||
-h, --help help for ignore-resource-updates
|
||||
--ignore-normalizer-jq-execution-timeout duration Set ignore normalizer JQ execution timeout (default 1s)
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -17,15 +17,16 @@ argocd app diff APPNAME [flags]
|
||||
### Options
|
||||
|
||||
```
|
||||
--exit-code Return non-zero exit code when there is a diff (default true)
|
||||
--hard-refresh Refresh application data as well as target manifests cache
|
||||
-h, --help help for diff
|
||||
--local string Compare live app to a local manifests
|
||||
--local-include stringArray Used with --server-side-generate, specify patterns of filenames to send. Matching is based on filename and not path. (default [*.yaml,*.yml,*.json])
|
||||
--local-repo-root string Path to the repository root. Used together with --local allows setting the repository root (default "/")
|
||||
--refresh Refresh application data when retrieving
|
||||
--revision string Compare live app to a particular revision
|
||||
--server-side-generate Used with --local, this will send your manifests to the server for diffing
|
||||
--exit-code Return non-zero exit code when there is a diff (default true)
|
||||
--hard-refresh Refresh application data as well as target manifests cache
|
||||
-h, --help help for diff
|
||||
--ignore-normalizer-jq-execution-timeout duration Set ignore normalizer JQ execution timeout (default 1s)
|
||||
--local string Compare live app to a local manifests
|
||||
--local-include stringArray Used with --server-side-generate, specify patterns of filenames to send. Matching is based on filename and not path. (default [*.yaml,*.yml,*.json])
|
||||
--local-repo-root string Path to the repository root. Used together with --local allows setting the repository root (default "/")
|
||||
--refresh Refresh application data when retrieving
|
||||
--revision string Compare live app to a particular revision
|
||||
--server-side-generate Used with --local, this will send your manifests to the server for diffing
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -38,31 +38,33 @@ argocd app sync [APPNAME... | -l selector | --project project-name] [flags]
|
||||
### Options
|
||||
|
||||
```
|
||||
--apply-out-of-sync-only Sync only out-of-sync resources
|
||||
--assumeYes Assume yes as answer for all user queries or prompts
|
||||
--async Do not wait for application to sync before continuing
|
||||
--dry-run Preview apply without affecting cluster
|
||||
--force Use a force apply
|
||||
-h, --help help for sync
|
||||
--info stringArray A list of key-value pairs during sync process. These infos will be persisted in app.
|
||||
--label stringArray Sync only specific resources with a label. This option may be specified repeatedly.
|
||||
--local string Path to a local directory. When this flag is present no git queries will be made
|
||||
--local-repo-root string Path to the repository root. Used together with --local allows setting the repository root (default "/")
|
||||
-o, --output string Output format. One of: json|yaml|wide|tree|tree=detailed (default "wide")
|
||||
--preview-changes Preview difference against the target and live state before syncing app and wait for user confirmation
|
||||
--project stringArray Sync apps that belong to the specified projects. This option may be specified repeatedly.
|
||||
--prune Allow deleting unexpected resources
|
||||
--replace Use a kubectl create/replace instead apply
|
||||
--resource stringArray Sync only specific resources as GROUP:KIND:NAME or !GROUP:KIND:NAME. Fields may be blank and '*' can be used. This option may be specified repeatedly
|
||||
--retry-backoff-duration duration Retry backoff base duration. Input needs to be a duration (e.g. 2m, 1h) (default 5s)
|
||||
--retry-backoff-factor int Factor multiplies the base duration after each failed retry (default 2)
|
||||
--retry-backoff-max-duration duration Max retry backoff duration. Input needs to be a duration (e.g. 2m, 1h) (default 3m0s)
|
||||
--retry-limit int Max number of allowed sync retries
|
||||
--revision string Sync to a specific revision. Preserves parameter overrides
|
||||
-l, --selector string Sync apps that match this label. Supports '=', '==', '!=', in, notin, exists & not exists. Matching apps must satisfy all of the specified label constraints.
|
||||
--server-side Use server-side apply while syncing the application
|
||||
--strategy string Sync strategy (one of: apply|hook)
|
||||
--timeout uint Time out after this many seconds
|
||||
-N, --app-namespace string Only sync an application in namespace
|
||||
--apply-out-of-sync-only Sync only out-of-sync resources
|
||||
--assumeYes Assume yes as answer for all user queries or prompts
|
||||
--async Do not wait for application to sync before continuing
|
||||
--dry-run Preview apply without affecting cluster
|
||||
--force Use a force apply
|
||||
-h, --help help for sync
|
||||
--ignore-normalizer-jq-execution-timeout duration Set ignore normalizer JQ execution timeout (default 1s)
|
||||
--info stringArray A list of key-value pairs during sync process. These infos will be persisted in app.
|
||||
--label stringArray Sync only specific resources with a label. This option may be specified repeatedly.
|
||||
--local string Path to a local directory. When this flag is present no git queries will be made
|
||||
--local-repo-root string Path to the repository root. Used together with --local allows setting the repository root (default "/")
|
||||
-o, --output string Output format. One of: json|yaml|wide|tree|tree=detailed (default "wide")
|
||||
--preview-changes Preview difference against the target and live state before syncing app and wait for user confirmation
|
||||
--project stringArray Sync apps that belong to the specified projects. This option may be specified repeatedly.
|
||||
--prune Allow deleting unexpected resources
|
||||
--replace Use a kubectl create/replace instead apply
|
||||
--resource stringArray Sync only specific resources as GROUP:KIND:NAME or !GROUP:KIND:NAME. Fields may be blank and '*' can be used. This option may be specified repeatedly
|
||||
--retry-backoff-duration duration Retry backoff base duration. Input needs to be a duration (e.g. 2m, 1h) (default 5s)
|
||||
--retry-backoff-factor int Factor multiplies the base duration after each failed retry (default 2)
|
||||
--retry-backoff-max-duration duration Max retry backoff duration. Input needs to be a duration (e.g. 2m, 1h) (default 3m0s)
|
||||
--retry-limit int Max number of allowed sync retries
|
||||
--revision string Sync to a specific revision. Preserves parameter overrides
|
||||
-l, --selector string Sync apps that match this label. Supports '=', '==', '!=', in, notin, exists & not exists. Matching apps must satisfy all of the specified label constraints.
|
||||
--server-side Use server-side apply while syncing the application
|
||||
--strategy string Sync strategy (one of: apply|hook)
|
||||
--timeout uint Time out after this many seconds
|
||||
```
|
||||
|
||||
### Options inherited from parent commands
|
||||
|
||||
@@ -182,3 +182,16 @@ data:
|
||||
```
|
||||
|
||||
The list of supported Kubernetes types is available in [diffing_known_types.txt](https://raw.githubusercontent.com/argoproj/argo-cd/master/util/argo/normalizers/diffing_known_types.txt)
|
||||
|
||||
|
||||
### JQ Path expression timeout
|
||||
|
||||
By default, the evaluation of a JQPathExpression is limited to one second. If you encounter a "JQ patch execution timed out" error message due to a complex JQPathExpression that requires more time to evaluate, you can extend the timeout period by configuring the `ignore.normalizer.jq.timeout` setting within the `argocd-cmd-params-cm` ConfigMap.
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: argocd-cmd-params-cm
|
||||
data:
|
||||
ignore.normalizer.jq.timeout: "5s"
|
||||
|
||||
8
go.mod
8
go.mod
@@ -26,6 +26,7 @@ require (
|
||||
github.com/fsnotify/fsnotify v1.6.0
|
||||
github.com/gfleury/go-bitbucket-v1 v0.0.0-20220301131131-8e7ed04b843e
|
||||
github.com/go-git/go-git/v5 v5.11.0
|
||||
github.com/go-jose/go-jose/v3 v3.0.1
|
||||
github.com/go-logr/logr v1.2.4
|
||||
github.com/go-openapi/loads v0.21.2
|
||||
github.com/go-openapi/runtime v0.26.0
|
||||
@@ -85,7 +86,6 @@ require (
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc
|
||||
google.golang.org/grpc v1.56.2
|
||||
google.golang.org/protobuf v1.31.0
|
||||
gopkg.in/square/go-jose.v2 v2.6.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
k8s.io/api v0.24.17
|
||||
@@ -175,7 +175,6 @@ require (
|
||||
github.com/go-errors/errors v1.4.2 // indirect
|
||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||
github.com/go-git/go-billy/v5 v5.5.0 // indirect
|
||||
github.com/go-jose/go-jose/v3 v3.0.0 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/analysis v0.21.4 // indirect
|
||||
github.com/go-openapi/errors v0.20.3 // indirect
|
||||
@@ -233,7 +232,7 @@ require (
|
||||
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/prometheus/client_model v0.3.0 // indirect
|
||||
github.com/prometheus/client_model v0.3.0
|
||||
github.com/prometheus/common v0.42.0 // indirect
|
||||
github.com/prometheus/procfs v0.10.1 // indirect
|
||||
github.com/rivo/uniseg v0.4.4 // indirect
|
||||
@@ -291,6 +290,9 @@ replace (
|
||||
github.com/golang/protobuf => github.com/golang/protobuf v1.4.2
|
||||
github.com/grpc-ecosystem/grpc-gateway => github.com/grpc-ecosystem/grpc-gateway v1.16.0
|
||||
|
||||
// Avoid CVE-2023-46402
|
||||
github.com/whilp/git-urls => github.com/chainguard-dev/git-urls v1.0.2
|
||||
|
||||
// Avoid CVE-2022-3064
|
||||
gopkg.in/yaml.v2 => gopkg.in/yaml.v2 v2.4.0
|
||||
|
||||
|
||||
10
go.sum
10
go.sum
@@ -782,6 +782,8 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1 h1:HD4PLRzjuCVW79mQ0/pdsalOLHJ+FaEoqJLxfltpb2U=
|
||||
github.com/chai2010/gettext-go v0.0.0-20170215093142-bf70f2a70fb1/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw=
|
||||
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/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
@@ -931,8 +933,8 @@ github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lK
|
||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-jose/go-jose/v3 v3.0.0 h1:s6rrhirfEP/CGIoc6p+PZAeogN2SxKav6Wp7+dyMWVo=
|
||||
github.com/go-jose/go-jose/v3 v3.0.0/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
|
||||
github.com/go-jose/go-jose/v3 v3.0.1 h1:pWmKFVtt+Jl0vBZTIpz/eAKwsm6LkIxDVVbFHKkchhA=
|
||||
github.com/go-jose/go-jose/v3 v3.0.1/go.mod h1:RNkWWRld676jZEYoV3+XK8L2ZnNSvIsxFMht0mSX+u8=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
|
||||
@@ -1705,8 +1707,6 @@ github.com/vmihailenco/msgpack/v5 v5.3.4 h1:qMKAwOV+meBw2Y8k9cVwAy7qErtYCwBzZ2el
|
||||
github.com/vmihailenco/msgpack/v5 v5.3.4/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||
github.com/whilp/git-urls v1.0.0 h1:95f6UMWN5FKW71ECsXRUd3FVYiXdrE7aX4NZKcPmIjU=
|
||||
github.com/whilp/git-urls v1.0.0/go.mod h1:J16SAmobsqc3Qcy98brfl5f5+e0clUvg1krgwk/qCfE=
|
||||
github.com/xanzy/go-gitlab v0.91.1 h1:gnV57IPGYywWer32oXKBcdmc8dVxeKl3AauV8Bu17rw=
|
||||
github.com/xanzy/go-gitlab v0.91.1/go.mod h1:5ryv+MnpZStBH8I/77HuQBsMbBGANtVpLWC15qOjWAw=
|
||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
@@ -2612,8 +2612,6 @@ gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/retry.v1 v1.0.3 h1:a9CArYczAVv6Qs6VGoLMio99GEs7kY9UzSF9+LD+iGs=
|
||||
gopkg.in/retry.v1 v1.0.3/go.mod h1:FJkXmWiMaAo7xB+xhvDF59zhfjDWyzmyAxiT4dB688g=
|
||||
gopkg.in/square/go-jose.v2 v2.6.0 h1:NGk74WTnPKBNUhNzQX7PYcTLUjoq7mzKk2OKbvwk2iI=
|
||||
gopkg.in/square/go-jose.v2 v2.6.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
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=
|
||||
|
||||
@@ -155,6 +155,12 @@ spec:
|
||||
name: argocd-cmd-params-cm
|
||||
key: controller.kubectl.parallelism.limit
|
||||
optional: true
|
||||
- name: ARGOCD_IGNORE_NORMALIZER_JQ_TIMEOUT
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: controller.ignore.normalizer.jq.timeout
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:latest
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
|
||||
@@ -5,7 +5,7 @@ kind: Kustomization
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v2.9.4
|
||||
newTag: v2.9.13
|
||||
resources:
|
||||
- ./application-controller
|
||||
- ./dex
|
||||
|
||||
@@ -23,7 +23,7 @@ spec:
|
||||
serviceAccountName: argocd-redis
|
||||
containers:
|
||||
- name: redis
|
||||
image: redis:7.0.11-alpine
|
||||
image: redis:7.0.15-alpine
|
||||
imagePullPolicy: Always
|
||||
args:
|
||||
- "--save"
|
||||
|
||||
@@ -25,136 +25,136 @@ spec:
|
||||
env:
|
||||
- name: ARGOCD_SERVER_INSECURE
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.insecure
|
||||
optional: true
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.insecure
|
||||
optional: true
|
||||
- name: ARGOCD_SERVER_BASEHREF
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.basehref
|
||||
optional: true
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.basehref
|
||||
optional: true
|
||||
- name: ARGOCD_SERVER_ROOTPATH
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.rootpath
|
||||
optional: true
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.rootpath
|
||||
optional: true
|
||||
- name: ARGOCD_SERVER_LOGFORMAT
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.log.format
|
||||
optional: true
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.log.format
|
||||
optional: true
|
||||
- name: ARGOCD_SERVER_LOG_LEVEL
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.log.level
|
||||
optional: true
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.log.level
|
||||
optional: true
|
||||
- name: ARGOCD_SERVER_REPO_SERVER
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: repo.server
|
||||
optional: true
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: repo.server
|
||||
optional: true
|
||||
- name: ARGOCD_SERVER_DEX_SERVER
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.dex.server
|
||||
optional: true
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.dex.server
|
||||
optional: true
|
||||
- name: ARGOCD_SERVER_DISABLE_AUTH
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.disable.auth
|
||||
optional: true
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.disable.auth
|
||||
optional: true
|
||||
- name: ARGOCD_SERVER_ENABLE_GZIP
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.enable.gzip
|
||||
optional: true
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.enable.gzip
|
||||
optional: true
|
||||
- name: ARGOCD_SERVER_REPO_SERVER_TIMEOUT_SECONDS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.repo.server.timeout.seconds
|
||||
optional: true
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.repo.server.timeout.seconds
|
||||
optional: true
|
||||
- name: ARGOCD_SERVER_X_FRAME_OPTIONS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.x.frame.options
|
||||
optional: true
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.x.frame.options
|
||||
optional: true
|
||||
- name: ARGOCD_SERVER_CONTENT_SECURITY_POLICY
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.content.security.policy
|
||||
optional: true
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.content.security.policy
|
||||
optional: true
|
||||
- name: ARGOCD_SERVER_REPO_SERVER_PLAINTEXT
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.repo.server.plaintext
|
||||
optional: true
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.repo.server.plaintext
|
||||
optional: true
|
||||
- name: ARGOCD_SERVER_REPO_SERVER_STRICT_TLS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.repo.server.strict.tls
|
||||
optional: true
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.repo.server.strict.tls
|
||||
optional: true
|
||||
- name: ARGOCD_SERVER_DEX_SERVER_PLAINTEXT
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.dex.server.plaintext
|
||||
optional: true
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.dex.server.plaintext
|
||||
optional: true
|
||||
- name: ARGOCD_SERVER_DEX_SERVER_STRICT_TLS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.dex.server.strict.tls
|
||||
optional: true
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.dex.server.strict.tls
|
||||
optional: true
|
||||
- name: ARGOCD_TLS_MIN_VERSION
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.tls.minversion
|
||||
optional: true
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.tls.minversion
|
||||
optional: true
|
||||
- name: ARGOCD_TLS_MAX_VERSION
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.tls.maxversion
|
||||
optional: true
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.tls.maxversion
|
||||
optional: true
|
||||
- name: ARGOCD_TLS_CIPHERS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.tls.ciphers
|
||||
optional: true
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.tls.ciphers
|
||||
optional: true
|
||||
- name: ARGOCD_SERVER_CONNECTION_STATUS_CACHE_EXPIRATION
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.connection.status.cache.expiration
|
||||
optional: true
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.connection.status.cache.expiration
|
||||
optional: true
|
||||
- name: ARGOCD_SERVER_OIDC_CACHE_EXPIRATION
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.oidc.cache.expiration
|
||||
optional: true
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.oidc.cache.expiration
|
||||
optional: true
|
||||
- name: ARGOCD_SERVER_LOGIN_ATTEMPTS_EXPIRATION
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.login.attempts.expiration
|
||||
optional: true
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.login.attempts.expiration
|
||||
optional: true
|
||||
- name: ARGOCD_SERVER_STATIC_ASSETS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
@@ -163,16 +163,16 @@ spec:
|
||||
optional: true
|
||||
- name: ARGOCD_APP_STATE_CACHE_EXPIRATION
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.app.state.cache.expiration
|
||||
optional: true
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.app.state.cache.expiration
|
||||
optional: true
|
||||
- name: REDIS_SERVER
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: redis.server
|
||||
optional: true
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: redis.server
|
||||
optional: true
|
||||
- name: REDIS_COMPRESSION
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
@@ -181,52 +181,58 @@ spec:
|
||||
optional: true
|
||||
- name: REDISDB
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: redis.db
|
||||
optional: true
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: redis.db
|
||||
optional: true
|
||||
- name: ARGOCD_DEFAULT_CACHE_EXPIRATION
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.default.cache.expiration
|
||||
optional: true
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.default.cache.expiration
|
||||
optional: true
|
||||
- name: ARGOCD_MAX_COOKIE_NUMBER
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.http.cookie.maxnumber
|
||||
optional: true
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.http.cookie.maxnumber
|
||||
optional: true
|
||||
- name: ARGOCD_SERVER_LISTEN_ADDRESS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.listen.address
|
||||
optional: true
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.listen.address
|
||||
optional: true
|
||||
- name: ARGOCD_SERVER_METRICS_LISTEN_ADDRESS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.metrics.listen.address
|
||||
optional: true
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.metrics.listen.address
|
||||
optional: true
|
||||
- name: ARGOCD_SERVER_OTLP_ADDRESS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: otlp.address
|
||||
optional: true
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: otlp.address
|
||||
optional: true
|
||||
- name: ARGOCD_APPLICATION_NAMESPACES
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: application.namespaces
|
||||
optional: true
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: application.namespaces
|
||||
optional: true
|
||||
- name: ARGOCD_SERVER_ENABLE_PROXY_EXTENSION
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.enable.proxy.extension
|
||||
optional: true
|
||||
- name: ARGOCD_API_CONTENT_TYPES
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: argocd-cmd-params-cm
|
||||
key: server.api.content.types
|
||||
optional: true
|
||||
volumeMounts:
|
||||
- name: ssh-known-hosts
|
||||
mountPath: /app/config/ssh
|
||||
|
||||
@@ -20750,7 +20750,7 @@ spec:
|
||||
key: applicationsetcontroller.allowed.scm.providers
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.9.4
|
||||
image: quay.io/argoproj/argocd:v2.9.13
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -20845,7 +20845,7 @@ spec:
|
||||
- ""
|
||||
- --appendonly
|
||||
- "no"
|
||||
image: redis:7.0.11-alpine
|
||||
image: redis:7.0.15-alpine
|
||||
imagePullPolicy: Always
|
||||
name: redis
|
||||
ports:
|
||||
@@ -21050,7 +21050,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.9.4
|
||||
image: quay.io/argoproj/argocd:v2.9.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -21102,7 +21102,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.9.4
|
||||
image: quay.io/argoproj/argocd:v2.9.13
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -21321,7 +21321,13 @@ spec:
|
||||
key: controller.kubectl.parallelism.limit
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.9.4
|
||||
- name: ARGOCD_IGNORE_NORMALIZER_JQ_TIMEOUT
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: controller.ignore.normalizer.jq.timeout
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.9.13
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -12,4 +12,4 @@ resources:
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v2.9.4
|
||||
newTag: v2.9.13
|
||||
|
||||
@@ -12,7 +12,7 @@ patches:
|
||||
images:
|
||||
- name: quay.io/argoproj/argocd
|
||||
newName: quay.io/argoproj/argocd
|
||||
newTag: v2.9.4
|
||||
newTag: v2.9.13
|
||||
resources:
|
||||
- ../../base/application-controller
|
||||
- ../../base/applicationset-controller
|
||||
|
||||
@@ -1207,7 +1207,7 @@ spec:
|
||||
automountServiceAccountToken: false
|
||||
initContainers:
|
||||
- name: config-init
|
||||
image: redis:7.0.11-alpine
|
||||
image: redis:7.0.15-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
resources:
|
||||
{}
|
||||
@@ -1241,7 +1241,7 @@ spec:
|
||||
|
||||
containers:
|
||||
- name: redis
|
||||
image: redis:7.0.11-alpine
|
||||
image: redis:7.0.15-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- redis-server
|
||||
@@ -1298,7 +1298,7 @@ spec:
|
||||
- /bin/sh
|
||||
- /readonly-config/trigger-failover-if-master.sh
|
||||
- name: sentinel
|
||||
image: redis:7.0.11-alpine
|
||||
image: redis:7.0.15-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- redis-sentinel
|
||||
@@ -1349,7 +1349,7 @@ spec:
|
||||
{}
|
||||
|
||||
- name: split-brain-fix
|
||||
image: redis:7.0.11-alpine
|
||||
image: redis:7.0.15-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- sh
|
||||
|
||||
@@ -20,7 +20,7 @@ redis-ha:
|
||||
metrics:
|
||||
enabled: true
|
||||
image:
|
||||
tag: 7.0.11-alpine
|
||||
tag: 7.0.15-alpine
|
||||
containerSecurityContext: null
|
||||
sentinel:
|
||||
bind: "0.0.0.0"
|
||||
|
||||
@@ -22007,7 +22007,7 @@ spec:
|
||||
key: applicationsetcontroller.allowed.scm.providers
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.9.4
|
||||
image: quay.io/argoproj/argocd:v2.9.13
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -22130,7 +22130,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v2.9.4
|
||||
image: quay.io/argoproj/argocd:v2.9.13
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -22206,7 +22206,7 @@ spec:
|
||||
key: application.namespaces
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.9.4
|
||||
image: quay.io/argoproj/argocd:v2.9.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -22537,7 +22537,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.9.4
|
||||
image: quay.io/argoproj/argocd:v2.9.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -22589,7 +22589,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.9.4
|
||||
image: quay.io/argoproj/argocd:v2.9.13
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -22878,7 +22878,13 @@ spec:
|
||||
key: server.enable.proxy.extension
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.9.4
|
||||
- name: ARGOCD_API_CONTENT_TYPES
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: server.api.content.types
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.9.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -23124,7 +23130,13 @@ spec:
|
||||
key: controller.kubectl.parallelism.limit
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.9.4
|
||||
- name: ARGOCD_IGNORE_NORMALIZER_JQ_TIMEOUT
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: controller.ignore.normalizer.jq.timeout
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.9.13
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
@@ -23201,7 +23213,7 @@ spec:
|
||||
- /data/conf/redis.conf
|
||||
command:
|
||||
- redis-server
|
||||
image: redis:7.0.11-alpine
|
||||
image: redis:7.0.15-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
lifecycle:
|
||||
preStop:
|
||||
@@ -23255,7 +23267,7 @@ spec:
|
||||
- /data/conf/sentinel.conf
|
||||
command:
|
||||
- redis-sentinel
|
||||
image: redis:7.0.11-alpine
|
||||
image: redis:7.0.15-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
lifecycle: {}
|
||||
livenessProbe:
|
||||
@@ -23308,7 +23320,7 @@ spec:
|
||||
value: 40000915ab58c3fa8fd888fb8b24711944e6cbb4
|
||||
- name: SENTINEL_ID_2
|
||||
value: 2bbec7894d954a8af3bb54d13eaec53cb024e2ca
|
||||
image: redis:7.0.11-alpine
|
||||
image: redis:7.0.15-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: split-brain-fix
|
||||
resources: {}
|
||||
@@ -23338,7 +23350,7 @@ spec:
|
||||
value: 40000915ab58c3fa8fd888fb8b24711944e6cbb4
|
||||
- name: SENTINEL_ID_2
|
||||
value: 2bbec7894d954a8af3bb54d13eaec53cb024e2ca
|
||||
image: redis:7.0.11-alpine
|
||||
image: redis:7.0.15-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: config-init
|
||||
securityContext:
|
||||
|
||||
@@ -1662,7 +1662,7 @@ spec:
|
||||
key: applicationsetcontroller.allowed.scm.providers
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.9.4
|
||||
image: quay.io/argoproj/argocd:v2.9.13
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -1785,7 +1785,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v2.9.4
|
||||
image: quay.io/argoproj/argocd:v2.9.13
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -1861,7 +1861,7 @@ spec:
|
||||
key: application.namespaces
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.9.4
|
||||
image: quay.io/argoproj/argocd:v2.9.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -2192,7 +2192,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.9.4
|
||||
image: quay.io/argoproj/argocd:v2.9.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -2244,7 +2244,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.9.4
|
||||
image: quay.io/argoproj/argocd:v2.9.13
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -2533,7 +2533,13 @@ spec:
|
||||
key: server.enable.proxy.extension
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.9.4
|
||||
- name: ARGOCD_API_CONTENT_TYPES
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: server.api.content.types
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.9.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -2779,7 +2785,13 @@ spec:
|
||||
key: controller.kubectl.parallelism.limit
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.9.4
|
||||
- name: ARGOCD_IGNORE_NORMALIZER_JQ_TIMEOUT
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: controller.ignore.normalizer.jq.timeout
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.9.13
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
@@ -2856,7 +2868,7 @@ spec:
|
||||
- /data/conf/redis.conf
|
||||
command:
|
||||
- redis-server
|
||||
image: redis:7.0.11-alpine
|
||||
image: redis:7.0.15-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
lifecycle:
|
||||
preStop:
|
||||
@@ -2910,7 +2922,7 @@ spec:
|
||||
- /data/conf/sentinel.conf
|
||||
command:
|
||||
- redis-sentinel
|
||||
image: redis:7.0.11-alpine
|
||||
image: redis:7.0.15-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
lifecycle: {}
|
||||
livenessProbe:
|
||||
@@ -2963,7 +2975,7 @@ spec:
|
||||
value: 40000915ab58c3fa8fd888fb8b24711944e6cbb4
|
||||
- name: SENTINEL_ID_2
|
||||
value: 2bbec7894d954a8af3bb54d13eaec53cb024e2ca
|
||||
image: redis:7.0.11-alpine
|
||||
image: redis:7.0.15-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: split-brain-fix
|
||||
resources: {}
|
||||
@@ -2993,7 +3005,7 @@ spec:
|
||||
value: 40000915ab58c3fa8fd888fb8b24711944e6cbb4
|
||||
- name: SENTINEL_ID_2
|
||||
value: 2bbec7894d954a8af3bb54d13eaec53cb024e2ca
|
||||
image: redis:7.0.11-alpine
|
||||
image: redis:7.0.15-alpine
|
||||
imagePullPolicy: IfNotPresent
|
||||
name: config-init
|
||||
securityContext:
|
||||
|
||||
@@ -21102,7 +21102,7 @@ spec:
|
||||
key: applicationsetcontroller.allowed.scm.providers
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.9.4
|
||||
image: quay.io/argoproj/argocd:v2.9.13
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -21225,7 +21225,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v2.9.4
|
||||
image: quay.io/argoproj/argocd:v2.9.13
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -21301,7 +21301,7 @@ spec:
|
||||
key: application.namespaces
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.9.4
|
||||
image: quay.io/argoproj/argocd:v2.9.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -21378,7 +21378,7 @@ spec:
|
||||
- ""
|
||||
- --appendonly
|
||||
- "no"
|
||||
image: redis:7.0.11-alpine
|
||||
image: redis:7.0.15-alpine
|
||||
imagePullPolicy: Always
|
||||
name: redis
|
||||
ports:
|
||||
@@ -21583,7 +21583,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.9.4
|
||||
image: quay.io/argoproj/argocd:v2.9.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -21635,7 +21635,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.9.4
|
||||
image: quay.io/argoproj/argocd:v2.9.13
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -21922,7 +21922,13 @@ spec:
|
||||
key: server.enable.proxy.extension
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.9.4
|
||||
- name: ARGOCD_API_CONTENT_TYPES
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: server.api.content.types
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.9.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -22168,7 +22174,13 @@ spec:
|
||||
key: controller.kubectl.parallelism.limit
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.9.4
|
||||
- name: ARGOCD_IGNORE_NORMALIZER_JQ_TIMEOUT
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: controller.ignore.normalizer.jq.timeout
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.9.13
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -757,7 +757,7 @@ spec:
|
||||
key: applicationsetcontroller.allowed.scm.providers
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.9.4
|
||||
image: quay.io/argoproj/argocd:v2.9.13
|
||||
imagePullPolicy: Always
|
||||
name: argocd-applicationset-controller
|
||||
ports:
|
||||
@@ -880,7 +880,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /shared/argocd-dex
|
||||
image: quay.io/argoproj/argocd:v2.9.4
|
||||
image: quay.io/argoproj/argocd:v2.9.13
|
||||
imagePullPolicy: Always
|
||||
name: copyutil
|
||||
securityContext:
|
||||
@@ -956,7 +956,7 @@ spec:
|
||||
key: application.namespaces
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.9.4
|
||||
image: quay.io/argoproj/argocd:v2.9.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
@@ -1033,7 +1033,7 @@ spec:
|
||||
- ""
|
||||
- --appendonly
|
||||
- "no"
|
||||
image: redis:7.0.11-alpine
|
||||
image: redis:7.0.15-alpine
|
||||
imagePullPolicy: Always
|
||||
name: redis
|
||||
ports:
|
||||
@@ -1238,7 +1238,7 @@ spec:
|
||||
value: /helm-working-dir
|
||||
- name: HELM_DATA_HOME
|
||||
value: /helm-working-dir
|
||||
image: quay.io/argoproj/argocd:v2.9.4
|
||||
image: quay.io/argoproj/argocd:v2.9.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
@@ -1290,7 +1290,7 @@ spec:
|
||||
- -n
|
||||
- /usr/local/bin/argocd
|
||||
- /var/run/argocd/argocd-cmp-server
|
||||
image: quay.io/argoproj/argocd:v2.9.4
|
||||
image: quay.io/argoproj/argocd:v2.9.13
|
||||
name: copyutil
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
@@ -1577,7 +1577,13 @@ spec:
|
||||
key: server.enable.proxy.extension
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.9.4
|
||||
- name: ARGOCD_API_CONTENT_TYPES
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: server.api.content.types
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.9.13
|
||||
imagePullPolicy: Always
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
@@ -1823,7 +1829,13 @@ spec:
|
||||
key: controller.kubectl.parallelism.limit
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.9.4
|
||||
- name: ARGOCD_IGNORE_NORMALIZER_JQ_TIMEOUT
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
key: controller.ignore.normalizer.jq.timeout
|
||||
name: argocd-cmd-params-cm
|
||||
optional: true
|
||||
image: quay.io/argoproj/argocd:v2.9.13
|
||||
imagePullPolicy: Always
|
||||
name: argocd-application-controller
|
||||
ports:
|
||||
|
||||
@@ -98,6 +98,9 @@ API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/applicat
|
||||
API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ApplicationSourcePluginParameter,String_
|
||||
API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ClusterCacheInfo,APIsCount
|
||||
API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ConnectionState,ModifiedAt
|
||||
API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ErrApplicationNotAllowedToUseProject,application
|
||||
API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ErrApplicationNotAllowedToUseProject,namespace
|
||||
API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,ErrApplicationNotAllowedToUseProject,project
|
||||
API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,HelmOptions,ValuesFileSchemes
|
||||
API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,JWTToken,ExpiresAt
|
||||
API rule violation: names_match,github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1,JWTToken,IssuedAt
|
||||
|
||||
@@ -17,6 +17,24 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
type ErrApplicationNotAllowedToUseProject struct {
|
||||
application string
|
||||
namespace string
|
||||
project string
|
||||
}
|
||||
|
||||
func NewErrApplicationNotAllowedToUseProject(application, namespace, project string) error {
|
||||
return &ErrApplicationNotAllowedToUseProject{
|
||||
application: application,
|
||||
namespace: namespace,
|
||||
project: project,
|
||||
}
|
||||
}
|
||||
|
||||
func (err *ErrApplicationNotAllowedToUseProject) Error() string {
|
||||
return fmt.Sprintf("application '%s' in namespace '%s' is not allowed to use project %s", err.application, err.namespace, err.project)
|
||||
}
|
||||
|
||||
// AppProjectList is list of AppProject resources
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type AppProjectList struct {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -893,6 +893,9 @@ message EnvEntry {
|
||||
optional string value = 2;
|
||||
}
|
||||
|
||||
message ErrApplicationNotAllowedToUseProject {
|
||||
}
|
||||
|
||||
// ExecProviderConfig is config used to call an external command to perform cluster authentication
|
||||
// See: https://godoc.org/k8s.io/client-go/tools/clientcmd/api#ExecConfig
|
||||
message ExecProviderConfig {
|
||||
|
||||
@@ -70,6 +70,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ConnectionState": schema_pkg_apis_application_v1alpha1_ConnectionState(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.DuckTypeGenerator": schema_pkg_apis_application_v1alpha1_DuckTypeGenerator(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.EnvEntry": schema_pkg_apis_application_v1alpha1_EnvEntry(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ErrApplicationNotAllowedToUseProject": schema_pkg_apis_application_v1alpha1_ErrApplicationNotAllowedToUseProject(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.ExecProviderConfig": schema_pkg_apis_application_v1alpha1_ExecProviderConfig(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.GitDirectoryGeneratorItem": schema_pkg_apis_application_v1alpha1_GitDirectoryGeneratorItem(ref),
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1.GitFileGeneratorItem": schema_pkg_apis_application_v1alpha1_GitFileGeneratorItem(ref),
|
||||
@@ -3186,6 +3187,40 @@ func schema_pkg_apis_application_v1alpha1_EnvEntry(ref common.ReferenceCallback)
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_application_v1alpha1_ErrApplicationNotAllowedToUseProject(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"application": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"namespace": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"project": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"application", "namespace", "project"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_application_v1alpha1_ExecProviderConfig(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
|
||||
@@ -1852,6 +1852,22 @@ func (in *EnvEntry) DeepCopy() *EnvEntry {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ErrApplicationNotAllowedToUseProject) DeepCopyInto(out *ErrApplicationNotAllowedToUseProject) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ErrApplicationNotAllowedToUseProject.
|
||||
func (in *ErrApplicationNotAllowedToUseProject) DeepCopy() *ErrApplicationNotAllowedToUseProject {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(ErrApplicationNotAllowedToUseProject)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *ExecProviderConfig) DeepCopyInto(out *ExecProviderConfig) {
|
||||
*out = *in
|
||||
|
||||
@@ -107,6 +107,7 @@ type RepoServerInitConstants struct {
|
||||
StreamedManifestMaxExtractedSize int64
|
||||
StreamedManifestMaxTarSize int64
|
||||
HelmManifestMaxExtractedSize int64
|
||||
HelmRegistryMaxIndexSize int64
|
||||
DisableHelmManifestMaxExtractedSize bool
|
||||
}
|
||||
|
||||
@@ -1378,7 +1379,7 @@ func GenerateManifests(ctx context.Context, appPath, repoRoot, revision string,
|
||||
if q.KustomizeOptions != nil {
|
||||
kustomizeBinary = q.KustomizeOptions.BinaryPath
|
||||
}
|
||||
k := kustomize.NewKustomizeApp(appPath, q.Repo.GetGitCreds(gitCredsStore), repoURL, kustomizeBinary)
|
||||
k := kustomize.NewKustomizeApp(repoRoot, appPath, q.Repo.GetGitCreds(gitCredsStore), repoURL, kustomizeBinary)
|
||||
targetObjs, _, err = k.Build(q.ApplicationSource.Kustomize, q.KustomizeOptions, env)
|
||||
case v1alpha1.ApplicationSourceTypePlugin:
|
||||
pluginName := ""
|
||||
@@ -1965,7 +1966,7 @@ func (s *Service) GetAppDetails(ctx context.Context, q *apiclient.RepoServerAppD
|
||||
return err
|
||||
}
|
||||
case v1alpha1.ApplicationSourceTypeKustomize:
|
||||
if err := populateKustomizeAppDetails(res, q, opContext.appPath, commitSHA, s.gitCredsStore); err != nil {
|
||||
if err := populateKustomizeAppDetails(res, q, repoRoot, opContext.appPath, commitSHA, s.gitCredsStore); err != nil {
|
||||
return err
|
||||
}
|
||||
case v1alpha1.ApplicationSourceTypePlugin:
|
||||
@@ -2106,13 +2107,13 @@ func findHelmValueFilesInPath(path string) ([]string, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func populateKustomizeAppDetails(res *apiclient.RepoAppDetailsResponse, q *apiclient.RepoServerAppDetailsQuery, appPath string, reversion string, credsStore git.CredsStore) error {
|
||||
func populateKustomizeAppDetails(res *apiclient.RepoAppDetailsResponse, q *apiclient.RepoServerAppDetailsQuery, repoRoot string, appPath string, reversion string, credsStore git.CredsStore) error {
|
||||
res.Kustomize = &apiclient.KustomizeAppSpec{}
|
||||
kustomizeBinary := ""
|
||||
if q.KustomizeOptions != nil {
|
||||
kustomizeBinary = q.KustomizeOptions.BinaryPath
|
||||
}
|
||||
k := kustomize.NewKustomizeApp(appPath, q.Repo.GetGitCreds(credsStore), q.Repo.Repo, kustomizeBinary)
|
||||
k := kustomize.NewKustomizeApp(repoRoot, appPath, q.Repo.GetGitCreds(credsStore), q.Repo.Repo, kustomizeBinary)
|
||||
fakeManifestRequest := apiclient.ManifestRequest{
|
||||
AppName: q.AppName,
|
||||
Namespace: "", // FIXME: omit it for now
|
||||
@@ -2345,7 +2346,7 @@ func (s *Service) newHelmClientResolveRevision(repo *v1alpha1.Repository, revisi
|
||||
return helmClient, version.String(), nil
|
||||
}
|
||||
|
||||
index, err := helmClient.GetIndex(noRevisionCache)
|
||||
index, err := helmClient.GetIndex(noRevisionCache, s.initConstants.HelmRegistryMaxIndexSize)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
@@ -2423,7 +2424,7 @@ func checkoutRevision(gitClient git.Client, revision string, submoduleEnabled bo
|
||||
}
|
||||
|
||||
func (s *Service) GetHelmCharts(ctx context.Context, q *apiclient.HelmChartsRequest) (*apiclient.HelmChartsResponse, error) {
|
||||
index, err := s.newHelmClient(q.Repo.Repo, q.Repo.GetHelmCreds(), q.Repo.EnableOCI, q.Repo.Proxy, helm.WithChartPaths(s.chartPaths)).GetIndex(true)
|
||||
index, err := s.newHelmClient(q.Repo.Repo, q.Repo.GetHelmCreds(), q.Repo.EnableOCI, q.Repo.Proxy, helm.WithChartPaths(s.chartPaths)).GetIndex(true, s.initConstants.HelmRegistryMaxIndexSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -2458,7 +2459,7 @@ func (s *Service) TestRepository(ctx context.Context, q *apiclient.TestRepositor
|
||||
_, err := helm.NewClient(repo.Repo, repo.GetHelmCreds(), repo.EnableOCI, repo.Proxy).TestHelmOCI()
|
||||
return err
|
||||
} else {
|
||||
_, err := helm.NewClient(repo.Repo, repo.GetHelmCreds(), repo.EnableOCI, repo.Proxy).GetIndex(false)
|
||||
_, err := helm.NewClient(repo.Repo, repo.GetHelmCreds(), repo.EnableOCI, repo.Proxy).GetIndex(false, s.initConstants.HelmRegistryMaxIndexSize)
|
||||
return err
|
||||
}
|
||||
},
|
||||
|
||||
@@ -113,7 +113,7 @@ func newServiceWithMocks(t *testing.T, root string, signed bool) (*Service, *git
|
||||
chart := "my-chart"
|
||||
oobChart := "out-of-bounds-chart"
|
||||
version := "1.1.0"
|
||||
helmClient.On("GetIndex", mock.AnythingOfType("bool")).Return(&helm.Index{Entries: map[string]helm.Entries{
|
||||
helmClient.On("GetIndex", mock.AnythingOfType("bool"), mock.Anything).Return(&helm.Index{Entries: map[string]helm.Entries{
|
||||
chart: {{Version: "1.0.0"}, {Version: version}},
|
||||
oobChart: {{Version: "1.0.0"}, {Version: version}},
|
||||
}}, nil)
|
||||
|
||||
@@ -148,7 +148,7 @@ func NewServer(
|
||||
//
|
||||
// If the user does provide a "project," we can respond more specifically. If the user does not have access to the given
|
||||
// app name in the given project, we return "permission denied." If the app exists, but the project is different from
|
||||
func (s *Server) getAppEnforceRBAC(ctx context.Context, action, project, namespace, name string, getApp func() (*appv1.Application, error)) (*appv1.Application, error) {
|
||||
func (s *Server) getAppEnforceRBAC(ctx context.Context, action, project, namespace, name string, getApp func() (*appv1.Application, error)) (*appv1.Application, *appv1.AppProject, error) {
|
||||
logCtx := log.WithFields(map[string]interface{}{
|
||||
"application": name,
|
||||
"namespace": namespace,
|
||||
@@ -165,7 +165,7 @@ func (s *Server) getAppEnforceRBAC(ctx context.Context, action, project, namespa
|
||||
// but the app is in a different project" response. We don't want the user inferring the existence of the
|
||||
// app from response time.
|
||||
_, _ = getApp()
|
||||
return nil, permissionDeniedErr
|
||||
return nil, nil, permissionDeniedErr
|
||||
}
|
||||
}
|
||||
a, err := getApp()
|
||||
@@ -173,15 +173,15 @@ func (s *Server) getAppEnforceRBAC(ctx context.Context, action, project, namespa
|
||||
if apierr.IsNotFound(err) {
|
||||
if project != "" {
|
||||
// We know that the user was allowed to get the Application, but the Application does not exist. Return 404.
|
||||
return nil, status.Errorf(codes.NotFound, apierr.NewNotFound(schema.GroupResource{Group: "argoproj.io", Resource: "applications"}, name).Error())
|
||||
return nil, nil, status.Errorf(codes.NotFound, apierr.NewNotFound(schema.GroupResource{Group: "argoproj.io", Resource: "applications"}, name).Error())
|
||||
}
|
||||
// We don't know if the user was allowed to get the Application, and we don't want to leak information about
|
||||
// the Application's existence. Return 403.
|
||||
logCtx.Warn("application does not exist")
|
||||
return nil, permissionDeniedErr
|
||||
return nil, nil, permissionDeniedErr
|
||||
}
|
||||
logCtx.Errorf("failed to get application: %s", err)
|
||||
return nil, permissionDeniedErr
|
||||
return nil, nil, permissionDeniedErr
|
||||
}
|
||||
// Even if we performed an initial RBAC check (because the request was fully parameterized), we still need to
|
||||
// perform a second RBAC check to ensure that the user has access to the actual Application's project (not just the
|
||||
@@ -195,11 +195,11 @@ func (s *Server) getAppEnforceRBAC(ctx context.Context, action, project, namespa
|
||||
// The user specified a project. We would have returned a 404 if the user had access to the app, but the app
|
||||
// did not exist. So we have to return a 404 when the app does exist, but the user does not have access.
|
||||
// Otherwise, they could infer that the app exists based on the error code.
|
||||
return nil, status.Errorf(codes.NotFound, apierr.NewNotFound(schema.GroupResource{Group: "argoproj.io", Resource: "applications"}, name).Error())
|
||||
return nil, nil, status.Errorf(codes.NotFound, apierr.NewNotFound(schema.GroupResource{Group: "argoproj.io", Resource: "applications"}, name).Error())
|
||||
}
|
||||
// The user didn't specify a project. We always return permission denied for both lack of access and lack of
|
||||
// existence.
|
||||
return nil, permissionDeniedErr
|
||||
return nil, nil, permissionDeniedErr
|
||||
}
|
||||
effectiveProject := "default"
|
||||
if a.Spec.Project != "" {
|
||||
@@ -212,15 +212,20 @@ func (s *Server) getAppEnforceRBAC(ctx context.Context, action, project, namespa
|
||||
}).Warnf("user tried to %s application in project %s, but the application is in project %s", action, project, effectiveProject)
|
||||
// The user has access to the app, but the app is in a different project. Return 404, meaning "app doesn't
|
||||
// exist in that project".
|
||||
return nil, status.Errorf(codes.NotFound, apierr.NewNotFound(schema.GroupResource{Group: "argoproj.io", Resource: "applications"}, name).Error())
|
||||
return nil, nil, status.Errorf(codes.NotFound, apierr.NewNotFound(schema.GroupResource{Group: "argoproj.io", Resource: "applications"}, name).Error())
|
||||
}
|
||||
return a, nil
|
||||
// Get the app's associated project, and make sure all project restrictions are enforced.
|
||||
proj, err := s.getAppProject(ctx, a, logCtx)
|
||||
if err != nil {
|
||||
return a, nil, err
|
||||
}
|
||||
return a, proj, nil
|
||||
}
|
||||
|
||||
// getApplicationEnforceRBACInformer uses an informer to get an Application. If the app does not exist, permission is
|
||||
// denied, or any other error occurs when getting the app, we return a permission denied error to obscure any sensitive
|
||||
// information.
|
||||
func (s *Server) getApplicationEnforceRBACInformer(ctx context.Context, action, project, namespace, name string) (*appv1.Application, error) {
|
||||
func (s *Server) getApplicationEnforceRBACInformer(ctx context.Context, action, project, namespace, name string) (*appv1.Application, *appv1.AppProject, error) {
|
||||
namespaceOrDefault := s.appNamespaceOrDefault(namespace)
|
||||
return s.getAppEnforceRBAC(ctx, action, project, namespaceOrDefault, name, func() (*appv1.Application, error) {
|
||||
return s.appLister.Applications(namespaceOrDefault).Get(name)
|
||||
@@ -230,7 +235,7 @@ func (s *Server) getApplicationEnforceRBACInformer(ctx context.Context, action,
|
||||
// getApplicationEnforceRBACClient uses a client to get an Application. If the app does not exist, permission is denied,
|
||||
// or any other error occurs when getting the app, we return a permission denied error to obscure any sensitive
|
||||
// information.
|
||||
func (s *Server) getApplicationEnforceRBACClient(ctx context.Context, action, project, namespace, name, resourceVersion string) (*appv1.Application, error) {
|
||||
func (s *Server) getApplicationEnforceRBACClient(ctx context.Context, action, project, namespace, name, resourceVersion string) (*appv1.Application, *appv1.AppProject, error) {
|
||||
namespaceOrDefault := s.appNamespaceOrDefault(namespace)
|
||||
return s.getAppEnforceRBAC(ctx, action, project, namespaceOrDefault, name, func() (*appv1.Application, error) {
|
||||
if !s.isNamespaceEnabled(namespaceOrDefault) {
|
||||
@@ -314,7 +319,13 @@ func (s *Server) Create(ctx context.Context, q *application.ApplicationCreateReq
|
||||
if q.Validate != nil {
|
||||
validate = *q.Validate
|
||||
}
|
||||
err := s.validateAndNormalizeApp(ctx, a, validate)
|
||||
|
||||
proj, err := s.getAppProject(ctx, a, log.WithField("application", a.Name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = s.validateAndNormalizeApp(ctx, a, proj, validate)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error while validating and normalizing app: %w", err)
|
||||
}
|
||||
@@ -325,6 +336,15 @@ func (s *Server) Create(ctx context.Context, q *application.ApplicationCreateReq
|
||||
return nil, security.NamespaceNotPermittedError(appNs)
|
||||
}
|
||||
|
||||
// Don't let the app creator set the operation explicitly. Those requests should always go through the Sync API.
|
||||
if a.Operation != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"application": a.Name,
|
||||
argocommon.SecurityField: argocommon.SecurityLow,
|
||||
}).Warn("User attempted to set operation on application creation. This could have allowed them to bypass branch protection rules by setting manifests directly. Ignoring the set operation.")
|
||||
a.Operation = nil
|
||||
}
|
||||
|
||||
created, err := s.appclientset.ArgoprojV1alpha1().Applications(appNs).Create(ctx, a, metav1.CreateOptions{})
|
||||
if err == nil {
|
||||
s.logAppEvent(created, ctx, argo.EventReasonResourceCreated, "created application")
|
||||
@@ -361,7 +381,7 @@ func (s *Server) Create(ctx context.Context, q *application.ApplicationCreateReq
|
||||
return updated, nil
|
||||
}
|
||||
|
||||
func (s *Server) queryRepoServer(ctx context.Context, a *appv1.Application, action func(
|
||||
func (s *Server) queryRepoServer(ctx context.Context, a *appv1.Application, proj *appv1.AppProject, action func(
|
||||
client apiclient.RepoServerServiceClient,
|
||||
repo *appv1.Repository,
|
||||
helmRepos []*appv1.Repository,
|
||||
@@ -388,13 +408,6 @@ func (s *Server) queryRepoServer(ctx context.Context, a *appv1.Application, acti
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting kustomize settings options: %w", err)
|
||||
}
|
||||
proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
|
||||
if err != nil {
|
||||
if apierr.IsNotFound(err) {
|
||||
return status.Errorf(codes.InvalidArgument, "application references project %s which does not exist", a.Spec.Project)
|
||||
}
|
||||
return fmt.Errorf("error getting application's project: %w", err)
|
||||
}
|
||||
|
||||
helmRepos, err := s.db.ListHelmRepositories(ctx)
|
||||
if err != nil {
|
||||
@@ -429,7 +442,7 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
|
||||
if q.Name == nil || *q.Name == "" {
|
||||
return nil, fmt.Errorf("invalid request: application name is missing")
|
||||
}
|
||||
a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
|
||||
a, proj, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -441,7 +454,7 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
|
||||
}
|
||||
|
||||
var manifestInfo *apiclient.ManifestResponse
|
||||
err = s.queryRepoServer(ctx, a, func(
|
||||
err = s.queryRepoServer(ctx, a, proj, func(
|
||||
client apiclient.RepoServerServiceClient, repo *appv1.Repository, helmRepos []*appv1.Repository, helmCreds []*appv1.RepoCreds, helmOptions *appv1.HelmOptions, kustomizeOptions *appv1.KustomizeOptions, enableGenerateManifests map[string]bool) error {
|
||||
revision := source.TargetRevision
|
||||
if q.GetRevision() != "" {
|
||||
@@ -467,11 +480,6 @@ func (s *Server) GetManifests(ctx context.Context, q *application.ApplicationMan
|
||||
return fmt.Errorf("error getting API resources: %w", err)
|
||||
}
|
||||
|
||||
proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting app project: %w", err)
|
||||
}
|
||||
|
||||
manifestInfo, err = client.GenerateManifest(ctx, &apiclient.ManifestRequest{
|
||||
Repo: repo,
|
||||
Revision: revision,
|
||||
@@ -534,13 +542,13 @@ func (s *Server) GetManifestsWithFiles(stream application.ApplicationService_Get
|
||||
return fmt.Errorf("invalid request: application name is missing")
|
||||
}
|
||||
|
||||
a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, query.GetProject(), query.GetAppNamespace(), query.GetName())
|
||||
a, proj, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, query.GetProject(), query.GetAppNamespace(), query.GetName())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var manifestInfo *apiclient.ManifestResponse
|
||||
err = s.queryRepoServer(ctx, a, func(
|
||||
err = s.queryRepoServer(ctx, a, proj, func(
|
||||
client apiclient.RepoServerServiceClient, repo *appv1.Repository, helmRepos []*appv1.Repository, helmCreds []*appv1.RepoCreds, helmOptions *appv1.HelmOptions, kustomizeOptions *appv1.KustomizeOptions, enableGenerateManifests map[string]bool) error {
|
||||
|
||||
appInstanceLabelKey, err := s.settingsMgr.GetAppInstanceLabelKey()
|
||||
@@ -651,7 +659,7 @@ func (s *Server) Get(ctx context.Context, q *application.ApplicationQuery) (*app
|
||||
// We must use a client Get instead of an informer Get, because it's common to call Get immediately
|
||||
// following a Watch (which is not yet powered by an informer), and the Get must reflect what was
|
||||
// previously seen by the client.
|
||||
a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, project, appNs, appName, q.GetResourceVersion())
|
||||
a, proj, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, project, appNs, appName, q.GetResourceVersion())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -682,7 +690,7 @@ func (s *Server) Get(ctx context.Context, q *application.ApplicationQuery) (*app
|
||||
|
||||
if refreshType == appv1.RefreshTypeHard {
|
||||
// force refresh cached application details
|
||||
if err := s.queryRepoServer(ctx, a, func(
|
||||
if err := s.queryRepoServer(ctx, a, proj, func(
|
||||
client apiclient.RepoServerServiceClient,
|
||||
repo *appv1.Repository,
|
||||
helmRepos []*appv1.Repository,
|
||||
@@ -734,7 +742,7 @@ func (s *Server) Get(ctx context.Context, q *application.ApplicationQuery) (*app
|
||||
|
||||
// ListResourceEvents returns a list of event resources
|
||||
func (s *Server) ListResourceEvents(ctx context.Context, q *application.ApplicationResourceEventsQuery) (*v1.EventList, error) {
|
||||
a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
|
||||
a, _, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -802,12 +810,12 @@ func (s *Server) validateAndUpdateApp(ctx context.Context, newApp *appv1.Applica
|
||||
s.projectLock.RLock(newApp.Spec.GetProject())
|
||||
defer s.projectLock.RUnlock(newApp.Spec.GetProject())
|
||||
|
||||
app, err := s.getApplicationEnforceRBACClient(ctx, action, currentProject, newApp.Namespace, newApp.Name, "")
|
||||
app, proj, err := s.getApplicationEnforceRBACClient(ctx, action, currentProject, newApp.Namespace, newApp.Name, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = s.validateAndNormalizeApp(ctx, newApp, validate)
|
||||
err = s.validateAndNormalizeApp(ctx, newApp, proj, validate)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error validating and normalizing app: %w", err)
|
||||
}
|
||||
@@ -906,7 +914,7 @@ func (s *Server) UpdateSpec(ctx context.Context, q *application.ApplicationUpdat
|
||||
if q.GetSpec() == nil {
|
||||
return nil, fmt.Errorf("error updating application spec: spec is nil in request")
|
||||
}
|
||||
a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionUpdate, q.GetProject(), q.GetAppNamespace(), q.GetName(), "")
|
||||
a, _, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionUpdate, q.GetProject(), q.GetAppNamespace(), q.GetName(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -925,7 +933,7 @@ func (s *Server) UpdateSpec(ctx context.Context, q *application.ApplicationUpdat
|
||||
|
||||
// Patch patches an application
|
||||
func (s *Server) Patch(ctx context.Context, q *application.ApplicationPatchRequest) (*appv1.Application, error) {
|
||||
app, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName(), "")
|
||||
app, _, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -968,11 +976,35 @@ func (s *Server) Patch(ctx context.Context, q *application.ApplicationPatchReque
|
||||
return s.validateAndUpdateApp(ctx, newApp, false, true, rbacpolicy.ActionUpdate, q.GetProject())
|
||||
}
|
||||
|
||||
func (s *Server) getAppProject(ctx context.Context, a *appv1.Application, logCtx *log.Entry) (*appv1.AppProject, error) {
|
||||
proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
|
||||
if err == nil {
|
||||
return proj, nil
|
||||
}
|
||||
|
||||
// If there's a permission issue or the app doesn't exist, return a vague error to avoid letting the user enumerate project names.
|
||||
vagueError := status.Errorf(codes.InvalidArgument, "app is not allowed in project %q, or the project does not exist", a.Spec.Project)
|
||||
|
||||
if apierr.IsNotFound(err) {
|
||||
return nil, vagueError
|
||||
}
|
||||
|
||||
if _, ok := err.(*appv1.ErrApplicationNotAllowedToUseProject); ok {
|
||||
logCtx.WithFields(map[string]interface{}{
|
||||
"project": a.Spec.Project,
|
||||
argocommon.SecurityField: argocommon.SecurityMedium,
|
||||
}).Warnf("error getting app project: %s", err)
|
||||
return nil, vagueError
|
||||
}
|
||||
|
||||
return nil, vagueError
|
||||
}
|
||||
|
||||
// Delete removes an application and all associated resources
|
||||
func (s *Server) Delete(ctx context.Context, q *application.ApplicationDeleteRequest) (*application.ApplicationResponse, error) {
|
||||
appName := q.GetName()
|
||||
appNs := s.appNamespaceOrDefault(q.GetAppNamespace())
|
||||
a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetProject(), appNs, appName, "")
|
||||
a, _, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetProject(), appNs, appName, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1127,16 +1159,7 @@ func (s *Server) Watch(q *application.ApplicationQuery, ws application.Applicati
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) validateAndNormalizeApp(ctx context.Context, app *appv1.Application, validate bool) error {
|
||||
proj, err := argo.GetAppProject(app, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
|
||||
if err != nil {
|
||||
if apierr.IsNotFound(err) {
|
||||
// Offer no hint that the project does not exist.
|
||||
log.Warnf("User attempted to create/update application in non-existent project %q", app.Spec.Project)
|
||||
return permissionDeniedErr
|
||||
}
|
||||
return fmt.Errorf("error getting application's project: %w", err)
|
||||
}
|
||||
func (s *Server) validateAndNormalizeApp(ctx context.Context, app *appv1.Application, proj *appv1.AppProject, validate bool) error {
|
||||
if app.GetName() == "" {
|
||||
return fmt.Errorf("resource name may not be empty")
|
||||
}
|
||||
@@ -1240,7 +1263,7 @@ func (s *Server) getAppResources(ctx context.Context, a *appv1.Application) (*ap
|
||||
}
|
||||
|
||||
func (s *Server) getAppLiveResource(ctx context.Context, action string, q *application.ApplicationResourceRequest) (*appv1.ResourceNode, *rest.Config, *appv1.Application, error) {
|
||||
a, err := s.getApplicationEnforceRBACInformer(ctx, action, q.GetProject(), q.GetAppNamespace(), q.GetName())
|
||||
a, _, err := s.getApplicationEnforceRBACInformer(ctx, action, q.GetProject(), q.GetAppNamespace(), q.GetName())
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
@@ -1377,7 +1400,7 @@ func (s *Server) DeleteResource(ctx context.Context, q *application.ApplicationR
|
||||
}
|
||||
|
||||
func (s *Server) ResourceTree(ctx context.Context, q *application.ResourcesQuery) (*appv1.ApplicationTree, error) {
|
||||
a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetApplicationName())
|
||||
a, _, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetApplicationName())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1386,7 +1409,7 @@ func (s *Server) ResourceTree(ctx context.Context, q *application.ResourcesQuery
|
||||
}
|
||||
|
||||
func (s *Server) WatchResourceTree(q *application.ResourcesQuery, ws application.ApplicationService_WatchResourceTreeServer) error {
|
||||
_, err := s.getApplicationEnforceRBACInformer(ws.Context(), rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetApplicationName())
|
||||
_, _, err := s.getApplicationEnforceRBACInformer(ws.Context(), rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetApplicationName())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1403,7 +1426,7 @@ func (s *Server) WatchResourceTree(q *application.ResourcesQuery, ws application
|
||||
}
|
||||
|
||||
func (s *Server) RevisionMetadata(ctx context.Context, q *application.RevisionMetadataQuery) (*appv1.RevisionMetadata, error) {
|
||||
a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
|
||||
a, proj, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1413,12 +1436,6 @@ func (s *Server) RevisionMetadata(ctx context.Context, q *application.RevisionMe
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting repository by URL: %w", err)
|
||||
}
|
||||
// We need to get some information with the project associated to the app,
|
||||
// so we'll know whether GPG signatures are enforced.
|
||||
proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting app project: %w", err)
|
||||
}
|
||||
conn, repoClient, err := s.repoClientset.NewRepoServerClient()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating repo server client: %w", err)
|
||||
@@ -1433,7 +1450,7 @@ func (s *Server) RevisionMetadata(ctx context.Context, q *application.RevisionMe
|
||||
|
||||
// RevisionChartDetails returns the helm chart metadata, as fetched from the reposerver
|
||||
func (s *Server) RevisionChartDetails(ctx context.Context, q *application.RevisionMetadataQuery) (*appv1.ChartDetails, error) {
|
||||
a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
|
||||
a, _, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1464,7 +1481,7 @@ func isMatchingResource(q *application.ResourcesQuery, key kube.ResourceKey) boo
|
||||
}
|
||||
|
||||
func (s *Server) ManagedResources(ctx context.Context, q *application.ResourcesQuery) (*application.ManagedResourcesResponse, error) {
|
||||
a, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetApplicationName())
|
||||
a, _, err := s.getApplicationEnforceRBACInformer(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetApplicationName())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1521,7 +1538,7 @@ func (s *Server) PodLogs(q *application.ApplicationPodLogsQuery, ws application.
|
||||
}
|
||||
}
|
||||
|
||||
a, err := s.getApplicationEnforceRBACInformer(ws.Context(), rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
|
||||
a, _, err := s.getApplicationEnforceRBACInformer(ws.Context(), rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1713,19 +1730,11 @@ func isTheSelectedOne(currentNode *appv1.ResourceNode, q *application.Applicatio
|
||||
|
||||
// Sync syncs an application to its target state
|
||||
func (s *Server) Sync(ctx context.Context, syncReq *application.ApplicationSyncRequest) (*appv1.Application, error) {
|
||||
a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, syncReq.GetProject(), syncReq.GetAppNamespace(), syncReq.GetName(), "")
|
||||
a, proj, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, syncReq.GetProject(), syncReq.GetAppNamespace(), syncReq.GetName(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
|
||||
if err != nil {
|
||||
if apierr.IsNotFound(err) {
|
||||
return a, status.Errorf(codes.InvalidArgument, "application references project %s which does not exist", a.Spec.Project)
|
||||
}
|
||||
return a, fmt.Errorf("error getting app project: %w", err)
|
||||
}
|
||||
|
||||
s.inferResourcesStatusHealth(a)
|
||||
|
||||
if !proj.Spec.SyncWindows.Matches(a).CanSync(true) {
|
||||
@@ -1822,7 +1831,7 @@ func (s *Server) Sync(ctx context.Context, syncReq *application.ApplicationSyncR
|
||||
}
|
||||
|
||||
func (s *Server) Rollback(ctx context.Context, rollbackReq *application.ApplicationRollbackRequest) (*appv1.Application, error) {
|
||||
a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionSync, rollbackReq.GetProject(), rollbackReq.GetAppNamespace(), rollbackReq.GetName(), "")
|
||||
a, _, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionSync, rollbackReq.GetProject(), rollbackReq.GetAppNamespace(), rollbackReq.GetName(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1881,7 +1890,7 @@ func (s *Server) Rollback(ctx context.Context, rollbackReq *application.Applicat
|
||||
}
|
||||
|
||||
func (s *Server) ListLinks(ctx context.Context, req *application.ListAppLinksRequest) (*application.LinksResponse, error) {
|
||||
a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, req.GetProject(), req.GetNamespace(), req.GetName(), "")
|
||||
a, proj, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, req.GetProject(), req.GetNamespace(), req.GetName(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1896,7 +1905,7 @@ func (s *Server) ListLinks(ctx context.Context, req *application.ListAppLinksReq
|
||||
return nil, fmt.Errorf("failed to read application deep links from configmap: %w", err)
|
||||
}
|
||||
|
||||
clstObj, _, err := s.getObjectsForDeepLinks(ctx, a)
|
||||
clstObj, _, err := s.getObjectsForDeepLinks(ctx, a, proj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1911,12 +1920,7 @@ func (s *Server) ListLinks(ctx context.Context, req *application.ListAppLinksReq
|
||||
return finalList, nil
|
||||
}
|
||||
|
||||
func (s *Server) getObjectsForDeepLinks(ctx context.Context, app *appv1.Application) (cluster *unstructured.Unstructured, project *unstructured.Unstructured, err error) {
|
||||
proj, err := argo.GetAppProject(app, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error getting app project: %w", err)
|
||||
}
|
||||
|
||||
func (s *Server) getObjectsForDeepLinks(ctx context.Context, app *appv1.Application, proj *appv1.AppProject) (cluster *unstructured.Unstructured, project *unstructured.Unstructured, err error) {
|
||||
// sanitize project jwt tokens
|
||||
proj.Status = appv1.AppProjectStatus{}
|
||||
|
||||
@@ -1979,7 +1983,12 @@ func (s *Server) ListResourceLinks(ctx context.Context, req *application.Applica
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clstObj, projObj, err := s.getObjectsForDeepLinks(ctx, app)
|
||||
proj, err := s.getAppProject(ctx, app, log.WithField("application", app.GetName()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
clstObj, projObj, err := s.getObjectsForDeepLinks(ctx, app, proj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -2035,7 +2044,7 @@ func (s *Server) resolveRevision(ctx context.Context, app *appv1.Application, sy
|
||||
func (s *Server) TerminateOperation(ctx context.Context, termOpReq *application.OperationTerminateRequest) (*application.OperationTerminateResponse, error) {
|
||||
appName := termOpReq.GetName()
|
||||
appNs := s.appNamespaceOrDefault(termOpReq.GetAppNamespace())
|
||||
a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionSync, termOpReq.GetProject(), appNs, appName, "")
|
||||
a, _, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionSync, termOpReq.GetProject(), appNs, appName, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -2108,7 +2117,7 @@ func (s *Server) ListResourceActions(ctx context.Context, q *application.Applica
|
||||
|
||||
func (s *Server) getUnstructuredLiveResourceOrApp(ctx context.Context, rbacRequest string, q *application.ApplicationResourceRequest) (obj *unstructured.Unstructured, res *appv1.ResourceNode, app *appv1.Application, config *rest.Config, err error) {
|
||||
if q.GetKind() == applicationType.ApplicationKind && q.GetGroup() == applicationType.Group && q.GetName() == q.GetResourceName() {
|
||||
app, err = s.getApplicationEnforceRBACInformer(ctx, rbacRequest, q.GetProject(), q.GetAppNamespace(), q.GetName())
|
||||
app, _, err = s.getApplicationEnforceRBACInformer(ctx, rbacRequest, q.GetProject(), q.GetAppNamespace(), q.GetName())
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
@@ -2204,6 +2213,11 @@ func (s *Server) RunResourceAction(ctx context.Context, q *application.ResourceA
|
||||
}
|
||||
}
|
||||
|
||||
proj, err := s.getAppProject(ctx, a, log.WithField("application", a.Name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// First, make sure all the returned resources are permitted, for each operation.
|
||||
// Also perform create with dry-runs for all create-operation resources.
|
||||
// This is performed separately to reduce the risk of only some of the resources being successfully created later.
|
||||
@@ -2211,7 +2225,7 @@ func (s *Server) RunResourceAction(ctx context.Context, q *application.ResourceA
|
||||
// the dry-run for relevant apply/delete operation would have to be invoked as well.
|
||||
for _, impactedResource := range newObjects {
|
||||
newObj := impactedResource.UnstructuredObj
|
||||
err := s.verifyResourcePermitted(ctx, app, newObj)
|
||||
err := s.verifyResourcePermitted(ctx, app, proj, newObj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -2305,14 +2319,7 @@ func (s *Server) patchResource(ctx context.Context, config *rest.Config, liveObj
|
||||
return &application.ApplicationResponse{}, nil
|
||||
}
|
||||
|
||||
func (s *Server) verifyResourcePermitted(ctx context.Context, app *appv1.Application, obj *unstructured.Unstructured) error {
|
||||
proj, err := argo.GetAppProject(app, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
|
||||
if err != nil {
|
||||
if apierr.IsNotFound(err) {
|
||||
return fmt.Errorf("application references project %s which does not exist", app.Spec.Project)
|
||||
}
|
||||
return fmt.Errorf("failed to get project %s: %w", app.Spec.Project, err)
|
||||
}
|
||||
func (s *Server) verifyResourcePermitted(ctx context.Context, app *appv1.Application, proj *appv1.AppProject, obj *unstructured.Unstructured) error {
|
||||
permitted, err := proj.IsResourcePermitted(schema.GroupKind{Group: obj.GroupVersionKind().Group, Kind: obj.GroupVersionKind().Kind}, obj.GetNamespace(), app.Spec.Destination, func(project string) ([]*appv1.Cluster, error) {
|
||||
clusters, err := s.db.GetProjectClusters(context.TODO(), project)
|
||||
if err != nil {
|
||||
@@ -2372,16 +2379,11 @@ func splitStatusPatch(patch []byte) ([]byte, []byte, error) {
|
||||
}
|
||||
|
||||
func (s *Server) GetApplicationSyncWindows(ctx context.Context, q *application.ApplicationSyncWindowsQuery) (*application.ApplicationSyncWindowsResponse, error) {
|
||||
a, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName(), "")
|
||||
a, proj, err := s.getApplicationEnforceRBACClient(ctx, rbacpolicy.ActionGet, q.GetProject(), q.GetAppNamespace(), q.GetName(), "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proj, err := argo.GetAppProject(a, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), s.ns, s.settingsMgr, s.db, ctx)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting app project: %w", err)
|
||||
}
|
||||
|
||||
windows := proj.Spec.SyncWindows.Matches(a)
|
||||
sync := windows.CanSync(true)
|
||||
|
||||
|
||||
@@ -1439,6 +1439,27 @@ func TestCreateAppWithDestName(t *testing.T) {
|
||||
assert.Equal(t, app.Spec.Destination.Server, "https://cluster-api.com")
|
||||
}
|
||||
|
||||
// TestCreateAppWithOperation tests that an application created with an operation is created with the operation removed.
|
||||
// Avoids regressions of https://github.com/argoproj/argo-cd/security/advisories/GHSA-g623-jcgg-mhmm
|
||||
func TestCreateAppWithOperation(t *testing.T) {
|
||||
appServer := newTestAppServer(t)
|
||||
testApp := newTestAppWithDestName()
|
||||
testApp.Operation = &appsv1.Operation{
|
||||
Sync: &appsv1.SyncOperation{
|
||||
Manifests: []string{
|
||||
"test",
|
||||
},
|
||||
},
|
||||
}
|
||||
createReq := application.ApplicationCreateRequest{
|
||||
Application: testApp,
|
||||
}
|
||||
app, err := appServer.Create(context.Background(), &createReq)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, app)
|
||||
assert.Nil(t, app.Operation)
|
||||
}
|
||||
|
||||
func TestUpdateApp(t *testing.T) {
|
||||
testApp := newTestApp()
|
||||
appServer := newTestAppServer(t, testApp)
|
||||
@@ -1797,7 +1818,7 @@ func TestServer_GetApplicationSyncWindowsState(t *testing.T) {
|
||||
appServer := newTestAppServer(t, testApp)
|
||||
|
||||
active, err := appServer.GetApplicationSyncWindows(context.Background(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name})
|
||||
assert.Contains(t, err.Error(), "not found")
|
||||
assert.Contains(t, err.Error(), "not exist")
|
||||
assert.Nil(t, active)
|
||||
})
|
||||
}
|
||||
@@ -2407,7 +2428,16 @@ func TestAppNamespaceRestrictions(t *testing.T) {
|
||||
t.Run("Get application in other namespace when allowed", func(t *testing.T) {
|
||||
testApp := newTestApp()
|
||||
testApp.Namespace = "argocd-1"
|
||||
appServer := newTestAppServer(t, testApp)
|
||||
testApp.Spec.Project = "other-ns"
|
||||
otherNsProj := &appsv1.AppProject{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
|
||||
Spec: appsv1.AppProjectSpec{
|
||||
SourceRepos: []string{"*"},
|
||||
Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
|
||||
SourceNamespaces: []string{"argocd-1"},
|
||||
},
|
||||
}
|
||||
appServer := newTestAppServer(t, testApp, otherNsProj)
|
||||
appServer.enabledNamespaces = []string{"argocd-1"}
|
||||
app, err := appServer.Get(context.TODO(), &application.ApplicationQuery{
|
||||
Name: pointer.String("test-app"),
|
||||
@@ -2418,6 +2448,28 @@ func TestAppNamespaceRestrictions(t *testing.T) {
|
||||
require.Equal(t, "argocd-1", app.Namespace)
|
||||
require.Equal(t, "test-app", app.Name)
|
||||
})
|
||||
t.Run("Get application in other namespace when project is not allowed", func(t *testing.T) {
|
||||
testApp := newTestApp()
|
||||
testApp.Namespace = "argocd-1"
|
||||
testApp.Spec.Project = "other-ns"
|
||||
otherNsProj := &appsv1.AppProject{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
|
||||
Spec: appsv1.AppProjectSpec{
|
||||
SourceRepos: []string{"*"},
|
||||
Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
|
||||
SourceNamespaces: []string{"argocd-2"},
|
||||
},
|
||||
}
|
||||
appServer := newTestAppServer(t, testApp, otherNsProj)
|
||||
appServer.enabledNamespaces = []string{"argocd-1"}
|
||||
app, err := appServer.Get(context.TODO(), &application.ApplicationQuery{
|
||||
Name: pointer.String("test-app"),
|
||||
AppNamespace: pointer.String("argocd-1"),
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Nil(t, app)
|
||||
require.ErrorContains(t, err, "app is not allowed in project")
|
||||
})
|
||||
t.Run("Create application in other namespace when allowed", func(t *testing.T) {
|
||||
testApp := newTestApp()
|
||||
testApp.Namespace = "argocd-1"
|
||||
@@ -2460,7 +2512,7 @@ func TestAppNamespaceRestrictions(t *testing.T) {
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Nil(t, app)
|
||||
require.ErrorContains(t, err, "not allowed to use project")
|
||||
require.ErrorContains(t, err, "app is not allowed in project")
|
||||
})
|
||||
|
||||
t.Run("Create application in other namespace when not allowed by configuration", func(t *testing.T) {
|
||||
@@ -2484,5 +2536,84 @@ func TestAppNamespaceRestrictions(t *testing.T) {
|
||||
require.Nil(t, app)
|
||||
require.ErrorContains(t, err, "namespace 'argocd-1' is not permitted")
|
||||
})
|
||||
|
||||
t.Run("Get application sync window in other namespace when project is allowed", func(t *testing.T) {
|
||||
testApp := newTestApp()
|
||||
testApp.Namespace = "argocd-1"
|
||||
testApp.Spec.Project = "other-ns"
|
||||
otherNsProj := &appsv1.AppProject{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
|
||||
Spec: appsv1.AppProjectSpec{
|
||||
SourceRepos: []string{"*"},
|
||||
Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
|
||||
SourceNamespaces: []string{"argocd-1"},
|
||||
},
|
||||
}
|
||||
appServer := newTestAppServer(t, testApp, otherNsProj)
|
||||
appServer.enabledNamespaces = []string{"argocd-1"}
|
||||
active, err := appServer.GetApplicationSyncWindows(context.TODO(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name, AppNamespace: &testApp.Namespace})
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, len(active.ActiveWindows))
|
||||
})
|
||||
t.Run("Get application sync window in other namespace when project is not allowed", func(t *testing.T) {
|
||||
testApp := newTestApp()
|
||||
testApp.Namespace = "argocd-1"
|
||||
testApp.Spec.Project = "other-ns"
|
||||
otherNsProj := &appsv1.AppProject{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
|
||||
Spec: appsv1.AppProjectSpec{
|
||||
SourceRepos: []string{"*"},
|
||||
Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
|
||||
SourceNamespaces: []string{"argocd-2"},
|
||||
},
|
||||
}
|
||||
appServer := newTestAppServer(t, testApp, otherNsProj)
|
||||
appServer.enabledNamespaces = []string{"argocd-1"}
|
||||
active, err := appServer.GetApplicationSyncWindows(context.TODO(), &application.ApplicationSyncWindowsQuery{Name: &testApp.Name, AppNamespace: &testApp.Namespace})
|
||||
require.Error(t, err)
|
||||
require.Nil(t, active)
|
||||
require.ErrorContains(t, err, "app is not allowed in project")
|
||||
})
|
||||
t.Run("Get list of links in other namespace when project is not allowed", func(t *testing.T) {
|
||||
testApp := newTestApp()
|
||||
testApp.Namespace = "argocd-1"
|
||||
testApp.Spec.Project = "other-ns"
|
||||
otherNsProj := &appsv1.AppProject{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
|
||||
Spec: appsv1.AppProjectSpec{
|
||||
SourceRepos: []string{"*"},
|
||||
Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
|
||||
SourceNamespaces: []string{"argocd-2"},
|
||||
},
|
||||
}
|
||||
appServer := newTestAppServer(t, testApp, otherNsProj)
|
||||
appServer.enabledNamespaces = []string{"argocd-1"}
|
||||
links, err := appServer.ListLinks(context.TODO(), &application.ListAppLinksRequest{
|
||||
Name: pointer.String("test-app"),
|
||||
Namespace: pointer.String("argocd-1"),
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.Nil(t, links)
|
||||
require.ErrorContains(t, err, "app is not allowed in project")
|
||||
})
|
||||
t.Run("Get list of links in other namespace when project is allowed", func(t *testing.T) {
|
||||
testApp := newTestApp()
|
||||
testApp.Namespace = "argocd-1"
|
||||
testApp.Spec.Project = "other-ns"
|
||||
otherNsProj := &appsv1.AppProject{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "other-ns", Namespace: "default"},
|
||||
Spec: appsv1.AppProjectSpec{
|
||||
SourceRepos: []string{"*"},
|
||||
Destinations: []appsv1.ApplicationDestination{{Server: "*", Namespace: "*"}},
|
||||
SourceNamespaces: []string{"argocd-1"},
|
||||
},
|
||||
}
|
||||
appServer := newTestAppServer(t, testApp, otherNsProj)
|
||||
appServer.enabledNamespaces = []string{"argocd-1"}
|
||||
links, err := appServer.ListLinks(context.TODO(), &application.ListAppLinksRequest{
|
||||
Name: pointer.String("test-app"),
|
||||
Namespace: pointer.String("argocd-1"),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 0, len(links.Items))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -38,12 +38,12 @@ type terminalHandler struct {
|
||||
allowedShells []string
|
||||
namespace string
|
||||
enabledNamespaces []string
|
||||
sessionManager util_session.SessionManager
|
||||
sessionManager *util_session.SessionManager
|
||||
}
|
||||
|
||||
// NewHandler returns a new terminal handler.
|
||||
func NewHandler(appLister applisters.ApplicationLister, namespace string, enabledNamespaces []string, db db.ArgoDB, enf *rbac.Enforcer, cache *servercache.Cache,
|
||||
appResourceTree AppResourceTreeFn, allowedShells []string, sessionManager util_session.SessionManager) *terminalHandler {
|
||||
appResourceTree AppResourceTreeFn, allowedShells []string, sessionManager *util_session.SessionManager) *terminalHandler {
|
||||
return &terminalHandler{
|
||||
appLister: appLister,
|
||||
db: db,
|
||||
|
||||
@@ -37,7 +37,7 @@ type terminalSession struct {
|
||||
tty bool
|
||||
readLock sync.Mutex
|
||||
writeLock sync.Mutex
|
||||
sessionManager util_session.SessionManager
|
||||
sessionManager *util_session.SessionManager
|
||||
token *string
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ func getToken(r *http.Request) (string, error) {
|
||||
}
|
||||
|
||||
// newTerminalSession create terminalSession
|
||||
func newTerminalSession(w http.ResponseWriter, r *http.Request, responseHeader http.Header, sessionManager util_session.SessionManager) (*terminalSession, error) {
|
||||
func newTerminalSession(w http.ResponseWriter, r *http.Request, responseHeader http.Header, sessionManager *util_session.SessionManager) (*terminalSession, error) {
|
||||
token, err := getToken(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -992,10 +992,12 @@ func (a *ArgoCDServer) newHTTPServer(ctx context.Context, port int, grpcWebHandl
|
||||
}
|
||||
if len(a.ContentTypes) > 0 {
|
||||
handler = enforceContentTypes(handler, a.ContentTypes)
|
||||
} else {
|
||||
log.WithField(common.SecurityField, common.SecurityHigh).Warnf("Content-Type enforcement is disabled, which may make your API vulnerable to CSRF attacks")
|
||||
}
|
||||
mux.Handle("/api/", handler)
|
||||
|
||||
terminal := application.NewHandler(a.appLister, a.Namespace, a.ApplicationNamespaces, a.db, a.enf, a.Cache, appResourceTreeFn, a.settings.ExecShells, *a.sessionMgr).
|
||||
terminal := application.NewHandler(a.appLister, a.Namespace, a.ApplicationNamespaces, a.db, a.enf, a.Cache, appResourceTreeFn, a.settings.ExecShells, a.sessionMgr).
|
||||
WithFeatureFlagMiddleware(a.settingsMgr.GetSettings)
|
||||
th := util_session.WithAuthMiddleware(a.DisableAuth, a.sessionMgr, terminal)
|
||||
mux.Handle("/terminal", th)
|
||||
|
||||
@@ -1425,3 +1425,46 @@ func TestReplaceBaseHRef(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_enforceContentTypes(t *testing.T) {
|
||||
getBaseHandler := func(t *testing.T, allow bool) http.Handler {
|
||||
return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||||
assert.True(t, allow, "http handler was hit when it should have been blocked by content type enforcement")
|
||||
writer.WriteHeader(200)
|
||||
})
|
||||
}
|
||||
|
||||
t.Parallel()
|
||||
|
||||
t.Run("GET - not providing a content type, should still succeed", func(t *testing.T) {
|
||||
handler := enforceContentTypes(getBaseHandler(t, true), []string{"application/json"}).(http.HandlerFunc)
|
||||
req := httptest.NewRequest("GET", "/", nil)
|
||||
w := httptest.NewRecorder()
|
||||
handler(w, req)
|
||||
resp := w.Result()
|
||||
assert.Equal(t, 200, resp.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("POST", func(t *testing.T) {
|
||||
handler := enforceContentTypes(getBaseHandler(t, true), []string{"application/json"}).(http.HandlerFunc)
|
||||
req := httptest.NewRequest("POST", "/", nil)
|
||||
w := httptest.NewRecorder()
|
||||
handler(w, req)
|
||||
resp := w.Result()
|
||||
assert.Equal(t, 415, resp.StatusCode, "didn't provide a content type, should have gotten an error")
|
||||
|
||||
req = httptest.NewRequest("POST", "/", nil)
|
||||
req.Header = map[string][]string{"Content-Type": {"application/json"}}
|
||||
w = httptest.NewRecorder()
|
||||
handler(w, req)
|
||||
resp = w.Result()
|
||||
assert.Equal(t, 200, resp.StatusCode, "should have passed, since an allowed content type was provided")
|
||||
|
||||
req = httptest.NewRequest("POST", "/", nil)
|
||||
req.Header = map[string][]string{"Content-Type": {"not-allowed"}}
|
||||
w = httptest.NewRecorder()
|
||||
handler(w, req)
|
||||
resp = w.Result()
|
||||
assert.Equal(t, 415, resp.StatusCode, "should not have passed, since a disallowed content type was provided")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -432,7 +432,7 @@ func TestNamespacedInvalidAppProject(t *testing.T) {
|
||||
Then().
|
||||
// We're not allowed to infer whether the project exists based on this error message. Instead, we get a generic
|
||||
// permission denied error.
|
||||
Expect(Error("", "permission denied"))
|
||||
Expect(Error("", "is not allowed"))
|
||||
}
|
||||
|
||||
func TestNamespacedAppDeletion(t *testing.T) {
|
||||
@@ -748,7 +748,7 @@ func TestNamespacedResourceDiffing(t *testing.T) {
|
||||
Then().
|
||||
Expect(SyncStatusIs(SyncStatusCodeOutOfSync)).
|
||||
And(func(app *Application) {
|
||||
diffOutput, err := RunCli("app", "diff", ctx.AppQualifiedName(), "--local", "testdata/guestbook")
|
||||
diffOutput, err := RunCli("app", "diff", ctx.AppQualifiedName(), "--local-repo-root", ".", "--local", "testdata/guestbook")
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, diffOutput, fmt.Sprintf("===== apps/Deployment %s/guestbook-ui ======", DeploymentNamespace()))
|
||||
}).
|
||||
@@ -761,7 +761,7 @@ func TestNamespacedResourceDiffing(t *testing.T) {
|
||||
Then().
|
||||
Expect(SyncStatusIs(SyncStatusCodeSynced)).
|
||||
And(func(app *Application) {
|
||||
diffOutput, err := RunCli("app", "diff", ctx.AppQualifiedName(), "--local", "testdata/guestbook")
|
||||
diffOutput, err := RunCli("app", "diff", ctx.AppQualifiedName(), "--local-repo-root", ".", "--local", "testdata/guestbook")
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, diffOutput)
|
||||
}).
|
||||
@@ -897,7 +897,7 @@ func testNSEdgeCasesApplicationResources(t *testing.T, appPath string, statusCod
|
||||
expect.
|
||||
Expect(HealthIs(statusCode)).
|
||||
And(func(app *Application) {
|
||||
diffOutput, err := RunCli("app", "diff", ctx.AppQualifiedName(), "--local", path.Join("testdata", appPath))
|
||||
diffOutput, err := RunCli("app", "diff", ctx.AppQualifiedName(), "--local-repo-root", ".", "--local", path.Join("testdata", appPath))
|
||||
assert.Empty(t, diffOutput)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
@@ -998,7 +998,7 @@ func TestNamespacedLocalManifestSync(t *testing.T) {
|
||||
Given().
|
||||
LocalPath(guestbookPathLocal).
|
||||
When().
|
||||
Sync().
|
||||
Sync("--local-repo-root", ".").
|
||||
Then().
|
||||
Expect(SyncStatusIs(SyncStatusCodeSynced)).
|
||||
And(func(app *Application) {
|
||||
@@ -1066,7 +1066,7 @@ func TestNamespacedLocalSyncDryRunWithASEnabled(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
appBefore := app.DeepCopy()
|
||||
_, err = RunCli("app", "sync", app.QualifiedName(), "--dry-run", "--local", guestbookPathLocal)
|
||||
_, err = RunCli("app", "sync", app.QualifiedName(), "--dry-run", "--local-repo-root", ".", "--local", guestbookPathLocal)
|
||||
assert.NoError(t, err)
|
||||
|
||||
appAfter := app.DeepCopy()
|
||||
|
||||
@@ -547,7 +547,7 @@ func TestInvalidAppProject(t *testing.T) {
|
||||
Then().
|
||||
// We're not allowed to infer whether the project exists based on this error message. Instead, we get a generic
|
||||
// permission denied error.
|
||||
Expect(Error("", "permission denied"))
|
||||
Expect(Error("", "is not allowed"))
|
||||
}
|
||||
|
||||
func TestAppDeletion(t *testing.T) {
|
||||
@@ -1324,7 +1324,7 @@ func TestLocalManifestSync(t *testing.T) {
|
||||
Given().
|
||||
LocalPath(guestbookPathLocal).
|
||||
When().
|
||||
Sync().
|
||||
Sync("--local-repo-root", ".").
|
||||
Then().
|
||||
Expect(SyncStatusIs(SyncStatusCodeSynced)).
|
||||
And(func(app *Application) {
|
||||
@@ -1385,7 +1385,7 @@ func TestLocalSyncDryRunWithAutosyncEnabled(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
appBefore := app.DeepCopy()
|
||||
_, err = RunCli("app", "sync", app.Name, "--dry-run", "--local", guestbookPathLocal)
|
||||
_, err = RunCli("app", "sync", app.Name, "--dry-run", "--local-repo-root", ".", "--local", guestbookPathLocal)
|
||||
assert.NoError(t, err)
|
||||
|
||||
appAfter := app.DeepCopy()
|
||||
|
||||
@@ -54,12 +54,14 @@ func TestDeclarativeInvalidProject(t *testing.T) {
|
||||
Expect(Success("")).
|
||||
Expect(HealthIs(health.HealthStatusUnknown)).
|
||||
Expect(SyncStatusIs(SyncStatusCodeUnknown)).
|
||||
Expect(Condition(ApplicationConditionInvalidSpecError, "Application referencing project garbage which does not exist")).
|
||||
When().
|
||||
Delete(false).
|
||||
Then().
|
||||
Expect(Success("")).
|
||||
Expect(DoesNotExist())
|
||||
Expect(Condition(ApplicationConditionInvalidSpecError, "Application referencing project garbage which does not exist"))
|
||||
// TODO: you can`t delete application with invalid project due to enforcment that was recently added,
|
||||
// in https://github.com/argoproj/argo-cd/security/advisories/GHSA-2gvw-w6fj-7m3c
|
||||
//When().
|
||||
//Delete(false).
|
||||
//Then().
|
||||
//Expect(Success("")).
|
||||
//Expect(DoesNotExist())
|
||||
}
|
||||
|
||||
func TestDeclarativeInvalidRepoURL(t *testing.T) {
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
RevisionHelpIcon
|
||||
} from '../../../shared/components';
|
||||
import {BadgePanel, Spinner} from '../../../shared/components';
|
||||
import {Consumer, ContextApis} from '../../../shared/context';
|
||||
import {AuthSettingsCtx, Consumer, ContextApis} from '../../../shared/context';
|
||||
import * as models from '../../../shared/models';
|
||||
import {services} from '../../../shared/services';
|
||||
|
||||
@@ -30,6 +30,7 @@ import {EditAnnotations} from './edit-annotations';
|
||||
|
||||
import './application-summary.scss';
|
||||
import {DeepLinks} from '../../../shared/components/deep-links';
|
||||
import {ExternalLinks} from '../application-urls';
|
||||
|
||||
function swap(array: any[], a: number, b: number) {
|
||||
array = array.slice();
|
||||
@@ -47,6 +48,7 @@ export const ApplicationSummary = (props: ApplicationSummaryProps) => {
|
||||
const source = getAppDefaultSource(app);
|
||||
const isHelm = source.hasOwnProperty('chart');
|
||||
const initialState = app.spec.destination.server === undefined ? 'NAME' : 'URL';
|
||||
const useAuthSettingsCtx = React.useContext(AuthSettingsCtx);
|
||||
const [destFormat, setDestFormat] = React.useState(initialState);
|
||||
const [changeSync, setChangeSync] = React.useState(false);
|
||||
|
||||
@@ -325,20 +327,19 @@ export const ApplicationSummary = (props: ApplicationSummaryProps) => {
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
const urls = app.status.summary.externalURLs || [];
|
||||
const urls = ExternalLinks(app.status.summary.externalURLs);
|
||||
if (urls.length > 0) {
|
||||
attributes.push({
|
||||
title: 'URLs',
|
||||
view: (
|
||||
<React.Fragment>
|
||||
{urls
|
||||
.map(item => item.split('|'))
|
||||
.map((parts, i) => (
|
||||
<a key={i} href={parts.length > 1 ? parts[1] : parts[0]} target='__blank'>
|
||||
{parts[0]}
|
||||
{urls.map((url, i) => {
|
||||
return (
|
||||
<a key={i} href={url.ref} target='__blank'>
|
||||
{url.title}
|
||||
</a>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</React.Fragment>
|
||||
)
|
||||
});
|
||||
@@ -589,7 +590,7 @@ export const ApplicationSummary = (props: ApplicationSummaryProps) => {
|
||||
</div>
|
||||
)}
|
||||
</Consumer>
|
||||
<BadgePanel app={props.app.metadata.name} />
|
||||
<BadgePanel app={props.app.metadata.name} appNamespace={props.app.metadata.namespace} nsEnabled={useAuthSettingsCtx?.appsInAnyNamespaceEnabled} />
|
||||
<EditablePanel
|
||||
save={updateApp}
|
||||
values={app}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {ExternalLink, InvalidExternalLinkError} from './application-urls';
|
||||
import { ExternalLink, ExternalLinks, InvalidExternalLinkError } from './application-urls';
|
||||
|
||||
test('rejects malicious URLs', () => {
|
||||
expect(() => {
|
||||
@@ -7,6 +7,16 @@ test('rejects malicious URLs', () => {
|
||||
expect(() => {
|
||||
const _ = new ExternalLink('data:text/html;<h1>hi</h1>');
|
||||
}).toThrowError(InvalidExternalLinkError);
|
||||
expect(() => {
|
||||
const _ = new ExternalLink('title|data:text/html;<h1>hi</h1>');
|
||||
}).toThrowError(InvalidExternalLinkError);
|
||||
expect(() => {
|
||||
const _ = new ExternalLink('data:title|data:text/html;<h1>hi</h1>');
|
||||
}).toThrowError(InvalidExternalLinkError);
|
||||
|
||||
expect(() => {
|
||||
const _ = new ExternalLink('data:title|https://localhost:8080/applications');
|
||||
}).not.toThrowError(InvalidExternalLinkError);
|
||||
});
|
||||
|
||||
test('allows absolute URLs', () => {
|
||||
@@ -18,3 +28,59 @@ test('allows relative URLs', () => {
|
||||
window.location = new URL('https://localhost:8080/applications');
|
||||
expect(new ExternalLink('/applications').ref).toEqual('/applications');
|
||||
});
|
||||
|
||||
|
||||
test('URLs format', () => {
|
||||
expect(new ExternalLink('https://localhost:8080/applications')).toEqual({
|
||||
ref: 'https://localhost:8080/applications',
|
||||
title: 'https://localhost:8080/applications',
|
||||
})
|
||||
expect(new ExternalLink('title|https://localhost:8080/applications')).toEqual({
|
||||
ref: 'https://localhost:8080/applications',
|
||||
title: 'title',
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
test('malicious URLs from list to be removed', () => {
|
||||
const urls: string[] = [
|
||||
'javascript:alert("hi")',
|
||||
'https://localhost:8080/applications',
|
||||
]
|
||||
const links = ExternalLinks(urls);
|
||||
|
||||
expect(links).toHaveLength(1);
|
||||
expect(links).toContainEqual({
|
||||
ref: 'https://localhost:8080/applications',
|
||||
title: 'https://localhost:8080/applications',
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
test('list to be sorted', () => {
|
||||
const urls: string[] = [
|
||||
'https://a',
|
||||
'https://b',
|
||||
'a|https://c',
|
||||
'z|https://c',
|
||||
'x|https://d',
|
||||
'x|https://c',
|
||||
]
|
||||
const links = ExternalLinks(urls);
|
||||
|
||||
// 'a|https://c',
|
||||
// 'x|https://c',
|
||||
// 'x|https://d',
|
||||
// 'z|https://c',
|
||||
// 'https://a',
|
||||
// 'https://b',
|
||||
expect(links).toHaveLength(6);
|
||||
expect(links[0].title).toEqual('a')
|
||||
expect(links[1].title).toEqual('x')
|
||||
expect(links[1].ref).toEqual('https://c')
|
||||
expect(links[2].title).toEqual('x')
|
||||
expect(links[2].ref).toEqual('https://d')
|
||||
expect(links[3].title).toEqual('z')
|
||||
expect(links[4].title).toEqual('https://a')
|
||||
expect(links[5].title).toEqual('https://b')
|
||||
});
|
||||
|
||||
@@ -29,7 +29,7 @@ export class ExternalLink {
|
||||
}
|
||||
}
|
||||
|
||||
export const ApplicationURLs = ({urls}: {urls: string[]}) => {
|
||||
export const ExternalLinks = (urls?: string[]) => {
|
||||
const externalLinks: ExternalLink[] = [];
|
||||
for (const url of urls || []) {
|
||||
try {
|
||||
@@ -42,16 +42,26 @@ export const ApplicationURLs = ({urls}: {urls: string[]}) => {
|
||||
|
||||
// sorted alphabetically & links with titles first
|
||||
externalLinks.sort((a, b) => {
|
||||
if (a.title !== '' && b.title !== '') {
|
||||
const hasTitle = (x: ExternalLink): boolean => {
|
||||
return x.title !== x.ref && x.title !== '';
|
||||
};
|
||||
|
||||
if (hasTitle(a) && hasTitle(b) && a.title !== b.title) {
|
||||
return a.title > b.title ? 1 : -1;
|
||||
} else if (a.title === '') {
|
||||
} else if (hasTitle(b) && !hasTitle(a)) {
|
||||
return 1;
|
||||
} else if (b.title === '') {
|
||||
} else if (hasTitle(a) && !hasTitle(b)) {
|
||||
return -1;
|
||||
}
|
||||
return a.ref > b.ref ? 1 : -1;
|
||||
});
|
||||
|
||||
return externalLinks;
|
||||
};
|
||||
|
||||
export const ApplicationURLs = ({urls}: {urls: string[]}) => {
|
||||
const externalLinks: ExternalLink[] = ExternalLinks(urls);
|
||||
|
||||
return (
|
||||
((externalLinks || []).length > 0 && (
|
||||
<div className='applications-list__external-links-icon-container'>
|
||||
|
||||
@@ -6,7 +6,7 @@ import {Context} from '../../context';
|
||||
|
||||
require('./badge-panel.scss');
|
||||
|
||||
export const BadgePanel = ({app, project}: {app?: string; project?: string}) => {
|
||||
export const BadgePanel = ({app, project, appNamespace, nsEnabled}: {app?: string; project?: string; appNamespace?: string; nsEnabled?: boolean}) => {
|
||||
const [badgeType, setBadgeType] = React.useState('URL');
|
||||
const context = React.useContext(Context);
|
||||
if (!app && !project) {
|
||||
@@ -20,6 +20,9 @@ export const BadgePanel = ({app, project}: {app?: string; project?: string}) =>
|
||||
let alt = '';
|
||||
if (app) {
|
||||
badgeURL = `${root}api/badge?name=${app}&revision=true`;
|
||||
if (nsEnabled) {
|
||||
badgeURL += `&namespace=${appNamespace}`;
|
||||
}
|
||||
entityURL = `${root}applications/${app}`;
|
||||
alt = 'App Status';
|
||||
} else if (project) {
|
||||
|
||||
@@ -51,19 +51,19 @@ export default {
|
||||
},
|
||||
|
||||
post(url: string) {
|
||||
return initHandlers(agent.post(`${apiRoot()}${url}`));
|
||||
return initHandlers(agent.post(`${apiRoot()}${url}`)).set('Content-Type', 'application/json');
|
||||
},
|
||||
|
||||
put(url: string) {
|
||||
return initHandlers(agent.put(`${apiRoot()}${url}`));
|
||||
return initHandlers(agent.put(`${apiRoot()}${url}`)).set('Content-Type', 'application/json');
|
||||
},
|
||||
|
||||
patch(url: string) {
|
||||
return initHandlers(agent.patch(`${apiRoot()}${url}`));
|
||||
return initHandlers(agent.patch(`${apiRoot()}${url}`)).set('Content-Type', 'application/json');
|
||||
},
|
||||
|
||||
delete(url: string) {
|
||||
return initHandlers(agent.del(`${apiRoot()}${url}`));
|
||||
return initHandlers(agent.del(`${apiRoot()}${url}`)).set('Content-Type', 'application/json');
|
||||
},
|
||||
|
||||
loadEventSource(url: string): Observable<string> {
|
||||
|
||||
26
ui/yarn.lock
26
ui/yarn.lock
@@ -4596,10 +4596,10 @@ fs-minipass@^2.0.0:
|
||||
dependencies:
|
||||
minipass "^3.0.0"
|
||||
|
||||
fs-monkey@1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.3.tgz#ae3ac92d53bb328efe0e9a1d9541f6ad8d48e2d3"
|
||||
integrity sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q==
|
||||
fs-monkey@^1.0.4:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.5.tgz#fe450175f0db0d7ea758102e1d84096acb925788"
|
||||
integrity sha512-8uMbBjrhzW76TYgEV27Y5E//W2f/lTFmx78P2w19FZSxarhI/798APGQyuGCwmkNxgwGRhrLfvWyLBvNtuOmew==
|
||||
|
||||
fs.realpath@^1.0.0:
|
||||
version "1.0.0"
|
||||
@@ -6264,12 +6264,12 @@ media-typer@0.3.0:
|
||||
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
||||
integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==
|
||||
|
||||
memfs@^3.4.1:
|
||||
version "3.4.1"
|
||||
resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.4.1.tgz#b78092f466a0dce054d63d39275b24c71d3f1305"
|
||||
integrity sha512-1c9VPVvW5P7I85c35zAdEr1TD5+F11IToIHIlrVIcflfnzPkJa0ZoYEoEdYDP8KgPFoSZ/opDrUsAoZWym3mtw==
|
||||
memfs@^3.4.3:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6"
|
||||
integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ==
|
||||
dependencies:
|
||||
fs-monkey "1.0.3"
|
||||
fs-monkey "^1.0.4"
|
||||
|
||||
merge-descriptors@1.0.1:
|
||||
version "1.0.1"
|
||||
@@ -9652,12 +9652,12 @@ webpack-cli@^4.9.2:
|
||||
webpack-merge "^5.7.3"
|
||||
|
||||
webpack-dev-middleware@^5.3.1:
|
||||
version "5.3.1"
|
||||
resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.1.tgz#aa079a8dedd7e58bfeab358a9af7dab304cee57f"
|
||||
integrity sha512-81EujCKkyles2wphtdrnPg/QqegC/AtqNH//mQkBYSMqwFVCQrxM6ktB2O/SPlZy7LqeEfTbV3cZARGQz6umhg==
|
||||
version "5.3.4"
|
||||
resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz#eb7b39281cbce10e104eb2b8bf2b63fce49a3517"
|
||||
integrity sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==
|
||||
dependencies:
|
||||
colorette "^2.0.10"
|
||||
memfs "^3.4.1"
|
||||
memfs "^3.4.3"
|
||||
mime-types "^2.1.31"
|
||||
range-parser "^1.2.1"
|
||||
schema-utils "^4.0.0"
|
||||
|
||||
@@ -694,8 +694,7 @@ func GetAppProject(app *argoappv1.Application, projLister applicationsv1.AppProj
|
||||
return nil, err
|
||||
}
|
||||
if !proj.IsAppNamespacePermitted(app, ns) {
|
||||
return nil, fmt.Errorf("application '%s' in namespace '%s' is not allowed to use project '%s'",
|
||||
app.Name, app.Namespace, proj.Name)
|
||||
return nil, argoappv1.NewErrApplicationNotAllowedToUseProject(app.Name, app.Namespace, proj.Name)
|
||||
}
|
||||
return proj, nil
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/managedfields"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
|
||||
appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/diff"
|
||||
@@ -31,7 +32,7 @@ func NewDiffConfigBuilder() *DiffConfigBuilder {
|
||||
}
|
||||
|
||||
// WithDiffSettings will set the diff settings in the builder.
|
||||
func (b *DiffConfigBuilder) WithDiffSettings(id []v1alpha1.ResourceIgnoreDifferences, o map[string]v1alpha1.ResourceOverride, ignoreAggregatedRoles bool) *DiffConfigBuilder {
|
||||
func (b *DiffConfigBuilder) WithDiffSettings(id []v1alpha1.ResourceIgnoreDifferences, o map[string]v1alpha1.ResourceOverride, ignoreAggregatedRoles bool, ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts) *DiffConfigBuilder {
|
||||
ignores := id
|
||||
if ignores == nil {
|
||||
ignores = []v1alpha1.ResourceIgnoreDifferences{}
|
||||
@@ -44,6 +45,7 @@ func (b *DiffConfigBuilder) WithDiffSettings(id []v1alpha1.ResourceIgnoreDiffere
|
||||
}
|
||||
b.diffConfig.overrides = overrides
|
||||
b.diffConfig.ignoreAggregatedRoles = ignoreAggregatedRoles
|
||||
b.diffConfig.ignoreNormalizerOpts = ignoreNormalizerOpts
|
||||
return b
|
||||
}
|
||||
|
||||
@@ -140,6 +142,8 @@ type DiffConfig interface {
|
||||
// Manager returns the manager that should be used by the diff while
|
||||
// calculating the structured merge diff.
|
||||
Manager() string
|
||||
|
||||
IgnoreNormalizerOpts() normalizers.IgnoreNormalizerOpts
|
||||
}
|
||||
|
||||
// diffConfig defines the configurations used while applying diffs.
|
||||
@@ -156,6 +160,7 @@ type diffConfig struct {
|
||||
gvkParser *k8smanagedfields.GvkParser
|
||||
structuredMergeDiff bool
|
||||
manager string
|
||||
ignoreNormalizerOpts normalizers.IgnoreNormalizerOpts
|
||||
}
|
||||
|
||||
func (c *diffConfig) Ignores() []v1alpha1.ResourceIgnoreDifferences {
|
||||
@@ -194,6 +199,9 @@ func (c *diffConfig) StructuredMergeDiff() bool {
|
||||
func (c *diffConfig) Manager() string {
|
||||
return c.manager
|
||||
}
|
||||
func (c *diffConfig) IgnoreNormalizerOpts() normalizers.IgnoreNormalizerOpts {
|
||||
return c.ignoreNormalizerOpts
|
||||
}
|
||||
|
||||
// Validate will check the current state of this diffConfig and return
|
||||
// error if it finds any required configuration missing.
|
||||
@@ -243,7 +251,7 @@ func StateDiffs(lives, configs []*unstructured.Unstructured, diffConfig DiffConf
|
||||
return nil, fmt.Errorf("failed to perform pre-diff normalization: %w", err)
|
||||
}
|
||||
|
||||
diffNormalizer, err := newDiffNormalizer(diffConfig.Ignores(), diffConfig.Overrides())
|
||||
diffNormalizer, err := newDiffNormalizer(diffConfig.Ignores(), diffConfig.Overrides(), diffConfig.IgnoreNormalizerOpts())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create diff normalizer: %w", err)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
testutil "github.com/argoproj/argo-cd/v2/test"
|
||||
argo "github.com/argoproj/argo-cd/v2/util/argo/diff"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/testdata"
|
||||
appstatecache "github.com/argoproj/argo-cd/v2/util/cache/appstate"
|
||||
)
|
||||
@@ -40,7 +41,7 @@ func TestStateDiff(t *testing.T) {
|
||||
diffConfig := func(t *testing.T, params *diffConfigParams) argo.DiffConfig {
|
||||
t.Helper()
|
||||
diffConfig, err := argo.NewDiffConfigBuilder().
|
||||
WithDiffSettings(params.ignores, params.overrides, params.ignoreRoles).
|
||||
WithDiffSettings(params.ignores, params.overrides, params.ignoreRoles, normalizers.IgnoreNormalizerOpts{}).
|
||||
WithTracking(params.label, params.trackingMethod).
|
||||
WithNoCache().
|
||||
Build()
|
||||
@@ -185,7 +186,7 @@ func TestDiffConfigBuilder(t *testing.T) {
|
||||
|
||||
// when
|
||||
diffConfig, err := argo.NewDiffConfigBuilder().
|
||||
WithDiffSettings(f.ignores, f.overrides, f.ignoreRoles).
|
||||
WithDiffSettings(f.ignores, f.overrides, f.ignoreRoles, normalizers.IgnoreNormalizerOpts{}).
|
||||
WithTracking(f.label, f.trackingMethod).
|
||||
WithNoCache().
|
||||
Build()
|
||||
@@ -209,7 +210,7 @@ func TestDiffConfigBuilder(t *testing.T) {
|
||||
|
||||
// when
|
||||
diffConfig, err := argo.NewDiffConfigBuilder().
|
||||
WithDiffSettings(nil, nil, f.ignoreRoles).
|
||||
WithDiffSettings(nil, nil, f.ignoreRoles, normalizers.IgnoreNormalizerOpts{}).
|
||||
WithTracking(f.label, f.trackingMethod).
|
||||
WithNoCache().
|
||||
Build()
|
||||
@@ -231,7 +232,7 @@ func TestDiffConfigBuilder(t *testing.T) {
|
||||
|
||||
// when
|
||||
diffConfig, err := argo.NewDiffConfigBuilder().
|
||||
WithDiffSettings(f.ignores, f.overrides, f.ignoreRoles).
|
||||
WithDiffSettings(f.ignores, f.overrides, f.ignoreRoles, normalizers.IgnoreNormalizerOpts{}).
|
||||
WithTracking(f.label, f.trackingMethod).
|
||||
WithCache(&appstatecache.Cache{}, "").
|
||||
Build()
|
||||
@@ -246,7 +247,7 @@ func TestDiffConfigBuilder(t *testing.T) {
|
||||
|
||||
// when
|
||||
diffConfig, err := argo.NewDiffConfigBuilder().
|
||||
WithDiffSettings(f.ignores, f.overrides, f.ignoreRoles).
|
||||
WithDiffSettings(f.ignores, f.overrides, f.ignoreRoles, normalizers.IgnoreNormalizerOpts{}).
|
||||
WithTracking(f.label, f.trackingMethod).
|
||||
WithCache(nil, f.appName).
|
||||
Build()
|
||||
|
||||
@@ -15,7 +15,7 @@ func Normalize(lives, configs []*unstructured.Unstructured, diffConfig DiffConfi
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
diffNormalizer, err := newDiffNormalizer(diffConfig.Ignores(), diffConfig.Overrides())
|
||||
diffNormalizer, err := newDiffNormalizer(diffConfig.Ignores(), diffConfig.Overrides(), diffConfig.IgnoreNormalizerOpts())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -40,8 +40,8 @@ func Normalize(lives, configs []*unstructured.Unstructured, diffConfig DiffConfi
|
||||
}
|
||||
|
||||
// newDiffNormalizer creates normalizer that uses Argo CD and application settings to normalize the resource prior to diffing.
|
||||
func newDiffNormalizer(ignore []v1alpha1.ResourceIgnoreDifferences, overrides map[string]v1alpha1.ResourceOverride) (diff.Normalizer, error) {
|
||||
ignoreNormalizer, err := normalizers.NewIgnoreNormalizer(ignore, overrides)
|
||||
func newDiffNormalizer(ignore []v1alpha1.ResourceIgnoreDifferences, overrides map[string]v1alpha1.ResourceOverride, opts normalizers.IgnoreNormalizerOpts) (diff.Normalizer, error) {
|
||||
ignoreNormalizer, err := normalizers.NewIgnoreNormalizer(ignore, overrides, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1"
|
||||
"github.com/argoproj/argo-cd/v2/test"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/diff"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/normalizers"
|
||||
"github.com/argoproj/argo-cd/v2/util/argo/testdata"
|
||||
)
|
||||
|
||||
@@ -22,7 +23,7 @@ func TestNormalize(t *testing.T) {
|
||||
setup := func(t *testing.T, ignores []v1alpha1.ResourceIgnoreDifferences) *fixture {
|
||||
t.Helper()
|
||||
dc, err := diff.NewDiffConfigBuilder().
|
||||
WithDiffSettings(ignores, nil, true).
|
||||
WithDiffSettings(ignores, nil, true, normalizers.IgnoreNormalizerOpts{}).
|
||||
WithNoCache().
|
||||
Build()
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package normalizers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/argoproj/gitops-engine/pkg/diff"
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
@@ -16,6 +18,11 @@ import (
|
||||
"github.com/argoproj/argo-cd/v2/util/glob"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultJQExecutionTimeout is the maximum time allowed for a JQ patch to execute
|
||||
DefaultJQExecutionTimeout = 1 * time.Second
|
||||
)
|
||||
|
||||
type normalizerPatch interface {
|
||||
GetGroupKind() schema.GroupKind
|
||||
GetNamespace() string
|
||||
@@ -57,7 +64,8 @@ func (np *jsonPatchNormalizerPatch) Apply(data []byte) ([]byte, error) {
|
||||
|
||||
type jqNormalizerPatch struct {
|
||||
baseNormalizerPatch
|
||||
code *gojq.Code
|
||||
code *gojq.Code
|
||||
jqExecutionTimeout time.Duration
|
||||
}
|
||||
|
||||
func (np *jqNormalizerPatch) Apply(data []byte) ([]byte, error) {
|
||||
@@ -67,12 +75,18 @@ func (np *jqNormalizerPatch) Apply(data []byte) ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
iter := np.code.Run(dataJson)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), np.jqExecutionTimeout)
|
||||
defer cancel()
|
||||
|
||||
iter := np.code.RunWithContext(ctx, dataJson)
|
||||
first, ok := iter.Next()
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("JQ patch did not return any data")
|
||||
}
|
||||
if err, ok = first.(error); ok {
|
||||
if err == context.DeadlineExceeded {
|
||||
return nil, fmt.Errorf("JQ patch execution timed out (%v)", np.jqExecutionTimeout.String())
|
||||
}
|
||||
return nil, fmt.Errorf("JQ patch returned error: %w", err)
|
||||
}
|
||||
_, ok = iter.Next()
|
||||
@@ -91,8 +105,19 @@ type ignoreNormalizer struct {
|
||||
patches []normalizerPatch
|
||||
}
|
||||
|
||||
type IgnoreNormalizerOpts struct {
|
||||
JQExecutionTimeout time.Duration
|
||||
}
|
||||
|
||||
func (opts *IgnoreNormalizerOpts) getJQExecutionTimeout() time.Duration {
|
||||
if opts == nil || opts.JQExecutionTimeout == 0 {
|
||||
return DefaultJQExecutionTimeout
|
||||
}
|
||||
return opts.JQExecutionTimeout
|
||||
}
|
||||
|
||||
// NewIgnoreNormalizer creates diff normalizer which removes ignored fields according to given application spec and resource overrides
|
||||
func NewIgnoreNormalizer(ignore []v1alpha1.ResourceIgnoreDifferences, overrides map[string]v1alpha1.ResourceOverride) (diff.Normalizer, error) {
|
||||
func NewIgnoreNormalizer(ignore []v1alpha1.ResourceIgnoreDifferences, overrides map[string]v1alpha1.ResourceOverride, opts IgnoreNormalizerOpts) (diff.Normalizer, error) {
|
||||
for key, override := range overrides {
|
||||
group, kind, err := getGroupKindForOverrideKey(key)
|
||||
if err != nil {
|
||||
@@ -147,7 +172,8 @@ func NewIgnoreNormalizer(ignore []v1alpha1.ResourceIgnoreDifferences, overrides
|
||||
name: ignore[i].Name,
|
||||
namespace: ignore[i].Namespace,
|
||||
},
|
||||
code: jqDeletionCode,
|
||||
code: jqDeletionCode,
|
||||
jqExecutionTimeout: opts.getJQExecutionTimeout(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ func TestNormalizeObjectWithMatchedGroupKind(t *testing.T) {
|
||||
Group: "apps",
|
||||
Kind: "Deployment",
|
||||
JSONPointers: []string{"/not-matching-path", "/spec/template/spec/containers"},
|
||||
}}, make(map[string]v1alpha1.ResourceOverride))
|
||||
}}, make(map[string]v1alpha1.ResourceOverride), IgnoreNormalizerOpts{})
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -44,7 +44,7 @@ func TestNormalizeNoMatchedGroupKinds(t *testing.T) {
|
||||
Group: "",
|
||||
Kind: "Service",
|
||||
JSONPointers: []string{"/spec"},
|
||||
}}, make(map[string]v1alpha1.ResourceOverride))
|
||||
}}, make(map[string]v1alpha1.ResourceOverride), IgnoreNormalizerOpts{})
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -63,7 +63,7 @@ func TestNormalizeMatchedResourceOverrides(t *testing.T) {
|
||||
"apps/Deployment": {
|
||||
IgnoreDifferences: v1alpha1.OverrideIgnoreDiff{JSONPointers: []string{"/spec/template/spec/containers"}},
|
||||
},
|
||||
})
|
||||
}, IgnoreNormalizerOpts{})
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -118,7 +118,7 @@ func TestNormalizeMissingJsonPointer(t *testing.T) {
|
||||
"apiextensions.k8s.io/CustomResourceDefinition": {
|
||||
IgnoreDifferences: v1alpha1.OverrideIgnoreDiff{JSONPointers: []string{"/spec/additionalPrinterColumns/0/priority"}},
|
||||
},
|
||||
})
|
||||
}, IgnoreNormalizerOpts{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
deployment := test.NewDeployment()
|
||||
@@ -139,7 +139,7 @@ func TestNormalizeGlobMatch(t *testing.T) {
|
||||
"*/*": {
|
||||
IgnoreDifferences: v1alpha1.OverrideIgnoreDiff{JSONPointers: []string{"/spec/template/spec/containers"}},
|
||||
},
|
||||
})
|
||||
}, IgnoreNormalizerOpts{})
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -161,7 +161,7 @@ func TestNormalizeJQPathExpression(t *testing.T) {
|
||||
Group: "apps",
|
||||
Kind: "Deployment",
|
||||
JQPathExpressions: []string{".spec.template.spec.initContainers[] | select(.name == \"init-container-0\")"},
|
||||
}}, make(map[string]v1alpha1.ResourceOverride))
|
||||
}}, make(map[string]v1alpha1.ResourceOverride), IgnoreNormalizerOpts{})
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -197,7 +197,7 @@ func TestNormalizeIllegalJQPathExpression(t *testing.T) {
|
||||
Kind: "Deployment",
|
||||
JQPathExpressions: []string{".spec.template.spec.containers[] | select(.name == \"missing-quote)"},
|
||||
// JSONPointers: []string{"no-starting-slash"},
|
||||
}}, make(map[string]v1alpha1.ResourceOverride))
|
||||
}}, make(map[string]v1alpha1.ResourceOverride), IgnoreNormalizerOpts{})
|
||||
|
||||
assert.Error(t, err)
|
||||
}
|
||||
@@ -207,7 +207,7 @@ func TestNormalizeJQPathExpressionWithError(t *testing.T) {
|
||||
Group: "apps",
|
||||
Kind: "Deployment",
|
||||
JQPathExpressions: []string{".spec.fakeField.foo[]"},
|
||||
}}, make(map[string]v1alpha1.ResourceOverride))
|
||||
}}, make(map[string]v1alpha1.ResourceOverride), IgnoreNormalizerOpts{})
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
||||
@@ -230,7 +230,7 @@ func TestNormalizeExpectedErrorAreSilenced(t *testing.T) {
|
||||
JSONPointers: []string{"/invalid", "/invalid/json/path"},
|
||||
},
|
||||
},
|
||||
})
|
||||
}, IgnoreNormalizerOpts{})
|
||||
assert.Nil(t, err)
|
||||
|
||||
ignoreNormalizer := normalizer.(*ignoreNormalizer)
|
||||
@@ -254,12 +254,34 @@ func TestNormalizeExpectedErrorAreSilenced(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestJqPathExpressionFailWithTimeout(t *testing.T) {
|
||||
normalizer, err := NewIgnoreNormalizer([]v1alpha1.ResourceIgnoreDifferences{}, map[string]v1alpha1.ResourceOverride{
|
||||
"*/*": {
|
||||
IgnoreDifferences: v1alpha1.OverrideIgnoreDiff{
|
||||
JQPathExpressions: []string{"until(true==false; [.] + [1])"},
|
||||
},
|
||||
},
|
||||
}, IgnoreNormalizerOpts{})
|
||||
assert.Nil(t, err)
|
||||
|
||||
ignoreNormalizer := normalizer.(*ignoreNormalizer)
|
||||
assert.Len(t, ignoreNormalizer.patches, 1)
|
||||
jqPatch := ignoreNormalizer.patches[0]
|
||||
|
||||
deployment := test.NewDeployment()
|
||||
deploymentData, err := json.Marshal(deployment)
|
||||
assert.Nil(t, err)
|
||||
|
||||
_, err = jqPatch.Apply(deploymentData)
|
||||
assert.ErrorContains(t, err, "JQ patch execution timed out")
|
||||
}
|
||||
|
||||
func TestJQPathExpressionReturnsHelpfulError(t *testing.T) {
|
||||
normalizer, err := NewIgnoreNormalizer([]v1alpha1.ResourceIgnoreDifferences{{
|
||||
Kind: "ConfigMap",
|
||||
// This is a really wild expression, but it does trigger the desired error.
|
||||
JQPathExpressions: []string{`.nothing) | .data["config.yaml"] |= (fromjson | del(.auth) | tojson`},
|
||||
}}, nil)
|
||||
}}, nil, IgnoreNormalizerOpts{})
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
|
||||
41
util/cache/redis.go
vendored
41
util/cache/redis.go
vendored
@@ -7,6 +7,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
ioutil "github.com/argoproj/argo-cd/v2/util/io"
|
||||
@@ -155,41 +156,27 @@ type MetricsRegistry interface {
|
||||
ObserveRedisRequestDuration(duration time.Duration)
|
||||
}
|
||||
|
||||
var metricStartTimeKey = struct{}{}
|
||||
|
||||
type redisHook struct {
|
||||
registry MetricsRegistry
|
||||
}
|
||||
|
||||
func (rh *redisHook) BeforeProcess(ctx context.Context, cmd redis.Cmder) (context.Context, error) {
|
||||
return context.WithValue(ctx, metricStartTimeKey, time.Now()), nil
|
||||
func (rh *redisHook) DialHook(next redis.DialHook) redis.DialHook {
|
||||
return func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
conn, err := next(ctx, network, addr)
|
||||
return conn, err
|
||||
}
|
||||
}
|
||||
|
||||
func (rh *redisHook) AfterProcess(ctx context.Context, cmd redis.Cmder) error {
|
||||
cmdErr := cmd.Err()
|
||||
rh.registry.IncRedisRequest(cmdErr != nil && cmdErr != redis.Nil)
|
||||
func (rh *redisHook) ProcessHook(next redis.ProcessHook) redis.ProcessHook {
|
||||
return func(ctx context.Context, cmd redis.Cmder) error {
|
||||
startTime := time.Now()
|
||||
|
||||
startTime := ctx.Value(metricStartTimeKey).(time.Time)
|
||||
duration := time.Since(startTime)
|
||||
rh.registry.ObserveRedisRequestDuration(duration)
|
||||
err := next(ctx, cmd)
|
||||
rh.registry.IncRedisRequest(err != nil && err != redis.Nil)
|
||||
rh.registry.ObserveRedisRequestDuration(time.Since(startTime))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (redisHook) BeforeProcessPipeline(ctx context.Context, _ []redis.Cmder) (context.Context, error) {
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
func (redisHook) AfterProcessPipeline(_ context.Context, _ []redis.Cmder) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (redisHook) DialHook(next redis.DialHook) redis.DialHook {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (redisHook) ProcessHook(next redis.ProcessHook) redis.ProcessHook {
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func (redisHook) ProcessPipelineHook(next redis.ProcessPipelineHook) redis.ProcessPipelineHook {
|
||||
|
||||
40
util/cache/redis_hook.go
vendored
40
util/cache/redis_hook.go
vendored
@@ -2,14 +2,13 @@ package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const NoSuchHostErr = "no such host"
|
||||
|
||||
type argoRedisHooks struct {
|
||||
reconnectCallback func()
|
||||
}
|
||||
@@ -18,32 +17,23 @@ func NewArgoRedisHook(reconnectCallback func()) *argoRedisHooks {
|
||||
return &argoRedisHooks{reconnectCallback: reconnectCallback}
|
||||
}
|
||||
|
||||
func (hook *argoRedisHooks) BeforeProcess(ctx context.Context, cmd redis.Cmder) (context.Context, error) {
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
func (hook *argoRedisHooks) AfterProcess(ctx context.Context, cmd redis.Cmder) error {
|
||||
if cmd.Err() != nil && strings.Contains(cmd.Err().Error(), NoSuchHostErr) {
|
||||
log.Warnf("Reconnect to redis because error: \"%v\"", cmd.Err())
|
||||
hook.reconnectCallback()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hook *argoRedisHooks) BeforeProcessPipeline(ctx context.Context, cmds []redis.Cmder) (context.Context, error) {
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
func (hook *argoRedisHooks) AfterProcessPipeline(ctx context.Context, cmds []redis.Cmder) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (hook *argoRedisHooks) DialHook(next redis.DialHook) redis.DialHook {
|
||||
return nil
|
||||
return func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
conn, err := next(ctx, network, addr)
|
||||
return conn, err
|
||||
}
|
||||
}
|
||||
|
||||
func (hook *argoRedisHooks) ProcessHook(next redis.ProcessHook) redis.ProcessHook {
|
||||
return nil
|
||||
return func(ctx context.Context, cmd redis.Cmder) error {
|
||||
var dnsError *net.DNSError
|
||||
err := next(ctx, cmd)
|
||||
if err != nil && errors.As(err, &dnsError) {
|
||||
log.Warnf("Reconnect to redis because error: \"%v\"", err)
|
||||
hook.reconnectCallback()
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func (hook *argoRedisHooks) ProcessPipelineHook(next redis.ProcessPipelineHook) redis.ProcessPipelineHook {
|
||||
|
||||
33
util/cache/redis_hook_test.go
vendored
33
util/cache/redis_hook_test.go
vendored
@@ -1,38 +1,53 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/alicebob/miniredis/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
func Test_ReconnectCallbackHookCalled(t *testing.T) {
|
||||
mr, err := miniredis.Run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer mr.Close()
|
||||
|
||||
called := false
|
||||
hook := NewArgoRedisHook(func() {
|
||||
called = true
|
||||
})
|
||||
|
||||
cmd := &redis.StringCmd{}
|
||||
cmd.SetErr(errors.New("Failed to resync revoked tokens. retrying again in 1 minute: dial tcp: lookup argocd-redis on 10.179.0.10:53: no such host"))
|
||||
|
||||
_ = hook.AfterProcess(context.Background(), cmd)
|
||||
faultyDNSRedisClient := redis.NewClient(&redis.Options{Addr: "invalidredishost.invalid:12345"})
|
||||
faultyDNSRedisClient.AddHook(hook)
|
||||
|
||||
faultyDNSClient := NewRedisCache(faultyDNSRedisClient, 60*time.Second, RedisCompressionNone)
|
||||
err = faultyDNSClient.Set(&Item{Key: "baz", Object: "foo"})
|
||||
assert.Equal(t, called, true)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func Test_ReconnectCallbackHookNotCalled(t *testing.T) {
|
||||
mr, err := miniredis.Run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer mr.Close()
|
||||
|
||||
called := false
|
||||
hook := NewArgoRedisHook(func() {
|
||||
called = true
|
||||
})
|
||||
cmd := &redis.StringCmd{}
|
||||
cmd.SetErr(errors.New("Something wrong"))
|
||||
|
||||
_ = hook.AfterProcess(context.Background(), cmd)
|
||||
redisClient := redis.NewClient(&redis.Options{Addr: mr.Addr()})
|
||||
redisClient.AddHook(hook)
|
||||
client := NewRedisCache(redisClient, 60*time.Second, RedisCompressionNone)
|
||||
|
||||
err = client.Set(&Item{Key: "foo", Object: "bar"})
|
||||
assert.Equal(t, called, false)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
92
util/cache/redis_test.go
vendored
92
util/cache/redis_test.go
vendored
@@ -2,14 +2,59 @@ package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
promcm "github.com/prometheus/client_model/go"
|
||||
|
||||
"github.com/alicebob/miniredis/v2"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
redisRequestCounter = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Name: "argocd_redis_request_total",
|
||||
},
|
||||
[]string{"initiator", "failed"},
|
||||
)
|
||||
redisRequestHistogram = prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Name: "argocd_redis_request_duration",
|
||||
Buckets: []float64{0.1, 0.25, .5, 1, 2},
|
||||
},
|
||||
[]string{"initiator"},
|
||||
)
|
||||
)
|
||||
|
||||
type MockMetricsServer struct {
|
||||
registry *prometheus.Registry
|
||||
redisRequestCounter *prometheus.CounterVec
|
||||
redisRequestHistogram *prometheus.HistogramVec
|
||||
}
|
||||
|
||||
func NewMockMetricsServer() *MockMetricsServer {
|
||||
registry := prometheus.NewRegistry()
|
||||
registry.MustRegister(redisRequestCounter)
|
||||
registry.MustRegister(redisRequestHistogram)
|
||||
return &MockMetricsServer{
|
||||
registry: registry,
|
||||
redisRequestCounter: redisRequestCounter,
|
||||
redisRequestHistogram: redisRequestHistogram,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MockMetricsServer) IncRedisRequest(failed bool) {
|
||||
m.redisRequestCounter.WithLabelValues("mock", strconv.FormatBool(failed)).Inc()
|
||||
}
|
||||
|
||||
func (m *MockMetricsServer) ObserveRedisRequestDuration(duration time.Duration) {
|
||||
m.redisRequestHistogram.WithLabelValues("mock").Observe(duration.Seconds())
|
||||
}
|
||||
|
||||
func TestRedisSetCache(t *testing.T) {
|
||||
mr, err := miniredis.Run()
|
||||
if err != nil {
|
||||
@@ -70,3 +115,50 @@ func TestRedisSetCacheCompressed(t *testing.T) {
|
||||
|
||||
assert.Equal(t, testValue, result)
|
||||
}
|
||||
|
||||
func TestRedisMetrics(t *testing.T) {
|
||||
mr, err := miniredis.Run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer mr.Close()
|
||||
|
||||
metric := &promcm.Metric{}
|
||||
ms := NewMockMetricsServer()
|
||||
redisClient := redis.NewClient(&redis.Options{Addr: mr.Addr()})
|
||||
faultyRedisClient := redis.NewClient(&redis.Options{Addr: "invalidredishost.invalid:12345"})
|
||||
CollectMetrics(redisClient, ms)
|
||||
CollectMetrics(faultyRedisClient, ms)
|
||||
|
||||
client := NewRedisCache(redisClient, 60*time.Second, RedisCompressionNone)
|
||||
faultyClient := NewRedisCache(faultyRedisClient, 60*time.Second, RedisCompressionNone)
|
||||
var res string
|
||||
|
||||
//client successful request
|
||||
err = client.Set(&Item{Key: "foo", Object: "bar"})
|
||||
assert.NoError(t, err)
|
||||
err = client.Get("foo", &res)
|
||||
assert.NoError(t, err)
|
||||
|
||||
c, err := ms.redisRequestCounter.GetMetricWithLabelValues("mock", "false")
|
||||
assert.NoError(t, err)
|
||||
err = c.Write(metric)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, metric.Counter.GetValue(), float64(2))
|
||||
|
||||
//faulty client failed request
|
||||
err = faultyClient.Get("foo", &res)
|
||||
assert.Error(t, err)
|
||||
c, err = ms.redisRequestCounter.GetMetricWithLabelValues("mock", "true")
|
||||
assert.NoError(t, err)
|
||||
err = c.Write(metric)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, metric.Counter.GetValue(), float64(1))
|
||||
|
||||
//both clients histogram count
|
||||
o, err := ms.redisRequestHistogram.GetMetricWithLabelValues("mock")
|
||||
assert.NoError(t, err)
|
||||
err = o.(prometheus.Metric).Write(metric)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, int(metric.Histogram.GetSampleCount()), 3)
|
||||
}
|
||||
|
||||
13
util/env/env.go
vendored
13
util/env/env.go
vendored
@@ -124,8 +124,17 @@ func ParseDurationFromEnv(env string, defaultValue, min, max time.Duration) time
|
||||
return dur
|
||||
}
|
||||
|
||||
func StringFromEnv(env string, defaultValue string) string {
|
||||
if str := os.Getenv(env); str != "" {
|
||||
type StringFromEnvOpts struct {
|
||||
// AllowEmpty allows the value to be empty as long as the environment variable is set.
|
||||
AllowEmpty bool
|
||||
}
|
||||
|
||||
func StringFromEnv(env string, defaultValue string, opts ...StringFromEnvOpts) string {
|
||||
opt := StringFromEnvOpts{}
|
||||
for _, o := range opts {
|
||||
opt.AllowEmpty = opt.AllowEmpty || o.AllowEmpty
|
||||
}
|
||||
if str, ok := os.LookupEnv(env); opt.AllowEmpty && ok || str != "" {
|
||||
return str
|
||||
}
|
||||
return defaultValue
|
||||
|
||||
19
util/env/env_test.go
vendored
19
util/env/env_test.go
vendored
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/utils/pointer"
|
||||
)
|
||||
|
||||
func TestParseNumFromEnv(t *testing.T) {
|
||||
@@ -167,19 +168,25 @@ func TestStringFromEnv(t *testing.T) {
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
env string
|
||||
env *string
|
||||
expected string
|
||||
def string
|
||||
opts []StringFromEnvOpts
|
||||
}{
|
||||
{"Some string", "true", "true", def},
|
||||
{"Empty string with default", "", def, def},
|
||||
{"Empty string without default", "", "", ""},
|
||||
{"Some string", pointer.String("true"), "true", def, nil},
|
||||
{"Empty string with default", pointer.String(""), def, def, nil},
|
||||
{"Empty string without default", pointer.String(""), "", "", nil},
|
||||
{"No env variable with default allow empty", nil, "default", "default", []StringFromEnvOpts{{AllowEmpty: true}}},
|
||||
{"Some variable with default allow empty", pointer.String("true"), "true", "default", []StringFromEnvOpts{{AllowEmpty: true}}},
|
||||
{"Empty variable with default allow empty", pointer.String(""), "", "default", []StringFromEnvOpts{{AllowEmpty: true}}},
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Setenv(envKey, tt.env)
|
||||
b := StringFromEnv(envKey, tt.def)
|
||||
if tt.env != nil {
|
||||
t.Setenv(envKey, *tt.env)
|
||||
}
|
||||
b := StringFromEnv(envKey, tt.def, tt.opts...)
|
||||
assert.Equal(t, tt.expected, b)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
executil "github.com/argoproj/argo-cd/v2/util/exec"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -19,6 +18,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
executil "github.com/argoproj/argo-cd/v2/util/exec"
|
||||
|
||||
"github.com/argoproj/pkg/sync"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/yaml.v2"
|
||||
@@ -34,6 +35,8 @@ import (
|
||||
var (
|
||||
globalLock = sync.NewKeyLock()
|
||||
indexLock = sync.NewKeyLock()
|
||||
|
||||
OCINotEnabledErr = errors.New("could not perform the action when oci is not enabled")
|
||||
)
|
||||
|
||||
type Creds struct {
|
||||
@@ -53,7 +56,7 @@ type indexCache interface {
|
||||
type Client interface {
|
||||
CleanChartCache(chart string, version string) error
|
||||
ExtractChart(chart string, version string, passCredentials bool, manifestMaxExtractedSize int64, disableManifestMaxExtractedSize bool) (string, argoio.Closer, error)
|
||||
GetIndex(noCache bool) (*Index, error)
|
||||
GetIndex(noCache bool, maxIndexSize int64) (*Index, error)
|
||||
GetTags(chart string, noCache bool) (*TagsList, error)
|
||||
TestHelmOCI() (bool, error)
|
||||
}
|
||||
@@ -227,7 +230,7 @@ func (c *nativeHelmChart) ExtractChart(chart string, version string, passCredent
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (c *nativeHelmChart) GetIndex(noCache bool) (*Index, error) {
|
||||
func (c *nativeHelmChart) GetIndex(noCache bool, maxIndexSize int64) (*Index, error) {
|
||||
indexLock.Lock(c.repoURL)
|
||||
defer indexLock.Unlock(c.repoURL)
|
||||
|
||||
@@ -241,7 +244,7 @@ func (c *nativeHelmChart) GetIndex(noCache bool) (*Index, error) {
|
||||
if len(data) == 0 {
|
||||
start := time.Now()
|
||||
var err error
|
||||
data, err = c.loadRepoIndex()
|
||||
data, err = c.loadRepoIndex(maxIndexSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -294,7 +297,7 @@ func (c *nativeHelmChart) TestHelmOCI() (bool, error) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (c *nativeHelmChart) loadRepoIndex() ([]byte, error) {
|
||||
func (c *nativeHelmChart) loadRepoIndex(maxIndexSize int64) ([]byte, error) {
|
||||
indexURL, err := getIndexURL(c.repoURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -329,7 +332,7 @@ func (c *nativeHelmChart) loadRepoIndex() ([]byte, error) {
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, errors.New("failed to get index: " + resp.Status)
|
||||
}
|
||||
return io.ReadAll(resp.Body)
|
||||
return io.ReadAll(io.LimitReader(resp.Body, maxIndexSize))
|
||||
}
|
||||
|
||||
func newTLSConfig(creds Creds) (*tls.Config, error) {
|
||||
@@ -401,6 +404,10 @@ func getIndexURL(rawURL string) (string, error) {
|
||||
}
|
||||
|
||||
func (c *nativeHelmChart) GetTags(chart string, noCache bool) (*TagsList, error) {
|
||||
if !c.enableOci {
|
||||
return nil, OCINotEnabledErr
|
||||
}
|
||||
|
||||
tagsURL := strings.Replace(fmt.Sprintf("%s/%s", c.repoURL, chart), "https://", "", 1)
|
||||
indexLock.Lock(tagsURL)
|
||||
defer indexLock.Unlock(tagsURL)
|
||||
@@ -428,10 +435,12 @@ func (c *nativeHelmChart) GetTags(chart string, noCache bool) (*TagsList, error)
|
||||
TLSClientConfig: tlsConf,
|
||||
DisableKeepAlives: true,
|
||||
}}
|
||||
|
||||
repoHost, _, _ := strings.Cut(tagsURL, "/")
|
||||
repo.Client = &auth.Client{
|
||||
Client: client,
|
||||
Cache: nil,
|
||||
Credential: auth.StaticCredential(c.repoURL, auth.Credential{
|
||||
Credential: auth.StaticCredential(repoHost, auth.Credential{
|
||||
Username: c.creds.Username,
|
||||
Password: c.creds.Password,
|
||||
}),
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -36,12 +37,12 @@ func (f *fakeIndexCache) GetHelmIndex(_ string, indexData *[]byte) error {
|
||||
func TestIndex(t *testing.T) {
|
||||
t.Run("Invalid", func(t *testing.T) {
|
||||
client := NewClient("", Creds{}, false, "")
|
||||
_, err := client.GetIndex(false)
|
||||
_, err := client.GetIndex(false, 10000)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
t.Run("Stable", func(t *testing.T) {
|
||||
client := NewClient("https://argoproj.github.io/argo-helm", Creds{}, false, "")
|
||||
index, err := client.GetIndex(false)
|
||||
index, err := client.GetIndex(false, 10000)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, index)
|
||||
})
|
||||
@@ -50,7 +51,7 @@ func TestIndex(t *testing.T) {
|
||||
Username: "my-password",
|
||||
Password: "my-username",
|
||||
}, false, "")
|
||||
index, err := client.GetIndex(false)
|
||||
index, err := client.GetIndex(false, 10000)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, index)
|
||||
})
|
||||
@@ -62,12 +63,18 @@ func TestIndex(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
client := NewClient("https://argoproj.github.io/argo-helm", Creds{}, false, "", WithIndexCache(&fakeIndexCache{data: data.Bytes()}))
|
||||
index, err := client.GetIndex(false)
|
||||
index, err := client.GetIndex(false, 10000)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, fakeIndex, *index)
|
||||
})
|
||||
|
||||
t.Run("Limited", func(t *testing.T) {
|
||||
client := NewClient("https://argoproj.github.io/argo-helm", Creds{}, false, "")
|
||||
_, err := client.GetIndex(false, 100)
|
||||
|
||||
assert.ErrorContains(t, err, "unexpected end of stream")
|
||||
})
|
||||
}
|
||||
|
||||
func Test_nativeHelmChart_ExtractChart(t *testing.T) {
|
||||
@@ -159,41 +166,129 @@ func TestGetIndexURL(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetTagsFromUrl(t *testing.T) {
|
||||
t.Run("should return tags correctly while following the link header", func(t *testing.T) {
|
||||
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Logf("called %s", r.URL.Path)
|
||||
responseTags := TagsList{}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if !strings.Contains(r.URL.String(), "token") {
|
||||
w.Header().Set("Link", fmt.Sprintf("<https://%s%s?token=next-token>; rel=next", r.Host, r.URL.Path))
|
||||
responseTags.Tags = []string{"first"}
|
||||
} else {
|
||||
responseTags.Tags = []string{
|
||||
"second",
|
||||
"2.8.0",
|
||||
"2.8.0-prerelease",
|
||||
"2.8.0_build",
|
||||
"2.8.0-prerelease_build",
|
||||
"2.8.0-prerelease.1_build.1234",
|
||||
}
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
err := json.NewEncoder(w).Encode(responseTags)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}))
|
||||
|
||||
client := NewClient(server.URL, Creds{InsecureSkipVerify: true}, true, "")
|
||||
|
||||
tags, err := client.GetTags("mychart", true)
|
||||
assert.NoError(t, err)
|
||||
assert.ElementsMatch(t, tags.Tags, []string{
|
||||
"first",
|
||||
"second",
|
||||
"2.8.0",
|
||||
"2.8.0-prerelease",
|
||||
"2.8.0+build",
|
||||
"2.8.0-prerelease+build",
|
||||
"2.8.0-prerelease.1+build.1234",
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("should return an error not when oci is not enabled", func(t *testing.T) {
|
||||
client := NewClient("example.com", Creds{}, false, "")
|
||||
|
||||
_, err := client.GetTags("my-chart", true)
|
||||
assert.ErrorIs(t, OCINotEnabledErr, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetTagsFromURLPrivateRepoAuthentication(t *testing.T) {
|
||||
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Logf("called %s", r.URL.Path)
|
||||
responseTags := TagsList{}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if !strings.Contains(r.URL.String(), "token") {
|
||||
w.Header().Set("Link", fmt.Sprintf("<https://%s%s?token=next-token>; rel=next", r.Host, r.URL.Path))
|
||||
responseTags.Tags = []string{"first"}
|
||||
} else {
|
||||
responseTags.Tags = []string{
|
||||
"second",
|
||||
|
||||
authorization := r.Header.Get("Authorization")
|
||||
if authorization == "" {
|
||||
w.Header().Set("WWW-Authenticate", `Basic realm="helm repo to get tags"`)
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
t.Logf("authorization received %s", authorization)
|
||||
|
||||
responseTags := TagsList{
|
||||
Tags: []string{
|
||||
"2.8.0",
|
||||
"2.8.0-prerelease",
|
||||
"2.8.0_build",
|
||||
"2.8.0-prerelease_build",
|
||||
"2.8.0-prerelease.1_build.1234",
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
err := json.NewEncoder(w).Encode(responseTags)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}))
|
||||
t.Cleanup(server.Close)
|
||||
|
||||
client := NewClient(server.URL, Creds{InsecureSkipVerify: true}, true, "")
|
||||
|
||||
tags, err := client.GetTags("mychart", true)
|
||||
serverURL, err := url.Parse(server.URL)
|
||||
assert.NoError(t, err)
|
||||
assert.ElementsMatch(t, tags.Tags, []string{
|
||||
"first",
|
||||
"second",
|
||||
"2.8.0",
|
||||
"2.8.0-prerelease",
|
||||
"2.8.0+build",
|
||||
"2.8.0-prerelease+build",
|
||||
"2.8.0-prerelease.1+build.1234",
|
||||
})
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
repoURL string
|
||||
}{
|
||||
{
|
||||
name: "should login correctly when the repo path is in the server root with http scheme",
|
||||
repoURL: server.URL,
|
||||
},
|
||||
{
|
||||
name: "should login correctly when the repo path is not in the server root with http scheme",
|
||||
repoURL: fmt.Sprintf("%s/my-repo", server.URL),
|
||||
},
|
||||
{
|
||||
name: "should login correctly when the repo path is in the server root without http scheme",
|
||||
repoURL: serverURL.Host,
|
||||
},
|
||||
{
|
||||
name: "should login correctly when the repo path is not in the server root without http scheme",
|
||||
repoURL: fmt.Sprintf("%s/my-repo", serverURL.Host),
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
client := NewClient(testCase.repoURL, Creds{
|
||||
InsecureSkipVerify: true,
|
||||
Username: "my-username",
|
||||
Password: "my-password",
|
||||
}, true, "")
|
||||
|
||||
tags, err := client.GetTags("mychart", true)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.ElementsMatch(t, tags.Tags, []string{
|
||||
"2.8.0",
|
||||
"2.8.0-prerelease",
|
||||
"2.8.0+build",
|
||||
"2.8.0-prerelease+build",
|
||||
"2.8.0-prerelease.1+build.1234",
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,28 @@ func (c *Cmd) RegistryLogin(repo string, creds Creds) (string, error) {
|
||||
args = append(args, "--password", creds.Password)
|
||||
}
|
||||
|
||||
if creds.CAPath != "" {
|
||||
args = append(args, "--ca-file", creds.CAPath)
|
||||
}
|
||||
|
||||
if len(creds.CertData) > 0 {
|
||||
filePath, closer, err := writeToTmp(creds.CertData)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer argoio.Close(closer)
|
||||
args = append(args, "--cert-file", filePath)
|
||||
}
|
||||
|
||||
if len(creds.KeyData) > 0 {
|
||||
filePath, closer, err := writeToTmp(creds.KeyData)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer argoio.Close(closer)
|
||||
args = append(args, "--key-file", filePath)
|
||||
}
|
||||
|
||||
if creds.InsecureSkipVerify {
|
||||
args = append(args, "--insecure")
|
||||
}
|
||||
@@ -238,6 +260,25 @@ func (c *Cmd) PullOCI(repo string, chart string, version string, destination str
|
||||
if creds.CAPath != "" {
|
||||
args = append(args, "--ca-file", creds.CAPath)
|
||||
}
|
||||
|
||||
if len(creds.CertData) > 0 {
|
||||
filePath, closer, err := writeToTmp(creds.CertData)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer argoio.Close(closer)
|
||||
args = append(args, "--cert-file", filePath)
|
||||
}
|
||||
|
||||
if len(creds.KeyData) > 0 {
|
||||
filePath, closer, err := writeToTmp(creds.KeyData)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer argoio.Close(closer)
|
||||
args = append(args, "--key-file", filePath)
|
||||
}
|
||||
|
||||
if creds.InsecureSkipVerify && c.insecureSkipVerifySupported {
|
||||
args = append(args, "--insecure-skip-tls-verify")
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ func (_m *Client) ExtractChart(chart string, version string, passCredentials boo
|
||||
}
|
||||
|
||||
// GetIndex provides a mock function with given fields: noCache
|
||||
func (_m *Client) GetIndex(noCache bool) (*helm.Index, error) {
|
||||
func (_m *Client) GetIndex(noCache bool, maxIndexSize int64) (*helm.Index, error) {
|
||||
ret := _m.Called(noCache)
|
||||
|
||||
var r0 *helm.Index
|
||||
|
||||
@@ -35,8 +35,9 @@ type Kustomize interface {
|
||||
}
|
||||
|
||||
// NewKustomizeApp create a new wrapper to run commands on the `kustomize` command-line tool.
|
||||
func NewKustomizeApp(path string, creds git.Creds, fromRepo string, binaryPath string) Kustomize {
|
||||
func NewKustomizeApp(repoRoot string, path string, creds git.Creds, fromRepo string, binaryPath string) Kustomize {
|
||||
return &kustomize{
|
||||
repoRoot: repoRoot,
|
||||
path: path,
|
||||
creds: creds,
|
||||
repo: fromRepo,
|
||||
@@ -45,6 +46,8 @@ func NewKustomizeApp(path string, creds git.Creds, fromRepo string, binaryPath s
|
||||
}
|
||||
|
||||
type kustomize struct {
|
||||
// path to the Git repository root
|
||||
repoRoot string
|
||||
// path inside the checked out tree
|
||||
path string
|
||||
// creds structure
|
||||
@@ -281,6 +284,7 @@ func (k *kustomize) Build(opts *v1alpha1.ApplicationSourceKustomize, kustomizeOp
|
||||
}
|
||||
|
||||
cmd.Env = append(cmd.Env, environ...)
|
||||
cmd.Dir = k.repoRoot
|
||||
out, err := executil.Run(cmd)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
||||
@@ -39,7 +39,7 @@ func TestKustomizeBuild(t *testing.T) {
|
||||
namePrefix := "namePrefix-"
|
||||
nameSuffix := "-nameSuffix"
|
||||
namespace := "custom-namespace"
|
||||
kustomize := NewKustomizeApp(appPath, git.NopCreds{}, "", "")
|
||||
kustomize := NewKustomizeApp(appPath, appPath, git.NopCreds{}, "", "")
|
||||
env := &v1alpha1.Env{
|
||||
&v1alpha1.EnvEntry{Name: "ARGOCD_APP_NAME", Value: "argo-cd-tests"},
|
||||
}
|
||||
@@ -122,7 +122,7 @@ func TestKustomizeBuild(t *testing.T) {
|
||||
func TestFailKustomizeBuild(t *testing.T) {
|
||||
appPath, err := testDataDir(t, kustomization1)
|
||||
assert.Nil(t, err)
|
||||
kustomize := NewKustomizeApp(appPath, git.NopCreds{}, "", "")
|
||||
kustomize := NewKustomizeApp(appPath, appPath, git.NopCreds{}, "", "")
|
||||
kustomizeSource := v1alpha1.ApplicationSourceKustomize{
|
||||
Replicas: []v1alpha1.KustomizeReplica{
|
||||
{
|
||||
@@ -221,7 +221,7 @@ func TestKustomizeBuildForceCommonLabels(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
appPath, err := testDataDir(t, tc.TestData)
|
||||
assert.Nil(t, err)
|
||||
kustomize := NewKustomizeApp(appPath, git.NopCreds{}, "", "")
|
||||
kustomize := NewKustomizeApp(appPath, appPath, git.NopCreds{}, "", "")
|
||||
objs, _, err := kustomize.Build(&tc.KustomizeSource, nil, tc.Env)
|
||||
switch tc.ExpectErr {
|
||||
case true:
|
||||
@@ -313,7 +313,7 @@ func TestKustomizeBuildForceCommonAnnotations(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
appPath, err := testDataDir(t, tc.TestData)
|
||||
assert.Nil(t, err)
|
||||
kustomize := NewKustomizeApp(appPath, git.NopCreds{}, "", "")
|
||||
kustomize := NewKustomizeApp(appPath, appPath, git.NopCreds{}, "", "")
|
||||
objs, _, err := kustomize.Build(&tc.KustomizeSource, nil, tc.Env)
|
||||
switch tc.ExpectErr {
|
||||
case true:
|
||||
@@ -333,7 +333,7 @@ func TestKustomizeCustomVersion(t *testing.T) {
|
||||
kustomizePath, err := testDataDir(t, kustomization4)
|
||||
assert.Nil(t, err)
|
||||
envOutputFile := kustomizePath + "/env_output"
|
||||
kustomize := NewKustomizeApp(appPath, git.NopCreds{}, "", kustomizePath+"/kustomize.special")
|
||||
kustomize := NewKustomizeApp(appPath, appPath, git.NopCreds{}, "", kustomizePath+"/kustomize.special")
|
||||
kustomizeSource := v1alpha1.ApplicationSourceKustomize{
|
||||
Version: "special",
|
||||
}
|
||||
@@ -355,7 +355,7 @@ func TestKustomizeCustomVersion(t *testing.T) {
|
||||
func TestKustomizeBuildPatches(t *testing.T) {
|
||||
appPath, err := testDataDir(t, kustomization5)
|
||||
assert.Nil(t, err)
|
||||
kustomize := NewKustomizeApp(appPath, git.NopCreds{}, "", "")
|
||||
kustomize := NewKustomizeApp(appPath, appPath, git.NopCreds{}, "", "")
|
||||
|
||||
kustomizeSource := v1alpha1.ApplicationSourceKustomize{
|
||||
Patches: []v1alpha1.KustomizePatch{
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
@@ -41,6 +42,7 @@ type SessionManager struct {
|
||||
storage UserStateStorage
|
||||
sleep func(d time.Duration)
|
||||
verificationDelayNoiseEnabled bool
|
||||
failedLock sync.RWMutex
|
||||
}
|
||||
|
||||
// LoginAttempts is a timestamped counter for failed login attempts
|
||||
@@ -69,7 +71,7 @@ const (
|
||||
// Maximum length of username, too keep the cache's memory signature low
|
||||
maxUsernameLength = 32
|
||||
// The default maximum session cache size
|
||||
defaultMaxCacheSize = 1000
|
||||
defaultMaxCacheSize = 10000
|
||||
// The default number of maximum login failures before delay kicks in
|
||||
defaultMaxLoginFailures = 5
|
||||
// The default time in seconds for the failure window
|
||||
@@ -284,7 +286,7 @@ func (mgr *SessionManager) Parse(tokenString string) (jwt.Claims, string, error)
|
||||
return token.Claims, newToken, nil
|
||||
}
|
||||
|
||||
// GetLoginFailures retrieves the login failure information from the cache
|
||||
// GetLoginFailures retrieves the login failure information from the cache. Any modifications to the LoginAttemps map must be done in a thread-safe manner.
|
||||
func (mgr *SessionManager) GetLoginFailures() map[string]LoginAttempts {
|
||||
// Get failures from the cache
|
||||
var failures map[string]LoginAttempts
|
||||
@@ -299,25 +301,43 @@ func (mgr *SessionManager) GetLoginFailures() map[string]LoginAttempts {
|
||||
return failures
|
||||
}
|
||||
|
||||
func expireOldFailedAttempts(maxAge time.Duration, failures *map[string]LoginAttempts) int {
|
||||
func expireOldFailedAttempts(maxAge time.Duration, failures map[string]LoginAttempts) int {
|
||||
expiredCount := 0
|
||||
for key, attempt := range *failures {
|
||||
for key, attempt := range failures {
|
||||
if time.Since(attempt.LastFailed) > maxAge*time.Second {
|
||||
expiredCount += 1
|
||||
delete(*failures, key)
|
||||
delete(failures, key)
|
||||
}
|
||||
}
|
||||
return expiredCount
|
||||
}
|
||||
|
||||
// Protect admin user from login attempt reset caused by attempts to overflow cache in a brute force attack. Instead remove random non-admin to make room in cache.
|
||||
func pickRandomNonAdminLoginFailure(failures map[string]LoginAttempts, username string) *string {
|
||||
idx := rand.Intn(len(failures) - 1)
|
||||
i := 0
|
||||
for key := range failures {
|
||||
if i == idx {
|
||||
if key == common.ArgoCDAdminUsername || key == username {
|
||||
return pickRandomNonAdminLoginFailure(failures, username)
|
||||
}
|
||||
return &key
|
||||
}
|
||||
i++
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Updates the failure count for a given username. If failed is true, increases the counter. Otherwise, sets counter back to 0.
|
||||
func (mgr *SessionManager) updateFailureCount(username string, failed bool) {
|
||||
mgr.failedLock.Lock()
|
||||
defer mgr.failedLock.Unlock()
|
||||
|
||||
failures := mgr.GetLoginFailures()
|
||||
|
||||
// Expire old entries in the cache if we have a failure window defined.
|
||||
if window := getLoginFailureWindow(); window > 0 {
|
||||
count := expireOldFailedAttempts(window, &failures)
|
||||
count := expireOldFailedAttempts(window, failures)
|
||||
if count > 0 {
|
||||
log.Infof("Expired %d entries from session cache due to max age reached", count)
|
||||
}
|
||||
@@ -327,23 +347,13 @@ func (mgr *SessionManager) updateFailureCount(username string, failed bool) {
|
||||
// prevent overbloating the cache with fake entries, as this could lead to
|
||||
// memory exhaustion and ultimately in a DoS. We remove a single entry to
|
||||
// replace it with the new one.
|
||||
//
|
||||
// Chances are that we remove the one that is under active attack, but this
|
||||
// chance is low (1:cache_size)
|
||||
if failed && len(failures) >= getMaximumCacheSize() {
|
||||
log.Warnf("Session cache size exceeds %d entries, removing random entry", getMaximumCacheSize())
|
||||
idx := rand.Intn(len(failures) - 1)
|
||||
var rmUser string
|
||||
i := 0
|
||||
for key := range failures {
|
||||
if i == idx {
|
||||
rmUser = key
|
||||
delete(failures, key)
|
||||
break
|
||||
}
|
||||
i++
|
||||
rmUser := pickRandomNonAdminLoginFailure(failures, username)
|
||||
if rmUser != nil {
|
||||
delete(failures, *rmUser)
|
||||
log.Infof("Deleted entry for user %s from cache", *rmUser)
|
||||
}
|
||||
log.Infof("Deleted entry for user %s from cache", rmUser)
|
||||
}
|
||||
|
||||
attempt, ok := failures[username]
|
||||
@@ -374,6 +384,8 @@ func (mgr *SessionManager) updateFailureCount(username string, failed bool) {
|
||||
|
||||
// Get the current login failure attempts for given username
|
||||
func (mgr *SessionManager) getFailureCount(username string) LoginAttempts {
|
||||
mgr.failedLock.RLock()
|
||||
defer mgr.failedLock.RUnlock()
|
||||
failures := mgr.GetLoginFailures()
|
||||
attempt, ok := failures[username]
|
||||
if !ok {
|
||||
|
||||
@@ -1173,3 +1173,42 @@ requestedScopes: ["oidc"]`, oidcTestServer.URL),
|
||||
assert.ErrorIs(t, err, common.TokenVerificationErr)
|
||||
})
|
||||
}
|
||||
|
||||
func Test_PickFailureAttemptWhenOverflowed(t *testing.T) {
|
||||
t.Run("Not pick admin user from the queue", func(t *testing.T) {
|
||||
failures := map[string]LoginAttempts{
|
||||
"admin": {
|
||||
FailCount: 1,
|
||||
},
|
||||
"test2": {
|
||||
FailCount: 1,
|
||||
},
|
||||
}
|
||||
|
||||
// inside pickRandomNonAdminLoginFailure, it uses random, so we need to test it multiple times
|
||||
for i := 0; i < 1000; i++ {
|
||||
user := pickRandomNonAdminLoginFailure(failures, "test")
|
||||
assert.Equal(t, "test2", *user)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Not pick admin user and current user from the queue", func(t *testing.T) {
|
||||
failures := map[string]LoginAttempts{
|
||||
"test": {
|
||||
FailCount: 1,
|
||||
},
|
||||
"admin": {
|
||||
FailCount: 1,
|
||||
},
|
||||
"test2": {
|
||||
FailCount: 1,
|
||||
},
|
||||
}
|
||||
|
||||
// inside pickRandomNonAdminLoginFailure, it uses random, so we need to test it multiple times
|
||||
for i := 0; i < 1000; i++ {
|
||||
user := pickRandomNonAdminLoginFailure(failures, "test")
|
||||
assert.Equal(t, "test2", *user)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@ import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/go-jose/go-jose/v3"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
)
|
||||
|
||||
// Cert is a certificate for tests. It was generated like this:
|
||||
|
||||
Reference in New Issue
Block a user