Files
41536738c8 refactor(cd): hard-fork - rename CRD API group argoproj.io -> deploy.hanzo.ai (#1)
* 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]>
2026-07-29 18:27:58 -07:00

2736 lines
90 KiB
Go

package commands
import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"slices"
"strings"
"testing"
"time"
"github.com/hanzoai/deploy/gitops-engine/pkg/health"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/oauth2"
"google.golang.org/grpc"
grpccodes "google.golang.org/grpc/codes"
grpcstatus "google.golang.org/grpc/status"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/intstr"
"k8s.io/apimachinery/pkg/watch"
"sigs.k8s.io/yaml"
argocdclient "github.com/hanzoai/deploy/pkg/apiclient"
accountpkg "github.com/hanzoai/deploy/pkg/apiclient/account"
applicationpkg "github.com/hanzoai/deploy/pkg/apiclient/application"
applicationsetpkg "github.com/hanzoai/deploy/pkg/apiclient/applicationset"
certificatepkg "github.com/hanzoai/deploy/pkg/apiclient/certificate"
clusterpkg "github.com/hanzoai/deploy/pkg/apiclient/cluster"
eventspkg "github.com/hanzoai/deploy/pkg/apiclient/events"
gpgkeypkg "github.com/hanzoai/deploy/pkg/apiclient/gpgkey"
notificationpkg "github.com/hanzoai/deploy/pkg/apiclient/notification"
projectpkg "github.com/hanzoai/deploy/pkg/apiclient/project"
repocredspkg "github.com/hanzoai/deploy/pkg/apiclient/repocreds"
repositorypkg "github.com/hanzoai/deploy/pkg/apiclient/repository"
sessionpkg "github.com/hanzoai/deploy/pkg/apiclient/session"
settingspkg "github.com/hanzoai/deploy/pkg/apiclient/settings"
versionpkg "github.com/hanzoai/deploy/pkg/apiclient/version"
"github.com/hanzoai/deploy/pkg/apis/application"
"github.com/hanzoai/deploy/pkg/apis/application/v1alpha1"
"github.com/hanzoai/deploy/reposerver/apiclient"
)
func Test_getInfos(t *testing.T) {
testCases := []struct {
name string
infos []string
expectedInfos []*v1alpha1.Info
}{
{
name: "empty",
infos: []string{},
expectedInfos: []*v1alpha1.Info{},
},
{
name: "simple key value",
infos: []string{"key1=value1", "key2=value2"},
expectedInfos: []*v1alpha1.Info{
{Name: "key1", Value: "value1"},
{Name: "key2", Value: "value2"},
},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
infos := getInfos(testCase.infos)
assert.Len(t, infos, len(testCase.expectedInfos))
sort := func(a, b *v1alpha1.Info) bool { return a.Name < b.Name }
assert.Empty(t, cmp.Diff(testCase.expectedInfos, infos, cmpopts.SortSlices(sort)))
})
}
}
func Test_getRefreshType(t *testing.T) {
refreshTypeNormal := string(v1alpha1.RefreshTypeNormal)
refreshTypeHard := string(v1alpha1.RefreshTypeHard)
testCases := []struct {
refresh bool
hardRefresh bool
expected *string
}{
{false, false, nil},
{false, true, &refreshTypeHard},
{true, false, &refreshTypeNormal},
{true, true, &refreshTypeHard},
}
for _, testCase := range testCases {
t.Run(fmt.Sprintf("hardRefresh=%t refresh=%t", testCase.hardRefresh, testCase.refresh), func(t *testing.T) {
refreshType := getRefreshType(testCase.refresh, testCase.hardRefresh)
if testCase.expected == nil {
assert.Nil(t, refreshType)
} else {
assert.NotNil(t, refreshType)
assert.Equal(t, *testCase.expected, *refreshType)
}
})
}
}
func TestFindRevisionHistoryWithoutPassedId(t *testing.T) {
histories := v1alpha1.RevisionHistories{}
histories = append(histories, v1alpha1.RevisionHistory{ID: 1})
histories = append(histories, v1alpha1.RevisionHistory{ID: 2})
histories = append(histories, v1alpha1.RevisionHistory{ID: 3})
status := v1alpha1.ApplicationStatus{
Resources: nil,
Sync: v1alpha1.SyncStatus{},
Health: v1alpha1.AppHealthStatus{},
History: histories,
Conditions: nil,
ReconciledAt: nil,
OperationState: nil,
ObservedAt: nil,
SourceType: "",
Summary: v1alpha1.ApplicationSummary{},
}
application := v1alpha1.Application{
Status: status,
}
history, err := findRevisionHistory(&application, -1)
require.NoError(t, err, "Find revision history should fail without errors")
require.NotNil(t, history, "History should be found")
}
func TestPrintTreeViewAppGet(t *testing.T) {
var nodes [3]v1alpha1.ResourceNode
nodes[0].ResourceRef = v1alpha1.ResourceRef{Group: "", Version: "v1", Kind: "Pod", Namespace: "sandbox-rollout-numalogic-demo", Name: "numalogic-rollout-demo-5dcd5457d5-6trpt", UID: "92c3a5fe-d13e-4ae2-b8ec-c10dd3543b28"}
nodes[0].ParentRefs = []v1alpha1.ResourceRef{{Group: "apps", Version: "v1", Kind: "ReplicaSet", Namespace: "sandbox-rollout-numalogic-demo", Name: "numalogic-rollout-demo-5dcd5457d5", UID: "75c30dce-1b66-414f-a86c-573a74be0f40"}}
nodes[1].ResourceRef = v1alpha1.ResourceRef{Group: "apps", Version: "v1", Kind: "ReplicaSet", Namespace: "sandbox-rollout-numalogic-demo", Name: "numalogic-rollout-demo-5dcd5457d5", UID: "75c30dce-1b66-414f-a86c-573a74be0f40"}
nodes[1].ParentRefs = []v1alpha1.ResourceRef{{Group: "argoproj.io", Version: "", Kind: "Rollout", Namespace: "sandbox-rollout-numalogic-demo", Name: "numalogic-rollout-demo", UID: "87f3aab0-f634-4b2c-959a-7ddd30675ed0"}}
nodes[2].ResourceRef = v1alpha1.ResourceRef{Group: "argoproj.io", Version: "", Kind: "Rollout", Namespace: "sandbox-rollout-numalogic-demo", Name: "numalogic-rollout-demo", UID: "87f3aab0-f634-4b2c-959a-7ddd30675ed0"}
nodeMapping := make(map[string]v1alpha1.ResourceNode)
mapParentToChild := make(map[string][]string)
parentNode := make(map[string]struct{})
for _, node := range nodes {
nodeMapping[node.UID] = node
if len(node.ParentRefs) > 0 {
_, ok := mapParentToChild[node.ParentRefs[0].UID]
if !ok {
var temp []string
mapParentToChild[node.ParentRefs[0].UID] = temp
}
mapParentToChild[node.ParentRefs[0].UID] = append(mapParentToChild[node.ParentRefs[0].UID], node.UID)
} else {
parentNode[node.UID] = struct{}{}
}
}
output, _ := captureOutput(func() error {
printTreeView(nodeMapping, mapParentToChild, parentNode, nil)
return nil
})
assert.Contains(t, output, "Pod")
assert.Contains(t, output, "ReplicaSet")
assert.Contains(t, output, "Rollout")
assert.Contains(t, output, "numalogic-rollout-demo-5dcd5457d5-6trpt")
}
func TestPrintTreeViewDetailedAppGet(t *testing.T) {
var nodes [3]v1alpha1.ResourceNode
nodes[0].ResourceRef = v1alpha1.ResourceRef{Group: "", Version: "v1", Kind: "Pod", Namespace: "sandbox-rollout-numalogic-demo", Name: "numalogic-rollout-demo-5dcd5457d5-6trpt", UID: "92c3a5fe-d13e-4ae2-b8ec-c10dd3543b28"}
nodes[0].Health = &v1alpha1.HealthStatus{Status: "Degraded", Message: "Readiness Gate failed"}
nodes[0].ParentRefs = []v1alpha1.ResourceRef{{Group: "apps", Version: "v1", Kind: "ReplicaSet", Namespace: "sandbox-rollout-numalogic-demo", Name: "numalogic-rollout-demo-5dcd5457d5", UID: "75c30dce-1b66-414f-a86c-573a74be0f40"}}
nodes[1].ResourceRef = v1alpha1.ResourceRef{Group: "apps", Version: "v1", Kind: "ReplicaSet", Namespace: "sandbox-rollout-numalogic-demo", Name: "numalogic-rollout-demo-5dcd5457d5", UID: "75c30dce-1b66-414f-a86c-573a74be0f40"}
nodes[1].ParentRefs = []v1alpha1.ResourceRef{{Group: "argoproj.io", Version: "", Kind: "Rollout", Namespace: "sandbox-rollout-numalogic-demo", Name: "numalogic-rollout-demo", UID: "87f3aab0-f634-4b2c-959a-7ddd30675ed0"}}
nodes[2].ResourceRef = v1alpha1.ResourceRef{Group: "argoproj.io", Version: "", Kind: "Rollout", Namespace: "sandbox-rollout-numalogic-demo", Name: "numalogic-rollout-demo", UID: "87f3aab0-f634-4b2c-959a-7ddd30675ed0"}
nodeMapping := make(map[string]v1alpha1.ResourceNode)
mapParentToChild := make(map[string][]string)
parentNode := make(map[string]struct{})
for _, node := range nodes {
nodeMapping[node.UID] = node
if len(node.ParentRefs) > 0 {
_, ok := mapParentToChild[node.ParentRefs[0].UID]
if !ok {
var temp []string
mapParentToChild[node.ParentRefs[0].UID] = temp
}
mapParentToChild[node.ParentRefs[0].UID] = append(mapParentToChild[node.ParentRefs[0].UID], node.UID)
} else {
parentNode[node.UID] = struct{}{}
}
}
output, _ := captureOutput(func() error {
printTreeViewDetailed(nodeMapping, mapParentToChild, parentNode, nil)
return nil
})
assert.Contains(t, output, "Pod")
assert.Contains(t, output, "ReplicaSet")
assert.Contains(t, output, "Rollout")
assert.Contains(t, output, "numalogic-rollout-demo-5dcd5457d5-6trpt")
assert.Contains(t, output, "Degraded")
assert.Contains(t, output, "Readiness Gate failed")
}
func TestFindRevisionHistoryWithoutPassedIdWithMultipleSources(t *testing.T) {
histories := v1alpha1.RevisionHistories{}
histories = append(histories, v1alpha1.RevisionHistory{ID: 1})
histories = append(histories, v1alpha1.RevisionHistory{ID: 2})
histories = append(histories, v1alpha1.RevisionHistory{ID: 3})
status := v1alpha1.ApplicationStatus{
Resources: nil,
Sync: v1alpha1.SyncStatus{},
Health: v1alpha1.AppHealthStatus{},
History: histories,
Conditions: nil,
ReconciledAt: nil,
OperationState: nil,
ObservedAt: nil,
SourceType: "",
Summary: v1alpha1.ApplicationSummary{},
}
application := v1alpha1.Application{
Status: status,
}
history, err := findRevisionHistory(&application, -1)
require.NoError(t, err, "Find revision history should fail without errors")
require.NotNil(t, history, "History should be found")
}
func TestDefaultWaitOptions(t *testing.T) {
watch := watchOpts{
sync: false,
health: false,
operation: false,
suspended: false,
}
opts := getWatchOpts(watch)
assert.True(t, opts.sync)
assert.True(t, opts.health)
assert.True(t, opts.operation)
assert.False(t, opts.suspended)
}
func TestOverrideWaitOptions(t *testing.T) {
watch := watchOpts{
sync: true,
health: false,
operation: false,
suspended: false,
}
opts := getWatchOpts(watch)
assert.True(t, opts.sync)
assert.False(t, opts.health)
assert.False(t, opts.operation)
assert.False(t, opts.suspended)
}
func TestFindRevisionHistoryWithoutPassedIdAndEmptyHistoryList(t *testing.T) {
histories := v1alpha1.RevisionHistories{}
status := v1alpha1.ApplicationStatus{
Resources: nil,
Sync: v1alpha1.SyncStatus{},
Health: v1alpha1.AppHealthStatus{},
History: histories,
Conditions: nil,
ReconciledAt: nil,
OperationState: nil,
ObservedAt: nil,
SourceType: "",
Summary: v1alpha1.ApplicationSummary{},
}
application := v1alpha1.Application{
Status: status,
}
history, err := findRevisionHistory(&application, -1)
require.Error(t, err, "Find revision history should fail with errors")
require.Nil(t, history, "History should be empty")
require.EqualError(t, err, "application '' should have at least two successful deployments", "Find revision history should fail with correct error message")
}
func TestFindRevisionHistoryWithPassedId(t *testing.T) {
histories := v1alpha1.RevisionHistories{}
histories = append(histories, v1alpha1.RevisionHistory{ID: 1})
histories = append(histories, v1alpha1.RevisionHistory{ID: 2})
histories = append(histories, v1alpha1.RevisionHistory{ID: 3, Revision: "123"})
status := v1alpha1.ApplicationStatus{
Resources: nil,
Sync: v1alpha1.SyncStatus{},
Health: v1alpha1.AppHealthStatus{},
History: histories,
Conditions: nil,
ReconciledAt: nil,
OperationState: nil,
ObservedAt: nil,
SourceType: "",
Summary: v1alpha1.ApplicationSummary{},
}
application := v1alpha1.Application{
Status: status,
}
history, err := findRevisionHistory(&application, 3)
require.NoError(t, err, "Find revision history should fail without errors")
require.NotNil(t, history, "History should be found")
require.Equal(t, "123", history.Revision, "Failed to find correct history with correct revision")
}
func TestFindRevisionHistoryWithPassedIdThatNotExist(t *testing.T) {
histories := v1alpha1.RevisionHistories{}
histories = append(histories, v1alpha1.RevisionHistory{ID: 1})
histories = append(histories, v1alpha1.RevisionHistory{ID: 2})
histories = append(histories, v1alpha1.RevisionHistory{ID: 3, Revision: "123"})
status := v1alpha1.ApplicationStatus{
Resources: nil,
Sync: v1alpha1.SyncStatus{},
Health: v1alpha1.AppHealthStatus{},
History: histories,
Conditions: nil,
ReconciledAt: nil,
OperationState: nil,
ObservedAt: nil,
SourceType: "",
Summary: v1alpha1.ApplicationSummary{},
}
application := v1alpha1.Application{
Status: status,
}
history, err := findRevisionHistory(&application, 4)
require.Error(t, err, "Find revision history should fail with errors")
require.Nil(t, history, "History should be not found")
require.EqualError(t, err, "application '' does not have deployment id '4' in history", "Find revision history should fail with correct error message")
}
func TestFormatSyncPolicy(t *testing.T) {
t.Run("Policy not defined", func(t *testing.T) {
app := v1alpha1.Application{}
policy := formatSyncPolicy(app)
require.Equalf(t, "Manual", policy, "Incorrect policy %q, should be Manual", policy)
})
t.Run("Auto policy", func(t *testing.T) {
app := v1alpha1.Application{
Spec: v1alpha1.ApplicationSpec{
SyncPolicy: &v1alpha1.SyncPolicy{
Automated: &v1alpha1.SyncPolicyAutomated{},
},
},
}
policy := formatSyncPolicy(app)
require.Equalf(t, "Auto", policy, "Incorrect policy %q, should be Auto", policy)
})
t.Run("Auto policy with prune", func(t *testing.T) {
app := v1alpha1.Application{
Spec: v1alpha1.ApplicationSpec{
SyncPolicy: &v1alpha1.SyncPolicy{
Automated: &v1alpha1.SyncPolicyAutomated{
Prune: new(true),
},
},
},
}
policy := formatSyncPolicy(app)
require.Equalf(t, "Auto-Prune", policy, "Incorrect policy %q, should be Auto-Prune", policy)
})
}
func TestFormatConditionSummary(t *testing.T) {
t.Run("No conditions are defined", func(t *testing.T) {
app := v1alpha1.Application{
Spec: v1alpha1.ApplicationSpec{
SyncPolicy: &v1alpha1.SyncPolicy{
Automated: &v1alpha1.SyncPolicyAutomated{
Prune: new(true),
},
},
},
}
summary := formatConditionsSummary(app)
require.Equalf(t, "<none>", summary, "Incorrect summary %q, should be <none>", summary)
})
t.Run("Few conditions are defined", func(t *testing.T) {
app := v1alpha1.Application{
Status: v1alpha1.ApplicationStatus{
Conditions: []v1alpha1.ApplicationCondition{
{
Type: "type1",
},
{
Type: "type1",
},
{
Type: "type2",
},
},
},
}
summary := formatConditionsSummary(app)
require.Equalf(t, "type1(2),type2", summary, "Incorrect summary %q, should be type1(2),type2", summary)
})
t.Run("Conditions are sorted for idempotent summary", func(t *testing.T) {
app := v1alpha1.Application{
Status: v1alpha1.ApplicationStatus{
Conditions: []v1alpha1.ApplicationCondition{
{
Type: "type2",
},
{
Type: "type1",
},
{
Type: "type1",
},
},
},
}
summary := formatConditionsSummary(app)
require.Equalf(t, "type1(2),type2", summary, "Incorrect summary %q, should be type1(2),type2", summary)
})
}
func TestPrintOperationResult(t *testing.T) {
t.Run("Operation state is empty", func(t *testing.T) {
output, _ := captureOutput(func() error {
printOperationResult(nil)
return nil
})
require.Emptyf(t, output, "Incorrect print operation output %q, should be ''", output)
})
t.Run("Operation state sync result is not empty", func(t *testing.T) {
time := metav1.Date(2020, time.November, 10, 23, 0, 0, 0, time.UTC)
output, _ := captureOutput(func() error {
printOperationResult(&v1alpha1.OperationState{
SyncResult: &v1alpha1.SyncOperationResult{Revision: "revision"},
FinishedAt: &time,
})
return nil
})
expectation := "Operation: Sync\nSync Revision: revision\nPhase: \nStart: 0001-01-01 00:00:00 +0000 UTC\nFinished: 2020-11-10 23:00:00 +0000 UTC\nDuration: 2333448h16m18.871345152s\n"
require.Equalf(t, output, expectation, "Incorrect print operation output %q, should be %q", output, expectation)
})
t.Run("Operation state sync result with message is not empty", func(t *testing.T) {
time := metav1.Date(2020, time.November, 10, 23, 0, 0, 0, time.UTC)
output, _ := captureOutput(func() error {
printOperationResult(&v1alpha1.OperationState{
SyncResult: &v1alpha1.SyncOperationResult{Revision: "revision"},
FinishedAt: &time,
Message: "test",
})
return nil
})
expectation := "Operation: Sync\nSync Revision: revision\nPhase: \nStart: 0001-01-01 00:00:00 +0000 UTC\nFinished: 2020-11-10 23:00:00 +0000 UTC\nDuration: 2333448h16m18.871345152s\nMessage: test\n"
require.Equalf(t, output, expectation, "Incorrect print operation output %q, should be %q", output, expectation)
})
}
func TestPrintApplicationHistoryTable(t *testing.T) {
histories := []v1alpha1.RevisionHistory{
{
ID: 1,
Source: v1alpha1.ApplicationSource{
TargetRevision: "1",
RepoURL: "test",
},
},
{
ID: 2,
Source: v1alpha1.ApplicationSource{
TargetRevision: "2",
RepoURL: "test",
},
},
{
ID: 3,
Source: v1alpha1.ApplicationSource{
TargetRevision: "3",
RepoURL: "test",
},
},
}
output, _ := captureOutput(func() error {
printApplicationHistoryTable(histories)
return nil
})
expectation := "SOURCE test\nID DATE REVISION\n1 0001-01-01 00:00:00 +0000 UTC 1\n2 0001-01-01 00:00:00 +0000 UTC 2\n3 0001-01-01 00:00:00 +0000 UTC 3\n"
require.Equalf(t, output, expectation, "Incorrect print operation output %q, should be %q", output, expectation)
}
func TestPrintApplicationHistoryTableWithMultipleSources(t *testing.T) {
histories := []v1alpha1.RevisionHistory{
{
ID: 0,
Source: v1alpha1.ApplicationSource{
TargetRevision: "0",
RepoURL: "test",
},
},
{
ID: 1,
Revisions: []string{
"1a",
"1b",
},
// added Source just for testing the fuction
Source: v1alpha1.ApplicationSource{
TargetRevision: "-1",
RepoURL: "ignore",
},
Sources: v1alpha1.ApplicationSources{
v1alpha1.ApplicationSource{
RepoURL: "test-1",
TargetRevision: "1a",
},
v1alpha1.ApplicationSource{
RepoURL: "test-2",
TargetRevision: "1b",
},
},
},
{
ID: 2,
Revisions: []string{
"2a",
"2b",
},
Sources: v1alpha1.ApplicationSources{
v1alpha1.ApplicationSource{
RepoURL: "test-1",
TargetRevision: "2a",
},
v1alpha1.ApplicationSource{
RepoURL: "test-2",
TargetRevision: "2b",
},
},
},
{
ID: 3,
Revisions: []string{
"3a",
"3b",
},
Sources: v1alpha1.ApplicationSources{
v1alpha1.ApplicationSource{
RepoURL: "test-1",
TargetRevision: "3a",
},
v1alpha1.ApplicationSource{
RepoURL: "test-2",
TargetRevision: "3b",
},
},
},
}
output, _ := captureOutput(func() error {
printApplicationHistoryTable(histories)
return nil
})
expectation := "SOURCE test\nID DATE REVISION\n0 0001-01-01 00:00:00 +0000 UTC 0\n\nSOURCE test-1\nID DATE REVISION\n1 0001-01-01 00:00:00 +0000 UTC 1a\n2 0001-01-01 00:00:00 +0000 UTC 2a\n3 0001-01-01 00:00:00 +0000 UTC 3a\n\nSOURCE test-2\nID DATE REVISION\n1 0001-01-01 00:00:00 +0000 UTC 1b\n2 0001-01-01 00:00:00 +0000 UTC 2b\n3 0001-01-01 00:00:00 +0000 UTC 3b\n"
require.Equalf(t, output, expectation, "Incorrect print operation output %q, should be %q", output, expectation)
}
func TestPrintAppSummaryTable(t *testing.T) {
output, _ := captureOutput(func() error {
app := &v1alpha1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "argocd",
},
Spec: v1alpha1.ApplicationSpec{
SyncPolicy: &v1alpha1.SyncPolicy{
Automated: &v1alpha1.SyncPolicyAutomated{
Prune: new(true),
},
},
Project: "default",
Destination: v1alpha1.ApplicationDestination{Server: "local", Namespace: "argocd"},
Source: &v1alpha1.ApplicationSource{
RepoURL: "test",
TargetRevision: "master",
Path: "/test",
Helm: &v1alpha1.ApplicationSourceHelm{
ValueFiles: []string{"path1", "path2"},
},
Kustomize: &v1alpha1.ApplicationSourceKustomize{NamePrefix: "prefix"},
},
},
Status: v1alpha1.ApplicationStatus{
Sync: v1alpha1.SyncStatus{
Status: v1alpha1.SyncStatusCodeOutOfSync,
},
Health: v1alpha1.AppHealthStatus{
Status: health.HealthStatusProgressing,
},
},
}
windows := &v1alpha1.SyncWindows{
{
Kind: "allow",
Schedule: "0 0 * * *",
Duration: "24h",
Applications: []string{
"*-prod",
},
ManualSync: true,
},
{
Kind: "deny",
Schedule: "0 0 * * *",
Duration: "24h",
Namespaces: []string{
"default",
},
},
{
Kind: "allow",
Schedule: "0 0 * * *",
Duration: "24h",
Clusters: []string{
"in-cluster",
"cluster1",
},
},
}
printAppSummaryTable(app, "url", windows)
return nil
})
expectation := `Name: argocd/test
Project: default
Server: local
Namespace: argocd
URL: url
Source:
- Repo: test
Target: master
Path: /test
Helm Values: path1,path2
Name Prefix: prefix
SyncWindow: Sync Denied
Assigned Windows: allow:0 0 * * *:24h,deny:0 0 * * *:24h,allow:0 0 * * *:24h
Sync Policy: Automated (Prune)
Sync Status: OutOfSync from master
Health Status: Progressing
`
assert.Equalf(t, expectation, output, "Incorrect print app summary output %q, should be %q", output, expectation)
}
func TestPrintAppSummaryTable_MultipleSources(t *testing.T) {
output, _ := captureOutput(func() error {
app := &v1alpha1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "argocd",
},
Spec: v1alpha1.ApplicationSpec{
SyncPolicy: &v1alpha1.SyncPolicy{
Automated: &v1alpha1.SyncPolicyAutomated{
Prune: new(true),
},
},
Project: "default",
Destination: v1alpha1.ApplicationDestination{Server: "local", Namespace: "argocd"},
Sources: v1alpha1.ApplicationSources{
{
RepoURL: "test",
TargetRevision: "master",
Path: "/test",
Helm: &v1alpha1.ApplicationSourceHelm{
ValueFiles: []string{"path1", "path2"},
},
Kustomize: &v1alpha1.ApplicationSourceKustomize{NamePrefix: "prefix"},
}, {
RepoURL: "test2",
TargetRevision: "master2",
Path: "/test2",
},
},
},
Status: v1alpha1.ApplicationStatus{
Sync: v1alpha1.SyncStatus{
Status: v1alpha1.SyncStatusCodeOutOfSync,
},
Health: v1alpha1.AppHealthStatus{
Status: health.HealthStatusProgressing,
},
},
}
windows := &v1alpha1.SyncWindows{
{
Kind: "allow",
Schedule: "0 0 * * *",
Duration: "24h",
Applications: []string{
"*-prod",
},
ManualSync: true,
},
{
Kind: "deny",
Schedule: "0 0 * * *",
Duration: "24h",
Namespaces: []string{
"default",
},
},
{
Kind: "allow",
Schedule: "0 0 * * *",
Duration: "24h",
Clusters: []string{
"in-cluster",
"cluster1",
},
},
}
printAppSummaryTable(app, "url", windows)
return nil
})
expectation := `Name: argocd/test
Project: default
Server: local
Namespace: argocd
URL: url
Sources:
- Repo: test
Target: master
Path: /test
Helm Values: path1,path2
Name Prefix: prefix
- Repo: test2
Target: master2
Path: /test2
SyncWindow: Sync Denied
Assigned Windows: allow:0 0 * * *:24h,deny:0 0 * * *:24h,allow:0 0 * * *:24h
Sync Policy: Automated (Prune)
Sync Status: OutOfSync from master
Health Status: Progressing
`
assert.Equalf(t, expectation, output, "Incorrect print app summary output %q, should be %q", output, expectation)
}
func TestPrintAppConditions(t *testing.T) {
output, _ := captureOutput(func() error {
app := &v1alpha1.Application{
Status: v1alpha1.ApplicationStatus{
Conditions: []v1alpha1.ApplicationCondition{
{
Type: v1alpha1.ApplicationConditionDeletionError,
Message: "test",
},
{
Type: v1alpha1.ApplicationConditionExcludedResourceWarning,
Message: "test2",
},
{
Type: v1alpha1.ApplicationConditionRepeatedResourceWarning,
Message: "test3",
},
},
},
}
printAppConditions(os.Stdout, app)
return nil
})
expectation := "CONDITION\tMESSAGE\tLAST TRANSITION\nDeletionError\ttest\t<nil>\nExcludedResourceWarning\ttest2\t<nil>\nRepeatedResourceWarning\ttest3\t<nil>\n"
require.Equalf(t, output, expectation, "Incorrect print app conditions output %q, should be %q", output, expectation)
}
func TestPrintParams(t *testing.T) {
testCases := []struct {
name string
app *v1alpha1.Application
sourcePosition int
expectedOutput string
}{
{
name: "Single Source application with valid helm parameters",
app: &v1alpha1.Application{
Spec: v1alpha1.ApplicationSpec{
Source: &v1alpha1.ApplicationSource{
Helm: &v1alpha1.ApplicationSourceHelm{
Parameters: []v1alpha1.HelmParameter{
{
Name: "name1",
Value: "value1",
},
{
Name: "name2",
Value: "value2",
},
{
Name: "name3",
Value: "value3",
},
},
},
},
},
},
sourcePosition: -1,
expectedOutput: "\n\nNAME VALUE\nname1 value1\nname2 value2\nname3 value3\n",
},
{
name: "Multi-source application with a valid Source Position",
app: &v1alpha1.Application{
Spec: v1alpha1.ApplicationSpec{
Sources: []v1alpha1.ApplicationSource{
{
Helm: &v1alpha1.ApplicationSourceHelm{
Parameters: []v1alpha1.HelmParameter{
{
Name: "nameA",
Value: "valueA",
},
},
},
},
{
Helm: &v1alpha1.ApplicationSourceHelm{
Parameters: []v1alpha1.HelmParameter{
{
Name: "nameB",
Value: "valueB",
},
},
},
},
},
},
},
sourcePosition: 1,
expectedOutput: "\n\nNAME VALUE\nnameA valueA\n",
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
output, _ := captureOutput(func() error {
printParams(tc.app, tc.sourcePosition)
return nil
})
require.Equalf(t, tc.expectedOutput, output, "Incorrect print params output %q, should be %q\n", output, tc.expectedOutput)
})
}
}
func TestAppUrlDefault(t *testing.T) {
t.Run("Plain text", func(t *testing.T) {
result := appURLDefault(argocdclient.NewClientOrDie(&argocdclient.ClientOptions{
ServerAddr: "localhost:80",
PlainText: true,
}), "test")
expectation := "http://localhost:80/applications/test"
require.Equalf(t, result, expectation, "Incorrect url %q, should be %q", result, expectation)
})
t.Run("https", func(t *testing.T) {
result := appURLDefault(argocdclient.NewClientOrDie(&argocdclient.ClientOptions{
ServerAddr: "localhost:443",
PlainText: false,
}), "test")
expectation := "https://localhost/applications/test"
require.Equalf(t, result, expectation, "Incorrect url %q, should be %q", result, expectation)
})
}
func TestTruncateString(t *testing.T) {
result := truncateString("argocdtool", 2)
expectation := "ar..."
require.Equalf(t, result, expectation, "Incorrect truncate string %q, should be %q", result, expectation)
}
func TestGetService(t *testing.T) {
t.Run("Server", func(t *testing.T) {
app := &v1alpha1.Application{
Spec: v1alpha1.ApplicationSpec{
Destination: v1alpha1.ApplicationDestination{
Server: "test-server",
},
},
}
result := getServer(app)
expectation := "test-server"
require.Equal(t, result, expectation, "Incorrect server %q, should be %q", result, expectation)
})
t.Run("Name", func(t *testing.T) {
app := &v1alpha1.Application{
Spec: v1alpha1.ApplicationSpec{
Destination: v1alpha1.ApplicationDestination{
Name: "test-name",
},
},
}
result := getServer(app)
expectation := "test-name"
require.Equal(t, result, expectation, "Incorrect server name %q, should be %q", result, expectation)
})
}
func TestTargetObjects(t *testing.T) {
resources := []*v1alpha1.ResourceDiff{
{
TargetState: "{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"name\":\"test-helm-guestbook\",\"namespace\":\"argocd\"},\"spec\":{\"selector\":{\"app\":\"helm-guestbook\",\"release\":\"test\"},\"sessionAffinity\":\"None\",\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}",
},
{
TargetState: "{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"name\":\"test-helm-guestbook\",\"namespace\":\"ns\"},\"spec\":{\"selector\":{\"app\":\"helm-guestbook\",\"release\":\"test\"},\"sessionAffinity\":\"None\",\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}",
},
}
objects, err := targetObjects(resources)
require.NoError(t, err, "operation should finish without error")
require.Lenf(t, objects, 2, "incorrect number of objects %v, should be 2", len(objects))
require.Equalf(t, "test-helm-guestbook", objects[0].GetName(), "incorrect name %q, should be %q", objects[0].GetName(), "test-helm-guestbook")
}
func TestTargetObjects_invalid(t *testing.T) {
resources := []*v1alpha1.ResourceDiff{{TargetState: "{"}}
_, err := targetObjects(resources)
assert.Error(t, err)
}
func TestCheckForDeleteEvent(t *testing.T) {
fakeClient := new(fakeAcdClient)
checkForDeleteEvent(t.Context(), fakeClient, "testApp")
}
func TestPrintApplicationNames(t *testing.T) {
output, _ := captureOutput(func() error {
app := &v1alpha1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
}
printApplicationNames([]v1alpha1.Application{*app, *app})
return nil
})
expectation := "test\ntest\n"
require.Equalf(t, output, expectation, "Incorrect print params output %q, should be %q", output, expectation)
}
func TestNewApplicationUnsetCommand_Validation(t *testing.T) {
if os.Getenv("BE_CRASHER") == "1" {
cmd := NewApplicationUnsetCommand(nil)
cmd.SetArgs([]string{"my-app", "--source-position", "1", "--source-name", "test"})
_ = cmd.Execute()
}
cmd := exec.CommandContext(t.Context(), os.Args[0], "-test.run=TestNewApplicationUnsetCommand_Validation")
cmd.Env = append(os.Environ(), "BE_CRASHER=1")
var stderr bytes.Buffer
cmd.Stderr = &stderr
err := cmd.Run()
if e, ok := errors.AsType[*exec.ExitError](err); ok && !e.Success() {
assert.Contains(t, stderr.String(), "Only one of source-position and source-name can be specified.")
return
}
t.Fatalf("process ran with err %v, want exit status 1", err)
}
func TestNewApplicationUnsetCommand_Flags(t *testing.T) {
cmd := NewApplicationUnsetCommand(nil)
assert.NotNil(t, cmd)
flag := cmd.Flags().Lookup("source-name")
assert.NotNil(t, flag)
assert.Equal(t, "source-name", flag.Name)
flag = cmd.Flags().Lookup("source-position")
assert.NotNil(t, flag)
assert.Equal(t, "source-position", flag.Name)
}
func Test_unset(t *testing.T) {
kustomizeSource := &v1alpha1.ApplicationSource{
Kustomize: &v1alpha1.ApplicationSourceKustomize{
IgnoreMissingComponents: true,
NamePrefix: "some-prefix",
NameSuffix: "some-suffix",
Version: "123",
Images: v1alpha1.KustomizeImages{
"old1=new:tag",
"old2=new:tag",
},
Replicas: []v1alpha1.KustomizeReplica{
{
Name: "my-deployment",
Count: intstr.FromInt(2),
},
{
Name: "my-statefulset",
Count: intstr.FromInt(4),
},
},
},
}
helmSource := &v1alpha1.ApplicationSource{
Helm: &v1alpha1.ApplicationSourceHelm{
IgnoreMissingValueFiles: true,
Parameters: []v1alpha1.HelmParameter{
{
Name: "name-1",
Value: "value-1",
},
{
Name: "name-2",
Value: "value-2",
},
},
PassCredentials: true,
ValuesObject: &runtime.RawExtension{Raw: []byte("some: yaml")},
ValueFiles: []string{
"values-1.yaml",
"values-2.yaml",
},
},
}
pluginSource := &v1alpha1.ApplicationSource{
Plugin: &v1alpha1.ApplicationSourcePlugin{
Env: v1alpha1.Env{
{
Name: "env-1",
Value: "env-value-1",
},
{
Name: "env-2",
Value: "env-value-2",
},
},
},
}
assert.Equal(t, "some-prefix", kustomizeSource.Kustomize.NamePrefix)
updated, nothingToUnset := unset(kustomizeSource, unsetOpts{namePrefix: true})
assert.Empty(t, kustomizeSource.Kustomize.NamePrefix)
assert.True(t, updated)
assert.False(t, nothingToUnset)
updated, nothingToUnset = unset(kustomizeSource, unsetOpts{namePrefix: true})
assert.False(t, updated)
assert.False(t, nothingToUnset)
assert.Equal(t, "some-suffix", kustomizeSource.Kustomize.NameSuffix)
updated, nothingToUnset = unset(kustomizeSource, unsetOpts{nameSuffix: true})
assert.Empty(t, kustomizeSource.Kustomize.NameSuffix)
assert.True(t, updated)
assert.False(t, nothingToUnset)
updated, nothingToUnset = unset(kustomizeSource, unsetOpts{nameSuffix: true})
assert.False(t, updated)
assert.False(t, nothingToUnset)
assert.Equal(t, "123", kustomizeSource.Kustomize.Version)
updated, nothingToUnset = unset(kustomizeSource, unsetOpts{kustomizeVersion: true})
assert.Empty(t, kustomizeSource.Kustomize.Version)
assert.True(t, updated)
assert.False(t, nothingToUnset)
updated, nothingToUnset = unset(kustomizeSource, unsetOpts{kustomizeVersion: true})
assert.False(t, updated)
assert.False(t, nothingToUnset)
assert.Len(t, kustomizeSource.Kustomize.Images, 2)
updated, nothingToUnset = unset(kustomizeSource, unsetOpts{kustomizeImages: []string{"old1=new:tag"}})
assert.Len(t, kustomizeSource.Kustomize.Images, 1)
assert.True(t, updated)
assert.False(t, nothingToUnset)
updated, nothingToUnset = unset(kustomizeSource, unsetOpts{kustomizeImages: []string{"old1=new:tag"}})
assert.False(t, updated)
assert.False(t, nothingToUnset)
assert.Len(t, kustomizeSource.Kustomize.Replicas, 2)
updated, nothingToUnset = unset(kustomizeSource, unsetOpts{kustomizeReplicas: []string{"my-deployment"}})
assert.Len(t, kustomizeSource.Kustomize.Replicas, 1)
assert.True(t, updated)
assert.False(t, nothingToUnset)
updated, nothingToUnset = unset(kustomizeSource, unsetOpts{kustomizeReplicas: []string{"my-deployment"}})
assert.False(t, updated)
assert.False(t, nothingToUnset)
assert.True(t, kustomizeSource.Kustomize.IgnoreMissingComponents)
updated, nothingToUnset = unset(kustomizeSource, unsetOpts{ignoreMissingComponents: true})
assert.False(t, kustomizeSource.Kustomize.IgnoreMissingComponents)
assert.True(t, updated)
assert.False(t, nothingToUnset)
updated, nothingToUnset = unset(kustomizeSource, unsetOpts{ignoreMissingComponents: true})
assert.False(t, updated)
assert.False(t, nothingToUnset)
assert.Len(t, helmSource.Helm.Parameters, 2)
updated, nothingToUnset = unset(helmSource, unsetOpts{parameters: []string{"name-1"}})
assert.Len(t, helmSource.Helm.Parameters, 1)
assert.True(t, updated)
assert.False(t, nothingToUnset)
updated, nothingToUnset = unset(helmSource, unsetOpts{parameters: []string{"name-1"}})
assert.False(t, updated)
assert.False(t, nothingToUnset)
assert.Len(t, helmSource.Helm.ValueFiles, 2)
updated, nothingToUnset = unset(helmSource, unsetOpts{valuesFiles: []string{"values-1.yaml"}})
assert.Len(t, helmSource.Helm.ValueFiles, 1)
assert.True(t, updated)
assert.False(t, nothingToUnset)
updated, nothingToUnset = unset(helmSource, unsetOpts{valuesFiles: []string{"values-1.yaml"}})
assert.False(t, updated)
assert.False(t, nothingToUnset)
assert.Equal(t, "some: yaml", helmSource.Helm.ValuesString())
updated, nothingToUnset = unset(helmSource, unsetOpts{valuesLiteral: true})
assert.Empty(t, helmSource.Helm.ValuesString())
assert.True(t, updated)
assert.False(t, nothingToUnset)
updated, nothingToUnset = unset(helmSource, unsetOpts{valuesLiteral: true})
assert.False(t, updated)
assert.False(t, nothingToUnset)
assert.True(t, helmSource.Helm.IgnoreMissingValueFiles)
updated, nothingToUnset = unset(helmSource, unsetOpts{ignoreMissingValueFiles: true})
assert.False(t, helmSource.Helm.IgnoreMissingValueFiles)
assert.True(t, updated)
assert.False(t, nothingToUnset)
updated, nothingToUnset = unset(helmSource, unsetOpts{ignoreMissingValueFiles: true})
assert.False(t, updated)
assert.False(t, nothingToUnset)
assert.True(t, helmSource.Helm.PassCredentials)
updated, nothingToUnset = unset(helmSource, unsetOpts{passCredentials: true})
assert.False(t, helmSource.Helm.PassCredentials)
assert.True(t, updated)
assert.False(t, nothingToUnset)
updated, nothingToUnset = unset(helmSource, unsetOpts{passCredentials: true})
assert.False(t, updated)
assert.False(t, nothingToUnset)
assert.Len(t, pluginSource.Plugin.Env, 2)
updated, nothingToUnset = unset(pluginSource, unsetOpts{pluginEnvs: []string{"env-1"}})
assert.Len(t, pluginSource.Plugin.Env, 1)
assert.True(t, updated)
assert.False(t, nothingToUnset)
updated, nothingToUnset = unset(pluginSource, unsetOpts{pluginEnvs: []string{"env-1"}})
assert.False(t, updated)
assert.False(t, nothingToUnset)
}
func Test_unset_nothingToUnset(t *testing.T) {
testCases := []struct {
name string
source v1alpha1.ApplicationSource
}{
{"kustomize", v1alpha1.ApplicationSource{Kustomize: &v1alpha1.ApplicationSourceKustomize{}}},
{"helm", v1alpha1.ApplicationSource{Helm: &v1alpha1.ApplicationSourceHelm{}}},
{"plugin", v1alpha1.ApplicationSource{Plugin: &v1alpha1.ApplicationSourcePlugin{}}},
}
for _, testCase := range testCases {
testCaseCopy := testCase
t.Run(testCaseCopy.name, func(t *testing.T) {
updated, nothingToUnset := unset(&testCaseCopy.source, unsetOpts{})
assert.False(t, updated)
assert.True(t, nothingToUnset)
})
}
}
func TestFilterAppResources(t *testing.T) {
// App resources
var (
appReplicaSet1 = v1alpha1.ResourceStatus{
Group: "apps",
Kind: "ReplicaSet",
Namespace: "default",
Name: "replicaSet-name1",
}
appReplicaSet2 = v1alpha1.ResourceStatus{
Group: "apps",
Kind: "ReplicaSet",
Namespace: "default",
Name: "replicaSet-name2",
}
appJob = v1alpha1.ResourceStatus{
Group: "batch",
Kind: "Job",
Namespace: "default",
Name: "job-name",
}
appService1 = v1alpha1.ResourceStatus{
Group: "",
Kind: "Service",
Namespace: "default",
Name: "service-name1",
}
appService2 = v1alpha1.ResourceStatus{
Group: "",
Kind: "Service",
Namespace: "default",
Name: "service-name2",
}
appDeployment = v1alpha1.ResourceStatus{
Group: "apps",
Kind: "Deployment",
Namespace: "default",
Name: "deployment-name",
}
)
app := v1alpha1.Application{
Status: v1alpha1.ApplicationStatus{
Resources: []v1alpha1.ResourceStatus{
appReplicaSet1, appReplicaSet2, appJob, appService1, appService2, appDeployment,
},
},
}
// Resource filters
var (
blankValues = v1alpha1.SyncOperationResource{
Group: "",
Kind: "",
Name: "",
Namespace: "",
Exclude: false,
}
// *:*:*
includeAllResources = v1alpha1.SyncOperationResource{
Group: "*",
Kind: "*",
Name: "*",
Namespace: "",
Exclude: false,
}
// !*:*:*
excludeAllResources = v1alpha1.SyncOperationResource{
Group: "*",
Kind: "*",
Name: "*",
Namespace: "",
Exclude: true,
}
// *:Service:*
includeAllServiceResources = v1alpha1.SyncOperationResource{
Group: "*",
Kind: "Service",
Name: "*",
Namespace: "",
Exclude: false,
}
// !*:Service:*
excludeAllServiceResources = v1alpha1.SyncOperationResource{
Group: "*",
Kind: "Service",
Name: "*",
Namespace: "",
Exclude: true,
}
// apps:ReplicaSet:*
includeAllReplicaSetResource = v1alpha1.SyncOperationResource{
Group: "apps",
Kind: "ReplicaSet",
Name: "*",
Namespace: "",
Exclude: false,
}
// apps:ReplicaSet:replicaSet-name1
includeReplicaSet1Resource = v1alpha1.SyncOperationResource{
Group: "apps",
Kind: "ReplicaSet",
Name: "replicaSet-name1",
Namespace: "",
Exclude: false,
}
// !apps:ReplicaSet:replicaSet-name2
excludeReplicaSet2Resource = v1alpha1.SyncOperationResource{
Group: "apps",
Kind: "ReplicaSet",
Name: "replicaSet-name2",
Namespace: "",
Exclude: true,
}
)
// Filtered resources
var (
replicaSet1 = v1alpha1.SyncOperationResource{
Group: "apps",
Kind: "ReplicaSet",
Namespace: "default",
Name: "replicaSet-name1",
}
replicaSet2 = v1alpha1.SyncOperationResource{
Group: "apps",
Kind: "ReplicaSet",
Namespace: "default",
Name: "replicaSet-name2",
}
job = v1alpha1.SyncOperationResource{
Group: "batch",
Kind: "Job",
Namespace: "default",
Name: "job-name",
}
service1 = v1alpha1.SyncOperationResource{
Group: "",
Kind: "Service",
Namespace: "default",
Name: "service-name1",
}
service2 = v1alpha1.SyncOperationResource{
Group: "",
Kind: "Service",
Namespace: "default",
Name: "service-name2",
}
deployment = v1alpha1.SyncOperationResource{
Group: "apps",
Kind: "Deployment",
Namespace: "default",
Name: "deployment-name",
}
)
tests := []struct {
testName string
selectedResources []*v1alpha1.SyncOperationResource
expectedResult []*v1alpha1.SyncOperationResource
}{
// --resource apps:ReplicaSet:replicaSet-name1 --resource *:Service:*
{
testName: "Include ReplicaSet replicaSet-name1 resource and all service resources",
selectedResources: []*v1alpha1.SyncOperationResource{&includeAllServiceResources, &includeReplicaSet1Resource},
expectedResult: []*v1alpha1.SyncOperationResource{&replicaSet1, &service1, &service2},
},
// --resource apps:ReplicaSet:replicaSet-name1 --resource !*:Service:*
{
testName: "Include ReplicaSet replicaSet-name1 resource and exclude all service resources",
selectedResources: []*v1alpha1.SyncOperationResource{&excludeAllServiceResources, &includeReplicaSet1Resource},
expectedResult: []*v1alpha1.SyncOperationResource{&replicaSet1},
},
// --resource !apps:ReplicaSet:replicaSet-name2 --resource !*:Service:*
{
testName: "Exclude ReplicaSet replicaSet-name2 resource and all service resources",
selectedResources: []*v1alpha1.SyncOperationResource{&excludeReplicaSet2Resource, &excludeAllServiceResources},
expectedResult: []*v1alpha1.SyncOperationResource{&replicaSet1, &job, &deployment},
},
// --resource !apps:ReplicaSet:replicaSet-name2
{
testName: "Exclude ReplicaSet replicaSet-name2 resource",
selectedResources: []*v1alpha1.SyncOperationResource{&excludeReplicaSet2Resource},
expectedResult: []*v1alpha1.SyncOperationResource{&replicaSet1, &job, &service1, &service2, &deployment},
},
// --resource apps:ReplicaSet:replicaSet-name1
{
testName: "Include ReplicaSet replicaSet-name1 resource",
selectedResources: []*v1alpha1.SyncOperationResource{&includeReplicaSet1Resource},
expectedResult: []*v1alpha1.SyncOperationResource{&replicaSet1},
},
// --resource apps:ReplicaSet:* --resource !apps:ReplicaSet:replicaSet-name2
{
testName: "Include All ReplicaSet resource and exclude replicaSet-name1 resource",
selectedResources: []*v1alpha1.SyncOperationResource{&includeAllReplicaSetResource, &excludeReplicaSet2Resource},
expectedResult: []*v1alpha1.SyncOperationResource{&replicaSet1},
},
// --resource !*:Service:*
{
testName: "Exclude Service resources",
selectedResources: []*v1alpha1.SyncOperationResource{&excludeAllServiceResources},
expectedResult: []*v1alpha1.SyncOperationResource{&replicaSet1, &replicaSet2, &job, &deployment},
},
// --resource *:Service:*
{
testName: "Include Service resources",
selectedResources: []*v1alpha1.SyncOperationResource{&includeAllServiceResources},
expectedResult: []*v1alpha1.SyncOperationResource{&service1, &service2},
},
// --resource !*:*:*
{
testName: "Exclude all resources",
selectedResources: []*v1alpha1.SyncOperationResource{&excludeAllResources},
expectedResult: nil,
},
// --resource *:*:*
{
testName: "Include all resources",
selectedResources: []*v1alpha1.SyncOperationResource{&includeAllResources},
expectedResult: []*v1alpha1.SyncOperationResource{&replicaSet1, &replicaSet2, &job, &service1, &service2, &deployment},
},
{
testName: "No Filters",
selectedResources: []*v1alpha1.SyncOperationResource{&blankValues},
expectedResult: nil,
},
{
testName: "Empty Filter",
selectedResources: []*v1alpha1.SyncOperationResource{},
expectedResult: nil,
},
}
for _, test := range tests {
t.Run(test.testName, func(t *testing.T) {
filteredResources := filterAppResources(&app, test.selectedResources)
assert.Equal(t, test.expectedResult, filteredResources)
})
}
}
func TestParseSelectedResources(t *testing.T) {
resources := []string{
"v1alpha:Application:test",
"v1alpha:Application:namespace/test",
"!v1alpha:Application:test",
"apps:Deployment:default/test",
"!*:*:*",
}
operationResources, err := parseSelectedResources(resources)
require.NoError(t, err)
assert.Len(t, operationResources, 5)
assert.Equal(t, v1alpha1.SyncOperationResource{
Namespace: "",
Name: "test",
Kind: application.ApplicationKind,
Group: "v1alpha",
}, *operationResources[0])
assert.Equal(t, v1alpha1.SyncOperationResource{
Namespace: "namespace",
Name: "test",
Kind: application.ApplicationKind,
Group: "v1alpha",
}, *operationResources[1])
assert.Equal(t, v1alpha1.SyncOperationResource{
Namespace: "",
Name: "test",
Kind: "Application",
Group: "v1alpha",
Exclude: true,
}, *operationResources[2])
assert.Equal(t, v1alpha1.SyncOperationResource{
Namespace: "default",
Name: "test",
Kind: "Deployment",
Group: "apps",
Exclude: false,
}, *operationResources[3])
assert.Equal(t, v1alpha1.SyncOperationResource{
Namespace: "",
Name: "*",
Kind: "*",
Group: "*",
Exclude: true,
}, *operationResources[4])
}
func TestParseSelectedResourcesIncorrect(t *testing.T) {
resources := []string{"v1alpha:test", "v1alpha:Application:namespace/test"}
_, err := parseSelectedResources(resources)
assert.ErrorContains(t, err, "v1alpha:test")
}
func TestParseSelectedResourcesIncorrectNamespace(t *testing.T) {
resources := []string{"v1alpha:Application:namespace/test/unknown"}
_, err := parseSelectedResources(resources)
assert.ErrorContains(t, err, "v1alpha:Application:namespace/test/unknown")
}
func TestParseSelectedResourcesEmptyList(t *testing.T) {
var resources []string
operationResources, err := parseSelectedResources(resources)
require.NoError(t, err)
assert.Empty(t, operationResources)
}
func TestPrintApplicationTableNotWide(t *testing.T) {
output, err := captureOutput(func() error {
app := &v1alpha1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: "app-name",
},
Spec: v1alpha1.ApplicationSpec{
Destination: v1alpha1.ApplicationDestination{
Server: "http://localhost:8080",
Namespace: "default",
},
Project: "prj",
},
Status: v1alpha1.ApplicationStatus{
Sync: v1alpha1.SyncStatus{
Status: "OutOfSync",
},
Health: v1alpha1.AppHealthStatus{
Status: "Healthy",
},
},
}
output := "table"
printApplicationTable([]v1alpha1.Application{*app, *app}, &output)
return nil
})
require.NoError(t, err)
expectation := "NAME CLUSTER NAMESPACE PROJECT STATUS HEALTH SYNCPOLICY CONDITIONS\napp-name http://localhost:8080 default prj OutOfSync Healthy Manual <none>\napp-name http://localhost:8080 default prj OutOfSync Healthy Manual <none>\n"
assert.Equal(t, output, expectation)
}
func TestPrintApplicationTableWide(t *testing.T) {
output, err := captureOutput(func() error {
app := &v1alpha1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: "app-name",
},
Spec: v1alpha1.ApplicationSpec{
Destination: v1alpha1.ApplicationDestination{
Server: "http://localhost:8080",
Namespace: "default",
},
Source: &v1alpha1.ApplicationSource{
RepoURL: "https://github.com/argoproj/argocd-example-apps",
Path: "guestbook",
TargetRevision: "123",
},
Project: "prj",
},
Status: v1alpha1.ApplicationStatus{
Sync: v1alpha1.SyncStatus{
Status: "OutOfSync",
},
Health: v1alpha1.AppHealthStatus{
Status: "Healthy",
},
},
}
output := "wide"
printApplicationTable([]v1alpha1.Application{*app, *app}, &output)
return nil
})
require.NoError(t, err)
expectation := "NAME CLUSTER NAMESPACE PROJECT STATUS HEALTH SYNCPOLICY CONDITIONS REPO PATH TARGET\napp-name http://localhost:8080 default prj OutOfSync Healthy Manual <none> https://github.com/argoproj/argocd-example-apps guestbook 123\napp-name http://localhost:8080 default prj OutOfSync Healthy Manual <none> https://github.com/argoproj/argocd-example-apps guestbook 123\n"
assert.Equal(t, output, expectation)
}
func TestResourceStateKey(t *testing.T) {
rst := resourceState{
Group: "group",
Kind: "kind",
Namespace: "namespace",
Name: "name",
}
key := rst.Key()
assert.Equal(t, "group/kind/namespace/name", key)
}
func TestFormatItems(t *testing.T) {
rst := resourceState{
Group: "group",
Kind: "kind",
Namespace: "namespace",
Name: "name",
Status: "status",
Health: "health",
Hook: "hook",
Message: "message",
}
items := rst.FormatItems()
assert.Equal(t, "group", items[1])
assert.Equal(t, "kind", items[2])
assert.Equal(t, "namespace", items[3])
assert.Equal(t, "name", items[4])
assert.Equal(t, "status", items[5])
assert.Equal(t, "health", items[6])
assert.Equal(t, "hook", items[7])
assert.Equal(t, "message", items[8])
}
func TestMerge(t *testing.T) {
rst := resourceState{
Group: "group",
Kind: "kind",
Namespace: "namespace",
Name: "name",
Status: "status",
Health: "health",
Hook: "hook",
Message: "message",
}
rstNew := resourceState{
Group: "group",
Kind: "kind",
Namespace: "namespace",
Name: "name",
Status: "status",
Health: "health",
Hook: "hook2",
Message: "message2",
}
updated := rst.Merge(&rstNew)
assert.True(t, updated)
assert.Equal(t, rstNew.Hook, rst.Hook)
assert.Equal(t, rstNew.Message, rst.Message)
assert.Equal(t, rstNew.Status, rst.Status)
}
func TestMergeWitoutUpdate(t *testing.T) {
rst := resourceState{
Group: "group",
Kind: "kind",
Namespace: "namespace",
Name: "name",
Status: "status",
Health: "health",
Hook: "hook",
Message: "message",
}
rstNew := resourceState{
Group: "group",
Kind: "kind",
Namespace: "namespace",
Name: "name",
Status: "status",
Health: "health",
Hook: "hook",
Message: "message",
}
updated := rst.Merge(&rstNew)
assert.False(t, updated)
}
func TestCheckResourceStatus(t *testing.T) {
t.Run("Degraded, Suspended and health status passed", func(t *testing.T) {
res := checkResourceStatus(watchOpts{
suspended: true,
health: true,
degraded: true,
}, string(health.HealthStatusHealthy), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{}, true)
assert.True(t, res)
})
t.Run("Degraded, Suspended and health status failed", func(t *testing.T) {
res := checkResourceStatus(watchOpts{
suspended: true,
health: true,
degraded: true,
}, string(health.HealthStatusProgressing), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{}, true)
assert.False(t, res)
})
t.Run("Suspended and health status passed", func(t *testing.T) {
res := checkResourceStatus(watchOpts{
suspended: true,
health: true,
}, string(health.HealthStatusHealthy), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{}, true)
assert.True(t, res)
})
t.Run("Suspended and health status failed", func(t *testing.T) {
res := checkResourceStatus(watchOpts{
suspended: true,
health: true,
}, string(health.HealthStatusProgressing), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{}, true)
assert.False(t, res)
})
t.Run("Suspended passed", func(t *testing.T) {
res := checkResourceStatus(watchOpts{
suspended: true,
health: false,
}, string(health.HealthStatusSuspended), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{}, true)
assert.True(t, res)
})
t.Run("Suspended failed", func(t *testing.T) {
res := checkResourceStatus(watchOpts{
suspended: true,
health: false,
}, string(health.HealthStatusProgressing), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{}, true)
assert.False(t, res)
})
t.Run("Health passed", func(t *testing.T) {
res := checkResourceStatus(watchOpts{
suspended: false,
health: true,
}, string(health.HealthStatusHealthy), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{}, true)
assert.True(t, res)
})
t.Run("Health failed", func(t *testing.T) {
res := checkResourceStatus(watchOpts{
suspended: false,
health: true,
}, string(health.HealthStatusProgressing), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{}, true)
assert.False(t, res)
})
t.Run("Synced passed", func(t *testing.T) {
res := checkResourceStatus(watchOpts{}, string(health.HealthStatusProgressing), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{}, true)
assert.True(t, res)
})
t.Run("Synced failed", func(t *testing.T) {
res := checkResourceStatus(watchOpts{}, string(health.HealthStatusProgressing), string(v1alpha1.SyncStatusCodeOutOfSync), &v1alpha1.Operation{}, true)
assert.True(t, res)
})
t.Run("Degraded passed", func(t *testing.T) {
res := checkResourceStatus(watchOpts{
suspended: false,
health: false,
degraded: true,
}, string(health.HealthStatusDegraded), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{}, true)
assert.True(t, res)
})
t.Run("Degraded failed", func(t *testing.T) {
res := checkResourceStatus(watchOpts{
suspended: false,
health: false,
degraded: true,
}, string(health.HealthStatusProgressing), string(v1alpha1.SyncStatusCodeSynced), &v1alpha1.Operation{}, true)
assert.False(t, res)
})
}
func Test_hasAppChanged(t *testing.T) {
type args struct {
appReq *v1alpha1.Application
appRes *v1alpha1.Application
upsert bool
}
tests := []struct {
name string
args args
want bool
}{
{
name: "App has changed - Labels, Annotations, Finalizers empty",
args: args{
appReq: testApp("foo", "default", map[string]string{}, map[string]string{}, []string{}),
appRes: testApp("foo", "foo", nil, nil, nil),
upsert: true,
},
want: true,
},
{
name: "App unchanged - Labels, Annotations, Finalizers populated",
args: args{
appReq: testApp("foo", "default", map[string]string{"foo": "bar"}, map[string]string{"foo": "bar"}, []string{"foo"}),
appRes: testApp("foo", "default", map[string]string{"foo": "bar"}, map[string]string{"foo": "bar"}, []string{"foo"}),
upsert: true,
},
want: false,
},
{
name: "Apps unchanged - Using empty maps/list locally versus server returning nil",
args: args{
appReq: testApp("foo", "default", map[string]string{}, map[string]string{}, []string{}),
appRes: testApp("foo", "default", nil, nil, nil),
upsert: true,
},
want: false,
},
{
name: "App unchanged - Using empty project locally versus server returning default",
args: args{
appReq: testApp("foo", "", map[string]string{}, map[string]string{}, []string{}),
appRes: testApp("foo", "default", nil, nil, nil),
},
want: false,
},
{
name: "App unchanged - From upsert=false",
args: args{
appReq: testApp("foo", "foo", map[string]string{}, map[string]string{}, []string{}),
appRes: testApp("foo", "default", nil, nil, nil),
upsert: false,
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := hasAppChanged(tt.args.appReq, tt.args.appRes, tt.args.upsert)
assert.Equalf(t, tt.want, got, "hasAppChanged() = %v, want %v", got, tt.want)
})
}
}
func testApp(name, project string, labels map[string]string, annotations map[string]string, finalizers []string) *v1alpha1.Application {
return &v1alpha1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Labels: labels,
Annotations: annotations,
Finalizers: finalizers,
},
Spec: v1alpha1.ApplicationSpec{
Source: &v1alpha1.ApplicationSource{
RepoURL: "https://github.com/argoproj/argocd-example-apps.git",
},
Project: project,
},
}
}
func TestWaitOnApplicationStatus_JSON_YAML_WideOutput(t *testing.T) {
acdClient := &customAcdClient{&fakeAcdClient{}}
ctx := t.Context()
var selectResource []*v1alpha1.SyncOperationResource
watch := watchOpts{
sync: false,
health: false,
operation: true,
suspended: false,
}
watch = getWatchOpts(watch)
output, err := captureOutput(
func() error {
_, _, _ = waitOnApplicationStatus(ctx, acdClient, "app-name", 0, watch, selectResource, "json")
return nil
},
)
require.NoError(t, err)
assert.True(t, json.Valid([]byte(output)))
output, err = captureOutput(func() error {
_, _, _ = waitOnApplicationStatus(ctx, acdClient, "app-name", 0, watch, selectResource, "yaml")
return nil
})
require.NoError(t, err)
err = yaml.Unmarshal([]byte(output), &v1alpha1.Application{})
require.NoError(t, err)
output, _ = captureOutput(func() error {
_, _, _ = waitOnApplicationStatus(ctx, acdClient, "app-name", 0, watch, selectResource, "")
return nil
})
timeStr := time.Now().Format("2006-01-02T15:04:05-07:00")
expectation := `TIMESTAMP GROUP KIND NAMESPACE NAME STATUS HEALTH HOOK MESSAGE
%s Service default service-name1 Synced Healthy
%s apps Deployment default test Synced Healthy
Name: argocd/test
Project: default
Server: local
Namespace: argocd
URL: http://localhost:8080/applications/app-name
Source:
- Repo: test
Target: master
Path: /test
Helm Values: path1,path2
Name Prefix: prefix
SyncWindow: Sync Allowed
Sync Policy: Automated (Prune)
Sync Status: OutOfSync from master
Health Status: Progressing
Operation: Sync
Sync Revision: revision
Phase:
Start: 0001-01-01 00:00:00 +0000 UTC
Finished: 2020-11-10 23:00:00 +0000 UTC
Duration: 2333448h16m18.871345152s
Message: test
GROUP KIND NAMESPACE NAME STATUS HEALTH HOOK MESSAGE
Service default service-name1 Synced Healthy
apps Deployment default test Synced Healthy
`
expectation = fmt.Sprintf(expectation, timeStr, timeStr)
expectationParts := strings.Split(expectation, "\n")
// Trim trailing whitespace from each line
for i := range expectationParts {
expectationParts[i] = strings.TrimRight(expectationParts[i], " \t")
}
slices.Sort(expectationParts)
expectationSorted := strings.Join(expectationParts, "\n")
outputParts := strings.Split(output, "\n")
// Trim trailing whitespace from each line
for i := range outputParts {
outputParts[i] = strings.TrimRight(outputParts[i], " \t")
}
slices.Sort(outputParts)
outputSorted := strings.Join(outputParts, "\n")
// Need to compare sorted since map entries may not keep a specific order during serialization, leading to flakiness.
assert.Equalf(t, expectationSorted, outputSorted, "Incorrect output %q, should be %q (items order doesn't matter)", output, expectation)
}
func TestWaitOnApplicationStatus_JSON_YAML_WideOutput_With_Timeout(t *testing.T) {
acdClient := &customAcdClient{&fakeAcdClient{simulateTimeout: 15}}
ctx := t.Context()
var selectResource []*v1alpha1.SyncOperationResource
watch := watchOpts{
sync: false,
health: false,
operation: true,
suspended: false,
}
watch = getWatchOpts(watch)
output, _ := captureOutput(func() error {
_, _, _ = waitOnApplicationStatus(ctx, acdClient, "app-name", 5, watch, selectResource, "")
return nil
})
timeStr := time.Now().Format("2006-01-02T15:04:05-07:00")
expectation := `TIMESTAMP GROUP KIND NAMESPACE NAME STATUS HEALTH HOOK MESSAGE
%s Service default service-name1 Synced Healthy
%s apps Deployment default test Synced Healthy
The command timed out waiting for the conditions to be met.
This is the state of the app after wait timed out:
Name: argocd/test
Project: default
Server: local
Namespace: argocd
URL: http://localhost:8080/applications/app-name
Source:
- Repo: test
Target: master
Path: /test
Helm Values: path1,path2
Name Prefix: prefix
SyncWindow: Sync Allowed
Sync Policy: Automated (Prune)
Sync Status: OutOfSync from master
Health Status: Progressing
Operation: Sync
Sync Revision: revision
Phase:
Start: 0001-01-01 00:00:00 +0000 UTC
Finished: 2020-11-10 23:00:00 +0000 UTC
Duration: 2333448h16m18.871345152s
Message: test
GROUP KIND NAMESPACE NAME STATUS HEALTH HOOK MESSAGE
Service default service-name1 Synced Healthy
apps Deployment default test Synced Healthy
`
expectation = fmt.Sprintf(expectation, timeStr, timeStr)
expectationParts := strings.Split(expectation, "\n")
// Trim trailing whitespace from each line
for i := range expectationParts {
expectationParts[i] = strings.TrimRight(expectationParts[i], " \t")
}
slices.Sort(expectationParts)
expectationSorted := strings.Join(expectationParts, "\n")
outputParts := strings.Split(output, "\n")
// Trim trailing whitespace from each line
for i := range outputParts {
outputParts[i] = strings.TrimRight(outputParts[i], " \t")
}
slices.Sort(outputParts)
outputSorted := strings.Join(outputParts, "\n")
// Need to compare sorted since map entries may not keep a specific order during serialization, leading to flakiness.
assert.Equalf(t, expectationSorted, outputSorted, "Incorrect output %q, should be %q (items order doesn't matter)", output, expectation)
}
// TestCheckAppWaitConditions covers the branches of the pure readiness helper
// used by `waitOnApplicationStatus`. Related to #12211.
func TestCheckAppWaitConditions(t *testing.T) {
syncHealth := watchOpts{sync: true, health: true, operation: true}
finishedAt := metav1.NewTime(time.Date(2020, time.November, 10, 23, 0, 0, 0, time.UTC))
afterFinished := metav1.NewTime(finishedAt.Add(time.Minute))
beforeFinished := metav1.NewTime(finishedAt.Add(-time.Minute))
appWith := func(sync v1alpha1.SyncStatusCode, h health.HealthStatusCode) *v1alpha1.Application {
return &v1alpha1.Application{
Status: v1alpha1.ApplicationStatus{
Sync: v1alpha1.SyncStatus{Status: sync},
Health: v1alpha1.AppHealthStatus{Status: h},
},
}
}
tests := []struct {
name string
app *v1alpha1.Application
watch watchOpts
selectedResources []*v1alpha1.SyncOperationResource
wantReady bool
wantOpInProgress bool
}{
{
name: "synced and healthy with no operation returns ready",
app: appWith(v1alpha1.SyncStatusCodeSynced, health.HealthStatusHealthy),
watch: syncHealth,
wantReady: true,
},
{
name: "pending operation marks operation in progress",
app: &v1alpha1.Application{Operation: &v1alpha1.Operation{Sync: &v1alpha1.SyncOperation{}}, Status: v1alpha1.ApplicationStatus{Sync: v1alpha1.SyncStatus{Status: v1alpha1.SyncStatusCodeSynced}, Health: v1alpha1.AppHealthStatus{Status: health.HealthStatusHealthy}}},
watch: syncHealth,
wantReady: false, // !watch.operation || operationStatus == nil → false when operation pending and watch.operation
wantOpInProgress: true,
},
{
name: "operation state without FinishedAt marks operation in progress",
app: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
Sync: v1alpha1.SyncStatus{Status: v1alpha1.SyncStatusCodeSynced},
Health: v1alpha1.AppHealthStatus{Status: health.HealthStatusHealthy},
OperationState: &v1alpha1.OperationState{Operation: v1alpha1.Operation{Sync: &v1alpha1.SyncOperation{}}},
}},
watch: syncHealth,
wantReady: true,
wantOpInProgress: true,
},
{
name: "finished operation not yet reconciled marks operation in progress",
app: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
Sync: v1alpha1.SyncStatus{Status: v1alpha1.SyncStatusCodeSynced},
Health: v1alpha1.AppHealthStatus{Status: health.HealthStatusHealthy},
OperationState: &v1alpha1.OperationState{Operation: v1alpha1.Operation{Sync: &v1alpha1.SyncOperation{}}, FinishedAt: &finishedAt},
ReconciledAt: &beforeFinished,
}},
watch: syncHealth,
wantReady: true,
wantOpInProgress: true,
},
{
name: "finished operation reconciled afterwards is not in progress",
app: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
Sync: v1alpha1.SyncStatus{Status: v1alpha1.SyncStatusCodeSynced},
Health: v1alpha1.AppHealthStatus{Status: health.HealthStatusHealthy},
OperationState: &v1alpha1.OperationState{Operation: v1alpha1.Operation{Sync: &v1alpha1.SyncOperation{}}, FinishedAt: &finishedAt},
ReconciledAt: &afterFinished,
}},
watch: syncHealth,
wantReady: true,
},
{
name: "out of sync app is not ready",
app: appWith(v1alpha1.SyncStatusCodeOutOfSync, health.HealthStatusHealthy),
watch: syncHealth,
wantReady: false,
},
{
name: "selected resources all ready returns ready",
app: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
Resources: []v1alpha1.ResourceStatus{
{Kind: "Deployment", Name: "a", Namespace: "default", Status: v1alpha1.SyncStatusCodeSynced, Health: &v1alpha1.HealthStatus{Status: health.HealthStatusHealthy}},
},
}},
watch: syncHealth,
selectedResources: []*v1alpha1.SyncOperationResource{{Kind: "Deployment", Name: "a", Namespace: "default"}},
wantReady: true,
},
{
name: "selected resources with one not ready returns not ready",
app: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
Resources: []v1alpha1.ResourceStatus{
{Kind: "Deployment", Name: "a", Namespace: "default", Status: v1alpha1.SyncStatusCodeSynced, Health: &v1alpha1.HealthStatus{Status: health.HealthStatusHealthy}},
{Kind: "Deployment", Name: "b", Namespace: "default", Status: v1alpha1.SyncStatusCodeOutOfSync, Health: &v1alpha1.HealthStatus{Status: health.HealthStatusProgressing}},
},
}},
watch: syncHealth,
selectedResources: []*v1alpha1.SyncOperationResource{
{Kind: "Deployment", Name: "a", Namespace: "default"},
{Kind: "Deployment", Name: "b", Namespace: "default"},
},
wantReady: false,
},
{
// Regression: CurrentOperation set without LastSuccessfulOperation
// must not panic on the hydration check.
name: "hydration in progress without prior successful hydration does not panic",
app: &v1alpha1.Application{Status: v1alpha1.ApplicationStatus{
Sync: v1alpha1.SyncStatus{Status: v1alpha1.SyncStatusCodeSynced},
Health: v1alpha1.AppHealthStatus{Status: health.HealthStatusHealthy},
SourceHydrator: v1alpha1.SourceHydratorStatus{
CurrentOperation: &v1alpha1.HydrateOperation{Phase: v1alpha1.HydrateOperationPhaseHydrating},
},
}},
watch: watchOpts{sync: true, health: true, hydrated: true},
wantReady: false, // hydration not finished yet
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
ready, opInProgress := checkAppWaitConditions(tc.app, tc.watch, tc.selectedResources)
assert.Equal(t, tc.wantReady, ready, "ready")
assert.Equal(t, tc.wantOpInProgress, opInProgress, "operationInProgress")
})
}
}
// TestWaitOnApplicationStatus_ReturnsImmediatelyWhenAlreadyInDesiredState verifies
// that `argocd app wait` returns right away when the application already matches
// the requested conditions, instead of hanging on the watch stream until timeout.
// Regression test for https://github.com/argoproj/argo-cd/issues/12211.
func TestWaitOnApplicationStatus_ReturnsImmediatelyWhenAlreadyInDesiredState(t *testing.T) {
// simulateTimeout controls how long the fake watch blocks before emitting an
// event. If the fix is removed, waitOnApplicationStatus would block for this
// long before observing the readiness condition; with the fix it returns
// from the initial Get result without touching the watch channel.
acdClient := &readyAcdClient{&fakeAcdClient{simulateTimeout: 10}}
ctx := t.Context()
var selectResource []*v1alpha1.SyncOperationResource
watch := watchOpts{
sync: true,
health: true,
operation: true,
}
start := time.Now()
_, _, err := waitOnApplicationStatus(ctx, acdClient, "app-name", 0, watch, selectResource, "json")
elapsed := time.Since(start)
require.NoError(t, err)
assert.Less(t, elapsed, 2*time.Second, "wait should return immediately when app is already in desired state, took %s", elapsed)
}
// TestWaitOnApplicationStatus_DeleteWatchSkipsEarlyReturn verifies that
// `argocd app wait --delete` does not short-circuit on the initial Get and
// instead consumes the watch for the Deleted event — the Get can only return
// an existing application, so its state cannot satisfy the delete condition.
func TestWaitOnApplicationStatus_DeleteWatchSkipsEarlyReturn(t *testing.T) {
acdClient := &deleteAcdClient{fakeAcdClient: &fakeAcdClient{}}
ctx := t.Context()
watch := watchOpts{delete: true}
app, opState, err := waitOnApplicationStatus(ctx, acdClient, "app-name", 0, watch, nil, "")
require.NoError(t, err)
assert.Nil(t, app)
assert.Nil(t, opState)
}
// TestWaitOnApplicationStatus_ReturnsFromWatchLoopWhenEventSatisfiesConditions
// exercises the watch loop path: the initial Get returns an OutOfSync app so the
// early return is skipped, and a subsequent event carries the desired state.
// Covers the helper call and readiness return inside the watch loop.
func TestWaitOnApplicationStatus_ReturnsFromWatchLoopWhenEventSatisfiesConditions(t *testing.T) {
acdClient := &readyEventAcdClient{fakeAcdClient: &fakeAcdClient{}}
ctx := t.Context()
watch := watchOpts{sync: true, health: true}
app, _, err := waitOnApplicationStatus(ctx, acdClient, "app-name", 0, watch, nil, "json")
require.NoError(t, err)
// The function returns via the readiness path inside the watch loop.
// The returned app may be re-fetched by printFinalStatus so we only
// assert the function returned successfully rather than matching on its
// status fields.
assert.NotNil(t, app)
}
// readyEventAcdClient returns an OutOfSync app from Get so the early-return is
// skipped, then emits a single event carrying a Synced+Healthy app with a
// pending non-dry-run Operation (to exercise the refresh side-effect branch in
// the watch loop).
type readyEventAcdClient struct {
*fakeAcdClient
}
func (c *readyEventAcdClient) WatchApplicationWithRetry(_ context.Context, _ string, _ string) chan *v1alpha1.ApplicationWatchEvent {
appEventsCh := make(chan *v1alpha1.ApplicationWatchEvent, 1)
appEventsCh <- &v1alpha1.ApplicationWatchEvent{
Type: watch.Modified,
Application: v1alpha1.Application{
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "argocd"},
Operation: &v1alpha1.Operation{Sync: &v1alpha1.SyncOperation{}},
Status: v1alpha1.ApplicationStatus{
Sync: v1alpha1.SyncStatus{Status: v1alpha1.SyncStatusCodeSynced},
Health: v1alpha1.AppHealthStatus{Status: health.HealthStatusHealthy},
},
},
}
close(appEventsCh)
return appEventsCh
}
func (c *readyEventAcdClient) NewApplicationClientOrDie() (io.Closer, applicationpkg.ApplicationServiceClient) {
return &fakeConnection{}, &fakeAppServiceClient{}
}
func (c *readyEventAcdClient) NewSettingsClientOrDie() (io.Closer, settingspkg.SettingsServiceClient) {
return &fakeConnection{}, &fakeSettingsServiceClient{}
}
// deleteAcdClient emits a single Deleted event immediately so the watch loop
// can return without relying on a timeout.
type deleteAcdClient struct {
*fakeAcdClient
}
func (c *deleteAcdClient) WatchApplicationWithRetry(_ context.Context, _ string, _ string) chan *v1alpha1.ApplicationWatchEvent {
appEventsCh := make(chan *v1alpha1.ApplicationWatchEvent, 1)
appEventsCh <- &v1alpha1.ApplicationWatchEvent{
Type: watch.Deleted,
Application: v1alpha1.Application{
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "argocd"},
},
}
close(appEventsCh)
return appEventsCh
}
func (c *deleteAcdClient) NewApplicationClientOrDie() (io.Closer, applicationpkg.ApplicationServiceClient) {
return &fakeConnection{}, &readyFakeAppServiceClient{}
}
func (c *deleteAcdClient) NewSettingsClientOrDie() (io.Closer, settingspkg.SettingsServiceClient) {
return &fakeConnection{}, &fakeSettingsServiceClient{}
}
// readyAcdClient is like customAcdClient but returns an application that is
// already Synced and Healthy with no operation pending.
type readyAcdClient struct {
*fakeAcdClient
}
func (c *readyAcdClient) WatchApplicationWithRetry(_ context.Context, _ string, _ string) chan *v1alpha1.ApplicationWatchEvent {
appEventsCh := make(chan *v1alpha1.ApplicationWatchEvent)
go func() {
// Block long enough that the test would clearly fail if the early
// return regresses. Never emit an event — mirrors the real-world
// behavior reported in #12211 where no events arrive because the
// application CR is not changing.
time.Sleep(time.Duration(c.simulateTimeout) * time.Second)
close(appEventsCh)
}()
return appEventsCh
}
func (c *readyAcdClient) NewApplicationClientOrDie() (io.Closer, applicationpkg.ApplicationServiceClient) {
return &fakeConnection{}, &readyFakeAppServiceClient{}
}
func (c *readyAcdClient) NewSettingsClientOrDie() (io.Closer, settingspkg.SettingsServiceClient) {
return &fakeConnection{}, &fakeSettingsServiceClient{}
}
type readyFakeAppServiceClient struct {
fakeAppServiceClient
}
func (c *readyFakeAppServiceClient) Get(_ context.Context, _ *applicationpkg.ApplicationQuery, _ ...grpc.CallOption) (*v1alpha1.Application, error) {
return &v1alpha1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "argocd",
},
Spec: v1alpha1.ApplicationSpec{
Project: "default",
Destination: v1alpha1.ApplicationDestination{Server: "local", Namespace: "argocd"},
Source: &v1alpha1.ApplicationSource{RepoURL: "test", TargetRevision: "master", Path: "/test"},
},
Status: v1alpha1.ApplicationStatus{
Sync: v1alpha1.SyncStatus{Status: v1alpha1.SyncStatusCodeSynced},
Health: v1alpha1.AppHealthStatus{Status: health.HealthStatusHealthy},
},
}, nil
}
type customAcdClient struct {
*fakeAcdClient
}
func (c *customAcdClient) WatchApplicationWithRetry(ctx context.Context, _ string, _ string) chan *v1alpha1.ApplicationWatchEvent {
appEventsCh := make(chan *v1alpha1.ApplicationWatchEvent)
_, appClient := c.NewApplicationClientOrDie()
app, _ := appClient.Get(ctx, &applicationpkg.ApplicationQuery{})
newApp := v1alpha1.Application{
TypeMeta: app.TypeMeta,
ObjectMeta: app.ObjectMeta,
Spec: app.Spec,
Status: app.Status,
Operation: app.Operation,
}
go func() {
time.Sleep(time.Duration(c.simulateTimeout) * time.Second)
appEventsCh <- &v1alpha1.ApplicationWatchEvent{
Type: watch.Bookmark,
Application: newApp,
}
close(appEventsCh)
}()
return appEventsCh
}
func (c *customAcdClient) NewApplicationClientOrDie() (io.Closer, applicationpkg.ApplicationServiceClient) {
return &fakeConnection{}, &fakeAppServiceClient{}
}
func (c *customAcdClient) NewSettingsClientOrDie() (io.Closer, settingspkg.SettingsServiceClient) {
return &fakeConnection{}, &fakeSettingsServiceClient{}
}
type fakeConnection struct{}
func (c *fakeConnection) Close() error {
return nil
}
type fakeSettingsServiceClient struct{}
func (f fakeSettingsServiceClient) Get(_ context.Context, _ *settingspkg.SettingsQuery, _ ...grpc.CallOption) (*settingspkg.Settings, error) {
return &settingspkg.Settings{
URL: "http://localhost:8080",
}, nil
}
func (f fakeSettingsServiceClient) GetPlugins(_ context.Context, _ *settingspkg.SettingsQuery, _ ...grpc.CallOption) (*settingspkg.SettingsPluginsResponse, error) {
return nil, nil
}
type fakeAppServiceClient struct{}
func (c *fakeAppServiceClient) Get(_ context.Context, _ *applicationpkg.ApplicationQuery, _ ...grpc.CallOption) (*v1alpha1.Application, error) {
time := metav1.Date(2020, time.November, 10, 23, 0, 0, 0, time.UTC)
return &v1alpha1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: "test",
Namespace: "argocd",
},
Spec: v1alpha1.ApplicationSpec{
SyncPolicy: &v1alpha1.SyncPolicy{
Automated: &v1alpha1.SyncPolicyAutomated{
Prune: new(true),
},
},
Project: "default",
Destination: v1alpha1.ApplicationDestination{Server: "local", Namespace: "argocd"},
Source: &v1alpha1.ApplicationSource{
RepoURL: "test",
TargetRevision: "master",
Path: "/test",
Helm: &v1alpha1.ApplicationSourceHelm{
ValueFiles: []string{"path1", "path2"},
},
Kustomize: &v1alpha1.ApplicationSourceKustomize{NamePrefix: "prefix"},
},
},
Status: v1alpha1.ApplicationStatus{
Resources: []v1alpha1.ResourceStatus{
{
Group: "",
Kind: "Service",
Namespace: "default",
Name: "service-name1",
Status: "Synced",
Health: &v1alpha1.HealthStatus{
Status: health.HealthStatusHealthy,
Message: "health-message",
},
},
{
Group: "apps",
Kind: "Deployment",
Namespace: "default",
Name: "test",
Status: "Synced",
Health: &v1alpha1.HealthStatus{
Status: health.HealthStatusHealthy,
Message: "health-message",
},
},
},
OperationState: &v1alpha1.OperationState{
SyncResult: &v1alpha1.SyncOperationResult{
Revision: "revision",
},
FinishedAt: &time,
Message: "test",
},
Sync: v1alpha1.SyncStatus{
Status: v1alpha1.SyncStatusCodeOutOfSync,
},
Health: v1alpha1.AppHealthStatus{
Status: health.HealthStatusProgressing,
},
},
}, nil
}
func (c *fakeAppServiceClient) List(_ context.Context, _ *applicationpkg.ApplicationQuery, _ ...grpc.CallOption) (*v1alpha1.ApplicationList, error) {
return nil, nil
}
func (c *fakeAppServiceClient) ListResourceEvents(_ context.Context, _ *applicationpkg.ApplicationResourceEventsQuery, _ ...grpc.CallOption) (*eventspkg.EventList, error) {
return nil, nil
}
func (c *fakeAppServiceClient) Watch(_ context.Context, _ *applicationpkg.ApplicationQuery, _ ...grpc.CallOption) (applicationpkg.ApplicationService_WatchClient, error) {
return nil, nil
}
func (c *fakeAppServiceClient) Create(_ context.Context, _ *applicationpkg.ApplicationCreateRequest, _ ...grpc.CallOption) (*v1alpha1.Application, error) {
return nil, nil
}
func (c *fakeAppServiceClient) GetApplicationSyncWindows(_ context.Context, _ *applicationpkg.ApplicationSyncWindowsQuery, _ ...grpc.CallOption) (*applicationpkg.ApplicationSyncWindowsResponse, error) {
return nil, nil
}
func (c *fakeAppServiceClient) GetOCIMetadata(_ context.Context, _ *applicationpkg.RevisionMetadataQuery, _ ...grpc.CallOption) (*v1alpha1.OCIMetadata, error) {
return nil, nil
}
func (c *fakeAppServiceClient) RevisionMetadata(_ context.Context, _ *applicationpkg.RevisionMetadataQuery, _ ...grpc.CallOption) (*v1alpha1.RevisionMetadata, error) {
return nil, nil
}
func (c *fakeAppServiceClient) RevisionChartDetails(_ context.Context, _ *applicationpkg.RevisionMetadataQuery, _ ...grpc.CallOption) (*v1alpha1.ChartDetails, error) {
return nil, nil
}
func (c *fakeAppServiceClient) GetManifests(_ context.Context, _ *applicationpkg.ApplicationManifestQuery, _ ...grpc.CallOption) (*apiclient.ManifestResponse, error) {
return nil, nil
}
func (c *fakeAppServiceClient) GetManifestsWithFiles(_ context.Context, _ ...grpc.CallOption) (applicationpkg.ApplicationService_GetManifestsWithFilesClient, error) {
return nil, nil
}
func (c *fakeAppServiceClient) Update(_ context.Context, _ *applicationpkg.ApplicationUpdateRequest, _ ...grpc.CallOption) (*v1alpha1.Application, error) {
return nil, nil
}
func (c *fakeAppServiceClient) UpdateSpec(_ context.Context, _ *applicationpkg.ApplicationUpdateSpecRequest, _ ...grpc.CallOption) (*v1alpha1.ApplicationSpec, error) {
return nil, nil
}
func (c *fakeAppServiceClient) Patch(_ context.Context, _ *applicationpkg.ApplicationPatchRequest, _ ...grpc.CallOption) (*v1alpha1.Application, error) {
return nil, nil
}
func (c *fakeAppServiceClient) Delete(_ context.Context, _ *applicationpkg.ApplicationDeleteRequest, _ ...grpc.CallOption) (*applicationpkg.ApplicationResponse, error) {
return nil, nil
}
func (c *fakeAppServiceClient) Sync(_ context.Context, _ *applicationpkg.ApplicationSyncRequest, _ ...grpc.CallOption) (*v1alpha1.Application, error) {
return nil, nil
}
func (c *fakeAppServiceClient) ManagedResources(_ context.Context, _ *applicationpkg.ResourcesQuery, _ ...grpc.CallOption) (*applicationpkg.ManagedResourcesResponse, error) {
return nil, nil
}
func (c *fakeAppServiceClient) ResourceTree(_ context.Context, _ *applicationpkg.ResourcesQuery, _ ...grpc.CallOption) (*v1alpha1.ApplicationTree, error) {
return nil, nil
}
func (c *fakeAppServiceClient) WatchResourceTree(_ context.Context, _ *applicationpkg.ResourcesQuery, _ ...grpc.CallOption) (applicationpkg.ApplicationService_WatchResourceTreeClient, error) {
return nil, nil
}
func (c *fakeAppServiceClient) Rollback(_ context.Context, _ *applicationpkg.ApplicationRollbackRequest, _ ...grpc.CallOption) (*v1alpha1.Application, error) {
return nil, nil
}
func (c *fakeAppServiceClient) TerminateOperation(_ context.Context, _ *applicationpkg.OperationTerminateRequest, _ ...grpc.CallOption) (*applicationpkg.OperationTerminateResponse, error) {
return nil, nil
}
func (c *fakeAppServiceClient) GetResource(_ context.Context, _ *applicationpkg.ApplicationResourceRequest, _ ...grpc.CallOption) (*applicationpkg.ApplicationResourceResponse, error) {
return nil, nil
}
func (c *fakeAppServiceClient) PatchResource(_ context.Context, _ *applicationpkg.ApplicationResourcePatchRequest, _ ...grpc.CallOption) (*applicationpkg.ApplicationResourceResponse, error) {
return nil, nil
}
func (c *fakeAppServiceClient) ListResourceActions(_ context.Context, _ *applicationpkg.ApplicationResourceRequest, _ ...grpc.CallOption) (*applicationpkg.ResourceActionsListResponse, error) {
return nil, nil
}
// nolint:staticcheck // ResourceActionRunRequest is deprecated, but we still need to implement it to satisfy the server interface.
func (c *fakeAppServiceClient) RunResourceAction(_ context.Context, _ *applicationpkg.ResourceActionRunRequest, _ ...grpc.CallOption) (*applicationpkg.ApplicationResponse, error) {
return nil, nil
}
func (c *fakeAppServiceClient) RunResourceActionV2(_ context.Context, _ *applicationpkg.ResourceActionRunRequestV2, _ ...grpc.CallOption) (*applicationpkg.ApplicationResponse, error) {
return nil, nil
}
func (c *fakeAppServiceClient) DeleteResource(_ context.Context, _ *applicationpkg.ApplicationResourceDeleteRequest, _ ...grpc.CallOption) (*applicationpkg.ApplicationResponse, error) {
return nil, nil
}
func (c *fakeAppServiceClient) PodLogs(_ context.Context, _ *applicationpkg.ApplicationPodLogsQuery, _ ...grpc.CallOption) (applicationpkg.ApplicationService_PodLogsClient, error) {
return nil, nil
}
func (c *fakeAppServiceClient) ListLinks(_ context.Context, _ *applicationpkg.ListAppLinksRequest, _ ...grpc.CallOption) (*applicationpkg.LinksResponse, error) {
return nil, nil
}
func (c *fakeAppServiceClient) ListResourceLinks(_ context.Context, _ *applicationpkg.ApplicationResourceRequest, _ ...grpc.CallOption) (*applicationpkg.LinksResponse, error) {
return nil, nil
}
func (c *fakeAppServiceClient) ServerSideDiff(_ context.Context, _ *applicationpkg.ApplicationServerSideDiffQuery, _ ...grpc.CallOption) (*applicationpkg.ApplicationServerSideDiffResponse, error) {
return nil, nil
}
type fakeAcdClient struct {
simulateTimeout uint
}
func (c *fakeAcdClient) ClientOptions() argocdclient.ClientOptions {
return argocdclient.ClientOptions{}
}
func (c *fakeAcdClient) HTTPClient() (*http.Client, error) { return nil, nil }
func (c *fakeAcdClient) OIDCConfig(context.Context, *settingspkg.Settings) (*oauth2.Config, *oidc.Provider, error) {
return nil, nil, nil
}
func (c *fakeAcdClient) NewRepoClient() (io.Closer, repositorypkg.RepositoryServiceClient, error) {
return nil, nil, nil
}
func (c *fakeAcdClient) NewRepoClientOrDie() (io.Closer, repositorypkg.RepositoryServiceClient) {
return nil, nil
}
func (c *fakeAcdClient) NewRepoCredsClient() (io.Closer, repocredspkg.RepoCredsServiceClient, error) {
return nil, nil, nil
}
func (c *fakeAcdClient) NewRepoCredsClientOrDie() (io.Closer, repocredspkg.RepoCredsServiceClient) {
return nil, nil
}
func (c *fakeAcdClient) NewCertClient() (io.Closer, certificatepkg.CertificateServiceClient, error) {
return nil, nil, nil
}
func (c *fakeAcdClient) NewCertClientOrDie() (io.Closer, certificatepkg.CertificateServiceClient) {
return nil, nil
}
func (c *fakeAcdClient) NewClusterClient() (io.Closer, clusterpkg.ClusterServiceClient, error) {
return nil, nil, nil
}
func (c *fakeAcdClient) NewClusterClientOrDie() (io.Closer, clusterpkg.ClusterServiceClient) {
return nil, nil
}
func (c *fakeAcdClient) NewGPGKeyClient() (io.Closer, gpgkeypkg.GPGKeyServiceClient, error) {
return nil, nil, nil
}
func (c *fakeAcdClient) NewGPGKeyClientOrDie() (io.Closer, gpgkeypkg.GPGKeyServiceClient) {
return nil, nil
}
func (c *fakeAcdClient) NewApplicationClient() (io.Closer, applicationpkg.ApplicationServiceClient, error) {
return nil, nil, nil
}
func (c *fakeAcdClient) NewApplicationSetClient() (io.Closer, applicationsetpkg.ApplicationSetServiceClient, error) {
return nil, nil, nil
}
func (c *fakeAcdClient) NewApplicationClientOrDie() (io.Closer, applicationpkg.ApplicationServiceClient) {
return nil, nil
}
func (c *fakeAcdClient) NewApplicationSetClientOrDie() (io.Closer, applicationsetpkg.ApplicationSetServiceClient) {
return nil, nil
}
func (c *fakeAcdClient) NewNotificationClient() (io.Closer, notificationpkg.NotificationServiceClient, error) {
return nil, nil, nil
}
func (c *fakeAcdClient) NewNotificationClientOrDie() (io.Closer, notificationpkg.NotificationServiceClient) {
return nil, nil
}
func (c *fakeAcdClient) NewSessionClient() (io.Closer, sessionpkg.SessionServiceClient, error) {
return nil, nil, nil
}
func (c *fakeAcdClient) NewSessionClientOrDie() (io.Closer, sessionpkg.SessionServiceClient) {
return nil, nil
}
func (c *fakeAcdClient) NewSettingsClient() (io.Closer, settingspkg.SettingsServiceClient, error) {
return nil, nil, nil
}
func (c *fakeAcdClient) NewSettingsClientOrDie() (io.Closer, settingspkg.SettingsServiceClient) {
return nil, nil
}
func (c *fakeAcdClient) NewVersionClient() (io.Closer, versionpkg.VersionServiceClient, error) {
return nil, nil, nil
}
func (c *fakeAcdClient) NewVersionClientOrDie() (io.Closer, versionpkg.VersionServiceClient) {
return nil, nil
}
func (c *fakeAcdClient) NewProjectClient() (io.Closer, projectpkg.ProjectServiceClient, error) {
return nil, nil, nil
}
func (c *fakeAcdClient) NewProjectClientOrDie() (io.Closer, projectpkg.ProjectServiceClient) {
return nil, nil
}
func (c *fakeAcdClient) NewAccountClient() (io.Closer, accountpkg.AccountServiceClient, error) {
return nil, nil, nil
}
func (c *fakeAcdClient) NewAccountClientOrDie() (io.Closer, accountpkg.AccountServiceClient) {
return nil, nil
}
func (c *fakeAcdClient) WatchApplicationWithRetry(_ context.Context, _ string, _ string) chan *v1alpha1.ApplicationWatchEvent {
appEventsCh := make(chan *v1alpha1.ApplicationWatchEvent)
go func() {
modifiedEvent := new(v1alpha1.ApplicationWatchEvent)
modifiedEvent.Type = watch.Modified
appEventsCh <- modifiedEvent
deletedEvent := new(v1alpha1.ApplicationWatchEvent)
deletedEvent.Type = watch.Deleted
appEventsCh <- deletedEvent
}()
return appEventsCh
}
func (c *fakeAcdClient) WatchApplicationSetWithRetry(_ context.Context, _ string, _ string) chan *v1alpha1.ApplicationSetWatchEvent {
appSetEventsCh := make(chan *v1alpha1.ApplicationSetWatchEvent)
go func() {
defer close(appSetEventsCh)
addedEvent := &v1alpha1.ApplicationSetWatchEvent{
Type: watch.Added,
ApplicationSet: v1alpha1.ApplicationSet{
Status: v1alpha1.ApplicationSetStatus{
Conditions: []v1alpha1.ApplicationSetCondition{
{Type: v1alpha1.ApplicationSetConditionResourcesUpToDate, Status: v1alpha1.ApplicationSetConditionStatusTrue},
},
},
},
}
appSetEventsCh <- addedEvent
}()
return appSetEventsCh
}
func TestIsContextCanceledErr(t *testing.T) {
t.Parallel()
t.Run("nil error returns false", func(t *testing.T) {
t.Parallel()
assert.False(t, isContextCanceledErr(nil))
})
t.Run("context.Canceled returns true", func(t *testing.T) {
t.Parallel()
assert.True(t, isContextCanceledErr(context.Canceled))
})
t.Run("context.DeadlineExceeded returns true", func(t *testing.T) {
t.Parallel()
assert.True(t, isContextCanceledErr(context.DeadlineExceeded))
})
t.Run("gRPC Canceled status returns true", func(t *testing.T) {
t.Parallel()
grpcErr := grpcstatus.Error(grpccodes.Canceled, "context canceled")
assert.True(t, isContextCanceledErr(grpcErr))
})
t.Run("gRPC DeadlineExceeded status returns true", func(t *testing.T) {
t.Parallel()
grpcErr := grpcstatus.Error(grpccodes.DeadlineExceeded, "deadline exceeded")
assert.True(t, isContextCanceledErr(grpcErr))
})
t.Run("unrelated error returns false", func(t *testing.T) {
t.Parallel()
assert.False(t, isContextCanceledErr(errors.New("some other error")))
})
}