Skip to content

Commit

Permalink
feat: global project (#4506)
Browse files Browse the repository at this point in the history
* feat: global project

* feat: revert back argocd-cm.yaml

* feat: remove commented code.

* feat: check err

* feat: corrected comments.

* feat: merge sync windows

* feat: getProject

* feat: fix lint error

* feat: update existing test case

* feat: minor comments

* feat: Fixed for sync window which is also called from API server.

* feat: fix application tests

* feat: block by sync window

* feat: test using sync window

* feat: updated based on code review

* feat: fixed comment
  • Loading branch information
mayzhang2000 committed Oct 13, 2020
1 parent 35914ff commit f512d21
Show file tree
Hide file tree
Showing 9 changed files with 245 additions and 9 deletions.
4 changes: 2 additions & 2 deletions controller/appcontroller.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ func isSelfReferencedApp(app *appv1.Application, ref v1.ObjectReference) bool {
}

func (ctrl *ApplicationController) getAppProj(app *appv1.Application) (*appv1.AppProject, error) {
return argo.GetAppProject(&app.Spec, applisters.NewAppProjectLister(ctrl.projInformer.GetIndexer()), ctrl.namespace)
return argo.GetAppProject(&app.Spec, applisters.NewAppProjectLister(ctrl.projInformer.GetIndexer()), ctrl.namespace, ctrl.settingsMgr)
}

func (ctrl *ApplicationController) handleObjectUpdated(managedByApp map[string]bool, ref v1.ObjectReference) {
Expand Down Expand Up @@ -304,7 +304,7 @@ func isKnownOrphanedResourceExclusion(key kube.ResourceKey, proj *appv1.AppProje
func (ctrl *ApplicationController) getResourceTree(a *appv1.Application, managedResources []*appv1.ResourceDiff) (*appv1.ApplicationTree, error) {
nodes := make([]appv1.ResourceNode, 0)

proj, err := argo.GetAppProject(&a.Spec, applisters.NewAppProjectLister(ctrl.projInformer.GetIndexer()), ctrl.namespace)
proj, err := argo.GetAppProject(&a.Spec, applisters.NewAppProjectLister(ctrl.projInformer.GetIndexer()), ctrl.namespace, ctrl.settingsMgr)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion controller/sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func (m *appStateManager) SyncAppState(app *v1alpha1.Application, state *v1alpha
revision = syncOp.Revision
}

proj, err := argo.GetAppProject(&app.Spec, listersv1alpha1.NewAppProjectLister(m.projInformer.GetIndexer()), m.namespace)
proj, err := argo.GetAppProject(&app.Spec, listersv1alpha1.NewAppProjectLister(m.projInformer.GetIndexer()), m.namespace, m.settingsMgr)
if err != nil {
state.Phase = common.OperationError
state.Message = fmt.Sprintf("Failed to load application project: %v", err)
Expand Down
7 changes: 5 additions & 2 deletions server/application/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ type Server struct {
auditLogger *argo.AuditLogger
settingsMgr *settings.SettingsManager
cache *servercache.Cache
projInformer cache.SharedIndexInformer
}

// NewServer returns a new instance of the Application service
Expand All @@ -95,6 +96,7 @@ func NewServer(
enf *rbac.Enforcer,
projectLock sync.KeyLock,
settingsMgr *settings.SettingsManager,
projInformer cache.SharedIndexInformer,
) application.ApplicationServiceServer {
appBroadcaster := &broadcasterHandler{}
appInformer.AddEventHandler(appBroadcaster)
Expand All @@ -113,6 +115,7 @@ func NewServer(
projectLock: projectLock,
auditLogger: argo.NewAuditLogger(namespace, kubeclientset, "argocd-server"),
settingsMgr: settingsMgr,
projInformer: projInformer,
}
}

Expand Down Expand Up @@ -1115,7 +1118,7 @@ func (s *Server) Sync(ctx context.Context, syncReq *application.ApplicationSyncR
return nil, err
}

proj, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Get(ctx, a.Spec.GetProject(), metav1.GetOptions{})
proj, err := argo.GetAppProject(&a.Spec, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), a.Namespace, s.settingsMgr)
if err != nil {
if apierr.IsNotFound(err) {
return a, status.Errorf(codes.InvalidArgument, "application references project %s which does not exist", a.Spec.Project)
Expand Down Expand Up @@ -1488,7 +1491,7 @@ func (s *Server) GetApplicationSyncWindows(ctx context.Context, q *application.A
return nil, err
}

proj, err := s.appclientset.ArgoprojV1alpha1().AppProjects(s.ns).Get(ctx, a.Spec.Project, metav1.GetOptions{})
proj, err := argo.GetAppProject(&a.Spec, applisters.NewAppProjectLister(s.projInformer.GetIndexer()), a.Namespace, s.settingsMgr)
if err != nil {
return nil, err
}
Expand Down
7 changes: 7 additions & 0 deletions server/application/application_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,12 @@ func newTestAppServer(objects ...runtime.Object) *Server {
panic("Timed out waiting forfff caches to sync")
}

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")
}

server := NewServer(
testNamespace,
kubeclientset,
Expand All @@ -168,6 +174,7 @@ func newTestAppServer(objects ...runtime.Object) *Server {
enforcer,
sync.NewKeyLock(),
settingsMgr,
projInformer,
)
return server.(*Server)
}
Expand Down
3 changes: 2 additions & 1 deletion server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -538,7 +538,8 @@ func (a *ArgoCDServer) newGRPCServer() *grpc.Server {
db,
a.enf,
projectLock,
a.settingsMgr)
a.settingsMgr,
a.projInformer)
projectService := project.NewServer(a.Namespace, a.KubeClientset, a.AppClientset, a.enf, projectLock, a.sessionMgr, a.policyEnforcer)
settingsService := settings.NewServer(a.settingsMgr, a, a.DisableAuth)
accountService := account.NewServer(a.sessionMgr, a.settingsMgr, a.enf)
Expand Down
125 changes: 125 additions & 0 deletions test/e2e/project_management_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package e2e
import (
"context"
"encoding/json"
"fmt"
"strconv"
"strings"
"testing"
Expand Down Expand Up @@ -430,3 +431,127 @@ func TestRemoveOrphanedIgnore(t *testing.T) {
assert.Equal(t, 0, len(proj.Spec.OrphanedResources.Ignore))
assertProjHasEvent(t, proj, "update", argo.EventReasonResourceUpdated)
}

func createAndConfigGlobalProject() error {
//Create global project
projectGlobalName := "proj-g-" + fixture.Name()
_, err := fixture.RunCli("proj", "create", projectGlobalName,
"--description", "Test description",
"-d", "https://192.168.99.100:8443,default",
"-d", "https://192.168.99.100:8443,service",
"-s", "https://github.com/argoproj/argo-cd.git",
"--orphaned-resources")
if err != nil {
return err
}

projGlobal, err := fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.ArgoCDNamespace).Get(context.Background(), projectGlobalName, metav1.GetOptions{})
if err != nil {
return err
}

projGlobal.Spec.NamespaceResourceBlacklist = []metav1.GroupKind{
{Group: "", Kind: "Service"},
}

projGlobal.Spec.SyncWindows = v1alpha1.SyncWindows{}
win := &v1alpha1.SyncWindow{Kind: "deny", Schedule: "* * * * *", Duration: "1h", Applications: []string{"*"}}
projGlobal.Spec.SyncWindows = append(projGlobal.Spec.SyncWindows, win)

_, err = fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.ArgoCDNamespace).Update(context.Background(), projGlobal, metav1.UpdateOptions{})
if err != nil {
return err
}

//Configure global project settings
globalProjectsSettings := `data:
accounts.config-service: apiKey
globalProjects: |
- labelSelector:
matchExpressions:
- key: opt
operator: In
values:
- me
- you
projectName: %s`

_, err = fixture.Run("", "kubectl", "patch", "cm", "argocd-cm",
"-n", fixture.ArgoCDNamespace,
"-p", fmt.Sprintf(globalProjectsSettings, projGlobal.Name))
if err != nil {
return err
}

return nil
}

func TestGetVirtualProjectNoMatch(t *testing.T) {
fixture.EnsureCleanState(t)
err := createAndConfigGlobalProject()
assert.NoError(t, err)

//Create project which does not matche global project settings
projectName := "proj-" + fixture.Name()
_, err = fixture.RunCli("proj", "create", projectName,
"--description", "Test description",
"-d", fmt.Sprintf("%s,*", common.KubernetesInternalAPIServerAddr),
"-s", "*",
"--orphaned-resources")
assert.NoError(t, err)

proj, err := fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.ArgoCDNamespace).Get(context.Background(), projectName, metav1.GetOptions{})
assert.NoError(t, err)

//Create an app belongs to proj project
_, err = fixture.RunCli("app", "create", fixture.Name(), "--repo", fixture.RepoURL(fixture.RepoURLTypeFile),
"--path", guestbookPath, "--project", proj.Name, "--dest-server", common.KubernetesInternalAPIServerAddr, "--dest-namespace", fixture.DeploymentNamespace())
assert.NoError(t, err)

//App trying to sync a resource which is not blacked listed anywhere
_, err = fixture.RunCli("app", "sync", fixture.Name(), "--resource", "apps:Deployment:guestbook-ui", "--timeout", fmt.Sprintf("%v", 10))
assert.NoError(t, err)

//app trying to sync a resource which is black listed by global project
_, err = fixture.RunCli("app", "sync", fixture.Name(), "--resource", ":Service:guestbook-ui", "--timeout", fmt.Sprintf("%v", 10))
assert.NoError(t, err)

}

func TestGetVirtualProjectMatch(t *testing.T) {
fixture.EnsureCleanState(t)
err := createAndConfigGlobalProject()
assert.NoError(t, err)

//Create project which matches global project settings
projectName := "proj-" + fixture.Name()
_, err = fixture.RunCli("proj", "create", projectName,
"--description", "Test description",
"-d", fmt.Sprintf("%s,*", common.KubernetesInternalAPIServerAddr),
"-s", "*",
"--orphaned-resources")
assert.NoError(t, err)

proj, err := fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.ArgoCDNamespace).Get(context.Background(), projectName, metav1.GetOptions{})
assert.NoError(t, err)

//Add a label to this project so that this project match global project selector
proj.Labels = map[string]string{"opt": "me"}
_, err = fixture.AppClientset.ArgoprojV1alpha1().AppProjects(fixture.ArgoCDNamespace).Update(context.Background(), proj, metav1.UpdateOptions{})
assert.NoError(t, err)

//Create an app belongs to proj project
_, err = fixture.RunCli("app", "create", fixture.Name(), "--repo", fixture.RepoURL(fixture.RepoURLTypeFile),
"--path", guestbookPath, "--project", proj.Name, "--dest-server", common.KubernetesInternalAPIServerAddr, "--dest-namespace", fixture.DeploymentNamespace())
assert.NoError(t, err)

//App trying to sync a resource which is not blacked listed anywhere
_, err = fixture.RunCli("app", "sync", fixture.Name(), "--resource", "apps:Deployment:guestbook-ui", "--timeout", fmt.Sprintf("%v", 10))
assert.Error(t, err)
assert.Contains(t, err.Error(), "Blocked by sync window")

//app trying to sync a resource which is black listed by global project
_, err = fixture.RunCli("app", "sync", fixture.Name(), "--resource", ":Service:guestbook-ui", "--timeout", fmt.Sprintf("%v", 10))
assert.Contains(t, err.Error(), "Blocked by sync window")

}
62 changes: 60 additions & 2 deletions util/argo/argo.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/argoproj/argo-cd/util/db"
"github.com/argoproj/argo-cd/util/git"
"github.com/argoproj/argo-cd/util/helm"
"github.com/argoproj/argo-cd/util/settings"
)

const (
Expand Down Expand Up @@ -317,8 +318,12 @@ func APIGroupsToVersions(apiGroups []metav1.APIGroup) []string {
}

// GetAppProject returns a project from an application
func GetAppProject(spec *argoappv1.ApplicationSpec, projLister applicationsv1.AppProjectLister, ns string) (*argoappv1.AppProject, error) {
return projLister.AppProjects(ns).Get(spec.GetProject())
func GetAppProject(spec *argoappv1.ApplicationSpec, projLister applicationsv1.AppProjectLister, ns string, settingsManager *settings.SettingsManager) (*argoappv1.AppProject, error) {
projOrig, err := projLister.AppProjects(ns).Get(spec.GetProject())
if err != nil {
return nil, err
}
return getAppVirtualProject(projOrig, projLister, settingsManager)
}

// verifyGenerateManifests verifies a repo path can generate manifests
Expand Down Expand Up @@ -455,3 +460,56 @@ func getDestinationServer(ctx context.Context, db db.ArgoDB, clusterName string)
}
return servers[0], nil
}

func getAppVirtualProject(proj *argoappv1.AppProject, projLister applicationsv1.AppProjectLister, settingsManager *settings.SettingsManager) (*argoappv1.AppProject, error) {
gps, err := settingsManager.GetGlobalProjectsSettings()
if err != nil {
log.Warnf("Failed to get global project settings: %v", err)
return proj, nil
}
virtualProj := proj.DeepCopy()

for _, gp := range gps {
selector, err := metav1.LabelSelectorAsSelector(&gp.LabelSelector)
if err != nil {
break
}
//Get projects which match the label selector, then see if proj is a match
projList, err := projLister.AppProjects(proj.Namespace).List(selector)
if err != nil {
break
}
var matchMe bool
for _, item := range projList {
if item.Name == proj.Name {
matchMe = true
break
}
}
if !matchMe {
break
}
//If proj is a match for this global project setting, then merge with the global project
globalProj, err := projLister.AppProjects(proj.Namespace).Get(gp.ProjectName)
if err != nil {
break
}
virtualProj = mergeVirtualProject(virtualProj, globalProj)
}
return virtualProj, nil
}

func mergeVirtualProject(proj *argoappv1.AppProject, globalProj *argoappv1.AppProject) *argoappv1.AppProject {
if globalProj == nil {
return proj
}
proj.Spec.ClusterResourceWhitelist = append(proj.Spec.ClusterResourceWhitelist, globalProj.Spec.ClusterResourceWhitelist...)
proj.Spec.ClusterResourceBlacklist = append(proj.Spec.ClusterResourceBlacklist, globalProj.Spec.ClusterResourceBlacklist...)

proj.Spec.NamespaceResourceWhitelist = append(proj.Spec.NamespaceResourceWhitelist, globalProj.Spec.NamespaceResourceWhitelist...)
proj.Spec.NamespaceResourceBlacklist = append(proj.Spec.NamespaceResourceBlacklist, globalProj.Spec.NamespaceResourceBlacklist...)

proj.Spec.SyncWindows = append(proj.Spec.SyncWindows, globalProj.Spec.SyncWindows...)

return proj
}
19 changes: 18 additions & 1 deletion util/argo/argo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import (
"github.com/stretchr/testify/mock"
"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/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/tools/cache"

argoappv1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
Expand All @@ -21,7 +23,9 @@ import (
applisters "github.com/argoproj/argo-cd/pkg/client/listers/application/v1alpha1"
"github.com/argoproj/argo-cd/reposerver/apiclient"
"github.com/argoproj/argo-cd/reposerver/apiclient/mocks"
"github.com/argoproj/argo-cd/test"
dbmocks "github.com/argoproj/argo-cd/util/db/mocks"
"github.com/argoproj/argo-cd/util/settings"
)

func TestRefreshApp(t *testing.T) {
Expand All @@ -42,6 +46,16 @@ func TestGetAppProjectWithNoProjDefined(t *testing.T) {
projName := "default"
namespace := "default"

cm := corev1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: "argocd-cm",
Namespace: test.FakeArgoCDNamespace,
Labels: map[string]string{
"app.kubernetes.io/part-of": "argocd",
},
},
}

testProj := &argoappv1.AppProject{
ObjectMeta: metav1.ObjectMeta{Name: projName, Namespace: namespace},
}
Expand All @@ -56,7 +70,10 @@ func TestGetAppProjectWithNoProjDefined(t *testing.T) {
informer := v1alpha1.NewAppProjectInformer(appClientset, namespace, 0, indexers)
go informer.Run(ctx.Done())
cache.WaitForCacheSync(ctx.Done(), informer.HasSynced)
proj, err := GetAppProject(&testApp.Spec, applisters.NewAppProjectLister(informer.GetIndexer()), namespace)

kubeClient := fake.NewSimpleClientset(&cm)
settingsMgr := settings.NewSettingsManager(context.Background(), kubeClient, test.FakeArgoCDNamespace)
proj, err := GetAppProject(&testApp.Spec, applisters.NewAppProjectLister(informer.GetIndexer()), namespace, settingsMgr)
assert.Nil(t, err)
assert.Equal(t, proj.Name, projName)
}
Expand Down
Loading

0 comments on commit f512d21

Please sign in to comment.