From e81f85c2176ee2e382965976e84bf832c7322a90 Mon Sep 17 00:00:00 2001 From: Manabu Mccloskey Date: Tue, 28 Nov 2023 18:00:52 +0000 Subject: [PATCH 1/7] allow for custom packages to be installed Signed-off-by: Manabu Mccloskey --- api/v1alpha1/gitrepository_types.go | 2 +- api/v1alpha1/localbuild_types.go | 7 + api/v1alpha1/zz_generated.deepcopy.go | 24 +- pkg/build/build.go | 21 +- pkg/cmd/create/root.go | 34 ++- pkg/controllers/localbuild/controller.go | 214 ++++++++++++++++-- pkg/controllers/localbuild/controller_test.go | 181 +++++++++++++++ .../resources/customPackages/testDir/app.yaml | 19 ++ .../testDir/busybox/busybox.yaml | 17 ++ .../customPackages/testDir2/exampleApp.yaml | 17 ++ .../customPackages/testDir2/exampleApp2.yaml | 17 ++ .../idpbuilder.cnoe.io_localbuilds.yaml | 11 + 12 files changed, 534 insertions(+), 30 deletions(-) create mode 100644 pkg/controllers/localbuild/controller_test.go create mode 100644 pkg/controllers/localbuild/test/resources/customPackages/testDir/app.yaml create mode 100644 pkg/controllers/localbuild/test/resources/customPackages/testDir/busybox/busybox.yaml create mode 100644 pkg/controllers/localbuild/test/resources/customPackages/testDir2/exampleApp.yaml create mode 100644 pkg/controllers/localbuild/test/resources/customPackages/testDir2/exampleApp2.yaml diff --git a/api/v1alpha1/gitrepository_types.go b/api/v1alpha1/gitrepository_types.go index 2146316c..5f8638d2 100644 --- a/api/v1alpha1/gitrepository_types.go +++ b/api/v1alpha1/gitrepository_types.go @@ -18,7 +18,7 @@ type GitRepositorySpec struct { type GitRepositorySource struct { // +kubebuilder:validation:Enum:=argocd;backstage;crossplane;gitea;nginx // +kubebuilder:validation:Optional - EmbeddedAppName string `json:"embeddedAppName"` + EmbeddedAppName string `json:"embeddedAppName,omitempty"` // Path is the absolute path to directory that contains Kustomize structure or raw manifests. // This is required when Type is set to local. // +kubebuilder:validation:Optional diff --git a/api/v1alpha1/localbuild_types.go b/api/v1alpha1/localbuild_types.go index f76169ea..beec5041 100644 --- a/api/v1alpha1/localbuild_types.go +++ b/api/v1alpha1/localbuild_types.go @@ -30,6 +30,13 @@ type PackageConfigsSpec struct { GitConfig GitConfigSpec `json:"gitConfig,omitempty"` Argo ArgoPackageConfigSpec `json:"argoPackageConfigs,omitempty"` EmbeddedArgoApplications EmbeddedArgoApplicationsPackageConfigSpec `json:"embeddedArgoApplicationsPackageConfigs,omitempty"` + CustomPackages []CustomPackageSpec `json:"customPackages,omitempty"` +} + +// CustomPackageSpec controls the installation of the custom argocd applications. +type CustomPackageSpec struct { + // Directory specifies the absolute path to the directory which contains ArgoCD Application manifests + Directory string `json:"directory,omitempty"` } type LocalbuildSpec struct { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 6399c8b1..febd545a 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -70,6 +70,21 @@ func (in *Commit) DeepCopy() *Commit { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CustomPackageSpec) DeepCopyInto(out *CustomPackageSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomPackageSpec. +func (in *CustomPackageSpec) DeepCopy() *CustomPackageSpec { + if in == nil { + return nil + } + out := new(CustomPackageSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *EmbeddedArgoApplicationsPackageConfigSpec) DeepCopyInto(out *EmbeddedArgoApplicationsPackageConfigSpec) { *out = *in @@ -332,7 +347,7 @@ func (in *Localbuild) DeepCopyInto(out *Localbuild) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status } @@ -389,7 +404,7 @@ func (in *LocalbuildList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LocalbuildSpec) DeepCopyInto(out *LocalbuildSpec) { *out = *in - out.PackageConfigs = in.PackageConfigs + in.PackageConfigs.DeepCopyInto(&out.PackageConfigs) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalbuildSpec. @@ -441,6 +456,11 @@ func (in *PackageConfigsSpec) DeepCopyInto(out *PackageConfigsSpec) { out.GitConfig = in.GitConfig out.Argo = in.Argo out.EmbeddedArgoApplications = in.EmbeddedArgoApplications + if in.CustomPackages != nil { + in, out := &in.CustomPackages, &out.CustomPackages + *out = make([]CustomPackageSpec, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PackageConfigsSpec. diff --git a/pkg/build/build.go b/pkg/build/build.go index f1611486..937ae602 100644 --- a/pkg/build/build.go +++ b/pkg/build/build.go @@ -2,7 +2,7 @@ package build import ( "context" - + "fmt" "github.com/cnoe-io/idpbuilder/api/v1alpha1" "github.com/cnoe-io/idpbuilder/globals" "github.com/cnoe-io/idpbuilder/pkg/controllers" @@ -27,17 +27,19 @@ type Build struct { kubeConfigPath string kubeVersion string extraPortsMapping string + customPackageDirs []string scheme *runtime.Scheme CancelFunc context.CancelFunc } -func NewBuild(name, kubeVersion, kubeConfigPath, kindConfigPath, extraPortsMapping string, scheme *runtime.Scheme, ctxCancel context.CancelFunc) *Build { +func NewBuild(name, kubeVersion, kubeConfigPath, kindConfigPath, extraPortsMapping string, customPackageDirs []string, scheme *runtime.Scheme, ctxCancel context.CancelFunc) *Build { return &Build{ name: name, kindConfigPath: kindConfigPath, kubeConfigPath: kubeConfigPath, kubeVersion: kubeVersion, extraPortsMapping: extraPortsMapping, + customPackageDirs: customPackageDirs, scheme: scheme, CancelFunc: ctxCancel, } @@ -144,6 +146,8 @@ func (b *Build) Run(ctx context.Context, recreateCluster bool) error { }, } + pkgs, err := getPackages(b.customPackageDirs) + setupLog.Info("Creating localbuild resource") _, err = controllerutil.CreateOrUpdate(ctx, kubeClient, &localBuild, func() error { localBuild.Spec = v1alpha1.LocalbuildSpec{ @@ -158,10 +162,14 @@ func (b *Build) Run(ctx context.Context, recreateCluster bool) error { // hint: for the old behavior, replace Type value below with globals.GitServerResourcename() Type: globals.GiteaResourceName(), }, + CustomPackages: pkgs, }, } return nil }) + if err != nil { + return fmt.Errorf("creating localbuild resource: %w", err) + } if err != nil { setupLog.Error(err, "Error creating localbuild resource") @@ -172,3 +180,12 @@ func (b *Build) Run(ctx context.Context, recreateCluster bool) error { close(managerExit) return err } + +func getPackages(srcDirs []string) ([]v1alpha1.CustomPackageSpec, error) { + out := make([]v1alpha1.CustomPackageSpec, len(srcDirs), len(srcDirs)) + for i := range srcDirs { + out[i] = v1alpha1.CustomPackageSpec{Directory: srcDirs[i]} + } + + return out, nil +} diff --git a/pkg/cmd/create/root.go b/pkg/cmd/create/root.go index d7f3fe1a..b44e1e87 100644 --- a/pkg/cmd/create/root.go +++ b/pkg/cmd/create/root.go @@ -23,6 +23,7 @@ var ( kubeVersion string extraPortsMapping string kindConfigPath string + extraPackagesDirs []string ) var CreateCmd = &cobra.Command{ @@ -38,6 +39,7 @@ func init() { CreateCmd.PersistentFlags().StringVar(&kubeVersion, "kubeVersion", "v1.26.3", "Version of the kind kubernetes cluster to create.") CreateCmd.PersistentFlags().StringVar(&extraPortsMapping, "extraPorts", "", "List of extra ports to expose on the docker container and kubernetes cluster as nodePort (e.g. \"22:32222,9090:39090,etc\").") CreateCmd.PersistentFlags().StringVar(&kindConfigPath, "kindConfig", "", "Path of the kind config file to be used instead of the default.") + CreateCmd.Flags().StringSliceVarP(&extraPackagesDirs, "package-dir", "p", []string{}, "paths to custom packages") zapfs := flag.NewFlagSet("zap", flag.ExitOnError) opts := zap.Options{ @@ -60,10 +62,40 @@ func create(cmd *cobra.Command, args []string) error { os.Exit(1) } - b := build.NewBuild(buildName, kubeVersion, kubeConfigPath, kindConfigPath, extraPortsMapping, k8s.GetScheme(), ctxCancel) + var absPaths []string + if len(extraPackagesDirs) > 0 { + p, err := getPackageAbsDirs(extraPackagesDirs) + if err != nil { + return err + } + absPaths = p + } + + b := build.NewBuild(buildName, kubeVersion, kubeConfigPath, kindConfigPath, extraPortsMapping, absPaths, k8s.GetScheme(), ctxCancel) if err := b.Run(ctx, recreateCluster); err != nil { return err } return nil } + +func getPackageAbsDirs(paths []string) ([]string, error) { + out := make([]string, len(paths), len(paths)) + for i := range paths { + path := paths[i] + absPath, err := filepath.Abs(path) + if err != nil { + return nil, fmt.Errorf("failed to validate path %s : %w", path, err) + } + f, err := os.Stat(absPath) + if err != nil { + return nil, fmt.Errorf("failed to validate path %s : %w", absPath, err) + } + if !f.IsDir() { + return nil, fmt.Errorf("given path is not a directory. %s", absPath) + } + out[i] = absPath + } + + return out, nil +} diff --git a/pkg/controllers/localbuild/controller.go b/pkg/controllers/localbuild/controller.go index 04a2047a..54075fa6 100644 --- a/pkg/controllers/localbuild/controller.go +++ b/pkg/controllers/localbuild/controller.go @@ -3,6 +3,10 @@ package localbuild import ( "context" "fmt" + "github.com/cnoe-io/idpbuilder/pkg/k8s" + "os" + "path/filepath" + "strings" "time" argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" @@ -298,6 +302,14 @@ func (r *LocalbuildReconciler) ReconcileArgoAppsWithGitea(ctx context.Context, r return result, fmt.Errorf("reconciling embedded apps %w", err) } } + if resource.Spec.PackageConfigs.CustomPackages != nil && len(resource.Spec.PackageConfigs.CustomPackages) > 0 { + for i := range resource.Spec.PackageConfigs.CustomPackages { + result, err := r.reconcileCustomPkg(ctx, resource, resource.Spec.PackageConfigs.CustomPackages[i]) + if err != nil { + return result, err + } + } + } shutdown, err := r.shouldShutDown(ctx, resource) if err != nil { @@ -311,31 +323,9 @@ func (r *LocalbuildReconciler) ReconcileArgoAppsWithGitea(ctx context.Context, r func (r *LocalbuildReconciler) reconcileEmbeddedApp(ctx context.Context, appName string, resource *v1alpha1.Localbuild) (ctrl.Result, error) { logger := log.FromContext(ctx) - logger.Info("Ensuring Argo Application", "name", appName) - repo := &v1alpha1.GitRepository{ - ObjectMeta: metav1.ObjectMeta{ - Name: appName, - Namespace: globals.GetProjectNamespace(resource.Name), - }, - Spec: v1alpha1.GitRepositorySpec{ - Source: v1alpha1.GitRepositorySource{ - EmbeddedAppName: appName, - Type: "embedded", - }, - GitURL: resource.Status.Gitea.ExternalURL, - SecretRef: v1alpha1.SecretReference{ - Name: resource.Status.Gitea.AdminUserSecretName, - Namespace: resource.Status.Gitea.AdminUserSecretNamespace, - }, - }, - } + logger.Info("Ensuring embedded ArgoCD Application", "name", appName) + repo, err := r.reconcileGitRepo(ctx, resource, "embedded", appName, appName, "") - _, err := controllerutil.CreateOrUpdate(ctx, r.Client, repo, func() error { - if err := controllerutil.SetControllerReference(resource, repo, r.Scheme); err != nil { - return err - } - return nil - }) if err != nil { return ctrl.Result{}, fmt.Errorf("creating %s repo CR: %w", appName, err) } @@ -371,6 +361,9 @@ func (r *LocalbuildReconciler) reconcileEmbeddedApp(ctx context.Context, appName } func (r *LocalbuildReconciler) shouldShutDown(ctx context.Context, resource *v1alpha1.Localbuild) (bool, error) { + if len(resource.Spec.PackageConfigs.CustomPackages) > 0 { + return false, nil + } repos := &v1alpha1.GitRepositoryList{} err := r.Client.List(ctx, repos, client.InNamespace(resource.Namespace)) if err != nil { @@ -385,6 +378,155 @@ func (r *LocalbuildReconciler) shouldShutDown(ctx context.Context, resource *v1a return true, nil } +func (r *LocalbuildReconciler) reconcileCustomPkg(ctx context.Context, resource *v1alpha1.Localbuild, pkg v1alpha1.CustomPackageSpec) (ctrl.Result, error) { + logger := log.FromContext(ctx) + + sc := runtime.NewScheme() + err := argov1alpha1.SchemeBuilder.AddToScheme(sc) + if err != nil { + return ctrl.Result{}, fmt.Errorf("adding argocd application scheme: %w", err) + } + + files, err := os.ReadDir(pkg.Directory) + if err != nil { + return ctrl.Result{}, fmt.Errorf("reading dir, %s: %w", pkg.Directory, err) + } + + for i := range files { + file := files[i] + if !file.Type().IsRegular() { + continue + } + + filePath := filepath.Join(pkg.Directory, file.Name()) + b, fErr := os.ReadFile(filePath) + if fErr != nil { + logger.Error(fErr, "reading file", "file", filePath) + continue + } + + objs, fErr := k8s.ConvertYamlToObjects(sc, b) + if fErr != nil { + //logger.Error(CErr, "converting yaml to object", "file", filePath) + continue + } + if len(objs) == 0 { + continue + } + + app, ok := objs[0].(*argov1alpha1.Application) + if !ok { + continue + } + + appName := app.GetName() + if appName == "" { + continue + } + + logger.Info("Ensuring custom ArgoCD Application", "name", appName) + if app.Spec.HasMultipleSources() { + for j := range app.Spec.Sources { + s := app.Spec.Sources[j] + res, repo, sErr := r.reconcileArgocdSource(ctx, resource, appName, pkg.Directory, s.RepoURL) + if sErr != nil { + return res, sErr + } + if repo != nil { + s.RepoURL = getRepositoryURL(repo.Namespace, repo.Name, resource.Status.Gitea.InternalURL) + } + } + } else { + s := app.Spec.Source + res, repo, sErr := r.reconcileArgocdSource(ctx, resource, appName, pkg.Directory, s.RepoURL) + if sErr != nil { + return res, sErr + } + if repo != nil { + s.RepoURL = getRepositoryURL(repo.Namespace, repo.Name, resource.Status.Gitea.InternalURL) + } + } + + foundAppObj := argov1alpha1.Application{} + err = r.Client.Get(ctx, client.ObjectKeyFromObject(app), &foundAppObj) + if err != nil { + if errors.IsNotFound(err) { + err = r.Client.Create(ctx, app) + if err != nil { + return ctrl.Result{}, fmt.Errorf("creating %s app CR: %w", appName, err) + } + + return ctrl.Result{}, nil + } + return ctrl.Result{}, fmt.Errorf("getting argocd application object: %w", err) + } + + foundAppObj.Spec = app.Spec + foundAppObj.ObjectMeta.Annotations = app.Annotations + foundAppObj.ObjectMeta.Labels = app.Labels + err = r.Client.Update(ctx, &foundAppObj) + if err != nil { + return ctrl.Result{}, fmt.Errorf("updating argocd application object %s: %w", appName, err) + } + } + + return ctrl.Result{}, nil +} + +func (r *LocalbuildReconciler) reconcileArgocdSource(ctx context.Context, resource *v1alpha1.Localbuild, appName, pkgDir, repoURL string) (ctrl.Result, *v1alpha1.GitRepository, error) { + logger := log.FromContext(ctx) + + process, absPath, err := isCNOEDirectory(pkgDir, repoURL) + if err != nil { + logger.Error(err, "processing argocd app source", "dir", pkgDir, "repoURL", repoURL) + return ctrl.Result{RequeueAfter: time.Second * 60}, nil, nil + } + if !process { + return ctrl.Result{}, nil, nil + } + + repo, err := r.reconcileGitRepo(ctx, resource, "local", repoName(appName, absPath), "", absPath) + if err != nil { + return ctrl.Result{}, nil, err + } + + return ctrl.Result{}, repo, nil +} + +func (r *LocalbuildReconciler) reconcileGitRepo(ctx context.Context, resource *v1alpha1.Localbuild, repoType, repoName, embeddedName, absPath string) (*v1alpha1.GitRepository, error) { + repo := &v1alpha1.GitRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: repoName, + Namespace: globals.GetProjectNamespace(resource.Name), + }, + Spec: v1alpha1.GitRepositorySpec{ + Source: v1alpha1.GitRepositorySource{ + Type: repoType, + }, + GitURL: resource.Status.Gitea.ExternalURL, + SecretRef: v1alpha1.SecretReference{ + Name: resource.Status.Gitea.AdminUserSecretName, + Namespace: resource.Status.Gitea.AdminUserSecretNamespace, + }, + }, + } + + if repoType == "embedded" { + repo.Spec.Source.EmbeddedAppName = embeddedName + } else { + repo.Spec.Source.Path = absPath + } + + _, err := controllerutil.CreateOrUpdate(ctx, r.Client, repo, func() error { + if err := controllerutil.SetControllerReference(resource, repo, r.Scheme); err != nil { + return err + } + return nil + }) + + return repo, err +} + func GetEmbeddedRawInstallResources(name string) ([][]byte, error) { switch name { case "argocd": @@ -399,3 +541,27 @@ func GetEmbeddedRawInstallResources(name string) ([][]byte, error) { return nil, fmt.Errorf("unsupported embedded app name %s", name) } } + +func isCNOEDirectory(parentDir, path string) (bool, string, error) { + if strings.HasPrefix(path, "cnoe://") { + relativePath := strings.TrimPrefix(path, "cnoe://") + absPath, err := filepath.Abs(filepath.Join(parentDir, relativePath)) + if err != nil { + return false, "", err + } + + f, err := os.Stat(absPath) + if err != nil { + return false, "", err + } + if !f.IsDir() { + return false, "", fmt.Errorf("path not a directory: %s", absPath) + } + return true, absPath, err + } + return false, "", nil +} + +func repoName(appName, dir string) string { + return fmt.Sprintf("%s-%s", appName, filepath.Base(dir)) +} diff --git a/pkg/controllers/localbuild/controller_test.go b/pkg/controllers/localbuild/controller_test.go new file mode 100644 index 00000000..dc61f16c --- /dev/null +++ b/pkg/controllers/localbuild/controller_test.go @@ -0,0 +1,181 @@ +package localbuild + +import ( + "context" + "fmt" + argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" + "github.com/cnoe-io/idpbuilder/api/v1alpha1" + "github.com/cnoe-io/idpbuilder/globals" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + k8sruntime "k8s.io/apimachinery/pkg/runtime" + "path/filepath" + "reflect" + "runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + "strings" + "testing" +) + +func TestReconcileCustomPkg(t *testing.T) { + s := k8sruntime.NewScheme() + sb := k8sruntime.NewSchemeBuilder( + v1.AddToScheme, + argov1alpha1.AddToScheme, + v1alpha1.AddToScheme, + ) + sb.AddToScheme(s) + testEnv := &envtest.Environment{ + CRDDirectoryPaths: []string{ + filepath.Join("..", "resources"), + "resources/argo/install.yaml", + }, + ErrorIfCRDPathMissing: true, + Scheme: s, + BinaryAssetsDirectory: filepath.Join("..", "..", "..", "bin", "k8s", + fmt.Sprintf("1.27.1-%s-%s", runtime.GOOS, runtime.GOARCH)), + } + + cfg, err := testEnv.Start() + if err != nil { + t.Fatalf("Starting testenv: %v", err) + } + defer testEnv.Stop() + + mgr, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: s, + }) + if err != nil { + t.Fatalf("getting manager: %v", err) + } + + ctx, ctxCancel := context.WithCancel(context.Background()) + stoppedCh := make(chan error) + go func() { + err := mgr.Start(ctx) + stoppedCh <- err + }() + + defer func() { + ctxCancel() + err := <-stoppedCh + if err != nil { + t.Errorf("Starting controller manager: %v", err) + t.FailNow() + } + }() + + r := &LocalbuildReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + CancelFunc: nil, + shouldShutdown: false, + } + customPkgs := []v1alpha1.CustomPackageSpec{ + { + Directory: "test/resources/customPackages/testDir", + }, + { + Directory: "test/resources/customPackages/testDir2", + }, + } + + res := v1alpha1.Localbuild{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + UID: "uid", + }, + Spec: v1alpha1.LocalbuildSpec{ + PackageConfigs: v1alpha1.PackageConfigsSpec{ + GitConfig: v1alpha1.GitConfigSpec{}, + Argo: v1alpha1.ArgoPackageConfigSpec{}, + EmbeddedArgoApplications: v1alpha1.EmbeddedArgoApplicationsPackageConfigSpec{}, + CustomPackages: customPkgs, + }, + }, + Status: v1alpha1.LocalbuildStatus{ + Gitea: v1alpha1.GiteaStatus{ + Available: true, + ExternalURL: "https://cnoe.io", + InternalURL: "http://internal.cnoe.io", + AdminUserSecretName: "abc", + AdminUserSecretNamespace: "abc", + }, + }, + } + + for _, n := range []string{"argocd", globals.GetProjectNamespace(res.Name)} { + ns := v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: n, + }, + } + err = mgr.GetClient().Create(context.Background(), &ns) + if err != nil { + t.Fatalf("creating test ns: %v", err) + } + } + + for i := range customPkgs { + _, err = r.reconcileCustomPkg(context.Background(), &res, customPkgs[i]) + if err != nil { + t.Fatalf("reconciling custom packages %v", err) + } + } + + // verify repo. + c := mgr.GetClient() + repo := v1alpha1.GitRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-app-busybox", + Namespace: globals.GetProjectNamespace(res.Name), + }, + } + err = c.Get(context.Background(), client.ObjectKeyFromObject(&repo), &repo) + if err != nil { + t.Fatalf("getting my-app-busybox git repo %v", err) + } + + p, _ := filepath.Abs("test/resources/customPackages/testDir/busybox") + expectedRepo := v1alpha1.GitRepository{ + Spec: v1alpha1.GitRepositorySpec{ + Source: v1alpha1.GitRepositorySource{ + Type: "local", + Path: p, + }, + GitURL: "https://cnoe.io", + SecretRef: v1alpha1.SecretReference{ + Name: "abc", + Namespace: "abc", + }, + }, + } + ok := reflect.DeepEqual(repo.Spec, expectedRepo.Spec) + if !ok { + t.Fatalf("expected spec does not match") + } + + // verify argocd apps + localApp := argov1alpha1.Application{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-app", + Namespace: "argocd", + }, + } + err = c.Get(context.Background(), client.ObjectKeyFromObject(&localApp), &localApp) + if err != nil { + t.Fatalf("failed getting my-app %v", err) + } + if strings.HasPrefix(localApp.Spec.Source.RepoURL, "cnoe://") { + t.Fatalf("cnoe:// prefix should be removed") + } + + for _, n := range []string{"guestbook", "guestbook2"} { + err = c.Get(context.Background(), client.ObjectKeyFromObject(&localApp), &localApp) + if err != nil { + t.Fatalf("expected %s arogapp : %v", n, err) + } + } +} diff --git a/pkg/controllers/localbuild/test/resources/customPackages/testDir/app.yaml b/pkg/controllers/localbuild/test/resources/customPackages/testDir/app.yaml new file mode 100644 index 00000000..15bb1fd3 --- /dev/null +++ b/pkg/controllers/localbuild/test/resources/customPackages/testDir/app.yaml @@ -0,0 +1,19 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: my-app + namespace: argocd +spec: + destination: + namespace: my-app + server: "https://kubernetes.default.svc" + source: + repoURL: cnoe://busybox + targetRevision: HEAD + path: "." + project: default + syncPolicy: + automated: + selfHeal: true + syncOptions: + - CreateNamespace=true diff --git a/pkg/controllers/localbuild/test/resources/customPackages/testDir/busybox/busybox.yaml b/pkg/controllers/localbuild/test/resources/customPackages/testDir/busybox/busybox.yaml new file mode 100644 index 00000000..47a63156 --- /dev/null +++ b/pkg/controllers/localbuild/test/resources/customPackages/testDir/busybox/busybox.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Pod +metadata: + name: busybox + namespace: argocd + labels: + abc: ded + notused: remove-me +spec: + containers: + - image: alpine:3.18 + command: + - sleep + - "3600" + imagePullPolicy: IfNotPresent + name: busybox + restartPolicy: Always diff --git a/pkg/controllers/localbuild/test/resources/customPackages/testDir2/exampleApp.yaml b/pkg/controllers/localbuild/test/resources/customPackages/testDir2/exampleApp.yaml new file mode 100644 index 00000000..25281aeb --- /dev/null +++ b/pkg/controllers/localbuild/test/resources/customPackages/testDir2/exampleApp.yaml @@ -0,0 +1,17 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: guestbook + namespace: argocd +spec: + project: default + source: + repoURL: https://github.com/argoproj/argocd-example-apps.git + targetRevision: HEAD + path: guestbook + destination: + server: https://kubernetes.default.svc + namespace: guestbook + syncPolicy: + syncOptions: + - CreateNamespace=true diff --git a/pkg/controllers/localbuild/test/resources/customPackages/testDir2/exampleApp2.yaml b/pkg/controllers/localbuild/test/resources/customPackages/testDir2/exampleApp2.yaml new file mode 100644 index 00000000..cb7bed4d --- /dev/null +++ b/pkg/controllers/localbuild/test/resources/customPackages/testDir2/exampleApp2.yaml @@ -0,0 +1,17 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: guestbook2 + namespace: argocd +spec: + project: default + source: + repoURL: https://github.com/argoproj/argocd-example-apps.git + targetRevision: HEAD + path: guestbook + destination: + server: https://kubernetes.default.svc + namespace: guestbook2 + syncPolicy: + syncOptions: + - CreateNamespace=true diff --git a/pkg/controllers/resources/idpbuilder.cnoe.io_localbuilds.yaml b/pkg/controllers/resources/idpbuilder.cnoe.io_localbuilds.yaml index a426d676..559b217a 100644 --- a/pkg/controllers/resources/idpbuilder.cnoe.io_localbuilds.yaml +++ b/pkg/controllers/resources/idpbuilder.cnoe.io_localbuilds.yaml @@ -43,6 +43,17 @@ spec: description: Enabled controls whether to install ArgoCD. type: boolean type: object + customPackages: + items: + description: CustomPackageSpec controls the installation of + the custom argocd applications. + properties: + directory: + description: Directory specifies the absolute path to the + directory which contains raw manifests to be installed + type: string + type: object + type: array embeddedArgoApplicationsPackageConfigs: description: EmbeddedArgoApplicationsPackageConfigSpec Controls the installation of the embedded argo applications. From 9f8354499e543c37aeaf417ae088173a5d46ee69 Mon Sep 17 00:00:00 2001 From: Manabu Mccloskey Date: Thu, 7 Dec 2023 20:40:43 +0000 Subject: [PATCH 2/7] custom package root type Signed-off-by: Manabu Mccloskey --- api/v1alpha1/custom_package_types.go | 44 +++ api/v1alpha1/gitrepository_types.go | 2 + api/v1alpha1/groupversion_info.go | 1 + api/v1alpha1/localbuild_types.go | 8 +- api/v1alpha1/zz_generated.deepcopy.go | 97 ++++- pkg/build/build.go | 13 +- pkg/controllers/custompackage/controller.go | 195 ++++++++++ pkg/controllers/gitrepository/controller.go | 5 + pkg/controllers/localbuild/controller.go | 179 +++------ pkg/controllers/localbuild/controller_test.go | 359 +++++++++--------- pkg/controllers/localbuild/gitea.go | 6 - .../idpbuilder.cnoe.io_custompackages.yaml | 87 +++++ .../idpbuilder.cnoe.io_gitrepositories.yaml | 5 + .../idpbuilder.cnoe.io_localbuilds.yaml | 13 +- pkg/controllers/run.go | 10 + 15 files changed, 692 insertions(+), 332 deletions(-) create mode 100644 api/v1alpha1/custom_package_types.go create mode 100644 pkg/controllers/custompackage/controller.go create mode 100644 pkg/controllers/resources/idpbuilder.cnoe.io_custompackages.yaml diff --git a/api/v1alpha1/custom_package_types.go b/api/v1alpha1/custom_package_types.go new file mode 100644 index 00000000..8d0bd33f --- /dev/null +++ b/api/v1alpha1/custom_package_types.go @@ -0,0 +1,44 @@ +package v1alpha1 + +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +type CustomPackage struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec CustomPackageSpec `json:"spec,omitempty"` + Status CustomPackageStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true +type CustomPackageList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []CustomPackage `json:"items"` +} + +// CustomPackageSpec controls the installation of the custom applications. +type CustomPackageSpec struct { + //// Type specifies what kind of package this is. local means local files specified in the Directory field must be synced to a Git Repository + //Type string `json:"type"` + // Replicate specifies whether to replicate remote or local contents to the local gitea server. + Replicate bool `json:"replicate"` + // GitServerURL specifies the base URL for the git server for API calls. for example, http://gitea.cnoe.localtest.me:8880 + GitServerURL string `json:"gitServerURL"` + GitServerAuthSecretRef SecretReference `json:"gitServerAuthSecretRef"` + + ArgoCD ArgoCDPackageSpec `json:"argoCD,omitempty"` +} + +type ArgoCDPackageSpec struct { + // ApplicationFile specifies the absolute path to the ArgoCD application file + ApplicationFile string `json:"applicationFile"` + Name string `json:"name"` + Namespace string `json:"namespace"` +} + +type CustomPackageStatus struct { + Synced bool `json:"synced,omitempty"` +} diff --git a/api/v1alpha1/gitrepository_types.go b/api/v1alpha1/gitrepository_types.go index 5f8638d2..dfd339dd 100644 --- a/api/v1alpha1/gitrepository_types.go +++ b/api/v1alpha1/gitrepository_types.go @@ -10,6 +10,8 @@ type GitRepositorySpec struct { // +kubebuilder:validation:Required // +kubebuilder:validation:Pattern=`^https?:\/\/.+$` GitURL string `json:"gitURL"` + // InternalGitURL is the base URL of Git server accessible within the cluster only. + InternalGitURL string `json:"internalGitURL"` // SecretRef is the reference to secret that contain Git server credentials // +kubebuilder:validation:Optional SecretRef SecretReference `json:"secretRef"` diff --git a/api/v1alpha1/groupversion_info.go b/api/v1alpha1/groupversion_info.go index 4a493739..888923e9 100644 --- a/api/v1alpha1/groupversion_info.go +++ b/api/v1alpha1/groupversion_info.go @@ -22,4 +22,5 @@ func init() { SchemeBuilder.Register(&Localbuild{}, &LocalbuildList{}) SchemeBuilder.Register(&GitServer{}, &GitServerList{}) SchemeBuilder.Register(&GitRepository{}, &GitRepositoryList{}) + SchemeBuilder.Register(&CustomPackage{}, &CustomPackageList{}) } diff --git a/api/v1alpha1/localbuild_types.go b/api/v1alpha1/localbuild_types.go index beec5041..f473d06b 100644 --- a/api/v1alpha1/localbuild_types.go +++ b/api/v1alpha1/localbuild_types.go @@ -30,13 +30,7 @@ type PackageConfigsSpec struct { GitConfig GitConfigSpec `json:"gitConfig,omitempty"` Argo ArgoPackageConfigSpec `json:"argoPackageConfigs,omitempty"` EmbeddedArgoApplications EmbeddedArgoApplicationsPackageConfigSpec `json:"embeddedArgoApplicationsPackageConfigs,omitempty"` - CustomPackages []CustomPackageSpec `json:"customPackages,omitempty"` -} - -// CustomPackageSpec controls the installation of the custom argocd applications. -type CustomPackageSpec struct { - // Directory specifies the absolute path to the directory which contains ArgoCD Application manifests - Directory string `json:"directory,omitempty"` + CustomPackageDirs []string `json:"customPackageDirs"` } type LocalbuildSpec struct { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index febd545a..6aa6cc56 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -25,6 +25,21 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ArgoCDPackageSpec) DeepCopyInto(out *ArgoCDPackageSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgoCDPackageSpec. +func (in *ArgoCDPackageSpec) DeepCopy() *ArgoCDPackageSpec { + if in == nil { + return nil + } + out := new(ArgoCDPackageSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ArgoCDStatus) DeepCopyInto(out *ArgoCDStatus) { *out = *in @@ -70,9 +85,70 @@ func (in *Commit) DeepCopy() *Commit { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CustomPackage) DeepCopyInto(out *CustomPackage) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomPackage. +func (in *CustomPackage) DeepCopy() *CustomPackage { + if in == nil { + return nil + } + out := new(CustomPackage) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CustomPackage) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CustomPackageList) DeepCopyInto(out *CustomPackageList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]CustomPackage, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomPackageList. +func (in *CustomPackageList) DeepCopy() *CustomPackageList { + if in == nil { + return nil + } + out := new(CustomPackageList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *CustomPackageList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CustomPackageSpec) DeepCopyInto(out *CustomPackageSpec) { *out = *in + out.GitServerAuthSecretRef = in.GitServerAuthSecretRef + out.ArgoCD = in.ArgoCD } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomPackageSpec. @@ -85,6 +161,21 @@ func (in *CustomPackageSpec) DeepCopy() *CustomPackageSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *CustomPackageStatus) DeepCopyInto(out *CustomPackageStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomPackageStatus. +func (in *CustomPackageStatus) DeepCopy() *CustomPackageStatus { + if in == nil { + return nil + } + out := new(CustomPackageStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *EmbeddedArgoApplicationsPackageConfigSpec) DeepCopyInto(out *EmbeddedArgoApplicationsPackageConfigSpec) { *out = *in @@ -456,9 +547,9 @@ func (in *PackageConfigsSpec) DeepCopyInto(out *PackageConfigsSpec) { out.GitConfig = in.GitConfig out.Argo = in.Argo out.EmbeddedArgoApplications = in.EmbeddedArgoApplications - if in.CustomPackages != nil { - in, out := &in.CustomPackages, &out.CustomPackages - *out = make([]CustomPackageSpec, len(*in)) + if in.CustomPackageDirs != nil { + in, out := &in.CustomPackageDirs, &out.CustomPackageDirs + *out = make([]string, len(*in)) copy(*out, *in) } } diff --git a/pkg/build/build.go b/pkg/build/build.go index 937ae602..39155bc8 100644 --- a/pkg/build/build.go +++ b/pkg/build/build.go @@ -146,8 +146,6 @@ func (b *Build) Run(ctx context.Context, recreateCluster bool) error { }, } - pkgs, err := getPackages(b.customPackageDirs) - setupLog.Info("Creating localbuild resource") _, err = controllerutil.CreateOrUpdate(ctx, kubeClient, &localBuild, func() error { localBuild.Spec = v1alpha1.LocalbuildSpec{ @@ -162,7 +160,7 @@ func (b *Build) Run(ctx context.Context, recreateCluster bool) error { // hint: for the old behavior, replace Type value below with globals.GitServerResourcename() Type: globals.GiteaResourceName(), }, - CustomPackages: pkgs, + CustomPackageDirs: b.customPackageDirs, }, } return nil @@ -180,12 +178,3 @@ func (b *Build) Run(ctx context.Context, recreateCluster bool) error { close(managerExit) return err } - -func getPackages(srcDirs []string) ([]v1alpha1.CustomPackageSpec, error) { - out := make([]v1alpha1.CustomPackageSpec, len(srcDirs), len(srcDirs)) - for i := range srcDirs { - out[i] = v1alpha1.CustomPackageSpec{Directory: srcDirs[i]} - } - - return out, nil -} diff --git a/pkg/controllers/custompackage/controller.go b/pkg/controllers/custompackage/controller.go new file mode 100644 index 00000000..989e540c --- /dev/null +++ b/pkg/controllers/custompackage/controller.go @@ -0,0 +1,195 @@ +package custompackage + +import ( + "context" + "fmt" + argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" + "github.com/cnoe-io/idpbuilder/api/v1alpha1" + "github.com/cnoe-io/idpbuilder/pkg/k8s" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/record" + "os" + "path/filepath" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/log" + "strings" + "time" +) + +const ( + requeueTime = time.Second * 30 +) + +type Reconciler struct { + client.Client + Recorder record.EventRecorder + Scheme *runtime.Scheme +} + +func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + logger := log.FromContext(ctx) + + pkg := v1alpha1.CustomPackage{} + err := r.Get(ctx, req.NamespacedName, &pkg) + if err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + logger.Info("reconciling custom package", "name", req.Name, "namespace", req.Namespace) + result, err := r.reconcileCustomPackage(ctx, &pkg) + if err != nil { + r.Recorder.Event(&pkg, "Warning", "reconcile error", err.Error()) + } else { + r.Recorder.Event(&pkg, "Normal", "reconcile success", "Successfully reconciled") + } + + return result, err +} + +// create an in-cluster repository CR, update the application spec, then apply +func (r *Reconciler) reconcileCustomPackage(ctx context.Context, resource *v1alpha1.CustomPackage) (ctrl.Result, error) { + b, err := os.ReadFile(resource.Spec.ArgoCD.ApplicationFile) + if err != nil { + return ctrl.Result{}, fmt.Errorf("reading file %s: %w", resource.Spec.ArgoCD.ApplicationFile, err) + } + + objs, err := k8s.ConvertYamlToObjects(r.Scheme, b) + if err != nil { + return ctrl.Result{}, fmt.Errorf("converting yaml to object %w", err) + } + if len(objs) == 0 { + return ctrl.Result{}, fmt.Errorf("file contained 0 kubernetes objects %s", resource.Spec.ArgoCD.ApplicationFile) + } + + app, ok := objs[0].(*argov1alpha1.Application) + if !ok { + return ctrl.Result{}, fmt.Errorf("object is not an ArgoCD application %s", resource.Spec.ArgoCD.ApplicationFile) + } + + appName := app.GetName() + if app.Spec.HasMultipleSources() { + for j := range app.Spec.Sources { + s := app.Spec.Sources[j] + res, repo, sErr := r.reconcileArgocdSource(ctx, resource, appName, resource.Spec.ArgoCD.ApplicationFile, s.RepoURL) + if sErr != nil { + return res, sErr + } + if repo != nil { + s.RepoURL = repo.Status.InternalGitRepositoryUrl + } + } + } else { + s := app.Spec.Source + res, repo, sErr := r.reconcileArgocdSource(ctx, resource, appName, resource.Spec.ArgoCD.ApplicationFile, s.RepoURL) + if sErr != nil { + return res, sErr + } + if repo != nil { + s.RepoURL = repo.Status.InternalGitRepositoryUrl + } + } + + foundAppObj := argov1alpha1.Application{} + err = r.Client.Get(ctx, client.ObjectKeyFromObject(app), &foundAppObj) + if err != nil { + if errors.IsNotFound(err) { + err = r.Client.Create(ctx, app) + if err != nil { + return ctrl.Result{}, fmt.Errorf("creating %s app CR: %w", appName, err) + } + + return ctrl.Result{}, nil + } + return ctrl.Result{}, fmt.Errorf("getting argocd application object: %w", err) + } + + foundAppObj.Spec = app.Spec + foundAppObj.ObjectMeta.Annotations = app.Annotations + foundAppObj.ObjectMeta.Labels = app.Labels + err = r.Client.Update(ctx, &foundAppObj) + if err != nil { + return ctrl.Result{}, fmt.Errorf("updating argocd application object %s: %w", appName, err) + } + return ctrl.Result{RequeueAfter: requeueTime}, nil +} + +func (r *Reconciler) reconcileArgocdSource(ctx context.Context, resource *v1alpha1.CustomPackage, appName, pkgDir, repoURL string) (ctrl.Result, *v1alpha1.GitRepository, error) { + logger := log.FromContext(ctx) + + process, absPath, err := isCNOEDirectory(pkgDir, repoURL) + if err != nil { + logger.Error(err, "processing argocd app source", "dir", pkgDir, "repoURL", repoURL) + return ctrl.Result{RequeueAfter: time.Second * 60}, nil, nil + } + if !process { + return ctrl.Result{}, nil, nil + } + + repo, err := r.reconcileGitRepo(ctx, resource, repoName(appName, absPath), absPath) + if err != nil { + return ctrl.Result{}, nil, err + } + + return ctrl.Result{}, repo, nil +} + +func (r *Reconciler) reconcileGitRepo(ctx context.Context, resource *v1alpha1.CustomPackage, repoName, absPath string) (*v1alpha1.GitRepository, error) { + repo := &v1alpha1.GitRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: repoName, + Namespace: resource.Namespace, + }, + Spec: v1alpha1.GitRepositorySpec{ + Source: v1alpha1.GitRepositorySource{ + Type: "local", + Path: absPath, + }, + GitURL: resource.Spec.GitServerURL, + SecretRef: resource.Spec.GitServerAuthSecretRef, + }, + } + + _, err := controllerutil.CreateOrUpdate(ctx, r.Client, repo, func() error { + if err := controllerutil.SetControllerReference(resource, repo, r.Scheme); err != nil { + return err + } + return nil + }) + + return repo, err +} + +func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&v1alpha1.CustomPackage{}). + Complete(r) +} + +func isCNOEDirectory(fPath, repoURL string) (bool, string, error) { + if strings.HasPrefix(repoURL, "cnoe://") { + parentDir := filepath.Dir(fPath) + relativePath := strings.TrimPrefix(repoURL, "cnoe://") + absPath, err := filepath.Abs(filepath.Join(parentDir, relativePath)) + if err != nil { + return false, "", err + } + + f, err := os.Stat(absPath) + if err != nil { + return false, "", err + } + if !f.IsDir() { + return false, "", fmt.Errorf("path not a directory: %s", absPath) + } + return true, absPath, err + } + return false, "", nil +} + +func repoName(appName, dir string) string { + return fmt.Sprintf("%s-%s", appName, filepath.Base(dir)) +} diff --git a/pkg/controllers/gitrepository/controller.go b/pkg/controllers/gitrepository/controller.go index b2d5a751..7b3d30a8 100644 --- a/pkg/controllers/gitrepository/controller.go +++ b/pkg/controllers/gitrepository/controller.go @@ -141,6 +141,7 @@ func (r *RepositoryReconciler) reconcileGitRepo(ctx context.Context, repo *v1alp return ctrl.Result{Requeue: true, RequeueAfter: requeueTime}, fmt.Errorf("failed to create or update repo %w", err) } repo.Status.ExternalGitRepositoryUrl = giteaRepo.CloneURL + repo.Status.InternalGitRepositoryUrl = getRepositoryURL(repo.Namespace, repo.Name, repo.Spec.InternalGitURL) err = r.reconcileRepoContent(ctx, repo, giteaRepo) if err != nil { @@ -281,3 +282,7 @@ func writeRepoContents(repo *v1alpha1.GitRepository, dstPath string) error { } return nil } + +func getRepositoryURL(namespace, name, baseUrl string) string { + return fmt.Sprintf("%s/giteaAdmin/%s-%s.git", baseUrl, namespace, name) +} diff --git a/pkg/controllers/localbuild/controller.go b/pkg/controllers/localbuild/controller.go index 54075fa6..34ac7c0e 100644 --- a/pkg/controllers/localbuild/controller.go +++ b/pkg/controllers/localbuild/controller.go @@ -3,7 +3,7 @@ package localbuild import ( "context" "fmt" - "github.com/cnoe-io/idpbuilder/pkg/k8s" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "os" "path/filepath" "strings" @@ -19,6 +19,7 @@ import ( "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/scheme" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -302,9 +303,9 @@ func (r *LocalbuildReconciler) ReconcileArgoAppsWithGitea(ctx context.Context, r return result, fmt.Errorf("reconciling embedded apps %w", err) } } - if resource.Spec.PackageConfigs.CustomPackages != nil && len(resource.Spec.PackageConfigs.CustomPackages) > 0 { - for i := range resource.Spec.PackageConfigs.CustomPackages { - result, err := r.reconcileCustomPkg(ctx, resource, resource.Spec.PackageConfigs.CustomPackages[i]) + if resource.Spec.PackageConfigs.CustomPackageDirs != nil { + for i := range resource.Spec.PackageConfigs.CustomPackageDirs { + result, err := r.reconcileCustomPkg(ctx, resource, resource.Spec.PackageConfigs.CustomPackageDirs[i]) if err != nil { return result, err } @@ -345,7 +346,7 @@ func (r *LocalbuildReconciler) reconcileEmbeddedApp(ctx context.Context, appName if err != nil && errors.IsNotFound(err) { localbuild.SetApplicationSpec( app, - getRepositoryURL(repo.Namespace, repo.Name, resource.Status.Gitea.InternalURL), + repo.Status.InternalGitRepositoryUrl, ".", defaultArgoCDProjectName, appName, @@ -357,11 +358,24 @@ func (r *LocalbuildReconciler) reconcileEmbeddedApp(ctx context.Context, appName } } + localbuild.SetApplicationSpec( + app, + repo.Status.InternalGitRepositoryUrl, + ".", + defaultArgoCDProjectName, + appName, + nil, + ) + err = r.Client.Update(ctx, app) + if err != nil { + return ctrl.Result{}, fmt.Errorf("updating argoapp: %w", err) + } + return ctrl.Result{}, nil } func (r *LocalbuildReconciler) shouldShutDown(ctx context.Context, resource *v1alpha1.Localbuild) (bool, error) { - if len(resource.Spec.PackageConfigs.CustomPackages) > 0 { + if len(resource.Spec.PackageConfigs.CustomPackageDirs) > 0 { return false, nil } repos := &v1alpha1.GitRepositoryList{} @@ -378,18 +392,12 @@ func (r *LocalbuildReconciler) shouldShutDown(ctx context.Context, resource *v1a return true, nil } -func (r *LocalbuildReconciler) reconcileCustomPkg(ctx context.Context, resource *v1alpha1.Localbuild, pkg v1alpha1.CustomPackageSpec) (ctrl.Result, error) { +func (r *LocalbuildReconciler) reconcileCustomPkg(ctx context.Context, resource *v1alpha1.Localbuild, pkgDir string) (ctrl.Result, error) { logger := log.FromContext(ctx) - sc := runtime.NewScheme() - err := argov1alpha1.SchemeBuilder.AddToScheme(sc) + files, err := os.ReadDir(pkgDir) if err != nil { - return ctrl.Result{}, fmt.Errorf("adding argocd application scheme: %w", err) - } - - files, err := os.ReadDir(pkg.Directory) - if err != nil { - return ctrl.Result{}, fmt.Errorf("reading dir, %s: %w", pkg.Directory, err) + return ctrl.Result{}, fmt.Errorf("reading dir, %s: %w", pkgDir, err) } for i := range files { @@ -398,101 +406,58 @@ func (r *LocalbuildReconciler) reconcileCustomPkg(ctx context.Context, resource continue } - filePath := filepath.Join(pkg.Directory, file.Name()) + filePath := filepath.Join(pkgDir, file.Name()) b, fErr := os.ReadFile(filePath) if fErr != nil { logger.Error(fErr, "reading file", "file", filePath) continue } - objs, fErr := k8s.ConvertYamlToObjects(sc, b) + o := &unstructured.Unstructured{} + _, gvk, fErr := scheme.Codecs.UniversalDeserializer().Decode(b, nil, o) if fErr != nil { - //logger.Error(CErr, "converting yaml to object", "file", filePath) continue } - if len(objs) == 0 { - continue - } - - app, ok := objs[0].(*argov1alpha1.Application) - if !ok { - continue - } - - appName := app.GetName() - if appName == "" { - continue - } - - logger.Info("Ensuring custom ArgoCD Application", "name", appName) - if app.Spec.HasMultipleSources() { - for j := range app.Spec.Sources { - s := app.Spec.Sources[j] - res, repo, sErr := r.reconcileArgocdSource(ctx, resource, appName, pkg.Directory, s.RepoURL) - if sErr != nil { - return res, sErr - } - if repo != nil { - s.RepoURL = getRepositoryURL(repo.Namespace, repo.Name, resource.Status.Gitea.InternalURL) - } - } - } else { - s := app.Spec.Source - res, repo, sErr := r.reconcileArgocdSource(ctx, resource, appName, pkg.Directory, s.RepoURL) - if sErr != nil { - return res, sErr + if gvk.Kind == "Application" && gvk.Group == "argoproj.io" { + appName := o.GetName() + appNS := o.GetNamespace() + customPkg := &v1alpha1.CustomPackage{ + ObjectMeta: metav1.ObjectMeta{ + Name: getCustomPackageName(file.Name(), appName), + Namespace: globals.GetProjectNamespace(resource.Name), + }, + Spec: v1alpha1.CustomPackageSpec{ + Replicate: true, + GitServerURL: resource.Status.Gitea.ExternalURL, + GitServerAuthSecretRef: v1alpha1.SecretReference{ + Name: resource.Status.Gitea.AdminUserSecretName, + Namespace: resource.Status.Gitea.AdminUserSecretNamespace, + }, + ArgoCD: v1alpha1.ArgoCDPackageSpec{ + ApplicationFile: filePath, + Name: appName, + Namespace: appNS, + }, + }, + Status: v1alpha1.CustomPackageStatus{}, } - if repo != nil { - s.RepoURL = getRepositoryURL(repo.Namespace, repo.Name, resource.Status.Gitea.InternalURL) - } - } - foundAppObj := argov1alpha1.Application{} - err = r.Client.Get(ctx, client.ObjectKeyFromObject(app), &foundAppObj) - if err != nil { - if errors.IsNotFound(err) { - err = r.Client.Create(ctx, app) - if err != nil { - return ctrl.Result{}, fmt.Errorf("creating %s app CR: %w", appName, err) + _, fErr = controllerutil.CreateOrUpdate(ctx, r.Client, customPkg, func() error { + if err := controllerutil.SetControllerReference(resource, customPkg, r.Scheme); err != nil { + return err } - - return ctrl.Result{}, nil + return nil + }) + if fErr != nil { + logger.Error(fErr, "failed creating custom package object", "name", appName, "namespace", appNS) + continue } - return ctrl.Result{}, fmt.Errorf("getting argocd application object: %w", err) - } - - foundAppObj.Spec = app.Spec - foundAppObj.ObjectMeta.Annotations = app.Annotations - foundAppObj.ObjectMeta.Labels = app.Labels - err = r.Client.Update(ctx, &foundAppObj) - if err != nil { - return ctrl.Result{}, fmt.Errorf("updating argocd application object %s: %w", appName, err) } } return ctrl.Result{}, nil } -func (r *LocalbuildReconciler) reconcileArgocdSource(ctx context.Context, resource *v1alpha1.Localbuild, appName, pkgDir, repoURL string) (ctrl.Result, *v1alpha1.GitRepository, error) { - logger := log.FromContext(ctx) - - process, absPath, err := isCNOEDirectory(pkgDir, repoURL) - if err != nil { - logger.Error(err, "processing argocd app source", "dir", pkgDir, "repoURL", repoURL) - return ctrl.Result{RequeueAfter: time.Second * 60}, nil, nil - } - if !process { - return ctrl.Result{}, nil, nil - } - - repo, err := r.reconcileGitRepo(ctx, resource, "local", repoName(appName, absPath), "", absPath) - if err != nil { - return ctrl.Result{}, nil, err - } - - return ctrl.Result{}, repo, nil -} - func (r *LocalbuildReconciler) reconcileGitRepo(ctx context.Context, resource *v1alpha1.Localbuild, repoType, repoName, embeddedName, absPath string) (*v1alpha1.GitRepository, error) { repo := &v1alpha1.GitRepository{ ObjectMeta: metav1.ObjectMeta{ @@ -503,7 +468,8 @@ func (r *LocalbuildReconciler) reconcileGitRepo(ctx context.Context, resource *v Source: v1alpha1.GitRepositorySource{ Type: repoType, }, - GitURL: resource.Status.Gitea.ExternalURL, + GitURL: resource.Status.Gitea.ExternalURL, + InternalGitURL: resource.Status.Gitea.InternalURL, SecretRef: v1alpha1.SecretReference{ Name: resource.Status.Gitea.AdminUserSecretName, Namespace: resource.Status.Gitea.AdminUserSecretNamespace, @@ -527,6 +493,11 @@ func (r *LocalbuildReconciler) reconcileGitRepo(ctx context.Context, resource *v return repo, err } +func getCustomPackageName(fileName, appName string) string { + s := strings.Split(fileName, ".") + return fmt.Sprintf("%s-%s", strings.ToLower(s[0]), appName) +} + func GetEmbeddedRawInstallResources(name string) ([][]byte, error) { switch name { case "argocd": @@ -541,27 +512,3 @@ func GetEmbeddedRawInstallResources(name string) ([][]byte, error) { return nil, fmt.Errorf("unsupported embedded app name %s", name) } } - -func isCNOEDirectory(parentDir, path string) (bool, string, error) { - if strings.HasPrefix(path, "cnoe://") { - relativePath := strings.TrimPrefix(path, "cnoe://") - absPath, err := filepath.Abs(filepath.Join(parentDir, relativePath)) - if err != nil { - return false, "", err - } - - f, err := os.Stat(absPath) - if err != nil { - return false, "", err - } - if !f.IsDir() { - return false, "", fmt.Errorf("path not a directory: %s", absPath) - } - return true, absPath, err - } - return false, "", nil -} - -func repoName(appName, dir string) string { - return fmt.Sprintf("%s-%s", appName, filepath.Base(dir)) -} diff --git a/pkg/controllers/localbuild/controller_test.go b/pkg/controllers/localbuild/controller_test.go index dc61f16c..b2d87b3a 100644 --- a/pkg/controllers/localbuild/controller_test.go +++ b/pkg/controllers/localbuild/controller_test.go @@ -1,181 +1,182 @@ package localbuild -import ( - "context" - "fmt" - argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" - "github.com/cnoe-io/idpbuilder/api/v1alpha1" - "github.com/cnoe-io/idpbuilder/globals" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - k8sruntime "k8s.io/apimachinery/pkg/runtime" - "path/filepath" - "reflect" - "runtime" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/envtest" - "strings" - "testing" -) - -func TestReconcileCustomPkg(t *testing.T) { - s := k8sruntime.NewScheme() - sb := k8sruntime.NewSchemeBuilder( - v1.AddToScheme, - argov1alpha1.AddToScheme, - v1alpha1.AddToScheme, - ) - sb.AddToScheme(s) - testEnv := &envtest.Environment{ - CRDDirectoryPaths: []string{ - filepath.Join("..", "resources"), - "resources/argo/install.yaml", - }, - ErrorIfCRDPathMissing: true, - Scheme: s, - BinaryAssetsDirectory: filepath.Join("..", "..", "..", "bin", "k8s", - fmt.Sprintf("1.27.1-%s-%s", runtime.GOOS, runtime.GOARCH)), - } - - cfg, err := testEnv.Start() - if err != nil { - t.Fatalf("Starting testenv: %v", err) - } - defer testEnv.Stop() - - mgr, err := ctrl.NewManager(cfg, ctrl.Options{ - Scheme: s, - }) - if err != nil { - t.Fatalf("getting manager: %v", err) - } - - ctx, ctxCancel := context.WithCancel(context.Background()) - stoppedCh := make(chan error) - go func() { - err := mgr.Start(ctx) - stoppedCh <- err - }() - - defer func() { - ctxCancel() - err := <-stoppedCh - if err != nil { - t.Errorf("Starting controller manager: %v", err) - t.FailNow() - } - }() - - r := &LocalbuildReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - CancelFunc: nil, - shouldShutdown: false, - } - customPkgs := []v1alpha1.CustomPackageSpec{ - { - Directory: "test/resources/customPackages/testDir", - }, - { - Directory: "test/resources/customPackages/testDir2", - }, - } - - res := v1alpha1.Localbuild{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test", - UID: "uid", - }, - Spec: v1alpha1.LocalbuildSpec{ - PackageConfigs: v1alpha1.PackageConfigsSpec{ - GitConfig: v1alpha1.GitConfigSpec{}, - Argo: v1alpha1.ArgoPackageConfigSpec{}, - EmbeddedArgoApplications: v1alpha1.EmbeddedArgoApplicationsPackageConfigSpec{}, - CustomPackages: customPkgs, - }, - }, - Status: v1alpha1.LocalbuildStatus{ - Gitea: v1alpha1.GiteaStatus{ - Available: true, - ExternalURL: "https://cnoe.io", - InternalURL: "http://internal.cnoe.io", - AdminUserSecretName: "abc", - AdminUserSecretNamespace: "abc", - }, - }, - } - - for _, n := range []string{"argocd", globals.GetProjectNamespace(res.Name)} { - ns := v1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: n, - }, - } - err = mgr.GetClient().Create(context.Background(), &ns) - if err != nil { - t.Fatalf("creating test ns: %v", err) - } - } - - for i := range customPkgs { - _, err = r.reconcileCustomPkg(context.Background(), &res, customPkgs[i]) - if err != nil { - t.Fatalf("reconciling custom packages %v", err) - } - } - - // verify repo. - c := mgr.GetClient() - repo := v1alpha1.GitRepository{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-app-busybox", - Namespace: globals.GetProjectNamespace(res.Name), - }, - } - err = c.Get(context.Background(), client.ObjectKeyFromObject(&repo), &repo) - if err != nil { - t.Fatalf("getting my-app-busybox git repo %v", err) - } - - p, _ := filepath.Abs("test/resources/customPackages/testDir/busybox") - expectedRepo := v1alpha1.GitRepository{ - Spec: v1alpha1.GitRepositorySpec{ - Source: v1alpha1.GitRepositorySource{ - Type: "local", - Path: p, - }, - GitURL: "https://cnoe.io", - SecretRef: v1alpha1.SecretReference{ - Name: "abc", - Namespace: "abc", - }, - }, - } - ok := reflect.DeepEqual(repo.Spec, expectedRepo.Spec) - if !ok { - t.Fatalf("expected spec does not match") - } - - // verify argocd apps - localApp := argov1alpha1.Application{ - ObjectMeta: metav1.ObjectMeta{ - Name: "my-app", - Namespace: "argocd", - }, - } - err = c.Get(context.Background(), client.ObjectKeyFromObject(&localApp), &localApp) - if err != nil { - t.Fatalf("failed getting my-app %v", err) - } - if strings.HasPrefix(localApp.Spec.Source.RepoURL, "cnoe://") { - t.Fatalf("cnoe:// prefix should be removed") - } - - for _, n := range []string{"guestbook", "guestbook2"} { - err = c.Get(context.Background(), client.ObjectKeyFromObject(&localApp), &localApp) - if err != nil { - t.Fatalf("expected %s arogapp : %v", n, err) - } - } -} +// +//import ( +// "context" +// "fmt" +// argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" +// "github.com/cnoe-io/idpbuilder/api/v1alpha1" +// "github.com/cnoe-io/idpbuilder/globals" +// v1 "k8s.io/api/core/v1" +// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +// k8sruntime "k8s.io/apimachinery/pkg/runtime" +// "path/filepath" +// "reflect" +// "runtime" +// ctrl "sigs.k8s.io/controller-runtime" +// "sigs.k8s.io/controller-runtime/pkg/client" +// "sigs.k8s.io/controller-runtime/pkg/envtest" +// "strings" +// "testing" +//) +// +//func TestReconcileCustomPkg(t *testing.T) { +// s := k8sruntime.NewScheme() +// sb := k8sruntime.NewSchemeBuilder( +// v1.AddToScheme, +// argov1alpha1.AddToScheme, +// v1alpha1.AddToScheme, +// ) +// sb.AddToScheme(s) +// testEnv := &envtest.Environment{ +// CRDDirectoryPaths: []string{ +// filepath.Join("..", "resources"), +// "resources/argo/install.yaml", +// }, +// ErrorIfCRDPathMissing: true, +// Scheme: s, +// BinaryAssetsDirectory: filepath.Join("..", "..", "..", "bin", "k8s", +// fmt.Sprintf("1.27.1-%s-%s", runtime.GOOS, runtime.GOARCH)), +// } +// +// cfg, err := testEnv.Start() +// if err != nil { +// t.Fatalf("Starting testenv: %v", err) +// } +// defer testEnv.Stop() +// +// mgr, err := ctrl.NewManager(cfg, ctrl.Options{ +// Scheme: s, +// }) +// if err != nil { +// t.Fatalf("getting manager: %v", err) +// } +// +// ctx, ctxCancel := context.WithCancel(context.Background()) +// stoppedCh := make(chan error) +// go func() { +// err := mgr.Start(ctx) +// stoppedCh <- err +// }() +// +// defer func() { +// ctxCancel() +// err := <-stoppedCh +// if err != nil { +// t.Errorf("Starting controller manager: %v", err) +// t.FailNow() +// } +// }() +// +// r := &LocalbuildReconciler{ +// Client: mgr.GetClient(), +// Scheme: mgr.GetScheme(), +// CancelFunc: nil, +// shouldShutdown: false, +// } +// customPkgs := []v1alpha1.CustomPackageSpec{ +// { +// Directory: "test/resources/customPackages/testDir", +// }, +// { +// Directory: "test/resources/customPackages/testDir2", +// }, +// } +// +// res := v1alpha1.Localbuild{ +// ObjectMeta: metav1.ObjectMeta{ +// Name: "test", +// UID: "uid", +// }, +// Spec: v1alpha1.LocalbuildSpec{ +// PackageConfigs: v1alpha1.PackageConfigsSpec{ +// GitConfig: v1alpha1.GitConfigSpec{}, +// Argo: v1alpha1.ArgoPackageConfigSpec{}, +// EmbeddedArgoApplications: v1alpha1.EmbeddedArgoApplicationsPackageConfigSpec{}, +// CustomPackages: customPkgs, +// }, +// }, +// Status: v1alpha1.LocalbuildStatus{ +// Gitea: v1alpha1.GiteaStatus{ +// Available: true, +// ExternalURL: "https://cnoe.io", +// InternalURL: "http://internal.cnoe.io", +// AdminUserSecretName: "abc", +// AdminUserSecretNamespace: "abc", +// }, +// }, +// } +// +// for _, n := range []string{"argocd", globals.GetProjectNamespace(res.Name)} { +// ns := v1.Namespace{ +// ObjectMeta: metav1.ObjectMeta{ +// Name: n, +// }, +// } +// err = mgr.GetClient().Create(context.Background(), &ns) +// if err != nil { +// t.Fatalf("creating test ns: %v", err) +// } +// } +// +// for i := range customPkgs { +// _, err = r.reconcileCustomPkg(context.Background(), &res, customPkgs[i]) +// if err != nil { +// t.Fatalf("reconciling custom packages %v", err) +// } +// } +// +// // verify repo. +// c := mgr.GetClient() +// repo := v1alpha1.GitRepository{ +// ObjectMeta: metav1.ObjectMeta{ +// Name: "my-app-busybox", +// Namespace: globals.GetProjectNamespace(res.Name), +// }, +// } +// err = c.Get(context.Background(), client.ObjectKeyFromObject(&repo), &repo) +// if err != nil { +// t.Fatalf("getting my-app-busybox git repo %v", err) +// } +// +// p, _ := filepath.Abs("test/resources/customPackages/testDir/busybox") +// expectedRepo := v1alpha1.GitRepository{ +// Spec: v1alpha1.GitRepositorySpec{ +// Source: v1alpha1.GitRepositorySource{ +// Type: "local", +// Path: p, +// }, +// GitURL: "https://cnoe.io", +// SecretRef: v1alpha1.SecretReference{ +// Name: "abc", +// Namespace: "abc", +// }, +// }, +// } +// ok := reflect.DeepEqual(repo.Spec, expectedRepo.Spec) +// if !ok { +// t.Fatalf("expected spec does not match") +// } +// +// // verify argocd apps +// localApp := argov1alpha1.Application{ +// ObjectMeta: metav1.ObjectMeta{ +// Name: "my-app", +// Namespace: "argocd", +// }, +// } +// err = c.Get(context.Background(), client.ObjectKeyFromObject(&localApp), &localApp) +// if err != nil { +// t.Fatalf("failed getting my-app %v", err) +// } +// if strings.HasPrefix(localApp.Spec.Source.RepoURL, "cnoe://") { +// t.Fatalf("cnoe:// prefix should be removed") +// } +// +// for _, n := range []string{"guestbook", "guestbook2"} { +// err = c.Get(context.Background(), client.ObjectKeyFromObject(&localApp), &localApp) +// if err != nil { +// t.Fatalf("expected %s arogapp : %v", n, err) +// } +// } +//} diff --git a/pkg/controllers/localbuild/gitea.go b/pkg/controllers/localbuild/gitea.go index 300f1f04..a243a29a 100644 --- a/pkg/controllers/localbuild/gitea.go +++ b/pkg/controllers/localbuild/gitea.go @@ -3,8 +3,6 @@ package localbuild import ( "context" "embed" - "fmt" - "github.com/cnoe-io/idpbuilder/api/v1alpha1" "github.com/cnoe-io/idpbuilder/pkg/util" "k8s.io/apimachinery/pkg/runtime/schema" @@ -54,7 +52,3 @@ func (r *LocalbuildReconciler) ReconcileGitea(ctx context.Context, req ctrl.Requ resource.Status.Gitea.Available = true return ctrl.Result{}, nil } - -func getRepositoryURL(namespace, name, baseUrl string) string { - return fmt.Sprintf("%s/giteaAdmin/%s-%s.git", baseUrl, namespace, name) -} diff --git a/pkg/controllers/resources/idpbuilder.cnoe.io_custompackages.yaml b/pkg/controllers/resources/idpbuilder.cnoe.io_custompackages.yaml new file mode 100644 index 00000000..35e8699f --- /dev/null +++ b/pkg/controllers/resources/idpbuilder.cnoe.io_custompackages.yaml @@ -0,0 +1,87 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.12.0 + name: custompackages.idpbuilder.cnoe.io +spec: + group: idpbuilder.cnoe.io + names: + kind: CustomPackage + listKind: CustomPackageList + plural: custompackages + singular: custompackage + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: CustomPackageSpec controls the installation of the custom + applications. + properties: + argoCD: + properties: + applicationFile: + description: ApplicationFile specifies the absolute path to the + ArgoCD application file + type: string + name: + type: string + namespace: + type: string + required: + - applicationFile + - name + - namespace + type: object + gitServerAuthSecretRef: + properties: + name: + type: string + namespace: + type: string + required: + - name + - namespace + type: object + gitServerURL: + description: GitServerURL specifies the base URL for the git server + for API calls. for example, http://gitea.cnoe.localtest.me:8880 + type: string + replicate: + description: // Type specifies what kind of package this is. local + means local files specified in the Directory field must be synced + to a Git Repository Type string `json:"type"` Replicate specifies + whether to replicate remote or local contents to the local gitea + server. + type: boolean + required: + - gitServerAuthSecretRef + - gitServerURL + - replicate + type: object + status: + properties: + synced: + type: boolean + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/pkg/controllers/resources/idpbuilder.cnoe.io_gitrepositories.yaml b/pkg/controllers/resources/idpbuilder.cnoe.io_gitrepositories.yaml index 22b3ec88..c03b6782 100644 --- a/pkg/controllers/resources/idpbuilder.cnoe.io_gitrepositories.yaml +++ b/pkg/controllers/resources/idpbuilder.cnoe.io_gitrepositories.yaml @@ -36,6 +36,10 @@ spec: description: GitURL is the base URL of Git server used for API calls. pattern: ^https?:\/\/.+$ type: string + internalGitURL: + description: InternalGitURL is the base URL of Git server accessible + within the cluster only. + type: string secretRef: description: SecretRef is the reference to secret that contain Git server credentials @@ -75,6 +79,7 @@ spec: type: object required: - gitURL + - internalGitURL type: object status: properties: diff --git a/pkg/controllers/resources/idpbuilder.cnoe.io_localbuilds.yaml b/pkg/controllers/resources/idpbuilder.cnoe.io_localbuilds.yaml index 559b217a..aa518787 100644 --- a/pkg/controllers/resources/idpbuilder.cnoe.io_localbuilds.yaml +++ b/pkg/controllers/resources/idpbuilder.cnoe.io_localbuilds.yaml @@ -43,16 +43,9 @@ spec: description: Enabled controls whether to install ArgoCD. type: boolean type: object - customPackages: + customPackageDirs: items: - description: CustomPackageSpec controls the installation of - the custom argocd applications. - properties: - directory: - description: Directory specifies the absolute path to the - directory which contains raw manifests to be installed - type: string - type: object + type: string type: array embeddedArgoApplicationsPackageConfigs: description: EmbeddedArgoApplicationsPackageConfigSpec Controls @@ -71,6 +64,8 @@ spec: type: type: string type: object + required: + - customPackageDirs type: object type: object status: diff --git a/pkg/controllers/run.go b/pkg/controllers/run.go index 08181392..64cc797f 100644 --- a/pkg/controllers/run.go +++ b/pkg/controllers/run.go @@ -2,6 +2,7 @@ package controllers import ( "context" + "github.com/cnoe-io/idpbuilder/pkg/controllers/custompackage" "github.com/cnoe-io/idpbuilder/pkg/controllers/gitrepository" "github.com/cnoe-io/idpbuilder/pkg/controllers/localbuild" @@ -32,6 +33,15 @@ func RunControllers(ctx context.Context, mgr manager.Manager, exitCh chan error, log.Error(err, "unable to create repo controller") } + err = (&custompackage.Reconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor("custompackage-controller"), + }).SetupWithManager(mgr) + if err != nil { + log.Error(err, "unable to create custom package controller") + } + // Start our manager in another goroutine log.Info("starting manager") go func() { From 9054d09b0a748b98104ed869ee3136447558f1e1 Mon Sep 17 00:00:00 2001 From: Manabu Mccloskey Date: Fri, 8 Dec 2023 00:52:00 +0000 Subject: [PATCH 3/7] fix tests, ensure internal repo urls are passed Signed-off-by: Manabu Mccloskey --- api/v1alpha1/custom_package_types.go | 4 +- pkg/controllers/custompackage/controller.go | 33 ++-- .../custompackage/controller_test.go | 185 ++++++++++++++++++ .../resources/customPackages/testDir/app.yaml | 0 .../testDir/busybox/busybox.yaml | 0 .../customPackages/testDir2/exampleApp.yaml | 0 .../customPackages/testDir2/exampleApp2.yaml | 0 .../gitrepository/controller_test.go | 5 + pkg/controllers/localbuild/controller.go | 5 +- pkg/controllers/localbuild/controller_test.go | 182 ----------------- .../idpbuilder.cnoe.io_custompackages.yaml | 5 + 11 files changed, 219 insertions(+), 200 deletions(-) create mode 100644 pkg/controllers/custompackage/controller_test.go rename pkg/controllers/{localbuild => custompackage}/test/resources/customPackages/testDir/app.yaml (100%) rename pkg/controllers/{localbuild => custompackage}/test/resources/customPackages/testDir/busybox/busybox.yaml (100%) rename pkg/controllers/{localbuild => custompackage}/test/resources/customPackages/testDir2/exampleApp.yaml (100%) rename pkg/controllers/{localbuild => custompackage}/test/resources/customPackages/testDir2/exampleApp2.yaml (100%) delete mode 100644 pkg/controllers/localbuild/controller_test.go diff --git a/api/v1alpha1/custom_package_types.go b/api/v1alpha1/custom_package_types.go index 8d0bd33f..b3ef4b73 100644 --- a/api/v1alpha1/custom_package_types.go +++ b/api/v1alpha1/custom_package_types.go @@ -26,7 +26,9 @@ type CustomPackageSpec struct { // Replicate specifies whether to replicate remote or local contents to the local gitea server. Replicate bool `json:"replicate"` // GitServerURL specifies the base URL for the git server for API calls. for example, http://gitea.cnoe.localtest.me:8880 - GitServerURL string `json:"gitServerURL"` + GitServerURL string `json:"gitServerURL"` + // InternalGitServeURL specifies the base URL for the git server accessible within the cluster. for example, http://my-gitea-http.gitea.svc.cluster.local:3000 + InternalGitServeURL string `json:"internalGitServeURL"` GitServerAuthSecretRef SecretReference `json:"gitServerAuthSecretRef"` ArgoCD ArgoCDPackageSpec `json:"argoCD,omitempty"` diff --git a/pkg/controllers/custompackage/controller.go b/pkg/controllers/custompackage/controller.go index 989e540c..4cb4d60f 100644 --- a/pkg/controllers/custompackage/controller.go +++ b/pkg/controllers/custompackage/controller.go @@ -71,9 +71,20 @@ func (r *Reconciler) reconcileCustomPackage(ctx context.Context, resource *v1alp } appName := app.GetName() - if app.Spec.HasMultipleSources() { - for j := range app.Spec.Sources { - s := app.Spec.Sources[j] + if resource.Spec.Replicate { + if app.Spec.HasMultipleSources() { + for j := range app.Spec.Sources { + s := app.Spec.Sources[j] + res, repo, sErr := r.reconcileArgocdSource(ctx, resource, appName, resource.Spec.ArgoCD.ApplicationFile, s.RepoURL) + if sErr != nil { + return res, sErr + } + if repo != nil { + s.RepoURL = repo.Status.InternalGitRepositoryUrl + } + } + } else { + s := app.Spec.Source res, repo, sErr := r.reconcileArgocdSource(ctx, resource, appName, resource.Spec.ArgoCD.ApplicationFile, s.RepoURL) if sErr != nil { return res, sErr @@ -82,15 +93,6 @@ func (r *Reconciler) reconcileCustomPackage(ctx context.Context, resource *v1alp s.RepoURL = repo.Status.InternalGitRepositoryUrl } } - } else { - s := app.Spec.Source - res, repo, sErr := r.reconcileArgocdSource(ctx, resource, appName, resource.Spec.ArgoCD.ApplicationFile, s.RepoURL) - if sErr != nil { - return res, sErr - } - if repo != nil { - s.RepoURL = repo.Status.InternalGitRepositoryUrl - } } foundAppObj := argov1alpha1.Application{} @@ -102,7 +104,7 @@ func (r *Reconciler) reconcileCustomPackage(ctx context.Context, resource *v1alp return ctrl.Result{}, fmt.Errorf("creating %s app CR: %w", appName, err) } - return ctrl.Result{}, nil + return ctrl.Result{RequeueAfter: requeueTime}, nil } return ctrl.Result{}, fmt.Errorf("getting argocd application object: %w", err) } @@ -148,8 +150,9 @@ func (r *Reconciler) reconcileGitRepo(ctx context.Context, resource *v1alpha1.Cu Type: "local", Path: absPath, }, - GitURL: resource.Spec.GitServerURL, - SecretRef: resource.Spec.GitServerAuthSecretRef, + GitURL: resource.Spec.GitServerURL, + InternalGitURL: resource.Spec.InternalGitServeURL, + SecretRef: resource.Spec.GitServerAuthSecretRef, }, } diff --git a/pkg/controllers/custompackage/controller_test.go b/pkg/controllers/custompackage/controller_test.go new file mode 100644 index 00000000..58158201 --- /dev/null +++ b/pkg/controllers/custompackage/controller_test.go @@ -0,0 +1,185 @@ +package custompackage + +import ( + "context" + "fmt" + argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" + "github.com/cnoe-io/idpbuilder/api/v1alpha1" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + k8sruntime "k8s.io/apimachinery/pkg/runtime" + "os" + "path/filepath" + "reflect" + "runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + "strings" + "testing" +) + +func TestReconcileCustomPkg(t *testing.T) { + s := k8sruntime.NewScheme() + sb := k8sruntime.NewSchemeBuilder( + v1.AddToScheme, + argov1alpha1.AddToScheme, + v1alpha1.AddToScheme, + ) + sb.AddToScheme(s) + testEnv := &envtest.Environment{ + CRDDirectoryPaths: []string{ + filepath.Join("..", "resources"), + "../localbuild/resources/argo/install.yaml", + }, + ErrorIfCRDPathMissing: true, + Scheme: s, + BinaryAssetsDirectory: filepath.Join("..", "..", "..", "bin", "k8s", + fmt.Sprintf("1.27.1-%s-%s", runtime.GOOS, runtime.GOARCH)), + } + + cfg, err := testEnv.Start() + if err != nil { + t.Fatalf("Starting testenv: %v", err) + } + defer testEnv.Stop() + + mgr, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: s, + }) + if err != nil { + t.Fatalf("getting manager: %v", err) + } + + ctx, ctxCancel := context.WithCancel(context.Background()) + stoppedCh := make(chan error) + go func() { + err := mgr.Start(ctx) + stoppedCh <- err + }() + + defer func() { + ctxCancel() + err := <-stoppedCh + if err != nil { + t.Errorf("Starting controller manager: %v", err) + t.FailNow() + } + }() + + r := &Reconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor("test-custompkg-controller"), + } + cwd, err := os.Getwd() + if err != nil { + t.Fatalf("getting cwd %v", err) + } + customPkgs := []v1alpha1.CustomPackage{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test1", + Namespace: "test", + UID: "abc", + }, + Spec: v1alpha1.CustomPackageSpec{ + Replicate: true, + GitServerURL: "https://cnoe.io", + InternalGitServeURL: "http://internal.cnoe.io", + ArgoCD: v1alpha1.ArgoCDPackageSpec{ + ApplicationFile: filepath.Join(cwd, "test/resources/customPackages/testDir/app.yaml"), + Name: "my-app", + Namespace: "argocd", + }, + }, + }, + { + ObjectMeta: metav1.ObjectMeta{ + Name: "test2", + Namespace: "test", + UID: "abc", + }, + Spec: v1alpha1.CustomPackageSpec{ + Replicate: false, + GitServerURL: "https://cnoe.io", + InternalGitServeURL: "http://cnoe.io/internal", + ArgoCD: v1alpha1.ArgoCDPackageSpec{ + ApplicationFile: filepath.Join(cwd, "test/resources/customPackages/testDir2/exampleApp.yaml"), + Name: "guestbook", + Namespace: "argocd", + }, + }, + }, + } + + for _, n := range []string{"argocd", "test"} { + ns := v1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: n, + }, + } + err = mgr.GetClient().Create(context.Background(), &ns) + if err != nil { + t.Fatalf("creating test ns: %v", err) + } + } + + for i := range customPkgs { + _, err = r.reconcileCustomPackage(context.Background(), &customPkgs[i]) + if err != nil { + t.Fatalf("reconciling custom packages %v", err) + } + } + + // verify repo. + c := mgr.GetClient() + repo := v1alpha1.GitRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: repoName("my-app", "test/resources/customPackages/testDir/busybox"), + Namespace: "test", + }, + } + err = c.Get(context.Background(), client.ObjectKeyFromObject(&repo), &repo) + if err != nil { + t.Fatalf("getting my-app-busybox git repo %v", err) + } + + p, _ := filepath.Abs("test/resources/customPackages/testDir/busybox") + expectedRepo := v1alpha1.GitRepository{ + Spec: v1alpha1.GitRepositorySpec{ + Source: v1alpha1.GitRepositorySource{ + Type: "local", + Path: p, + }, + GitURL: "https://cnoe.io", + InternalGitURL: "http://internal.cnoe.io", + }, + } + ok := reflect.DeepEqual(repo.Spec, expectedRepo.Spec) + if !ok { + t.Fatalf("expected spec does not match") + } + + // verify argocd apps + localApp := argov1alpha1.Application{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-app", + Namespace: "argocd", + }, + } + err = c.Get(context.Background(), client.ObjectKeyFromObject(&localApp), &localApp) + if err != nil { + t.Fatalf("failed getting my-app %v", err) + } + if strings.HasPrefix(localApp.Spec.Source.RepoURL, "cnoe://") { + t.Fatalf("cnoe:// prefix should be removed") + } + + for _, n := range []string{"guestbook", "guestbook2"} { + err = c.Get(context.Background(), client.ObjectKeyFromObject(&localApp), &localApp) + if err != nil { + t.Fatalf("expected %s arogapp : %v", n, err) + } + } +} diff --git a/pkg/controllers/localbuild/test/resources/customPackages/testDir/app.yaml b/pkg/controllers/custompackage/test/resources/customPackages/testDir/app.yaml similarity index 100% rename from pkg/controllers/localbuild/test/resources/customPackages/testDir/app.yaml rename to pkg/controllers/custompackage/test/resources/customPackages/testDir/app.yaml diff --git a/pkg/controllers/localbuild/test/resources/customPackages/testDir/busybox/busybox.yaml b/pkg/controllers/custompackage/test/resources/customPackages/testDir/busybox/busybox.yaml similarity index 100% rename from pkg/controllers/localbuild/test/resources/customPackages/testDir/busybox/busybox.yaml rename to pkg/controllers/custompackage/test/resources/customPackages/testDir/busybox/busybox.yaml diff --git a/pkg/controllers/localbuild/test/resources/customPackages/testDir2/exampleApp.yaml b/pkg/controllers/custompackage/test/resources/customPackages/testDir2/exampleApp.yaml similarity index 100% rename from pkg/controllers/localbuild/test/resources/customPackages/testDir2/exampleApp.yaml rename to pkg/controllers/custompackage/test/resources/customPackages/testDir2/exampleApp.yaml diff --git a/pkg/controllers/localbuild/test/resources/customPackages/testDir2/exampleApp2.yaml b/pkg/controllers/custompackage/test/resources/customPackages/testDir2/exampleApp2.yaml similarity index 100% rename from pkg/controllers/localbuild/test/resources/customPackages/testDir2/exampleApp2.yaml rename to pkg/controllers/custompackage/test/resources/customPackages/testDir2/exampleApp2.yaml diff --git a/pkg/controllers/gitrepository/controller_test.go b/pkg/controllers/gitrepository/controller_test.go index 78c73e7c..5799e582 100644 --- a/pkg/controllers/gitrepository/controller_test.go +++ b/pkg/controllers/gitrepository/controller_test.go @@ -259,6 +259,7 @@ func TestGitRepositoryContentReconcileEmbedded(t *testing.T) { EmbeddedAppName: "nginx", Type: "embedded", }, + InternalGitURL: "http://cnoe.io", }, } @@ -315,6 +316,7 @@ func TestGitRepositoryReconcile(t *testing.T) { Path: resourcePath, Type: "local", }, + InternalGitURL: "http://cnoe.io", }, }, expect: expect{ @@ -322,6 +324,7 @@ func TestGitRepositoryReconcile(t *testing.T) { ExternalGitRepositoryUrl: dir, LatestCommit: v1alpha1.Commit{Hash: hash}, Synced: true, + InternalGitRepositoryUrl: "http://cnoe.io/giteaAdmin/test-test.git", }, }, }, @@ -340,12 +343,14 @@ func TestGitRepositoryReconcile(t *testing.T) { Path: addDir, Type: "local", }, + InternalGitURL: "http://cnoe.io", }, }, expect: expect{ resource: v1alpha1.GitRepositoryStatus{ ExternalGitRepositoryUrl: dir, Synced: true, + InternalGitRepositoryUrl: "http://cnoe.io/giteaAdmin/test-test.git", }, }, }, diff --git a/pkg/controllers/localbuild/controller.go b/pkg/controllers/localbuild/controller.go index 34ac7c0e..b8eec71a 100644 --- a/pkg/controllers/localbuild/controller.go +++ b/pkg/controllers/localbuild/controller.go @@ -427,8 +427,9 @@ func (r *LocalbuildReconciler) reconcileCustomPkg(ctx context.Context, resource Namespace: globals.GetProjectNamespace(resource.Name), }, Spec: v1alpha1.CustomPackageSpec{ - Replicate: true, - GitServerURL: resource.Status.Gitea.ExternalURL, + Replicate: true, + GitServerURL: resource.Status.Gitea.ExternalURL, + InternalGitServeURL: resource.Status.Gitea.InternalURL, GitServerAuthSecretRef: v1alpha1.SecretReference{ Name: resource.Status.Gitea.AdminUserSecretName, Namespace: resource.Status.Gitea.AdminUserSecretNamespace, diff --git a/pkg/controllers/localbuild/controller_test.go b/pkg/controllers/localbuild/controller_test.go deleted file mode 100644 index b2d87b3a..00000000 --- a/pkg/controllers/localbuild/controller_test.go +++ /dev/null @@ -1,182 +0,0 @@ -package localbuild - -// -//import ( -// "context" -// "fmt" -// argov1alpha1 "github.com/argoproj/argo-cd/v2/pkg/apis/application/v1alpha1" -// "github.com/cnoe-io/idpbuilder/api/v1alpha1" -// "github.com/cnoe-io/idpbuilder/globals" -// v1 "k8s.io/api/core/v1" -// metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -// k8sruntime "k8s.io/apimachinery/pkg/runtime" -// "path/filepath" -// "reflect" -// "runtime" -// ctrl "sigs.k8s.io/controller-runtime" -// "sigs.k8s.io/controller-runtime/pkg/client" -// "sigs.k8s.io/controller-runtime/pkg/envtest" -// "strings" -// "testing" -//) -// -//func TestReconcileCustomPkg(t *testing.T) { -// s := k8sruntime.NewScheme() -// sb := k8sruntime.NewSchemeBuilder( -// v1.AddToScheme, -// argov1alpha1.AddToScheme, -// v1alpha1.AddToScheme, -// ) -// sb.AddToScheme(s) -// testEnv := &envtest.Environment{ -// CRDDirectoryPaths: []string{ -// filepath.Join("..", "resources"), -// "resources/argo/install.yaml", -// }, -// ErrorIfCRDPathMissing: true, -// Scheme: s, -// BinaryAssetsDirectory: filepath.Join("..", "..", "..", "bin", "k8s", -// fmt.Sprintf("1.27.1-%s-%s", runtime.GOOS, runtime.GOARCH)), -// } -// -// cfg, err := testEnv.Start() -// if err != nil { -// t.Fatalf("Starting testenv: %v", err) -// } -// defer testEnv.Stop() -// -// mgr, err := ctrl.NewManager(cfg, ctrl.Options{ -// Scheme: s, -// }) -// if err != nil { -// t.Fatalf("getting manager: %v", err) -// } -// -// ctx, ctxCancel := context.WithCancel(context.Background()) -// stoppedCh := make(chan error) -// go func() { -// err := mgr.Start(ctx) -// stoppedCh <- err -// }() -// -// defer func() { -// ctxCancel() -// err := <-stoppedCh -// if err != nil { -// t.Errorf("Starting controller manager: %v", err) -// t.FailNow() -// } -// }() -// -// r := &LocalbuildReconciler{ -// Client: mgr.GetClient(), -// Scheme: mgr.GetScheme(), -// CancelFunc: nil, -// shouldShutdown: false, -// } -// customPkgs := []v1alpha1.CustomPackageSpec{ -// { -// Directory: "test/resources/customPackages/testDir", -// }, -// { -// Directory: "test/resources/customPackages/testDir2", -// }, -// } -// -// res := v1alpha1.Localbuild{ -// ObjectMeta: metav1.ObjectMeta{ -// Name: "test", -// UID: "uid", -// }, -// Spec: v1alpha1.LocalbuildSpec{ -// PackageConfigs: v1alpha1.PackageConfigsSpec{ -// GitConfig: v1alpha1.GitConfigSpec{}, -// Argo: v1alpha1.ArgoPackageConfigSpec{}, -// EmbeddedArgoApplications: v1alpha1.EmbeddedArgoApplicationsPackageConfigSpec{}, -// CustomPackages: customPkgs, -// }, -// }, -// Status: v1alpha1.LocalbuildStatus{ -// Gitea: v1alpha1.GiteaStatus{ -// Available: true, -// ExternalURL: "https://cnoe.io", -// InternalURL: "http://internal.cnoe.io", -// AdminUserSecretName: "abc", -// AdminUserSecretNamespace: "abc", -// }, -// }, -// } -// -// for _, n := range []string{"argocd", globals.GetProjectNamespace(res.Name)} { -// ns := v1.Namespace{ -// ObjectMeta: metav1.ObjectMeta{ -// Name: n, -// }, -// } -// err = mgr.GetClient().Create(context.Background(), &ns) -// if err != nil { -// t.Fatalf("creating test ns: %v", err) -// } -// } -// -// for i := range customPkgs { -// _, err = r.reconcileCustomPkg(context.Background(), &res, customPkgs[i]) -// if err != nil { -// t.Fatalf("reconciling custom packages %v", err) -// } -// } -// -// // verify repo. -// c := mgr.GetClient() -// repo := v1alpha1.GitRepository{ -// ObjectMeta: metav1.ObjectMeta{ -// Name: "my-app-busybox", -// Namespace: globals.GetProjectNamespace(res.Name), -// }, -// } -// err = c.Get(context.Background(), client.ObjectKeyFromObject(&repo), &repo) -// if err != nil { -// t.Fatalf("getting my-app-busybox git repo %v", err) -// } -// -// p, _ := filepath.Abs("test/resources/customPackages/testDir/busybox") -// expectedRepo := v1alpha1.GitRepository{ -// Spec: v1alpha1.GitRepositorySpec{ -// Source: v1alpha1.GitRepositorySource{ -// Type: "local", -// Path: p, -// }, -// GitURL: "https://cnoe.io", -// SecretRef: v1alpha1.SecretReference{ -// Name: "abc", -// Namespace: "abc", -// }, -// }, -// } -// ok := reflect.DeepEqual(repo.Spec, expectedRepo.Spec) -// if !ok { -// t.Fatalf("expected spec does not match") -// } -// -// // verify argocd apps -// localApp := argov1alpha1.Application{ -// ObjectMeta: metav1.ObjectMeta{ -// Name: "my-app", -// Namespace: "argocd", -// }, -// } -// err = c.Get(context.Background(), client.ObjectKeyFromObject(&localApp), &localApp) -// if err != nil { -// t.Fatalf("failed getting my-app %v", err) -// } -// if strings.HasPrefix(localApp.Spec.Source.RepoURL, "cnoe://") { -// t.Fatalf("cnoe:// prefix should be removed") -// } -// -// for _, n := range []string{"guestbook", "guestbook2"} { -// err = c.Get(context.Background(), client.ObjectKeyFromObject(&localApp), &localApp) -// if err != nil { -// t.Fatalf("expected %s arogapp : %v", n, err) -// } -// } -//} diff --git a/pkg/controllers/resources/idpbuilder.cnoe.io_custompackages.yaml b/pkg/controllers/resources/idpbuilder.cnoe.io_custompackages.yaml index 35e8699f..12f4c596 100644 --- a/pkg/controllers/resources/idpbuilder.cnoe.io_custompackages.yaml +++ b/pkg/controllers/resources/idpbuilder.cnoe.io_custompackages.yaml @@ -63,6 +63,10 @@ spec: description: GitServerURL specifies the base URL for the git server for API calls. for example, http://gitea.cnoe.localtest.me:8880 type: string + internalGitServeURL: + description: InternalGitServeURL specifies the base URL for the git + server accessible within the cluster. for example, http://my-gitea-http.gitea.svc.cluster.local:3000 + type: string replicate: description: // Type specifies what kind of package this is. local means local files specified in the Directory field must be synced @@ -73,6 +77,7 @@ spec: required: - gitServerAuthSecretRef - gitServerURL + - internalGitServeURL - replicate type: object status: From 23dd3522e933202e00a01bab1588d34bdd3f8db0 Mon Sep 17 00:00:00 2001 From: Manabu Mccloskey Date: Fri, 8 Dec 2023 01:07:57 +0000 Subject: [PATCH 4/7] clean up comments Signed-off-by: Manabu Mccloskey --- api/v1alpha1/custom_package_types.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/api/v1alpha1/custom_package_types.go b/api/v1alpha1/custom_package_types.go index b3ef4b73..aaaf790b 100644 --- a/api/v1alpha1/custom_package_types.go +++ b/api/v1alpha1/custom_package_types.go @@ -21,13 +21,13 @@ type CustomPackageList struct { // CustomPackageSpec controls the installation of the custom applications. type CustomPackageSpec struct { - //// Type specifies what kind of package this is. local means local files specified in the Directory field must be synced to a Git Repository - //Type string `json:"type"` // Replicate specifies whether to replicate remote or local contents to the local gitea server. Replicate bool `json:"replicate"` - // GitServerURL specifies the base URL for the git server for API calls. for example, http://gitea.cnoe.localtest.me:8880 + // GitServerURL specifies the base URL for the git server for API calls. + // for example, http://gitea.cnoe.localtest.me:8880 GitServerURL string `json:"gitServerURL"` - // InternalGitServeURL specifies the base URL for the git server accessible within the cluster. for example, http://my-gitea-http.gitea.svc.cluster.local:3000 + // InternalGitServeURL specifies the base URL for the git server accessible within the cluster. + // for example, http://my-gitea-http.gitea.svc.cluster.local:3000 InternalGitServeURL string `json:"internalGitServeURL"` GitServerAuthSecretRef SecretReference `json:"gitServerAuthSecretRef"` From 1aa1601ebcf6819da8feff25f18dfd6c51857519 Mon Sep 17 00:00:00 2001 From: Manabu Mccloskey Date: Fri, 8 Dec 2023 17:58:55 +0000 Subject: [PATCH 5/7] re-generate manifest Signed-off-by: Manabu Mccloskey --- .../resources/idpbuilder.cnoe.io_custompackages.yaml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/pkg/controllers/resources/idpbuilder.cnoe.io_custompackages.yaml b/pkg/controllers/resources/idpbuilder.cnoe.io_custompackages.yaml index 12f4c596..9ad63741 100644 --- a/pkg/controllers/resources/idpbuilder.cnoe.io_custompackages.yaml +++ b/pkg/controllers/resources/idpbuilder.cnoe.io_custompackages.yaml @@ -68,11 +68,8 @@ spec: server accessible within the cluster. for example, http://my-gitea-http.gitea.svc.cluster.local:3000 type: string replicate: - description: // Type specifies what kind of package this is. local - means local files specified in the Directory field must be synced - to a Git Repository Type string `json:"type"` Replicate specifies - whether to replicate remote or local contents to the local gitea - server. + description: Replicate specifies whether to replicate remote or local + contents to the local gitea server. type: boolean required: - gitServerAuthSecretRef From 73b7fe8989c9258f9dfb8bd26379c413fc267446 Mon Sep 17 00:00:00 2001 From: Manabu Mccloskey Date: Fri, 8 Dec 2023 22:46:28 +0000 Subject: [PATCH 6/7] custom packages are optional Signed-off-by: Manabu Mccloskey --- api/v1alpha1/localbuild_types.go | 2 +- pkg/controllers/resources/idpbuilder.cnoe.io_localbuilds.yaml | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/api/v1alpha1/localbuild_types.go b/api/v1alpha1/localbuild_types.go index f473d06b..949942eb 100644 --- a/api/v1alpha1/localbuild_types.go +++ b/api/v1alpha1/localbuild_types.go @@ -30,7 +30,7 @@ type PackageConfigsSpec struct { GitConfig GitConfigSpec `json:"gitConfig,omitempty"` Argo ArgoPackageConfigSpec `json:"argoPackageConfigs,omitempty"` EmbeddedArgoApplications EmbeddedArgoApplicationsPackageConfigSpec `json:"embeddedArgoApplicationsPackageConfigs,omitempty"` - CustomPackageDirs []string `json:"customPackageDirs"` + CustomPackageDirs []string `json:"customPackageDirs,omitempty"` } type LocalbuildSpec struct { diff --git a/pkg/controllers/resources/idpbuilder.cnoe.io_localbuilds.yaml b/pkg/controllers/resources/idpbuilder.cnoe.io_localbuilds.yaml index aa518787..3eab2e00 100644 --- a/pkg/controllers/resources/idpbuilder.cnoe.io_localbuilds.yaml +++ b/pkg/controllers/resources/idpbuilder.cnoe.io_localbuilds.yaml @@ -64,8 +64,6 @@ spec: type: type: string type: object - required: - - customPackageDirs type: object type: object status: From 43d4b7936cf672d239306cbb217bc3f08dd4a3c4 Mon Sep 17 00:00:00 2001 From: Manabu Mccloskey Date: Sat, 9 Dec 2023 01:24:52 +0000 Subject: [PATCH 7/7] explicit default value for the replicate field populate status field Signed-off-by: Manabu Mccloskey --- api/v1alpha1/custom_package_types.go | 16 +++++++++++-- api/v1alpha1/zz_generated.deepcopy.go | 22 +++++++++++++++++- pkg/cmd/create/root.go | 6 ++--- pkg/controllers/custompackage/controller.go | 23 ++++++++++++++++++- .../idpbuilder.cnoe.io_custompackages.yaml | 16 +++++++++++++ 5 files changed, 76 insertions(+), 7 deletions(-) diff --git a/api/v1alpha1/custom_package_types.go b/api/v1alpha1/custom_package_types.go index aaaf790b..08e89151 100644 --- a/api/v1alpha1/custom_package_types.go +++ b/api/v1alpha1/custom_package_types.go @@ -1,6 +1,8 @@ package v1alpha1 -import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) // +kubebuilder:object:root=true // +kubebuilder:subresource:status @@ -22,6 +24,7 @@ type CustomPackageList struct { // CustomPackageSpec controls the installation of the custom applications. type CustomPackageSpec struct { // Replicate specifies whether to replicate remote or local contents to the local gitea server. + // +kubebuilder:default:=false Replicate bool `json:"replicate"` // GitServerURL specifies the base URL for the git server for API calls. // for example, http://gitea.cnoe.localtest.me:8880 @@ -42,5 +45,14 @@ type ArgoCDPackageSpec struct { } type CustomPackageStatus struct { - Synced bool `json:"synced,omitempty"` + Synced bool `json:"synced,omitempty"` + GitRepositoryRefs []ObjectRef `json:"gitRepositoryRefs,omitempty"` +} + +type ObjectRef struct { + APIVersion string `json:"apiVersion,omitempty"` + Name string `json:"name,omitempty"` + Namespace string `json:"namespace,omitempty"` + Kind string `json:"kind,omitempty"` + UID string `json:"uid,omitempty"` } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 6aa6cc56..fa74b221 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -91,7 +91,7 @@ func (in *CustomPackage) DeepCopyInto(out *CustomPackage) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) out.Spec = in.Spec - out.Status = in.Status + in.Status.DeepCopyInto(&out.Status) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomPackage. @@ -164,6 +164,11 @@ func (in *CustomPackageSpec) DeepCopy() *CustomPackageSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CustomPackageStatus) DeepCopyInto(out *CustomPackageStatus) { *out = *in + if in.GitRepositoryRefs != nil { + in, out := &in.GitRepositoryRefs, &out.GitRepositoryRefs + *out = make([]ObjectRef, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CustomPackageStatus. @@ -541,6 +546,21 @@ func (in *NginxStatus) DeepCopy() *NginxStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectRef) DeepCopyInto(out *ObjectRef) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectRef. +func (in *ObjectRef) DeepCopy() *ObjectRef { + if in == nil { + return nil + } + out := new(ObjectRef) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PackageConfigsSpec) DeepCopyInto(out *PackageConfigsSpec) { *out = *in diff --git a/pkg/cmd/create/root.go b/pkg/cmd/create/root.go index b44e1e87..66fc90fd 100644 --- a/pkg/cmd/create/root.go +++ b/pkg/cmd/create/root.go @@ -62,16 +62,16 @@ func create(cmd *cobra.Command, args []string) error { os.Exit(1) } - var absPaths []string + var absDirPaths []string if len(extraPackagesDirs) > 0 { p, err := getPackageAbsDirs(extraPackagesDirs) if err != nil { return err } - absPaths = p + absDirPaths = p } - b := build.NewBuild(buildName, kubeVersion, kubeConfigPath, kindConfigPath, extraPortsMapping, absPaths, k8s.GetScheme(), ctxCancel) + b := build.NewBuild(buildName, kubeVersion, kubeConfigPath, kindConfigPath, extraPortsMapping, absDirPaths, k8s.GetScheme(), ctxCancel) if err := b.Run(ctx, recreateCluster); err != nil { return err diff --git a/pkg/controllers/custompackage/controller.go b/pkg/controllers/custompackage/controller.go index 4cb4d60f..ae933a81 100644 --- a/pkg/controllers/custompackage/controller.go +++ b/pkg/controllers/custompackage/controller.go @@ -40,6 +40,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu } logger.Info("reconciling custom package", "name", req.Name, "namespace", req.Namespace) + defer r.postProcessReconcile(ctx, req, &pkg) result, err := r.reconcileCustomPackage(ctx, &pkg) if err != nil { r.Recorder.Event(&pkg, "Warning", "reconcile error", err.Error()) @@ -50,6 +51,14 @@ func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Resu return result, err } +func (r *Reconciler) postProcessReconcile(ctx context.Context, req ctrl.Request, pkg *v1alpha1.CustomPackage) { + logger := log.FromContext(ctx) + err := r.Status().Update(ctx, pkg) + if err != nil { + logger.Error(err, "failed updating repo status") + } +} + // create an in-cluster repository CR, update the application spec, then apply func (r *Reconciler) reconcileCustomPackage(ctx context.Context, resource *v1alpha1.CustomPackage) (ctrl.Result, error) { b, err := os.ReadFile(resource.Spec.ArgoCD.ApplicationFile) @@ -67,11 +76,12 @@ func (r *Reconciler) reconcileCustomPackage(ctx context.Context, resource *v1alp app, ok := objs[0].(*argov1alpha1.Application) if !ok { - return ctrl.Result{}, fmt.Errorf("object is not an ArgoCD application %s", resource.Spec.ArgoCD.ApplicationFile) + return ctrl.Result{}, fmt.Errorf("object is not an PackageSpec application %s", resource.Spec.ArgoCD.ApplicationFile) } appName := app.GetName() if resource.Spec.Replicate { + repoRefs := make([]v1alpha1.ObjectRef, 0, 1) if app.Spec.HasMultipleSources() { for j := range app.Spec.Sources { s := app.Spec.Sources[j] @@ -81,6 +91,11 @@ func (r *Reconciler) reconcileCustomPackage(ctx context.Context, resource *v1alp } if repo != nil { s.RepoURL = repo.Status.InternalGitRepositoryUrl + repoRefs = append(repoRefs, v1alpha1.ObjectRef{ + Namespace: repo.Namespace, + Name: repo.Name, + UID: string(repo.ObjectMeta.UID), + }) } } } else { @@ -91,8 +106,14 @@ func (r *Reconciler) reconcileCustomPackage(ctx context.Context, resource *v1alp } if repo != nil { s.RepoURL = repo.Status.InternalGitRepositoryUrl + repoRefs = append(repoRefs, v1alpha1.ObjectRef{ + Namespace: repo.Namespace, + Name: repo.Name, + UID: string(repo.ObjectMeta.UID), + }) } } + resource.Status.GitRepositoryRefs = repoRefs } foundAppObj := argov1alpha1.Application{} diff --git a/pkg/controllers/resources/idpbuilder.cnoe.io_custompackages.yaml b/pkg/controllers/resources/idpbuilder.cnoe.io_custompackages.yaml index 9ad63741..c1acb60d 100644 --- a/pkg/controllers/resources/idpbuilder.cnoe.io_custompackages.yaml +++ b/pkg/controllers/resources/idpbuilder.cnoe.io_custompackages.yaml @@ -68,6 +68,7 @@ spec: server accessible within the cluster. for example, http://my-gitea-http.gitea.svc.cluster.local:3000 type: string replicate: + default: false description: Replicate specifies whether to replicate remote or local contents to the local gitea server. type: boolean @@ -79,6 +80,21 @@ spec: type: object status: properties: + gitRepositoryRefs: + items: + properties: + apiVersion: + type: string + kind: + type: string + name: + type: string + namespace: + type: string + uid: + type: string + type: object + type: array synced: type: boolean type: object