mirror of
https://github.com/hanzoai/deploy.git
synced 2026-10-07 08:44:50 +00:00
* feat(api): hard-fork CRD API group argoproj.io -> deploy.hanzo.ai
Hanzo CD hard-fork, phase 1.1. Single source of truth is
pkg/apis/application/register.go's Group constant (Application/AppProject/
ApplicationSet all derive their FullName from it); every other Go call-site
that hardcoded the literal group string is updated to match: the 3 generated
informer GVRs, the app-of-apps nested-Application detector in types.go, the
resource-tree Application-kind cache populator, the 3 NotFound error
constructors in server/application, and the applicationset-controller's
leader-election Lease name/ID (kept in lockstep across Go source + RBAC
resourceNames, or leader election would get RBAC-denied on startup).
CRD YAMLs (application/appproject/applicationset) and every RBAC apiGroups
rule that actually governs our 3 CRDs are repointed to deploy.hanzo.ai, across
both manifests/base/* (namespaced Roles) and manifests/cluster-rbac/* (cluster
scope). Two adjacent things that share the "argoproj.io" string but are NOT
ours are deliberately left untouched: gitops-engine's health-check dispatch
for Argo Workflows' `Workflow` kind, and argocd-server-clusterrole's second
apiGroups block granting read on Argo Workflows' `workflows` resource — both
belong to a separate, unrelated Argoproj-family project we are not forking.
Also untouched, per explicit scope: annotation/label keys (argocd.argoproj.io/*)
and finalizers (*.argoproj.io) that track already-applied live resources, and
argocd-cm.yaml's resource.customizations.ignoreResourceUpdates keys (a narrow
app-of-apps diffing nicety, not build- or RBAC-relevant).
CGO_ENABLED=0 go build ./... verified green.
* ci: wire canonical hanzo.yml + cicd.yml (Hanzo CD image build)
Phase 1.3. Follows the same one-way pattern every hanzo/lux/zoo repo uses:
hanzo.yml declares the image (root Dockerfile -> ghcr.io/hanzoai/deploy) and a
fast go-build sanity test (the multi-stage Dockerfile build is the slow, real
gate); cicd.yml is the ~10-line trigger shim importing hanzoai/ci's reusable
build workflow on the arc pool, secrets from KMS only.
No `deploy:` block yet, matching the hanzoai/cms build-only-first-cutover
pattern: this repo's own live hanzo-cd namespace runs the whole Hanzo fleet's
GitOps, so the prod image/CRD swap is a deliberate separate action (see the
[HELD] universe PR), never an automatic rollout on merge. Every PR/push still
builds and pushes an immutable ghcr.io/hanzoai/deploy:sha-<7>-amd64 image.
* feat(ui): rebrand Argo CD -> Hanzo CD (title, favicon, logo, sidebar)
Phase 1.2. User-facing "Argo CD"/"ArgoCD" text replaced with "Hanzo CD"
across the login page, help modal, settings screens, version-info panel,
sync-options warning, fullscreen-logs title, app.tsx page titles, and
index.html's <title>/noscript text (dropped the now-wrong upstream CLI
doc link rather than invent a hanzo.ai URL that doesn't exist).
Logo/favicon assets replaced with the canonical Hanzo mark (never
hand-drawn, copied from ~/work/hanzo/logo/dist): the two real usages of
Argo's logo collapse onto one Hanzo glyph (mono + white variants) at the
same 5 call sites (login icon + footer, login.scss $logoPath/
$logoNegativePath, sidebar text-logo), preserving the existing CSS
invert() filters so both the light login panel and dark sidebar still
render correctly. Old argologo.svg/argo_o.svg/argo.png deleted (no
longer referenced). favicon-{16x16,32x32}.png added at
ui/src/assets/favicon/ (index.html's links to that path were previously
dead - the directory didn't exist in this checkout). package.json name
argo-cd-ui -> hanzo-cd-ui.
Left alone, deliberately: the two Kubernetes resource-kind icon sets
under ui/src/assets/images/resources/ (unrelated to branding), and
upstream doc/project links (argoproj.io, argo-cd.readthedocs.io) since
we have no equivalent hanzo.ai docs to point to yet - except the login
footer logo's outbound link, which now points to hanzo.ai instead of
argoproj.io (a Hanzo-branded mark linking to the upstream project it
was forked from read as an obvious leftover).
* feat(module): rename Go module github.com/argoproj/argo-cd/v3 -> github.com/hanzoai/deploy
Phase 2. No version suffix on the new path (org policy: never publish our
own Go modules above v1.x.x / with a v2+ suffix) — this fork starts a fresh
v0/v1 module identity rather than carrying the v3 the upstream fork point
had reached.
Mechanical, symmetric rewrite: 704 .go files (import statements only; two
disjoint substitutions applied specific-first so the nested gitops-engine
module path never double-matches), plus go.mod (module line, the
gitops-engine require+replace pair, version reset to v0.0.0-... since a
suffix-less path can't carry a v3 semver), gitops-engine/go.mod, Makefile
(ldflags PACKAGE + 2 TEST_MODULE filters), .golangci.yaml, .goreleaser.yaml,
.mockery.yaml (+ gitops-engine's), Procfile, the codegen/proto generation
scripts (hack/generate-proto.sh, update-codegen.sh, update-openapi.sh), and
20 .proto files' go_package options. Also one OpenTelemetry tracer name
string in gitops-engine/pkg/sync/sync_context.go (a cosmestic label, not an
import, but worth keeping consistent).
CGO_ENABLED=0 go build ./... verified green for both the root module and
the nested gitops-engine module; go vet clean on both; go mod tidy run
(network available) to reconcile go.sum, dependency set unchanged.
Left alone, deliberately: doc-only mentions (docs/snyk/*.html scan reports,
gitops-engine/README.md), a comment-only reference in
.github/workflows/release.yaml, and one Renovate bot config line — none are
build- or codegen-load-bearing. Also left alone: comments citing upstream
GitHub issue URLs (github.com/argoproj/argo-cd/issues/NNNNN) and a handful
of test fixtures using "https://github.com/argoproj/argo-cd.git" as a
representative example repo URL — both are accurate historical/test-data
references, not stale self-references.
---------
Co-authored-by: Hanzo Dev <[email protected]>
2970 lines
95 KiB
Go
2970 lines
95 KiB
Go
package settings
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"fmt"
|
|
"maps"
|
|
"net/http"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
corev1 "k8s.io/api/core/v1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/labels"
|
|
"k8s.io/apimachinery/pkg/util/yaml"
|
|
"k8s.io/client-go/kubernetes/fake"
|
|
|
|
"github.com/hanzoai/deploy/common"
|
|
"github.com/hanzoai/deploy/pkg/apis/application/v1alpha1"
|
|
testutil "github.com/hanzoai/deploy/test"
|
|
"github.com/hanzoai/deploy/util/test"
|
|
)
|
|
|
|
func fixtures(ctx context.Context, data map[string]string, opts ...func(secret *corev1.Secret)) (*fake.Clientset, *SettingsManager) {
|
|
cm := &corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDConfigMapName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: data,
|
|
}
|
|
secret := &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDSecretName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: map[string][]byte{},
|
|
}
|
|
for i := range opts {
|
|
opts[i](secret)
|
|
}
|
|
kubeClient := fake.NewClientset(cm, secret)
|
|
settingsManager := NewSettingsManager(ctx, kubeClient, "default")
|
|
|
|
return kubeClient, settingsManager
|
|
}
|
|
|
|
func TestDocumentedArgoCDConfigMapIsValid(t *testing.T) {
|
|
var argocdCM *corev1.ConfigMap
|
|
settings := ArgoCDSettings{}
|
|
data, err := os.ReadFile("../../docs/operator-manual/argocd-cm.yaml")
|
|
require.NoError(t, err)
|
|
err = yaml.Unmarshal(data, &argocdCM)
|
|
require.NoError(t, err)
|
|
updateSettingsFromConfigMap(&settings, argocdCM)
|
|
}
|
|
|
|
func TestGetConfigMapByName(t *testing.T) {
|
|
t.Run("data is never nil", func(t *testing.T) {
|
|
_, settingsManager := fixtures(t.Context(), nil)
|
|
cm, err := settingsManager.GetConfigMapByName(common.ArgoCDConfigMapName)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, cm.Data)
|
|
})
|
|
t.Run("cannot update informer value", func(t *testing.T) {
|
|
_, settingsManager := fixtures(t.Context(), nil)
|
|
cm1, err := settingsManager.GetConfigMapByName(common.ArgoCDConfigMapName)
|
|
require.NoError(t, err)
|
|
cm1.Data["test"] = "invalid"
|
|
cm2, err := settingsManager.GetConfigMapByName(common.ArgoCDConfigMapName)
|
|
require.NoError(t, err)
|
|
assert.NotContains(t, cm2.Data, "test")
|
|
})
|
|
}
|
|
|
|
func TestGetSecretByName(t *testing.T) {
|
|
t.Run("data is never nil", func(t *testing.T) {
|
|
_, settingsManager := fixtures(t.Context(), nil, func(secret *corev1.Secret) { secret.Data = nil })
|
|
secret, err := settingsManager.GetSecretByName(common.ArgoCDSecretName)
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, secret.Data)
|
|
})
|
|
t.Run("cannot update informer value", func(t *testing.T) {
|
|
_, settingsManager := fixtures(t.Context(), nil)
|
|
s1, err := settingsManager.GetSecretByName(common.ArgoCDSecretName)
|
|
require.NoError(t, err)
|
|
s1.Data["test"] = []byte("invalid")
|
|
s2, err := settingsManager.GetSecretByName(common.ArgoCDSecretName)
|
|
require.NoError(t, err)
|
|
assert.NotContains(t, s2.Data, "test")
|
|
})
|
|
}
|
|
|
|
func TestGetExtensionConfigs(t *testing.T) {
|
|
type cases struct {
|
|
name string
|
|
input map[string]string
|
|
expected map[string]string
|
|
expectedLen int
|
|
}
|
|
|
|
testCases := []cases{
|
|
{
|
|
name: "will return main config successfully",
|
|
expectedLen: 1,
|
|
input: map[string]string{
|
|
extensionConfig: "test",
|
|
},
|
|
expected: map[string]string{
|
|
"": "test",
|
|
},
|
|
},
|
|
{
|
|
name: "will return main and additional config successfully",
|
|
expectedLen: 2,
|
|
input: map[string]string{
|
|
extensionConfig: "main config",
|
|
extensionConfig + ".anotherExtension": "another config",
|
|
},
|
|
expected: map[string]string{
|
|
"": "main config",
|
|
"anotherExtension": "another config",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
// When
|
|
output := getExtensionConfigs(tc.input)
|
|
|
|
// Then
|
|
assert.Len(t, output, tc.expectedLen)
|
|
assert.Equal(t, tc.expected, output)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetResourceFilter(t *testing.T) {
|
|
data := map[string]string{
|
|
"resource.exclusions": "\n - apiGroups: [\"group1\"]\n kinds: [\"kind1\"]\n clusters: [\"cluster1\"]\n",
|
|
"resource.inclusions": "\n - apiGroups: [\"group2\"]\n kinds: [\"kind2\"]\n clusters: [\"cluster2\"]\n",
|
|
}
|
|
_, settingsManager := fixtures(t.Context(), data)
|
|
filter, err := settingsManager.GetResourcesFilter()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, &ResourcesFilter{
|
|
ResourceExclusions: []FilteredResource{{APIGroups: []string{"group1"}, Kinds: []string{"kind1"}, Clusters: []string{"cluster1"}}},
|
|
ResourceInclusions: []FilteredResource{{APIGroups: []string{"group2"}, Kinds: []string{"kind2"}, Clusters: []string{"cluster2"}}},
|
|
}, filter)
|
|
}
|
|
|
|
func TestInClusterServerAddressEnabled(t *testing.T) {
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{
|
|
"cluster.inClusterEnabled": "true",
|
|
})
|
|
argoCDCM, err := settingsManager.getConfigMap()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "true", argoCDCM.Data[inClusterEnabledKey])
|
|
|
|
_, settingsManager = fixtures(t.Context(), map[string]string{
|
|
"cluster.inClusterEnabled": "false",
|
|
})
|
|
argoCDCM, err = settingsManager.getConfigMap()
|
|
require.NoError(t, err)
|
|
assert.NotEqual(t, "true", argoCDCM.Data[inClusterEnabledKey])
|
|
}
|
|
|
|
func TestInClusterServerAddressEnabledByDefault(t *testing.T) {
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{})
|
|
enabled, err := settingsManager.IsInClusterEnabled()
|
|
require.NoError(t, err)
|
|
require.True(t, enabled)
|
|
}
|
|
|
|
func TestGetSettings_InClusterIsEnabledWithMissingServerSecretKey(t *testing.T) {
|
|
kubeClient := fake.NewClientset(
|
|
&corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDConfigMapName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: map[string]string{},
|
|
},
|
|
&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDSecretName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"admin.password": nil,
|
|
},
|
|
},
|
|
)
|
|
settingsManager := NewSettingsManager(t.Context(), kubeClient, "default")
|
|
// IsInClusterEnabled reads ConfigMap directly and does not depend on server.secretkey
|
|
enabled, err := settingsManager.IsInClusterEnabled()
|
|
require.NoError(t, err)
|
|
require.True(t, enabled)
|
|
}
|
|
|
|
func TestGetAppInstanceLabelKey(t *testing.T) {
|
|
t.Run("should get custom instanceLabelKey", func(t *testing.T) {
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{
|
|
"application.instanceLabelKey": "testLabel",
|
|
})
|
|
label, err := settingsManager.GetAppInstanceLabelKey()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "testLabel", label)
|
|
})
|
|
|
|
t.Run("should get default instanceLabelKey if custom not defined", func(t *testing.T) {
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{})
|
|
label, err := settingsManager.GetAppInstanceLabelKey()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, common.LabelKeyAppInstance, label)
|
|
})
|
|
}
|
|
|
|
func TestGetTrackingMethod(t *testing.T) {
|
|
t.Run("should get custom trackingMethod", func(t *testing.T) {
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{
|
|
"application.resourceTrackingMethod": string(v1alpha1.TrackingMethodLabel),
|
|
})
|
|
label, err := settingsManager.GetTrackingMethod()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, string(v1alpha1.TrackingMethodLabel), label)
|
|
})
|
|
|
|
t.Run("should get default trackingMethod if custom not defined", func(t *testing.T) {
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{})
|
|
label, err := settingsManager.GetTrackingMethod()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, string(v1alpha1.TrackingMethodAnnotation), label)
|
|
})
|
|
}
|
|
|
|
func TestGetInstallationID(t *testing.T) {
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{
|
|
"installationID": "123456789",
|
|
})
|
|
id, err := settingsManager.GetInstallationID()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "123456789", id)
|
|
}
|
|
|
|
func TestApplicationFineGrainedRBACInheritanceDisabledDefault(t *testing.T) {
|
|
_, settingsManager := fixtures(t.Context(), nil)
|
|
flag, err := settingsManager.ApplicationFineGrainedRBACInheritanceDisabled()
|
|
require.NoError(t, err)
|
|
assert.True(t, flag)
|
|
}
|
|
|
|
func TestApplicationFineGrainedRBACInheritanceDisabled(t *testing.T) {
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{
|
|
"server.rbac.disableApplicationFineGrainedRBACInheritance": "false",
|
|
})
|
|
flag, err := settingsManager.ApplicationFineGrainedRBACInheritanceDisabled()
|
|
require.NoError(t, err)
|
|
assert.False(t, flag)
|
|
}
|
|
|
|
func TestGetIsIgnoreResourceUpdatesEnabled(t *testing.T) {
|
|
_, settingsManager := fixtures(t.Context(), nil)
|
|
ignoreResourceUpdatesEnabled, err := settingsManager.GetIsIgnoreResourceUpdatesEnabled()
|
|
require.NoError(t, err)
|
|
assert.True(t, ignoreResourceUpdatesEnabled)
|
|
|
|
_, settingsManager = fixtures(t.Context(), map[string]string{
|
|
"resource.ignoreResourceUpdatesEnabled": "true",
|
|
})
|
|
ignoreResourceUpdatesEnabled, err = settingsManager.GetIsIgnoreResourceUpdatesEnabled()
|
|
require.NoError(t, err)
|
|
assert.True(t, ignoreResourceUpdatesEnabled)
|
|
}
|
|
|
|
func TestGetIsIgnoreResourceUpdatesEnabledFalse(t *testing.T) {
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{
|
|
"resource.ignoreResourceUpdatesEnabled": "false",
|
|
})
|
|
ignoreResourceUpdatesEnabled, err := settingsManager.GetIsIgnoreResourceUpdatesEnabled()
|
|
require.NoError(t, err)
|
|
assert.False(t, ignoreResourceUpdatesEnabled)
|
|
}
|
|
|
|
func TestGetResourceOverrides(t *testing.T) {
|
|
ignoreStatus := v1alpha1.ResourceOverride{IgnoreDifferences: v1alpha1.OverrideIgnoreDiff{
|
|
JSONPointers: []string{"/status"},
|
|
}}
|
|
crdGK := "apiextensions.k8s.io/CustomResourceDefinition"
|
|
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{
|
|
"resource.customizations": `
|
|
admissionregistration.k8s.io/MutatingWebhookConfiguration:
|
|
ignoreDifferences: |
|
|
jsonPointers:
|
|
- /webhooks/0/clientConfig/caBundle
|
|
jqPathExpressions:
|
|
- .webhooks[0].clientConfig.caBundle
|
|
ignoreResourceUpdates: |
|
|
jsonPointers:
|
|
- /webhooks/1/clientConfig/caBundle
|
|
jqPathExpressions:
|
|
- .webhooks[1].clientConfig.caBundle`,
|
|
})
|
|
overrides, err := settingsManager.GetResourceOverrides()
|
|
require.NoError(t, err)
|
|
|
|
webHookOverrides := overrides["admissionregistration.k8s.io/MutatingWebhookConfiguration"]
|
|
assert.NotNil(t, webHookOverrides)
|
|
|
|
assert.Equal(t, v1alpha1.ResourceOverride{
|
|
IgnoreDifferences: v1alpha1.OverrideIgnoreDiff{
|
|
JSONPointers: []string{"/webhooks/0/clientConfig/caBundle"},
|
|
JQPathExpressions: []string{".webhooks[0].clientConfig.caBundle"},
|
|
},
|
|
IgnoreResourceUpdates: v1alpha1.OverrideIgnoreDiff{
|
|
JSONPointers: []string{"/webhooks/1/clientConfig/caBundle"},
|
|
JQPathExpressions: []string{".webhooks[1].clientConfig.caBundle"},
|
|
},
|
|
}, webHookOverrides)
|
|
|
|
// by default, all status should be ignored
|
|
globalOverrides := overrides["*/*"]
|
|
assert.NotNil(t, globalOverrides)
|
|
assert.Equal(t, ignoreStatus, globalOverrides)
|
|
|
|
// with value all, status of all objects should be ignored
|
|
_, settingsManager = fixtures(t.Context(), map[string]string{
|
|
"resource.compareoptions": `
|
|
ignoreResourceStatusField: all`,
|
|
})
|
|
overrides, err = settingsManager.GetResourceOverrides()
|
|
require.NoError(t, err)
|
|
|
|
globalOverrides = overrides["*/*"]
|
|
assert.NotNil(t, globalOverrides)
|
|
assert.Equal(t, ignoreStatus, globalOverrides)
|
|
|
|
// with value crd, status of crd objects should be ignored
|
|
_, settingsManager = fixtures(t.Context(), map[string]string{
|
|
"resource.compareoptions": `
|
|
ignoreResourceStatusField: crd`,
|
|
|
|
"resource.customizations": `
|
|
apiextensions.k8s.io/CustomResourceDefinition:
|
|
ignoreDifferences: |
|
|
jsonPointers:
|
|
- /webhooks/0/clientConfig/caBundle
|
|
jqPathExpressions:
|
|
- .webhooks[0].clientConfig.caBundle`,
|
|
})
|
|
overrides, err = settingsManager.GetResourceOverrides()
|
|
require.NoError(t, err)
|
|
|
|
crdOverrides := overrides[crdGK]
|
|
assert.NotNil(t, crdOverrides)
|
|
assert.Equal(t, v1alpha1.ResourceOverride{IgnoreDifferences: v1alpha1.OverrideIgnoreDiff{
|
|
JSONPointers: []string{"/webhooks/0/clientConfig/caBundle", "/status"},
|
|
JQPathExpressions: []string{".webhooks[0].clientConfig.caBundle"},
|
|
}}, crdOverrides)
|
|
|
|
// with incorrect value, status of all objects should be ignored
|
|
_, settingsManager = fixtures(t.Context(), map[string]string{
|
|
"resource.compareoptions": `
|
|
ignoreResourceStatusField: foobar`,
|
|
})
|
|
overrides, err = settingsManager.GetResourceOverrides()
|
|
require.NoError(t, err)
|
|
|
|
globalOverrides = overrides["*/*"]
|
|
assert.NotNil(t, globalOverrides)
|
|
assert.Equal(t, ignoreStatus, globalOverrides)
|
|
|
|
// with value non-string off, status of no objects should be ignored
|
|
_, settingsManager = fixtures(t.Context(), map[string]string{
|
|
"resource.compareoptions": `
|
|
ignoreResourceStatusField: off`,
|
|
})
|
|
overrides, err = settingsManager.GetResourceOverrides()
|
|
require.NoError(t, err)
|
|
assert.Empty(t, overrides)
|
|
|
|
// with value non-string false, status of no objects should be ignored
|
|
_, settingsManager = fixtures(t.Context(), map[string]string{
|
|
"resource.compareoptions": `
|
|
ignoreResourceStatusField: false`,
|
|
})
|
|
overrides, err = settingsManager.GetResourceOverrides()
|
|
require.NoError(t, err)
|
|
assert.Empty(t, overrides)
|
|
|
|
// with value none, status of no objects should be ignored
|
|
_, settingsManager = fixtures(t.Context(), map[string]string{
|
|
"resource.compareoptions": `
|
|
ignoreResourceStatusField: none`,
|
|
})
|
|
overrides, err = settingsManager.GetResourceOverrides()
|
|
require.NoError(t, err)
|
|
assert.Empty(t, overrides)
|
|
}
|
|
|
|
func TestGetResourceOverridesHealthWithWildcard(t *testing.T) {
|
|
data := map[string]string{
|
|
"resource.customizations": `
|
|
"*.aws.crossplane.io/*":
|
|
health.lua: |
|
|
foo`,
|
|
}
|
|
|
|
t.Run("TestResourceHealthOverrideWithWildcard", func(t *testing.T) {
|
|
_, settingsManager := fixtures(t.Context(), data)
|
|
|
|
overrides, err := settingsManager.GetResourceOverrides()
|
|
require.NoError(t, err)
|
|
assert.Len(t, overrides, 2)
|
|
assert.Equal(t, "foo", overrides["*.aws.crossplane.io/*"].HealthLua)
|
|
})
|
|
}
|
|
|
|
func TestSettingsManager_GetResourceOverrides_with_empty_string(t *testing.T) {
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{
|
|
resourceCustomizationsKey: "",
|
|
})
|
|
overrides, err := settingsManager.GetResourceOverrides()
|
|
require.NoError(t, err)
|
|
|
|
assert.Len(t, overrides, 1)
|
|
}
|
|
|
|
func TestGetResourceOverrides_with_splitted_keys(t *testing.T) {
|
|
data := map[string]string{
|
|
"resource.compareoptions": `ignoreResourceStatusField: none`,
|
|
|
|
"resource.customizations": `
|
|
admissionregistration.k8s.io/MutatingWebhookConfiguration:
|
|
ignoreDifferences: |
|
|
jsonPointers:
|
|
- foo
|
|
ignoreResourceUpdates: |
|
|
jsonPointers:
|
|
- foo
|
|
certmanager.k8s.io/Certificate:
|
|
health.lua.useOpenLibs: true
|
|
health.lua: |
|
|
foo
|
|
cert-manager.io/Certificate:
|
|
health.lua: |
|
|
foo
|
|
apps/Deployment:
|
|
actions: |
|
|
foo`,
|
|
}
|
|
|
|
t.Run("MergedKey", func(t *testing.T) {
|
|
_, settingsManager := fixtures(t.Context(), data)
|
|
|
|
overrides, err := settingsManager.GetResourceOverrides()
|
|
require.NoError(t, err)
|
|
assert.Len(t, overrides, 4)
|
|
assert.Len(t, overrides["admissionregistration.k8s.io/MutatingWebhookConfiguration"].IgnoreDifferences.JSONPointers, 1)
|
|
assert.Equal(t, "foo", overrides["admissionregistration.k8s.io/MutatingWebhookConfiguration"].IgnoreDifferences.JSONPointers[0])
|
|
assert.Len(t, overrides["admissionregistration.k8s.io/MutatingWebhookConfiguration"].IgnoreResourceUpdates.JSONPointers, 1)
|
|
assert.Equal(t, "foo", overrides["admissionregistration.k8s.io/MutatingWebhookConfiguration"].IgnoreResourceUpdates.JSONPointers[0])
|
|
assert.Equal(t, "foo\n", overrides["certmanager.k8s.io/Certificate"].HealthLua)
|
|
assert.True(t, overrides["certmanager.k8s.io/Certificate"].UseOpenLibs)
|
|
assert.Equal(t, "foo\n", overrides["cert-manager.io/Certificate"].HealthLua)
|
|
assert.False(t, overrides["cert-manager.io/Certificate"].UseOpenLibs)
|
|
assert.Equal(t, "foo", overrides["apps/Deployment"].Actions)
|
|
})
|
|
|
|
t.Run("SplitKeys", func(t *testing.T) {
|
|
newData := map[string]string{
|
|
"resource.compareoptions": `ignoreResourceStatusField: none`,
|
|
|
|
"resource.customizations.health.admissionregistration.k8s.io_MutatingWebhookConfiguration": "bar",
|
|
"resource.customizations.ignoreDifferences.admissionregistration.k8s.io_MutatingWebhookConfiguration": `jsonPointers:
|
|
- bar`,
|
|
"resource.customizations.ignoreResourceUpdates.admissionregistration.k8s.io_MutatingWebhookConfiguration": `jsonPointers:
|
|
- bar`,
|
|
"resource.customizations.knownTypeFields.admissionregistration.k8s.io_MutatingWebhookConfiguration": `
|
|
- field: foo
|
|
type: bar`,
|
|
"resource.customizations.health.certmanager.k8s.io_Certificate": "bar",
|
|
"resource.customizations.health.cert-manager.io_Certificate": "bar",
|
|
"resource.customizations.useOpenLibs.certmanager.k8s.io_Certificate": "false",
|
|
"resource.customizations.useOpenLibs.cert-manager.io_Certificate": "true",
|
|
"resource.customizations.actions.apps_Deployment": "bar",
|
|
"resource.customizations.actions.Deployment": "bar",
|
|
"resource.customizations.health.iam-manager.k8s.io_Iamrole": "bar",
|
|
"resource.customizations.health.Iamrole": "bar",
|
|
"resource.customizations.ignoreDifferences.iam-manager.k8s.io_Iamrole": `jsonPointers:
|
|
- bar`,
|
|
"resource.customizations.ignoreDifferences.apps_Deployment": `jqPathExpressions:
|
|
- bar`,
|
|
"resource.customizations.ignoreDifferences.all": `managedFieldsManagers:
|
|
- kube-controller-manager
|
|
- argo-rollouts`,
|
|
"resource.customizations.ignoreResourceUpdates.iam-manager.k8s.io_Iamrole": `jsonPointers:
|
|
- bar`,
|
|
"resource.customizations.ignoreResourceUpdates.apps_Deployment": `jqPathExpressions:
|
|
- bar`,
|
|
}
|
|
|
|
_, settingsManager := fixtures(t.Context(), mergemaps(data, newData))
|
|
|
|
overrides, err := settingsManager.GetResourceOverrides()
|
|
require.NoError(t, err)
|
|
assert.Len(t, overrides, 8)
|
|
assert.Len(t, overrides["admissionregistration.k8s.io/MutatingWebhookConfiguration"].IgnoreDifferences.JSONPointers, 1)
|
|
assert.Equal(t, "bar", overrides["admissionregistration.k8s.io/MutatingWebhookConfiguration"].IgnoreDifferences.JSONPointers[0])
|
|
assert.Len(t, overrides["admissionregistration.k8s.io/MutatingWebhookConfiguration"].IgnoreResourceUpdates.JSONPointers, 1)
|
|
assert.Equal(t, "bar", overrides["admissionregistration.k8s.io/MutatingWebhookConfiguration"].IgnoreResourceUpdates.JSONPointers[0])
|
|
assert.Len(t, overrides["admissionregistration.k8s.io/MutatingWebhookConfiguration"].KnownTypeFields, 1)
|
|
assert.Equal(t, "bar", overrides["admissionregistration.k8s.io/MutatingWebhookConfiguration"].KnownTypeFields[0].Type)
|
|
assert.Equal(t, "bar", overrides["admissionregistration.k8s.io/MutatingWebhookConfiguration"].HealthLua)
|
|
assert.Equal(t, "bar", overrides["certmanager.k8s.io/Certificate"].HealthLua)
|
|
assert.Equal(t, "bar", overrides["cert-manager.io/Certificate"].HealthLua)
|
|
assert.False(t, overrides["certmanager.k8s.io/Certificate"].UseOpenLibs)
|
|
assert.True(t, overrides["cert-manager.io/Certificate"].UseOpenLibs)
|
|
assert.Equal(t, "bar", overrides["apps/Deployment"].Actions)
|
|
assert.Equal(t, "bar", overrides["Deployment"].Actions)
|
|
assert.Equal(t, "bar", overrides["iam-manager.k8s.io/Iamrole"].HealthLua)
|
|
assert.Equal(t, "bar", overrides["Iamrole"].HealthLua)
|
|
assert.Len(t, overrides["iam-manager.k8s.io/Iamrole"].IgnoreDifferences.JSONPointers, 1)
|
|
assert.Len(t, overrides["apps/Deployment"].IgnoreDifferences.JQPathExpressions, 1)
|
|
assert.Equal(t, "bar", overrides["apps/Deployment"].IgnoreDifferences.JQPathExpressions[0])
|
|
assert.Len(t, overrides["*/*"].IgnoreDifferences.ManagedFieldsManagers, 2)
|
|
assert.Equal(t, "kube-controller-manager", overrides["*/*"].IgnoreDifferences.ManagedFieldsManagers[0])
|
|
assert.Equal(t, "argo-rollouts", overrides["*/*"].IgnoreDifferences.ManagedFieldsManagers[1])
|
|
assert.Len(t, overrides["iam-manager.k8s.io/Iamrole"].IgnoreResourceUpdates.JSONPointers, 1)
|
|
assert.Len(t, overrides["apps/Deployment"].IgnoreResourceUpdates.JQPathExpressions, 1)
|
|
assert.Equal(t, "bar", overrides["apps/Deployment"].IgnoreResourceUpdates.JQPathExpressions[0])
|
|
})
|
|
}
|
|
|
|
func mergemaps(mapA map[string]string, mapB map[string]string) map[string]string {
|
|
maps.Copy(mapB, mapA)
|
|
return mapB
|
|
}
|
|
|
|
func TestGetIgnoreResourceUpdatesOverrides(t *testing.T) {
|
|
allDefault := v1alpha1.ResourceOverride{IgnoreDifferences: v1alpha1.OverrideIgnoreDiff{
|
|
JSONPointers: []string{"/metadata/resourceVersion", "/metadata/generation", "/metadata/managedFields"},
|
|
}}
|
|
allGK := "*/*"
|
|
|
|
testCustomizations := map[string]string{
|
|
"resource.compareoptions": `
|
|
ignoreResourceStatusField: none
|
|
ignoreDifferencesOnResourceUpdates: true`,
|
|
|
|
"resource.customizations": `
|
|
admissionregistration.k8s.io/MutatingWebhookConfiguration:
|
|
ignoreDifferences: |
|
|
jsonPointers:
|
|
- /webhooks/0/clientConfig/caBundle
|
|
jqPathExpressions:
|
|
- .webhooks[0].clientConfig.caBundle
|
|
ignoreResourceUpdates: |
|
|
jsonPointers:
|
|
- /webhooks/1/clientConfig/caBundle
|
|
jqPathExpressions:
|
|
- .webhooks[1].clientConfig.caBundle`,
|
|
}
|
|
|
|
_, settingsManager := fixtures(t.Context(), testCustomizations)
|
|
overrides, err := settingsManager.GetIgnoreResourceUpdatesOverrides()
|
|
require.NoError(t, err)
|
|
|
|
// default overrides should always be present
|
|
allOverrides := overrides[allGK]
|
|
assert.NotNil(t, allOverrides)
|
|
assert.Equal(t, allDefault, allOverrides)
|
|
|
|
// with ignoreDifferencesOnResourceUpdates, ignoreDifferences should be added
|
|
assert.NotNil(t, overrides["admissionregistration.k8s.io/MutatingWebhookConfiguration"])
|
|
assert.Equal(t, v1alpha1.ResourceOverride{
|
|
IgnoreDifferences: v1alpha1.OverrideIgnoreDiff{
|
|
JSONPointers: []string{"/webhooks/1/clientConfig/caBundle", "/webhooks/0/clientConfig/caBundle"},
|
|
JQPathExpressions: []string{".webhooks[1].clientConfig.caBundle", ".webhooks[0].clientConfig.caBundle"},
|
|
},
|
|
IgnoreResourceUpdates: v1alpha1.OverrideIgnoreDiff{},
|
|
}, overrides["admissionregistration.k8s.io/MutatingWebhookConfiguration"])
|
|
|
|
// without ignoreDifferencesOnResourceUpdates, only ignoreResourceUpdates should be added
|
|
_, settingsManager = fixtures(t.Context(), mergemaps(map[string]string{
|
|
"resource.compareoptions": `
|
|
ignoreResourceStatusField: none
|
|
ignoreDifferencesOnResourceUpdates: false`,
|
|
}, testCustomizations))
|
|
overrides, err = settingsManager.GetIgnoreResourceUpdatesOverrides()
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, overrides["admissionregistration.k8s.io/MutatingWebhookConfiguration"])
|
|
assert.Equal(t, v1alpha1.ResourceOverride{
|
|
IgnoreDifferences: v1alpha1.OverrideIgnoreDiff{
|
|
JSONPointers: []string{"/webhooks/1/clientConfig/caBundle"},
|
|
JQPathExpressions: []string{".webhooks[1].clientConfig.caBundle"},
|
|
},
|
|
IgnoreResourceUpdates: v1alpha1.OverrideIgnoreDiff{},
|
|
}, overrides["admissionregistration.k8s.io/MutatingWebhookConfiguration"])
|
|
}
|
|
|
|
func TestConvertToOverrideKey(t *testing.T) {
|
|
key, err := convertToOverrideKey("cert-manager.io_Certificate")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "cert-manager.io/Certificate", key)
|
|
|
|
key, err = convertToOverrideKey("Certificate")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "Certificate", key)
|
|
|
|
_, err = convertToOverrideKey("")
|
|
require.Error(t, err)
|
|
|
|
_, err = convertToOverrideKey("_")
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func TestGetResourceCompareOptions(t *testing.T) {
|
|
// ignoreAggregatedRules is true
|
|
{
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{
|
|
"resource.compareoptions": "ignoreAggregatedRoles: true",
|
|
})
|
|
compareOptions, err := settingsManager.GetResourceCompareOptions()
|
|
require.NoError(t, err)
|
|
assert.True(t, compareOptions.IgnoreAggregatedRoles)
|
|
}
|
|
|
|
// ignoreAggregatedRules is false
|
|
{
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{
|
|
"resource.compareoptions": "ignoreAggregatedRoles: false",
|
|
})
|
|
compareOptions, err := settingsManager.GetResourceCompareOptions()
|
|
require.NoError(t, err)
|
|
assert.False(t, compareOptions.IgnoreAggregatedRoles)
|
|
}
|
|
|
|
// ignoreDifferencesOnResourceUpdates is true
|
|
{
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{
|
|
"resource.compareoptions": "ignoreDifferencesOnResourceUpdates: true",
|
|
})
|
|
compareOptions, err := settingsManager.GetResourceCompareOptions()
|
|
require.NoError(t, err)
|
|
assert.True(t, compareOptions.IgnoreDifferencesOnResourceUpdates)
|
|
}
|
|
|
|
// ignoreDifferencesOnResourceUpdates is false
|
|
{
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{
|
|
"resource.compareoptions": "ignoreDifferencesOnResourceUpdates: false",
|
|
})
|
|
compareOptions, err := settingsManager.GetResourceCompareOptions()
|
|
require.NoError(t, err)
|
|
assert.False(t, compareOptions.IgnoreDifferencesOnResourceUpdates)
|
|
}
|
|
|
|
// The empty resource.compareoptions should result in default being returned
|
|
{
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{
|
|
"resource.compareoptions": "",
|
|
})
|
|
compareOptions, err := settingsManager.GetResourceCompareOptions()
|
|
defaultOptions := GetDefaultDiffOptions()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, defaultOptions.IgnoreAggregatedRoles, compareOptions.IgnoreAggregatedRoles)
|
|
assert.Equal(t, defaultOptions.IgnoreDifferencesOnResourceUpdates, compareOptions.IgnoreDifferencesOnResourceUpdates)
|
|
}
|
|
|
|
// resource.compareoptions not defined - should result in default being returned
|
|
{
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{})
|
|
compareOptions, err := settingsManager.GetResourceCompareOptions()
|
|
defaultOptions := GetDefaultDiffOptions()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, defaultOptions.IgnoreAggregatedRoles, compareOptions.IgnoreAggregatedRoles)
|
|
assert.Equal(t, defaultOptions.IgnoreDifferencesOnResourceUpdates, compareOptions.IgnoreDifferencesOnResourceUpdates)
|
|
}
|
|
}
|
|
|
|
func TestSettingsManager_GetKustomizeBuildOptions(t *testing.T) {
|
|
t.Run("Empty", func(t *testing.T) {
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{})
|
|
|
|
settings, err := settingsManager.GetKustomizeSettings()
|
|
|
|
require.NoError(t, err)
|
|
assert.Empty(t, settings.BuildOptions)
|
|
assert.Empty(t, settings.Versions)
|
|
})
|
|
t.Run("Set", func(t *testing.T) {
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{
|
|
"kustomize.buildOptions": "foo",
|
|
"kustomize.version.v3.2.1": "somePath",
|
|
})
|
|
|
|
options, err := settingsManager.GetKustomizeSettings()
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "foo", options.BuildOptions)
|
|
assert.Equal(t, []v1alpha1.KustomizeVersion{{Name: "v3.2.1", Path: "somePath"}}, options.Versions)
|
|
})
|
|
|
|
t.Run("Kustomize settings per-version", func(t *testing.T) {
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{
|
|
"kustomize.buildOptions": "--global true",
|
|
"kustomize.version.v3.2.1": "/path_3.2.1",
|
|
"kustomize.buildOptions.v3.2.3": "--options v3.2.3",
|
|
"kustomize.path.v3.2.3": "/path_3.2.3",
|
|
"kustomize.path.v3.2.4": "/path_3.2.4",
|
|
"kustomize.buildOptions.v3.2.4": "--options v3.2.4",
|
|
"kustomize.buildOptions.v3.2.5": "--options v3.2.5",
|
|
})
|
|
|
|
got, err := settingsManager.GetKustomizeSettings()
|
|
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "--global true", got.BuildOptions)
|
|
want := &v1alpha1.KustomizeOptions{
|
|
BuildOptions: "--global true",
|
|
Versions: []v1alpha1.KustomizeVersion{
|
|
{Name: "v3.2.1", Path: "/path_3.2.1"},
|
|
{Name: "v3.2.3", Path: "/path_3.2.3", BuildOptions: "--options v3.2.3"},
|
|
{Name: "v3.2.4", Path: "/path_3.2.4", BuildOptions: "--options v3.2.4"},
|
|
},
|
|
}
|
|
sortVersionsByName := func(versions []v1alpha1.KustomizeVersion) {
|
|
sort.Slice(versions, func(i, j int) bool {
|
|
return versions[i].Name > versions[j].Name
|
|
})
|
|
}
|
|
sortVersionsByName(want.Versions)
|
|
sortVersionsByName(got.Versions)
|
|
assert.Equal(t, want, got)
|
|
})
|
|
|
|
t.Run("Kustomize settings per-version with duplicate versions", func(t *testing.T) {
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{
|
|
"kustomize.buildOptions": "--global true",
|
|
"kustomize.version.v3.2.1": "/path_3.2.1",
|
|
"kustomize.buildOptions.v3.2.1": "--options v3.2.3",
|
|
"kustomize.path.v3.2.2": "/other_path_3.2.2",
|
|
"kustomize.path.v3.2.1": "/other_path_3.2.1",
|
|
})
|
|
|
|
got, err := settingsManager.GetKustomizeSettings()
|
|
require.ErrorContains(t, err, "found duplicate kustomize version: v3.2.1")
|
|
assert.Empty(t, got)
|
|
})
|
|
|
|
t.Run("Config map with no Kustomize settings", func(t *testing.T) {
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{
|
|
"other.options": "--global true",
|
|
})
|
|
|
|
got, err := settingsManager.GetKustomizeSettings()
|
|
require.NoError(t, err)
|
|
assert.Empty(t, got)
|
|
})
|
|
}
|
|
|
|
func TestSettingsManager_GetEventLabelKeys(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
data string
|
|
expectedKeys []string
|
|
}{
|
|
{
|
|
name: "Comma separated data",
|
|
data: "app,env, tier, example.com/team-*, *",
|
|
expectedKeys: []string{"app", "env", "tier", "example.com/team-*", "*"},
|
|
},
|
|
{
|
|
name: "Empty data",
|
|
expectedKeys: []string{},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{})
|
|
if tt.data != "" {
|
|
_, settingsManager = fixtures(t.Context(), map[string]string{
|
|
resourceIncludeEventLabelKeys: tt.data,
|
|
resourceExcludeEventLabelKeys: tt.data,
|
|
})
|
|
}
|
|
|
|
inKeys := settingsManager.GetIncludeEventLabelKeys()
|
|
assert.Len(t, inKeys, len(tt.expectedKeys))
|
|
|
|
exKeys := settingsManager.GetExcludeEventLabelKeys()
|
|
assert.Len(t, exKeys, len(tt.expectedKeys))
|
|
|
|
for i := range tt.expectedKeys {
|
|
assert.Equal(t, tt.expectedKeys[i], inKeys[i])
|
|
assert.Equal(t, tt.expectedKeys[i], exKeys[i])
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_GetKustomizeBinaryPath(t *testing.T) {
|
|
ko := &v1alpha1.KustomizeOptions{
|
|
BuildOptions: "--opt1 val1",
|
|
Versions: []v1alpha1.KustomizeVersion{
|
|
{Name: "v1", Path: "path_v1"},
|
|
{Name: "v2", Path: "path_v2"},
|
|
{Name: "v3", Path: "path_v3", BuildOptions: "--opt2 val2"},
|
|
},
|
|
}
|
|
|
|
t.Run("VersionDoesNotExist", func(t *testing.T) {
|
|
_, err := GetKustomizeBinaryPath(ko, v1alpha1.ApplicationSource{
|
|
Kustomize: &v1alpha1.ApplicationSourceKustomize{Version: "v4"},
|
|
})
|
|
require.Error(t, err)
|
|
})
|
|
|
|
t.Run("DefaultBuildOptions", func(t *testing.T) {
|
|
ver, err := GetKustomizeBinaryPath(ko, v1alpha1.ApplicationSource{})
|
|
require.NoError(t, err)
|
|
assert.Empty(t, ver)
|
|
})
|
|
|
|
t.Run("VersionExists", func(t *testing.T) {
|
|
ver, err := GetKustomizeBinaryPath(ko, v1alpha1.ApplicationSource{
|
|
Kustomize: &v1alpha1.ApplicationSourceKustomize{Version: "v2"},
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "path_v2", ver)
|
|
})
|
|
|
|
t.Run("VersionExistsWithBuildOption", func(t *testing.T) {
|
|
ver, err := GetKustomizeBinaryPath(ko, v1alpha1.ApplicationSource{
|
|
Kustomize: &v1alpha1.ApplicationSourceKustomize{Version: "v3"},
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "path_v3", ver)
|
|
})
|
|
|
|
t.Run("ExplicitVersionSet", func(t *testing.T) {
|
|
// nolint:staticcheck // test for backwards compatibility with deprecated field
|
|
ko.BinaryPath = "custom_path"
|
|
ver, err := GetKustomizeBinaryPath(ko, v1alpha1.ApplicationSource{
|
|
Kustomize: &v1alpha1.ApplicationSourceKustomize{Version: "v3"},
|
|
})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "custom_path", ver)
|
|
})
|
|
}
|
|
|
|
func TestGetGoogleAnalytics(t *testing.T) {
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{
|
|
"ga.trackingid": "123",
|
|
})
|
|
ga, err := settingsManager.GetGoogleAnalytics()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "123", ga.TrackingID)
|
|
assert.True(t, ga.AnonymizeUsers)
|
|
}
|
|
|
|
func TestSettingsManager_GetHelp(t *testing.T) {
|
|
t.Run("Default", func(t *testing.T) {
|
|
_, settingsManager := fixtures(t.Context(), nil)
|
|
h, err := settingsManager.GetHelp()
|
|
require.NoError(t, err)
|
|
assert.Empty(t, h.ChatURL)
|
|
assert.Empty(t, h.ChatText)
|
|
})
|
|
t.Run("Set", func(t *testing.T) {
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{
|
|
"help.chatUrl": "foo",
|
|
"help.chatText": "bar",
|
|
})
|
|
h, err := settingsManager.GetHelp()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "foo", h.ChatURL)
|
|
assert.Equal(t, "bar", h.ChatText)
|
|
})
|
|
t.Run("SetOnlyChatUrl", func(t *testing.T) {
|
|
_, settingManager := fixtures(t.Context(), map[string]string{
|
|
"help.chatUrl": "foo",
|
|
})
|
|
h, err := settingManager.GetHelp()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "foo", h.ChatURL)
|
|
assert.Equal(t, "Chat now!", h.ChatText)
|
|
})
|
|
t.Run("SetOnlyChatText", func(t *testing.T) {
|
|
_, settingManager := fixtures(t.Context(), map[string]string{
|
|
"help.chatText": "bar",
|
|
})
|
|
h, err := settingManager.GetHelp()
|
|
require.NoError(t, err)
|
|
assert.Empty(t, h.ChatURL)
|
|
assert.Empty(t, h.ChatText)
|
|
})
|
|
t.Run("GetBinaryUrls", func(t *testing.T) {
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{
|
|
"help.download.darwin-amd64": "amd64-path",
|
|
"help.download.linux-s390x": "s390x-path",
|
|
"help.download.unsupported": "nowhere",
|
|
})
|
|
h, err := settingsManager.GetHelp()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, map[string]string{"darwin-amd64": "amd64-path", "linux-s390x": "s390x-path"}, h.BinaryURLs)
|
|
})
|
|
}
|
|
|
|
func TestSettingsManager_GetSettings(t *testing.T) {
|
|
t.Run("UserSessionDurationNotProvided", func(t *testing.T) {
|
|
kubeClient := fake.NewClientset(
|
|
&corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDConfigMapName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: nil,
|
|
},
|
|
&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDSecretName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"server.secretkey": nil,
|
|
},
|
|
},
|
|
)
|
|
settingsManager := NewSettingsManager(t.Context(), kubeClient, "default")
|
|
s, err := settingsManager.GetSettings()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, time.Hour*24, s.UserSessionDuration)
|
|
})
|
|
t.Run("UserSessionDurationInvalidFormat", func(t *testing.T) {
|
|
kubeClient := fake.NewClientset(
|
|
&corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDConfigMapName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: map[string]string{
|
|
"users.session.duration": "10hh",
|
|
},
|
|
},
|
|
&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDSecretName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"server.secretkey": nil,
|
|
},
|
|
},
|
|
)
|
|
settingsManager := NewSettingsManager(t.Context(), kubeClient, "default")
|
|
s, err := settingsManager.GetSettings()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, time.Hour*24, s.UserSessionDuration)
|
|
})
|
|
t.Run("UserSessionDurationProvided", func(t *testing.T) {
|
|
kubeClient := fake.NewClientset(
|
|
&corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDConfigMapName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: map[string]string{
|
|
"users.session.duration": "10h",
|
|
},
|
|
},
|
|
&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDSecretName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"server.secretkey": nil,
|
|
},
|
|
},
|
|
)
|
|
settingsManager := NewSettingsManager(t.Context(), kubeClient, "default")
|
|
s, err := settingsManager.GetSettings()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, time.Hour*10, s.UserSessionDuration)
|
|
})
|
|
}
|
|
|
|
func TestGetOIDCConfig(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
configMapData map[string]string
|
|
testFunc func(t *testing.T, settingsManager *SettingsManager)
|
|
}{
|
|
{
|
|
name: "requestedIDTokenClaims",
|
|
configMapData: map[string]string{
|
|
"oidc.config": "\n requestedIDTokenClaims: {\"groups\": {\"essential\": true}}\n",
|
|
},
|
|
testFunc: func(t *testing.T, settingsManager *SettingsManager) {
|
|
t.Helper()
|
|
settings, err := settingsManager.GetSettings()
|
|
require.NoError(t, err)
|
|
|
|
oidcConfig := settings.OIDCConfig()
|
|
assert.NotNil(t, oidcConfig)
|
|
|
|
claim := oidcConfig.RequestedIDTokenClaims["groups"]
|
|
assert.NotNil(t, claim)
|
|
assert.True(t, claim.Essential)
|
|
},
|
|
},
|
|
{
|
|
name: "refreshTokenThreshold success",
|
|
configMapData: map[string]string{
|
|
"oidc.config": "\n refreshTokenThreshold: 5m\n",
|
|
},
|
|
testFunc: func(t *testing.T, settingsManager *SettingsManager) {
|
|
t.Helper()
|
|
settings, err := settingsManager.GetSettings()
|
|
require.NoError(t, err)
|
|
|
|
oidcConfig := settings.OIDCConfig()
|
|
assert.NotNil(t, oidcConfig)
|
|
|
|
assert.Equal(t, 5*time.Minute, settings.RefreshTokenThreshold())
|
|
},
|
|
},
|
|
{
|
|
name: "refreshTokenThreshold parse failure",
|
|
configMapData: map[string]string{
|
|
"oidc.config": "\n refreshTokenThreshold: 5xx\n",
|
|
},
|
|
testFunc: func(t *testing.T, settingsManager *SettingsManager) {
|
|
t.Helper()
|
|
settings, err := settingsManager.GetSettings()
|
|
require.NoError(t, err)
|
|
|
|
oidcConfig := settings.OIDCConfig()
|
|
assert.NotNil(t, oidcConfig)
|
|
|
|
assert.Equal(t, time.Duration(0), settings.RefreshTokenThreshold())
|
|
},
|
|
},
|
|
{
|
|
name: "enableUserInfoEndpoints success",
|
|
configMapData: map[string]string{
|
|
"oidc.config": `
|
|
enableUserInfoGroups: true
|
|
userInfoPath: "/userinfo"
|
|
userInfoBaseURL: "https://users.example.com"
|
|
userInfoCacheExpiration: "5m"
|
|
`,
|
|
},
|
|
testFunc: func(t *testing.T, settingsManager *SettingsManager) {
|
|
t.Helper()
|
|
settings, err := settingsManager.GetSettings()
|
|
require.NoError(t, err)
|
|
|
|
oidcConfig := settings.OIDCConfig()
|
|
assert.NotNil(t, oidcConfig)
|
|
|
|
assert.Equal(t, 5*time.Minute, settings.UserInfoCacheExpiration())
|
|
assert.Equal(t, "/userinfo", settings.UserInfoPath())
|
|
assert.Equal(t, "https://users.example.com", settings.UserInfoBaseURL())
|
|
assert.True(t, settings.UserInfoGroupsEnabled())
|
|
},
|
|
},
|
|
{
|
|
name: "userInfoBaseURL malformed url",
|
|
configMapData: map[string]string{
|
|
"oidc.config": `
|
|
userInfoBaseURL: "://users.example.com"
|
|
`,
|
|
},
|
|
testFunc: func(t *testing.T, settingsManager *SettingsManager) {
|
|
t.Helper()
|
|
settings, err := settingsManager.GetSettings()
|
|
require.NoError(t, err)
|
|
|
|
oidcConfig := settings.OIDCConfig()
|
|
assert.NotNil(t, oidcConfig)
|
|
|
|
assert.Empty(t, settings.UserInfoBaseURL())
|
|
},
|
|
},
|
|
}
|
|
for i := range testCases {
|
|
tc := testCases[i]
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
kubeClient := fake.NewClientset(
|
|
&corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDConfigMapName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: tc.configMapData,
|
|
},
|
|
&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDSecretName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"admin.password": nil,
|
|
"server.secretkey": nil,
|
|
},
|
|
},
|
|
)
|
|
settingsManager := NewSettingsManager(t.Context(), kubeClient, "default")
|
|
tc.testFunc(t, settingsManager)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRedirectURL(t *testing.T) {
|
|
cases := map[string][]string{
|
|
"https://localhost:4000": {"https://localhost:4000/auth/callback", "https://localhost:4000/api/dex/callback"},
|
|
"https://localhost:4000/": {"https://localhost:4000/auth/callback", "https://localhost:4000/api/dex/callback"},
|
|
"https://localhost:4000/argocd": {"https://localhost:4000/argocd/auth/callback", "https://localhost:4000/argocd/api/dex/callback"},
|
|
"https://localhost:4000/argocd/": {"https://localhost:4000/argocd/auth/callback", "https://localhost:4000/argocd/api/dex/callback"},
|
|
}
|
|
for given, expected := range cases {
|
|
settings := ArgoCDSettings{URL: given}
|
|
redirectURL, err := settings.RedirectURL()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, expected[0], redirectURL)
|
|
dexRedirectURL, err := settings.DexRedirectURL()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, expected[1], dexRedirectURL)
|
|
}
|
|
}
|
|
|
|
func Test_validateExternalURL(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
url string
|
|
errMsg string
|
|
}{
|
|
{name: "Valid URL", url: "https://my.domain.com"},
|
|
{name: "No URL - Valid", url: ""},
|
|
{name: "Invalid URL", url: "my.domain.com", errMsg: "URL must include http or https protocol"},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
err := ValidateExternalURL(tt.url)
|
|
if tt.errMsg != "" {
|
|
assert.EqualError(t, err, tt.errMsg)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetOIDCSecretTrim(t *testing.T) {
|
|
kubeClient := fake.NewClientset(
|
|
&corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDConfigMapName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: map[string]string{
|
|
"oidc.config": "\n name: Okta\n clientSecret: test-secret\r\n \n clientID: aaaabbbbccccddddeee\n",
|
|
},
|
|
},
|
|
&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDSecretName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"admin.password": nil,
|
|
"server.secretkey": nil,
|
|
},
|
|
},
|
|
)
|
|
settingsManager := NewSettingsManager(t.Context(), kubeClient, "default")
|
|
settings, err := settingsManager.GetSettings()
|
|
require.NoError(t, err)
|
|
|
|
oidcConfig := settings.OIDCConfig()
|
|
assert.NotNil(t, oidcConfig)
|
|
assert.Equal(t, "test-secret", oidcConfig.ClientSecret)
|
|
}
|
|
|
|
func getCNFromCertificate(cert *tls.Certificate) string {
|
|
c, err := x509.ParseCertificate(cert.Certificate[0])
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return c.Subject.CommonName
|
|
}
|
|
|
|
func Test_GetTLSConfiguration(t *testing.T) {
|
|
t.Run("Valid external TLS secret with success", func(t *testing.T) {
|
|
kubeClient := fake.NewClientset(
|
|
&corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDConfigMapName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: map[string]string{
|
|
"oidc.config": "\n name: Okta\n clientSecret: test-secret\r\n \n clientID: aaaabbbbccccddddeee\n",
|
|
},
|
|
},
|
|
&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDSecretName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"admin.password": nil,
|
|
"server.secretkey": nil,
|
|
},
|
|
},
|
|
&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: externalServerTLSSecretName,
|
|
Namespace: "default",
|
|
},
|
|
Data: map[string][]byte{
|
|
"tls.crt": []byte(testutil.MustLoadFileToString("../../test/fixture/certs/argocd-test-server.crt")),
|
|
"tls.key": []byte(testutil.MustLoadFileToString("../../test/fixture/certs/argocd-test-server.key")),
|
|
},
|
|
},
|
|
)
|
|
settingsManager := NewSettingsManager(t.Context(), kubeClient, "default")
|
|
settings, err := settingsManager.GetSettings()
|
|
require.NoError(t, err)
|
|
assert.True(t, settings.CertificateIsExternal)
|
|
assert.NotNil(t, settings.Certificate)
|
|
assert.Contains(t, getCNFromCertificate(settings.Certificate), "localhost")
|
|
})
|
|
|
|
t.Run("Valid external TLS secret overrides argocd-secret", func(t *testing.T) {
|
|
kubeClient := fake.NewClientset(
|
|
&corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDConfigMapName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: map[string]string{
|
|
"oidc.config": "\n name: Okta\n clientSecret: test-secret\r\n \n clientID: aaaabbbbccccddddeee\n",
|
|
},
|
|
},
|
|
&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDSecretName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"admin.password": nil,
|
|
"server.secretkey": nil,
|
|
"tls.crt": []byte(testutil.MustLoadFileToString("../../test/fixture/certs/argocd-e2e-server.crt")),
|
|
"tls.key": []byte(testutil.MustLoadFileToString("../../test/fixture/certs/argocd-e2e-server.key")),
|
|
},
|
|
},
|
|
&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: externalServerTLSSecretName,
|
|
Namespace: "default",
|
|
},
|
|
Data: map[string][]byte{
|
|
"tls.crt": []byte(testutil.MustLoadFileToString("../../test/fixture/certs/argocd-test-server.crt")),
|
|
"tls.key": []byte(testutil.MustLoadFileToString("../../test/fixture/certs/argocd-test-server.key")),
|
|
},
|
|
},
|
|
)
|
|
settingsManager := NewSettingsManager(t.Context(), kubeClient, "default")
|
|
settings, err := settingsManager.GetSettings()
|
|
require.NoError(t, err)
|
|
assert.True(t, settings.CertificateIsExternal)
|
|
assert.NotNil(t, settings.Certificate)
|
|
assert.Contains(t, getCNFromCertificate(settings.Certificate), "localhost")
|
|
})
|
|
t.Run("Invalid external TLS secret", func(t *testing.T) {
|
|
kubeClient := fake.NewClientset(
|
|
&corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDConfigMapName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: map[string]string{
|
|
"oidc.config": "\n name: Okta\n clientSecret: test-secret\r\n \n clientID: aaaabbbbccccddddeee\n",
|
|
},
|
|
},
|
|
&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDSecretName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"admin.password": nil,
|
|
"server.secretkey": nil,
|
|
},
|
|
},
|
|
&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: externalServerTLSSecretName,
|
|
Namespace: "default",
|
|
},
|
|
Data: map[string][]byte{
|
|
"tls.crt": []byte(""),
|
|
"tls.key": []byte(""),
|
|
},
|
|
},
|
|
)
|
|
settingsManager := NewSettingsManager(t.Context(), kubeClient, "default")
|
|
settings, err := settingsManager.GetSettings()
|
|
require.ErrorContains(t, err, "failed to find any PEM data in certificate input")
|
|
assert.NotNil(t, settings)
|
|
})
|
|
t.Run("Does not parse TLS cert key pair on cache hit", func(t *testing.T) {
|
|
cm := &corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDConfigMapName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
}
|
|
secret := &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDSecretName,
|
|
Namespace: "default",
|
|
ResourceVersion: "1",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"server.secretkey": nil,
|
|
},
|
|
}
|
|
tlsSecret := &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: externalServerTLSSecretName,
|
|
Namespace: "default",
|
|
ResourceVersion: "1",
|
|
},
|
|
Data: map[string][]byte{
|
|
"tls.crt": []byte(testutil.MustLoadFileToString("../../test/fixture/certs/argocd-test-server.crt")),
|
|
"tls.key": []byte(testutil.MustLoadFileToString("../../test/fixture/certs/argocd-test-server.key")),
|
|
},
|
|
}
|
|
|
|
kubeClient := fake.NewClientset(cm, secret, tlsSecret)
|
|
callCount := 0
|
|
settingsManager := NewSettingsManager(t.Context(), kubeClient, "default", func(mgr *SettingsManager) {
|
|
mgr.tlsCertParser = func(certpem []byte, keypem []byte) (tls.Certificate, error) {
|
|
callCount++
|
|
|
|
return tls.X509KeyPair(certpem, keypem)
|
|
}
|
|
})
|
|
|
|
// should not be called by initialization
|
|
assert.Equal(t, 0, callCount)
|
|
|
|
// should be called by first call to GetSettings
|
|
_, err := settingsManager.GetSettings()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, callCount)
|
|
|
|
// should not be called by subsequent call to GetSettings
|
|
_, err = settingsManager.GetSettings()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, callCount)
|
|
})
|
|
t.Run("Parses TLS cert key pair when TLS secret update causes cache miss", func(t *testing.T) {
|
|
cm := &corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDConfigMapName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
}
|
|
secret := &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDSecretName,
|
|
Namespace: "default",
|
|
ResourceVersion: "1",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"server.secretkey": nil,
|
|
},
|
|
}
|
|
tlsSecret := &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: externalServerTLSSecretName,
|
|
Namespace: "default",
|
|
ResourceVersion: "1",
|
|
},
|
|
Data: map[string][]byte{
|
|
"tls.crt": []byte(testutil.MustLoadFileToString("../../test/fixture/certs/argocd-test-server.crt")),
|
|
"tls.key": []byte(testutil.MustLoadFileToString("../../test/fixture/certs/argocd-test-server.key")),
|
|
},
|
|
}
|
|
|
|
kubeClient := fake.NewClientset(cm, secret, tlsSecret)
|
|
callCount := 0
|
|
settingsManager := NewSettingsManager(t.Context(), kubeClient, "default", func(mgr *SettingsManager) {
|
|
mgr.tlsCertParser = func(certpem []byte, keypem []byte) (tls.Certificate, error) {
|
|
callCount++
|
|
|
|
return tls.X509KeyPair(certpem, keypem)
|
|
}
|
|
})
|
|
|
|
// should be called by first call to GetSettings
|
|
settings, err := settingsManager.GetSettings()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 1, callCount)
|
|
assert.NotNil(t, settings.Certificate)
|
|
assert.True(t, settings.CertificateIsExternal)
|
|
assert.Equal(t, "localhost", getCNFromCertificate(settings.Certificate))
|
|
|
|
// update secret
|
|
tlsSecret.SetResourceVersion("2")
|
|
_, err = kubeClient.CoreV1().Secrets("default").Update(t.Context(), tlsSecret, metav1.UpdateOptions{})
|
|
require.NoError(t, err)
|
|
|
|
// allow time for the update to resolve to avoid timing issues below
|
|
time.Sleep(250 * time.Millisecond)
|
|
|
|
// should be called again after secret update resolves
|
|
settings, err = settingsManager.GetSettings()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 2, callCount)
|
|
assert.NotNil(t, settings.Certificate)
|
|
assert.True(t, settings.CertificateIsExternal)
|
|
assert.Equal(t, "localhost", getCNFromCertificate(settings.Certificate))
|
|
})
|
|
t.Run("Overrides cached internal TLS cert when external TLS secret added", func(t *testing.T) {
|
|
kubeClient := fake.NewClientset(
|
|
&corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDConfigMapName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
},
|
|
&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDSecretName,
|
|
Namespace: "default",
|
|
ResourceVersion: "1",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"server.secretkey": nil,
|
|
"tls.crt": []byte(testutil.MustLoadFileToString("../../test/fixture/certs/argocd-e2e-server.crt")),
|
|
"tls.key": []byte(testutil.MustLoadFileToString("../../test/fixture/certs/argocd-e2e-server.key")),
|
|
},
|
|
},
|
|
)
|
|
settingsManager := NewSettingsManager(t.Context(), kubeClient, "default")
|
|
settings, err := settingsManager.GetSettings()
|
|
require.NoError(t, err)
|
|
// should have internal cert at this point
|
|
assert.NotNil(t, settings.Certificate)
|
|
assert.False(t, settings.CertificateIsExternal)
|
|
assert.Equal(t, "argocd-e2e-server", getCNFromCertificate(settings.Certificate))
|
|
|
|
externalTLSSecret := &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: externalServerTLSSecretName,
|
|
Namespace: "default",
|
|
ResourceVersion: "1",
|
|
},
|
|
Data: map[string][]byte{
|
|
"tls.crt": []byte(testutil.MustLoadFileToString("../../test/fixture/certs/argocd-test-server.crt")),
|
|
"tls.key": []byte(testutil.MustLoadFileToString("../../test/fixture/certs/argocd-test-server.key")),
|
|
},
|
|
}
|
|
_, err = kubeClient.CoreV1().Secrets("default").Create(t.Context(), externalTLSSecret, metav1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
|
|
// allow time for the create to resolve to avoid timing issues below
|
|
time.Sleep(250 * time.Millisecond)
|
|
|
|
settings, err = settingsManager.GetSettings()
|
|
require.NoError(t, err)
|
|
// should now have an external cert
|
|
assert.NotNil(t, settings.Certificate)
|
|
assert.True(t, settings.CertificateIsExternal)
|
|
assert.Equal(t, "localhost", getCNFromCertificate(settings.Certificate))
|
|
})
|
|
t.Run("Falls back to internal TLS cert when external TLS secret deleted", func(t *testing.T) {
|
|
kubeClient := fake.NewClientset(
|
|
&corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDConfigMapName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
},
|
|
&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDSecretName,
|
|
Namespace: "default",
|
|
ResourceVersion: "1",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"server.secretkey": nil,
|
|
"tls.crt": []byte(testutil.MustLoadFileToString("../../test/fixture/certs/argocd-e2e-server.crt")),
|
|
"tls.key": []byte(testutil.MustLoadFileToString("../../test/fixture/certs/argocd-e2e-server.key")),
|
|
},
|
|
},
|
|
&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: externalServerTLSSecretName,
|
|
Namespace: "default",
|
|
ResourceVersion: "1",
|
|
},
|
|
Data: map[string][]byte{
|
|
"tls.crt": []byte(testutil.MustLoadFileToString("../../test/fixture/certs/argocd-test-server.crt")),
|
|
"tls.key": []byte(testutil.MustLoadFileToString("../../test/fixture/certs/argocd-test-server.key")),
|
|
},
|
|
},
|
|
)
|
|
settingsManager := NewSettingsManager(t.Context(), kubeClient, "default")
|
|
settings, err := settingsManager.GetSettings()
|
|
require.NoError(t, err)
|
|
// should have external cert at this point
|
|
assert.NotNil(t, settings.Certificate)
|
|
assert.True(t, settings.CertificateIsExternal)
|
|
assert.Equal(t, "localhost", getCNFromCertificate(settings.Certificate))
|
|
|
|
err = kubeClient.CoreV1().Secrets("default").Delete(t.Context(), externalServerTLSSecretName, metav1.DeleteOptions{})
|
|
require.NoError(t, err)
|
|
|
|
// allow time for the delete to resolve to avoid timing issues below
|
|
time.Sleep(250 * time.Millisecond)
|
|
|
|
settings, err = settingsManager.GetSettings()
|
|
require.NoError(t, err)
|
|
// should now have an internal cert
|
|
assert.NotNil(t, settings.Certificate)
|
|
assert.False(t, settings.CertificateIsExternal)
|
|
assert.Equal(t, "argocd-e2e-server", getCNFromCertificate(settings.Certificate))
|
|
})
|
|
t.Run("No external TLS secret", func(t *testing.T) {
|
|
kubeClient := fake.NewClientset(
|
|
&corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDConfigMapName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: map[string]string{
|
|
"oidc.config": "\n name: Okta\n clientSecret: test-secret\r\n \n clientID: aaaabbbbccccddddeee\n",
|
|
},
|
|
},
|
|
&corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDSecretName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"admin.password": nil,
|
|
"server.secretkey": nil,
|
|
"tls.crt": []byte(testutil.MustLoadFileToString("../../test/fixture/certs/argocd-e2e-server.crt")),
|
|
"tls.key": []byte(testutil.MustLoadFileToString("../../test/fixture/certs/argocd-e2e-server.key")),
|
|
},
|
|
},
|
|
)
|
|
settingsManager := NewSettingsManager(t.Context(), kubeClient, "default")
|
|
settings, err := settingsManager.GetSettings()
|
|
require.NoError(t, err)
|
|
assert.False(t, settings.CertificateIsExternal)
|
|
assert.NotNil(t, settings.Certificate)
|
|
assert.Contains(t, getCNFromCertificate(settings.Certificate), "argocd-e2e-server")
|
|
})
|
|
}
|
|
|
|
func TestDownloadArgoCDBinaryUrls(t *testing.T) {
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{
|
|
"help.download.darwin-amd64": "some-url",
|
|
})
|
|
argoCDCM, err := settingsManager.getConfigMap()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "some-url", argoCDCM.Data["help.download.darwin-amd64"])
|
|
|
|
_, settingsManager = fixtures(t.Context(), map[string]string{
|
|
"help.download.linux-s390x": "some-url",
|
|
})
|
|
argoCDCM, err = settingsManager.getConfigMap()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "some-url", argoCDCM.Data["help.download.linux-s390x"])
|
|
|
|
_, settingsManager = fixtures(t.Context(), map[string]string{
|
|
"help.download.unsupported": "some-url",
|
|
})
|
|
argoCDCM, err = settingsManager.getConfigMap()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "some-url", argoCDCM.Data["help.download.unsupported"])
|
|
}
|
|
|
|
func TestSecretKeyRef(t *testing.T) {
|
|
data := map[string]string{
|
|
"oidc.config": `name: Okta
|
|
issuer: $ext:issuerSecret
|
|
clientID: aaaabbbbccccddddeee
|
|
clientSecret: $ext:clientSecret
|
|
# Optional set of OIDC scopes to request. If omitted, defaults to: ["openid", "profile", "email", "groups"]
|
|
requestedScopes: ["openid", "profile", "email"]
|
|
# Optional set of OIDC claims to request on the ID token.
|
|
requestedIDTokenClaims: {"groups": {"essential": true}}`,
|
|
}
|
|
cm := &corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDConfigMapName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: data,
|
|
}
|
|
argocdSecret := &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDSecretName,
|
|
Namespace: "default",
|
|
},
|
|
Data: map[string][]byte{
|
|
"admin.password": nil,
|
|
"server.secretkey": nil,
|
|
"webhook.github.secret": []byte("$ext:webhook.github.secret"),
|
|
},
|
|
}
|
|
secret := &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "ext",
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"issuerSecret": []byte("https://dev-123456.oktapreview.com"),
|
|
"clientSecret": []byte("deadbeef"),
|
|
"webhook.github.secret": []byte("mywebhooksecret"),
|
|
},
|
|
}
|
|
kubeClient := fake.NewClientset(cm, secret, argocdSecret)
|
|
settingsManager := NewSettingsManager(t.Context(), kubeClient, "default")
|
|
|
|
settings, err := settingsManager.GetSettings()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "mywebhooksecret", settings.GetWebhookGitHubSecret())
|
|
|
|
oidcConfig := settings.OIDCConfig()
|
|
assert.Equal(t, "https://dev-123456.oktapreview.com", oidcConfig.Issuer)
|
|
assert.Equal(t, "deadbeef", oidcConfig.ClientSecret)
|
|
}
|
|
|
|
func TestGetEnableManifestGeneration(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
enabled bool
|
|
data map[string]string
|
|
source string
|
|
}{{
|
|
name: "default",
|
|
enabled: true,
|
|
data: map[string]string{},
|
|
source: string(v1alpha1.ApplicationSourceTypeKustomize),
|
|
}, {
|
|
name: "disabled",
|
|
enabled: false,
|
|
data: map[string]string{"kustomize.enable": `false`},
|
|
source: string(v1alpha1.ApplicationSourceTypeKustomize),
|
|
}, {
|
|
name: "enabled",
|
|
enabled: true,
|
|
data: map[string]string{"kustomize.enable": `true`},
|
|
source: string(v1alpha1.ApplicationSourceTypeKustomize),
|
|
}}
|
|
for i := range testCases {
|
|
tc := testCases[i]
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
cm := &corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDConfigMapName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: tc.data,
|
|
}
|
|
argocdSecret := &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDSecretName,
|
|
Namespace: "default",
|
|
},
|
|
Data: map[string][]byte{
|
|
"admin.password": nil,
|
|
"server.secretkey": nil,
|
|
},
|
|
}
|
|
|
|
kubeClient := fake.NewClientset(cm, argocdSecret)
|
|
settingsManager := NewSettingsManager(t.Context(), kubeClient, "default")
|
|
|
|
enableManifestGeneration, err := settingsManager.GetEnabledSourceTypes()
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, enableManifestGeneration[tc.source], tc.enabled)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetHelmSettings(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
data map[string]string
|
|
expected []string
|
|
}{{
|
|
name: "Default",
|
|
data: map[string]string{},
|
|
expected: []string{"http", "https"},
|
|
}, {
|
|
name: "Configured Not Empty",
|
|
data: map[string]string{
|
|
"helm.valuesFileSchemes": "s3, git",
|
|
},
|
|
expected: []string{"s3", "git"},
|
|
}, {
|
|
name: "Configured Empty",
|
|
data: map[string]string{
|
|
"helm.valuesFileSchemes": "",
|
|
},
|
|
expected: nil,
|
|
}}
|
|
|
|
for i := range testCases {
|
|
tc := testCases[i]
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
cm := &corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDConfigMapName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: tc.data,
|
|
}
|
|
argocdSecret := &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDSecretName,
|
|
Namespace: "default",
|
|
},
|
|
Data: map[string][]byte{
|
|
"admin.password": nil,
|
|
"server.secretkey": nil,
|
|
},
|
|
}
|
|
secret := &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "acme",
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"clientSecret": []byte("deadbeef"),
|
|
},
|
|
}
|
|
kubeClient := fake.NewClientset(cm, secret, argocdSecret)
|
|
settingsManager := NewSettingsManager(t.Context(), kubeClient, "default")
|
|
|
|
helmSettings, err := settingsManager.GetHelmSettings()
|
|
require.NoError(t, err)
|
|
|
|
assert.ElementsMatch(t, tc.expected, helmSettings.ValuesFileSchemes)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestArgoCDSettings_OIDCTLSConfig_OIDCTLSInsecureSkipVerify(t *testing.T) {
|
|
certParsed, err := tls.X509KeyPair(test.Cert, test.PrivateKey)
|
|
require.NoError(t, err)
|
|
|
|
testCases := []struct {
|
|
name string
|
|
settings *ArgoCDSettings
|
|
expectNilTLSConfig bool
|
|
}{
|
|
{
|
|
name: "OIDC configured, no root CA",
|
|
settings: &ArgoCDSettings{OIDCConfigRAW: `name: Test
|
|
issuer: aaa
|
|
clientID: xxx
|
|
clientSecret: yyy
|
|
requestedScopes: ["oidc"]`},
|
|
},
|
|
{
|
|
name: "OIDC configured, valid root CA",
|
|
settings: &ArgoCDSettings{OIDCConfigRAW: fmt.Sprintf(`
|
|
name: Test
|
|
issuer: aaa
|
|
clientID: xxx
|
|
clientSecret: yyy
|
|
requestedScopes: ["oidc"]
|
|
rootCA: |
|
|
%s
|
|
`, strings.ReplaceAll(string(test.Cert), "\n", "\n "))},
|
|
},
|
|
{
|
|
name: "OIDC configured, invalid root CA",
|
|
settings: &ArgoCDSettings{OIDCConfigRAW: `name: Test
|
|
issuer: aaa
|
|
clientID: xxx
|
|
clientSecret: yyy
|
|
requestedScopes: ["oidc"]
|
|
rootCA: "invalid"`},
|
|
},
|
|
{
|
|
name: "OIDC not configured, no cert configured",
|
|
settings: &ArgoCDSettings{},
|
|
expectNilTLSConfig: true,
|
|
},
|
|
{
|
|
name: "OIDC not configured, cert configured",
|
|
settings: &ArgoCDSettings{Certificate: &certParsed},
|
|
},
|
|
}
|
|
|
|
for _, testCase := range testCases {
|
|
t.Run(testCase.name, func(t *testing.T) {
|
|
if testCase.expectNilTLSConfig {
|
|
assert.Nil(t, testCase.settings.OIDCTLSConfig())
|
|
} else {
|
|
assert.False(t, testCase.settings.OIDCTLSConfig().InsecureSkipVerify)
|
|
|
|
testCase.settings.OIDCTLSInsecureSkipVerify = true
|
|
|
|
assert.True(t, testCase.settings.OIDCTLSConfig().InsecureSkipVerify)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_OAuth2AllowedAudiences(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
testCases := []struct {
|
|
name string
|
|
settings *ArgoCDSettings
|
|
expected []string
|
|
}{
|
|
{
|
|
name: "Empty",
|
|
settings: &ArgoCDSettings{},
|
|
expected: []string{},
|
|
},
|
|
{
|
|
name: "OIDC configured, no audiences specified, clientID used",
|
|
settings: &ArgoCDSettings{OIDCConfigRAW: `name: Test
|
|
issuer: aaa
|
|
clientID: xxx
|
|
clientSecret: yyy
|
|
requestedScopes: ["oidc"]`},
|
|
expected: []string{"xxx"},
|
|
},
|
|
{
|
|
name: "OIDC configured, no audiences specified, clientID and cliClientID used",
|
|
settings: &ArgoCDSettings{OIDCConfigRAW: `name: Test
|
|
issuer: aaa
|
|
clientID: xxx
|
|
cliClientID: cli-xxx
|
|
clientSecret: yyy
|
|
requestedScopes: ["oidc"]`},
|
|
expected: []string{"xxx", "cli-xxx"},
|
|
},
|
|
{
|
|
name: "OIDC configured, audiences specified",
|
|
settings: &ArgoCDSettings{OIDCConfigRAW: `name: Test
|
|
issuer: aaa
|
|
clientID: xxx
|
|
clientSecret: yyy
|
|
requestedScopes: ["oidc"]
|
|
allowedAudiences: ["aud1", "aud2"]`},
|
|
expected: []string{"aud1", "aud2"},
|
|
},
|
|
{
|
|
name: "Dex configured",
|
|
settings: &ArgoCDSettings{DexConfig: `connectors:
|
|
- type: github
|
|
id: github
|
|
name: GitHub
|
|
config:
|
|
clientID: aabbccddeeff00112233
|
|
clientSecret: $dex.github.clientSecret
|
|
orgs:
|
|
- name: your-github-org
|
|
`},
|
|
expected: []string{common.ArgoCDClientAppID, common.ArgoCDCLIClientAppID},
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
tcc := tc
|
|
t.Run(tcc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
assert.ElementsMatch(t, tcc.expected, tcc.settings.OAuth2AllowedAudiences())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestReplaceStringSecret(t *testing.T) {
|
|
secretValues := map[string]string{"my-secret-key": "my-secret-value"}
|
|
result := ReplaceStringSecret("$my-secret-key", secretValues)
|
|
assert.Equal(t, "my-secret-value", result)
|
|
|
|
result = ReplaceStringSecret("$invalid-secret-key", secretValues)
|
|
assert.Equal(t, "$invalid-secret-key", result)
|
|
|
|
result = ReplaceStringSecret("", secretValues)
|
|
assert.Empty(t, result)
|
|
|
|
result = ReplaceStringSecret("my-value", secretValues)
|
|
assert.Equal(t, "my-value", result)
|
|
}
|
|
|
|
func TestRedirectURLForRequest(t *testing.T) {
|
|
testCases := []struct {
|
|
Name string
|
|
Settings *ArgoCDSettings
|
|
RequestURL string
|
|
ExpectedURL string
|
|
ExpectError bool
|
|
}{
|
|
{
|
|
Name: "Single URL",
|
|
Settings: &ArgoCDSettings{
|
|
URL: "https://example.org",
|
|
},
|
|
RequestURL: "https://example.org/login",
|
|
ExpectedURL: "https://example.org/auth/callback",
|
|
ExpectError: false,
|
|
},
|
|
{
|
|
Name: "Request does not match configured URL.",
|
|
Settings: &ArgoCDSettings{
|
|
URL: "https://otherhost.org",
|
|
},
|
|
RequestURL: "https://example.org/login",
|
|
ExpectedURL: "https://otherhost.org/auth/callback",
|
|
ExpectError: false,
|
|
},
|
|
{
|
|
Name: "Cannot parse URL.",
|
|
Settings: &ArgoCDSettings{
|
|
URL: ":httpsotherhostorg",
|
|
},
|
|
RequestURL: "https://example.org/login",
|
|
ExpectedURL: "",
|
|
ExpectError: true,
|
|
},
|
|
{
|
|
Name: "Match extended URL in settings.URL.",
|
|
Settings: &ArgoCDSettings{
|
|
URL: "https://otherhost.org",
|
|
AdditionalURLs: []string{"https://anotherhost.org"},
|
|
},
|
|
RequestURL: "https://anotherhost.org/login",
|
|
ExpectedURL: "https://anotherhost.org/auth/callback",
|
|
ExpectError: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
request, err := http.NewRequestWithContext(t.Context(), http.MethodPost, tc.RequestURL, http.NoBody)
|
|
require.NoError(t, err)
|
|
result, err := tc.Settings.RedirectURLForRequest(request)
|
|
assert.Equal(t, tc.ExpectedURL, result)
|
|
if tc.ExpectError {
|
|
assert.Error(t, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRedirectAdditionalURLs(t *testing.T) {
|
|
testCases := []struct {
|
|
Name string
|
|
Settings *ArgoCDSettings
|
|
ExpectedResult []string
|
|
ExpectedError bool
|
|
}{
|
|
{
|
|
Name: "Good case with two AdditionalURLs",
|
|
Settings: &ArgoCDSettings{
|
|
URL: "https://example.org",
|
|
AdditionalURLs: []string{"https://anotherhost.org", "https://yetanother.org"},
|
|
},
|
|
ExpectedResult: []string{
|
|
"https://anotherhost.org/auth/callback",
|
|
"https://yetanother.org/auth/callback",
|
|
},
|
|
ExpectedError: false,
|
|
},
|
|
{
|
|
Name: "Bad URL causes error",
|
|
Settings: &ArgoCDSettings{
|
|
URL: "https://example.org",
|
|
AdditionalURLs: []string{":httpsotherhostorg"},
|
|
},
|
|
ExpectedResult: []string{},
|
|
ExpectedError: true,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
result, err := tc.Settings.RedirectAdditionalURLs()
|
|
if tc.ExpectedError {
|
|
require.Error(t, err)
|
|
} else {
|
|
require.NoError(t, err)
|
|
}
|
|
require.Equal(t, tc.ExpectedResult, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestUseAzureWorkloadIdentity(t *testing.T) {
|
|
testCases := []struct {
|
|
Name string
|
|
Settings *ArgoCDSettings
|
|
ExpectedResult bool
|
|
}{
|
|
{
|
|
Name: "UseAzureWorkloadIdentity defined and set to true",
|
|
Settings: &ArgoCDSettings{
|
|
OIDCConfigRAW: "{ \"azure\": {\"useWorkloadIdentity\": true }}",
|
|
},
|
|
ExpectedResult: true,
|
|
},
|
|
{
|
|
Name: "UseAzureWorkloadIdentity defined and set to false",
|
|
Settings: &ArgoCDSettings{
|
|
OIDCConfigRAW: "{ \"azure\": {\"useWorkloadIdentity\": false }}",
|
|
},
|
|
ExpectedResult: false,
|
|
},
|
|
{
|
|
Name: "UseAzureWorkloadIdentity not defined, with azure key present",
|
|
Settings: &ArgoCDSettings{
|
|
OIDCConfigRAW: "{ \"azure\": {}}",
|
|
},
|
|
ExpectedResult: false,
|
|
},
|
|
{
|
|
Name: "UseAzureWorkloadIdentity not defined",
|
|
Settings: &ArgoCDSettings{
|
|
OIDCConfigRAW: "{}",
|
|
},
|
|
ExpectedResult: false,
|
|
},
|
|
{
|
|
Name: "OIDC config isnot defined",
|
|
Settings: &ArgoCDSettings{},
|
|
ExpectedResult: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
result := tc.Settings.UseAzureWorkloadIdentity()
|
|
require.Equal(t, tc.ExpectedResult, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAzureUserGroupOverageClaimEnabled(t *testing.T) {
|
|
testCases := []struct {
|
|
Name string
|
|
Settings *ArgoCDSettings
|
|
ExpectedResult bool
|
|
}{
|
|
{
|
|
Name: "enabled and set to true",
|
|
Settings: &ArgoCDSettings{
|
|
OIDCConfigRAW: `{"azure": {"enableUserGroupOverageClaim": true}}`,
|
|
},
|
|
ExpectedResult: true,
|
|
},
|
|
{
|
|
Name: "enabled and set to false",
|
|
Settings: &ArgoCDSettings{
|
|
OIDCConfigRAW: `{"azure": {"enableUserGroupOverageClaim": false}}`,
|
|
},
|
|
ExpectedResult: false,
|
|
},
|
|
{
|
|
Name: "not defined with azure key present",
|
|
Settings: &ArgoCDSettings{
|
|
OIDCConfigRAW: `{"azure": {}}`,
|
|
},
|
|
ExpectedResult: false,
|
|
},
|
|
{
|
|
Name: "not defined",
|
|
Settings: &ArgoCDSettings{
|
|
OIDCConfigRAW: `{}`,
|
|
},
|
|
ExpectedResult: false,
|
|
},
|
|
{
|
|
Name: "OIDC config not defined",
|
|
Settings: &ArgoCDSettings{},
|
|
ExpectedResult: false,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
result := tc.Settings.AzureUserGroupOverageClaimEnabled()
|
|
require.Equal(t, tc.ExpectedResult, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAzureGraphAPIEndpoint(t *testing.T) {
|
|
testCases := []struct {
|
|
Name string
|
|
Settings *ArgoCDSettings
|
|
ExpectedResult string
|
|
}{
|
|
{
|
|
Name: "default endpoint when azure section present",
|
|
Settings: &ArgoCDSettings{
|
|
OIDCConfigRAW: `{"azure": {}}`,
|
|
},
|
|
ExpectedResult: "https://graph.microsoft.com/v1.0",
|
|
},
|
|
{
|
|
Name: "custom endpoint for sovereign cloud",
|
|
Settings: &ArgoCDSettings{
|
|
OIDCConfigRAW: `{"azure": {"graphApiEndpoint": "https://graph.microsoft.us/v1.0"}}`,
|
|
},
|
|
ExpectedResult: "https://graph.microsoft.us/v1.0",
|
|
},
|
|
{
|
|
Name: "custom endpoint for China cloud",
|
|
Settings: &ArgoCDSettings{
|
|
OIDCConfigRAW: `{"azure": {"graphApiEndpoint": "https://microsoftgraph.chinacloudapi.cn/v1.0"}}`,
|
|
},
|
|
ExpectedResult: "https://microsoftgraph.chinacloudapi.cn/v1.0",
|
|
},
|
|
{
|
|
Name: "non-https endpoint returns empty string",
|
|
Settings: &ArgoCDSettings{
|
|
OIDCConfigRAW: `{"azure": {"graphApiEndpoint": "http://graph.microsoft.com/v1.0"}}`,
|
|
},
|
|
ExpectedResult: "",
|
|
},
|
|
{
|
|
Name: "non-Microsoft hostname returns empty string",
|
|
Settings: &ArgoCDSettings{
|
|
OIDCConfigRAW: `{"azure": {"graphApiEndpoint": "https://evil.example.com/v1.0"}}`,
|
|
},
|
|
ExpectedResult: "",
|
|
},
|
|
{
|
|
Name: "empty string when no azure section",
|
|
Settings: &ArgoCDSettings{
|
|
OIDCConfigRAW: `{}`,
|
|
},
|
|
ExpectedResult: "",
|
|
},
|
|
{
|
|
Name: "empty string when no OIDC config",
|
|
Settings: &ArgoCDSettings{},
|
|
ExpectedResult: "",
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
result := tc.Settings.AzureGraphAPIEndpoint()
|
|
require.Equal(t, tc.ExpectedResult, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAzureUserGroupOverageClaimCacheExpiration(t *testing.T) {
|
|
testCases := []struct {
|
|
Name string
|
|
Settings *ArgoCDSettings
|
|
ExpectedResult time.Duration
|
|
}{
|
|
{
|
|
Name: "valid duration",
|
|
Settings: &ArgoCDSettings{
|
|
OIDCConfigRAW: `{"azure": {"userGroupOverageClaimCacheExpiration": "10m"}}`,
|
|
},
|
|
ExpectedResult: 10 * time.Minute,
|
|
},
|
|
{
|
|
Name: "not configured",
|
|
Settings: &ArgoCDSettings{
|
|
OIDCConfigRAW: `{"azure": {}}`,
|
|
},
|
|
ExpectedResult: 0,
|
|
},
|
|
{
|
|
Name: "invalid duration returns 0",
|
|
Settings: &ArgoCDSettings{
|
|
OIDCConfigRAW: `{"azure": {"userGroupOverageClaimCacheExpiration": "invalid"}}`,
|
|
},
|
|
ExpectedResult: 0,
|
|
},
|
|
{
|
|
Name: "no OIDC config",
|
|
Settings: &ArgoCDSettings{},
|
|
ExpectedResult: 0,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.Name, func(t *testing.T) {
|
|
result := tc.Settings.AzureUserGroupOverageClaimCacheExpiration()
|
|
require.Equal(t, tc.ExpectedResult, result)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsImpersonationEnabled(t *testing.T) {
|
|
// When there is no argocd-cm itself,
|
|
// Then IsImpersonationEnabled() must return false (default value) and an error with appropriate error message.
|
|
kubeClient := fake.NewClientset()
|
|
settingsManager := NewSettingsManager(t.Context(), kubeClient, "default")
|
|
featureFlag, err := settingsManager.IsImpersonationEnabled()
|
|
require.False(t, featureFlag,
|
|
"with no argocd-cm config map, IsImpersonationEnabled() must return false (default value)")
|
|
require.ErrorContains(t, err, "configmap \"argocd-cm\" not found",
|
|
"with no argocd-cm config map, IsImpersonationEnabled() must return an error")
|
|
|
|
// When there is no impersonation feature flag present in the argocd-cm,
|
|
// Then IsImpersonationEnabled() must return false (default value) and nil error.
|
|
_, settingsManager = fixtures(t.Context(), map[string]string{})
|
|
featureFlag, err = settingsManager.IsImpersonationEnabled()
|
|
require.False(t, featureFlag,
|
|
"with empty argocd-cm config map, IsImpersonationEnabled() must return false (default value)")
|
|
require.NoError(t, err,
|
|
"with empty argocd-cm config map, IsImpersonationEnabled() must not return any error")
|
|
|
|
// When user disables the feature explicitly,
|
|
// Then IsImpersonationEnabled() must return false and nil error.
|
|
_, settingsManager = fixtures(t.Context(), map[string]string{
|
|
"application.sync.impersonation.enabled": "false",
|
|
})
|
|
featureFlag, err = settingsManager.IsImpersonationEnabled()
|
|
require.False(t, featureFlag,
|
|
"when user enables the flag in argocd-cm config map, IsImpersonationEnabled() must return user set value")
|
|
require.NoError(t, err,
|
|
"when user enables the flag in argocd-cm config map, IsImpersonationEnabled() must not return any error")
|
|
|
|
// When user enables the feature explicitly,
|
|
// Then IsImpersonationEnabled() must return true and nil error.
|
|
_, settingsManager = fixtures(t.Context(), map[string]string{
|
|
"application.sync.impersonation.enabled": "true",
|
|
})
|
|
featureFlag, err = settingsManager.IsImpersonationEnabled()
|
|
require.True(t, featureFlag,
|
|
"when user enables the flag in argocd-cm config map, IsImpersonationEnabled() must return user set value")
|
|
require.NoError(t, err,
|
|
"when user enables the flag in argocd-cm config map, IsImpersonationEnabled() must not return any error")
|
|
}
|
|
|
|
func TestIsImpersonationEnforced(t *testing.T) {
|
|
// When there is no argocd-cm itself,
|
|
// Then IsImpersonationEnforced() must return true (default value) and an error with appropriate error message.
|
|
kubeClient := fake.NewClientset()
|
|
settingsManager := NewSettingsManager(t.Context(), kubeClient, "default")
|
|
enforced, err := settingsManager.IsImpersonationEnforced()
|
|
require.True(t, enforced,
|
|
"with no argocd-cm config map, IsImpersonationEnforced() must return true (default value)")
|
|
require.ErrorContains(t, err, "configmap \"argocd-cm\" not found",
|
|
"with no argocd-cm config map, IsImpersonationEnforced() must return an error")
|
|
|
|
// When there is no enforcement flag present in the argocd-cm,
|
|
// Then IsImpersonationEnforced() must return true (default value) and nil error.
|
|
_, settingsManager = fixtures(t.Context(), map[string]string{})
|
|
enforced, err = settingsManager.IsImpersonationEnforced()
|
|
require.True(t, enforced,
|
|
"with empty argocd-cm config map, IsImpersonationEnforced() must return true (default value)")
|
|
require.NoError(t, err,
|
|
"with empty argocd-cm config map, IsImpersonationEnforced() must not return any error")
|
|
|
|
// When user disables enforcement explicitly,
|
|
// Then IsImpersonationEnforced() must return false and nil error.
|
|
_, settingsManager = fixtures(t.Context(), map[string]string{
|
|
"application.sync.impersonation.enforced": "false",
|
|
})
|
|
enforced, err = settingsManager.IsImpersonationEnforced()
|
|
require.False(t, enforced,
|
|
"when user disables enforcement in argocd-cm config map, IsImpersonationEnforced() must return false")
|
|
require.NoError(t, err,
|
|
"when user disables enforcement in argocd-cm config map, IsImpersonationEnforced() must not return any error")
|
|
|
|
// When user enables enforcement explicitly,
|
|
// Then IsImpersonationEnforced() must return true and nil error.
|
|
_, settingsManager = fixtures(t.Context(), map[string]string{
|
|
"application.sync.impersonation.enforced": "true",
|
|
})
|
|
enforced, err = settingsManager.IsImpersonationEnforced()
|
|
require.True(t, enforced,
|
|
"when user enables enforcement in argocd-cm config map, IsImpersonationEnforced() must return true")
|
|
require.NoError(t, err,
|
|
"when user enables enforcement in argocd-cm config map, IsImpersonationEnforced() must not return any error")
|
|
|
|
// When user sets enforcement to an invalid value,
|
|
// Then IsImpersonationEnforced() must return true (default value) and nil error.
|
|
_, settingsManager = fixtures(t.Context(), map[string]string{
|
|
"application.sync.impersonation.enforced": "something",
|
|
})
|
|
enforced, err = settingsManager.IsImpersonationEnforced()
|
|
require.True(t, enforced,
|
|
"when user specify invalid value for enforcement in argocd-cm config map, IsImpersonationEnforced() must return true (default value)")
|
|
require.NoError(t, err,
|
|
"when user specify invalid value for enforcement in argocd-cm config map, IsImpersonationEnforced() must not return any error")
|
|
}
|
|
|
|
func TestIsInClusterEnabled(t *testing.T) {
|
|
// When there is no argocd-cm itself,
|
|
// Then IsInClusterEnabled() must return true (default value) and an error with appropriate error message.
|
|
kubeClient := fake.NewClientset()
|
|
settingsManager := NewSettingsManager(t.Context(), kubeClient, "default")
|
|
enabled, err := settingsManager.IsInClusterEnabled()
|
|
require.True(t, enabled,
|
|
"with no argocd-cm config map, IsInClusterEnabled() must return true (default value)")
|
|
require.ErrorContains(t, err, "configmap \"argocd-cm\" not found",
|
|
"with no argocd-cm config map, IsInClusterEnabled() must return an error")
|
|
|
|
// When there is no in-cluster flag present in the argocd-cm,
|
|
// Then IsInClusterEnabled() must return true (default value) and nil error.
|
|
_, settingsManager = fixtures(t.Context(), map[string]string{})
|
|
enabled, err = settingsManager.IsInClusterEnabled()
|
|
require.True(t, enabled,
|
|
"with empty argocd-cm config map, IsInClusterEnabled() must return true (default value)")
|
|
require.NoError(t, err,
|
|
"with empty argocd-cm config map, IsInClusterEnabled() must not return any error")
|
|
|
|
// When user disables in-cluster explicitly,
|
|
// Then IsInClusterEnabled() must return false and nil error.
|
|
_, settingsManager = fixtures(t.Context(), map[string]string{
|
|
"cluster.inClusterEnabled": "false",
|
|
})
|
|
enabled, err = settingsManager.IsInClusterEnabled()
|
|
require.False(t, enabled,
|
|
"when user sets the flag to false in argocd-cm config map, IsInClusterEnabled() must return false")
|
|
require.NoError(t, err,
|
|
"when user sets the flag to false in argocd-cm config map, IsInClusterEnabled() must not return any error")
|
|
|
|
// When user enables in-cluster explicitly,
|
|
// Then IsInClusterEnabled() must return true and nil error.
|
|
_, settingsManager = fixtures(t.Context(), map[string]string{
|
|
"cluster.inClusterEnabled": "true",
|
|
})
|
|
enabled, err = settingsManager.IsInClusterEnabled()
|
|
require.True(t, enabled,
|
|
"when user sets the flag to true in argocd-cm config map, IsInClusterEnabled() must return true")
|
|
require.NoError(t, err,
|
|
"when user sets the flag to true in argocd-cm config map, IsInClusterEnabled() must not return any error")
|
|
}
|
|
|
|
func TestRequireOverridePrivilegeForRevisionSyncNoConfigMap(t *testing.T) {
|
|
// When there is no argocd-cm itself,
|
|
// Then RequireOverridePrivilegeForRevisionSync() must return false (default value) and an error with appropriate error message.
|
|
kubeClient := fake.NewClientset()
|
|
settingsManager := NewSettingsManager(t.Context(), kubeClient, "default")
|
|
featureFlag, err := settingsManager.RequireOverridePrivilegeForRevisionSync()
|
|
require.False(t, featureFlag,
|
|
"with no argocd-cm config map, RequireOverridePrivilegeForRevisionSync() must return false (default value)")
|
|
require.ErrorContains(t, err, "configmap \"argocd-cm\" not found",
|
|
"with no argocd-cm config map, RequireOverridePrivilegeForRevisionSync() must return an error")
|
|
}
|
|
|
|
func TestRequireOverridePrivilegeForRevisionSyncEmptyConfigMap(t *testing.T) {
|
|
// When there is no feature flag present in the argocd-cm,
|
|
// Then RequireOverridePrivilegeForRevisionSync() must return false (default value) and nil error.
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{})
|
|
featureFlag, err := settingsManager.RequireOverridePrivilegeForRevisionSync()
|
|
require.False(t, featureFlag,
|
|
"with empty argocd-cm config map, RequireOverridePrivilegeForRevisionSync() must return false (default value)")
|
|
require.NoError(t, err,
|
|
"with empty argocd-cm config map, RequireOverridePrivilegeForRevisionSync() must not return any error")
|
|
}
|
|
|
|
func TestRequireOverridePrivilegeForRevisionSyncFalse(t *testing.T) {
|
|
// When user disables the feature explicitly,
|
|
// Then RequireOverridePrivilegeForRevisionSync() must return false and nil error.
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{
|
|
"application.sync.RequireOverridePrivilegeForRevisionSync": "false",
|
|
})
|
|
featureFlag, err := settingsManager.RequireOverridePrivilegeForRevisionSync()
|
|
require.False(t, featureFlag,
|
|
"when user disables the flag in argocd-cm config map, RequireOverridePrivilegeForRevisionSync() must return user set value")
|
|
require.NoError(t, err,
|
|
"when user disables the flag in argocd-cm config map, RequireOverridePrivilegeForRevisionSync() must not return any error")
|
|
}
|
|
|
|
func TestRequireOverridePrivilegeForRevisionSyncTrue(t *testing.T) {
|
|
// When user enables the feature explicitly,
|
|
// Then RequireOverridePrivilegeForRevisionSync() must return true and nil error.
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{
|
|
"application.sync.requireOverridePrivilegeForRevisionSync": "true",
|
|
})
|
|
featureFlag, err := settingsManager.RequireOverridePrivilegeForRevisionSync()
|
|
require.True(t, featureFlag,
|
|
"when user enables the flag in argocd-cm config map, RequireOverridePrivilegeForRevisionSync() must return user set value")
|
|
require.NoError(t, err,
|
|
"when user enables the flag in argocd-cm config map, RequireOverridePrivilegeForRevisionSync() must not return any error")
|
|
}
|
|
|
|
func TestRequireOverridePrivilegeForRevisionSyncParseError(t *testing.T) {
|
|
// When a value is set that cannot be parsed as boolean,
|
|
// Then RequireOverridePrivilegeForRevisionSync() must return false and nil error.
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{
|
|
"application.sync.requireOverridePrivilegeForRevisionSync": "BANANA",
|
|
})
|
|
featureFlag, err := settingsManager.RequireOverridePrivilegeForRevisionSync()
|
|
require.False(t, featureFlag,
|
|
"when user set the flag to unparseable value in argocd-cm config map, RequireOverridePrivilegeForRevisionSync() must return false (default value)")
|
|
require.ErrorContains(t, err, "invalid syntax",
|
|
"when user set the flag to unparseable value in argocd-cm config map, RequireOverridePrivilegeForRevisionSync() must return an error")
|
|
}
|
|
|
|
func TestSettingsManager_GetHideSecretAnnotations(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
output map[string]bool
|
|
}{
|
|
{
|
|
name: "Empty input",
|
|
input: "",
|
|
output: map[string]bool{},
|
|
},
|
|
{
|
|
name: "Comma separated data",
|
|
input: "example.com/token-secret.value,token,key",
|
|
output: map[string]bool{"example.com/token-secret.value": true, "token": true, "key": true},
|
|
},
|
|
{
|
|
name: "Comma separated data with space",
|
|
input: "example.com/token-secret.value, token, key",
|
|
output: map[string]bool{"example.com/token-secret.value": true, "token": true, "key": true},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{
|
|
resourceSensitiveAnnotationsKey: tt.input,
|
|
})
|
|
keys := settingsManager.GetSensitiveAnnotations()
|
|
assert.Len(t, keys, len(tt.output))
|
|
assert.Equal(t, tt.output, keys)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSettingsManager_GetAllowedNodeLabels(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
output []string
|
|
}{
|
|
{
|
|
name: "Empty input",
|
|
input: "",
|
|
output: []string{},
|
|
},
|
|
{
|
|
name: "Comma separated data",
|
|
input: "example.com/label,label1,label2",
|
|
output: []string{"example.com/label", "label1", "label2"},
|
|
},
|
|
{
|
|
name: "Comma separated data with space",
|
|
input: "example.com/label, label1, label2",
|
|
output: []string{"example.com/label", "label1", "label2"},
|
|
},
|
|
{
|
|
name: "Comma separated data with invalid label",
|
|
input: "example.com/label,_invalid,label1,label2",
|
|
output: []string{"example.com/label", "label1", "label2"},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{
|
|
allowedNodeLabelsKey: tt.input,
|
|
})
|
|
keys := settingsManager.GetAllowedNodeLabels()
|
|
assert.Len(t, keys, len(tt.output))
|
|
assert.Equal(t, tt.output, keys)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetHydratorReadmeTemplate(t *testing.T) {
|
|
t.Run("DefaultTemplate", func(t *testing.T) {
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{})
|
|
template, err := settingsManager.GetHydratorReadmeTemplate()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, DefaultManifestHydrationReadmeTemplate, template)
|
|
})
|
|
|
|
t.Run("CustomTemplateInConfigMap", func(t *testing.T) {
|
|
customTemplate := "Custom Readme Template: {{ .RepoURL }}"
|
|
_, settingsManager := fixtures(t.Context(), map[string]string{
|
|
settingsSourceHydratorReadmeMessageTemplateKey: customTemplate,
|
|
})
|
|
template, err := settingsManager.GetHydratorReadmeTemplate()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, customTemplate, template)
|
|
})
|
|
|
|
t.Run("ConfigMapError", func(t *testing.T) {
|
|
kubeClient := fake.NewClientset()
|
|
settingsManager := NewSettingsManager(t.Context(), kubeClient, "default")
|
|
template, err := settingsManager.GetHydratorReadmeTemplate()
|
|
require.Error(t, err)
|
|
assert.Equal(t, DefaultManifestHydrationReadmeTemplate, template)
|
|
})
|
|
}
|
|
|
|
func TestSecretsInformerExcludesClusterSecrets(t *testing.T) {
|
|
cm := &corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDConfigMapName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
}
|
|
argoSecret := &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDSecretName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
},
|
|
},
|
|
Data: map[string][]byte{},
|
|
}
|
|
repoSecret := &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "repo-secret",
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
common.LabelKeySecretType: common.LabelValueSecretTypeRepository,
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"url": []byte("https://github.com/example/repo"),
|
|
},
|
|
}
|
|
clusterSecret := &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "cluster-secret",
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
common.LabelKeySecretType: common.LabelValueSecretTypeCluster,
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"server": []byte("https://cluster.example.com"),
|
|
"name": []byte("test-cluster"),
|
|
"config": []byte("{}"),
|
|
},
|
|
}
|
|
|
|
kubeClient := fake.NewClientset(cm, argoSecret, repoSecret, clusterSecret)
|
|
settingsManager := NewSettingsManager(t.Context(), kubeClient, "default")
|
|
|
|
t.Run("secrets lister excludes cluster secrets", func(t *testing.T) {
|
|
lister, err := settingsManager.GetSecretsLister()
|
|
require.NoError(t, err)
|
|
|
|
secrets, err := lister.Secrets("default").List(labels.Everything())
|
|
require.NoError(t, err)
|
|
|
|
secretNames := make([]string, 0, len(secrets))
|
|
for _, s := range secrets {
|
|
secretNames = append(secretNames, s.Name)
|
|
}
|
|
assert.Contains(t, secretNames, "repo-secret", "repository secret should be in secrets informer")
|
|
assert.NotContains(t, secretNames, "cluster-secret", "cluster secret should be excluded from secrets informer")
|
|
})
|
|
|
|
t.Run("cluster informer includes cluster secrets", func(t *testing.T) {
|
|
informer, err := settingsManager.GetClusterInformer()
|
|
require.NoError(t, err)
|
|
|
|
cluster, err := informer.GetClusterByURL("https://cluster.example.com")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "test-cluster", cluster.Name)
|
|
})
|
|
|
|
t.Run("cluster informer excludes non-cluster secrets", func(t *testing.T) {
|
|
informer, err := settingsManager.GetClusterInformer()
|
|
require.NoError(t, err)
|
|
|
|
clusters, err := informer.ListClusters()
|
|
require.NoError(t, err)
|
|
for _, c := range clusters {
|
|
assert.NotEqual(t, "repo-secret", c.ObjectMeta.Name, "repository secret should not appear in cluster informer")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestIsRepositorySecret(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
obj any
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "repository secret matches",
|
|
obj: &corev1.Secret{ObjectMeta: metav1.ObjectMeta{
|
|
Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeRepository},
|
|
}},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "unlabeled secret does not match",
|
|
obj: &corev1.Secret{ObjectMeta: metav1.ObjectMeta{}},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "cluster secret does not match",
|
|
obj: &corev1.Secret{ObjectMeta: metav1.ObjectMeta{
|
|
Labels: map[string]string{common.LabelKeySecretType: common.LabelValueSecretTypeCluster},
|
|
}},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "unknown type does not match",
|
|
obj: "unexpected-type",
|
|
expected: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert.Equal(t, tt.expected, isRepositorySecret(tt.obj))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsSettingsObject(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
obj any
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "secret with part-of=argocd matches",
|
|
obj: &corev1.Secret{ObjectMeta: metav1.ObjectMeta{
|
|
Labels: map[string]string{"app.kubernetes.io/part-of": "argocd"},
|
|
}},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "unlabeled secret does not match",
|
|
obj: &corev1.Secret{ObjectMeta: metav1.ObjectMeta{}},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "secret with different part-of value does not match",
|
|
obj: &corev1.Secret{ObjectMeta: metav1.ObjectMeta{
|
|
Labels: map[string]string{"app.kubernetes.io/part-of": "other-app"},
|
|
}},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "configmap with part-of=argocd matches",
|
|
obj: &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{
|
|
Labels: map[string]string{"app.kubernetes.io/part-of": "argocd"},
|
|
}},
|
|
expected: true,
|
|
},
|
|
|
|
{
|
|
name: "unknown type does not match",
|
|
obj: "unexpected-type",
|
|
expected: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert.Equal(t, tt.expected, isSettingsObject(tt.obj))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsArgoCDConfigMap(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
obj any
|
|
expected bool
|
|
}{
|
|
{
|
|
name: "argocd-cm matches",
|
|
obj: &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDConfigMapName,
|
|
}},
|
|
expected: true,
|
|
},
|
|
{
|
|
name: "other configmap does not match",
|
|
obj: &corev1.ConfigMap{ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDRBACConfigMapName,
|
|
}},
|
|
expected: false,
|
|
},
|
|
{
|
|
name: "unknown type does not match",
|
|
obj: "unexpected-type",
|
|
expected: false,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert.Equal(t, tt.expected, isArgoCDConfigMap(tt.obj))
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestGettersRaceWithResyncInformers verifies that concurrent calls to
|
|
// SettingsManager getters and ResyncInformers do not trigger data races.
|
|
// Run with: go test -race -run TestGettersRaceWithResyncInformers ./util/settings/...
|
|
func TestGettersRaceWithResyncInformers(t *testing.T) {
|
|
cm := &corev1.ConfigMap{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDConfigMapName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{"app.kubernetes.io/part-of": "argocd"},
|
|
},
|
|
Data: map[string]string{},
|
|
}
|
|
secret := &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: common.ArgoCDSecretName,
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
common.LabelKeySecretType: common.LabelValueSecretTypeRepository,
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"server.secretkey": []byte("test-secret-key"),
|
|
},
|
|
}
|
|
clusterSecret := &corev1.Secret{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: "cluster-secret",
|
|
Namespace: "default",
|
|
Labels: map[string]string{
|
|
"app.kubernetes.io/part-of": "argocd",
|
|
common.LabelKeySecretType: common.LabelValueSecretTypeCluster,
|
|
},
|
|
},
|
|
Data: map[string][]byte{
|
|
"server": []byte("https://kubernetes.default.svc"),
|
|
"name": []byte("in-cluster"),
|
|
"config": []byte("{}"),
|
|
},
|
|
}
|
|
kubeClient := fake.NewClientset(cm, secret, clusterSecret)
|
|
mgr := NewSettingsManager(t.Context(), kubeClient, "default")
|
|
|
|
// Prime the informers so getters have something to return.
|
|
err := mgr.ensureSynced(false)
|
|
require.NoError(t, err)
|
|
|
|
const goroutines = 10
|
|
|
|
// done signals all goroutines to stop.
|
|
done := make(chan struct{})
|
|
var wg sync.WaitGroup
|
|
|
|
// Spawn goroutines calling ResyncInformers concurrently.
|
|
for range goroutines / 2 {
|
|
wg.Go(func() {
|
|
for {
|
|
select {
|
|
case <-done:
|
|
return
|
|
default:
|
|
_ = mgr.ResyncInformers()
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// Spawn goroutines calling the various getters concurrently.
|
|
for range goroutines / 2 {
|
|
wg.Go(func() {
|
|
for {
|
|
select {
|
|
case <-done:
|
|
return
|
|
default:
|
|
_, _ = mgr.GetSecretsLister()
|
|
_, _ = mgr.GetSecretsInformer()
|
|
_, _ = mgr.GetClusterInformer()
|
|
_, _ = mgr.GetConfigMapByName(common.ArgoCDConfigMapName)
|
|
_, _ = mgr.GetSecretByName(common.ArgoCDSecretName)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// Let the race run for a bit.
|
|
time.Sleep(200 * time.Millisecond)
|
|
close(done)
|
|
wg.Wait()
|
|
}
|
|
|
|
func TestSettingsManager_GetWebhookRefreshJitter(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
notConfigured bool
|
|
output time.Duration
|
|
}{
|
|
{
|
|
name: "Valid jitter value",
|
|
input: "60s",
|
|
output: 60 * time.Second,
|
|
},
|
|
{
|
|
name: "Not configured",
|
|
notConfigured: true,
|
|
output: 0,
|
|
},
|
|
{
|
|
name: "Empty input",
|
|
input: "",
|
|
output: 0,
|
|
},
|
|
{
|
|
name: "Invalid format",
|
|
input: "invalid",
|
|
output: 0,
|
|
},
|
|
{
|
|
name: "Invalid format with only number",
|
|
input: "60",
|
|
output: 0,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
configMap := map[string]string{}
|
|
if !tt.notConfigured {
|
|
configMap["webhook.refresh.jitter"] = tt.input
|
|
}
|
|
_, settingsManager := fixtures(t.Context(), configMap)
|
|
jitter := settingsManager.GetWebhookRefreshJitter()
|
|
assert.Equal(t, tt.output, jitter)
|
|
})
|
|
}
|
|
}
|