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

961 lines
49 KiB
Go

package project
import (
"context"
"fmt"
"strings"
"testing"
"github.com/hanzoai/deploy/util/argo"
"github.com/hanzoai/deploy/util/db"
"github.com/argoproj/pkg/v2/sync"
"github.com/golang-jwt/jwt/v5"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
k8scache "k8s.io/client-go/tools/cache"
"github.com/hanzoai/deploy/common"
"github.com/hanzoai/deploy/pkg/apiclient/project"
"github.com/hanzoai/deploy/pkg/apis/application/v1alpha1"
apps "github.com/hanzoai/deploy/pkg/client/clientset/versioned/fake"
informer "github.com/hanzoai/deploy/pkg/client/informers/externalversions"
"github.com/hanzoai/deploy/server/rbacpolicy"
"github.com/hanzoai/deploy/test"
"github.com/hanzoai/deploy/util/assets"
jwtutil "github.com/hanzoai/deploy/util/jwt"
"github.com/hanzoai/deploy/util/rbac"
"github.com/hanzoai/deploy/util/session"
"github.com/hanzoai/deploy/util/settings"
)
const testNamespace = "default"
var testEnableEventList = argo.DefaultEnableEventList()
func TestProjectServer(t *testing.T) {
kubeclientset := fake.NewClientset(&corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Namespace: testNamespace,
Name: "argocd-cm",
Labels: map[string]string{
"app.kubernetes.io/part-of": "argocd",
},
},
}, &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-secret",
Namespace: testNamespace,
},
Data: map[string][]byte{
"admin.password": []byte("test"),
"server.secretkey": []byte("test"),
},
}, &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "cluster-1",
Namespace: testNamespace,
Labels: map[string]string{
common.LabelKeySecretType: common.LabelValueSecretTypeCluster,
},
},
Data: map[string][]byte{
"name": []byte("server1"),
"server": []byte("https://server1"),
},
}, &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "cluster-2",
Namespace: testNamespace,
Labels: map[string]string{
common.LabelKeySecretType: common.LabelValueSecretTypeCluster,
},
},
Data: map[string][]byte{
"name": []byte("server2"),
"server": []byte("https://server2"),
},
}, &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "cluster-3",
Namespace: testNamespace,
Labels: map[string]string{
common.LabelKeySecretType: common.LabelValueSecretTypeCluster,
},
},
Data: map[string][]byte{
"name": []byte("server3"),
"server": []byte("https://server3"),
},
})
settingsMgr := settings.NewSettingsManager(t.Context(), kubeclientset, testNamespace)
enforcer := newEnforcer(kubeclientset)
existingProj := v1alpha1.AppProject{
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: testNamespace},
Spec: v1alpha1.AppProjectSpec{
Destinations: []v1alpha1.ApplicationDestination{
{Namespace: "ns1", Server: "https://server1"},
{Namespace: "ns2", Server: "https://server2"},
},
SourceRepos: []string{"https://github.com/argoproj/argo-cd.git"},
},
}
existingApp := v1alpha1.Application{
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
Spec: v1alpha1.ApplicationSpec{Source: &v1alpha1.ApplicationSource{}, Project: "test", Destination: v1alpha1.ApplicationDestination{Namespace: "ns3", Server: "https://server3"}},
}
policyTemplate := "p, proj:%s:%s, applications, %s, %s/%s, %s"
ctx := t.Context()
fakeAppsClientset := apps.NewSimpleClientset()
factory := informer.NewSharedInformerFactoryWithOptions(fakeAppsClientset, 0, informer.WithNamespace(""), informer.WithTweakListOptions(func(_ *metav1.ListOptions) {}))
projInformer := factory.Argoproj().V1alpha1().AppProjects().Informer()
go projInformer.Run(ctx.Done())
if !k8scache.WaitForCacheSync(ctx.Done(), projInformer.HasSynced) {
panic("Timed out waiting forfff caches to sync")
}
t.Run("TestNormalizeProj", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", nil, session.NewUserStateStorage(nil))
projectWithRole := existingProj.DeepCopy()
roleName := "roleName"
role1 := v1alpha1.ProjectRole{Name: roleName, JWTTokens: []v1alpha1.JWTToken{{IssuedAt: 1}}}
projectWithRole.Spec.Roles = append(projectWithRole.Spec.Roles, role1)
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewClientset(), apps.NewSimpleClientset(projectWithRole), enforcer, sync.NewKeyLock(), sessionMgr, nil, projInformer, settingsMgr, argoDB, testEnableEventList)
err := projectServer.NormalizeProjs()
require.NoError(t, err)
appList, err := projectServer.appclientset.ArgoprojV1alpha1().AppProjects(projectWithRole.Namespace).List(t.Context(), metav1.ListOptions{})
require.NoError(t, err)
assert.Equal(t, int64(1), appList.Items[0].Status.JWTTokensByRole[roleName].Items[0].IssuedAt)
assert.ElementsMatch(t, appList.Items[0].Status.JWTTokensByRole[roleName].Items, appList.Items[0].Spec.Roles[0].JWTTokens)
})
t.Run("TestClusterUpdateDenied", func(t *testing.T) {
enforcer.SetDefaultRole("role:projects")
_ = enforcer.SetBuiltinPolicy("p, role:projects, projects, update, *, allow")
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(&existingProj, &existingApp), enforcer, sync.NewKeyLock(), nil, nil, projInformer, settingsMgr, argoDB, testEnableEventList)
updatedProj := existingProj.DeepCopy()
updatedProj.Spec.Destinations = nil
_, err := projectServer.Update(t.Context(), &project.ProjectUpdateRequest{Project: updatedProj})
assert.Equal(t, status.Error(codes.PermissionDenied, "permission denied: clusters, update, https://server1"), err)
})
t.Run("TestReposUpdateDenied", func(t *testing.T) {
enforcer.SetDefaultRole("role:projects")
_ = enforcer.SetBuiltinPolicy("p, role:projects, projects, update, *, allow")
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(&existingProj, &existingApp), enforcer, sync.NewKeyLock(), nil, nil, projInformer, settingsMgr, argoDB, testEnableEventList)
updatedProj := existingProj.DeepCopy()
updatedProj.Spec.SourceRepos = nil
_, err := projectServer.Update(t.Context(), &project.ProjectUpdateRequest{Project: updatedProj})
assert.Equal(t, status.Error(codes.PermissionDenied, "permission denied: repositories, update, https://github.com/argoproj/argo-cd.git"), err)
})
t.Run("TestClusterResourceWhitelistUpdateDenied", func(t *testing.T) {
enforcer.SetDefaultRole("role:projects")
_ = enforcer.SetBuiltinPolicy("p, role:projects, projects, update, *, allow")
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(&existingProj, &existingApp), enforcer, sync.NewKeyLock(), nil, nil, projInformer, settingsMgr, argoDB, testEnableEventList)
updatedProj := existingProj.DeepCopy()
updatedProj.Spec.ClusterResourceWhitelist = []v1alpha1.ClusterResourceRestrictionItem{{}}
_, err := projectServer.Update(t.Context(), &project.ProjectUpdateRequest{Project: updatedProj})
assert.Equal(t, status.Error(codes.PermissionDenied, "permission denied: clusters, update, https://server1"), err)
})
t.Run("TestNamespaceResourceBlacklistUpdateDenied", func(t *testing.T) {
enforcer.SetDefaultRole("role:projects")
_ = enforcer.SetBuiltinPolicy("p, role:projects, projects, update, *, allow")
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(&existingProj, &existingApp), enforcer, sync.NewKeyLock(), nil, nil, projInformer, settingsMgr, argoDB, testEnableEventList)
updatedProj := existingProj.DeepCopy()
updatedProj.Spec.NamespaceResourceBlacklist = []metav1.GroupKind{{}}
_, err := projectServer.Update(t.Context(), &project.ProjectUpdateRequest{Project: updatedProj})
assert.Equal(t, status.Error(codes.PermissionDenied, "permission denied: clusters, update, https://server1"), err)
})
enforcer = newEnforcer(kubeclientset)
t.Run("TestRemoveDestinationSuccessful", func(t *testing.T) {
existingApp := v1alpha1.Application{
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
Spec: v1alpha1.ApplicationSpec{Source: &v1alpha1.ApplicationSource{}, Project: "test", Destination: v1alpha1.ApplicationDestination{Namespace: "ns3", Server: "https://server3"}},
}
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(&existingProj, &existingApp), enforcer, sync.NewKeyLock(), nil, nil, projInformer, settingsMgr, argoDB, testEnableEventList)
updatedProj := existingProj.DeepCopy()
updatedProj.Spec.Destinations = updatedProj.Spec.Destinations[1:]
_, err := projectServer.Update(t.Context(), &project.ProjectUpdateRequest{Project: updatedProj})
require.NoError(t, err)
})
t.Run("TestRemoveDestinationUsedByApp", func(t *testing.T) {
existingApp := v1alpha1.Application{
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
Spec: v1alpha1.ApplicationSpec{Source: &v1alpha1.ApplicationSource{}, Project: "test", Destination: v1alpha1.ApplicationDestination{Namespace: "ns1", Server: "https://server1"}},
}
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(&existingProj, &existingApp), enforcer, sync.NewKeyLock(), nil, nil, projInformer, settingsMgr, argoDB, testEnableEventList)
updatedProj := existingProj.DeepCopy()
updatedProj.Spec.Destinations = updatedProj.Spec.Destinations[1:]
_, err := projectServer.Update(t.Context(), &project.ProjectUpdateRequest{Project: updatedProj})
require.Error(t, err)
statusCode, _ := status.FromError(err)
assert.Equal(t, codes.InvalidArgument, statusCode.Code())
assert.Equal(t, "as a result of project update 1 applications destination became invalid", statusCode.Message())
})
t.Run("TestRemoveSourceSuccessful", func(t *testing.T) {
existingApp := v1alpha1.Application{
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
Spec: v1alpha1.ApplicationSpec{Destination: v1alpha1.ApplicationDestination{Server: "https://server1"}, Source: &v1alpha1.ApplicationSource{}, Project: "test"},
}
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(&existingProj, &existingApp), enforcer, sync.NewKeyLock(), nil, nil, projInformer, settingsMgr, argoDB, testEnableEventList)
updatedProj := existingProj.DeepCopy()
updatedProj.Spec.SourceRepos = []string{}
_, err := projectServer.Update(t.Context(), &project.ProjectUpdateRequest{Project: updatedProj})
require.NoError(t, err)
})
t.Run("TestRemoveSourceUsedByApp", func(t *testing.T) {
existingApp := v1alpha1.Application{
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
Spec: v1alpha1.ApplicationSpec{Destination: v1alpha1.ApplicationDestination{Name: "server1"}, Project: "test", Source: &v1alpha1.ApplicationSource{RepoURL: "https://github.com/argoproj/argo-cd.git"}},
}
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(&existingProj, &existingApp), enforcer, sync.NewKeyLock(), nil, nil, projInformer, settingsMgr, argoDB, testEnableEventList)
updatedProj := existingProj.DeepCopy()
updatedProj.Spec.SourceRepos = []string{}
_, err := projectServer.Update(t.Context(), &project.ProjectUpdateRequest{Project: updatedProj})
require.Error(t, err)
statusCode, _ := status.FromError(err)
assert.Equalf(t, codes.InvalidArgument, statusCode.Code(), "Got unexpected error code with error: %v", err)
assert.Equal(t, "as a result of project update 1 applications source became invalid", statusCode.Message())
})
t.Run("TestRemoveSourceUsedByAppSuccessfulIfPermittedByAnotherSrc", func(t *testing.T) {
proj := existingProj.DeepCopy()
proj.Spec.SourceRepos = []string{"https://github.com/argoproj/argo-cd.git", "https://github.com/argoproj/*"}
existingApp := v1alpha1.Application{
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
Spec: v1alpha1.ApplicationSpec{Destination: v1alpha1.ApplicationDestination{Server: "https://server1"}, Project: "test", Source: &v1alpha1.ApplicationSource{RepoURL: "https://github.com/argoproj/argo-cd.git"}},
}
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(proj, &existingApp), enforcer, sync.NewKeyLock(), nil, nil, projInformer, settingsMgr, argoDB, testEnableEventList)
updatedProj := proj.DeepCopy()
updatedProj.Spec.SourceRepos = []string{"https://github.com/argoproj/*"}
res, err := projectServer.Update(t.Context(), &project.ProjectUpdateRequest{Project: updatedProj})
require.NoError(t, err)
assert.ElementsMatch(t, res.Spec.SourceRepos, updatedProj.Spec.SourceRepos)
})
t.Run("TestRemoveDestinationUsedByAppSuccessfulIfPermittedByAnotherDestination", func(t *testing.T) {
proj := existingProj.DeepCopy()
proj.Spec.Destinations = []v1alpha1.ApplicationDestination{
{Namespace: "org1-team1", Server: "https://server1"},
{Namespace: "org1-*", Server: "https://server1"},
}
existingApp := v1alpha1.Application{
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
Spec: v1alpha1.ApplicationSpec{Source: &v1alpha1.ApplicationSource{}, Project: "test", Destination: v1alpha1.ApplicationDestination{
Server: "https://server1",
Namespace: "org1-team1",
}},
}
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(proj, &existingApp), enforcer, sync.NewKeyLock(), nil, nil, projInformer, settingsMgr, argoDB, testEnableEventList)
updatedProj := proj.DeepCopy()
updatedProj.Spec.Destinations = []v1alpha1.ApplicationDestination{
{Namespace: "org1-*", Server: "https://server1"},
}
res, err := projectServer.Update(t.Context(), &project.ProjectUpdateRequest{Project: updatedProj})
require.NoError(t, err)
assert.ElementsMatch(t, res.Spec.Destinations, updatedProj.Spec.Destinations)
})
t.Run("TestDeleteProjectSuccessful", func(t *testing.T) {
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(&existingProj), enforcer, sync.NewKeyLock(), nil, nil, projInformer, settingsMgr, argoDB, testEnableEventList)
_, err := projectServer.Delete(t.Context(), &project.ProjectQuery{Name: "test"})
require.NoError(t, err)
})
t.Run("TestDeleteDefaultProjectFailure", func(t *testing.T) {
defaultProj := v1alpha1.AppProject{
ObjectMeta: metav1.ObjectMeta{Name: "default", Namespace: "default"},
Spec: v1alpha1.AppProjectSpec{},
}
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(&defaultProj), enforcer, sync.NewKeyLock(), nil, nil, projInformer, settingsMgr, argoDB, testEnableEventList)
_, err := projectServer.Delete(t.Context(), &project.ProjectQuery{Name: defaultProj.Name})
statusCode, _ := status.FromError(err)
assert.Equal(t, codes.InvalidArgument, statusCode.Code())
})
t.Run("TestDeleteProjectReferencedByApp", func(t *testing.T) {
existingApp := v1alpha1.Application{
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
Spec: v1alpha1.ApplicationSpec{Project: "test"},
}
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(&existingProj, &existingApp), enforcer, sync.NewKeyLock(), nil, nil, projInformer, settingsMgr, argoDB, testEnableEventList)
_, err := projectServer.Delete(t.Context(), &project.ProjectQuery{Name: "test"})
require.Error(t, err)
statusCode, _ := status.FromError(err)
assert.Equal(t, codes.InvalidArgument, statusCode.Code())
assert.Equal(t, "project is referenced by 1 applications", statusCode.Message())
})
// configure a user named "admin" which is denied by default
enforcer = newEnforcer(kubeclientset)
_ = enforcer.SetBuiltinPolicy(`p, *, *, *, *, deny`)
enforcer.SetClaimsEnforcerFunc(nil)
//nolint:staticcheck
ctx = context.WithValue(t.Context(), "claims", &jwt.MapClaims{"groups": []string{"my-group"}})
policyEnf := rbacpolicy.NewRBACPolicyEnforcer(enforcer, nil)
policyEnf.SetScopes([]string{"groups"})
tokenName := "testToken"
id := "testId"
t.Run("TestCreateTokenDenied", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", nil, session.NewUserStateStorage(nil))
projectWithRole := existingProj.DeepCopy()
projectWithRole.Spec.Roles = []v1alpha1.ProjectRole{{Name: tokenName}}
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projectWithRole), enforcer, sync.NewKeyLock(), sessionMgr, policyEnf, projInformer, settingsMgr, argoDB, testEnableEventList)
_, err := projectServer.CreateToken(ctx, &project.ProjectTokenCreateRequest{Project: projectWithRole.Name, Role: tokenName, ExpiresIn: 1})
assert.EqualError(t, err, "rpc error: code = PermissionDenied desc = permission denied: projects, update, test")
})
t.Run("TestCreateTokenSuccessfullyUsingGroup", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", nil, session.NewUserStateStorage(nil))
projectWithRole := existingProj.DeepCopy()
projectWithRole.Spec.Roles = []v1alpha1.ProjectRole{{Name: tokenName, Groups: []string{"my-group"}}}
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projectWithRole), enforcer, sync.NewKeyLock(), sessionMgr, policyEnf, projInformer, settingsMgr, argoDB, testEnableEventList)
_, err := projectServer.CreateToken(ctx, &project.ProjectTokenCreateRequest{Project: projectWithRole.Name, Role: tokenName, ExpiresIn: 1})
require.NoError(t, err)
})
_ = enforcer.SetBuiltinPolicy(`p, role:admin, projects, update, *, allow`)
t.Run("TestCreateTokenSuccessfully", func(t *testing.T) {
projectWithRole := existingProj.DeepCopy()
projectWithRole.Spec.Roles = []v1alpha1.ProjectRole{{Name: tokenName}}
clientset := apps.NewSimpleClientset(projectWithRole)
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjListerFromInterface(clientset.ArgoprojV1alpha1().AppProjects("default")), "", nil, session.NewUserStateStorage(nil))
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), clientset, enforcer, sync.NewKeyLock(), sessionMgr, policyEnf, projInformer, settingsMgr, argoDB, testEnableEventList)
tokenResponse, err := projectServer.CreateToken(t.Context(), &project.ProjectTokenCreateRequest{Project: projectWithRole.Name, Role: tokenName, ExpiresIn: 100})
require.NoError(t, err)
claims, _, err := sessionMgr.Parse(tokenResponse.Token)
require.NoError(t, err)
mapClaims, err := jwtutil.MapClaims(claims)
subject, ok := mapClaims["sub"].(string)
assert.True(t, ok)
expectedSubject := fmt.Sprintf(JWTTokenSubFormat, projectWithRole.Name, tokenName)
assert.Equal(t, expectedSubject, subject)
require.NoError(t, err)
})
t.Run("TestCreateTokenWithIDSuccessfully", func(t *testing.T) {
projectWithRole := existingProj.DeepCopy()
projectWithRole.Spec.Roles = []v1alpha1.ProjectRole{{Name: tokenName}}
clientset := apps.NewSimpleClientset(projectWithRole)
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjListerFromInterface(clientset.ArgoprojV1alpha1().AppProjects("default")), "", nil, session.NewUserStateStorage(nil))
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), clientset, enforcer, sync.NewKeyLock(), sessionMgr, policyEnf, projInformer, settingsMgr, argoDB, testEnableEventList)
tokenResponse, err := projectServer.CreateToken(t.Context(), &project.ProjectTokenCreateRequest{Project: projectWithRole.Name, Role: tokenName, ExpiresIn: 1, Id: id})
require.NoError(t, err)
claims, _, err := sessionMgr.Parse(tokenResponse.Token)
require.NoError(t, err)
mapClaims, err := jwtutil.MapClaims(claims)
subject, ok := mapClaims["sub"].(string)
assert.True(t, ok)
expectedSubject := fmt.Sprintf(JWTTokenSubFormat, projectWithRole.Name, tokenName)
assert.Equal(t, expectedSubject, subject)
require.NoError(t, err)
})
t.Run("TestCreateTokenWithSameIdDeny", func(t *testing.T) {
projectWithRole := existingProj.DeepCopy()
projectWithRole.Spec.Roles = []v1alpha1.ProjectRole{{Name: tokenName}}
clientset := apps.NewSimpleClientset(projectWithRole)
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjListerFromInterface(clientset.ArgoprojV1alpha1().AppProjects("default")), "", nil, session.NewUserStateStorage(nil))
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), clientset, enforcer, sync.NewKeyLock(), sessionMgr, policyEnf, projInformer, settingsMgr, argoDB, testEnableEventList)
tokenResponse, err := projectServer.CreateToken(t.Context(), &project.ProjectTokenCreateRequest{Project: projectWithRole.Name, Role: tokenName, ExpiresIn: 1, Id: id})
require.NoError(t, err)
claims, _, err := sessionMgr.Parse(tokenResponse.Token)
require.NoError(t, err)
mapClaims, err := jwtutil.MapClaims(claims)
subject, ok := mapClaims["sub"].(string)
assert.True(t, ok)
expectedSubject := fmt.Sprintf(JWTTokenSubFormat, projectWithRole.Name, tokenName)
assert.Equal(t, expectedSubject, subject)
require.NoError(t, err)
_, err1 := projectServer.CreateToken(t.Context(), &project.ProjectTokenCreateRequest{Project: projectWithRole.Name, Role: tokenName, ExpiresIn: 1, Id: id})
expectedErr := fmt.Sprintf("rpc error: code = InvalidArgument desc = rpc error: code = InvalidArgument desc = Token id '%s' has been used. ", id)
assert.EqualError(t, err1, expectedErr)
})
_ = enforcer.SetBuiltinPolicy(`p, *, *, *, *, deny`)
t.Run("TestDeleteTokenDenied", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", nil, session.NewUserStateStorage(nil))
projWithToken := existingProj.DeepCopy()
issuedAt := int64(1)
secondIssuedAt := issuedAt + 1
token := v1alpha1.ProjectRole{Name: tokenName, JWTTokens: []v1alpha1.JWTToken{{IssuedAt: issuedAt}, {IssuedAt: secondIssuedAt}}}
projWithToken.Spec.Roles = append(projWithToken.Spec.Roles, token)
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projWithToken), enforcer, sync.NewKeyLock(), sessionMgr, policyEnf, projInformer, settingsMgr, argoDB, testEnableEventList)
_, err := projectServer.DeleteToken(ctx, &project.ProjectTokenDeleteRequest{Project: projWithToken.Name, Role: tokenName, Iat: issuedAt})
assert.EqualError(t, err, "rpc error: code = PermissionDenied desc = permission denied: projects, update, test")
})
t.Run("TestDeleteTokenSuccessfullyWithGroup", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", nil, session.NewUserStateStorage(nil))
projWithToken := existingProj.DeepCopy()
issuedAt := int64(1)
secondIssuedAt := issuedAt + 1
token := v1alpha1.ProjectRole{Name: tokenName, Groups: []string{"my-group"}, JWTTokens: []v1alpha1.JWTToken{{IssuedAt: issuedAt}, {IssuedAt: secondIssuedAt}}}
projWithToken.Spec.Roles = append(projWithToken.Spec.Roles, token)
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projWithToken), enforcer, sync.NewKeyLock(), sessionMgr, policyEnf, projInformer, settingsMgr, argoDB, testEnableEventList)
_, err := projectServer.DeleteToken(ctx, &project.ProjectTokenDeleteRequest{Project: projWithToken.Name, Role: tokenName, Iat: issuedAt})
require.NoError(t, err)
})
_ = enforcer.SetBuiltinPolicy(`p, role:admin, projects, get, *, allow
p, role:admin, projects, update, *, allow`)
t.Run("TestDeleteTokenSuccessfully", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", nil, session.NewUserStateStorage(nil))
projWithToken := existingProj.DeepCopy()
issuedAt := int64(1)
secondIssuedAt := issuedAt + 1
token := v1alpha1.ProjectRole{Name: tokenName, JWTTokens: []v1alpha1.JWTToken{{IssuedAt: issuedAt}, {IssuedAt: secondIssuedAt}}}
projWithToken.Spec.Roles = append(projWithToken.Spec.Roles, token)
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projWithToken), enforcer, sync.NewKeyLock(), sessionMgr, policyEnf, projInformer, settingsMgr, argoDB, testEnableEventList)
_, err := projectServer.DeleteToken(ctx, &project.ProjectTokenDeleteRequest{Project: projWithToken.Name, Role: tokenName, Iat: issuedAt})
require.NoError(t, err)
projWithoutToken, err := projectServer.Get(t.Context(), &project.ProjectQuery{Name: projWithToken.Name})
require.NoError(t, err)
assert.Len(t, projWithoutToken.Spec.Roles, 1)
assert.Len(t, projWithoutToken.Spec.Roles[0].JWTTokens, 1)
assert.Equal(t, projWithoutToken.Spec.Roles[0].JWTTokens[0].IssuedAt, secondIssuedAt)
})
_ = enforcer.SetBuiltinPolicy(`p, role:admin, projects, get, *, allow
p, role:admin, projects, update, *, allow`)
t.Run("TestDeleteTokenByIdSuccessfully", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", nil, session.NewUserStateStorage(nil))
projWithToken := existingProj.DeepCopy()
issuedAt := int64(1)
secondIssuedAt := issuedAt + 1
id := "testId"
uniqueId, _ := uuid.NewRandom()
secondId := uniqueId.String()
token := v1alpha1.ProjectRole{Name: tokenName, JWTTokens: []v1alpha1.JWTToken{{IssuedAt: issuedAt, ID: id}, {IssuedAt: secondIssuedAt, ID: secondId}}}
projWithToken.Spec.Roles = append(projWithToken.Spec.Roles, token)
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projWithToken), enforcer, sync.NewKeyLock(), sessionMgr, policyEnf, projInformer, settingsMgr, argoDB, testEnableEventList)
_, err := projectServer.DeleteToken(ctx, &project.ProjectTokenDeleteRequest{Project: projWithToken.Name, Role: tokenName, Iat: secondIssuedAt, Id: id})
require.NoError(t, err)
projWithoutToken, err := projectServer.Get(t.Context(), &project.ProjectQuery{Name: projWithToken.Name})
require.NoError(t, err)
assert.Len(t, projWithoutToken.Spec.Roles, 1)
assert.Len(t, projWithoutToken.Spec.Roles[0].JWTTokens, 1)
assert.Equal(t, projWithoutToken.Spec.Roles[0].JWTTokens[0].IssuedAt, secondIssuedAt)
})
enforcer = newEnforcer(kubeclientset)
t.Run("TestCreateTwoTokensInRoleSuccess", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", nil, session.NewUserStateStorage(nil))
projWithToken := existingProj.DeepCopy()
tokenName := "testToken"
token := v1alpha1.ProjectRole{Name: tokenName, JWTTokens: []v1alpha1.JWTToken{{IssuedAt: 1}}}
projWithToken.Spec.Roles = append(projWithToken.Spec.Roles, token)
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projWithToken), enforcer, sync.NewKeyLock(), sessionMgr, policyEnf, projInformer, settingsMgr, argoDB, testEnableEventList)
_, err := projectServer.CreateToken(t.Context(), &project.ProjectTokenCreateRequest{Project: projWithToken.Name, Role: tokenName})
require.NoError(t, err)
projWithTwoTokens, err := projectServer.Get(t.Context(), &project.ProjectQuery{Name: projWithToken.Name})
require.NoError(t, err)
assert.Len(t, projWithTwoTokens.Spec.Roles, 1)
assert.Len(t, projWithTwoTokens.Spec.Roles[0].JWTTokens, 2)
})
t.Run("TestAddWildcardSource", func(t *testing.T) {
proj := existingProj.DeepCopy()
wildSourceRepo := "*"
proj.Spec.SourceRepos = append(proj.Spec.SourceRepos, wildSourceRepo)
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(proj), enforcer, sync.NewKeyLock(), nil, policyEnf, projInformer, settingsMgr, argoDB, testEnableEventList)
request := &project.ProjectUpdateRequest{Project: proj}
updatedProj, err := projectServer.Update(t.Context(), request)
require.NoError(t, err)
assert.Equal(t, wildSourceRepo, updatedProj.Spec.SourceRepos[1])
})
t.Run("TestCreateRolePolicySuccessfully", func(t *testing.T) {
action := "create"
object := "testApplication"
roleName := "testRole"
effect := "allow"
projWithRole := existingProj.DeepCopy()
role := v1alpha1.ProjectRole{Name: roleName, JWTTokens: []v1alpha1.JWTToken{{IssuedAt: 1}}}
policy := fmt.Sprintf(policyTemplate, projWithRole.Name, roleName, action, projWithRole.Name, object, effect)
role.Policies = append(role.Policies, policy)
projWithRole.Spec.Roles = append(projWithRole.Spec.Roles, role)
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projWithRole), enforcer, sync.NewKeyLock(), nil, policyEnf, projInformer, settingsMgr, argoDB, testEnableEventList)
request := &project.ProjectUpdateRequest{Project: projWithRole}
_, err := projectServer.Update(t.Context(), request)
require.NoError(t, err)
t.Log(projWithRole.Spec.Roles[0].Policies[0])
expectedPolicy := fmt.Sprintf(policyTemplate, projWithRole.Name, role.Name, action, projWithRole.Name, object, effect)
assert.Equal(t, expectedPolicy, projWithRole.Spec.Roles[0].Policies[0])
})
t.Run("TestValidatePolicyDuplicatePolicyFailure", func(t *testing.T) {
action := "create"
object := "testApplication"
roleName := "testRole"
effect := "allow"
projWithRole := existingProj.DeepCopy()
role := v1alpha1.ProjectRole{Name: roleName, JWTTokens: []v1alpha1.JWTToken{{IssuedAt: 1}}}
policy := fmt.Sprintf(policyTemplate, projWithRole.Name, roleName, action, projWithRole.Name, object, effect)
role.Policies = append(role.Policies, policy)
role.Policies = append(role.Policies, policy)
projWithRole.Spec.Roles = append(projWithRole.Spec.Roles, role)
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projWithRole), enforcer, sync.NewKeyLock(), nil, nil, projInformer, settingsMgr, argoDB, testEnableEventList)
request := &project.ProjectUpdateRequest{Project: projWithRole}
_, err := projectServer.Update(t.Context(), request)
expectedErr := fmt.Sprintf("rpc error: code = AlreadyExists desc = policy '%s' already exists for role '%s'", policy, roleName)
assert.EqualError(t, err, expectedErr)
})
t.Run("TestValidateProjectAccessToSeparateProjectObjectFailure", func(t *testing.T) {
action := "create"
object := "testApplication"
roleName := "testRole"
otherProject := "other-project"
effect := "allow"
projWithRole := existingProj.DeepCopy()
role := v1alpha1.ProjectRole{Name: roleName, JWTTokens: []v1alpha1.JWTToken{{IssuedAt: 1}}}
policy := fmt.Sprintf(policyTemplate, projWithRole.Name, roleName, action, otherProject, object, effect)
role.Policies = append(role.Policies, policy)
projWithRole.Spec.Roles = append(projWithRole.Spec.Roles, role)
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projWithRole), enforcer, sync.NewKeyLock(), nil, nil, projInformer, settingsMgr, argoDB, testEnableEventList)
request := &project.ProjectUpdateRequest{Project: projWithRole}
_, err := projectServer.Update(t.Context(), request)
assert.ErrorContains(t, err, "object must be of form 'test/*', 'test[/<NAMESPACE>]/<APPNAME>' or 'test/<APPNAME>'")
})
t.Run("TestValidateProjectIncorrectProjectInRoleFailure", func(t *testing.T) {
action := "create"
object := "testApplication"
roleName := "testRole"
otherProject := "other-project"
effect := "allow"
projWithRole := existingProj.DeepCopy()
role := v1alpha1.ProjectRole{Name: roleName, JWTTokens: []v1alpha1.JWTToken{{IssuedAt: 1}}}
invalidPolicy := fmt.Sprintf(policyTemplate, otherProject, roleName, action, projWithRole.Name, object, effect)
role.Policies = append(role.Policies, invalidPolicy)
projWithRole.Spec.Roles = append(projWithRole.Spec.Roles, role)
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projWithRole), enforcer, sync.NewKeyLock(), nil, nil, projInformer, settingsMgr, argoDB, testEnableEventList)
request := &project.ProjectUpdateRequest{Project: projWithRole}
_, err := projectServer.Update(t.Context(), request)
assert.ErrorContains(t, err, "policy subject must be: 'proj:test:testRole'")
})
t.Run("TestValidateProjectIncorrectTokenInRoleFailure", func(t *testing.T) {
action := "create"
object := "testApplication"
roleName := "testRole"
otherToken := "other-token"
effect := "allow"
projWithRole := existingProj.DeepCopy()
role := v1alpha1.ProjectRole{Name: roleName, JWTTokens: []v1alpha1.JWTToken{{IssuedAt: 1}}}
invalidPolicy := fmt.Sprintf(policyTemplate, projWithRole.Name, otherToken, action, projWithRole.Name, object, effect)
role.Policies = append(role.Policies, invalidPolicy)
projWithRole.Spec.Roles = append(projWithRole.Spec.Roles, role)
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projWithRole), enforcer, sync.NewKeyLock(), nil, nil, projInformer, settingsMgr, argoDB, testEnableEventList)
request := &project.ProjectUpdateRequest{Project: projWithRole}
_, err := projectServer.Update(t.Context(), request)
assert.ErrorContains(t, err, "policy subject must be: 'proj:test:testRole'")
})
t.Run("TestValidateProjectInvalidEffectFailure", func(t *testing.T) {
action := "create"
object := "testApplication"
roleName := "testRole"
effect := "testEffect"
projWithRole := existingProj.DeepCopy()
role := v1alpha1.ProjectRole{Name: roleName, JWTTokens: []v1alpha1.JWTToken{{IssuedAt: 1}}}
invalidPolicy := fmt.Sprintf(policyTemplate, projWithRole.Name, roleName, action, projWithRole.Name, object, effect)
role.Policies = append(role.Policies, invalidPolicy)
projWithRole.Spec.Roles = append(projWithRole.Spec.Roles, role)
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projWithRole), enforcer, sync.NewKeyLock(), nil, nil, projInformer, settingsMgr, argoDB, testEnableEventList)
request := &project.ProjectUpdateRequest{Project: projWithRole}
_, err := projectServer.Update(t.Context(), request)
assert.ErrorContains(t, err, "effect must be: 'allow' or 'deny'")
})
t.Run("TestNormalizeProjectRolePolicies", func(t *testing.T) {
action := "create"
object := "testApplication"
roleName := "testRole"
effect := "allow"
projWithRole := existingProj.DeepCopy()
role := v1alpha1.ProjectRole{Name: roleName, JWTTokens: []v1alpha1.JWTToken{{IssuedAt: 1}}}
noSpacesPolicyTemplate := strings.ReplaceAll(policyTemplate, " ", "")
invalidPolicy := fmt.Sprintf(noSpacesPolicyTemplate, projWithRole.Name, roleName, action, projWithRole.Name, object, effect)
role.Policies = append(role.Policies, invalidPolicy)
projWithRole.Spec.Roles = append(projWithRole.Spec.Roles, role)
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projWithRole), enforcer, sync.NewKeyLock(), nil, nil, projInformer, settingsMgr, argoDB, testEnableEventList)
request := &project.ProjectUpdateRequest{Project: projWithRole}
updateProj, err := projectServer.Update(t.Context(), request)
require.NoError(t, err)
expectedPolicy := fmt.Sprintf(policyTemplate, projWithRole.Name, roleName, action, projWithRole.Name, object, effect)
assert.Equal(t, expectedPolicy, updateProj.Spec.Roles[0].Policies[0])
})
t.Run("TestSyncWindowsActive", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", nil, session.NewUserStateStorage(nil))
projectWithSyncWindows := existingProj.DeepCopy()
projectWithSyncWindows.Spec.SyncWindows = v1alpha1.SyncWindows{}
win := &v1alpha1.SyncWindow{Kind: "allow", Schedule: "* * * * *", Duration: "1h"}
projectWithSyncWindows.Spec.SyncWindows = append(projectWithSyncWindows.Spec.SyncWindows, win)
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projectWithSyncWindows), enforcer, sync.NewKeyLock(), sessionMgr, nil, projInformer, settingsMgr, argoDB, testEnableEventList)
res, err := projectServer.GetSyncWindowsState(ctx, &project.SyncWindowsQuery{Name: projectWithSyncWindows.Name})
require.NoError(t, err)
assert.Len(t, res.Windows, 1)
})
t.Run("TestGetSyncWindowsStateCannotGetProjectDetails", func(t *testing.T) {
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", nil, session.NewUserStateStorage(nil))
projectWithSyncWindows := existingProj.DeepCopy()
projectWithSyncWindows.Spec.SyncWindows = v1alpha1.SyncWindows{}
win := &v1alpha1.SyncWindow{Kind: "allow", Schedule: "* * * * *", Duration: "1h"}
projectWithSyncWindows.Spec.SyncWindows = append(projectWithSyncWindows.Spec.SyncWindows, win)
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projectWithSyncWindows), enforcer, sync.NewKeyLock(), sessionMgr, nil, projInformer, settingsMgr, argoDB, testEnableEventList)
res, err := projectServer.GetSyncWindowsState(ctx, &project.SyncWindowsQuery{Name: "incorrect"})
require.ErrorContains(t, err, "not found")
assert.Nil(t, res)
})
t.Run("TestGetSyncWindowsStateDenied", func(t *testing.T) {
enforcer = newEnforcer(kubeclientset)
_ = enforcer.SetBuiltinPolicy(`p, *, *, *, *, deny`)
enforcer.SetClaimsEnforcerFunc(nil)
//nolint:staticcheck
ctx := context.WithValue(t.Context(), "claims", &jwt.MapClaims{"groups": []string{"my-group"}})
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", nil, session.NewUserStateStorage(nil))
projectWithSyncWindows := existingProj.DeepCopy()
win := &v1alpha1.SyncWindow{Kind: "allow", Schedule: "* * * * *", Duration: "1h"}
projectWithSyncWindows.Spec.SyncWindows = append(projectWithSyncWindows.Spec.SyncWindows, win)
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projectWithSyncWindows), enforcer, sync.NewKeyLock(), sessionMgr, nil, projInformer, settingsMgr, argoDB, testEnableEventList)
_, err := projectServer.GetSyncWindowsState(ctx, &project.SyncWindowsQuery{Name: projectWithSyncWindows.Name})
assert.EqualError(t, err, "rpc error: code = PermissionDenied desc = permission denied: projects, get, test")
})
t.Run("TestAddSyncWindowWhenAnAppReferencesAClusterThatDoesNotExist", func(t *testing.T) {
_ = enforcer.SetBuiltinPolicy(`p, role:admin, projects, get, *, allow
p, role:admin, projects, update, *, allow`)
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", nil, session.NewUserStateStorage(nil))
projectWithAppWithInvalidCluster := existingProj.DeepCopy()
argoDB := db.NewDB("default", settingsMgr, kubeclientset)
invalidApp := v1alpha1.Application{
ObjectMeta: metav1.ObjectMeta{Name: "test-invalid", Namespace: "default"},
Spec: v1alpha1.ApplicationSpec{Source: &v1alpha1.ApplicationSource{}, Project: "test", Destination: v1alpha1.ApplicationDestination{Namespace: "ns3", Server: "https://server4"}},
}
projectServer := NewServer("default", fake.NewSimpleClientset(), apps.NewSimpleClientset(projectWithAppWithInvalidCluster, &invalidApp), enforcer, sync.NewKeyLock(), sessionMgr, nil, projInformer, settingsMgr, argoDB, testEnableEventList)
// Add sync window
syncWindow := v1alpha1.SyncWindow{
Kind: "deny",
Schedule: "* * * * *",
Duration: "1h",
Applications: []string{"*"},
Clusters: []string{"*"},
}
projectWithAppWithInvalidCluster.Spec.SyncWindows = append(projectWithAppWithInvalidCluster.Spec.SyncWindows, &syncWindow)
res, err := projectServer.Update(ctx, &project.ProjectUpdateRequest{
Project: projectWithAppWithInvalidCluster,
})
require.NoError(t, err)
assert.Len(t, res.Spec.SyncWindows, 1)
})
}
func newEnforcer(kubeclientset *fake.Clientset) *rbac.Enforcer {
enforcer := rbac.NewEnforcer(kubeclientset, testNamespace, common.ArgoCDRBACConfigMapName, nil)
_ = enforcer.SetBuiltinPolicy(assets.BuiltinPolicyCSV)
enforcer.SetDefaultRole("role:admin")
enforcer.SetClaimsEnforcerFunc(func(_ jwt.Claims, _ ...any) bool {
return true
})
return enforcer
}
func TestListEvents(t *testing.T) {
t.Parallel()
existingProj := &v1alpha1.AppProject{
ObjectMeta: metav1.ObjectMeta{
Name: "test-project",
Namespace: testNamespace,
UID: "test-project-uid",
},
Spec: v1alpha1.AppProjectSpec{
SourceRepos: []string{"*"},
Destinations: []v1alpha1.ApplicationDestination{{Server: "*", Namespace: "*"}},
},
}
t.Run("ListEvents returns empty list for project without events", func(t *testing.T) {
t.Parallel()
kubeclientset := fake.NewClientset(&corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Namespace: testNamespace,
Name: "argocd-cm",
Labels: map[string]string{"app.kubernetes.io/part-of": "argocd"},
},
}, &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "argocd-secret", Namespace: testNamespace},
Data: map[string][]byte{"admin.password": []byte("test"), "server.secretkey": []byte("test")},
})
settingsMgr := settings.NewSettingsManager(t.Context(), kubeclientset, testNamespace)
enforcer := newEnforcer(kubeclientset)
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", nil, session.NewUserStateStorage(nil))
argoDB := db.NewDB(testNamespace, settingsMgr, kubeclientset)
projInformer := informer.NewSharedInformerFactory(apps.NewSimpleClientset(existingProj), 0).Argoproj().V1alpha1().AppProjects().Informer()
go projInformer.Run(t.Context().Done())
k8scache.WaitForCacheSync(t.Context().Done(), projInformer.HasSynced)
projectServer := NewServer(testNamespace, kubeclientset, apps.NewSimpleClientset(existingProj), enforcer, sync.NewKeyLock(), sessionMgr, nil, projInformer, settingsMgr, argoDB, testEnableEventList)
res, err := projectServer.ListEvents(t.Context(), &project.ProjectQuery{Name: existingProj.Name})
require.NoError(t, err)
require.NotNil(t, res)
assert.Empty(t, res.Items, "items should be empty when no events exist")
})
t.Run("ListEvents returns events for project", func(t *testing.T) {
// Create events associated with the project
t.Parallel()
event1 := &corev1.Event{
ObjectMeta: metav1.ObjectMeta{
Name: "test-event-1",
Namespace: testNamespace,
},
InvolvedObject: corev1.ObjectReference{
Name: existingProj.Name,
Namespace: existingProj.Namespace,
UID: existingProj.UID,
},
Reason: "ProjectCreated",
Message: "Project was created",
Type: corev1.EventTypeNormal,
}
event2 := &corev1.Event{
ObjectMeta: metav1.ObjectMeta{
Name: "test-event-2",
Namespace: testNamespace,
},
InvolvedObject: corev1.ObjectReference{
Name: existingProj.Name,
Namespace: existingProj.Namespace,
UID: existingProj.UID,
},
Reason: "ProjectUpdated",
Message: "Project was updated",
Type: corev1.EventTypeNormal,
}
kubeclientset := fake.NewClientset(&corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Namespace: testNamespace,
Name: "argocd-cm",
Labels: map[string]string{"app.kubernetes.io/part-of": "argocd"},
},
}, &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "argocd-secret", Namespace: testNamespace},
Data: map[string][]byte{"admin.password": []byte("test"), "server.secretkey": []byte("test")},
}, event1, event2)
settingsMgr := settings.NewSettingsManager(t.Context(), kubeclientset, testNamespace)
enforcer := newEnforcer(kubeclientset)
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", nil, session.NewUserStateStorage(nil))
argoDB := db.NewDB(testNamespace, settingsMgr, kubeclientset)
projInformer := informer.NewSharedInformerFactory(apps.NewSimpleClientset(existingProj), 0).Argoproj().V1alpha1().AppProjects().Informer()
go projInformer.Run(t.Context().Done())
k8scache.WaitForCacheSync(t.Context().Done(), projInformer.HasSynced)
projectServer := NewServer(testNamespace, kubeclientset, apps.NewSimpleClientset(existingProj), enforcer, sync.NewKeyLock(), sessionMgr, nil, projInformer, settingsMgr, argoDB, testEnableEventList)
res, err := projectServer.ListEvents(t.Context(), &project.ProjectQuery{Name: existingProj.Name})
require.NoError(t, err)
require.NotNil(t, res)
assert.Len(t, res.Items, 2, "should have 2 events")
eventNames := make([]string, 0, len(res.Items))
for _, item := range res.Items {
eventNames = append(eventNames, item.Metadata.Name)
assert.NotEmpty(t, item.Reason)
assert.NotEmpty(t, item.Message)
}
assert.Contains(t, eventNames, "test-event-1")
assert.Contains(t, eventNames, "test-event-2")
})
t.Run("ListEvents returns error for non-existent project", func(t *testing.T) {
t.Parallel()
kubeclientset := fake.NewClientset(&corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Namespace: testNamespace,
Name: "argocd-cm",
Labels: map[string]string{"app.kubernetes.io/part-of": "argocd"},
},
}, &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "argocd-secret", Namespace: testNamespace},
Data: map[string][]byte{"admin.password": []byte("test"), "server.secretkey": []byte("test")},
})
settingsMgr := settings.NewSettingsManager(t.Context(), kubeclientset, testNamespace)
enforcer := newEnforcer(kubeclientset)
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", nil, session.NewUserStateStorage(nil))
argoDB := db.NewDB(testNamespace, settingsMgr, kubeclientset)
projInformer := informer.NewSharedInformerFactory(apps.NewSimpleClientset(existingProj), 0).Argoproj().V1alpha1().AppProjects().Informer()
go projInformer.Run(t.Context().Done())
k8scache.WaitForCacheSync(t.Context().Done(), projInformer.HasSynced)
projectServer := NewServer(testNamespace, kubeclientset, apps.NewSimpleClientset(existingProj), enforcer, sync.NewKeyLock(), sessionMgr, nil, projInformer, settingsMgr, argoDB, testEnableEventList)
_, err := projectServer.ListEvents(t.Context(), &project.ProjectQuery{Name: "non-existent"})
require.Error(t, err)
assert.Contains(t, err.Error(), "not found")
})
t.Run("ListEvents denied without permission", func(t *testing.T) {
t.Parallel()
kubeclientset := fake.NewClientset(&corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Namespace: testNamespace,
Name: "argocd-cm",
Labels: map[string]string{"app.kubernetes.io/part-of": "argocd"},
},
}, &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{Name: "argocd-secret", Namespace: testNamespace},
Data: map[string][]byte{"admin.password": []byte("test"), "server.secretkey": []byte("test")},
})
settingsMgr := settings.NewSettingsManager(t.Context(), kubeclientset, testNamespace)
enforcer := rbac.NewEnforcer(kubeclientset, testNamespace, common.ArgoCDRBACConfigMapName, nil)
_ = enforcer.SetBuiltinPolicy(`p, *, *, *, *, deny`)
enforcer.SetClaimsEnforcerFunc(nil)
sessionMgr := session.NewSessionManager(settingsMgr, test.NewFakeProjLister(), "", nil, session.NewUserStateStorage(nil))
argoDB := db.NewDB(testNamespace, settingsMgr, kubeclientset)
projInformer := informer.NewSharedInformerFactory(apps.NewSimpleClientset(existingProj), 0).Argoproj().V1alpha1().AppProjects().Informer()
go projInformer.Run(t.Context().Done())
k8scache.WaitForCacheSync(t.Context().Done(), projInformer.HasSynced)
projectServer := NewServer(testNamespace, kubeclientset, apps.NewSimpleClientset(existingProj), enforcer, sync.NewKeyLock(), sessionMgr, nil, projInformer, settingsMgr, argoDB, testEnableEventList)
//nolint:staticcheck
ctx := context.WithValue(t.Context(), "claims", &jwt.MapClaims{"groups": []string{"my-group"}})
_, err := projectServer.ListEvents(ctx, &project.ProjectQuery{Name: existingProj.Name})
require.Error(t, err)
statusErr, ok := status.FromError(err)
require.True(t, ok)
assert.Equal(t, codes.PermissionDenied, statusErr.Code())
})
}