From f971d50c2e2fa333584af03276369ff1d735e70f Mon Sep 17 00:00:00 2001 From: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> Date: Thu, 30 Mar 2023 10:47:29 +0200 Subject: [PATCH 1/3] feat: add repository object This change adds the ability to create repositories and pull requests for different providers. --- Dockerfile | 2 +- PROJECT | 16 +- .../delivery}/v1alpha1/condition_types.go | 1 - .../delivery}/v1alpha1/groupversion_info.go | 1 - {api => apis/delivery}/v1alpha1/sync_types.go | 1 - .../v1alpha1/zz_generated.deepcopy.go | 0 apis/mpas/v1alpha1/groupversion_info.go | 24 +++ apis/mpas/v1alpha1/repository_types.go | 89 ++++++++++ apis/mpas/v1alpha1/zz_generated.deepcopy.go | 149 +++++++++++++++++ .../bases/mpas.ocm.software_repositories.yaml | 157 ++++++++++++++++++ config/crd/kustomization.yaml | 1 + .../cainjection_in_mpas_repositories.yaml | 7 + .../patches/webhook_in_mpas_repositories.yaml | 16 ++ config/rbac/mpas_repository_editor_role.yaml | 31 ++++ config/rbac/mpas_repository_viewer_role.yaml | 27 +++ config/rbac/role.yaml | 26 +++ config/samples/mpas_v1alpha1_repository.yaml | 12 ++ controllers/{ => delivery}/suite_test.go | 5 +- controllers/{ => delivery}/sync_controller.go | 2 +- .../{ => delivery}/sync_controller_test.go | 2 +- controllers/mpas/repository_controller.go | 125 ++++++++++++++ controllers/mpas/suite_test.go | 68 ++++++++ go.mod | 22 ++- go.sum | 35 ++-- main.go | 25 ++- pkg/{providers => }/gogit/git.go | 0 pkg/{providers => }/gogit/tar.go | 0 pkg/providers/gitea/gitea.go | 1 + pkg/providers/github/github.go | 33 ++++ pkg/providers/gitlab/gitlab.go | 1 + pkg/providers/providers.go | 8 + 31 files changed, 849 insertions(+), 38 deletions(-) rename {api => apis/delivery}/v1alpha1/condition_types.go (97%) rename {api => apis/delivery}/v1alpha1/groupversion_info.go (97%) rename {api => apis/delivery}/v1alpha1/sync_types.go (99%) rename {api => apis/delivery}/v1alpha1/zz_generated.deepcopy.go (100%) create mode 100644 apis/mpas/v1alpha1/groupversion_info.go create mode 100644 apis/mpas/v1alpha1/repository_types.go create mode 100644 apis/mpas/v1alpha1/zz_generated.deepcopy.go create mode 100644 config/crd/bases/mpas.ocm.software_repositories.yaml create mode 100644 config/crd/patches/cainjection_in_mpas_repositories.yaml create mode 100644 config/crd/patches/webhook_in_mpas_repositories.yaml create mode 100644 config/rbac/mpas_repository_editor_role.yaml create mode 100644 config/rbac/mpas_repository_viewer_role.yaml create mode 100644 config/samples/mpas_v1alpha1_repository.yaml rename controllers/{ => delivery}/suite_test.go (97%) rename controllers/{ => delivery}/sync_controller.go (99%) rename controllers/{ => delivery}/sync_controller_test.go (96%) create mode 100644 controllers/mpas/repository_controller.go create mode 100644 controllers/mpas/suite_test.go rename pkg/{providers => }/gogit/git.go (100%) rename pkg/{providers => }/gogit/tar.go (100%) create mode 100644 pkg/providers/gitea/gitea.go create mode 100644 pkg/providers/github/github.go create mode 100644 pkg/providers/gitlab/gitlab.go create mode 100644 pkg/providers/providers.go diff --git a/Dockerfile b/Dockerfile index e2d3e4d..0b464b8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ RUN go mod download # Copy the go source COPY main.go main.go -COPY api/ api/ +COPY apis/ apis/ COPY controllers/ controllers/ COPY pkg/ pkg/ diff --git a/PROJECT b/PROJECT index 5507a1f..95727b2 100644 --- a/PROJECT +++ b/PROJECT @@ -1,6 +1,11 @@ +# Code generated by tool. DO NOT EDIT. +# This file is used to track the info used to scaffold your project +# and allow the plugins properly work. +# More info: https://book.kubebuilder.io/reference/project-config.html domain: ocm.software layout: - go.kubebuilder.io/v3 +multigroup: true projectName: git-controller repo: github.com/open-component-model/git-controller resources: @@ -11,6 +16,15 @@ resources: domain: ocm.software group: delivery kind: Sync - path: github.com/open-component-model/git-controller/api/v1alpha1 + path: github.com/open-component-model/git-controller/apis/delivery/v1alpha1 + version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: ocm.software + group: mpas + kind: Repository + path: github.com/open-component-model/git-controller/apis/mpas/v1alpha1 version: v1alpha1 version: "3" diff --git a/api/v1alpha1/condition_types.go b/apis/delivery/v1alpha1/condition_types.go similarity index 97% rename from api/v1alpha1/condition_types.go rename to apis/delivery/v1alpha1/condition_types.go index 0442dba..52c7167 100644 --- a/api/v1alpha1/condition_types.go +++ b/apis/delivery/v1alpha1/condition_types.go @@ -1,4 +1,3 @@ -// Copyright 2022. // SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. // // SPDX-License-Identifier: Apache-2.0 diff --git a/api/v1alpha1/groupversion_info.go b/apis/delivery/v1alpha1/groupversion_info.go similarity index 97% rename from api/v1alpha1/groupversion_info.go rename to apis/delivery/v1alpha1/groupversion_info.go index b941a24..c5d95a1 100644 --- a/api/v1alpha1/groupversion_info.go +++ b/apis/delivery/v1alpha1/groupversion_info.go @@ -1,4 +1,3 @@ -// Copyright 2022. // SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. // // SPDX-License-Identifier: Apache-2.0 diff --git a/api/v1alpha1/sync_types.go b/apis/delivery/v1alpha1/sync_types.go similarity index 99% rename from api/v1alpha1/sync_types.go rename to apis/delivery/v1alpha1/sync_types.go index bba2e6c..a2997b6 100644 --- a/api/v1alpha1/sync_types.go +++ b/apis/delivery/v1alpha1/sync_types.go @@ -1,4 +1,3 @@ -// Copyright 2022. // SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. // // SPDX-License-Identifier: Apache-2.0 diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/apis/delivery/v1alpha1/zz_generated.deepcopy.go similarity index 100% rename from api/v1alpha1/zz_generated.deepcopy.go rename to apis/delivery/v1alpha1/zz_generated.deepcopy.go diff --git a/apis/mpas/v1alpha1/groupversion_info.go b/apis/mpas/v1alpha1/groupversion_info.go new file mode 100644 index 0000000..8ed463d --- /dev/null +++ b/apis/mpas/v1alpha1/groupversion_info.go @@ -0,0 +1,24 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +// Package v1alpha1 contains API Schema definitions for the mpas v1alpha1 API group +// +kubebuilder:object:generate=true +// +groupName=mpas.ocm.software +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "mpas.ocm.software", Version: "v1alpha1"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) diff --git a/apis/mpas/v1alpha1/repository_types.go b/apis/mpas/v1alpha1/repository_types.go new file mode 100644 index 0000000..3ec46c7 --- /dev/null +++ b/apis/mpas/v1alpha1/repository_types.go @@ -0,0 +1,89 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package v1alpha1 + +import ( + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// SecretRef is a reference to a secret in the same namespace as the referencing object. +type SecretRef struct { + Name string `json:"name"` +} + +// Credentials contains ways of authenticating the creation of a repository. +type Credentials struct { + SecretRef SecretRef `json:"secretRef"` +} + +// RepositorySpec defines the desired state of Repository +type RepositorySpec struct { + Provider string `json:"provider"` + Owner string `json:"owner"` + Repository string `json:"repository"` + Credentials Credentials `json:"credentials"` + Interval metav1.Duration `json:"interval"` + + //+optional + Maintainers []string `json:"maintainers,omitempty"` + //+optional + //+kubebuilder:default=true; + AutomaticPullRequestCreation bool `json:"automaticPullRequestCreation,omitempty"` +} + +// RepositoryStatus defines the observed state of Repository +type RepositoryStatus struct { + // ObservedGeneration is the last reconciled generation. + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + + // +optional + // +kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].status",description="" + // +kubebuilder:printcolumn:name="Status",type="string",JSONPath=".status.conditions[?(@.type==\"Ready\")].message",description="" + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// GetConditions returns the conditions of the ComponentVersion. +func (in *Repository) GetConditions() []metav1.Condition { + return in.Status.Conditions +} + +// SetConditions sets the conditions of the ComponentVersion. +func (in *Repository) SetConditions(conditions []metav1.Condition) { + in.Status.Conditions = conditions +} + +// GetRequeueAfter returns the duration after which the ComponentVersion must be +// reconciled again. +func (in Repository) GetRequeueAfter() time.Duration { + return in.Spec.Interval.Duration +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// Repository is the Schema for the repositories API +type Repository struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec RepositorySpec `json:"spec,omitempty"` + Status RepositoryStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// RepositoryList contains a list of Repository +type RepositoryList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Repository `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Repository{}, &RepositoryList{}) +} diff --git a/apis/mpas/v1alpha1/zz_generated.deepcopy.go b/apis/mpas/v1alpha1/zz_generated.deepcopy.go new file mode 100644 index 0000000..19919b4 --- /dev/null +++ b/apis/mpas/v1alpha1/zz_generated.deepcopy.go @@ -0,0 +1,149 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by controller-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1" + 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 *Credentials) DeepCopyInto(out *Credentials) { + *out = *in + out.SecretRef = in.SecretRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Credentials. +func (in *Credentials) DeepCopy() *Credentials { + if in == nil { + return nil + } + out := new(Credentials) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Repository) DeepCopyInto(out *Repository) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Repository. +func (in *Repository) DeepCopy() *Repository { + if in == nil { + return nil + } + out := new(Repository) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Repository) 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 *RepositoryList) DeepCopyInto(out *RepositoryList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Repository, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RepositoryList. +func (in *RepositoryList) DeepCopy() *RepositoryList { + if in == nil { + return nil + } + out := new(RepositoryList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RepositoryList) 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 *RepositorySpec) DeepCopyInto(out *RepositorySpec) { + *out = *in + out.Credentials = in.Credentials + out.Interval = in.Interval + if in.Maintainers != nil { + in, out := &in.Maintainers, &out.Maintainers + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RepositorySpec. +func (in *RepositorySpec) DeepCopy() *RepositorySpec { + if in == nil { + return nil + } + out := new(RepositorySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RepositoryStatus) DeepCopyInto(out *RepositoryStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RepositoryStatus. +func (in *RepositoryStatus) DeepCopy() *RepositoryStatus { + if in == nil { + return nil + } + out := new(RepositoryStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *SecretRef) DeepCopyInto(out *SecretRef) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretRef. +func (in *SecretRef) DeepCopy() *SecretRef { + if in == nil { + return nil + } + out := new(SecretRef) + in.DeepCopyInto(out) + return out +} diff --git a/config/crd/bases/mpas.ocm.software_repositories.yaml b/config/crd/bases/mpas.ocm.software_repositories.yaml new file mode 100644 index 0000000..b46a6b4 --- /dev/null +++ b/config/crd/bases/mpas.ocm.software_repositories.yaml @@ -0,0 +1,157 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + creationTimestamp: null + name: repositories.mpas.ocm.software +spec: + group: mpas.ocm.software + names: + kind: Repository + listKind: RepositoryList + plural: repositories + singular: repository + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: Repository is the Schema for the repositories API + 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: RepositorySpec defines the desired state of Repository + properties: + automaticPullRequestCreation: + default: + - true + type: boolean + credentials: + description: Credentials contains ways of authenticating the creation + of a repository. + properties: + secretRef: + description: SecretRef is a reference to a secret in the same + namespace as the referencing object. + properties: + name: + type: string + required: + - name + type: object + required: + - secretRef + type: object + interval: + type: string + maintainers: + items: + type: string + type: array + owner: + type: string + provider: + type: string + repository: + type: string + required: + - credentials + - interval + - owner + - provider + - repository + type: object + status: + description: RepositoryStatus defines the observed state of Repository + properties: + conditions: + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + \n type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + observedGeneration: + description: ObservedGeneration is the last reconciled generation. + format: int64 + type: integer + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index c894074..09fcce7 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -3,6 +3,7 @@ # It should be run by config/default resources: - bases/delivery.ocm.software_syncs.yaml +- bases/mpas.ocm.software_repositories.yaml #+kubebuilder:scaffold:crdkustomizeresource # the following config is for teaching kustomize how to do kustomization for CRDs. diff --git a/config/crd/patches/cainjection_in_mpas_repositories.yaml b/config/crd/patches/cainjection_in_mpas_repositories.yaml new file mode 100644 index 0000000..d7ab9b5 --- /dev/null +++ b/config/crd/patches/cainjection_in_mpas_repositories.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: repositories.mpas.ocm.software diff --git a/config/crd/patches/webhook_in_mpas_repositories.yaml b/config/crd/patches/webhook_in_mpas_repositories.yaml new file mode 100644 index 0000000..ca17e9d --- /dev/null +++ b/config/crd/patches/webhook_in_mpas_repositories.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: repositories.mpas.ocm.software +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/rbac/mpas_repository_editor_role.yaml b/config/rbac/mpas_repository_editor_role.yaml new file mode 100644 index 0000000..8282747 --- /dev/null +++ b/config/rbac/mpas_repository_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit repositories. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: repository-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: git-controller + app.kubernetes.io/part-of: git-controller + app.kubernetes.io/managed-by: kustomize + name: repository-editor-role +rules: +- apiGroups: + - mpas.ocm.software + resources: + - repositories + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - mpas.ocm.software + resources: + - repositories/status + verbs: + - get diff --git a/config/rbac/mpas_repository_viewer_role.yaml b/config/rbac/mpas_repository_viewer_role.yaml new file mode 100644 index 0000000..5894445 --- /dev/null +++ b/config/rbac/mpas_repository_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view repositories. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: repository-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: git-controller + app.kubernetes.io/part-of: git-controller + app.kubernetes.io/managed-by: kustomize + name: repository-viewer-role +rules: +- apiGroups: + - mpas.ocm.software + resources: + - repositories + verbs: + - get + - list + - watch +- apiGroups: + - mpas.ocm.software + resources: + - repositories/status + verbs: + - get diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index d8e52ca..4ba99cf 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -71,3 +71,29 @@ rules: - get - patch - update +- apiGroups: + - mpas.ocm.software + resources: + - repositories + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - mpas.ocm.software + resources: + - repositories/finalizers + verbs: + - update +- apiGroups: + - mpas.ocm.software + resources: + - repositories/status + verbs: + - get + - patch + - update diff --git a/config/samples/mpas_v1alpha1_repository.yaml b/config/samples/mpas_v1alpha1_repository.yaml new file mode 100644 index 0000000..c3d5f91 --- /dev/null +++ b/config/samples/mpas_v1alpha1_repository.yaml @@ -0,0 +1,12 @@ +apiVersion: mpas.ocm.software/v1alpha1 +kind: Repository +metadata: + labels: + app.kubernetes.io/name: repository + app.kubernetes.io/instance: repository-sample + app.kubernetes.io/part-of: git-controller + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: git-controller + name: repository-sample +spec: + # TODO(user): Add fields here diff --git a/controllers/suite_test.go b/controllers/delivery/suite_test.go similarity index 97% rename from controllers/suite_test.go rename to controllers/delivery/suite_test.go index 538444b..64efeb1 100644 --- a/controllers/suite_test.go +++ b/controllers/delivery/suite_test.go @@ -9,14 +9,15 @@ import ( "testing" "time" - ocmv1alpha1 "github.com/open-component-model/ocm-controller/api/v1alpha1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" - "github.com/open-component-model/git-controller/api/v1alpha1" + ocmv1alpha1 "github.com/open-component-model/ocm-controller/api/v1alpha1" + + "github.com/open-component-model/git-controller/apis/delivery/v1alpha1" ) type testEnv struct { diff --git a/controllers/sync_controller.go b/controllers/delivery/sync_controller.go similarity index 99% rename from controllers/sync_controller.go rename to controllers/delivery/sync_controller.go index a7c7c5b..4487084 100644 --- a/controllers/sync_controller.go +++ b/controllers/delivery/sync_controller.go @@ -24,7 +24,7 @@ import ( ocmv1 "github.com/open-component-model/ocm-controller/api/v1alpha1" - "github.com/open-component-model/git-controller/api/v1alpha1" + "github.com/open-component-model/git-controller/apis/delivery/v1alpha1" providers "github.com/open-component-model/git-controller/pkg" ) diff --git a/controllers/sync_controller_test.go b/controllers/delivery/sync_controller_test.go similarity index 96% rename from controllers/sync_controller_test.go rename to controllers/delivery/sync_controller_test.go index d542754..6c4fc81 100644 --- a/controllers/sync_controller_test.go +++ b/controllers/delivery/sync_controller_test.go @@ -15,7 +15,7 @@ import ( ocmv1 "github.com/open-component-model/ocm-controller/api/v1alpha1" - "github.com/open-component-model/git-controller/api/v1alpha1" + "github.com/open-component-model/git-controller/apis/delivery/v1alpha1" "github.com/open-component-model/git-controller/pkg" ) diff --git a/controllers/mpas/repository_controller.go b/controllers/mpas/repository_controller.go new file mode 100644 index 0000000..9d61a28 --- /dev/null +++ b/controllers/mpas/repository_controller.go @@ -0,0 +1,125 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package mpas + +import ( + "context" + "errors" + "fmt" + + "github.com/fluxcd/pkg/apis/meta" + "github.com/fluxcd/pkg/runtime/conditions" + "github.com/fluxcd/pkg/runtime/patch" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + mpasv1alpha1 "github.com/open-component-model/git-controller/apis/mpas/v1alpha1" +) + +// RepositoryReconciler reconciles a Repository object +type RepositoryReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=mpas.ocm.software,resources=repositories,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=mpas.ocm.software,resources=repositories/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=mpas.ocm.software,resources=repositories/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +func (r *RepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + var ( + retErr error + result ctrl.Result + ) + + logger := log.FromContext(ctx).WithName("repository") + + logger.V(4).Info("entering repository loop...") + + obj := &mpasv1alpha1.Repository{} + + if err := r.Get(ctx, req.NamespacedName, obj); err != nil { + if apierrors.IsNotFound(err) { + return result, nil + } + retErr = fmt.Errorf("failed to get component object: %w", err) + return result, retErr + } + + patchHelper, err := patch.NewHelper(obj, r.Client) + if err != nil { + retErr = errors.Join(retErr, err) + return result, retErr + } + + // Always attempt to patch the object and status after each reconciliation. + defer func() { + // Patching has not been set up, or the controller errored earlier. + if patchHelper == nil { + return + } + + if condition := conditions.Get(obj, meta.StalledCondition); condition != nil && condition.Status == metav1.ConditionTrue { + conditions.Delete(obj, meta.ReconcilingCondition) + } + + // Check if it's a successful reconciliation. + // We don't set Requeue in case of error, so we can safely check for Requeue. + if result.RequeueAfter == obj.GetRequeueAfter() && !result.Requeue && retErr == nil { + // Remove the reconciling condition if it's set. + conditions.Delete(obj, meta.ReconcilingCondition) + + // Set the return err as the ready failure message if the resource is not ready, but also not reconciling or stalled. + if ready := conditions.Get(obj, meta.ReadyCondition); ready != nil && ready.Status == metav1.ConditionFalse && !conditions.IsStalled(obj) { + retErr = errors.New(conditions.GetMessage(obj, meta.ReadyCondition)) + } + } + + // If still reconciling then reconciliation did not succeed, set to ProgressingWithRetry to + // indicate that reconciliation will be retried. + if conditions.IsReconciling(obj) { + reconciling := conditions.Get(obj, meta.ReconcilingCondition) + reconciling.Reason = meta.ProgressingWithRetryReason + conditions.Set(obj, reconciling) + } + + // If not reconciling or stalled than mark Ready=True + if !conditions.IsReconciling(obj) && + !conditions.IsStalled(obj) && + retErr == nil && + result.RequeueAfter == obj.GetRequeueAfter() { + conditions.MarkTrue(obj, meta.ReadyCondition, meta.SucceededReason, "Reconciliation success") + } + // Set status observed generation option if the component is stalled or ready. + if conditions.IsStalled(obj) || conditions.IsReady(obj) { + obj.Status.ObservedGeneration = obj.Generation + } + + // Update the object. + if err := patchHelper.Patch(ctx, obj); err != nil { + retErr = errors.Join(retErr, err) + } + }() + + result, retErr = r.reconcile(ctx, obj) + return result, retErr +} + +// SetupWithManager sets up the controller with the Manager. +func (r *RepositoryReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&mpasv1alpha1.Repository{}). + Complete(r) +} + +func (r *RepositoryReconciler) reconcile(ctx context.Context, obj *mpasv1alpha1.Repository) (ctrl.Result, error) { + return ctrl.Result{}, nil +} diff --git a/controllers/mpas/suite_test.go b/controllers/mpas/suite_test.go new file mode 100644 index 0000000..8079592 --- /dev/null +++ b/controllers/mpas/suite_test.go @@ -0,0 +1,68 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package mpas + +import ( + "path/filepath" + "testing" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + + mpasv1alpha1 "github.com/open-component-model/git-controller/apis/mpas/v1alpha1" + //+kubebuilder:scaffold:imports +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var cfg *rest.Config +var k8sClient client.Client +var testEnv *envtest.Environment + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecs(t, "Controller Suite") +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: true, + } + + var err error + // cfg is defined in this file globally. + cfg, err = testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + err = mpasv1alpha1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + +}) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/go.mod b/go.mod index 6393d10..85bd5e0 100644 --- a/go.mod +++ b/go.mod @@ -4,10 +4,13 @@ go 1.20 require ( github.com/Masterminds/semver/v3 v3.2.0 + github.com/fluxcd/go-git-providers v0.15.0 github.com/fluxcd/pkg/apis/meta v0.19.0 github.com/fluxcd/pkg/runtime v0.27.0 github.com/go-git/go-git/v5 v5.6.0 github.com/go-logr/logr v1.2.3 + github.com/onsi/ginkgo/v2 v2.8.4 + github.com/onsi/gomega v1.27.2 github.com/open-component-model/ocm-controller v0.4.0 github.com/stretchr/testify v1.8.1 k8s.io/api v0.26.2 @@ -73,17 +76,21 @@ require ( github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.2 // indirect + github.com/golang/protobuf v1.5.3 // indirect github.com/google/gnostic v0.6.9 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/go-containerregistry v0.13.0 // indirect github.com/google/go-github/v45 v45.2.0 // indirect + github.com/google/go-github/v49 v49.1.0 // 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-20210720184732-4bb14d4b1be1 // indirect github.com/google/uuid v1.3.0 // indirect github.com/gorilla/mux v1.8.0 // indirect + github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/imdario/mergo v0.3.13 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect @@ -111,7 +118,6 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/onsi/gomega v1.27.2 // indirect github.com/open-component-model/ocm v0.2.0-rc.1 // indirect github.com/open-component-model/ocm-controllers-sdk v0.0.6 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect @@ -142,19 +148,19 @@ require ( go.uber.org/zap v1.24.0 // indirect golang.org/x/crypto v0.6.0 // indirect golang.org/x/mod v0.8.0 // indirect - golang.org/x/net v0.7.0 // indirect - golang.org/x/oauth2 v0.5.0 // indirect + golang.org/x/net v0.8.0 // indirect + golang.org/x/oauth2 v0.6.0 // indirect golang.org/x/sync v0.1.0 // indirect - golang.org/x/sys v0.5.0 // indirect - golang.org/x/term v0.5.0 // indirect - golang.org/x/text v0.7.0 // indirect + golang.org/x/sys v0.6.0 // indirect + golang.org/x/term v0.6.0 // indirect + golang.org/x/text v0.8.0 // indirect golang.org/x/time v0.3.0 // indirect golang.org/x/tools v0.6.0 // indirect gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006 // indirect google.golang.org/grpc v1.49.0 // indirect - google.golang.org/protobuf v1.28.1 // indirect + google.golang.org/protobuf v1.29.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 13d7a73..caef0b8 100644 --- a/go.sum +++ b/go.sum @@ -471,6 +471,8 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= +github.com/fluxcd/go-git-providers v0.15.0 h1:WuBw+CcmXi7UhSf8mFNB6tbGelS0kVlgI9wtlWjzimk= +github.com/fluxcd/go-git-providers v0.15.0/go.mod h1:SgShGfc2rA5Gi7N65CBjMOIolarDZzZCMzEHOoY3P0I= github.com/fluxcd/pkg/apis/meta v0.19.0 h1:CX75e/eaRWZDTzNdMSWomY1InlssLKcS8GQDSg/aopI= github.com/fluxcd/pkg/apis/meta v0.19.0/go.mod h1:7b6prDPsViyAzoY7eRfSPS0/MbXpGGsOMvRq2QrTKa4= github.com/fluxcd/pkg/runtime v0.27.0 h1:zVA95Z0KvNjvZxEZhvIbJyJIwtaiv1aVttHZ4YB/FzY= @@ -599,8 +601,9 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/gomodule/redigo v1.8.2 h1:H5XSIre1MB5NbPYFp+i1NBbb5qN1W8Y8YAQoAYbkm8k= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -629,6 +632,8 @@ github.com/google/go-containerregistry v0.13.0 h1:y1C7Z3e149OJbOPDBxLYR8ITPz8dTK github.com/google/go-containerregistry v0.13.0/go.mod h1:J9FQ+eSS4a1aC2GNZxvNpbWhgp0487v+cgiilB4FqDo= github.com/google/go-github/v45 v45.2.0 h1:5oRLszbrkvxDDqBCNj2hjDZMKmvexaZ1xw/FCD+K3FI= github.com/google/go-github/v45 v45.2.0/go.mod h1:FObaZJEDSTa/WGCzZ2Z3eoCDXWJKMenWWTrd8jrta28= +github.com/google/go-github/v49 v49.1.0 h1:LFkMgawGQ8dfzWLH/rNE0b3u1D3n6/dw7ZmrN3b+YFY= +github.com/google/go-github/v49 v49.1.0/go.mod h1:MUUzHPrhGniB6vUKa27y37likpipzG+BXXJbG04J334= github.com/google/go-intervals v0.0.2/go.mod h1:MkaR3LNRfeKLPmqgJYs4E66z5InYjmCjbbr4TQlcT6Y= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= @@ -651,6 +656,7 @@ github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLe github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -676,6 +682,8 @@ github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-middleware v1.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= @@ -907,6 +915,7 @@ github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9k github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo/v2 v2.8.4 h1:gf5mIQ8cLFieruNLAdgijHF1PYfLphKm2dxxcUtcqK0= +github.com/onsi/ginkgo/v2 v2.8.4/go.mod h1:427dEDQZkDKsBvCjc2A/ZPefhKxsTTrsQegMlayL730= github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= @@ -1361,8 +1370,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1376,8 +1385,8 @@ golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= -golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= +golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= +golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -1501,8 +1510,8 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -1511,8 +1520,8 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1523,8 +1532,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1738,8 +1747,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.29.0 h1:44S3JjaKmLEE4YIkjzexaP+NzZsudE3Zin5Njn/pYX0= +google.golang.org/protobuf v1.29.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/cenkalti/backoff.v2 v2.2.1 h1:eJ9UAg01/HIHG987TwxvnzK2MgxXq97YY6rYDpY9aII= diff --git a/main.go b/main.go index 9a1a281..b9e6098 100644 --- a/main.go +++ b/main.go @@ -1,4 +1,3 @@ -// Copyright 2022. // SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. // // SPDX-License-Identifier: Apache-2.0 @@ -9,20 +8,22 @@ import ( "flag" "os" - "github.com/open-component-model/ocm-controller/api/v1alpha1" - "github.com/open-component-model/ocm-controller/pkg/oci" - _ "k8s.io/client-go/plugin/pkg/client/auth" - + "github.com/open-component-model/git-controller/pkg/gogit" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" + _ "k8s.io/client-go/plugin/pkg/client/auth" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" - deliveryv1alpha1 "github.com/open-component-model/git-controller/api/v1alpha1" - "github.com/open-component-model/git-controller/controllers" - "github.com/open-component-model/git-controller/pkg/providers/gogit" + "github.com/open-component-model/ocm-controller/api/v1alpha1" + "github.com/open-component-model/ocm-controller/pkg/oci" + + deliveryv1alpha1 "github.com/open-component-model/git-controller/apis/delivery/v1alpha1" + mpasv1alpha1 "github.com/open-component-model/git-controller/apis/mpas/v1alpha1" + controllers "github.com/open-component-model/git-controller/controllers/delivery" + mpascontrollers "github.com/open-component-model/git-controller/controllers/mpas" //+kubebuilder:scaffold:imports ) @@ -36,6 +37,7 @@ func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(v1alpha1.AddToScheme(scheme)) utilruntime.Must(deliveryv1alpha1.AddToScheme(scheme)) + utilruntime.Must(mpasv1alpha1.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme } @@ -98,6 +100,13 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "Sync") os.Exit(1) } + if err = (&mpascontrollers.RepositoryReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "Repository") + os.Exit(1) + } //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/pkg/providers/gogit/git.go b/pkg/gogit/git.go similarity index 100% rename from pkg/providers/gogit/git.go rename to pkg/gogit/git.go diff --git a/pkg/providers/gogit/tar.go b/pkg/gogit/tar.go similarity index 100% rename from pkg/providers/gogit/tar.go rename to pkg/gogit/tar.go diff --git a/pkg/providers/gitea/gitea.go b/pkg/providers/gitea/gitea.go new file mode 100644 index 0000000..3ac0cb7 --- /dev/null +++ b/pkg/providers/gitea/gitea.go @@ -0,0 +1 @@ +package gitea diff --git a/pkg/providers/github/github.go b/pkg/providers/github/github.go new file mode 100644 index 0000000..7b757c5 --- /dev/null +++ b/pkg/providers/github/github.go @@ -0,0 +1,33 @@ +package github + +import ( + "context" + "fmt" + + "github.com/fluxcd/go-git-providers/github" + "github.com/open-component-model/git-controller/pkg/providers" +) + +type Client struct { + // TODO: Figure out how to get this. + BaseURL string +} + +func NewClient() *Client { + return &Client{} +} + +var _ providers.Provider = &Client{} + +func (c *Client) CreateRepository(ctx context.Context, owner, repo string) error { + _, err := github.NewClient() + if err != nil { + return fmt.Errorf("failed to create github client: %w", err) + } + + return nil +} + +func (c *Client) CreatePullRequest(ctx context.Context, owner, repo, title, branch, description string) error { + return nil +} diff --git a/pkg/providers/gitlab/gitlab.go b/pkg/providers/gitlab/gitlab.go new file mode 100644 index 0000000..4d4b35a --- /dev/null +++ b/pkg/providers/gitlab/gitlab.go @@ -0,0 +1 @@ +package gitlab diff --git a/pkg/providers/providers.go b/pkg/providers/providers.go new file mode 100644 index 0000000..d85e230 --- /dev/null +++ b/pkg/providers/providers.go @@ -0,0 +1,8 @@ +package providers + +import "context" + +type Provider interface { + CreateRepository(ctx context.Context, owner, repo string) error + CreatePullRequest(ctx context.Context, owner, repo, title, branch, description string) error +} From 5a5f4d5ac495acb4f209ecca63ce1b6e66b5ccb1 Mon Sep 17 00:00:00 2001 From: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> Date: Thu, 30 Mar 2023 14:47:29 +0200 Subject: [PATCH 2/3] updated tiltfile and added github repositroy support --- Tiltfile | 2 +- apis/mpas/v1alpha1/condition_types.go | 10 ++ apis/mpas/v1alpha1/repository_types.go | 20 ++- .../bases/mpas.ocm.software_repositories.yaml | 16 ++- config/samples/mpas_v1alpha1_repository.yaml | 14 +- controllers/mpas/repository_controller.go | 23 +++- main.go | 9 +- pkg/providers/github/github.go | 128 +++++++++++++++++- pkg/providers/providers.go | 9 +- 9 files changed, 196 insertions(+), 35 deletions(-) create mode 100644 apis/mpas/v1alpha1/condition_types.go diff --git a/Tiltfile b/Tiltfile index 473b9b6..65fcebb 100644 --- a/Tiltfile +++ b/Tiltfile @@ -39,7 +39,7 @@ local_resource( "main.go", "go.mod", "go.sum", - "api", + "apis", "controllers", "pkg", ], diff --git a/apis/mpas/v1alpha1/condition_types.go b/apis/mpas/v1alpha1/condition_types.go new file mode 100644 index 0000000..d945039 --- /dev/null +++ b/apis/mpas/v1alpha1/condition_types.go @@ -0,0 +1,10 @@ +// SPDX-FileCopyrightText: 2022 SAP SE or an SAP affiliate company and Open Component Model contributors. +// +// SPDX-License-Identifier: Apache-2.0 + +package v1alpha1 + +const ( + // RepositoryCreateFailedReason is used when we fail to create a Repository. + RepositoryCreateFailedReason = "RepositoryCreateFailed" +) diff --git a/apis/mpas/v1alpha1/repository_types.go b/apis/mpas/v1alpha1/repository_types.go index 3ec46c7..1580779 100644 --- a/apis/mpas/v1alpha1/repository_types.go +++ b/apis/mpas/v1alpha1/repository_types.go @@ -22,16 +22,24 @@ type Credentials struct { // RepositorySpec defines the desired state of Repository type RepositorySpec struct { - Provider string `json:"provider"` - Owner string `json:"owner"` - Repository string `json:"repository"` - Credentials Credentials `json:"credentials"` - Interval metav1.Duration `json:"interval"` + Provider string `json:"provider"` + Owner string `json:"owner"` + RepositoryName string `json:"repositoryName"` + Credentials Credentials `json:"credentials"` + //+optional + Interval metav1.Duration `json:"interval,omitempty"` + //+optional + //+kubebuilder:default:=internal + Visibility string `json:"visibility,omitempty"` + //+kubebuilder:default:=true + IsOrganization bool `json:"isOrganization,omitempty"` + //+optional + Domain string `json:"domain,omitempty"` //+optional Maintainers []string `json:"maintainers,omitempty"` //+optional - //+kubebuilder:default=true; + //+kubebuilder:default:=true AutomaticPullRequestCreation bool `json:"automaticPullRequestCreation,omitempty"` } diff --git a/config/crd/bases/mpas.ocm.software_repositories.yaml b/config/crd/bases/mpas.ocm.software_repositories.yaml index b46a6b4..336cec3 100644 --- a/config/crd/bases/mpas.ocm.software_repositories.yaml +++ b/config/crd/bases/mpas.ocm.software_repositories.yaml @@ -36,8 +36,7 @@ spec: description: RepositorySpec defines the desired state of Repository properties: automaticPullRequestCreation: - default: - - true + default: true type: boolean credentials: description: Credentials contains ways of authenticating the creation @@ -55,8 +54,13 @@ spec: required: - secretRef type: object + domain: + type: string interval: type: string + isOrganization: + default: true + type: boolean maintainers: items: type: string @@ -65,14 +69,16 @@ spec: type: string provider: type: string - repository: + repositoryName: + type: string + visibility: + default: internal type: string required: - credentials - - interval - owner - provider - - repository + - repositoryName type: object status: description: RepositoryStatus defines the observed state of Repository diff --git a/config/samples/mpas_v1alpha1_repository.yaml b/config/samples/mpas_v1alpha1_repository.yaml index c3d5f91..7f72388 100644 --- a/config/samples/mpas_v1alpha1_repository.yaml +++ b/config/samples/mpas_v1alpha1_repository.yaml @@ -1,12 +1,12 @@ apiVersion: mpas.ocm.software/v1alpha1 kind: Repository metadata: - labels: - app.kubernetes.io/name: repository - app.kubernetes.io/instance: repository-sample - app.kubernetes.io/part-of: git-controller - app.kubernetes.io/managed-by: kustomize - app.kubernetes.io/created-by: git-controller name: repository-sample spec: - # TODO(user): Add fields here + credentials: + secretRef: + name: github-creds + interval: 10m + owner: Skarlso + provider: github + repositoryName: new-repository-1 diff --git a/controllers/mpas/repository_controller.go b/controllers/mpas/repository_controller.go index 9d61a28..38a6b6e 100644 --- a/controllers/mpas/repository_controller.go +++ b/controllers/mpas/repository_controller.go @@ -16,16 +16,20 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" mpasv1alpha1 "github.com/open-component-model/git-controller/apis/mpas/v1alpha1" + "github.com/open-component-model/git-controller/pkg/providers" ) // RepositoryReconciler reconciles a Repository object type RepositoryReconciler struct { client.Client - Scheme *runtime.Scheme + Scheme *runtime.Scheme + Provider providers.Provider } //+kubebuilder:rbac:groups=mpas.ocm.software,resources=repositories,verbs=get;list;watch;create;update;patch;delete @@ -41,7 +45,6 @@ func (r *RepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Request) ) logger := log.FromContext(ctx).WithName("repository") - logger.V(4).Info("entering repository loop...") obj := &mpasv1alpha1.Repository{} @@ -94,10 +97,10 @@ func (r *RepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Request) // If not reconciling or stalled than mark Ready=True if !conditions.IsReconciling(obj) && !conditions.IsStalled(obj) && - retErr == nil && - result.RequeueAfter == obj.GetRequeueAfter() { + retErr == nil { conditions.MarkTrue(obj, meta.ReadyCondition, meta.SucceededReason, "Reconciliation success") } + // Set status observed generation option if the component is stalled or ready. if conditions.IsStalled(obj) || conditions.IsReady(obj) { obj.Status.ObservedGeneration = obj.Generation @@ -109,6 +112,11 @@ func (r *RepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Request) } }() + // Remove any stale Ready condition, most likely False, set above. Its value + // is derived from the overall result of the reconciliation in the deferred + // block at the very end. + conditions.Delete(obj, meta.ReadyCondition) + result, retErr = r.reconcile(ctx, obj) return result, retErr } @@ -116,10 +124,15 @@ func (r *RepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Request) // SetupWithManager sets up the controller with the Manager. func (r *RepositoryReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). - For(&mpasv1alpha1.Repository{}). + For(&mpasv1alpha1.Repository{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). Complete(r) } func (r *RepositoryReconciler) reconcile(ctx context.Context, obj *mpasv1alpha1.Repository) (ctrl.Result, error) { + if err := r.Provider.CreateRepository(ctx, *obj); err != nil { + conditions.MarkFalse(obj, meta.ReadyCondition, mpasv1alpha1.RepositoryCreateFailedReason, err.Error()) + return ctrl.Result{}, fmt.Errorf("failed to create repository: %w", err) + } + return ctrl.Result{}, nil } diff --git a/main.go b/main.go index b9e6098..882d85d 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,7 @@ import ( "os" "github.com/open-component-model/git-controller/pkg/gogit" + "github.com/open-component-model/git-controller/pkg/providers/github" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" @@ -30,7 +31,6 @@ import ( var ( scheme = runtime.NewScheme() setupLog = ctrl.Log.WithName("setup") - ociAgent = "git-controller/v1alpha1" ) func init() { @@ -100,9 +100,12 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "Sync") os.Exit(1) } + + githubProvider := github.NewClient(mgr.GetClient(), nil) if err = (&mpascontrollers.RepositoryReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Provider: githubProvider, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "Repository") os.Exit(1) diff --git a/pkg/providers/github/github.go b/pkg/providers/github/github.go index 7b757c5..6bd177e 100644 --- a/pkg/providers/github/github.go +++ b/pkg/providers/github/github.go @@ -5,26 +5,142 @@ import ( "fmt" "github.com/fluxcd/go-git-providers/github" + "github.com/fluxcd/go-git-providers/gitprovider" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + mpasv1alpha1 "github.com/open-component-model/git-controller/apis/mpas/v1alpha1" "github.com/open-component-model/git-controller/pkg/providers" ) +const ( + tokenKey = "token" + providerType = "github" + defaultDomain = "github.com" +) + +// Client github. type Client struct { - // TODO: Figure out how to get this. - BaseURL string + client client.Client + next providers.Provider +} + +// TODO: Use this instead and somehow abstract the two clients. +type RepositoryOpts struct { + Owner string + Domain string + Visibility gitprovider.RepositoryVisibility } -func NewClient() *Client { - return &Client{} +// NewClient creates a new GitHub client. +func NewClient(client client.Client, next providers.Provider) *Client { + return &Client{ + client: client, + next: next, + } } var _ providers.Provider = &Client{} -func (c *Client) CreateRepository(ctx context.Context, owner, repo string) error { - _, err := github.NewClient() +func (c *Client) CreateRepository(ctx context.Context, obj mpasv1alpha1.Repository) error { + if obj.Spec.Provider != providerType { + if c.next == nil { + return fmt.Errorf("can't handle provider type '%s' and no next provider is configured", obj.Spec.Provider) + } + + return c.next.CreateRepository(ctx, obj) + } + + authenticationOption, err := c.constructAuthenticationOption(ctx, obj) + if err != nil { + return err + } + + gc, err := github.NewClient(authenticationOption) if err != nil { return fmt.Errorf("failed to create github client: %w", err) } + visibility := gitprovider.RepositoryVisibility(obj.Spec.Visibility) + + if err := gitprovider.ValidateRepositoryVisibility(visibility); err != nil { + return fmt.Errorf("failed to validate visibility: %w", err) + } + + domain := defaultDomain + if obj.Spec.Domain != "" { + domain = obj.Spec.Domain + } + + if obj.Spec.IsOrganization { + return c.createOrganizationRepository(ctx, gc, domain, visibility, obj.Spec) + } + + return c.createUserRepository(ctx, gc, domain, visibility, obj.Spec) +} + +// constructAuthenticationOption will take the object and construct an authentication option. +// For now, only token secret is supported, this will be extended in the future. +func (c *Client) constructAuthenticationOption(ctx context.Context, obj mpasv1alpha1.Repository) (gitprovider.ClientOption, error) { + secret := &v1.Secret{} + if err := c.client.Get(ctx, types.NamespacedName{ + Name: obj.Spec.Credentials.SecretRef.Name, + Namespace: obj.Namespace, + }, secret); err != nil { + return nil, fmt.Errorf("failed to get secret: %w", err) + } + + token, ok := secret.Data[tokenKey] + if !ok { + return nil, fmt.Errorf("token '%s' not found in secret", tokenKey) + } + + return gitprovider.WithOAuth2Token(string(token)), nil +} + +func (c *Client) createOrganizationRepository(ctx context.Context, gc gitprovider.Client, domain string, visibility gitprovider.RepositoryVisibility, spec mpasv1alpha1.RepositorySpec) error { + logger := log.FromContext(ctx) + + repo, err := gc.OrgRepositories().Create(ctx, gitprovider.OrgRepositoryRef{ + OrganizationRef: gitprovider.OrganizationRef{ + Domain: domain, + Organization: spec.Owner, + }, + RepositoryName: spec.RepositoryName, + }, gitprovider.RepositoryInfo{ + DefaultBranch: gitprovider.StringVar("main"), + Visibility: &visibility, + }) + if err != nil { + return fmt.Errorf("failed to create repository: %w", err) + } + + logger.Info("organization repository successfully created", "name", repo.Repository().String()) + + return nil +} + +func (c *Client) createUserRepository(ctx context.Context, gc gitprovider.Client, domain string, visibility gitprovider.RepositoryVisibility, spec mpasv1alpha1.RepositorySpec) error { + logger := log.FromContext(ctx) + + repo, err := gc.UserRepositories().Create(ctx, gitprovider.UserRepositoryRef{ + UserRef: gitprovider.UserRef{ + Domain: domain, + UserLogin: spec.Owner, + }, + RepositoryName: spec.RepositoryName, + }, gitprovider.RepositoryInfo{ + DefaultBranch: gitprovider.StringVar("main"), + Visibility: &visibility, + }) + if err != nil { + return fmt.Errorf("failed to create repository: %w", err) + } + + logger.Info("user repository successfully created", "name", repo.Repository().String()) + return nil } diff --git a/pkg/providers/providers.go b/pkg/providers/providers.go index d85e230..367b1ae 100644 --- a/pkg/providers/providers.go +++ b/pkg/providers/providers.go @@ -1,8 +1,13 @@ package providers -import "context" +import ( + "context" + mpasv1alpha1 "github.com/open-component-model/git-controller/apis/mpas/v1alpha1" +) + +// Provider adds the ability to create repositories and pull requests. type Provider interface { - CreateRepository(ctx context.Context, owner, repo string) error + CreateRepository(ctx context.Context, spec mpasv1alpha1.Repository) error CreatePullRequest(ctx context.Context, owner, repo, title, branch, description string) error } From c400f7449a827c2c14b31901221337a476edf49e Mon Sep 17 00:00:00 2001 From: Gergely Brautigam <182850+Skarlso@users.noreply.github.com> Date: Thu, 30 Mar 2023 15:53:19 +0200 Subject: [PATCH 3/3] added adopt or fail options for existing repositories --- apis/delivery/v1alpha1/condition_types.go | 2 +- apis/mpas/v1alpha1/repository_types.go | 28 +++++++-- .../bases/mpas.ocm.software_repositories.yaml | 10 +++- config/samples/mpas_v1alpha1_repository.yaml | 1 - pkg/providers/github/github.go | 60 +++++++++++++++---- 5 files changed, 81 insertions(+), 20 deletions(-) diff --git a/apis/delivery/v1alpha1/condition_types.go b/apis/delivery/v1alpha1/condition_types.go index 52c7167..7bc03cd 100644 --- a/apis/delivery/v1alpha1/condition_types.go +++ b/apis/delivery/v1alpha1/condition_types.go @@ -11,7 +11,7 @@ const ( // SnapshotGetFailedReason is used when the needed snapshot does not exist. SnapshotGetFailedReason = "SnapshotGetFailed" - // AuthenticateGetFailedReason is used when the needed authentication does not exist. + // CredentialsNotFoundReason is used when the needed authentication does not exist. CredentialsNotFoundReason = "CredentialsNotFound" // GitRepositoryPushFailedReason is used when the needed pushing to a git repository failed. diff --git a/apis/mpas/v1alpha1/repository_types.go b/apis/mpas/v1alpha1/repository_types.go index 1580779..93ff1f8 100644 --- a/apis/mpas/v1alpha1/repository_types.go +++ b/apis/mpas/v1alpha1/repository_types.go @@ -20,17 +20,31 @@ type Credentials struct { SecretRef SecretRef `json:"secretRef"` } +// ExistingRepositoryPolicy defines what to do in case a requested repository already exists. +type ExistingRepositoryPolicy string + +var ( + // ExistingRepositoryPolicyAdopt will use the repository if it exists. + ExistingRepositoryPolicyAdopt ExistingRepositoryPolicy = "adopt" + // ExistingRepositoryPolicyFail will fail if the requested repository already exists. + ExistingRepositoryPolicyFail ExistingRepositoryPolicy = "fail" +) + // RepositorySpec defines the desired state of Repository type RepositorySpec struct { - Provider string `json:"provider"` - Owner string `json:"owner"` - RepositoryName string `json:"repositoryName"` - Credentials Credentials `json:"credentials"` + //+required + Provider string `json:"provider"` + //+required + Owner string `json:"owner"` + //+required + RepositoryName string `json:"repositoryName"` + //+required + Credentials Credentials `json:"credentials"` //+optional Interval metav1.Duration `json:"interval,omitempty"` //+optional - //+kubebuilder:default:=internal + //+kubebuilder:default:=private Visibility string `json:"visibility,omitempty"` //+kubebuilder:default:=true IsOrganization bool `json:"isOrganization,omitempty"` @@ -41,6 +55,10 @@ type RepositorySpec struct { //+optional //+kubebuilder:default:=true AutomaticPullRequestCreation bool `json:"automaticPullRequestCreation,omitempty"` + //+optional + //+kubebuilder:default:=adopt + //+kubebuilder:validation:Enum=adopt;fail + ExistingRepositoryPolicy ExistingRepositoryPolicy `json:"existingRepositoryPolicy,omitempty"` } // RepositoryStatus defines the observed state of Repository diff --git a/config/crd/bases/mpas.ocm.software_repositories.yaml b/config/crd/bases/mpas.ocm.software_repositories.yaml index 336cec3..164f136 100644 --- a/config/crd/bases/mpas.ocm.software_repositories.yaml +++ b/config/crd/bases/mpas.ocm.software_repositories.yaml @@ -56,6 +56,14 @@ spec: type: object domain: type: string + existingRepositoryPolicy: + default: adopt + description: ExistingRepositoryPolicy defines what to do in case a + requested repository already exists. + enum: + - adopt + - fail + type: string interval: type: string isOrganization: @@ -72,7 +80,7 @@ spec: repositoryName: type: string visibility: - default: internal + default: private type: string required: - credentials diff --git a/config/samples/mpas_v1alpha1_repository.yaml b/config/samples/mpas_v1alpha1_repository.yaml index 7f72388..d898eee 100644 --- a/config/samples/mpas_v1alpha1_repository.yaml +++ b/config/samples/mpas_v1alpha1_repository.yaml @@ -6,7 +6,6 @@ spec: credentials: secretRef: name: github-creds - interval: 10m owner: Skarlso provider: github repositoryName: new-repository-1 diff --git a/pkg/providers/github/github.go b/pkg/providers/github/github.go index 6bd177e..53da925 100644 --- a/pkg/providers/github/github.go +++ b/pkg/providers/github/github.go @@ -103,21 +103,39 @@ func (c *Client) constructAuthenticationOption(ctx context.Context, obj mpasv1al func (c *Client) createOrganizationRepository(ctx context.Context, gc gitprovider.Client, domain string, visibility gitprovider.RepositoryVisibility, spec mpasv1alpha1.RepositorySpec) error { logger := log.FromContext(ctx) - repo, err := gc.OrgRepositories().Create(ctx, gitprovider.OrgRepositoryRef{ + ref := gitprovider.OrgRepositoryRef{ OrganizationRef: gitprovider.OrganizationRef{ Domain: domain, Organization: spec.Owner, }, RepositoryName: spec.RepositoryName, - }, gitprovider.RepositoryInfo{ + } + info := gitprovider.RepositoryInfo{ DefaultBranch: gitprovider.StringVar("main"), Visibility: &visibility, - }) - if err != nil { - return fmt.Errorf("failed to create repository: %w", err) } - logger.Info("organization repository successfully created", "name", repo.Repository().String()) + switch spec.ExistingRepositoryPolicy { + case mpasv1alpha1.ExistingRepositoryPolicyFail: + if _, err := gc.OrgRepositories().Create(ctx, ref, info); err != nil { + return fmt.Errorf("failed to create repository: %w", err) + } + + logger.Info("successfully created organization repository", "domain", domain, "repository", spec.RepositoryName) + case mpasv1alpha1.ExistingRepositoryPolicyAdopt: + _, created, err := gc.OrgRepositories().Reconcile(ctx, ref, info) + if err != nil { + return fmt.Errorf("failed to reconcile repository: %w", err) + } + + if !created { + logger.Info("using existing repository", "domain", domain, "repository", spec.RepositoryName) + } else { + logger.Info("successfully created organization repository", "domain", domain, "repository", spec.RepositoryName) + } + default: + return fmt.Errorf("unknown repository policy '%s'", spec.ExistingRepositoryPolicy) + } return nil } @@ -125,21 +143,39 @@ func (c *Client) createOrganizationRepository(ctx context.Context, gc gitprovide func (c *Client) createUserRepository(ctx context.Context, gc gitprovider.Client, domain string, visibility gitprovider.RepositoryVisibility, spec mpasv1alpha1.RepositorySpec) error { logger := log.FromContext(ctx) - repo, err := gc.UserRepositories().Create(ctx, gitprovider.UserRepositoryRef{ + ref := gitprovider.UserRepositoryRef{ UserRef: gitprovider.UserRef{ Domain: domain, UserLogin: spec.Owner, }, RepositoryName: spec.RepositoryName, - }, gitprovider.RepositoryInfo{ + } + info := gitprovider.RepositoryInfo{ DefaultBranch: gitprovider.StringVar("main"), Visibility: &visibility, - }) - if err != nil { - return fmt.Errorf("failed to create repository: %w", err) } - logger.Info("user repository successfully created", "name", repo.Repository().String()) + switch spec.ExistingRepositoryPolicy { + case mpasv1alpha1.ExistingRepositoryPolicyFail: + if _, err := gc.UserRepositories().Create(ctx, ref, info); err != nil { + return fmt.Errorf("failed to create repository: %w", err) + } + + logger.Info("successfully created user repository", "domain", domain, "repository", spec.RepositoryName) + case mpasv1alpha1.ExistingRepositoryPolicyAdopt: + _, created, err := gc.UserRepositories().Reconcile(ctx, ref, info) + if err != nil { + return fmt.Errorf("failed to reconcile repository: %w", err) + } + + if !created { + logger.Info("using existing repository", "domain", domain, "repository", spec.RepositoryName) + } else { + logger.Info("successfully created user repository", "domain", domain, "repository", spec.RepositoryName) + } + default: + return fmt.Errorf("unknown repository policy '%s'", spec.ExistingRepositoryPolicy) + } return nil }