From c96f4ef236a508bf3928918adc2653d0e42aab64 Mon Sep 17 00:00:00 2001 From: Manabu McCloskey Date: Mon, 13 May 2024 11:13:56 -0700 Subject: [PATCH] use git providers (#205) Signed-off-by: Manabu McCloskey --- api/v1alpha1/gitrepository_types.go | 26 +- api/v1alpha1/zz_generated.deepcopy.go | 16 ++ go.mod | 2 + go.sum | 5 + pkg/controllers/custompackage/controller.go | 10 +- .../custompackage/controller_test.go | 10 +- pkg/controllers/gitrepository/controller.go | 229 +++++++----------- .../gitrepository/controller_test.go | 127 +++++----- .../gitrepository/git_repository.go | 29 +++ pkg/controllers/gitrepository/gitea.go | 147 +++++++++++ pkg/controllers/gitrepository/github.go | 113 +++++++++ pkg/controllers/gitrepository/github_test.go | 197 +++++++++++++++ pkg/controllers/localbuild/controller.go | 14 +- pkg/controllers/localbuild/gitea.go | 2 +- .../idpbuilder.cnoe.io_gitrepositories.yaml | 35 ++- pkg/controllers/run.go | 2 +- 16 files changed, 742 insertions(+), 222 deletions(-) create mode 100644 pkg/controllers/gitrepository/gitea.go create mode 100644 pkg/controllers/gitrepository/github.go create mode 100644 pkg/controllers/gitrepository/github_test.go diff --git a/api/v1alpha1/gitrepository_types.go b/api/v1alpha1/gitrepository_types.go index e715a132..457c110c 100644 --- a/api/v1alpha1/gitrepository_types.go +++ b/api/v1alpha1/gitrepository_types.go @@ -4,19 +4,20 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +const ( + GitProviderGitea = "gitea" + GitProviderGitHub = "github" + GiteaAdminUserName = "giteaAdmin" +) + type GitRepositorySpec struct { // +kubebuilder:validation:Optional Customization PackageCustomization `json:"customization,omitempty"` - // GitURL is the base URL of Git server used for API calls. - // +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"` Source GitRepositorySource `json:"source,omitempty"` + Provider Provider `json:"provider"` } type GitRepositorySource struct { @@ -33,6 +34,19 @@ type GitRepositorySource struct { Type string `json:"type"` } +type Provider struct { + // +kubebuilder:validation:Enum:=gitea;github + // +kubebuilder:validation:Required + Name string `json:"name"` + // GitURL is the base URL of Git server used for API calls. + // +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"` + OrganizationName string `json:"organizationName"` +} + type SecretReference struct { Name string `json:"name"` Namespace string `json:"namespace"` diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index a9641983..c3cf1482 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -282,6 +282,7 @@ func (in *GitRepositorySpec) DeepCopyInto(out *GitRepositorySpec) { out.Customization = in.Customization out.SecretRef = in.SecretRef out.Source = in.Source + out.Provider = in.Provider } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitRepositorySpec. @@ -485,6 +486,21 @@ func (in *PackageCustomization) DeepCopy() *PackageCustomization { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Provider) DeepCopyInto(out *Provider) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Provider. +func (in *Provider) DeepCopy() *Provider { + if in == nil { + return nil + } + out := new(Provider) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SecretReference) DeepCopyInto(out *SecretReference) { *out = *in diff --git a/go.mod b/go.mod index 46214bdc..c1465bd3 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/go-git/go-git/v5 v5.10.0 github.com/go-logr/logr v1.4.1 github.com/google/go-cmp v0.6.0 + github.com/google/go-github/v61 v61.0.0 github.com/spf13/cobra v1.7.0 github.com/stretchr/testify v1.9.0 k8s.io/api v0.29.1 @@ -56,6 +57,7 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/gnostic-models v0.6.8 // indirect + github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20230323073829-e72429f035bd // indirect github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2 // indirect diff --git a/go.sum b/go.sum index 01f3796c..dfa88a66 100644 --- a/go.sum +++ b/go.sum @@ -95,10 +95,15 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-github/v61 v61.0.0 h1:VwQCBwhyE9JclCI+22/7mLB1PuU9eowCXKY5pNlu1go= +github.com/google/go-github/v61 v61.0.0/go.mod h1:0WR+KmsWX75G2EbpyGsGmradjo3IiciuI4BmdVCobQY= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= diff --git a/pkg/controllers/custompackage/controller.go b/pkg/controllers/custompackage/controller.go index 6368b155..a0c65685 100644 --- a/pkg/controllers/custompackage/controller.go +++ b/pkg/controllers/custompackage/controller.go @@ -206,9 +206,13 @@ func (r *Reconciler) reconcileGitRepo(ctx context.Context, resource *v1alpha1.Cu Type: "local", Path: absPath, }, - GitURL: resource.Spec.GitServerURL, - InternalGitURL: resource.Spec.InternalGitServeURL, - SecretRef: resource.Spec.GitServerAuthSecretRef, + Provider: v1alpha1.Provider{ + Name: v1alpha1.GitProviderGitea, + GitURL: resource.Spec.GitServerURL, + InternalGitURL: resource.Spec.InternalGitServeURL, + OrganizationName: v1alpha1.GiteaAdminUserName, + }, + SecretRef: resource.Spec.GitServerAuthSecretRef, } return nil diff --git a/pkg/controllers/custompackage/controller_test.go b/pkg/controllers/custompackage/controller_test.go index d7096e39..6e9afa88 100644 --- a/pkg/controllers/custompackage/controller_test.go +++ b/pkg/controllers/custompackage/controller_test.go @@ -13,6 +13,7 @@ import ( argov1alpha1 "github.com/cnoe-io/argocd-api/api/argo/application/v1alpha1" "github.com/cnoe-io/idpbuilder/api/v1alpha1" + "github.com/stretchr/testify/assert" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" k8sruntime "k8s.io/apimachinery/pkg/runtime" @@ -171,10 +172,15 @@ func TestReconcileCustomPkg(t *testing.T) { Type: "local", Path: p, }, - GitURL: "https://cnoe.io", - InternalGitURL: "http://internal.cnoe.io", + Provider: v1alpha1.Provider{ + Name: v1alpha1.GitProviderGitea, + GitURL: "https://cnoe.io", + InternalGitURL: "http://internal.cnoe.io", + OrganizationName: v1alpha1.GiteaAdminUserName, + }, }, } + assert.Equal(t, repo.Spec, expectedRepo.Spec) ok := reflect.DeepEqual(repo.Spec, expectedRepo.Spec) if !ok { t.Fatalf("expected spec does not match") diff --git a/pkg/controllers/gitrepository/controller.go b/pkg/controllers/gitrepository/controller.go index e51f2c73..78062776 100644 --- a/pkg/controllers/gitrepository/controller.go +++ b/pkg/controllers/gitrepository/controller.go @@ -3,6 +3,7 @@ package gitrepository import ( "context" "crypto/tls" + "errors" "fmt" "net" "net/http" @@ -12,15 +13,12 @@ import ( "code.gitea.io/sdk/gitea" "github.com/cnoe-io/idpbuilder/api/v1alpha1" - "github.com/cnoe-io/idpbuilder/pkg/controllers/localbuild" "github.com/cnoe-io/idpbuilder/pkg/util" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing/object" gitclient "github.com/go-git/go-git/v5/plumbing/transport/client" githttp "github.com/go-git/go-git/v5/plumbing/transport/http" - v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -29,12 +27,10 @@ import ( ) const ( - DefaultBranchName = "main" - giteaAdminUsernameKey = "username" - giteaAdminPasswordKey = "password" - requeueTime = time.Second * 30 - gitCommitAuthorName = "git-reconciler" - gitCommitAuthorEmail = "idpbuilder-agent@cnoe.io" + DefaultBranchName = "main" + requeueTime = time.Second * 30 + gitCommitAuthorName = "git-reconciler" + gitCommitAuthorEmail = "idpbuilder-agent@cnoe.io" gitTCPTimeout = 5 * time.Second // timeout value for a git operation through http. clone, push, etc. @@ -45,18 +41,20 @@ func init() { configureGitClient() } -type GiteaClientFunc func(url string, options ...gitea.ClientOption) (GiteaClient, error) - -func NewGiteaClient(url string, options ...gitea.ClientOption) (GiteaClient, error) { - return gitea.NewClient(url, options...) -} - type RepositoryReconciler struct { client.Client - GiteaClientFunc GiteaClientFunc Recorder record.EventRecorder Scheme *runtime.Scheme Config util.CorePackageTemplateConfig + GitProviderFunc gitProviderFunc +} + +type gitProviderFunc func(context.Context, *v1alpha1.GitRepository, client.Client, *runtime.Scheme, util.CorePackageTemplateConfig) (gitProvider, error) + +type notFoundError struct{} + +func (n notFoundError) Error() string { + return fmt.Sprintf("repo not found") } func getRepositoryName(repo v1alpha1.GitRepository) string { @@ -64,39 +62,35 @@ func getRepositoryName(repo v1alpha1.GitRepository) string { } func getOrganizationName(repo v1alpha1.GitRepository) string { - return "giteaAdmin" -} - -func (r *RepositoryReconciler) getCredentials(ctx context.Context, repo *v1alpha1.GitRepository) (string, string, error) { - var secret v1.Secret - err := r.Client.Get(ctx, types.NamespacedName{ - Namespace: repo.Spec.SecretRef.Namespace, - Name: repo.Spec.SecretRef.Name, - }, &secret) - if err != nil { - return "", "", err - } - - username, ok := secret.Data[giteaAdminUsernameKey] - if !ok { - return "", "", fmt.Errorf("%s key not found in secret %s in %s ns", giteaAdminUsernameKey, repo.Spec.SecretRef.Name, repo.Spec.SecretRef.Namespace) - } - password, ok := secret.Data[giteaAdminPasswordKey] - if !ok { - return "", "", fmt.Errorf("%s key not found in secret %s in %s ns", giteaAdminPasswordKey, repo.Spec.SecretRef.Name, repo.Spec.SecretRef.Namespace) - } - return string(username), string(password), nil + return repo.Spec.Provider.OrganizationName } -func (r *RepositoryReconciler) getBasicAuth(ctx context.Context, repo *v1alpha1.GitRepository) (githttp.BasicAuth, error) { - u, p, err := r.getCredentials(ctx, repo) - if err != nil { - return githttp.BasicAuth{}, err +func GetGitProvider(ctx context.Context, repo *v1alpha1.GitRepository, kubeClient client.Client, scheme *runtime.Scheme, tmplConfig util.CorePackageTemplateConfig) (gitProvider, error) { + switch repo.Spec.Provider.Name { + case v1alpha1.GitProviderGitea: + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + c := &http.Client{Transport: tr} + giteaClient, err := NewGiteaClient(repo.Spec.Provider.GitURL, gitea.SetHTTPClient(c)) + if err != nil { + return nil, err + } + return &giteaProvider{ + Client: kubeClient, + Scheme: scheme, + giteaClient: giteaClient, + config: tmplConfig, + }, nil + case v1alpha1.GitProviderGitHub: + return &gitHubProvider{ + Client: kubeClient, + Scheme: scheme, + config: tmplConfig, + gitHubClient: newGitHubClient(nil), + }, nil } - return githttp.BasicAuth{ - Username: u, - Password: p, - }, nil + return nil, fmt.Errorf("invalid git provider %s ", repo.Spec.Provider.Name) } func (r *RepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { @@ -142,40 +136,63 @@ func (r *RepositoryReconciler) reconcileGitRepo(ctx context.Context, repo *v1alp logger := log.FromContext(ctx) logger.V(1).Info("reconciling", "name", repo.Name, "dir", repo.Spec.Source) repo.Status.Synced = false - tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } - client := &http.Client{Transport: tr} - giteaClient, err := r.GiteaClientFunc(repo.Spec.GitURL, gitea.SetHTTPClient(client)) + + provider, err := r.GitProviderFunc(ctx, repo, r.Client, r.Scheme, r.Config) if err != nil { - return ctrl.Result{}, fmt.Errorf("failed to get gitea client: %w", err) + return ctrl.Result{}, fmt.Errorf("initializing git provider: %w", err) } - user, pass, err := r.getCredentials(ctx, repo) + creds, err := provider.getProviderCredentials(ctx, repo) if err != nil { - return ctrl.Result{}, fmt.Errorf("failed to get gitea credentials: %w", err) + return ctrl.Result{}, fmt.Errorf("getting git provider credentials: %w", err) } - giteaClient.SetBasicAuth(user, pass) - giteaClient.SetContext(ctx) - - giteaRepo, err := reconcileRepo(giteaClient, repo) + err = provider.setProviderCredentials(ctx, repo, creds) if err != nil { - return ctrl.Result{}, fmt.Errorf("failed to create or update repo %w", err) + return ctrl.Result{}, fmt.Errorf("setting git provider credentials: %w", err) + } + var providerRepo repoInfo + p, err := provider.getRepository(ctx, repo) + if err != nil { + if errors.Is(err, notFoundError{}) { + p, err = provider.createRepository(ctx, repo) + if err != nil { + return ctrl.Result{}, fmt.Errorf("creating repository: %w", err) + } + providerRepo = p + } else { + return ctrl.Result{}, fmt.Errorf("getting repository: %w", err) + } + } else { + providerRepo = p } - err = r.reconcileRepoContent(ctx, repo, giteaRepo) + err = provider.updateRepoContent(ctx, repo, providerRepo, creds) if err != nil { - return ctrl.Result{}, fmt.Errorf("failed to reconcile repo content %w", err) + return ctrl.Result{}, fmt.Errorf("updating repository contents: %w", err) } - repo.Status.ExternalGitRepositoryUrl = giteaRepo.CloneURL - repo.Status.InternalGitRepositoryUrl = getRepositoryURL(repo.Namespace, repo.Name, repo.Spec.InternalGitURL) + repo.Status.ExternalGitRepositoryUrl = providerRepo.cloneUrl + repo.Status.InternalGitRepositoryUrl = providerRepo.internalGitRepositoryUrl repo.Status.Synced = true return ctrl.Result{Requeue: true, RequeueAfter: requeueTime}, nil } -func (r *RepositoryReconciler) reconcileRepoContent(ctx context.Context, repo *v1alpha1.GitRepository, giteaRepo *gitea.Repository) error { +func (r *RepositoryReconciler) SetupWithManager(mgr ctrl.Manager, notifyChan chan event.GenericEvent) error { + // TODO: should use notifyChan to trigger reconcile when FS changes + return ctrl.NewControllerManagedBy(mgr). + For(&v1alpha1.GitRepository{}). + Complete(r) +} + +func (r *RepositoryReconciler) shouldProcess(repo v1alpha1.GitRepository) bool { + if repo.Spec.Source.Type == "local" && !filepath.IsAbs(repo.Spec.Source.Path) { + return false + } + return true +} + +func updateRepoContent(ctx context.Context, repo *v1alpha1.GitRepository, repoInfo repoInfo, creds gitProviderCredentials, scheme *runtime.Scheme, tmplConfig util.CorePackageTemplateConfig) error { logger := log.FromContext(ctx) tempDir, err := os.MkdirTemp("", fmt.Sprintf("%s-%s", repo.Name, repo.Namespace)) @@ -184,8 +201,14 @@ func (r *RepositoryReconciler) reconcileRepoContent(ctx context.Context, repo *v return fmt.Errorf("creating temporary directory: %w", err) } + auth, err := getBasicAuth(creds) + if err != nil { + return fmt.Errorf("getting basic auth: %w", err) + } + cloneOptions := &git.CloneOptions{ - URL: giteaRepo.CloneURL, + Auth: &auth, + URL: repoInfo.cloneUrl, NoCheckout: true, InsecureSkipTLS: true, } @@ -194,7 +217,7 @@ func (r *RepositoryReconciler) reconcileRepoContent(ctx context.Context, repo *v // if we cannot clone with gitea's configured url, then we fallback to using the url provided in spec. logger.V(1).Info("failed cloning with returned clone URL. Falling back to default url.", "err", err) - cloneOptions.URL = fmt.Sprintf("%s/%s.git", repo.Spec.GitURL, giteaRepo.FullName) + cloneOptions.URL = fmt.Sprintf("%s/%s.git", repo.Spec.Provider.GitURL, repoInfo.fullName) c, retErr := git.PlainCloneContext(ctx, tempDir, false, cloneOptions) if retErr != nil { return fmt.Errorf("cloning repo with fall back url: %w", retErr) @@ -202,9 +225,9 @@ func (r *RepositoryReconciler) reconcileRepoContent(ctx context.Context, repo *v clonedRepo = c } - err = r.writeRepoContents(repo, tempDir) + err = writeRepoContents(repo, tempDir, tmplConfig, scheme) if err != nil { - return err + return fmt.Errorf("writing repo contents: %w", err) } tree, err := clonedRepo.Worktree() @@ -241,10 +264,6 @@ func (r *RepositoryReconciler) reconcileRepoContent(ctx context.Context, repo *v return fmt.Errorf("committing git files: %w", err) } - auth, err := r.getBasicAuth(ctx, repo) - if err != nil { - return fmt.Errorf("getting basic auth: %w", err) - } err = clonedRepo.Push(&git.PushOptions{ Auth: &auth, InsecureSkipTLS: true, @@ -254,75 +273,9 @@ func (r *RepositoryReconciler) reconcileRepoContent(ctx context.Context, repo *v } repo.Status.LatestCommit.Hash = commit.String() - return nil } -func reconcileRepo(giteaClient GiteaClient, repo *v1alpha1.GitRepository) (*gitea.Repository, error) { - resp, repoResp, err := giteaClient.GetRepo(getOrganizationName(*repo), getRepositoryName(*repo)) - if err != nil { - if repoResp != nil && repoResp.StatusCode == http.StatusNotFound { - createResp, _, CErr := giteaClient.CreateRepo(gitea.CreateRepoOption{ - Name: getRepositoryName(*repo), - Description: fmt.Sprintf("created by Git Repository controller for %s in %s namespace", repo.Name, repo.Namespace), - // we should reconsider this when targeting non-local clusters. - Private: false, - DefaultBranch: DefaultBranchName, - AutoInit: true, - }) - if CErr != nil { - return &gitea.Repository{}, fmt.Errorf("failed to create git repository: %w", CErr) - } - return createResp, nil - } - } - return resp, nil -} - -func (r *RepositoryReconciler) SetupWithManager(mgr ctrl.Manager, notifyChan chan event.GenericEvent) error { - // TODO: should use notifyChan to trigger reconcile when FS changes - return ctrl.NewControllerManagedBy(mgr). - For(&v1alpha1.GitRepository{}). - Complete(r) -} - -func (r *RepositoryReconciler) shouldProcess(repo v1alpha1.GitRepository) bool { - if repo.Spec.Source.Type == "local" && !filepath.IsAbs(repo.Spec.Source.Path) { - return false - } - return true -} - -func (r *RepositoryReconciler) writeRepoContents(repo *v1alpha1.GitRepository, dstPath string) error { - if repo.Spec.Source.EmbeddedAppName != "" { - resources, err := localbuild.GetEmbeddedRawInstallResources( - repo.Spec.Source.EmbeddedAppName, r.Config, - v1alpha1.PackageCustomization{Name: repo.Spec.Customization.Name, FilePath: repo.Spec.Customization.FilePath}, r.Scheme) - if err != nil { - return fmt.Errorf("getting embedded resource; %w", err) - } - - for i := range resources { - filePath := filepath.Join(dstPath, fmt.Sprintf("resource%d.yaml", i)) - err = os.WriteFile(filePath, resources[i], 0644) - if err != nil { - return fmt.Errorf("writing embedded resource; %w", err) - } - } - return nil - } - - err := util.CopyDirectory(repo.Spec.Source.Path, dstPath) - if err != nil { - return fmt.Errorf("copying files: %w", err) - } - return nil -} - -func getRepositoryURL(namespace, name, baseUrl string) string { - return fmt.Sprintf("%s/giteaAdmin/%s-%s.git", baseUrl, namespace, name) -} - func configureGitClient() { tr := http.DefaultTransport.(*http.Transport).Clone() diff --git a/pkg/controllers/gitrepository/controller_test.go b/pkg/controllers/gitrepository/controller_test.go index b4c26d2c..f8e8eabc 100644 --- a/pkg/controllers/gitrepository/controller_test.go +++ b/pkg/controllers/gitrepository/controller_test.go @@ -9,7 +9,9 @@ import ( "testing" "time" + "github.com/cnoe-io/idpbuilder/pkg/util" "github.com/stretchr/testify/assert" + "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "code.gitea.io/sdk/gitea" @@ -55,11 +57,20 @@ type expect struct { } type testCase struct { - giteaClient func(url string, options ...gitea.ClientOption) (GiteaClient, error) + giteaClient GiteaClient input v1alpha1.GitRepository expect expect } +func (t testCase) giteaProvider(ctx context.Context, repo *v1alpha1.GitRepository, kubeClient client.Client, scheme *runtime.Scheme, tmplConfig util.CorePackageTemplateConfig) (gitProvider, error) { + return &giteaProvider{ + Client: kubeClient, + Scheme: scheme, + giteaClient: t.giteaClient, + config: tmplConfig, + }, nil +} + type fakeClient struct { client.Client patchObj client.Object @@ -212,14 +223,12 @@ func TestGitRepositoryContentReconcile(t *testing.T) { } t.Run("files modified", func(t *testing.T) { - reconciler := RepositoryReconciler{ - Client: &fakeClient{}, - GiteaClientFunc: func(url string, options ...gitea.ClientOption) (GiteaClient, error) { - return mockGitea{}, nil - }, + p := giteaProvider{ + Client: &fakeClient{}, + giteaClient: mockGitea{}, } // add file to source directory, reconcile, clone the repo and check if the added file exists - err := reconciler.reconcileRepoContent(ctx, &resource, &gitea.Repository{CloneURL: dir}) + err = p.updateRepoContent(ctx, &resource, repoInfo{cloneUrl: dir}, gitProviderCredentials{}) if err != nil { t.Fatalf("failed adding %v", err) } @@ -241,7 +250,7 @@ func TestGitRepositoryContentReconcile(t *testing.T) { if err != nil { t.Fatalf("failed to remove added file %v", err) } - err = reconciler.reconcileRepoContent(ctx, &resource, &gitea.Repository{CloneURL: dir}) + err = p.updateRepoContent(ctx, &resource, repoInfo{cloneUrl: dir}, gitProviderCredentials{}) if err != nil { t.Fatalf("failed removing %v", err) } @@ -279,19 +288,18 @@ func TestGitRepositoryContentReconcileEmbedded(t *testing.T) { EmbeddedAppName: "nginx", Type: "embedded", }, - InternalGitURL: "http://cnoe.io", + Provider: v1alpha1.Provider{ + InternalGitURL: "http://cnoe.io", + }, }, } - t.Run("should sync embedded", func(t *testing.T) { - reconciler := RepositoryReconciler{ - Client: &fakeClient{}, - GiteaClientFunc: func(url string, options ...gitea.ClientOption) (GiteaClient, error) { - return mockGitea{}, nil - }, + t.Run("should update content", func(t *testing.T) { + p := giteaProvider{ + Client: &fakeClient{}, + giteaClient: mockGitea{}, } - - err := reconciler.reconcileRepoContent(ctx, &resource, &gitea.Repository{CloneURL: dir}) + err = p.updateRepoContent(ctx, &resource, repoInfo{cloneUrl: dir}, gitProviderCredentials{}) if err != nil { t.Fatalf("failed adding %v", err) } @@ -322,12 +330,10 @@ func TestGitRepositoryReconcile(t *testing.T) { cases := map[string]testCase{ "no op": { - giteaClient: func(url string, options ...gitea.ClientOption) (GiteaClient, error) { - return mockGitea{ - getRepo: func() (*gitea.Repository, *gitea.Response, error) { - return &gitea.Repository{CloneURL: dir}, nil, nil - }, - }, nil + giteaClient: mockGitea{ + getRepo: func() (*gitea.Repository, *gitea.Response, error) { + return &gitea.Repository{CloneURL: dir}, nil, nil + }, }, input: v1alpha1.GitRepository{ ObjectMeta: m, @@ -336,7 +342,10 @@ func TestGitRepositoryReconcile(t *testing.T) { Path: resourcePath, Type: "local", }, - InternalGitURL: "http://cnoe.io", + Provider: v1alpha1.Provider{ + Name: v1alpha1.GitProviderGitea, + InternalGitURL: "http://cnoe.io", + }, }, }, expect: expect{ @@ -348,44 +357,45 @@ func TestGitRepositoryReconcile(t *testing.T) { }, }, }, - //"update": { - // giteaClient: func(url string, options ...gitea.ClientOption) (GiteaClient, error) { - // return mockGitea{ - // getRepo: func() (*gitea.Repository, *gitea.Response, error) { - // return &gitea.Repository{CloneURL: dir}, nil, nil - // }, - // }, nil - // }, - // input: v1alpha1.GitRepository{ - // ObjectMeta: m, - // Spec: v1alpha1.GitRepositorySpec{ - // Source: v1alpha1.GitRepositorySource{ - // 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", - // }, - // }, - //}, + "update": { + giteaClient: mockGitea{ + getRepo: func() (*gitea.Repository, *gitea.Response, error) { + return &gitea.Repository{CloneURL: dir}, nil, nil + }, + }, + input: v1alpha1.GitRepository{ + ObjectMeta: m, + Spec: v1alpha1.GitRepositorySpec{ + Source: v1alpha1.GitRepositorySource{ + Path: addDir, + Type: "local", + }, + Provider: v1alpha1.Provider{ + Name: v1alpha1.GitProviderGitea, + InternalGitURL: "http://cnoe.io", + }, + }, + }, + expect: expect{ + resource: v1alpha1.GitRepositoryStatus{ + ExternalGitRepositoryUrl: dir, + Synced: true, + InternalGitRepositoryUrl: "http://cnoe.io/giteaAdmin/test-test.git", + }, + }, + }, } ctx := context.Background() - for k := range cases { - v := cases[k] - t.Run(k, func(t *testing.T) { - reconciler := RepositoryReconciler{ + t.Run("repo updates", func(t *testing.T) { + for k := range cases { + v := cases[k] + r := RepositoryReconciler{ Client: &fakeClient{}, - GiteaClientFunc: v.giteaClient, + GitProviderFunc: v.giteaProvider, } - _, err := reconciler.reconcileGitRepo(ctx, &v.input) + _, err := r.reconcileGitRepo(ctx, &v.input) if v.expect.err == nil && err != nil { t.Fatalf("failed %s: %v", k, err) } @@ -393,9 +403,10 @@ func TestGitRepositoryReconcile(t *testing.T) { if v.expect.resource.LatestCommit.Hash == "" { v.expect.resource.LatestCommit.Hash = v.input.Status.LatestCommit.Hash } + time.Sleep(100 * time.Millisecond) assert.Equal(t, v.input.Status, v.expect.resource) - }) - } + } + }) } func TestGitRepositoryPostReconcile(t *testing.T) { diff --git a/pkg/controllers/gitrepository/git_repository.go b/pkg/controllers/gitrepository/git_repository.go index 8cf18c7f..e218e1ee 100644 --- a/pkg/controllers/gitrepository/git_repository.go +++ b/pkg/controllers/gitrepository/git_repository.go @@ -4,6 +4,8 @@ import ( "context" "code.gitea.io/sdk/gitea" + "github.com/cnoe-io/idpbuilder/api/v1alpha1" + "github.com/google/go-github/v61/github" ) type GiteaClient interface { @@ -17,3 +19,30 @@ type GiteaClient interface { SetBasicAuth(username, password string) SetContext(ctx context.Context) } + +type gitHubClient interface { + getRepo(ctx context.Context, owner, repo string) (*github.Repository, *github.Response, error) + createRepo(ctx context.Context, owner string, req *github.Repository) (*github.Repository, *github.Response, error) + setToken(token string) error +} + +type repoInfo struct { + name string + cloneUrl string + internalGitRepositoryUrl string + fullName string +} + +type gitProviderCredentials struct { + username string + password string + accessToken string +} + +type gitProvider interface { + createRepository(ctx context.Context, repo *v1alpha1.GitRepository) (repoInfo, error) + getProviderCredentials(ctx context.Context, repo *v1alpha1.GitRepository) (gitProviderCredentials, error) + getRepository(ctx context.Context, repo *v1alpha1.GitRepository) (repoInfo, error) + setProviderCredentials(ctx context.Context, repo *v1alpha1.GitRepository, creds gitProviderCredentials) error + updateRepoContent(ctx context.Context, repo *v1alpha1.GitRepository, repoInfo repoInfo, creds gitProviderCredentials) error +} diff --git a/pkg/controllers/gitrepository/gitea.go b/pkg/controllers/gitrepository/gitea.go new file mode 100644 index 00000000..8a5b3b65 --- /dev/null +++ b/pkg/controllers/gitrepository/gitea.go @@ -0,0 +1,147 @@ +package gitrepository + +import ( + "context" + "fmt" + "os" + "path/filepath" + + "code.gitea.io/sdk/gitea" + "github.com/cnoe-io/idpbuilder/api/v1alpha1" + "github.com/cnoe-io/idpbuilder/pkg/controllers/localbuild" + "github.com/cnoe-io/idpbuilder/pkg/util" + githttp "github.com/go-git/go-git/v5/plumbing/transport/http" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + giteaAdminUsernameKey = "username" + giteaAdminPasswordKey = "password" +) + +type GiteaClientFunc func(url string, options ...gitea.ClientOption) (GiteaClient, error) + +type giteaProvider struct { + client.Client + Scheme *runtime.Scheme + giteaClient GiteaClient + config util.CorePackageTemplateConfig +} + +func (g *giteaProvider) createRepository(ctx context.Context, repo *v1alpha1.GitRepository) (repoInfo, error) { + resp, _, err := g.giteaClient.CreateRepo(gitea.CreateRepoOption{ + Name: getRepositoryName(*repo), + Description: fmt.Sprintf("created by Git Repository controller for %s in %s namespace", repo.Name, repo.Namespace), + // we should reconsider this when targeting non-local clusters. + Private: false, + DefaultBranch: DefaultBranchName, + AutoInit: true, + }) + if err != nil { + return repoInfo{}, fmt.Errorf("failed to create git repository: %w", err) + } + return repoInfo{ + name: resp.Name, + fullName: resp.FullName, + cloneUrl: resp.CloneURL, + }, nil +} + +func (g *giteaProvider) getProviderCredentials(ctx context.Context, repo *v1alpha1.GitRepository) (gitProviderCredentials, error) { + var secret v1.Secret + err := g.Client.Get(ctx, types.NamespacedName{ + Namespace: repo.Spec.SecretRef.Namespace, + Name: repo.Spec.SecretRef.Name, + }, &secret) + if err != nil { + return gitProviderCredentials{}, err + } + + username, ok := secret.Data[giteaAdminUsernameKey] + if !ok { + return gitProviderCredentials{}, fmt.Errorf("%s key not found in secret %s in %s ns", giteaAdminUsernameKey, repo.Spec.SecretRef.Name, repo.Spec.SecretRef.Namespace) + } + password, ok := secret.Data[giteaAdminPasswordKey] + if !ok { + return gitProviderCredentials{}, fmt.Errorf("%s key not found in secret %s in %s ns", giteaAdminPasswordKey, repo.Spec.SecretRef.Name, repo.Spec.SecretRef.Namespace) + } + return gitProviderCredentials{ + username: string(username), + password: string(password), + }, nil +} + +func (g *giteaProvider) setProviderCredentials(ctx context.Context, repo *v1alpha1.GitRepository, creds gitProviderCredentials) error { + g.giteaClient.SetBasicAuth(creds.username, creds.password) + g.giteaClient.SetContext(ctx) + return nil +} + +func (g *giteaProvider) getRepository(ctx context.Context, repo *v1alpha1.GitRepository) (repoInfo, error) { + resp, repoResp, err := g.giteaClient.GetRepo(getOrganizationName(*repo), getRepositoryName(*repo)) + if err != nil { + if repoResp.StatusCode == 404 { + return repoInfo{}, notFoundError{} + } + return repoInfo{}, err + } + + return repoInfo{ + name: resp.Name, + fullName: resp.FullName, + cloneUrl: resp.CloneURL, + internalGitRepositoryUrl: getInternalGiteaRepositoryURL(repo.Namespace, repo.Name, repo.Spec.Provider.InternalGitURL), + }, nil +} + +func (g *giteaProvider) updateRepoContent(ctx context.Context, repo *v1alpha1.GitRepository, repoInfo repoInfo, creds gitProviderCredentials) error { + return updateRepoContent(ctx, repo, repoInfo, creds, g.Scheme, g.config) +} + +func writeRepoContents(repo *v1alpha1.GitRepository, dstPath string, config util.CorePackageTemplateConfig, scheme *runtime.Scheme) error { + if repo.Spec.Source.EmbeddedAppName != "" { + resources, err := localbuild.GetEmbeddedRawInstallResources( + repo.Spec.Source.EmbeddedAppName, config, + v1alpha1.PackageCustomization{Name: repo.Spec.Customization.Name, FilePath: repo.Spec.Customization.FilePath}, scheme) + if err != nil { + return fmt.Errorf("getting embedded resource; %w", err) + } + + for i := range resources { + filePath := filepath.Join(dstPath, fmt.Sprintf("resource%d.yaml", i)) + err = os.WriteFile(filePath, resources[i], 0644) + if err != nil { + return fmt.Errorf("writing embedded resource; %w", err) + } + } + return nil + } + + err := util.CopyDirectory(repo.Spec.Source.Path, dstPath) + if err != nil { + return fmt.Errorf("copying files: %w", err) + } + return nil +} + +func getBasicAuth(creds gitProviderCredentials) (githttp.BasicAuth, error) { + b := githttp.BasicAuth{ + Username: creds.username, + Password: creds.password, + } + if creds.password == "" { + b.Password = creds.accessToken + } + return b, nil +} + +func NewGiteaClient(url string, options ...gitea.ClientOption) (GiteaClient, error) { + return gitea.NewClient(url, options...) +} + +func getInternalGiteaRepositoryURL(namespace, name, baseUrl string) string { + return fmt.Sprintf("%s/%s/%s-%s.git", baseUrl, v1alpha1.GiteaAdminUserName, namespace, name) +} diff --git a/pkg/controllers/gitrepository/github.go b/pkg/controllers/gitrepository/github.go new file mode 100644 index 00000000..ba21a27b --- /dev/null +++ b/pkg/controllers/gitrepository/github.go @@ -0,0 +1,113 @@ +package gitrepository + +import ( + "context" + "fmt" + "net/http" + + "github.com/cnoe-io/idpbuilder/api/v1alpha1" + "github.com/cnoe-io/idpbuilder/pkg/util" + "github.com/google/go-github/v61/github" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + gitHubTokenKey = "token" +) + +type ghClient struct { + c *github.Client +} + +func (g *ghClient) getRepo(ctx context.Context, owner, repo string) (*github.Repository, *github.Response, error) { + return g.c.Repositories.Get(ctx, owner, repo) +} + +func (g *ghClient) createRepo(ctx context.Context, owner string, req *github.Repository) (*github.Repository, *github.Response, error) { + return g.c.Repositories.Create(ctx, owner, req) +} + +func (g *ghClient) setToken(token string) error { + g.c = g.c.WithAuthToken(token) + return nil +} + +type gitHubProvider struct { + client.Client + Scheme *runtime.Scheme + gitHubClient gitHubClient + config util.CorePackageTemplateConfig +} + +func (g *gitHubProvider) createRepository(ctx context.Context, repo *v1alpha1.GitRepository) (repoInfo, error) { + req := github.Repository{ + Name: github.String(getRepositoryName(*repo)), + Private: github.Bool(true), + } + r, _, err := g.gitHubClient.createRepo(ctx, getOrganizationName(*repo), &req) + if err != nil { + return repoInfo{}, fmt.Errorf("creating repo: %w", err) + } + + return repoInfo{ + name: *r.Name, + cloneUrl: *r.CloneURL, + internalGitRepositoryUrl: "", + fullName: *r.FullName, + }, nil +} + +func (g *gitHubProvider) getRepository(ctx context.Context, repo *v1alpha1.GitRepository) (repoInfo, error) { + r, resp, err := g.gitHubClient.getRepo(ctx, getOrganizationName(*repo), getRepositoryName(*repo)) + if err != nil { + if resp != nil && resp.StatusCode == http.StatusNotFound { + return repoInfo{}, notFoundError{} + } else { + return repoInfo{}, fmt.Errorf("getting repo: %w", err) + } + } + + return repoInfo{ + name: *r.Name, + cloneUrl: *r.CloneURL, + internalGitRepositoryUrl: "", + fullName: *r.FullName, + }, nil +} + +func (g *gitHubProvider) getProviderCredentials(ctx context.Context, repo *v1alpha1.GitRepository) (gitProviderCredentials, error) { + var secret v1.Secret + err := g.Client.Get(ctx, types.NamespacedName{ + Namespace: repo.Spec.SecretRef.Namespace, + Name: repo.Spec.SecretRef.Name, + }, &secret) + if err != nil { + return gitProviderCredentials{}, err + } + + token, ok := secret.Data[gitHubTokenKey] + if !ok { + return gitProviderCredentials{}, fmt.Errorf("%s key not found in secret %s in %s ns", giteaAdminUsernameKey, repo.Spec.SecretRef.Name, repo.Spec.SecretRef.Namespace) + } + + return gitProviderCredentials{ + accessToken: string(token), + }, nil +} + +func (g *gitHubProvider) setProviderCredentials(ctx context.Context, repo *v1alpha1.GitRepository, creds gitProviderCredentials) error { + return g.gitHubClient.setToken(creds.accessToken) +} + +func (g *gitHubProvider) updateRepoContent(ctx context.Context, repo *v1alpha1.GitRepository, repoInfo repoInfo, creds gitProviderCredentials) error { + return updateRepoContent(ctx, repo, repoInfo, creds, g.Scheme, g.config) +} + +func newGitHubClient(httpClient *http.Client) gitHubClient { + return &ghClient{ + c: github.NewClient(httpClient), + } +} diff --git a/pkg/controllers/gitrepository/github_test.go b/pkg/controllers/gitrepository/github_test.go new file mode 100644 index 00000000..416b9be6 --- /dev/null +++ b/pkg/controllers/gitrepository/github_test.go @@ -0,0 +1,197 @@ +package gitrepository + +import ( + "context" + "fmt" + "net/http" + "testing" + + "github.com/cnoe-io/idpbuilder/api/v1alpha1" + "github.com/google/go-github/v61/github" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type fakeGH struct { + mock.Mock +} + +func (f *fakeGH) getRepo(ctx context.Context, owner, repo string) (*github.Repository, *github.Response, error) { + args := f.Called(ctx, owner, repo) + return args.Get(0).(*github.Repository), args.Get(1).(*github.Response), args.Error(2) +} + +func (f *fakeGH) createRepo(ctx context.Context, owner string, req *github.Repository) (*github.Repository, *github.Response, error) { + args := f.Called(ctx, owner, req) + return args.Get(0).(*github.Repository), args.Get(1).(*github.Response), args.Error(2) +} + +func (f *fakeGH) setToken(token string) error { + return nil +} + +func newResponse(r http.Response) *github.Response { + response := &github.Response{Response: &r} + return response +} + +type fakeKubeClient struct { + mock.Mock + client.Client +} + +func (f *fakeKubeClient) Get(ctx context.Context, key client.ObjectKey, obj client.Object, opts ...client.GetOption) error { + args := f.Called(ctx, key, obj, opts) + return args.Error(0) +} + +func TestGitHubCreateRepository(t *testing.T) { + fakeGH := new(fakeGH) + ctx := context.Background() + gh := gitHubProvider{ + Client: &fakeClient{}, + gitHubClient: fakeGH, + } + repoExpected := repoInfo{ + name: "repo1", + cloneUrl: "", + internalGitRepositoryUrl: "", + fullName: "owner/test-test", + } + resource := v1alpha1.GitRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + Spec: v1alpha1.GitRepositorySpec{ + Source: v1alpha1.GitRepositorySource{ + Path: "ac", + Type: "local", + }, + Provider: v1alpha1.Provider{ + Name: "github", + OrganizationName: "owner", + }, + }, + } + + expectedInput := &github.Repository{ + Name: github.String(getRepositoryName(resource)), + Private: github.Bool(true), + } + + fakeGH.On("createRepo", ctx, "owner", expectedInput).Return( + &github.Repository{ + Name: &repoExpected.name, + CloneURL: &repoExpected.cloneUrl, + FullName: &repoExpected.fullName, + }, + newResponse(http.Response{StatusCode: http.StatusOK}), + nil, + ) + + resp, err := gh.createRepository(ctx, &resource) + assert.Nil(t, err) + assert.Equal(t, repoExpected, resp) + fakeGH.AssertExpectations(t) +} + +func TestGitHubGetProviderCredentials(t *testing.T) { + fakeK8sClient := new(fakeKubeClient) + ctx := context.Background() + gh := gitHubProvider{ + Client: fakeK8sClient, + } + + resource := v1alpha1.GitRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + Spec: v1alpha1.GitRepositorySpec{ + SecretRef: v1alpha1.SecretReference{ + Name: "test", + Namespace: "testNS", + }, + }, + } + inputSecret := &v1.Secret{} + fakeK8sClient.On("Get", ctx, types.NamespacedName{ + Namespace: "testNS", + Name: "test", + }, inputSecret, []client.GetOption(nil)).Run(func(args mock.Arguments) { + sec := args.Get(2).(*v1.Secret) + sec.Data = make(map[string][]byte, 1) + sec.Data[gitHubTokenKey] = []byte("token") + }).Return(nil) + + creds, err := gh.getProviderCredentials(ctx, &resource) + assert.Nil(t, err) + assert.Equal(t, creds.accessToken, "token") + fakeK8sClient.AssertExpectations(t) + +} + +func TestGitHubGetRepository(t *testing.T) { + fakeGH := new(fakeGH) + ctx := context.Background() + gh := gitHubProvider{ + Client: &fakeClient{}, + gitHubClient: fakeGH, + } + + repoExpected := repoInfo{ + name: "repo1", + cloneUrl: "", + internalGitRepositoryUrl: "", + fullName: "owner/test-test", + } + + fakeGetRepo := fakeGH.On("getRepo", ctx, "owner", "test-test").Return( + &github.Repository{ + Name: &repoExpected.name, + CloneURL: &repoExpected.cloneUrl, + FullName: &repoExpected.fullName, + }, + newResponse(http.Response{StatusCode: http.StatusOK}), + nil, + ) + + resource := v1alpha1.GitRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "test", + }, + Spec: v1alpha1.GitRepositorySpec{ + Source: v1alpha1.GitRepositorySource{ + Path: "ac", + Type: "local", + }, + Provider: v1alpha1.Provider{ + Name: "github", + OrganizationName: "owner", + }, + }, + } + + resp, err := gh.getRepository(ctx, &resource) + assert.Nil(t, err) + assert.Equal(t, repoExpected, resp) + fakeGH.AssertExpectations(t) + + fakeGetRepo.Unset() + fakeGH.On("getRepo", ctx, "owner", "test-test").Return( + &github.Repository{}, + newResponse(http.Response{StatusCode: http.StatusNotFound}), + fmt.Errorf("some error"), + ) + + resp, err = gh.getRepository(ctx, &resource) + assert.Equal(t, notFoundError{}, err) + assert.Equal(t, repoInfo{}, resp) + fakeGH.AssertExpectations(t) +} diff --git a/pkg/controllers/localbuild/controller.go b/pkg/controllers/localbuild/controller.go index b84b3768..47b67182 100644 --- a/pkg/controllers/localbuild/controller.go +++ b/pkg/controllers/localbuild/controller.go @@ -31,6 +31,10 @@ const ( defaultArgoCDProjectName string = "default" ) +var ( + defaultRequeueTime = time.Second * 30 +) + type LocalbuildReconciler struct { client.Client Scheme *runtime.Scheme @@ -151,7 +155,7 @@ func (r *LocalbuildReconciler) ReconcileArgoAppsWithGitea(ctx context.Context, r } r.shouldShutdown = shutdown - return ctrl.Result{RequeueAfter: time.Second * 30}, nil + return ctrl.Result{RequeueAfter: defaultRequeueTime}, nil } func (r *LocalbuildReconciler) reconcileEmbeddedApp(ctx context.Context, appName string, resource *v1alpha1.Localbuild) (ctrl.Result, error) { @@ -384,8 +388,12 @@ func (r *LocalbuildReconciler) reconcileGitRepo(ctx context.Context, resource *v Source: v1alpha1.GitRepositorySource{ Type: repoType, }, - GitURL: resource.Status.Gitea.ExternalURL, - InternalGitURL: resource.Status.Gitea.InternalURL, + Provider: v1alpha1.Provider{ + Name: v1alpha1.GitProviderGitea, + GitURL: resource.Status.Gitea.ExternalURL, + InternalGitURL: resource.Status.Gitea.InternalURL, + OrganizationName: v1alpha1.GiteaAdminUserName, + }, SecretRef: v1alpha1.SecretReference{ Name: resource.Status.Gitea.AdminUserSecretName, Namespace: resource.Status.Gitea.AdminUserSecretNamespace, diff --git a/pkg/controllers/localbuild/gitea.go b/pkg/controllers/localbuild/gitea.go index 93139606..3905812a 100644 --- a/pkg/controllers/localbuild/gitea.go +++ b/pkg/controllers/localbuild/gitea.go @@ -49,7 +49,7 @@ func newGiteAdminSecret() (corev1.Secret, error) { Namespace: giteaNamespace, }, StringData: map[string]string{ - "username": "giteaAdmin", + "username": v1alpha1.GiteaAdminUserName, "password": pass, }, }, nil diff --git a/pkg/controllers/resources/idpbuilder.cnoe.io_gitrepositories.yaml b/pkg/controllers/resources/idpbuilder.cnoe.io_gitrepositories.yaml index 354efa71..b334960a 100644 --- a/pkg/controllers/resources/idpbuilder.cnoe.io_gitrepositories.yaml +++ b/pkg/controllers/resources/idpbuilder.cnoe.io_gitrepositories.yaml @@ -51,14 +51,30 @@ spec: required: - name type: object - gitURL: - 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 + provider: + properties: + gitURL: + 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 + name: + enum: + - gitea + - github + type: string + organizationName: + type: string + required: + - gitURL + - internalGitURL + - name + - organizationName + type: object secretRef: description: SecretRef is the reference to secret that contain Git server credentials @@ -95,8 +111,7 @@ spec: - type type: object required: - - gitURL - - internalGitURL + - provider type: object status: properties: diff --git a/pkg/controllers/run.go b/pkg/controllers/run.go index 3f60cfd0..f72262f8 100644 --- a/pkg/controllers/run.go +++ b/pkg/controllers/run.go @@ -31,8 +31,8 @@ func RunControllers(ctx context.Context, mgr manager.Manager, exitCh chan error, Client: mgr.GetClient(), Scheme: mgr.GetScheme(), Recorder: mgr.GetEventRecorderFor("gitrepository-controller"), - GiteaClientFunc: gitrepository.NewGiteaClient, Config: cfg, + GitProviderFunc: gitrepository.GetGitProvider, }).SetupWithManager(mgr, nil) if err != nil { logger.Error(err, "unable to create repo controller")