From 4c61ae7dfa0b970c73d9de203d40ea21618802b8 Mon Sep 17 00:00:00 2001 From: Damien Dassieu Date: Thu, 30 May 2024 00:43:45 +0200 Subject: [PATCH] Manage configuration inheritance + refactor GitRemote configuration specification --- Makefile | 11 +- api/v1/gitremote_types.go | 60 ++- api/v1/gitremote_webhook.go | 5 - api/v1/resourcesinterceptor_types.go | 40 +- api/v1/resourcesinterceptor_webhook.go | 11 +- api/v1/zz_generated.deepcopy.go | 68 +-- .../git-providers-configuration.yaml | 11 + cmd/main.go | 5 +- .../crd/bases/kgio.dams.kgio_gitremotes.yaml | 94 +++- .../kgio.dams.kgio_resourcesinterceptors.yaml | 436 ++++-------------- config/manager/git-provider_configmap.yaml | 18 - config/manager/kustomization.yaml | 3 +- config/samples/kgio_v1_gitremote.yaml | 4 +- config/samples/kgio_v1_gitremote2.yaml | 17 - config/samples/kgio_v1_gitremote3.yaml | 18 - config/samples/kgio_v1_gituserbinding.yaml | 2 +- .../samples/kgio_v1_resourcesinterceptor.yaml | 8 +- .../kgio_v1_resourcesinterceptor2.yaml | 27 -- config/webhook/gen-certs-serv-cli.sh | 57 +++ .../controller/dynamic_webhook_handlers.go | 2 + internal/controller/git_pusher.go | 2 +- internal/controller/gitremote_controller.go | 240 ++++++---- .../controller/gituserbinding_controller.go | 10 +- .../resourcesinterceptor_controller.go | 39 +- .../controller/webhook_request_checker.go | 90 +++- 25 files changed, 604 insertions(+), 674 deletions(-) create mode 100644 chart/0.0.1/templates/controller/git-providers-configuration.yaml delete mode 100644 config/manager/git-provider_configmap.yaml delete mode 100644 config/samples/kgio_v1_gitremote2.yaml delete mode 100644 config/samples/kgio_v1_gitremote3.yaml delete mode 100644 config/samples/kgio_v1_resourcesinterceptor2.yaml create mode 100755 config/webhook/gen-certs-serv-cli.sh diff --git a/Makefile b/Makefile index ab917d1..6aef660 100644 --- a/Makefile +++ b/Makefile @@ -53,7 +53,7 @@ dev-deploy: # Launch dev env on the cluster kind load docker-image $(IMAGE) --name dev-cluster cd $(WEBHOOK_PATH) && cp manifests.yaml manifests.yaml.temp cd $(WEBHOOK_PATH) && cp secret.yaml secret.yaml.temp - make deploy-dev-nr IMG=$(IMAGE) + make deploy IMG=$(IMAGE) # .PHONY: dev-run # dev-run: # Deploy fake webhook & launch dev env in cli @@ -121,7 +121,7 @@ build: manifests generate fmt vet ## Build manager binary. .PHONY: run run: manifests generate fmt vet ## Run a controller from your host. - export MANAGER_NAMESPACE=system DEV=true && go run ./cmd/main.go + export MANAGER_NAMESPACE=operator-system DEV=true && go run ./cmd/main.go # If you wish to build the manager image targeting other platforms you can use the --platform flag. # (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it. @@ -179,15 +179,10 @@ install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~ uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. $(KUSTOMIZE) build config/crd | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f - -.PHONY: deploy-dev-nr -deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. - cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} - cd $(WEBHOOK_PATH) && ./cert-injector.sh manifests.yaml - $(KUSTOMIZE) build config/default | $(KUBECTL) apply -f - - .PHONY: deploy deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} + cd $(WEBHOOK_PATH) && ./cert-injector.sh manifests.yaml $(KUSTOMIZE) build config/default | $(KUBECTL) apply -f - .PHONY: undeploy diff --git a/api/v1/gitremote_types.go b/api/v1/gitremote_types.go index 296b3f1..534a3c3 100644 --- a/api/v1/gitremote_types.go +++ b/api/v1/gitremote_types.go @@ -30,40 +30,50 @@ type GitRemoteSpec struct { GitBaseDomainFQDN string `json:"gitBaseDomainFQDN"` // +optional - TestAuthentication bool `json:"testAuthentication,omitempty"` + CustomGitProviderConfigRef corev1.ObjectReference `json:"customGitProviderConfigRef,omitempty"` // +optional - GitProvider string `json:"gitProvider,omitempty"` + TestAuthentication bool `json:"testAuthentication,omitempty"` +} +type GitProviderConfiguration struct { // +optional - CustomGitProvider GitProvider `json:"customGitProvider,omitempty"` - + Inherited bool `json:"inherited,omitempty" yaml:"inherited,omitempty"` + //+ optional + AuthenticationEndpoint string `json:"authenticationEndpoint,omitempty" yaml:"authenticationEndpoint,omitempty"` // +optional - RemoteConfiguration RemoteConfiguration `json:"remoteConfiguration,omitempty"` + CaBundle string `json:"caBundle,omitempty" yaml:"caBundle,omitempty"` + // +optional + InsecureSkipTlsVerify bool `json:"insecureSkipTlsVerify,omitempty" yaml:"insecureSkipTlsVerify,omitempty"` } -type RemoteConfiguration struct { - // +optional - CaBundle string `json:"caBundle,omitempty"` +type GitRemoteConnexionStatus struct { + Status GitRemoteConnexionStatusReason `json:"status,omitempty"` // +optional - InsecureSkipTlsVerify bool `json:"insecureSkipTlsVerify,omitempty"` + Details string `json:"details,omitempty"` } -type GitProvider struct { - FQDN string `json:"fqdn"` - Authentication string `json:"authentication"` -} +type GitRemoteConnexionStatusReason string -type GitRemoteConnexionStatus string +const ( + GitConnected GitRemoteConnexionStatusReason = "Connected" + GitUnauthorized GitRemoteConnexionStatusReason = "Unauthorized: bad credentials" + GitForbidden GitRemoteConnexionStatusReason = "Forbidden : Not enough permission" + GitNotFound GitRemoteConnexionStatusReason = "Not found: the git server is not found" + GitServerError GitRemoteConnexionStatusReason = "Server error: a server error happened" + GitUnexpectedStatus GitRemoteConnexionStatusReason = "Unexpected response status code" + GitNotConnected GitRemoteConnexionStatusReason = "Not Connected" + GitUnsupported GitRemoteConnexionStatusReason = "Unsupported Git provider" + GitConfigNotFound GitRemoteConnexionStatusReason = "Git provider ConfigMap not found" + GitConfigParseError GitRemoteConnexionStatusReason = "Failed to parse the git provider ConfigMap" +) + +type SecretBoundStatus string const ( - Connected GitRemoteConnexionStatus = "Connected" - Unauthorized GitRemoteConnexionStatus = "Unauthorized: bad credentials" - Forbidden GitRemoteConnexionStatus = "Forbidden : Not enough permission" - NotFound GitRemoteConnexionStatus = "Not found: the git repository is not found" - ServerError GitRemoteConnexionStatus = "Server error: a server error happened" - UnexpectedStatus GitRemoteConnexionStatus = "Unexpected response status code" - Disconnected GitRemoteConnexionStatus = "Disconnected: The secret has been deleted" + SecretBound SecretBoundStatus = "Secret bound" + SecretNotFound SecretBoundStatus = "Secret not found" + SecretWrongType SecretBoundStatus = "Secret type is not set to BasicAuth" ) // GitRemoteStatus defines the observed state of GitRemote @@ -72,10 +82,16 @@ type GitRemoteStatus struct { ConnexionStatus GitRemoteConnexionStatus `json:"connexionStatus,omitempty"` // +optional - GitUserID string `json:"gitUserID,omitempty"` + GitUser string `json:"gitUser,omitempty"` // +optional LastAuthTime metav1.Time `json:"lastAuthTime,omitempty"` + + // +optional + SecretBoundStatus SecretBoundStatus `json:"secretBoundStatus,omitempty"` + + // +optional + GitProviderConfiguration GitProviderConfiguration `json:"gitProviderConfiguration,omitempty"` } //+kubebuilder:object:root=true diff --git a/api/v1/gitremote_webhook.go b/api/v1/gitremote_webhook.go index d5a4183..478bab4 100644 --- a/api/v1/gitremote_webhook.go +++ b/api/v1/gitremote_webhook.go @@ -44,11 +44,6 @@ var _ webhook.Validator = &GitRemote{} func (r *GitRemoteSpec) ValidateGitRemoteSpec() field.ErrorList { var errors field.ErrorList - // The GitProvider must be set if the TestAuthentication field is setted to true - if r.TestAuthentication && r.GitProvider == "" { - errors = append(errors, field.Required(field.NewPath("gitProvider"), "should be set when testAuthentication is set to true")) - } - return errors } diff --git a/api/v1/resourcesinterceptor_types.go b/api/v1/resourcesinterceptor_types.go index 869670a..d57576c 100644 --- a/api/v1/resourcesinterceptor_types.go +++ b/api/v1/resourcesinterceptor_types.go @@ -20,6 +20,7 @@ import ( "strings" admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + authenticationv1 "k8s.io/api/authentication/v1" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -136,15 +137,22 @@ type NamespaceScopedObject struct { Name string `json:"name"` } +type JsonGVRN struct { + Group string `json:"group"` + Version string `json:"version"` + Resource string `json:"resource"` + Name string `json:"name"` +} + type LastBypassedObjectState struct { // +optional LastBypassedObjectTime metav1.Time `json:"lastBypassObjectTime,omitempty"` // +optional - LastBypassedObjectSubject rbacv1.Subject `json:"lastBypassObjectSubject,omitempty"` + LastBypassedObjectUserInfo authenticationv1.UserInfo `json:"lastBypassObjectUserInfo,omitempty"` // +optional - LastBypassedObject NamespaceScopedObject `json:"lastBypassObject,omitempty"` + LastBypassedObject JsonGVRN `json:"lastBypassObject,omitempty"` } type LastInterceptedObjectState struct { @@ -152,10 +160,10 @@ type LastInterceptedObjectState struct { LastInterceptedObjectTime metav1.Time `json:"lastInterceptedObjectTime,omitempty"` // +optional - LastInterceptedObjectKubernetesUser rbacv1.Subject `json:"lastInterceptedObjectKubernetesUser,omitempty"` + LastInterceptedObjectUserInfo authenticationv1.UserInfo `json:"lastInterceptedObjectUserInfo,omitempty"` // +optional - LastInterceptedObject NamespaceScopedObject `json:"lastInterceptedObject,omitempty"` + LastInterceptedObject JsonGVRN `json:"lastInterceptedObject,omitempty"` } type LastPushedObjectState struct { @@ -163,25 +171,31 @@ type LastPushedObjectState struct { LastPushedObjectTime metav1.Time `json:"lastPushedObjectTime,omitempty"` // +optional - LastPushedGitUserID string `json:"lastPushedGitUserID,omitempty"` + LastPushedGitUser string `json:"lastPushedGitUser,omitempty"` + + // +optional + LastPushedObjectGitRepo string `json:"lastPushedObjectGitRepo,omitempty"` // +optional LastPushedObjectGitPath string `json:"lastPushedObjectGitPath,omitempty"` // +optional - LastPushedObject NamespaceScopedObject `json:"lastPushedObject,omitempty"` + LastPushedObjectGitCommitHash string `json:"lastPushedObjectCommitHash,omitempty"` // +optional - LastPushedObjectStatus PushedObjectStatus `json:"lastPushedObjectState,omitempty"` + LastPushedObject JsonGVRN `json:"lastPushedObject,omitempty"` + + // +optional + LastPushedObjectStatus string `json:"lastPushedObjectState,omitempty"` } -type PushedObjectStatus string +// type PushedObjectStatus string -const ( - Pushed PushedObjectStatus = "Resource correctly pushed" - PushNotAllowed PushedObjectStatus = "Error: Push permission is not allowed on this git repository for this user" - NetworkError PushedObjectStatus = "Error: A network error occured" -) +// const ( +// Pushed PushedObjectStatus = "Resource correctly pushed" +// PushNotAllowed PushedObjectStatus = "Error: Push permission is not allowed on this git repository for this user" +// NetworkError PushedObjectStatus = "Error: A network error occured" +// ) // ResourcesInterceptorStatus defines the observed state of ResourcesInterceptor type ResourcesInterceptorStatus struct { diff --git a/api/v1/resourcesinterceptor_webhook.go b/api/v1/resourcesinterceptor_webhook.go index d33b933..79e88a8 100644 --- a/api/v1/resourcesinterceptor_webhook.go +++ b/api/v1/resourcesinterceptor_webhook.go @@ -54,14 +54,19 @@ func (r *ResourcesInterceptorSpec) ValidateResourcesInterceptorSpec() field.Erro errors = append(errors, field.Required(field.NewPath("defaultUserBind"), "should be set when defaultUnauthorizedUserMode is set to \"UseDefaultUserBind\"")) } - // Validate DefaultBlockAppliedMessage only exists if CommitProcess is set to CommitApply + // Validate DefaultBlockAppliedMessage only exists if CommitProcess is set to ApplyCommit if r.DefaultBlockAppliedMessage != "" && r.CommitProcess != "CommitApply" { errors = append(errors, field.Forbidden(field.NewPath("defaultBlockAppliedMessage"), "should not be set if .spec.commitApply is not set to \"CommitApply\"")) } + // Validate that CommitProcess is either CommitApply or CommitOnly + if r.CommitProcess != "CommitOnly" && r.CommitProcess != "CommitApply" { + errors = append(errors, field.Forbidden(field.NewPath("commitProcess"), "should be set to \"CommitApply\" or \"CommitOnly\"")) + } + // For Included and Excluded Resources. Validate that if a name is specified for a resource, then the concerned resource is not referenced without the name - errors = append(errors, r.validateFineGrainedIncludedResources(ParsegvrnList(NSRPstoNSRs(r.IncludedResources)))...) - errors = append(errors, r.validateFineGrainedExcludedResources(ParsegvrnList(r.ExcludedResources))...) + // errors = append(errors, r.validateFineGrainedIncludedResources(ParsegvrnList(NSRPstoNSRs(r.IncludedResources)))...) + // errors = append(errors, r.validateFineGrainedExcludedResources(ParsegvrnList(r.ExcludedResources))...) // Validate the ExcludedFields to ensure that it is a YAML path for _, fieldPath := range r.ExcludedFields { diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index f803e6b..4343cd9 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -29,16 +29,16 @@ import ( ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *GitProvider) DeepCopyInto(out *GitProvider) { +func (in *GitProviderConfiguration) DeepCopyInto(out *GitProviderConfiguration) { *out = *in } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitProvider. -func (in *GitProvider) DeepCopy() *GitProvider { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitProviderConfiguration. +func (in *GitProviderConfiguration) DeepCopy() *GitProviderConfiguration { if in == nil { return nil } - out := new(GitProvider) + out := new(GitProviderConfiguration) in.DeepCopyInto(out) return out } @@ -70,6 +70,21 @@ func (in *GitRemote) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GitRemoteConnexionStatus) DeepCopyInto(out *GitRemoteConnexionStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitRemoteConnexionStatus. +func (in *GitRemoteConnexionStatus) DeepCopy() *GitRemoteConnexionStatus { + if in == nil { + return nil + } + out := new(GitRemoteConnexionStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GitRemoteList) DeepCopyInto(out *GitRemoteList) { *out = *in @@ -106,8 +121,7 @@ func (in *GitRemoteList) DeepCopyObject() runtime.Object { func (in *GitRemoteSpec) DeepCopyInto(out *GitRemoteSpec) { *out = *in out.SecretRef = in.SecretRef - out.CustomGitProvider = in.CustomGitProvider - out.RemoteConfiguration = in.RemoteConfiguration + out.CustomGitProviderConfigRef = in.CustomGitProviderConfigRef } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitRemoteSpec. @@ -123,7 +137,9 @@ func (in *GitRemoteSpec) DeepCopy() *GitRemoteSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *GitRemoteStatus) DeepCopyInto(out *GitRemoteStatus) { *out = *in + out.ConnexionStatus = in.ConnexionStatus in.LastAuthTime.DeepCopyInto(&out.LastAuthTime) + out.GitProviderConfiguration = in.GitProviderConfiguration } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitRemoteStatus. @@ -316,12 +332,27 @@ func (in *GroupVersionResourceNamePath) DeepCopy() *GroupVersionResourceNamePath return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *JsonGVRN) DeepCopyInto(out *JsonGVRN) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JsonGVRN. +func (in *JsonGVRN) DeepCopy() *JsonGVRN { + if in == nil { + return nil + } + out := new(JsonGVRN) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *LastBypassedObjectState) DeepCopyInto(out *LastBypassedObjectState) { *out = *in in.LastBypassedObjectTime.DeepCopyInto(&out.LastBypassedObjectTime) - out.LastBypassedObjectSubject = in.LastBypassedObjectSubject - in.LastBypassedObject.DeepCopyInto(&out.LastBypassedObject) + in.LastBypassedObjectUserInfo.DeepCopyInto(&out.LastBypassedObjectUserInfo) + out.LastBypassedObject = in.LastBypassedObject } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LastBypassedObjectState. @@ -338,8 +369,8 @@ func (in *LastBypassedObjectState) DeepCopy() *LastBypassedObjectState { func (in *LastInterceptedObjectState) DeepCopyInto(out *LastInterceptedObjectState) { *out = *in in.LastInterceptedObjectTime.DeepCopyInto(&out.LastInterceptedObjectTime) - out.LastInterceptedObjectKubernetesUser = in.LastInterceptedObjectKubernetesUser - in.LastInterceptedObject.DeepCopyInto(&out.LastInterceptedObject) + in.LastInterceptedObjectUserInfo.DeepCopyInto(&out.LastInterceptedObjectUserInfo) + out.LastInterceptedObject = in.LastInterceptedObject } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LastInterceptedObjectState. @@ -356,7 +387,7 @@ func (in *LastInterceptedObjectState) DeepCopy() *LastInterceptedObjectState { func (in *LastPushedObjectState) DeepCopyInto(out *LastPushedObjectState) { *out = *in in.LastPushedObjectTime.DeepCopyInto(&out.LastPushedObjectTime) - in.LastPushedObject.DeepCopyInto(&out.LastPushedObject) + out.LastPushedObject = in.LastPushedObject } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LastPushedObjectState. @@ -490,21 +521,6 @@ func (in *NamespaceScopedResourcesPath) DeepCopy() *NamespaceScopedResourcesPath return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RemoteConfiguration) DeepCopyInto(out *RemoteConfiguration) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RemoteConfiguration. -func (in *RemoteConfiguration) DeepCopy() *RemoteConfiguration { - if in == nil { - return nil - } - out := new(RemoteConfiguration) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ResourcesInterceptor) DeepCopyInto(out *ResourcesInterceptor) { *out = *in diff --git a/chart/0.0.1/templates/controller/git-providers-configuration.yaml b/chart/0.0.1/templates/controller/git-providers-configuration.yaml new file mode 100644 index 0000000..fc3b668 --- /dev/null +++ b/chart/0.0.1/templates/controller/git-providers-configuration.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: git-providers-configuration +data: + bitbucket.org: | + authenticationEndpoint: https://api.bitbucket.org/2.0/user + github.com: | + authenticationEndpoint: https://api.github.com/user + gitlab.com: | + authenticationEndpoint: https://gitlab.com/api/v4/user \ No newline at end of file diff --git a/cmd/main.go b/cmd/main.go index 8b26bb3..343bbeb 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -142,8 +142,9 @@ func main() { os.Exit(1) } if err = (&controller.ResourcesInterceptorReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor("resourcesinterceptor-controller"), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "ResourcesInterceptor") os.Exit(1) diff --git a/config/crd/bases/kgio.dams.kgio_gitremotes.yaml b/config/crd/bases/kgio.dams.kgio_gitremotes.yaml index e3a5b79..83a6df7 100644 --- a/config/crd/bases/kgio.dams.kgio_gitremotes.yaml +++ b/config/crd/bases/kgio.dams.kgio_gitremotes.yaml @@ -39,29 +39,71 @@ spec: spec: description: GitRemoteSpec defines the desired state of GitRemote properties: - customGitProvider: + customGitProviderConfigRef: + description: |- + ObjectReference contains enough information to let you inspect or modify the referred object. + --- + New uses of this type are discouraged because of difficulty describing its usage when embedded in APIs. + 1. Ignored fields. It includes many fields which are not generally honored. For instance, ResourceVersion and FieldPath are both very rarely valid in actual usage. + 2. Invalid usage help. It is impossible to add specific help for individual usage. In most embedded usages, there are particular + restrictions like, "must refer only to types A and B" or "UID not honored" or "name must be restricted". + Those cannot be well described when embedded. + 3. Inconsistent validation. Because the usages are different, the validation rules are different by usage, which makes it hard for users to predict what will happen. + 4. The fields are both imprecise and overly precise. Kind is not a precise mapping to a URL. This can produce ambiguity + during interpretation and require a REST mapping. In most cases, the dependency is on the group,resource tuple + and the version of the actual struct is irrelevant. + 5. We cannot easily change it. Because this type is embedded in many locations, updates to this type + will affect numerous schemas. Don't make new APIs embed an underspecified API type they do not control. + + + Instead of using this type, create a locally provided and used type that is well-focused on your reference. + For example, ServiceReferences for admission registration: https://github.com/kubernetes/api/blob/release-1.17/admissionregistration/v1/types.go#L533 . properties: - authentication: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: |- + If referring to a piece of an object instead of an entire object, this string + should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within a pod, this would take on a value like: + "spec.containers{name}" (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only to have some well-defined way of + referencing a part of an object. + TODO: this design is not final and this field is subject to change in the future. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + type: string + resourceVersion: + description: |- + Specific resourceVersion to which this reference is made, if any. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency type: string - fqdn: + uid: + description: |- + UID of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids type: string - required: - - authentication - - fqdn type: object + x-kubernetes-map-type: atomic email: type: string gitBaseDomainFQDN: type: string - gitProvider: - type: string - remoteConfiguration: - properties: - caBundle: - type: string - insecureSkipTlsVerify: - type: boolean - type: object secretRef: description: |- SecretReference represents a Secret Reference. It has enough information to retrieve secret @@ -88,12 +130,30 @@ spec: description: GitRemoteStatus defines the observed state of GitRemote properties: connexionStatus: - type: string - gitUserID: + properties: + details: + type: string + status: + type: string + type: object + gitProviderConfiguration: + properties: + authenticationEndpoint: + type: string + caBundle: + type: string + inherited: + type: boolean + insecureSkipTlsVerify: + type: boolean + type: object + gitUser: type: string lastAuthTime: format: date-time type: string + secretBoundStatus: + type: string type: object type: object served: true diff --git a/config/crd/bases/kgio.dams.kgio_resourcesinterceptors.yaml b/config/crd/bases/kgio.dams.kgio_resourcesinterceptors.yaml index ea303a9..6248ce0 100644 --- a/config/crd/bases/kgio.dams.kgio_resourcesinterceptors.yaml +++ b/config/crd/bases/kgio.dams.kgio_resourcesinterceptors.yaml @@ -289,408 +289,132 @@ spec: properties: lastBypassObject: properties: - apiGroups: - description: |- - APIGroup contains the name, the supported versions, and the preferred version - of a group. - 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 - name: - description: name is the name of the group. - type: string - preferredVersion: - description: |- - preferredVersion is the version preferred by the API server, which - probably is the storage version. - properties: - groupVersion: - description: groupVersion specifies the API group - and version in the form "group/version" - type: string - version: - description: |- - version specifies the version in the form of "version". This is to save - the clients the trouble of splitting the GroupVersion. - type: string - required: - - groupVersion - - version - type: object - serverAddressByClientCIDRs: - description: |- - a map of client CIDR to server address that is serving this group. - This is to help clients reach servers in the most network-efficient way possible. - Clients can use the appropriate server address as per the CIDR that they match. - In case of multiple matches, clients should use the longest matching CIDR. - The server returns only those CIDRs that it thinks that the client can match. - For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP. - Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP. - items: - description: ServerAddressByClientCIDR helps the client - to determine the server address that they should use, - depending on the clientCIDR that they match. - properties: - clientCIDR: - description: The CIDR with which clients can match - their IP to figure out the server address that - they should use. - type: string - serverAddress: - description: |- - Address of this server, suitable for a client that matches the above CIDR. - This can be a hostname, hostname:port, IP or IP:port. - type: string - required: - - clientCIDR - - serverAddress - type: object - type: array - versions: - description: versions are the versions supported in this - group. - items: - description: |- - GroupVersion contains the "group/version" and "version" string of a version. - It is made a struct to keep extensibility. - properties: - groupVersion: - description: groupVersion specifies the API group - and version in the form "group/version" - type: string - version: - description: |- - version specifies the version in the form of "version". This is to save - the clients the trouble of splitting the GroupVersion. - type: string - required: - - groupVersion - - version - type: object - type: array - required: - - name - - versions - type: object - apiVersions: + group: type: string name: type: string - resources: + resource: + type: string + version: type: string required: - - apiGroups - - apiVersions + - group - name - - resources + - resource + - version type: object - lastBypassObjectSubject: + lastBypassObjectTime: + format: date-time + type: string + lastBypassObjectUserInfo: description: |- - Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, - or a value for non-objects such as user and group names. + UserInfo holds the information about the user needed to implement the + user.Info interface. properties: - apiGroup: + extra: + additionalProperties: + description: ExtraValue masks the value so protobuf can + generate + items: + type: string + type: array + description: Any additional information provided by the authenticator. + type: object + groups: + description: The names of groups this user is a part of. + items: + type: string + type: array + uid: description: |- - APIGroup holds the API group of the referenced subject. - Defaults to "" for ServiceAccount subjects. - Defaults to "rbac.authorization.k8s.io" for User and Group subjects. + A unique value that identifies this user across time. If this user is + deleted and another user by the same name is added, they will have + different UIDs. type: string - kind: - description: |- - Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". - If the Authorizer does not recognized the kind value, the Authorizer should report an error. + username: + description: The name that uniquely identifies this user among + all active users. type: string - name: - description: Name of the object being referenced. - type: string - namespace: - description: |- - Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty - the Authorizer should report an error. - type: string - required: - - kind - - name type: object - x-kubernetes-map-type: atomic - lastBypassObjectTime: - format: date-time - type: string type: object lastInterceptedObjectState: properties: lastInterceptedObject: properties: - apiGroups: - description: |- - APIGroup contains the name, the supported versions, and the preferred version - of a group. - 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 - name: - description: name is the name of the group. - type: string - preferredVersion: - description: |- - preferredVersion is the version preferred by the API server, which - probably is the storage version. - properties: - groupVersion: - description: groupVersion specifies the API group - and version in the form "group/version" - type: string - version: - description: |- - version specifies the version in the form of "version". This is to save - the clients the trouble of splitting the GroupVersion. - type: string - required: - - groupVersion - - version - type: object - serverAddressByClientCIDRs: - description: |- - a map of client CIDR to server address that is serving this group. - This is to help clients reach servers in the most network-efficient way possible. - Clients can use the appropriate server address as per the CIDR that they match. - In case of multiple matches, clients should use the longest matching CIDR. - The server returns only those CIDRs that it thinks that the client can match. - For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP. - Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP. - items: - description: ServerAddressByClientCIDR helps the client - to determine the server address that they should use, - depending on the clientCIDR that they match. - properties: - clientCIDR: - description: The CIDR with which clients can match - their IP to figure out the server address that - they should use. - type: string - serverAddress: - description: |- - Address of this server, suitable for a client that matches the above CIDR. - This can be a hostname, hostname:port, IP or IP:port. - type: string - required: - - clientCIDR - - serverAddress - type: object - type: array - versions: - description: versions are the versions supported in this - group. - items: - description: |- - GroupVersion contains the "group/version" and "version" string of a version. - It is made a struct to keep extensibility. - properties: - groupVersion: - description: groupVersion specifies the API group - and version in the form "group/version" - type: string - version: - description: |- - version specifies the version in the form of "version". This is to save - the clients the trouble of splitting the GroupVersion. - type: string - required: - - groupVersion - - version - type: object - type: array - required: - - name - - versions - type: object - apiVersions: + group: type: string name: type: string - resources: + resource: + type: string + version: type: string required: - - apiGroups - - apiVersions + - group - name - - resources + - resource + - version type: object - lastInterceptedObjectKubernetesUser: + lastInterceptedObjectTime: + format: date-time + type: string + lastInterceptedObjectUserInfo: description: |- - Subject contains a reference to the object or user identities a role binding applies to. This can either hold a direct API object reference, - or a value for non-objects such as user and group names. + UserInfo holds the information about the user needed to implement the + user.Info interface. properties: - apiGroup: - description: |- - APIGroup holds the API group of the referenced subject. - Defaults to "" for ServiceAccount subjects. - Defaults to "rbac.authorization.k8s.io" for User and Group subjects. - type: string - kind: + extra: + additionalProperties: + description: ExtraValue masks the value so protobuf can + generate + items: + type: string + type: array + description: Any additional information provided by the authenticator. + type: object + groups: + description: The names of groups this user is a part of. + items: + type: string + type: array + uid: description: |- - Kind of object being referenced. Values defined by this API group are "User", "Group", and "ServiceAccount". - If the Authorizer does not recognized the kind value, the Authorizer should report an error. - type: string - name: - description: Name of the object being referenced. + A unique value that identifies this user across time. If this user is + deleted and another user by the same name is added, they will have + different UIDs. type: string - namespace: - description: |- - Namespace of the referenced object. If the object kind is non-namespace, such as "User" or "Group", and this value is not empty - the Authorizer should report an error. + username: + description: The name that uniquely identifies this user among + all active users. type: string - required: - - kind - - name type: object - x-kubernetes-map-type: atomic - lastInterceptedObjectTime: - format: date-time - type: string type: object lastPushedObjectState: properties: - lastPushedGitUserID: + lastPushedGitUser: type: string lastPushedObject: properties: - apiGroups: - description: |- - APIGroup contains the name, the supported versions, and the preferred version - of a group. - 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 - name: - description: name is the name of the group. - type: string - preferredVersion: - description: |- - preferredVersion is the version preferred by the API server, which - probably is the storage version. - properties: - groupVersion: - description: groupVersion specifies the API group - and version in the form "group/version" - type: string - version: - description: |- - version specifies the version in the form of "version". This is to save - the clients the trouble of splitting the GroupVersion. - type: string - required: - - groupVersion - - version - type: object - serverAddressByClientCIDRs: - description: |- - a map of client CIDR to server address that is serving this group. - This is to help clients reach servers in the most network-efficient way possible. - Clients can use the appropriate server address as per the CIDR that they match. - In case of multiple matches, clients should use the longest matching CIDR. - The server returns only those CIDRs that it thinks that the client can match. - For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP. - Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP. - items: - description: ServerAddressByClientCIDR helps the client - to determine the server address that they should use, - depending on the clientCIDR that they match. - properties: - clientCIDR: - description: The CIDR with which clients can match - their IP to figure out the server address that - they should use. - type: string - serverAddress: - description: |- - Address of this server, suitable for a client that matches the above CIDR. - This can be a hostname, hostname:port, IP or IP:port. - type: string - required: - - clientCIDR - - serverAddress - type: object - type: array - versions: - description: versions are the versions supported in this - group. - items: - description: |- - GroupVersion contains the "group/version" and "version" string of a version. - It is made a struct to keep extensibility. - properties: - groupVersion: - description: groupVersion specifies the API group - and version in the form "group/version" - type: string - version: - description: |- - version specifies the version in the form of "version". This is to save - the clients the trouble of splitting the GroupVersion. - type: string - required: - - groupVersion - - version - type: object - type: array - required: - - name - - versions - type: object - apiVersions: + group: type: string name: type: string - resources: + resource: + type: string + version: type: string required: - - apiGroups - - apiVersions + - group - name - - resources + - resource + - version type: object + lastPushedObjectCommitHash: + type: string lastPushedObjectGitPath: type: string + lastPushedObjectGitRepo: + type: string lastPushedObjectState: type: string lastPushedObjectTime: diff --git a/config/manager/git-provider_configmap.yaml b/config/manager/git-provider_configmap.yaml deleted file mode 100644 index cbb9e0e..0000000 --- a/config/manager/git-provider_configmap.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: v1 -kind: ConfigMap -metadata: - name: git-providers-endpoints - namespace: system -data: - github: | - fqdn: github.com - authentication: https://api.github.com/user - gitlab: | - fqdn: gitlab.com - authentication: https://gitlab.com/api/v4/user - bitbucket: | - fqdn: bitbucket.org - authentication: https://api.bitbucket.org/2.0/user - # custom: | - # fqdn: custom_fqdn - # authentication: custom_fqdn/api_endpoint_to_check_if_user_is_connected diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 7b93197..1fa04cd 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -1,10 +1,9 @@ resources: - manager_namespace.yaml - manager.yaml -- git-provider_configmap.yaml apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization images: - name: controller newName: dams.com/op - newTag: oui + newTag: dev diff --git a/config/samples/kgio_v1_gitremote.yaml b/config/samples/kgio_v1_gitremote.yaml index 9259f29..93d9987 100644 --- a/config/samples/kgio_v1_gitremote.yaml +++ b/config/samples/kgio_v1_gitremote.yaml @@ -10,8 +10,8 @@ metadata: name: gitremote-sample namespace: test spec: - gitBaseDomainFQDN: "git.inpt.fr" + gitBaseDomainFQDN: "gitlab.com" testAuthentication: true - #gitProvider: "gitlab" + email: dassieu.damien@gmail.com secretRef: name: secret-basic-auth diff --git a/config/samples/kgio_v1_gitremote2.yaml b/config/samples/kgio_v1_gitremote2.yaml deleted file mode 100644 index 235e86b..0000000 --- a/config/samples/kgio_v1_gitremote2.yaml +++ /dev/null @@ -1,17 +0,0 @@ -apiVersion: kgio.dams.kgio/v1 -kind: GitRemote -metadata: - labels: - app.kubernetes.io/name: gitremote - app.kubernetes.io/instance: gitremote-sample - app.kubernetes.io/part-of: operator - app.kubernetes.io/managed-by: kustomize - app.kubernetes.io/created-by: operator - name: gitremote-sample2 - namespace: test -spec: - gitBaseDomainFQDN: "git.inpt.fr" - testAuthentication: true - gitProvider: "gitlab" - secretRef: - name: secret-basic-auth1 diff --git a/config/samples/kgio_v1_gitremote3.yaml b/config/samples/kgio_v1_gitremote3.yaml deleted file mode 100644 index d60f20b..0000000 --- a/config/samples/kgio_v1_gitremote3.yaml +++ /dev/null @@ -1,18 +0,0 @@ -apiVersion: kgio.dams.kgio/v1 -kind: GitRemote -metadata: - labels: - app.kubernetes.io/name: gitremote - app.kubernetes.io/instance: gitremote-sample - app.kubernetes.io/part-of: operator - app.kubernetes.io/managed-by: kustomize - app.kubernetes.io/created-by: operator - name: gitremote-sample3 - namespace: test -spec: - gitBaseDomainFQDN: "gitlab.com" - testAuthentication: true - gitProvider: "gitlab" - email: damien.dassieu@orange.com - secretRef: - name: secret-basic-auth1 diff --git a/config/samples/kgio_v1_gituserbinding.yaml b/config/samples/kgio_v1_gituserbinding.yaml index c4fa987..df57a0b 100644 --- a/config/samples/kgio_v1_gituserbinding.yaml +++ b/config/samples/kgio_v1_gituserbinding.yaml @@ -14,6 +14,6 @@ spec: kind: User name: kubernetes-admin remoteRefs: - - name: gitremote-sample3 + - name: gitremote-sample #- name: another-one #- name: gitremote-sample2 diff --git a/config/samples/kgio_v1_resourcesinterceptor.yaml b/config/samples/kgio_v1_resourcesinterceptor.yaml index 807c430..b6b1b39 100644 --- a/config/samples/kgio_v1_resourcesinterceptor.yaml +++ b/config/samples/kgio_v1_resourcesinterceptor.yaml @@ -13,14 +13,14 @@ spec: remoteRepository: https://gitlab.com/dassieu.damien/kgio-demo.git branch: second-main commitMode: Commit - commitProcess: CommitApply + commitProcess: CommitOnly operations: - CREATE - UPDATE - DELETE bypassInterceptionSubjects: - #- name: kubernetes-admin - # kind: User + - name: kubernetes-admin + kind: User authorizedUsers: - name: gituserbinding-sample defaultUnauthorizedUserMode: Block @@ -34,5 +34,5 @@ spec: - apiGroups: ["", "networking.k8s.io"] apiVersions: ["v1"] resources: ["configmaps", "ingresses", "pods"] - names: ["secret-basic-auth", "git-providers-endpoints"] + names: ["secret-basic-auth1", "random-cm"] #repoPath: "oui/config" diff --git a/config/samples/kgio_v1_resourcesinterceptor2.yaml b/config/samples/kgio_v1_resourcesinterceptor2.yaml deleted file mode 100644 index 30eee5b..0000000 --- a/config/samples/kgio_v1_resourcesinterceptor2.yaml +++ /dev/null @@ -1,27 +0,0 @@ -apiVersion: kgio.dams.kgio/v1 -kind: ResourcesInterceptor -metadata: - labels: - app.kubernetes.io/name: resourcesinterceptor - app.kubernetes.io/instance: resourcesinterceptor-sample - app.kubernetes.io/part-of: operator - app.kubernetes.io/managed-by: kustomize - app.kubernetes.io/created-by: operator - name: resourcesinterceptor-sample2 - namespace: test -spec: - remoteRepository: https://damiendassieu.fr - commitMode: Commit - commitProcess: CommitApply - operations: - - CREATE - - UPDATE - - DELETE - authorizedUsers: - - name: gituserbinding-sample - defaultUnauthorizedUserMode: Block - includedResources: - - apiGroups: ["", "networking.k8s.io"] - apiVersions: ["v1"] - resources: ["configmaps", "ingresses", "pods"] - #names: ["secret-basic-auth1"] diff --git a/config/webhook/gen-certs-serv-cli.sh b/config/webhook/gen-certs-serv-cli.sh new file mode 100755 index 0000000..d1389b5 --- /dev/null +++ b/config/webhook/gen-certs-serv-cli.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +cat > openssl.cnf <> base64 server caBundle:" +cat server.crt | base64 | tr -d '\n' +echo +echo ">> base64 server key:" +cat server.key | base64 | tr -d '\n' + +echo +echo ">> base64 client caBundle:" +cat client.crt | base64 | tr -d '\n' +echo +echo ">> base64 client key:" +cat client.key | base64 | tr -d '\n' + +mv ca.crt tls.crt +mv ca.key tls.key diff --git a/internal/controller/dynamic_webhook_handlers.go b/internal/controller/dynamic_webhook_handlers.go index 5d1c354..67fad64 100644 --- a/internal/controller/dynamic_webhook_handlers.go +++ b/internal/controller/dynamic_webhook_handlers.go @@ -35,6 +35,7 @@ type WebhookInterceptsAll struct { type DynamicWebhookHandler struct { resourcesInterceptor kgiov1.ResourcesInterceptor k8sClient client.Client + log *logr.Logger } // Start starts the webhook server @@ -179,6 +180,7 @@ func (dwc *DynamicWebhookHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque admReview: admissionReviewReq, resourcesInterceptor: dwc.resourcesInterceptor, k8sClient: dwc.k8sClient, + log: dwc.log, } admResponse := wrc.ProcessSteps() diff --git a/internal/controller/git_pusher.go b/internal/controller/git_pusher.go index 16791e0..7d53fe2 100644 --- a/internal/controller/git_pusher.go +++ b/internal/controller/git_pusher.go @@ -29,7 +29,7 @@ type GitPusher struct { gitEmail string gitToken string operation admissionv1.Operation - remoteConfiguration kgiov1.RemoteConfiguration + remoteConfiguration kgiov1.GitProviderConfiguration } type GitPushResponse struct { diff --git a/internal/controller/gitremote_controller.go b/internal/controller/gitremote_controller.go index 9027fa2..af81891 100644 --- a/internal/controller/gitremote_controller.go +++ b/internal/controller/gitremote_controller.go @@ -18,6 +18,7 @@ package controller import ( "context" + "errors" "fmt" "net/http" "os" @@ -49,6 +50,65 @@ type GitRemoteReconciler struct { Namespace string } +func (r *GitRemoteReconciler) updateStatus(ctx context.Context, gitRemote *kgiov1.GitRemote) error { + if err := r.Status().Update(ctx, gitRemote); err != nil { + return err + } + return nil +} + +func (r *GitRemoteReconciler) setProviderConfiguration(ctx context.Context, gitRemote *kgiov1.GitRemote) (kgiov1.GitProviderConfiguration, error) { + + gpc := kgiov1.GitProviderConfiguration{ + Inherited: false, + CaBundle: "", + InsecureSkipTlsVerify: false, + } + + // STEP 1 : Check the config map ref + var cm corev1.ConfigMap + if gitRemote.Spec.CustomGitProviderConfigRef.Name != "" { + // It is defined in the GitRemote object + namespacedName := types.NamespacedName{Namespace: gitRemote.Namespace, Name: gitRemote.Spec.CustomGitProviderConfigRef.Name} + if err := r.Get(ctx, namespacedName, &cm); err != nil { + gitRemote.Status.ConnexionStatus.Status = kgiov1.GitConfigNotFound + gitRemote.Status.ConnexionStatus.Details = "ConfigMap name: " + gitRemote.Spec.CustomGitProviderConfigRef.Name + return gpc, err + } + } else { + // It is not defined in the GitRemote object -> look for the default configmap of the operator + namespacedName := types.NamespacedName{Namespace: r.Namespace, Name: gitProvidersConfigMap} + if err := r.Get(ctx, namespacedName, &cm); err != nil { + gitRemote.Status.ConnexionStatus.Status = kgiov1.GitConfigNotFound + gitRemote.Status.ConnexionStatus.Details = "Configuration reference not found in the current GitRemote; ConfigMap " + gitProvidersConfigMap + " in the namespace of the operator not found as well" + return gpc, err + } + gpc.Inherited = true + } + + // STEP 2 : Build the GitProviderConfiguration + + // Parse the ConfigMap + providers, err := parseConfigMap(cm) + if err != nil { + gitRemote.Status.ConnexionStatus.Status = kgiov1.GitConfigParseError + gitRemote.Status.ConnexionStatus.Details = err.Error() + return gpc, err + } + + // Set the conf + for providerName, providerData := range providers { + if gitRemote.Spec.GitBaseDomainFQDN == providerName { + gpc.AuthenticationEndpoint = providerData.AuthenticationEndpoint + gpc.CaBundle = providerData.CaBundle + gpc.InsecureSkipTlsVerify = providerData.InsecureSkipTlsVerify + // .. Future conf + } + } + + return gpc, nil +} + // +kubebuilder:rbac:groups=kgio.dams.kgio,resources=gitremotes,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=kgio.dams.kgio,resources=gitremotes/status,verbs=get;update;patch // +kubebuilder:rbac:groups=kgio.dams.kgio,resources=gitremotes/finalizers,verbs=update @@ -57,7 +117,6 @@ type GitRemoteReconciler struct { // +kubebuilder:rbac:groups=core,resources=events,verbs=create;patch func (r *GitRemoteReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = log.FromContext(ctx) - var tabString = "\n " // Get the GitRemote Object var gitRemote kgiov1.GitRemote @@ -67,146 +126,122 @@ func (r *GitRemoteReconciler) Reconcile(ctx context.Context, req ctrl.Request) ( } gRNamespace := gitRemote.Namespace gRName := gitRemote.Name - gitBaseDomainFQDN := gitRemote.Spec.GitBaseDomainFQDN - var prefixMsg = "[" + gRNamespace + "/" + gRName + "]" + tabString - log.Log.Info(prefixMsg + "Reconciling request received") + var prefixMsg = "[" + gRNamespace + "/" + gRName + "]" + log.Log.Info(prefixMsg + " Reconciling request received") // Get the referenced Secret var secret corev1.Secret - retrievedSecret := types.NamespacedName{Namespace: req.Namespace, Name: gitRemote.Spec.SecretRef.Name} - if err := r.Get(ctx, retrievedSecret, &secret); err != nil { - log.Log.Error(nil, prefixMsg+"Secret not found with the name "+gitRemote.Spec.SecretRef.Name) - gitRemote.Status.ConnexionStatus = kgiov1.Disconnected - if err := r.Status().Update(ctx, &gitRemote); err != nil { - // log.Log.Error(err, prefixMsg + "Failed to update status") - return ctrl.Result{}, client.IgnoreNotFound(err) - } + namespacedNameSecret := types.NamespacedName{Namespace: req.Namespace, Name: gitRemote.Spec.SecretRef.Name} + if err := r.Get(ctx, namespacedNameSecret, &secret); err != nil { + gitRemote.Status.SecretBoundStatus = kgiov1.SecretNotFound + gitRemote.Status.ConnexionStatus.Status = "" + r.updateStatus(ctx, &gitRemote) return ctrl.Result{}, err } + gitRemote.Status.SecretBoundStatus = kgiov1.SecretBound username := string(secret.Data["username"]) - log.Log.Info(prefixMsg + "Secret found, username : " + username) + + // Update configuration + gpc, err := r.setProviderConfiguration(ctx, &gitRemote) + if err != nil { + errUpdate := r.updateStatus(ctx, &gitRemote) + return ctrl.Result{}, errUpdate + } + gitRemote.Status.GitProviderConfiguration = gpc + errUpdate := r.updateStatus(ctx, &gitRemote) + if errUpdate != nil { + return ctrl.Result{}, errUpdate + } if gitRemote.Spec.TestAuthentication { - log.Log.Info(prefixMsg + "Auth check on " + gitBaseDomainFQDN + " using the token associated to " + username) // Check if the referenced Secret is a basic-auth type if secret.Type != corev1.SecretTypeBasicAuth { - err := fmt.Errorf("secret type is not BasicAuth") - log.Log.Error(nil, prefixMsg+"The secret type is not BasicAuth, found: "+string(secret.Type)) + err := errors.New("secret type is not BasicAuth") + gitRemote.Status.SecretBoundStatus = kgiov1.SecretWrongType return ctrl.Result{}, err } // Get the username and password from the Secret - gitRemote.Status.GitUserID = username + gitRemote.Status.GitUser = username PAToken := string(secret.Data["password"]) - // Fetch the ConfigMap - // controllerNamespace := ctx.Value("controllerNamespace").(string) - configMap := &corev1.ConfigMap{} - configMapName := types.NamespacedName{Namespace: r.Namespace, Name: "git-providers-endpoints"} - if err := r.Get(ctx, configMapName, configMap); err != nil { - log.Log.Error(nil, prefixMsg+"ConfigMap not found with the name git-providers-endpoints in the operator's namespace") - return ctrl.Result{}, err - } - - // Parse the ConfigMap - providers, err := parseConfigMap(*configMap) - if err != nil { - log.Log.Error(nil, prefixMsg+"Failed to parse ConfigMap") - return ctrl.Result{}, err - } - - // Determine Git provider based on GitBaseDomainFQDN - var apiEndpoint string - var forbiddenMessage kgiov1.GitRemoteConnexionStatus - forbiddenMessage = kgiov1.Forbidden - gitProvider := gitRemote.Spec.GitProvider - - for providerName, providerData := range providers { - if gitRemote.Spec.GitProvider == providerName { - apiEndpoint = providerData.Authentication - forbiddenMessage = kgiov1.Forbidden - } - } - if apiEndpoint == "" { - if gitRemote.Spec.CustomGitProvider.Authentication != "" { - apiEndpoint = gitRemote.Spec.CustomGitProvider.Authentication + // If test auth -> the endpoint must exists + authenticationEndpoint := gpc.AuthenticationEndpoint + if authenticationEndpoint == "" { + errMsg := "" + if gpc.Inherited { + errMsg = "git provider not found in the " + gitProvidersConfigMap + " ConfigMap in the namespace of the operator" } else { - err := fmt.Errorf("unsupported git provider") - log.Log.Error(nil, prefixMsg+"Unsupported Git provider : "+string(gitRemote.Spec.GitProvider)) - return ctrl.Result{}, err + errMsg = "git provider not found in the " + gitRemote.Spec.CustomGitProviderConfigRef.Name + " ConfigMap" } + gitRemote.Status.ConnexionStatus.Status = kgiov1.GitUnsupported + gitRemote.Status.ConnexionStatus.Details = errMsg + errUpdate := r.updateStatus(ctx, &gitRemote) + return ctrl.Result{}, errUpdate } - var endpointCheckedMsg = tabString + "Process authentication checking on this endpoint : " + apiEndpoint - // Perform Git provider authentication check httpClient := &http.Client{} - gitReq, err := http.NewRequest("GET", apiEndpoint, nil) + gitReq, err := http.NewRequest("GET", authenticationEndpoint, nil) if err != nil { - log.Log.Error(nil, prefixMsg+"Failed to create Git Auth Test request"+endpointCheckedMsg) - return ctrl.Result{}, err + gitRemote.Status.ConnexionStatus.Status = kgiov1.GitServerError + gitRemote.Status.ConnexionStatus.Details = "Internal operator error : cannot create the http request" + errUpdate := r.updateStatus(ctx, &gitRemote) + return ctrl.Result{}, errUpdate } gitReq.Header.Add("Private-Token", PAToken) resp, err := httpClient.Do(gitReq) if err != nil { - log.Log.Error(nil, prefixMsg+"Failed to perform the Git Auth Test request; cannot communicate with the remote Git server (%s)", gitProvider+endpointCheckedMsg) - return ctrl.Result{}, err + gitRemote.Status.ConnexionStatus.Status = kgiov1.GitServerError + gitRemote.Status.ConnexionStatus.Details = "Internal operator error : the request cannot be processed" + errUpdate := r.updateStatus(ctx, &gitRemote) + return ctrl.Result{}, errUpdate } defer resp.Body.Close() // Check the response status code - connexionError := fmt.Errorf("status code : %d", resp.StatusCode) if resp.StatusCode == http.StatusOK { // Authentication successful - connexionError = nil - gitRemote.Status.ConnexionStatus = kgiov1.Connected + gitRemote.Status.ConnexionStatus.Status = kgiov1.GitConnected gitRemote.Status.LastAuthTime = metav1.Now() - log.Log.Info(prefixMsg + "✅ Auth succeeded - " + username + " connected" + endpointCheckedMsg) r.Recorder.Event(&gitRemote, "Normal", "Connected", "Auth succeeded") } else if resp.StatusCode == http.StatusUnauthorized { // Unauthorized: bad credentials - gitRemote.Status.ConnexionStatus = kgiov1.Unauthorized - log.Log.Error(connexionError, prefixMsg+"❌ Auth failed - Unauthorized"+endpointCheckedMsg) + gitRemote.Status.ConnexionStatus.Status = kgiov1.GitUnauthorized r.Recorder.Event(&gitRemote, "Warning", "AuthFailed", "Auth failed - unauthorized") } else if resp.StatusCode == http.StatusForbidden { // Forbidden : Not enough permission - gitRemote.Status.ConnexionStatus = forbiddenMessage - log.Log.Error(connexionError, prefixMsg+"❌ Auth failed - "+string(forbiddenMessage)+endpointCheckedMsg) + gitRemote.Status.ConnexionStatus.Status = kgiov1.GitForbidden r.Recorder.Event(&gitRemote, "Warning", "AuthFailed", "Auth failed - forbidden") } else if resp.StatusCode == http.StatusInternalServerError { // Server error: a server error happened - gitRemote.Status.ConnexionStatus = kgiov1.ServerError - log.Log.Error(connexionError, prefixMsg+"❌ Auth failed - "+gitBaseDomainFQDN+" returns a Server Error"+endpointCheckedMsg) + gitRemote.Status.ConnexionStatus.Status = kgiov1.GitServerError r.Recorder.Event(&gitRemote, "Warning", "AuthFailed", "Auth failed - server error") } else { // Handle other status codes if needed - gitRemote.Status.ConnexionStatus = kgiov1.UnexpectedStatus - log.Log.Error(connexionError, prefixMsg+"❌ Auth failed - Unexpected response from "+string(gitProvider)+endpointCheckedMsg) + gitRemote.Status.ConnexionStatus.Status = kgiov1.GitUnexpectedStatus r.Recorder.Event(&gitRemote, "Warning", "AuthFailed", fmt.Sprintf("Auth failed - unexpected response - %s", resp.Status)) } } // Update the status of GitRemote - if err := r.Status().Update(ctx, &gitRemote); err != nil { - log.Log.Error(nil, prefixMsg+"Failed to update status") - return ctrl.Result{}, err - } + r.updateStatus(ctx, &gitRemote) return ctrl.Result{}, nil } -func parseConfigMap(configMap corev1.ConfigMap) (map[string]kgiov1.GitProvider, error) { - providers := make(map[string]kgiov1.GitProvider) +func parseConfigMap(configMap corev1.ConfigMap) (map[string]kgiov1.GitProviderConfiguration, error) { + providers := make(map[string]kgiov1.GitProviderConfiguration) for key, value := range configMap.Data { - var gitProvider kgiov1.GitProvider + var gitProvider kgiov1.GitProviderConfiguration if err := yaml.Unmarshal([]byte(value), &gitProvider); err != nil { - return nil, fmt.Errorf("failed to unmarshal provider data for key %s: %w", key, err) + return nil, errors.New("failed to unmarshal provider data for key " + key + ": " + err.Error()) } providers[key] = gitProvider @@ -238,7 +273,30 @@ func (r *GitRemoteReconciler) findObjectsForSecret(ctx context.Context, secret c return requests } -func (r *GitRemoteReconciler) findObjectsForConfigMap(ctx context.Context, configMap client.Object) []reconcile.Request { +func (r *GitRemoteReconciler) findObjectsForGitProviderConfig(ctx context.Context, configMap client.Object) []reconcile.Request { + attachedGitRemotes := &kgiov1.GitRemoteList{} + listOps := &client.ListOptions{ + FieldSelector: fields.OneTermEqualSelector(gitProviderConfigRefField, configMap.GetName()), + Namespace: configMap.GetNamespace(), + } + err := r.List(ctx, attachedGitRemotes, listOps) + if err != nil { + return []reconcile.Request{} + } + + requests := make([]reconcile.Request, len(attachedGitRemotes.Items)) + for i, item := range attachedGitRemotes.Items { + requests[i] = reconcile.Request{ + NamespacedName: types.NamespacedName{ + Name: item.GetName(), + Namespace: item.GetNamespace(), + }, + } + } + return requests +} + +func (r *GitRemoteReconciler) findObjectsForRootConfigMap(ctx context.Context, configMap client.Object) []reconcile.Request { attachedGitRemotes := &kgiov1.GitRemoteList{} listOps := &client.ListOptions{} err := r.List(ctx, attachedGitRemotes, listOps) @@ -283,8 +341,9 @@ func (r *GitRemoteReconciler) gitEndpointsConfigDeletion(e event.DeleteEvent) bo } const ( - secretRefField = ".spec.secretRef.name" - gitProvidersConfigMap = "git-providers-endpoints" + secretRefField = ".spec.secretRef.name" + gitProviderConfigRefField = ".spec.customGitProviderConfigRef.name" + gitProvidersConfigMap = "git-providers-configuration" ) // SetupWithManager sets up the controller with the Manager. @@ -299,6 +358,18 @@ func (r *GitRemoteReconciler) SetupWithManager(mgr ctrl.Manager) error { }); err != nil { return err } + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &kgiov1.GitRemote{}, gitProviderConfigRefField, func(rawObj client.Object) []string { + // Extract the ConfigMap name from the GitRemote Spec, if one is provided + gitRemote := rawObj.(*kgiov1.GitRemote) + if gitRemote.Spec.CustomGitProviderConfigRef.Name == "" { + return nil + } + return []string{gitRemote.Spec.CustomGitProviderConfigRef.Name} + }); err != nil { + return err + } + + // Recorder to manage events recorder := mgr.GetEventRecorderFor("gitremote-controller") r.Recorder = recorder @@ -320,7 +391,12 @@ func (r *GitRemoteReconciler) SetupWithManager(mgr ctrl.Manager) error { ). Watches( &corev1.ConfigMap{}, - handler.EnqueueRequestsFromMapFunc(r.findObjectsForConfigMap), + handler.EnqueueRequestsFromMapFunc(r.findObjectsForGitProviderConfig), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ). + Watches( + &corev1.ConfigMap{}, + handler.EnqueueRequestsFromMapFunc(r.findObjectsForRootConfigMap), builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}, configMapPredicates), ). Complete(r) diff --git a/internal/controller/gituserbinding_controller.go b/internal/controller/gituserbinding_controller.go index 97c0f08..abf3cff 100644 --- a/internal/controller/gituserbinding_controller.go +++ b/internal/controller/gituserbinding_controller.go @@ -57,7 +57,6 @@ type GitUserBindingReconciler struct { // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.17.0/pkg/reconcile func (r *GitUserBindingReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = log.FromContext(ctx) - var tabString = "\n " // Get the GitUserBinding Object var gitUserBinding kgiov1.GitUserBinding @@ -68,14 +67,13 @@ func (r *GitUserBindingReconciler) Reconcile(ctx context.Context, req ctrl.Reque gUBNamespace := gitUserBinding.Namespace gUBName := gitUserBinding.Name subject := gitUserBinding.Spec.Subject - var prefixMsg = "[" + gUBNamespace + "/" + gUBName + "]" + tabString - log.Log.Info(prefixMsg + "Reconciling request received") + var prefixMsg = "[" + gUBNamespace + "/" + gUBName + "]" + log.Log.Info(prefixMsg + " Reconciling request received") // Get the referenced GitRemotes var isGloballyBound bool = false var isGloballyNotBound bool = false - var msg = "" var gitUserHosts []kgiov1.GitUserHost for _, gitRemoteRef := range gitUserBinding.Spec.RemoteRefs { @@ -90,15 +88,12 @@ func (r *GitUserBindingReconciler) Reconcile(ctx context.Context, req ctrl.Reque // Get the concerned GitRemote if err := r.Get(ctx, retrievedGitRemote, &gitRemote); err != nil { - log.Log.Error(nil, prefixMsg+"GitRemote not found with the name "+gitRemoteRef.Name) - msg += tabString + " ❌ " + gitUserHost.GitRemoteUsed + " Not Bound" r.Recorder.Event(&gitUserBinding, "Warning", "NotBound", gitUserHost.GitRemoteUsed+" not bound") isGloballyNotBound = true } else { gitUserHost.GitFQDN = gitRemote.Spec.GitBaseDomainFQDN gitUserHost.SecretRef = gitRemote.Spec.SecretRef gitUserHost.State = kgiov1.Bound - msg += tabString + " ✅ " + gitUserHost.GitRemoteUsed + " Bound" r.Recorder.Event(&gitUserBinding, "Normal", "Bound", gitUserHost.GitRemoteUsed+" bound") isGloballyBound = true } @@ -108,7 +103,6 @@ func (r *GitUserBindingReconciler) Reconcile(ctx context.Context, req ctrl.Reque } gitUserBinding.Status.GitUserHosts = gitUserHosts - log.Log.Info(prefixMsg + "GitRemotes status list:" + msg) if isGloballyBound && isGloballyNotBound { gitUserBinding.Status.GlobalState = kgiov1.PartiallyBound r.Recorder.Event(&gitUserBinding, "Warning", "PartiallyBound", "Some of the git repos are not bound") diff --git a/internal/controller/resourcesinterceptor_controller.go b/internal/controller/resourcesinterceptor_controller.go index 123cb2e..a392bc5 100644 --- a/internal/controller/resourcesinterceptor_controller.go +++ b/internal/controller/resourcesinterceptor_controller.go @@ -25,6 +25,7 @@ import ( v1 "k8s.io/apimachinery/pkg/apis/meta/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" "sigs.k8s.io/controller-runtime/pkg/log" @@ -40,6 +41,7 @@ type ResourcesInterceptorReconciler struct { webhookServer WebhookInterceptsAll Namespace string Dev bool + Recorder record.EventRecorder } //+kubebuilder:rbac:groups=kgio.dams.kgio,resources=resourcesinterceptors,verbs=get;list;watch;create;update;patch;delete @@ -48,18 +50,8 @@ type ResourcesInterceptorReconciler struct { //+kubebuilder:rbac:groups=*,resources=*,verbs=get;list;watch //+kubebuilder:rbac:groups=admissionregistration.k8s.io,resources=validatingwebhookconfigurations,verbs=create;get;list;watch;update;patch;delete -// Reconcile is part of the main kubernetes reconciliation loop which aims to -// move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the ResourcesInterceptor object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. -// -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.17.0/pkg/reconcile func (r *ResourcesInterceptorReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = log.FromContext(ctx) - var tabString = "\n " // Get the ResourcesInterceptor Object var resourcesInterceptor kgiov1.ResourcesInterceptor @@ -71,9 +63,8 @@ func (r *ResourcesInterceptorReconciler) Reconcile(ctx context.Context, req ctrl rINamespace := resourcesInterceptor.Namespace rIName := resourcesInterceptor.Name - var prefixMsg = "[" + rINamespace + "/" + rIName + "]" + tabString - - log.Log.Info(prefixMsg + "Reconciling request received") + var prefixMsg = "[" + rINamespace + "/" + rIName + "]" + log.Log.Info(prefixMsg + " Reconciling request received") // Define the webhook path webhookPath := "/kgio/validate/" + rINamespace + "/" + rIName @@ -84,7 +75,8 @@ func (r *ResourcesInterceptorReconciler) Reconcile(ctx context.Context, req ctrl caCert, err := os.ReadFile("/tmp/k8s-webhook-server/serving-certs/tls.crt") if err != nil { log.Log.Error(err, "failed to read the cert file /tmp/k8s-webhook-server/serving-certs/tls.crt") - panic(err) // handle error if unable to read file + r.Recorder.Event(&resourcesInterceptor, "Warning", "WebhookCertFail", "Operator internal error : the certificate file failed to be read") + return reconcile.Result{}, err } // The service is located in the manager/controller namespace @@ -120,7 +112,7 @@ func (r *ResourcesInterceptorReconciler) Reconcile(ctx context.Context, req ctrl // Create a new ValidatingWebhook object webhook := &admissionv1.ValidatingWebhookConfiguration{ ObjectMeta: v1.ObjectMeta{ - Name: webhookObjectName, + Name: webhookObjectName, Annotations: annotations, }, Webhooks: []admissionv1.ValidatingWebhook{ @@ -138,11 +130,6 @@ func (r *ResourcesInterceptorReconciler) Reconcile(ctx context.Context, req ctrl }, } - // Set controller reference to own the object - // if err := ctrl.SetControllerReference(&resourcesInterceptor, webhook, r.Scheme); err != nil { - // return ctrl.Result{}, err - // } - webhookNamespacedName := &types.NamespacedName{ Name: webhookObjectName, } @@ -150,9 +137,6 @@ func (r *ResourcesInterceptorReconciler) Reconcile(ctx context.Context, req ctrl // Check if the webhook already exists found := &admissionv1.ValidatingWebhookConfiguration{} err = r.Get(ctx, *webhookNamespacedName, found) - // if err != nil && client.IgnoreNotFound(err) != nil { - // return reconcile.Result{}, err - // } if err == nil { // Search for the webhook spec associated to this RI @@ -174,16 +158,20 @@ func (r *ResourcesInterceptorReconciler) Reconcile(ctx context.Context, req ctrl err = r.Update(ctx, found) if err != nil { + r.Recorder.Event(&resourcesInterceptor, "Warning", "WebhookNotUpdated", "The webhook exists but has not been updated") return reconcile.Result{}, err } } else { // Create a new webhook if not found -> if it is the first RI to be created err := r.Create(ctx, webhook) if err != nil { + r.Recorder.Event(&resourcesInterceptor, "Warning", "WebhookNotCreated", "The webhook does not exists and has not been created") return reconcile.Result{}, err } } + r.Recorder.Event(&resourcesInterceptor, "Normal", "WebhookUpdated", "The resources have been successfully added to the webhook") + return ctrl.Result{}, nil } @@ -209,6 +197,9 @@ func nsrListToRuleList(nsrList []kgiov1.NamespaceScopedResources, operations []a // SetupWithManager sets up the controller with the Manager. func (r *ResourcesInterceptorReconciler) SetupWithManager(mgr ctrl.Manager) error { + recorder := mgr.GetEventRecorderFor("resourcesinterceptor-controller") + r.Recorder = recorder + managerNamespace := os.Getenv("MANAGER_NAMESPACE") dev := os.Getenv("DEV") r.Namespace = managerNamespace @@ -220,7 +211,7 @@ func (r *ResourcesInterceptorReconciler) SetupWithManager(mgr ctrl.Manager) erro // Initialize the webhookServer r.webhookServer = WebhookInterceptsAll{ k8sClient: mgr.GetClient(), - dev: r.Dev, + dev: r.Dev, } r.webhookServer.Start() diff --git a/internal/controller/webhook_request_checker.go b/internal/controller/webhook_request_checker.go index 5a3b8b1..fa6178d 100644 --- a/internal/controller/webhook_request_checker.go +++ b/internal/controller/webhook_request_checker.go @@ -8,6 +8,7 @@ import ( "slices" kgiov1 "dams.kgio/kgio/api/v1" + "github.com/go-logr/logr" admissionv1 "k8s.io/api/admission/v1" corev1 "k8s.io/api/core/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -38,11 +39,12 @@ type wrcDetails struct { messageAddition string // GitPusher information - repoFQDN string - repoPath string - commitHash string - gitUser gitUser - remoteConf kgiov1.RemoteConfiguration + repoFQDN string + repoPath string + commitHash string + gitUser gitUser + remoteConf kgiov1.GitProviderConfiguration + pushDetails string } const ( @@ -57,6 +59,8 @@ type WebhookRequestChecker struct { resourcesInterceptor kgiov1.ResourcesInterceptor // The kubernetes client to make request to the api k8sClient client.Client + // Logger + log *logr.Logger } func (wrc *WebhookRequestChecker) ProcessSteps() admissionv1.AdmissionReview { @@ -102,7 +106,7 @@ func (wrc *WebhookRequestChecker) ProcessSteps() admissionv1.AdmissionReview { // STEP 6 : Git push isPushed, err := wrc.gitPush(&rDetails) - rDetails.processPass = isPushed + wrc.gitPushPostChecker(isPushed, err, &rDetails) if err != nil { return wrc.responseConstructor(rDetails) } @@ -131,6 +135,8 @@ func (wrc *WebhookRequestChecker) retrieveRequestDetails() (wrcDetails, error) { details.interceptedName = wrc.admReview.Request.Name + wrc.updateStatus("LastInterceptedObjectState", *details) + return *details, nil } @@ -172,7 +178,7 @@ func (wrc *WebhookRequestChecker) userAllowed(details *wrcDetails) (bool, error) gitEmail: "", gitToken: "", } - remoteConf := &kgiov1.RemoteConfiguration{ + remoteConf := &kgiov1.GitProviderConfiguration{ CaBundle: "", InsecureSkipTlsVerify: false, } @@ -192,7 +198,7 @@ func (wrc *WebhookRequestChecker) userAllowed(details *wrcDetails) (bool, error) // The subject name can not be unique -> in specific conditions, a commit can be done as another user // Need to be studied if gitUserBinding.Spec.Subject.Name == incomingUser.Username { - remoteConf, gitUser, err = wrc.searchForGitToken(*gitUserBinding, fqdn) + remoteConf, gitUser, err = wrc.searchForGitToken(*gitUserBinding, fqdn, remoteConf) if err != nil { errMsg := err.Error() details.messageAddition = errMsg @@ -219,14 +225,11 @@ func (wrc *WebhookRequestChecker) userAllowed(details *wrcDetails) (bool, error) return true, nil } -func (wrc *WebhookRequestChecker) searchForGitToken(gub kgiov1.GitUserBinding, fqdn string) (*kgiov1.RemoteConfiguration, *gitUser, error) { +func (wrc *WebhookRequestChecker) searchForGitToken(gub kgiov1.GitUserBinding, fqdn string, remoteConf *kgiov1.GitProviderConfiguration) (*kgiov1.GitProviderConfiguration, *gitUser, error) { userGitName := "" userGitEmail := "" userGitToken := "" - caBundle := "" - skipTLS := false - gitRemoteCount := 0 secretCount := 0 ctx := context.Background() @@ -260,8 +263,8 @@ func (wrc *WebhookRequestChecker) searchForGitToken(gub kgiov1.GitUserBinding, f userGitEmail = gitRemote.Spec.Email - caBundle = gitRemote.Spec.RemoteConfiguration.CaBundle - skipTLS = gitRemote.Spec.RemoteConfiguration.InsecureSkipTlsVerify + remoteConf.CaBundle = gitRemote.Status.GitProviderConfiguration.CaBundle + remoteConf.InsecureSkipTlsVerify = gitRemote.Status.GitProviderConfiguration.InsecureSkipTlsVerify } } @@ -270,10 +273,6 @@ func (wrc *WebhookRequestChecker) searchForGitToken(gub kgiov1.GitUserBinding, f gitEmail: userGitEmail, gitToken: userGitToken, } - remoteConf := &kgiov1.RemoteConfiguration{ - CaBundle: caBundle, - InsecureSkipTlsVerify: skipTLS, - } if gitRemoteCount == 0 { return remoteConf, gitUser, errors.New("no GitRemote found for the current user with this fqdn : " + fqdn) @@ -321,6 +320,8 @@ func (wrc *WebhookRequestChecker) letPassRequest(details *wrcDetails) admissionv details.webhookPass = true details.messageAddition = "this user bypass the process" + wrc.updateStatus("LastBypassedObjectState", *details) + return wrc.responseConstructor(*details) } @@ -390,6 +391,16 @@ func (wrc *WebhookRequestChecker) gitPush(details *wrcDetails) (bool, error) { return true, nil } +func (wrc *WebhookRequestChecker) gitPushPostChecker(isPushed bool, err error, details *wrcDetails) { + details.processPass = isPushed + if isPushed { + details.pushDetails = "Resource successfully pushed" + } else { + details.pushDetails = err.Error() + } + wrc.updateStatus("LastPushedObjectState", *details) +} + func (wrc *WebhookRequestChecker) postcheck(details *wrcDetails) bool { // Check the Commit Process mode if wrc.resourcesInterceptor.Spec.CommitProcess == kgiov1.CommitOnly { @@ -456,3 +467,46 @@ func (wrc *WebhookRequestChecker) responseConstructor(details wrcDetails) admiss return admissionReviewResp } + +func (wrc *WebhookRequestChecker) updateStatus(kind string, details wrcDetails) { + + gvrn := &kgiov1.JsonGVRN{ + Group: details.interceptedGVR.Group, + Version: details.interceptedGVR.Version, + Resource: details.interceptedGVR.Resource, + Name: details.interceptedName, + } + switch kind { + case "LastBypassedObjectState": + lastBypassedObjectState := &kgiov1.LastBypassedObjectState{ + LastBypassedObjectTime: v1.Now(), + LastBypassedObjectUserInfo: wrc.admReview.Request.UserInfo, + LastBypassedObject: *gvrn, + } + wrc.resourcesInterceptor.Status.LastBypassedObjectState = *lastBypassedObjectState + case "LastInterceptedObjectState": + lastInterceptedObjectState := &kgiov1.LastInterceptedObjectState{ + LastInterceptedObjectTime: v1.Now(), + LastInterceptedObjectUserInfo: wrc.admReview.Request.UserInfo, + LastInterceptedObject: *gvrn, + } + wrc.resourcesInterceptor.Status.LastInterceptedObjectState = *lastInterceptedObjectState + case "LastPushedObjectState": + lastPushedObjectState := &kgiov1.LastPushedObjectState{ + LastPushedObjectTime: v1.Now(), + LastPushedObject: *gvrn, + LastPushedObjectGitPath: details.repoPath, + LastPushedObjectGitRepo: details.repoFQDN, + LastPushedObjectGitCommitHash: details.commitHash, + LastPushedGitUser: details.gitUser.gitUser, + LastPushedObjectStatus: details.pushDetails, + } + wrc.resourcesInterceptor.Status.LastPushedObjectState = *lastPushedObjectState + } + + ctx := context.Background() + if err := wrc.k8sClient.Status().Update(ctx, &wrc.resourcesInterceptor); err != nil { + wrc.log.Error(err, "can't update the status of the resource interceptor "+wrc.resourcesInterceptor.Namespace+"/"+wrc.resourcesInterceptor.Name) + } + +}