diff --git a/.golangci.yaml b/.golangci.yaml index cce8715c..71625552 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -65,3 +65,11 @@ linters-settings: gosec: excludes: - G115 # Potential integer overflow when converting between integer types + +issues: + exclude-rules: + # exclude revive warnings for generated code + - linters: + - revive + path: 'api/v1alpha1/(.+)_conversion\.go' + text: "var-naming: don't use underscores in Go names; func (.+)" diff --git a/Makefile b/Makefile index d2611b58..a1c6a48f 100644 --- a/Makefile +++ b/Makefile @@ -49,8 +49,12 @@ help: ## Display this help. manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases +.PHONY: conversion +conversion: conversion-gen + $(CONVERSION_GEN) --go-header-file hack/boilerplate.go.txt --output-file zz_generated.conversion.go github.com/mercedes-benz/garm-operator/api/v1alpha1 + .PHONY: generate -generate: controller-gen mockgen ## Generate mock client and code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. +generate: controller-gen conversion mockgen ## Generate mock client and code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." go generate ./... @@ -162,6 +166,7 @@ $(LOCALBIN): KUBECTL ?= kubectl KUSTOMIZE ?= $(LOCALBIN)/kustomize CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen +CONVERSION_GEN ?= $(LOCALBIN)/conversion-gen ENVTEST ?= $(LOCALBIN)/setup-envtest GOLANGCI_LINT ?= $(LOCALBIN)/golangci-lint MOCKGEN ?= $(LOCALBIN)/mockgen @@ -176,7 +181,8 @@ KIND ?= $(LOCALBIN)/kind ## Tool Versions KUSTOMIZE_VERSION ?= v5.0.1 CONTROLLER_TOOLS_VERSION ?= v0.15.0 -GOLANGCI_LINT_VERSION ?= v1.61.0 +CONVERSION_GEN_VERSION ?= v0.30.5 +GOLANGCI_LINT_VERSION ?= v1.59.1 MOCKGEN_VERSION ?= v0.4.0 GORELEASER_VERSION ?= v1.21.0 MDTOC_VERSION ?= v1.1.0 @@ -200,6 +206,12 @@ $(CONTROLLER_GEN): $(LOCALBIN) test -s $(LOCALBIN)/controller-gen && $(LOCALBIN)/controller-gen --version | grep -q $(CONTROLLER_TOOLS_VERSION) || \ GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION) +.PHONY: conversion-gen +conversion-gen: $(CONVERSION_GEN) ## Download conversion-gen locally if necessary. If wrong version is installed, it will be overwritten. +$(CONVERSION_GEN): $(LOCALBIN) + test -s $(LOCALBIN)/conversion-gen || \ + GOBIN=$(LOCALBIN) go install k8s.io/code-generator/cmd/conversion-gen@$(CONVERSION_GEN_VERSION) + .PHONY: envtest envtest: $(ENVTEST) ## Download envtest-setup locally if necessary. $(ENVTEST): $(LOCALBIN) diff --git a/PROJECT b/PROJECT index 3608e28c..a60f7b0f 100644 --- a/PROJECT +++ b/PROJECT @@ -1,74 +1,160 @@ -# SPDX-License-Identifier: MIT - # 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: mercedes-benz.com layout: -- go.kubebuilder.io/v4 + - go.kubebuilder.io/v4 projectName: garm-operator repo: github.com/mercedes-benz/garm-operator resources: -- api: - crdVersion: v1 - namespaced: true - controller: true - domain: mercedes-benz.com - group: garm-operator - kind: Enterprise - path: github.com/mercedes-benz/garm-operator/api/v1alpha1 - version: v1alpha1 -- api: - crdVersion: v1 - namespaced: true - controller: true - domain: mercedes-benz.com - group: garm-operator - kind: Pool - path: github.com/mercedes-benz/garm-operator/api/v1alpha1 - version: v1alpha1 -- api: - crdVersion: v1 - namespaced: true - controller: true - domain: mercedes-benz.com - group: garm-operator - kind: Organization - path: github.com/mercedes-benz/garm-operator/api/v1alpha1 - version: v1alpha1 -- api: - crdVersion: v1 - namespaced: true - domain: mercedes-benz.com - group: garm-operator - kind: Image - path: github.com/mercedes-benz/garm-operator/api/v1alpha1 - version: v1alpha1 - webhooks: - defaulting: true - validation: true - webhookVersion: v1 -- api: - crdVersion: v1 - namespaced: true - controller: true - domain: mercedes-benz.com - group: garm-operator - kind: Repository - path: github.com/mercedes-benz/garm-operator/api/v1alpha1 - version: v1alpha1 - webhooks: - defaulting: true - validation: true - webhookVersion: v1 -- api: - crdVersion: v1 - namespaced: true - controller: true - domain: mercedes-benz.com - group: garm-operator - kind: Runner - path: github.com/mercedes-benz/garm-operator/api/v1alpha1 - version: v1alpha1 + - api: + crdVersion: v1 + namespaced: true + controller: true + domain: mercedes-benz.com + group: garm-operator + kind: Enterprise + path: github.com/mercedes-benz/garm-operator/api/v1alpha1 + version: v1alpha1 + webhooks: + conversion: true + webhookVersion: v1 + - api: + crdVersion: v1 + namespaced: true + controller: true + domain: mercedes-benz.com + group: garm-operator + kind: Pool + path: github.com/mercedes-benz/garm-operator/api/v1alpha1 + version: v1alpha1 + webhooks: + conversion: true + defaulting: true + validation: true + webhookVersion: v1 + - api: + crdVersion: v1 + namespaced: true + controller: true + domain: mercedes-benz.com + group: garm-operator + kind: Organization + path: github.com/mercedes-benz/garm-operator/api/v1alpha1 + version: v1alpha1 + webhooks: + conversion: true + webhookVersion: v1 + - api: + crdVersion: v1 + namespaced: true + domain: mercedes-benz.com + group: garm-operator + kind: Image + path: github.com/mercedes-benz/garm-operator/api/v1alpha1 + version: v1alpha1 + webhooks: + conversion: true + defaulting: true + validation: true + webhookVersion: v1 + - api: + crdVersion: v1 + namespaced: true + controller: true + domain: mercedes-benz.com + group: garm-operator + kind: Repository + path: github.com/mercedes-benz/garm-operator/api/v1alpha1 + version: v1alpha1 + webhooks: + conversion: true + defaulting: true + validation: true + webhookVersion: v1 + - api: + crdVersion: v1 + namespaced: true + controller: true + domain: mercedes-benz.com + group: garm-operator + kind: Runner + path: github.com/mercedes-benz/garm-operator/api/v1alpha1 + version: v1alpha1 + webhooks: + conversion: true + webhookVersion: v1 + - api: + crdVersion: v1 + namespaced: true + domain: mercedes-benz.com + group: garm-operator + kind: Enterprise + path: github.com/mercedes-benz/garm-operator/api/v1beta1 + version: v1beta1 + - api: + crdVersion: v1 + namespaced: true + domain: mercedes-benz.com + group: garm-operator + kind: Organization + path: github.com/mercedes-benz/garm-operator/api/v1beta1 + version: v1beta1 + - api: + crdVersion: v1 + namespaced: true + domain: mercedes-benz.com + group: garm-operator + kind: Repository + path: github.com/mercedes-benz/garm-operator/api/v1beta1 + version: v1beta1 + - api: + crdVersion: v1 + namespaced: true + domain: mercedes-benz.com + group: garm-operator + kind: Pool + path: github.com/mercedes-benz/garm-operator/api/v1beta1 + version: v1beta1 + - api: + crdVersion: v1 + namespaced: true + domain: mercedes-benz.com + group: garm-operator + kind: Runner + path: github.com/mercedes-benz/garm-operator/api/v1beta1 + version: v1beta1 + - api: + crdVersion: v1 + namespaced: true + domain: mercedes-benz.com + group: garm-operator + kind: Image + path: github.com/mercedes-benz/garm-operator/api/v1beta1 + version: v1beta1 + - api: + crdVersion: v1 + namespaced: true + domain: mercedes-benz.com + group: garm-operator + kind: GitHubEndpoint + path: github.com/mercedes-benz/garm-operator/api/v1beta1 + version: v1beta1 + - api: + crdVersion: v1 + namespaced: true + domain: mercedes-benz.com + group: garm-operator + kind: GitHubCredential + path: github.com/mercedes-benz/garm-operator/api/v1beta1 + version: v1beta1 + - api: + crdVersion: v1 + namespaced: true + domain: mercedes-benz.com + group: garm-operator + kind: GarmServerConfig + path: github.com/mercedes-benz/garm-operator/api/v1beta1 + version: v1beta1 version: "3" diff --git a/Tiltfile b/Tiltfile index 937c36bd..c5577507 100644 --- a/Tiltfile +++ b/Tiltfile @@ -10,7 +10,7 @@ allow_k8s_contexts('kind-garm-operator') # as the plugin has already well written readiness checks we can use it to wait for deploy_cert_manager( kind_cluster_name='garm-operator', # just for security reasons ;-) - version='v1.12.0' # the version of cert-manager to deploy + version='v1.15.3' # the version of cert-manager to deploy ) # mode could be either 'local' or 'debug' @@ -18,7 +18,7 @@ deploy_cert_manager( # the manager binary and the dlv debug port will be exposed # # for more details, please read the DEVELOPMENT.md -mode = 'local' +mode = 'local' # kustomize overlays templated_yaml = kustomize('config/overlays/' + mode) @@ -36,6 +36,9 @@ k8s_resource( 'pools.garm-operator.mercedes-benz.com:customresourcedefinition', 'runners.garm-operator.mercedes-benz.com:customresourcedefinition', 'repositories.garm-operator.mercedes-benz.com:customresourcedefinition', + 'garmserverconfigs.garm-operator.mercedes-benz.com:customresourcedefinition', + 'githubcredential.garm-operator.mercedes-benz.com:customresourcedefinition', + 'githubendpoints.garm-operator.mercedes-benz.com:customresourcedefinition', 'garm-operator-controller-manager:serviceaccount', 'garm-operator-leader-election-role:role', 'garm-operator-manager-role:clusterrole', diff --git a/api/v1alpha1/doc.go b/api/v1alpha1/doc.go new file mode 100644 index 00000000..4d472081 --- /dev/null +++ b/api/v1alpha1/doc.go @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: MIT + +// +k8s:conversion-gen=github.com/mercedes-benz/garm-operator/api/v1beta1 +package v1alpha1 diff --git a/api/v1alpha1/enterprise_conversion.go b/api/v1alpha1/enterprise_conversion.go new file mode 100644 index 00000000..92183636 --- /dev/null +++ b/api/v1alpha1/enterprise_conversion.go @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +package v1alpha1 + +import ( + corev1 "k8s.io/api/core/v1" + apiconversion "k8s.io/apimachinery/pkg/conversion" + "sigs.k8s.io/controller-runtime/pkg/conversion" + + "github.com/mercedes-benz/garm-operator/api/v1beta1" +) + +var _ conversion.Convertible = &Enterprise{} + +func (e *Enterprise) ConvertTo(dstRaw conversion.Hub) error { + return Convert_v1alpha1_Enterprise_To_v1beta1_Enterprise(e, dstRaw.(*v1beta1.Enterprise), nil) +} + +func (e *Enterprise) ConvertFrom(dstRaw conversion.Hub) error { + return Convert_v1beta1_Enterprise_To_v1alpha1_Enterprise(dstRaw.(*v1beta1.Enterprise), e, nil) +} + +func Convert_v1alpha1_EnterpriseSpec_To_v1beta1_EnterpriseSpec(in *EnterpriseSpec, out *v1beta1.EnterpriseSpec, s apiconversion.Scope) error { + out.CredentialsRef = corev1.TypedLocalObjectReference{ + Name: in.CredentialsName, + Kind: "GitHubCredential", + APIGroup: &v1beta1.GroupVersion.Group, + } + + return autoConvert_v1alpha1_EnterpriseSpec_To_v1beta1_EnterpriseSpec(in, out, s) +} + +func Convert_v1beta1_EnterpriseSpec_To_v1alpha1_EnterpriseSpec(in *v1beta1.EnterpriseSpec, out *EnterpriseSpec, s apiconversion.Scope) error { + out.CredentialsName = in.CredentialsRef.Name + + return autoConvert_v1beta1_EnterpriseSpec_To_v1alpha1_EnterpriseSpec(in, out, s) +} diff --git a/api/v1alpha1/enterprise_webhook.go b/api/v1alpha1/enterprise_webhook.go new file mode 100644 index 00000000..28bb2cd9 --- /dev/null +++ b/api/v1alpha1/enterprise_webhook.go @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +package v1alpha1 + +import ( + ctrl "sigs.k8s.io/controller-runtime" +) + +func (r *Enterprise) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(r). + Complete() +} diff --git a/api/v1alpha1/groupversion_info.go b/api/v1alpha1/groupversion_info.go index fd52d008..991d920b 100644 --- a/api/v1alpha1/groupversion_info.go +++ b/api/v1alpha1/groupversion_info.go @@ -19,4 +19,7 @@ var ( // AddToScheme adds the types in this group-version to the given scheme. AddToScheme = SchemeBuilder.AddToScheme + + // localSchemeBuilder is getting used by the generated conversion + localSchemeBuilder = SchemeBuilder.SchemeBuilder ) diff --git a/api/v1alpha1/image_conversion.go b/api/v1alpha1/image_conversion.go new file mode 100644 index 00000000..3c42aef4 --- /dev/null +++ b/api/v1alpha1/image_conversion.go @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +package v1alpha1 + +import ( + "sigs.k8s.io/controller-runtime/pkg/conversion" + + "github.com/mercedes-benz/garm-operator/api/v1beta1" +) + +var _ conversion.Convertible = &Image{} + +func (i *Image) ConvertTo(dstRaw conversion.Hub) error { + return Convert_v1alpha1_Image_To_v1beta1_Image(i, dstRaw.(*v1beta1.Image), nil) +} + +func (i *Image) ConvertFrom(dstRaw conversion.Hub) error { + return Convert_v1beta1_Image_To_v1alpha1_Image(dstRaw.(*v1beta1.Image), i, nil) +} diff --git a/api/v1alpha1/image_types.go b/api/v1alpha1/image_types.go index 3729a22f..b15044b9 100644 --- a/api/v1alpha1/image_types.go +++ b/api/v1alpha1/image_types.go @@ -31,7 +31,7 @@ type ImageStatus struct { //+kubebuilder:object:root=true //+kubebuilder:resource:path=images,scope=Namespaced,categories=garm -//+kubebuilder:printcolumn:name="Tag",type=string,JSONPath=`.spec.tag`,priority=1 +//+kubebuilder:printcolumn:name="Tag",type=string,JSONPath=`.spec.tag` //+kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" // Image is the Schema for the images API diff --git a/api/v1alpha1/organization_conversion.go b/api/v1alpha1/organization_conversion.go new file mode 100644 index 00000000..de505619 --- /dev/null +++ b/api/v1alpha1/organization_conversion.go @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +package v1alpha1 + +import ( + corev1 "k8s.io/api/core/v1" + apiconversion "k8s.io/apimachinery/pkg/conversion" + "sigs.k8s.io/controller-runtime/pkg/conversion" + + v1beta1 "github.com/mercedes-benz/garm-operator/api/v1beta1" +) + +func (o *Organization) ConvertTo(dstRaw conversion.Hub) error { + return Convert_v1alpha1_Organization_To_v1beta1_Organization(o, dstRaw.(*v1beta1.Organization), nil) +} + +func (o *Organization) ConvertFrom(dstRaw conversion.Hub) error { + return Convert_v1beta1_Organization_To_v1alpha1_Organization(dstRaw.(*v1beta1.Organization), o, nil) +} + +func Convert_v1alpha1_OrganizationSpec_To_v1beta1_OrganizationSpec(in *OrganizationSpec, out *v1beta1.OrganizationSpec, s apiconversion.Scope) error { + out.CredentialsRef = corev1.TypedLocalObjectReference{ + Name: in.CredentialsName, + Kind: "GitHubCredential", + APIGroup: &v1beta1.GroupVersion.Group, + } + + return autoConvert_v1alpha1_OrganizationSpec_To_v1beta1_OrganizationSpec(in, out, s) +} + +func Convert_v1beta1_OrganizationSpec_To_v1alpha1_OrganizationSpec(in *v1beta1.OrganizationSpec, out *OrganizationSpec, s apiconversion.Scope) error { + out.CredentialsName = in.CredentialsRef.Name + + return autoConvert_v1beta1_OrganizationSpec_To_v1alpha1_OrganizationSpec(in, out, s) +} diff --git a/api/v1alpha1/organization_webhook.go b/api/v1alpha1/organization_webhook.go new file mode 100644 index 00000000..612f5213 --- /dev/null +++ b/api/v1alpha1/organization_webhook.go @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +package v1alpha1 + +import ( + ctrl "sigs.k8s.io/controller-runtime" +) + +func (r *Organization) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(r). + Complete() +} diff --git a/api/v1alpha1/pool_conversion.go b/api/v1alpha1/pool_conversion.go new file mode 100644 index 00000000..2996486a --- /dev/null +++ b/api/v1alpha1/pool_conversion.go @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +package v1alpha1 + +import ( + "sigs.k8s.io/controller-runtime/pkg/conversion" + + "github.com/mercedes-benz/garm-operator/api/v1beta1" +) + +var _ conversion.Convertible = &Pool{} + +func (p *Pool) ConvertTo(dstRaw conversion.Hub) error { + return Convert_v1alpha1_Pool_To_v1beta1_Pool(p, dstRaw.(*v1beta1.Pool), nil) +} + +func (p *Pool) ConvertFrom(dstRaw conversion.Hub) error { + return Convert_v1beta1_Pool_To_v1alpha1_Pool(dstRaw.(*v1beta1.Pool), p, nil) +} diff --git a/api/v1alpha1/pool_webhook.go b/api/v1alpha1/pool_webhook.go index 2667997f..9fae201c 100644 --- a/api/v1alpha1/pool_webhook.go +++ b/api/v1alpha1/pool_webhook.go @@ -3,7 +3,6 @@ package v1alpha1 import ( - "context" "encoding/json" "fmt" "reflect" @@ -39,7 +38,6 @@ var _ webhook.Validator = &Pool{} // ValidateCreate implements webhook.Validator so a webhook will be registered for the type func (p *Pool) ValidateCreate() (admission.Warnings, error) { poollog.Info("validate create", "name", p.Name) - ctx := context.TODO() if err := p.validateExtraSpec(); err != nil { return nil, apierrors.NewInvalid(schema.GroupKind{Group: GroupVersion.Group, Kind: "Pool"}, @@ -48,23 +46,6 @@ func (p *Pool) ValidateCreate() (admission.Warnings, error) { ) } - poolImage, err := p.GetImageCR(ctx, c) - if err != nil { - poollog.Error(err, "cannot fetch Image", "error", err) - return nil, nil - } - - duplicate, duplicateName, err := p.CheckDuplicate(ctx, c, poolImage) - if err != nil { - poollog.Error(err, "error checking for duplicate", "error", err) - return nil, nil - } - - if duplicate { - err := fmt.Sprintf("pool with same image, flavor, provider and github scope already exists: %s", duplicateName) - return nil, apierrors.NewBadRequest(err) - } - return nil, nil } diff --git a/api/v1alpha1/repository_conversion.go b/api/v1alpha1/repository_conversion.go new file mode 100644 index 00000000..e737c884 --- /dev/null +++ b/api/v1alpha1/repository_conversion.go @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +package v1alpha1 + +import ( + corev1 "k8s.io/api/core/v1" + apiconversion "k8s.io/apimachinery/pkg/conversion" + "sigs.k8s.io/controller-runtime/pkg/conversion" + + "github.com/mercedes-benz/garm-operator/api/v1beta1" +) + +var _ conversion.Convertible = &Repository{} + +func (r *Repository) ConvertTo(dstRaw conversion.Hub) error { + return Convert_v1alpha1_Repository_To_v1beta1_Repository(r, dstRaw.(*v1beta1.Repository), nil) +} + +func (r *Repository) ConvertFrom(dstRaw conversion.Hub) error { + return Convert_v1beta1_Repository_To_v1alpha1_Repository(dstRaw.(*v1beta1.Repository), r, nil) +} + +func Convert_v1alpha1_RepositorySpec_To_v1beta1_RepositorySpec(in *RepositorySpec, out *v1beta1.RepositorySpec, s apiconversion.Scope) error { + out.CredentialsRef = corev1.TypedLocalObjectReference{ + Name: in.CredentialsName, + Kind: "GitHubCredential", + APIGroup: &v1beta1.GroupVersion.Group, + } + + return autoConvert_v1alpha1_RepositorySpec_To_v1beta1_RepositorySpec(in, out, s) +} + +func Convert_v1beta1_RepositorySpec_To_v1alpha1_RepositorySpec(in *v1beta1.RepositorySpec, out *RepositorySpec, s apiconversion.Scope) error { + out.CredentialsName = in.CredentialsRef.Name + + return autoConvert_v1beta1_RepositorySpec_To_v1alpha1_RepositorySpec(in, out, s) +} diff --git a/api/v1alpha1/runner_conversion.go b/api/v1alpha1/runner_conversion.go new file mode 100644 index 00000000..3809d8b4 --- /dev/null +++ b/api/v1alpha1/runner_conversion.go @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +package v1alpha1 + +import ( + "sigs.k8s.io/controller-runtime/pkg/conversion" + + "github.com/mercedes-benz/garm-operator/api/v1beta1" +) + +var _ conversion.Convertible = &Runner{} + +func (r *Runner) ConvertTo(dstRaw conversion.Hub) error { + return Convert_v1alpha1_Runner_To_v1beta1_Runner(r, dstRaw.(*v1beta1.Runner), nil) +} + +func (r *Runner) ConvertFrom(dstRaw conversion.Hub) error { + return Convert_v1beta1_Runner_To_v1alpha1_Runner(dstRaw.(*v1beta1.Runner), r, nil) +} diff --git a/api/v1alpha1/runner_webhook.go b/api/v1alpha1/runner_webhook.go new file mode 100644 index 00000000..f417ba93 --- /dev/null +++ b/api/v1alpha1/runner_webhook.go @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +package v1alpha1 + +import ( + ctrl "sigs.k8s.io/controller-runtime" +) + +func (r *Runner) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(r). + Complete() +} diff --git a/api/v1alpha1/zz_generated.conversion.go b/api/v1alpha1/zz_generated.conversion.go new file mode 100644 index 00000000..5d140450 --- /dev/null +++ b/api/v1alpha1/zz_generated.conversion.go @@ -0,0 +1,978 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +// SPDX-License-Identifier: MIT + +// Code generated by conversion-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + unsafe "unsafe" + + params "github.com/cloudbase/garm-provider-common/params" + garmparams "github.com/cloudbase/garm/params" + v1beta1 "github.com/mercedes-benz/garm-operator/api/v1beta1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + conversion "k8s.io/apimachinery/pkg/conversion" + runtime "k8s.io/apimachinery/pkg/runtime" +) + +func init() { + localSchemeBuilder.Register(RegisterConversions) +} + +// RegisterConversions adds conversion functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterConversions(s *runtime.Scheme) error { + if err := s.AddGeneratedConversionFunc((*Enterprise)(nil), (*v1beta1.Enterprise)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_Enterprise_To_v1beta1_Enterprise(a.(*Enterprise), b.(*v1beta1.Enterprise), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1beta1.Enterprise)(nil), (*Enterprise)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_Enterprise_To_v1alpha1_Enterprise(a.(*v1beta1.Enterprise), b.(*Enterprise), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*EnterpriseList)(nil), (*v1beta1.EnterpriseList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_EnterpriseList_To_v1beta1_EnterpriseList(a.(*EnterpriseList), b.(*v1beta1.EnterpriseList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1beta1.EnterpriseList)(nil), (*EnterpriseList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_EnterpriseList_To_v1alpha1_EnterpriseList(a.(*v1beta1.EnterpriseList), b.(*EnterpriseList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*EnterpriseStatus)(nil), (*v1beta1.EnterpriseStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_EnterpriseStatus_To_v1beta1_EnterpriseStatus(a.(*EnterpriseStatus), b.(*v1beta1.EnterpriseStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1beta1.EnterpriseStatus)(nil), (*EnterpriseStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_EnterpriseStatus_To_v1alpha1_EnterpriseStatus(a.(*v1beta1.EnterpriseStatus), b.(*EnterpriseStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*Image)(nil), (*v1beta1.Image)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_Image_To_v1beta1_Image(a.(*Image), b.(*v1beta1.Image), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1beta1.Image)(nil), (*Image)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_Image_To_v1alpha1_Image(a.(*v1beta1.Image), b.(*Image), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*ImageList)(nil), (*v1beta1.ImageList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_ImageList_To_v1beta1_ImageList(a.(*ImageList), b.(*v1beta1.ImageList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1beta1.ImageList)(nil), (*ImageList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_ImageList_To_v1alpha1_ImageList(a.(*v1beta1.ImageList), b.(*ImageList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*ImageSpec)(nil), (*v1beta1.ImageSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_ImageSpec_To_v1beta1_ImageSpec(a.(*ImageSpec), b.(*v1beta1.ImageSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1beta1.ImageSpec)(nil), (*ImageSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_ImageSpec_To_v1alpha1_ImageSpec(a.(*v1beta1.ImageSpec), b.(*ImageSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*ImageStatus)(nil), (*v1beta1.ImageStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_ImageStatus_To_v1beta1_ImageStatus(a.(*ImageStatus), b.(*v1beta1.ImageStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1beta1.ImageStatus)(nil), (*ImageStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_ImageStatus_To_v1alpha1_ImageStatus(a.(*v1beta1.ImageStatus), b.(*ImageStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*Organization)(nil), (*v1beta1.Organization)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_Organization_To_v1beta1_Organization(a.(*Organization), b.(*v1beta1.Organization), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1beta1.Organization)(nil), (*Organization)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_Organization_To_v1alpha1_Organization(a.(*v1beta1.Organization), b.(*Organization), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*OrganizationList)(nil), (*v1beta1.OrganizationList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_OrganizationList_To_v1beta1_OrganizationList(a.(*OrganizationList), b.(*v1beta1.OrganizationList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1beta1.OrganizationList)(nil), (*OrganizationList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_OrganizationList_To_v1alpha1_OrganizationList(a.(*v1beta1.OrganizationList), b.(*OrganizationList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*OrganizationStatus)(nil), (*v1beta1.OrganizationStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_OrganizationStatus_To_v1beta1_OrganizationStatus(a.(*OrganizationStatus), b.(*v1beta1.OrganizationStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1beta1.OrganizationStatus)(nil), (*OrganizationStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_OrganizationStatus_To_v1alpha1_OrganizationStatus(a.(*v1beta1.OrganizationStatus), b.(*OrganizationStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*Pool)(nil), (*v1beta1.Pool)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_Pool_To_v1beta1_Pool(a.(*Pool), b.(*v1beta1.Pool), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1beta1.Pool)(nil), (*Pool)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_Pool_To_v1alpha1_Pool(a.(*v1beta1.Pool), b.(*Pool), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*PoolList)(nil), (*v1beta1.PoolList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_PoolList_To_v1beta1_PoolList(a.(*PoolList), b.(*v1beta1.PoolList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1beta1.PoolList)(nil), (*PoolList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_PoolList_To_v1alpha1_PoolList(a.(*v1beta1.PoolList), b.(*PoolList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*PoolSpec)(nil), (*v1beta1.PoolSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_PoolSpec_To_v1beta1_PoolSpec(a.(*PoolSpec), b.(*v1beta1.PoolSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1beta1.PoolSpec)(nil), (*PoolSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_PoolSpec_To_v1alpha1_PoolSpec(a.(*v1beta1.PoolSpec), b.(*PoolSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*PoolStatus)(nil), (*v1beta1.PoolStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_PoolStatus_To_v1beta1_PoolStatus(a.(*PoolStatus), b.(*v1beta1.PoolStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1beta1.PoolStatus)(nil), (*PoolStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_PoolStatus_To_v1alpha1_PoolStatus(a.(*v1beta1.PoolStatus), b.(*PoolStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*Repository)(nil), (*v1beta1.Repository)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_Repository_To_v1beta1_Repository(a.(*Repository), b.(*v1beta1.Repository), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1beta1.Repository)(nil), (*Repository)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_Repository_To_v1alpha1_Repository(a.(*v1beta1.Repository), b.(*Repository), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*RepositoryList)(nil), (*v1beta1.RepositoryList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_RepositoryList_To_v1beta1_RepositoryList(a.(*RepositoryList), b.(*v1beta1.RepositoryList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1beta1.RepositoryList)(nil), (*RepositoryList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_RepositoryList_To_v1alpha1_RepositoryList(a.(*v1beta1.RepositoryList), b.(*RepositoryList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*RepositoryStatus)(nil), (*v1beta1.RepositoryStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_RepositoryStatus_To_v1beta1_RepositoryStatus(a.(*RepositoryStatus), b.(*v1beta1.RepositoryStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1beta1.RepositoryStatus)(nil), (*RepositoryStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_RepositoryStatus_To_v1alpha1_RepositoryStatus(a.(*v1beta1.RepositoryStatus), b.(*RepositoryStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*Runner)(nil), (*v1beta1.Runner)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_Runner_To_v1beta1_Runner(a.(*Runner), b.(*v1beta1.Runner), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1beta1.Runner)(nil), (*Runner)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_Runner_To_v1alpha1_Runner(a.(*v1beta1.Runner), b.(*Runner), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*RunnerList)(nil), (*v1beta1.RunnerList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_RunnerList_To_v1beta1_RunnerList(a.(*RunnerList), b.(*v1beta1.RunnerList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1beta1.RunnerList)(nil), (*RunnerList)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_RunnerList_To_v1alpha1_RunnerList(a.(*v1beta1.RunnerList), b.(*RunnerList), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*RunnerSpec)(nil), (*v1beta1.RunnerSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_RunnerSpec_To_v1beta1_RunnerSpec(a.(*RunnerSpec), b.(*v1beta1.RunnerSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1beta1.RunnerSpec)(nil), (*RunnerSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_RunnerSpec_To_v1alpha1_RunnerSpec(a.(*v1beta1.RunnerSpec), b.(*RunnerSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*RunnerStatus)(nil), (*v1beta1.RunnerStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_RunnerStatus_To_v1beta1_RunnerStatus(a.(*RunnerStatus), b.(*v1beta1.RunnerStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1beta1.RunnerStatus)(nil), (*RunnerStatus)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_RunnerStatus_To_v1alpha1_RunnerStatus(a.(*v1beta1.RunnerStatus), b.(*RunnerStatus), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*SecretRef)(nil), (*v1beta1.SecretRef)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_SecretRef_To_v1beta1_SecretRef(a.(*SecretRef), b.(*v1beta1.SecretRef), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*v1beta1.SecretRef)(nil), (*SecretRef)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_SecretRef_To_v1alpha1_SecretRef(a.(*v1beta1.SecretRef), b.(*SecretRef), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*EnterpriseSpec)(nil), (*v1beta1.EnterpriseSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_EnterpriseSpec_To_v1beta1_EnterpriseSpec(a.(*EnterpriseSpec), b.(*v1beta1.EnterpriseSpec), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*OrganizationSpec)(nil), (*v1beta1.OrganizationSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_OrganizationSpec_To_v1beta1_OrganizationSpec(a.(*OrganizationSpec), b.(*v1beta1.OrganizationSpec), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*RepositorySpec)(nil), (*v1beta1.RepositorySpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_RepositorySpec_To_v1beta1_RepositorySpec(a.(*RepositorySpec), b.(*v1beta1.RepositorySpec), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*v1beta1.EnterpriseSpec)(nil), (*EnterpriseSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_EnterpriseSpec_To_v1alpha1_EnterpriseSpec(a.(*v1beta1.EnterpriseSpec), b.(*EnterpriseSpec), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*v1beta1.OrganizationSpec)(nil), (*OrganizationSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_OrganizationSpec_To_v1alpha1_OrganizationSpec(a.(*v1beta1.OrganizationSpec), b.(*OrganizationSpec), scope) + }); err != nil { + return err + } + if err := s.AddConversionFunc((*v1beta1.RepositorySpec)(nil), (*RepositorySpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_RepositorySpec_To_v1alpha1_RepositorySpec(a.(*v1beta1.RepositorySpec), b.(*RepositorySpec), scope) + }); err != nil { + return err + } + return nil +} + +func autoConvert_v1alpha1_Enterprise_To_v1beta1_Enterprise(in *Enterprise, out *v1beta1.Enterprise, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1alpha1_EnterpriseSpec_To_v1beta1_EnterpriseSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1alpha1_EnterpriseStatus_To_v1beta1_EnterpriseStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1alpha1_Enterprise_To_v1beta1_Enterprise is an autogenerated conversion function. +func Convert_v1alpha1_Enterprise_To_v1beta1_Enterprise(in *Enterprise, out *v1beta1.Enterprise, s conversion.Scope) error { + return autoConvert_v1alpha1_Enterprise_To_v1beta1_Enterprise(in, out, s) +} + +func autoConvert_v1beta1_Enterprise_To_v1alpha1_Enterprise(in *v1beta1.Enterprise, out *Enterprise, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1beta1_EnterpriseSpec_To_v1alpha1_EnterpriseSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1beta1_EnterpriseStatus_To_v1alpha1_EnterpriseStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1beta1_Enterprise_To_v1alpha1_Enterprise is an autogenerated conversion function. +func Convert_v1beta1_Enterprise_To_v1alpha1_Enterprise(in *v1beta1.Enterprise, out *Enterprise, s conversion.Scope) error { + return autoConvert_v1beta1_Enterprise_To_v1alpha1_Enterprise(in, out, s) +} + +func autoConvert_v1alpha1_EnterpriseList_To_v1beta1_EnterpriseList(in *EnterpriseList, out *v1beta1.EnterpriseList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]v1beta1.Enterprise, len(*in)) + for i := range *in { + if err := Convert_v1alpha1_Enterprise_To_v1beta1_Enterprise(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_v1alpha1_EnterpriseList_To_v1beta1_EnterpriseList is an autogenerated conversion function. +func Convert_v1alpha1_EnterpriseList_To_v1beta1_EnterpriseList(in *EnterpriseList, out *v1beta1.EnterpriseList, s conversion.Scope) error { + return autoConvert_v1alpha1_EnterpriseList_To_v1beta1_EnterpriseList(in, out, s) +} + +func autoConvert_v1beta1_EnterpriseList_To_v1alpha1_EnterpriseList(in *v1beta1.EnterpriseList, out *EnterpriseList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Enterprise, len(*in)) + for i := range *in { + if err := Convert_v1beta1_Enterprise_To_v1alpha1_Enterprise(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_v1beta1_EnterpriseList_To_v1alpha1_EnterpriseList is an autogenerated conversion function. +func Convert_v1beta1_EnterpriseList_To_v1alpha1_EnterpriseList(in *v1beta1.EnterpriseList, out *EnterpriseList, s conversion.Scope) error { + return autoConvert_v1beta1_EnterpriseList_To_v1alpha1_EnterpriseList(in, out, s) +} + +func autoConvert_v1alpha1_EnterpriseSpec_To_v1beta1_EnterpriseSpec(in *EnterpriseSpec, out *v1beta1.EnterpriseSpec, s conversion.Scope) error { + // WARNING: in.CredentialsName requires manual conversion: does not exist in peer-type + if err := Convert_v1alpha1_SecretRef_To_v1beta1_SecretRef(&in.WebhookSecretRef, &out.WebhookSecretRef, s); err != nil { + return err + } + return nil +} + +func autoConvert_v1beta1_EnterpriseSpec_To_v1alpha1_EnterpriseSpec(in *v1beta1.EnterpriseSpec, out *EnterpriseSpec, s conversion.Scope) error { + // WARNING: in.CredentialsRef requires manual conversion: does not exist in peer-type + if err := Convert_v1beta1_SecretRef_To_v1alpha1_SecretRef(&in.WebhookSecretRef, &out.WebhookSecretRef, s); err != nil { + return err + } + // WARNING: in.PoolBalancerType requires manual conversion: does not exist in peer-type + return nil +} + +func autoConvert_v1alpha1_EnterpriseStatus_To_v1beta1_EnterpriseStatus(in *EnterpriseStatus, out *v1beta1.EnterpriseStatus, s conversion.Scope) error { + out.ID = in.ID + out.Conditions = *(*[]v1.Condition)(unsafe.Pointer(&in.Conditions)) + return nil +} + +// Convert_v1alpha1_EnterpriseStatus_To_v1beta1_EnterpriseStatus is an autogenerated conversion function. +func Convert_v1alpha1_EnterpriseStatus_To_v1beta1_EnterpriseStatus(in *EnterpriseStatus, out *v1beta1.EnterpriseStatus, s conversion.Scope) error { + return autoConvert_v1alpha1_EnterpriseStatus_To_v1beta1_EnterpriseStatus(in, out, s) +} + +func autoConvert_v1beta1_EnterpriseStatus_To_v1alpha1_EnterpriseStatus(in *v1beta1.EnterpriseStatus, out *EnterpriseStatus, s conversion.Scope) error { + out.ID = in.ID + out.Conditions = *(*[]v1.Condition)(unsafe.Pointer(&in.Conditions)) + return nil +} + +// Convert_v1beta1_EnterpriseStatus_To_v1alpha1_EnterpriseStatus is an autogenerated conversion function. +func Convert_v1beta1_EnterpriseStatus_To_v1alpha1_EnterpriseStatus(in *v1beta1.EnterpriseStatus, out *EnterpriseStatus, s conversion.Scope) error { + return autoConvert_v1beta1_EnterpriseStatus_To_v1alpha1_EnterpriseStatus(in, out, s) +} + +func autoConvert_v1alpha1_Image_To_v1beta1_Image(in *Image, out *v1beta1.Image, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1alpha1_ImageSpec_To_v1beta1_ImageSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1alpha1_ImageStatus_To_v1beta1_ImageStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1alpha1_Image_To_v1beta1_Image is an autogenerated conversion function. +func Convert_v1alpha1_Image_To_v1beta1_Image(in *Image, out *v1beta1.Image, s conversion.Scope) error { + return autoConvert_v1alpha1_Image_To_v1beta1_Image(in, out, s) +} + +func autoConvert_v1beta1_Image_To_v1alpha1_Image(in *v1beta1.Image, out *Image, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1beta1_ImageSpec_To_v1alpha1_ImageSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1beta1_ImageStatus_To_v1alpha1_ImageStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1beta1_Image_To_v1alpha1_Image is an autogenerated conversion function. +func Convert_v1beta1_Image_To_v1alpha1_Image(in *v1beta1.Image, out *Image, s conversion.Scope) error { + return autoConvert_v1beta1_Image_To_v1alpha1_Image(in, out, s) +} + +func autoConvert_v1alpha1_ImageList_To_v1beta1_ImageList(in *ImageList, out *v1beta1.ImageList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]v1beta1.Image)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1alpha1_ImageList_To_v1beta1_ImageList is an autogenerated conversion function. +func Convert_v1alpha1_ImageList_To_v1beta1_ImageList(in *ImageList, out *v1beta1.ImageList, s conversion.Scope) error { + return autoConvert_v1alpha1_ImageList_To_v1beta1_ImageList(in, out, s) +} + +func autoConvert_v1beta1_ImageList_To_v1alpha1_ImageList(in *v1beta1.ImageList, out *ImageList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]Image)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1beta1_ImageList_To_v1alpha1_ImageList is an autogenerated conversion function. +func Convert_v1beta1_ImageList_To_v1alpha1_ImageList(in *v1beta1.ImageList, out *ImageList, s conversion.Scope) error { + return autoConvert_v1beta1_ImageList_To_v1alpha1_ImageList(in, out, s) +} + +func autoConvert_v1alpha1_ImageSpec_To_v1beta1_ImageSpec(in *ImageSpec, out *v1beta1.ImageSpec, s conversion.Scope) error { + out.Tag = in.Tag + return nil +} + +// Convert_v1alpha1_ImageSpec_To_v1beta1_ImageSpec is an autogenerated conversion function. +func Convert_v1alpha1_ImageSpec_To_v1beta1_ImageSpec(in *ImageSpec, out *v1beta1.ImageSpec, s conversion.Scope) error { + return autoConvert_v1alpha1_ImageSpec_To_v1beta1_ImageSpec(in, out, s) +} + +func autoConvert_v1beta1_ImageSpec_To_v1alpha1_ImageSpec(in *v1beta1.ImageSpec, out *ImageSpec, s conversion.Scope) error { + out.Tag = in.Tag + return nil +} + +// Convert_v1beta1_ImageSpec_To_v1alpha1_ImageSpec is an autogenerated conversion function. +func Convert_v1beta1_ImageSpec_To_v1alpha1_ImageSpec(in *v1beta1.ImageSpec, out *ImageSpec, s conversion.Scope) error { + return autoConvert_v1beta1_ImageSpec_To_v1alpha1_ImageSpec(in, out, s) +} + +func autoConvert_v1alpha1_ImageStatus_To_v1beta1_ImageStatus(in *ImageStatus, out *v1beta1.ImageStatus, s conversion.Scope) error { + return nil +} + +// Convert_v1alpha1_ImageStatus_To_v1beta1_ImageStatus is an autogenerated conversion function. +func Convert_v1alpha1_ImageStatus_To_v1beta1_ImageStatus(in *ImageStatus, out *v1beta1.ImageStatus, s conversion.Scope) error { + return autoConvert_v1alpha1_ImageStatus_To_v1beta1_ImageStatus(in, out, s) +} + +func autoConvert_v1beta1_ImageStatus_To_v1alpha1_ImageStatus(in *v1beta1.ImageStatus, out *ImageStatus, s conversion.Scope) error { + return nil +} + +// Convert_v1beta1_ImageStatus_To_v1alpha1_ImageStatus is an autogenerated conversion function. +func Convert_v1beta1_ImageStatus_To_v1alpha1_ImageStatus(in *v1beta1.ImageStatus, out *ImageStatus, s conversion.Scope) error { + return autoConvert_v1beta1_ImageStatus_To_v1alpha1_ImageStatus(in, out, s) +} + +func autoConvert_v1alpha1_Organization_To_v1beta1_Organization(in *Organization, out *v1beta1.Organization, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1alpha1_OrganizationSpec_To_v1beta1_OrganizationSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1alpha1_OrganizationStatus_To_v1beta1_OrganizationStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1alpha1_Organization_To_v1beta1_Organization is an autogenerated conversion function. +func Convert_v1alpha1_Organization_To_v1beta1_Organization(in *Organization, out *v1beta1.Organization, s conversion.Scope) error { + return autoConvert_v1alpha1_Organization_To_v1beta1_Organization(in, out, s) +} + +func autoConvert_v1beta1_Organization_To_v1alpha1_Organization(in *v1beta1.Organization, out *Organization, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1beta1_OrganizationSpec_To_v1alpha1_OrganizationSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1beta1_OrganizationStatus_To_v1alpha1_OrganizationStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1beta1_Organization_To_v1alpha1_Organization is an autogenerated conversion function. +func Convert_v1beta1_Organization_To_v1alpha1_Organization(in *v1beta1.Organization, out *Organization, s conversion.Scope) error { + return autoConvert_v1beta1_Organization_To_v1alpha1_Organization(in, out, s) +} + +func autoConvert_v1alpha1_OrganizationList_To_v1beta1_OrganizationList(in *OrganizationList, out *v1beta1.OrganizationList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]v1beta1.Organization, len(*in)) + for i := range *in { + if err := Convert_v1alpha1_Organization_To_v1beta1_Organization(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_v1alpha1_OrganizationList_To_v1beta1_OrganizationList is an autogenerated conversion function. +func Convert_v1alpha1_OrganizationList_To_v1beta1_OrganizationList(in *OrganizationList, out *v1beta1.OrganizationList, s conversion.Scope) error { + return autoConvert_v1alpha1_OrganizationList_To_v1beta1_OrganizationList(in, out, s) +} + +func autoConvert_v1beta1_OrganizationList_To_v1alpha1_OrganizationList(in *v1beta1.OrganizationList, out *OrganizationList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Organization, len(*in)) + for i := range *in { + if err := Convert_v1beta1_Organization_To_v1alpha1_Organization(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_v1beta1_OrganizationList_To_v1alpha1_OrganizationList is an autogenerated conversion function. +func Convert_v1beta1_OrganizationList_To_v1alpha1_OrganizationList(in *v1beta1.OrganizationList, out *OrganizationList, s conversion.Scope) error { + return autoConvert_v1beta1_OrganizationList_To_v1alpha1_OrganizationList(in, out, s) +} + +func autoConvert_v1alpha1_OrganizationSpec_To_v1beta1_OrganizationSpec(in *OrganizationSpec, out *v1beta1.OrganizationSpec, s conversion.Scope) error { + // WARNING: in.CredentialsName requires manual conversion: does not exist in peer-type + if err := Convert_v1alpha1_SecretRef_To_v1beta1_SecretRef(&in.WebhookSecretRef, &out.WebhookSecretRef, s); err != nil { + return err + } + return nil +} + +func autoConvert_v1beta1_OrganizationSpec_To_v1alpha1_OrganizationSpec(in *v1beta1.OrganizationSpec, out *OrganizationSpec, s conversion.Scope) error { + // WARNING: in.CredentialsRef requires manual conversion: does not exist in peer-type + if err := Convert_v1beta1_SecretRef_To_v1alpha1_SecretRef(&in.WebhookSecretRef, &out.WebhookSecretRef, s); err != nil { + return err + } + // WARNING: in.PoolBalancerType requires manual conversion: does not exist in peer-type + return nil +} + +func autoConvert_v1alpha1_OrganizationStatus_To_v1beta1_OrganizationStatus(in *OrganizationStatus, out *v1beta1.OrganizationStatus, s conversion.Scope) error { + out.ID = in.ID + out.Conditions = *(*[]v1.Condition)(unsafe.Pointer(&in.Conditions)) + return nil +} + +// Convert_v1alpha1_OrganizationStatus_To_v1beta1_OrganizationStatus is an autogenerated conversion function. +func Convert_v1alpha1_OrganizationStatus_To_v1beta1_OrganizationStatus(in *OrganizationStatus, out *v1beta1.OrganizationStatus, s conversion.Scope) error { + return autoConvert_v1alpha1_OrganizationStatus_To_v1beta1_OrganizationStatus(in, out, s) +} + +func autoConvert_v1beta1_OrganizationStatus_To_v1alpha1_OrganizationStatus(in *v1beta1.OrganizationStatus, out *OrganizationStatus, s conversion.Scope) error { + out.ID = in.ID + out.Conditions = *(*[]v1.Condition)(unsafe.Pointer(&in.Conditions)) + return nil +} + +// Convert_v1beta1_OrganizationStatus_To_v1alpha1_OrganizationStatus is an autogenerated conversion function. +func Convert_v1beta1_OrganizationStatus_To_v1alpha1_OrganizationStatus(in *v1beta1.OrganizationStatus, out *OrganizationStatus, s conversion.Scope) error { + return autoConvert_v1beta1_OrganizationStatus_To_v1alpha1_OrganizationStatus(in, out, s) +} + +func autoConvert_v1alpha1_Pool_To_v1beta1_Pool(in *Pool, out *v1beta1.Pool, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1alpha1_PoolSpec_To_v1beta1_PoolSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1alpha1_PoolStatus_To_v1beta1_PoolStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1alpha1_Pool_To_v1beta1_Pool is an autogenerated conversion function. +func Convert_v1alpha1_Pool_To_v1beta1_Pool(in *Pool, out *v1beta1.Pool, s conversion.Scope) error { + return autoConvert_v1alpha1_Pool_To_v1beta1_Pool(in, out, s) +} + +func autoConvert_v1beta1_Pool_To_v1alpha1_Pool(in *v1beta1.Pool, out *Pool, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1beta1_PoolSpec_To_v1alpha1_PoolSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1beta1_PoolStatus_To_v1alpha1_PoolStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1beta1_Pool_To_v1alpha1_Pool is an autogenerated conversion function. +func Convert_v1beta1_Pool_To_v1alpha1_Pool(in *v1beta1.Pool, out *Pool, s conversion.Scope) error { + return autoConvert_v1beta1_Pool_To_v1alpha1_Pool(in, out, s) +} + +func autoConvert_v1alpha1_PoolList_To_v1beta1_PoolList(in *PoolList, out *v1beta1.PoolList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]v1beta1.Pool)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1alpha1_PoolList_To_v1beta1_PoolList is an autogenerated conversion function. +func Convert_v1alpha1_PoolList_To_v1beta1_PoolList(in *PoolList, out *v1beta1.PoolList, s conversion.Scope) error { + return autoConvert_v1alpha1_PoolList_To_v1beta1_PoolList(in, out, s) +} + +func autoConvert_v1beta1_PoolList_To_v1alpha1_PoolList(in *v1beta1.PoolList, out *PoolList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]Pool)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1beta1_PoolList_To_v1alpha1_PoolList is an autogenerated conversion function. +func Convert_v1beta1_PoolList_To_v1alpha1_PoolList(in *v1beta1.PoolList, out *PoolList, s conversion.Scope) error { + return autoConvert_v1beta1_PoolList_To_v1alpha1_PoolList(in, out, s) +} + +func autoConvert_v1alpha1_PoolSpec_To_v1beta1_PoolSpec(in *PoolSpec, out *v1beta1.PoolSpec, s conversion.Scope) error { + out.GitHubScopeRef = in.GitHubScopeRef + out.ProviderName = in.ProviderName + out.MaxRunners = in.MaxRunners + out.MinIdleRunners = in.MinIdleRunners + out.Flavor = in.Flavor + out.OSType = params.OSType(in.OSType) + out.OSArch = params.OSArch(in.OSArch) + out.Tags = *(*[]string)(unsafe.Pointer(&in.Tags)) + out.Enabled = in.Enabled + out.RunnerBootstrapTimeout = in.RunnerBootstrapTimeout + out.ImageName = in.ImageName + out.ExtraSpecs = in.ExtraSpecs + out.GitHubRunnerGroup = in.GitHubRunnerGroup + out.RunnerPrefix = in.RunnerPrefix + return nil +} + +// Convert_v1alpha1_PoolSpec_To_v1beta1_PoolSpec is an autogenerated conversion function. +func Convert_v1alpha1_PoolSpec_To_v1beta1_PoolSpec(in *PoolSpec, out *v1beta1.PoolSpec, s conversion.Scope) error { + return autoConvert_v1alpha1_PoolSpec_To_v1beta1_PoolSpec(in, out, s) +} + +func autoConvert_v1beta1_PoolSpec_To_v1alpha1_PoolSpec(in *v1beta1.PoolSpec, out *PoolSpec, s conversion.Scope) error { + out.GitHubScopeRef = in.GitHubScopeRef + out.ProviderName = in.ProviderName + out.MaxRunners = in.MaxRunners + out.MinIdleRunners = in.MinIdleRunners + out.Flavor = in.Flavor + out.OSType = params.OSType(in.OSType) + out.OSArch = params.OSArch(in.OSArch) + out.Tags = *(*[]string)(unsafe.Pointer(&in.Tags)) + out.Enabled = in.Enabled + out.RunnerBootstrapTimeout = in.RunnerBootstrapTimeout + out.ImageName = in.ImageName + out.ExtraSpecs = in.ExtraSpecs + out.GitHubRunnerGroup = in.GitHubRunnerGroup + out.RunnerPrefix = in.RunnerPrefix + return nil +} + +// Convert_v1beta1_PoolSpec_To_v1alpha1_PoolSpec is an autogenerated conversion function. +func Convert_v1beta1_PoolSpec_To_v1alpha1_PoolSpec(in *v1beta1.PoolSpec, out *PoolSpec, s conversion.Scope) error { + return autoConvert_v1beta1_PoolSpec_To_v1alpha1_PoolSpec(in, out, s) +} + +func autoConvert_v1alpha1_PoolStatus_To_v1beta1_PoolStatus(in *PoolStatus, out *v1beta1.PoolStatus, s conversion.Scope) error { + out.ID = in.ID + out.LongRunningIdleRunners = in.LongRunningIdleRunners + out.Selector = in.Selector + out.Conditions = *(*[]v1.Condition)(unsafe.Pointer(&in.Conditions)) + return nil +} + +// Convert_v1alpha1_PoolStatus_To_v1beta1_PoolStatus is an autogenerated conversion function. +func Convert_v1alpha1_PoolStatus_To_v1beta1_PoolStatus(in *PoolStatus, out *v1beta1.PoolStatus, s conversion.Scope) error { + return autoConvert_v1alpha1_PoolStatus_To_v1beta1_PoolStatus(in, out, s) +} + +func autoConvert_v1beta1_PoolStatus_To_v1alpha1_PoolStatus(in *v1beta1.PoolStatus, out *PoolStatus, s conversion.Scope) error { + out.ID = in.ID + out.LongRunningIdleRunners = in.LongRunningIdleRunners + out.Selector = in.Selector + out.Conditions = *(*[]v1.Condition)(unsafe.Pointer(&in.Conditions)) + return nil +} + +// Convert_v1beta1_PoolStatus_To_v1alpha1_PoolStatus is an autogenerated conversion function. +func Convert_v1beta1_PoolStatus_To_v1alpha1_PoolStatus(in *v1beta1.PoolStatus, out *PoolStatus, s conversion.Scope) error { + return autoConvert_v1beta1_PoolStatus_To_v1alpha1_PoolStatus(in, out, s) +} + +func autoConvert_v1alpha1_Repository_To_v1beta1_Repository(in *Repository, out *v1beta1.Repository, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1alpha1_RepositorySpec_To_v1beta1_RepositorySpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1alpha1_RepositoryStatus_To_v1beta1_RepositoryStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1alpha1_Repository_To_v1beta1_Repository is an autogenerated conversion function. +func Convert_v1alpha1_Repository_To_v1beta1_Repository(in *Repository, out *v1beta1.Repository, s conversion.Scope) error { + return autoConvert_v1alpha1_Repository_To_v1beta1_Repository(in, out, s) +} + +func autoConvert_v1beta1_Repository_To_v1alpha1_Repository(in *v1beta1.Repository, out *Repository, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1beta1_RepositorySpec_To_v1alpha1_RepositorySpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1beta1_RepositoryStatus_To_v1alpha1_RepositoryStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1beta1_Repository_To_v1alpha1_Repository is an autogenerated conversion function. +func Convert_v1beta1_Repository_To_v1alpha1_Repository(in *v1beta1.Repository, out *Repository, s conversion.Scope) error { + return autoConvert_v1beta1_Repository_To_v1alpha1_Repository(in, out, s) +} + +func autoConvert_v1alpha1_RepositoryList_To_v1beta1_RepositoryList(in *RepositoryList, out *v1beta1.RepositoryList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]v1beta1.Repository, len(*in)) + for i := range *in { + if err := Convert_v1alpha1_Repository_To_v1beta1_Repository(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_v1alpha1_RepositoryList_To_v1beta1_RepositoryList is an autogenerated conversion function. +func Convert_v1alpha1_RepositoryList_To_v1beta1_RepositoryList(in *RepositoryList, out *v1beta1.RepositoryList, s conversion.Scope) error { + return autoConvert_v1alpha1_RepositoryList_To_v1beta1_RepositoryList(in, out, s) +} + +func autoConvert_v1beta1_RepositoryList_To_v1alpha1_RepositoryList(in *v1beta1.RepositoryList, out *RepositoryList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Repository, len(*in)) + for i := range *in { + if err := Convert_v1beta1_Repository_To_v1alpha1_Repository(&(*in)[i], &(*out)[i], s); err != nil { + return err + } + } + } else { + out.Items = nil + } + return nil +} + +// Convert_v1beta1_RepositoryList_To_v1alpha1_RepositoryList is an autogenerated conversion function. +func Convert_v1beta1_RepositoryList_To_v1alpha1_RepositoryList(in *v1beta1.RepositoryList, out *RepositoryList, s conversion.Scope) error { + return autoConvert_v1beta1_RepositoryList_To_v1alpha1_RepositoryList(in, out, s) +} + +func autoConvert_v1alpha1_RepositorySpec_To_v1beta1_RepositorySpec(in *RepositorySpec, out *v1beta1.RepositorySpec, s conversion.Scope) error { + // WARNING: in.CredentialsName requires manual conversion: does not exist in peer-type + out.Owner = in.Owner + if err := Convert_v1alpha1_SecretRef_To_v1beta1_SecretRef(&in.WebhookSecretRef, &out.WebhookSecretRef, s); err != nil { + return err + } + return nil +} + +func autoConvert_v1beta1_RepositorySpec_To_v1alpha1_RepositorySpec(in *v1beta1.RepositorySpec, out *RepositorySpec, s conversion.Scope) error { + // WARNING: in.CredentialsRef requires manual conversion: does not exist in peer-type + out.Owner = in.Owner + if err := Convert_v1beta1_SecretRef_To_v1alpha1_SecretRef(&in.WebhookSecretRef, &out.WebhookSecretRef, s); err != nil { + return err + } + // WARNING: in.PoolBalancerType requires manual conversion: does not exist in peer-type + return nil +} + +func autoConvert_v1alpha1_RepositoryStatus_To_v1beta1_RepositoryStatus(in *RepositoryStatus, out *v1beta1.RepositoryStatus, s conversion.Scope) error { + out.ID = in.ID + out.Conditions = *(*[]v1.Condition)(unsafe.Pointer(&in.Conditions)) + return nil +} + +// Convert_v1alpha1_RepositoryStatus_To_v1beta1_RepositoryStatus is an autogenerated conversion function. +func Convert_v1alpha1_RepositoryStatus_To_v1beta1_RepositoryStatus(in *RepositoryStatus, out *v1beta1.RepositoryStatus, s conversion.Scope) error { + return autoConvert_v1alpha1_RepositoryStatus_To_v1beta1_RepositoryStatus(in, out, s) +} + +func autoConvert_v1beta1_RepositoryStatus_To_v1alpha1_RepositoryStatus(in *v1beta1.RepositoryStatus, out *RepositoryStatus, s conversion.Scope) error { + out.ID = in.ID + out.Conditions = *(*[]v1.Condition)(unsafe.Pointer(&in.Conditions)) + return nil +} + +// Convert_v1beta1_RepositoryStatus_To_v1alpha1_RepositoryStatus is an autogenerated conversion function. +func Convert_v1beta1_RepositoryStatus_To_v1alpha1_RepositoryStatus(in *v1beta1.RepositoryStatus, out *RepositoryStatus, s conversion.Scope) error { + return autoConvert_v1beta1_RepositoryStatus_To_v1alpha1_RepositoryStatus(in, out, s) +} + +func autoConvert_v1alpha1_Runner_To_v1beta1_Runner(in *Runner, out *v1beta1.Runner, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1alpha1_RunnerSpec_To_v1beta1_RunnerSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1alpha1_RunnerStatus_To_v1beta1_RunnerStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1alpha1_Runner_To_v1beta1_Runner is an autogenerated conversion function. +func Convert_v1alpha1_Runner_To_v1beta1_Runner(in *Runner, out *v1beta1.Runner, s conversion.Scope) error { + return autoConvert_v1alpha1_Runner_To_v1beta1_Runner(in, out, s) +} + +func autoConvert_v1beta1_Runner_To_v1alpha1_Runner(in *v1beta1.Runner, out *Runner, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if err := Convert_v1beta1_RunnerSpec_To_v1alpha1_RunnerSpec(&in.Spec, &out.Spec, s); err != nil { + return err + } + if err := Convert_v1beta1_RunnerStatus_To_v1alpha1_RunnerStatus(&in.Status, &out.Status, s); err != nil { + return err + } + return nil +} + +// Convert_v1beta1_Runner_To_v1alpha1_Runner is an autogenerated conversion function. +func Convert_v1beta1_Runner_To_v1alpha1_Runner(in *v1beta1.Runner, out *Runner, s conversion.Scope) error { + return autoConvert_v1beta1_Runner_To_v1alpha1_Runner(in, out, s) +} + +func autoConvert_v1alpha1_RunnerList_To_v1beta1_RunnerList(in *RunnerList, out *v1beta1.RunnerList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]v1beta1.Runner)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1alpha1_RunnerList_To_v1beta1_RunnerList is an autogenerated conversion function. +func Convert_v1alpha1_RunnerList_To_v1beta1_RunnerList(in *RunnerList, out *v1beta1.RunnerList, s conversion.Scope) error { + return autoConvert_v1alpha1_RunnerList_To_v1beta1_RunnerList(in, out, s) +} + +func autoConvert_v1beta1_RunnerList_To_v1alpha1_RunnerList(in *v1beta1.RunnerList, out *RunnerList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]Runner)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1beta1_RunnerList_To_v1alpha1_RunnerList is an autogenerated conversion function. +func Convert_v1beta1_RunnerList_To_v1alpha1_RunnerList(in *v1beta1.RunnerList, out *RunnerList, s conversion.Scope) error { + return autoConvert_v1beta1_RunnerList_To_v1alpha1_RunnerList(in, out, s) +} + +func autoConvert_v1alpha1_RunnerSpec_To_v1beta1_RunnerSpec(in *RunnerSpec, out *v1beta1.RunnerSpec, s conversion.Scope) error { + return nil +} + +// Convert_v1alpha1_RunnerSpec_To_v1beta1_RunnerSpec is an autogenerated conversion function. +func Convert_v1alpha1_RunnerSpec_To_v1beta1_RunnerSpec(in *RunnerSpec, out *v1beta1.RunnerSpec, s conversion.Scope) error { + return autoConvert_v1alpha1_RunnerSpec_To_v1beta1_RunnerSpec(in, out, s) +} + +func autoConvert_v1beta1_RunnerSpec_To_v1alpha1_RunnerSpec(in *v1beta1.RunnerSpec, out *RunnerSpec, s conversion.Scope) error { + return nil +} + +// Convert_v1beta1_RunnerSpec_To_v1alpha1_RunnerSpec is an autogenerated conversion function. +func Convert_v1beta1_RunnerSpec_To_v1alpha1_RunnerSpec(in *v1beta1.RunnerSpec, out *RunnerSpec, s conversion.Scope) error { + return autoConvert_v1beta1_RunnerSpec_To_v1alpha1_RunnerSpec(in, out, s) +} + +func autoConvert_v1alpha1_RunnerStatus_To_v1beta1_RunnerStatus(in *RunnerStatus, out *v1beta1.RunnerStatus, s conversion.Scope) error { + out.ID = in.ID + out.ProviderID = in.ProviderID + out.AgentID = in.AgentID + out.Name = in.Name + out.OSType = params.OSType(in.OSType) + out.OSName = in.OSName + out.OSVersion = in.OSVersion + out.OSArch = params.OSArch(in.OSArch) + out.Addresses = *(*[]params.Address)(unsafe.Pointer(&in.Addresses)) + out.Status = params.InstanceStatus(in.Status) + out.InstanceStatus = garmparams.RunnerStatus(in.InstanceStatus) + out.PoolID = in.PoolID + out.ProviderFault = in.ProviderFault + out.GitHubRunnerGroup = in.GitHubRunnerGroup + return nil +} + +// Convert_v1alpha1_RunnerStatus_To_v1beta1_RunnerStatus is an autogenerated conversion function. +func Convert_v1alpha1_RunnerStatus_To_v1beta1_RunnerStatus(in *RunnerStatus, out *v1beta1.RunnerStatus, s conversion.Scope) error { + return autoConvert_v1alpha1_RunnerStatus_To_v1beta1_RunnerStatus(in, out, s) +} + +func autoConvert_v1beta1_RunnerStatus_To_v1alpha1_RunnerStatus(in *v1beta1.RunnerStatus, out *RunnerStatus, s conversion.Scope) error { + out.ID = in.ID + out.ProviderID = in.ProviderID + out.AgentID = in.AgentID + out.Name = in.Name + out.OSType = params.OSType(in.OSType) + out.OSName = in.OSName + out.OSVersion = in.OSVersion + out.OSArch = params.OSArch(in.OSArch) + out.Addresses = *(*[]params.Address)(unsafe.Pointer(&in.Addresses)) + out.Status = params.InstanceStatus(in.Status) + out.InstanceStatus = garmparams.RunnerStatus(in.InstanceStatus) + out.PoolID = in.PoolID + out.ProviderFault = in.ProviderFault + out.GitHubRunnerGroup = in.GitHubRunnerGroup + return nil +} + +// Convert_v1beta1_RunnerStatus_To_v1alpha1_RunnerStatus is an autogenerated conversion function. +func Convert_v1beta1_RunnerStatus_To_v1alpha1_RunnerStatus(in *v1beta1.RunnerStatus, out *RunnerStatus, s conversion.Scope) error { + return autoConvert_v1beta1_RunnerStatus_To_v1alpha1_RunnerStatus(in, out, s) +} + +func autoConvert_v1alpha1_SecretRef_To_v1beta1_SecretRef(in *SecretRef, out *v1beta1.SecretRef, s conversion.Scope) error { + out.Name = in.Name + out.Key = in.Key + return nil +} + +// Convert_v1alpha1_SecretRef_To_v1beta1_SecretRef is an autogenerated conversion function. +func Convert_v1alpha1_SecretRef_To_v1beta1_SecretRef(in *SecretRef, out *v1beta1.SecretRef, s conversion.Scope) error { + return autoConvert_v1alpha1_SecretRef_To_v1beta1_SecretRef(in, out, s) +} + +func autoConvert_v1beta1_SecretRef_To_v1alpha1_SecretRef(in *v1beta1.SecretRef, out *SecretRef, s conversion.Scope) error { + out.Name = in.Name + out.Key = in.Key + return nil +} + +// Convert_v1beta1_SecretRef_To_v1alpha1_SecretRef is an autogenerated conversion function. +func Convert_v1beta1_SecretRef_To_v1alpha1_SecretRef(in *v1beta1.SecretRef, out *SecretRef, s conversion.Scope) error { + return autoConvert_v1beta1_SecretRef_To_v1alpha1_SecretRef(in, out, s) +} diff --git a/api/v1beta1/conversion.go b/api/v1beta1/conversion.go new file mode 100644 index 00000000..8d39b03c --- /dev/null +++ b/api/v1beta1/conversion.go @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +package v1beta1 + +// Hub marks this type as a conversion hub. +func (*Pool) Hub() {} + +// Hub marks this type as a conversion hub. +func (*Image) Hub() {} + +// Hub marks this type as a conversion hub. +func (*Runner) Hub() {} + +// Hub marks this type as a conversion hub. +func (*Enterprise) Hub() {} + +// Hub marks this type as a conversion hub. +func (*Organization) Hub() {} + +// Hub marks this type as a conversion hub. +func (*Repository) Hub() {} diff --git a/api/v1beta1/doc.go b/api/v1beta1/doc.go new file mode 100644 index 00000000..d4288261 --- /dev/null +++ b/api/v1beta1/doc.go @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT + +// Package v1beta1 contains API Schema definitions for the infrastructure v1alpha7 API group. +// +kubebuilder:object:generate=true +// +groupName=garm-operator.mercedes-benz.com +// +k8s:conversion-gen=github.com/mercedes-benz/garm-operator/api/v1alpha1 +// +k8s:openapi-gen=true +package v1beta1 diff --git a/api/v1beta1/enterprise_types.go b/api/v1beta1/enterprise_types.go new file mode 100644 index 00000000..6a9c2e32 --- /dev/null +++ b/api/v1beta1/enterprise_types.go @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: MIT + +package v1beta1 + +import ( + "github.com/cloudbase/garm/params" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/mercedes-benz/garm-operator/pkg/conditions" +) + +// EnterpriseSpec defines the desired state of Enterprise +type EnterpriseSpec struct { + CredentialsRef corev1.TypedLocalObjectReference `json:"credentialsRef"` + + // WebhookSecretRef represents a secret that should be used for the webhook + WebhookSecretRef SecretRef `json:"webhookSecretRef"` + PoolBalancerType params.PoolBalancerType `json:"poolBalancerType,omitempty"` +} + +// EnterpriseStatus defines the observed state of Enterprise +type EnterpriseStatus struct { + ID string `json:"id"` + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:resource:path=enterprises,scope=Namespaced,categories=garm,shortName=ent +//+kubebuilder:subresource:status +//+kubebuilder:storageversion +//+kubebuilder:printcolumn:name="ID",type="string",JSONPath=".status.id",description="Enterprise ID" +//+kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +//+kubebuilder:printcolumn:name="Error",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].message",priority=1 +//+kubebuilder:printcolumn:name="Pool_Manager_Failure",type="string",JSONPath=`.status.conditions[?(@.reason=='PoolManagerFailure')].message`,priority=1 +//+kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="Time duration since creation of Enterprise" + +// Enterprise is the Schema for the enterprises API +type Enterprise struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec EnterpriseSpec `json:"spec,omitempty"` + Status EnterpriseStatus `json:"status,omitempty"` +} + +func (e *Enterprise) SetConditions(conditions []metav1.Condition) { + e.Status.Conditions = conditions +} + +func (e *Enterprise) GetConditions() []metav1.Condition { + return e.Status.Conditions +} + +func (e *Enterprise) GetCredentialsName() string { + return e.Spec.CredentialsRef.Name +} + +func (e *Enterprise) GetID() string { + return e.Status.ID +} + +func (e *Enterprise) GetName() string { + return e.ObjectMeta.Name +} + +func (e *Enterprise) GetPoolManagerIsRunning() bool { + condition := conditions.Get(e, conditions.PoolManager) + if condition == nil { + return false + } + + return condition.Status == TrueAsString +} + +func (e *Enterprise) GetPoolManagerFailureReason() string { + condition := conditions.Get(e, conditions.PoolManager) + if condition == nil { + return "" + } + + if condition.Reason == string(conditions.PoolManagerFailureReason) { + return condition.Message + } + + return "" +} + +func (e *Enterprise) GetKind() string { + return e.Kind +} + +//+kubebuilder:object:root=true + +// EnterpriseList contains a list of Enterprise +type EnterpriseList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Enterprise `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Enterprise{}, &EnterpriseList{}) +} diff --git a/api/v1beta1/garmserverconfig_types.go b/api/v1beta1/garmserverconfig_types.go new file mode 100644 index 00000000..1f671597 --- /dev/null +++ b/api/v1beta1/garmserverconfig_types.go @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT + +package v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// GarmServerConfigSpec defines the desired state of GarmServerConfig +type GarmServerConfigSpec struct { + MetadataURL string `json:"metadataUrl,omitempty"` + CallbackURL string `json:"callbackUrl,omitempty"` + WebhookURL string `json:"webhookUrl,omitempty"` +} + +// GarmServerConfigStatus defines the observed state of GarmServerConfig +type GarmServerConfigStatus struct { + ControllerID string `json:"controllerId,omitempty"` + Hostname string `json:"hostname,omitempty"` + MetadataURL string `json:"metadataUrl,omitempty"` + CallbackURL string `json:"callbackUrl,omitempty"` + WebhookURL string `json:"webhookUrl,omitempty"` + ControllerWebhookURL string `json:"controllerWebhookUrl,omitempty"` + MinimumJobAgeBackoff uint `json:"minimumJobAgeBackoff,omitempty"` + Version string `json:"version,omitempty"` + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:resource:path=garmserverconfigs,scope=Namespaced,categories=garm,shortName=server +//+kubebuilder:subresource:status +//+kubebuilder:storageversion +//+kubebuilder:printcolumn:name="ID",type="string",JSONPath=".status.controllerId",description="Controller ID" +//+kubebuilder:printcolumn:name="Version",type="string",JSONPath=".status.version",description="Garm Version" +//+kubebuilder:printcolumn:name="MetadataURL",type="string",JSONPath=".status.metadataUrl",description="MetadataURL",priority=1 +//+kubebuilder:printcolumn:name="CallbackURL",type="string",JSONPath=".status.callbackUrl",description="CallbackURL",priority=1 +//+kubebuilder:printcolumn:name="WebhookURL",type="string",JSONPath=".status.webhookUrl",description="WebhookURL",priority=1 +//+kubebuilder:printcolumn:name="ControllerWebhookURL",type="string",JSONPath=".status.controllerWebhookUrl",description="ControllerWebhookURL",priority=1 +//+kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="Time duration since creation of GarmServerConfig" + +// GarmServerConfig is the Schema for the garmserverconfigs API +type GarmServerConfig struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec GarmServerConfigSpec `json:"spec,omitempty"` + Status GarmServerConfigStatus `json:"status,omitempty"` +} + +func (g *GarmServerConfig) SetConditions(conditions []metav1.Condition) { + g.Status.Conditions = conditions +} + +func (g *GarmServerConfig) GetConditions() []metav1.Condition { + return g.Status.Conditions +} + +//+kubebuilder:object:root=true + +// GarmServerConfigList contains a list of GarmServerConfig +type GarmServerConfigList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []GarmServerConfig `json:"items"` +} + +func init() { + SchemeBuilder.Register(&GarmServerConfig{}, &GarmServerConfigList{}) +} diff --git a/api/v1beta1/githubcredentials_types.go b/api/v1beta1/githubcredentials_types.go new file mode 100644 index 00000000..ae5a2441 --- /dev/null +++ b/api/v1beta1/githubcredentials_types.go @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: MIT + +package v1beta1 + +import ( + "github.com/cloudbase/garm/params" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// GitHubCredentialSpec defines the desired state of GitHubCredential +type GitHubCredentialSpec struct { + Description string `json:"description"` + EndpointRef corev1.TypedLocalObjectReference `json:"endpointRef"` + + // either pat or app + AuthType params.GithubAuthType `json:"authType"` + + // if AuthType is app + AppID int64 `json:"appId,omitempty"` + InstallationID int64 `json:"installationId,omitempty"` + + // containing either privateKey or pat token + SecretRef SecretRef `json:"secretRef,omitempty"` +} + +// GitHubCredentialStatus defines the observed state of GitHubCredential +type GitHubCredentialStatus struct { + ID int64 `json:"id"` + APIBaseURL string `json:"apiBaseUrl"` + UploadBaseURL string `json:"uploadBaseUrl"` + BaseURL string `json:"baseUrl"` + + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:resource:path=githubcredential,scope=Namespaced,categories=garm,shortName=creds +//+kubebuilder:subresource:status +//+kubebuilder:storageversion +//+kubebuilder:printcolumn:name="ID",type="string",JSONPath=".status.id",description="Credentials ID" +//+kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +//+kubebuilder:printcolumn:name="Error",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].message",priority=1 +//+kubebuilder:printcolumn:name="AuthType",type="string",JSONPath=`.spec.authType`,description="Authentication type" +//+kubebuilder:printcolumn:name="GitHubEndpoint",type="string",JSONPath=`.spec.endpointRef.name`,description="GitHubEndpoint name these credentials are tied to" +//+kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="Time duration since creation of GitHubCredential" + +// GitHubCredential is the Schema for the githubcredential API +type GitHubCredential struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec GitHubCredentialSpec `json:"spec,omitempty"` + Status GitHubCredentialStatus `json:"status,omitempty"` +} + +func (g *GitHubCredential) SetConditions(conditions []metav1.Condition) { + g.Status.Conditions = conditions +} + +func (g *GitHubCredential) GetConditions() []metav1.Condition { + return g.Status.Conditions +} + +//+kubebuilder:object:root=true + +// GitHubCredentialList contains a list of GitHubCredential +type GitHubCredentialList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []GitHubCredential `json:"items"` +} + +func init() { + SchemeBuilder.Register(&GitHubCredential{}, &GitHubCredentialList{}) +} diff --git a/api/v1beta1/githubendpoint_types.go b/api/v1beta1/githubendpoint_types.go new file mode 100644 index 00000000..64fbb56b --- /dev/null +++ b/api/v1beta1/githubendpoint_types.go @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: MIT + +package v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// GitHubEndpointSpec defines the desired state of GitHubEndpoint +type GitHubEndpointSpec struct { + Description string `json:"description,omitempty"` + APIBaseURL string `json:"apiBaseUrl,omitempty"` + UploadBaseURL string `json:"uploadBaseUrl,omitempty"` + BaseURL string `json:"baseUrl,omitempty"` + //nolint:godox + // TODO: This should be a secret reference + CACertBundle []byte `json:"caCertBundle,omitempty"` +} + +// GitHubEndpointStatus defines the observed state of GitHubEndpoint +type GitHubEndpointStatus struct { + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:resource:path=githubendpoints,scope=Namespaced,categories=garm,shortName=gep +//+kubebuilder:subresource:status +//+kubebuilder:storageversion +//+kubebuilder:printcolumn:name="URL",type="string",JSONPath=".spec.apiBaseUrl",description="API Base URL" +//+kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +//+kubebuilder:printcolumn:name="Error",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].message",priority=1 +//+kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="Time duration since creation of GitHubEndpoint" + +// GitHubEndpoint is the Schema for the githubendpoints API +type GitHubEndpoint struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec GitHubEndpointSpec `json:"spec,omitempty"` + Status GitHubEndpointStatus `json:"status,omitempty"` +} + +func (e *GitHubEndpoint) SetConditions(conditions []metav1.Condition) { + e.Status.Conditions = conditions +} + +func (e *GitHubEndpoint) GetConditions() []metav1.Condition { + return e.Status.Conditions +} + +//+kubebuilder:object:root=true + +// GitHubEndpointList contains a list of GitHubEndpoint +type GitHubEndpointList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []GitHubEndpoint `json:"items"` +} + +func init() { + SchemeBuilder.Register(&GitHubEndpoint{}, &GitHubEndpointList{}) +} diff --git a/api/v1beta1/groupversion_info.go b/api/v1beta1/groupversion_info.go new file mode 100644 index 00000000..da2b373c --- /dev/null +++ b/api/v1beta1/groupversion_info.go @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT + +// Package v1alpha1 contains API Schema definitions for the garm-operator v1alpha1 API group +// +kubebuilder:object:generate=true +// +groupName=garm-operator.mercedes-benz.com +package v1beta1 + +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: "garm-operator.mercedes-benz.com", Version: "v1beta1"} + + // 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/api/v1beta1/image_types.go b/api/v1beta1/image_types.go new file mode 100644 index 00000000..5064817a --- /dev/null +++ b/api/v1beta1/image_types.go @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT + +package v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// ImageSpec defines the desired state of Image +type ImageSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Tag is the Name of the image in its registry + // e.g. + // - in openstack it can be the image name or id + // - in k8s it can be the docker image name + tag + Tag string `json:"tag,omitempty"` +} + +// ImageStatus defines the observed state of Image +type ImageStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file +} + +//+kubebuilder:object:root=true +//+kubebuilder:storageversion +//+kubebuilder:resource:path=images,scope=Namespaced,categories=garm +//+kubebuilder:printcolumn:name="Tag",type=string,JSONPath=`.spec.tag` +//+kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" + +// Image is the Schema for the images API +type Image struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ImageSpec `json:"spec,omitempty"` + Status ImageStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// ImageList contains a list of Image +type ImageList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Image `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Image{}, &ImageList{}) +} diff --git a/api/v1beta1/image_webhook.go b/api/v1beta1/image_webhook.go new file mode 100644 index 00000000..17a22940 --- /dev/null +++ b/api/v1beta1/image_webhook.go @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: MIT + +package v1beta1 + +import ( + "context" + "fmt" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +// log is for logging in this package. +var imagelog = logf.Log.WithName("image-resource") + +func (i *Image) SetupWebhookWithManager(mgr ctrl.Manager) error { + c = mgr.GetClient() + return ctrl.NewWebhookManagedBy(mgr). + For(i). + Complete() +} + +//+kubebuilder:webhook:path=/validate-garm-operator-mercedes-benz-com-v1alpha1-image,mutating=false,failurePolicy=fail,sideEffects=None,groups=garm-operator.mercedes-benz.com,resources=images,verbs=create;update;delete,versions=v1alpha1,name=validate.image.garm-operator.mercedes-benz.com,admissionReviewVersions=v1 + +var _ webhook.Validator = &Image{} + +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type +func (i *Image) ValidateCreate() (admission.Warnings, error) { + imagelog.Info("validate create", "name", i.Name, "namespace", i.Namespace) + return nil, nil +} + +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type +func (i *Image) ValidateUpdate(_ runtime.Object) (admission.Warnings, error) { + imagelog.Info("validate update", "name", i.Name, "namespace", i.Namespace) + return nil, nil +} + +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type +func (i *Image) ValidateDelete() (admission.Warnings, error) { + imagelog.Info("validate delete", "name", i.Name, "namespace", i.Namespace) + var msg string + + pools, err := i.attachedPools(context.Background()) + if err != nil { + msg = fmt.Sprintf("imagename=%s with tag=%s can not be deleted, failed to fetch pools: %s", i.Name, i.Spec.Tag, err.Error()) + return nil, apierrors.NewBadRequest(msg) + } + + if len(pools) > 0 { + msg = fmt.Sprintf("imagename=%s with tag=%s can not be deleted, as it is still referenced by at least one pool", i.Name, i.Spec.Tag) + return nil, apierrors.NewBadRequest(msg) + } + return nil, nil +} + +func (i *Image) attachedPools(ctx context.Context) ([]Pool, error) { + var pools PoolList + var result []Pool + if err := c.List(ctx, &pools); err != nil { + return result, err + } + + for _, pool := range pools.Items { + // we do not care about pools that are already deleted + if pool.GetDeletionTimestamp() == nil { + if pool.Spec.ImageName == i.Name { + result = append(result, pool) + } + } + } + + return result, nil +} diff --git a/api/v1beta1/organization_types.go b/api/v1beta1/organization_types.go new file mode 100644 index 00000000..55b962f2 --- /dev/null +++ b/api/v1beta1/organization_types.go @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: MIT + +package v1beta1 + +import ( + "github.com/cloudbase/garm/params" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/mercedes-benz/garm-operator/pkg/conditions" +) + +// OrganizationSpec defines the desired state of Organization +type OrganizationSpec struct { + CredentialsRef corev1.TypedLocalObjectReference `json:"credentialsRef"` + + // WebhookSecretRef represents a secret that should be used for the webhook + WebhookSecretRef SecretRef `json:"webhookSecretRef"` + PoolBalancerType params.PoolBalancerType `json:"poolBalancerType,omitempty"` +} + +// OrganizationStatus defines the observed state of Organization +type OrganizationStatus struct { + ID string `json:"id"` + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:resource:path=organizations,scope=Namespaced,categories=garm,shortName=org +//+kubebuilder:subresource:status +//+kubebuilder:storageversion +//+kubebuilder:printcolumn:name="ID",type="string",JSONPath=".status.id",description="Organization ID" +//+kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +//+kubebuilder:printcolumn:name="Error",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].message",priority=1 +//+kubebuilder:printcolumn:name="Pool_Manager_Failure",type="string",JSONPath=`.status.conditions[?(@.reason=='PoolManagerFailure')].message`,priority=1 +//+kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="Time duration since creation of Organization" + +// Organization is the Schema for the organizations API +type Organization struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec OrganizationSpec `json:"spec,omitempty"` + Status OrganizationStatus `json:"status,omitempty"` +} + +func (o *Organization) SetConditions(conditions []metav1.Condition) { + o.Status.Conditions = conditions +} + +func (o *Organization) GetConditions() []metav1.Condition { + return o.Status.Conditions +} + +func (o *Organization) GetCredentialsName() string { + return o.Spec.CredentialsRef.Name +} + +func (o *Organization) GetID() string { + return o.Status.ID +} + +func (o *Organization) GetName() string { + return o.ObjectMeta.Name +} + +func (o *Organization) GetPoolManagerIsRunning() bool { + condition := conditions.Get(o, conditions.PoolManager) + if condition == nil { + return false + } + + return condition.Status == TrueAsString +} + +func (o *Organization) GetPoolManagerFailureReason() string { + condition := conditions.Get(o, conditions.PoolManager) + if condition == nil { + return "" + } + + if condition.Reason == string(conditions.PoolManagerFailureReason) { + return condition.Message + } + + return "" +} + +func (o *Organization) GetKind() string { + return o.Kind +} + +//+kubebuilder:object:root=true + +// OrganizationList contains a list of Organization +type OrganizationList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Organization `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Organization{}, &OrganizationList{}) +} diff --git a/api/v1beta1/pool_helper.go b/api/v1beta1/pool_helper.go new file mode 100644 index 00000000..a8fff800 --- /dev/null +++ b/api/v1beta1/pool_helper.go @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT + +package v1beta1 + +import ( + "context" + + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/mercedes-benz/garm-operator/pkg/filter" +) + +func (p *Pool) GetImageCR(ctx context.Context, client client.Client) (*Image, error) { + image := &Image{} + if p.Spec.ImageName != "" { + if err := client.Get(ctx, types.NamespacedName{Name: p.Spec.ImageName, Namespace: p.Namespace}, image); err != nil { + return nil, err + } + } + return image, nil +} + +func MatchesImage(image string) filter.Predicate[Pool] { + return func(p Pool) bool { + return p.Spec.ImageName == image + } +} + +func MatchesFlavor(flavor string) filter.Predicate[Pool] { + return func(p Pool) bool { + return p.Spec.Flavor == flavor + } +} + +func MatchesProvider(provider string) filter.Predicate[Pool] { + return func(p Pool) bool { + return p.Spec.ProviderName == provider + } +} + +func MatchesGitHubScope(name, kind string) filter.Predicate[Pool] { + return func(p Pool) bool { + return p.Spec.GitHubScopeRef.Name == name && p.Spec.GitHubScopeRef.Kind == kind + } +} + +func MatchesID(id string) filter.Predicate[Pool] { + return func(p Pool) bool { + return p.Status.ID == id + } +} + +func NotMatchingName(name string) filter.Predicate[Pool] { + return func(p Pool) bool { + return p.Name != name + } +} diff --git a/api/v1beta1/pool_types.go b/api/v1beta1/pool_types.go new file mode 100644 index 00000000..b38fe36e --- /dev/null +++ b/api/v1beta1/pool_types.go @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: MIT + +package v1beta1 + +import ( + commonParams "github.com/cloudbase/garm-provider-common/params" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// PoolSpec defines the desired state of Pool +// See: https://github.com/cloudbase/garm/blob/main/params/requests.go#L142 + +// +kubebuilder:validation:Required +// +kubebuilder:validation:XValidation:rule="self.minIdleRunners <= self.maxRunners",message="minIdleRunners must be less than or equal to maxRunners" +type PoolSpec struct { + // Defines in which Scope Runners a registered. Has a reference to either an Enterprise, Org or Repo CRD + GitHubScopeRef corev1.TypedLocalObjectReference `json:"githubScopeRef"` + ProviderName string `json:"providerName"` + MaxRunners uint `json:"maxRunners"` + // +kubebuilder:default=0 + MinIdleRunners uint `json:"minIdleRunners"` + Flavor string `json:"flavor"` + OSType commonParams.OSType `json:"osType"` + OSArch commonParams.OSArch `json:"osArch"` + Tags []string `json:"tags"` + Enabled bool `json:"enabled"` + RunnerBootstrapTimeout uint `json:"runnerBootstrapTimeout"` + + // The name of the image resource, this image resource must exists in the same namespace as the pool + ImageName string `json:"imageName"` + + // +optional + ExtraSpecs string `json:"extraSpecs"` + + // +optional + GitHubRunnerGroup string `json:"githubRunnerGroup"` + + // +optional + RunnerPrefix string `json:"runnerPrefix"` +} + +// PoolStatus defines the observed state of Pool +type PoolStatus struct { + ID string `json:"id"` + LongRunningIdleRunners uint `json:"longRunningIdleRunners"` + Selector string `json:"selector"` + + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +func (p *Pool) SetConditions(conditions []metav1.Condition) { + p.Status.Conditions = conditions +} + +func (p *Pool) GetConditions() []metav1.Condition { + return p.Status.Conditions +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status +//+kubebuilder:storageversion +//+kubebuilder:subresource:scale:specpath=.spec.minIdleRunners,statuspath=.status.longRunningIdleRunners,selectorpath=.status.selector +//+kubebuilder:resource:path=pools,scope=Namespaced,categories=garm +//+kubebuilder:printcolumn:name="ID",type=string,JSONPath=`.status.id` +//+kubebuilder:printcolumn:name="MinIdleRunners",type=string,JSONPath=`.spec.minIdleRunners` +//+kubebuilder:printcolumn:name="MaxRunners",type=string,JSONPath=`.spec.maxRunners` +//+kubebuilder:printcolumn:name="ImageName",type=string,JSONPath=`.spec.imageName`,priority=1 +//+kubebuilder:printcolumn:name="Flavor",type=string,JSONPath=`.spec.flavor`,priority=1 +//+kubebuilder:printcolumn:name="Provider",type=string,JSONPath=`.spec.providerName`,priority=1 +//+kubebuilder:printcolumn:name="ScopeType",type=string,JSONPath=`.spec.githubScopeRef.kind`,priority=1 +//+kubebuilder:printcolumn:name="ScopeName",type=string,JSONPath=`.spec.githubScopeRef.name`,priority=1 +//+kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +//+kubebuilder:printcolumn:name="Error",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].message",priority=1 +//+kubebuilder:printcolumn:name="Enabled",type=boolean,JSONPath=`.spec.enabled`,priority=1 +//+kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" + +// Pool is the Schema for the pools API +type Pool struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec PoolSpec `json:"spec,omitempty"` + Status PoolStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// PoolList contains a list of Pool +type PoolList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Pool `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Pool{}, &PoolList{}) +} diff --git a/api/v1beta1/pool_types_test.go b/api/v1beta1/pool_types_test.go new file mode 100644 index 00000000..72b62795 --- /dev/null +++ b/api/v1beta1/pool_types_test.go @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: MIT + +package v1beta1 + +import ( + "testing" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/utils/ptr" + + "github.com/mercedes-benz/garm-operator/pkg/filter" +) + +func TestPoolList_FilterByFields(t *testing.T) { + type fields struct { + TypeMeta metav1.TypeMeta + ListMeta metav1.ListMeta + Items []Pool + } + type args struct { + predicates []filter.Predicate[Pool] + } + tests := []struct { + name string + fields fields + args args + length int + }{ + { + name: "pool with spec already exist", + fields: fields{ + TypeMeta: metav1.TypeMeta{}, + ListMeta: metav1.ListMeta{}, + Items: []Pool{ + { + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "ubuntu-2004-large", + Namespace: "test", + }, + Spec: PoolSpec{ + ImageName: "ubuntu-2004", + Flavor: "large", + ProviderName: "openstack", + GitHubScopeRef: corev1.TypedLocalObjectReference{ + Name: "test", + Kind: "Enterprise", + APIGroup: ptr.To[string]("github.com"), + }, + }, + }, + { + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "ubuntu-2204-large", + Namespace: "test", + }, + Spec: PoolSpec{ + ImageName: "ubuntu-2204", + Flavor: "large", + ProviderName: "openstack", + GitHubScopeRef: corev1.TypedLocalObjectReference{ + Name: "test", + Kind: "Enterprise", + APIGroup: ptr.To[string]("github.com"), + }, + }, + }, + }, + }, + args: args{ + predicates: []filter.Predicate[Pool]{ + MatchesImage("ubuntu-2204"), + MatchesFlavor("large"), + MatchesProvider("openstack"), + MatchesGitHubScope("test", "Enterprise"), + }, + }, + length: 1, + }, + { + name: "pool with spec does not exist", + fields: fields{ + TypeMeta: metav1.TypeMeta{}, + ListMeta: metav1.ListMeta{}, + Items: []Pool{ + { + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "ubuntu-2004-large", + Namespace: "test", + }, + Spec: PoolSpec{ + ImageName: "ubuntu-2004", + Flavor: "large", + ProviderName: "openstack", + GitHubScopeRef: corev1.TypedLocalObjectReference{ + Name: "test", + Kind: "Enterprise", + APIGroup: ptr.To[string]("github.com"), + }, + }, + }, + { + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: "ubuntu-2204-large", + Namespace: "test", + }, + Spec: PoolSpec{ + ImageName: "ubuntu-2204", + Flavor: "large", + ProviderName: "openstack", + GitHubScopeRef: corev1.TypedLocalObjectReference{ + Name: "test", + Kind: "Enterprise", + APIGroup: ptr.To[string]("github.com"), + }, + }, + }, + }, + }, + args: args{ + predicates: []filter.Predicate[Pool]{ + MatchesImage("ubuntu-2404"), + MatchesFlavor("large"), + MatchesProvider("openstack"), + MatchesGitHubScope("test", "Enterprise"), + }, + }, + length: 0, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &PoolList{ + TypeMeta: tt.fields.TypeMeta, + ListMeta: tt.fields.ListMeta, + Items: tt.fields.Items, + } + + filteredItems := filter.Match(p.Items, tt.args.predicates...) + + if len(filteredItems) != tt.length { + t.Errorf("FilterByFields() = %v, want %v", len(p.Items), tt.length) + } + }) + } +} diff --git a/api/v1beta1/pool_webhook.go b/api/v1beta1/pool_webhook.go new file mode 100644 index 00000000..fc7972d4 --- /dev/null +++ b/api/v1beta1/pool_webhook.go @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: MIT + +package v1beta1 + +import ( + "encoding/json" + "fmt" + "reflect" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +// log is for logging in this package. +var ( + poollog = logf.Log.WithName("pool-resource") + c client.Client +) + +func (r *Pool) SetupWebhookWithManager(mgr ctrl.Manager) error { + c = mgr.GetClient() + return ctrl.NewWebhookManagedBy(mgr). + For(r). + Complete() +} + +//+kubebuilder:webhook:path=/validate-garm-operator-mercedes-benz-com-v1alpha1-pool,mutating=false,failurePolicy=fail,sideEffects=None,groups=garm-operator.mercedes-benz.com,resources=pools,verbs=create;update,versions=v1alpha1,name=validate.pool.garm-operator.mercedes-benz.com,admissionReviewVersions=v1 + +var _ webhook.Validator = &Pool{} + +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type +func (r *Pool) ValidateCreate() (admission.Warnings, error) { + poollog.Info("validate create request", "name", r.Name, "namespace", r.Namespace) + + if err := r.validateExtraSpec(); err != nil { + return nil, apierrors.NewInvalid(schema.GroupKind{Group: GroupVersion.Group, Kind: "Pool"}, + r.Name, + field.ErrorList{err}, + ) + } + + return nil, nil +} + +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type +func (r *Pool) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { + poollog.Info("validate update", "name", r.Name, "namespace", r.Namespace) + + oldCRD, ok := old.(*Pool) + if !ok { + return nil, apierrors.NewBadRequest("failed to convert runtime.Object to Pool CRD") + } + + // if the object is being deleted, skip validation + if r.GetDeletionTimestamp() == nil { + if err := r.validateExtraSpec(); err != nil { + return nil, apierrors.NewInvalid(schema.GroupKind{Group: GroupVersion.Group, Kind: "Pool"}, + r.Name, + field.ErrorList{err}, + ) + } + + if err := r.validateProviderName(oldCRD); err != nil { + return nil, apierrors.NewInvalid( + schema.GroupKind{Group: GroupVersion.Group, Kind: "Pool"}, + r.Name, + field.ErrorList{err}, + ) + } + + if err := r.validateGitHubScope(oldCRD); err != nil { + return nil, apierrors.NewInvalid( + schema.GroupKind{Group: GroupVersion.Group, Kind: "Pool"}, + r.Name, + field.ErrorList{err}, + ) + } + } + + return nil, nil +} + +func (r *Pool) validateProviderName(old *Pool) *field.Error { + poollog.Info("validate spec.providerName", "spec.providerName", r.Spec.ProviderName) + fieldPath := field.NewPath("spec").Child("providerName") + n := r.Spec.ProviderName + o := old.Spec.ProviderName + if n != o { + return field.Invalid( + fieldPath, + r.Spec.ProviderName, + fmt.Errorf("can not change provider of an existing pool. Old name: %s, new name: %s", o, n).Error(), + ) + } + return nil +} + +func (r *Pool) validateExtraSpec() *field.Error { + extraSpecs := json.RawMessage([]byte{}) + fieldPath := field.NewPath("spec").Child("extraSpecs") + err := json.Unmarshal([]byte(r.Spec.ExtraSpecs), &extraSpecs) + if err != nil { + return field.Invalid( + fieldPath, + r.Spec.ExtraSpecs, + fmt.Errorf("can not unmarshal extraSpecs: %s", err.Error()).Error(), + ) + } + + return nil +} + +func (r *Pool) validateGitHubScope(old *Pool) *field.Error { + poollog.Info("validate spec.githubScopeRef", "spec.githubScopeRef", r.Spec.GitHubScopeRef) + fieldPath := field.NewPath("spec").Child("githubScopeRef") + n := r.Spec.GitHubScopeRef + o := old.Spec.GitHubScopeRef + if !reflect.DeepEqual(n, o) { + return field.Invalid( + fieldPath, + r.Spec.ProviderName, + fmt.Errorf("can not change githubScopeRef of an existing pool. Old name: %+v, new name: %+v", o, n).Error(), + ) + } + return nil +} + +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type +func (r *Pool) ValidateDelete() (admission.Warnings, error) { + poollog.Info("validate delete", "name", r.Name, "namespace", r.Namespace) + return nil, nil +} diff --git a/api/v1beta1/repository_types.go b/api/v1beta1/repository_types.go new file mode 100644 index 00000000..adfb9574 --- /dev/null +++ b/api/v1beta1/repository_types.go @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: MIT + +package v1beta1 + +import ( + "github.com/cloudbase/garm/params" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/mercedes-benz/garm-operator/pkg/conditions" +) + +// RepositorySpec defines the desired state of Repository +type RepositorySpec struct { + CredentialsRef corev1.TypedLocalObjectReference `json:"credentialsRef"` + Owner string `json:"owner"` + + // WebhookSecretRef represents a secret that should be used for the webhook + WebhookSecretRef SecretRef `json:"webhookSecretRef"` + PoolBalancerType params.PoolBalancerType `json:"poolBalancerType,omitempty"` +} + +// RepositoryStatus defines the observed state of Repository +type RepositoryStatus struct { + ID string `json:"id"` + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:resource:path=repositories,scope=Namespaced,categories=garm,shortName=repo +//+kubebuilder:subresource:status +//+kubebuilder:storageversion +//+kubebuilder:printcolumn:name="ID",type="string",JSONPath=".status.id",description="Repository ID" +//+kubebuilder:printcolumn:name="Ready",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +//+kubebuilder:printcolumn:name="Error",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].message",priority=1 +//+kubebuilder:printcolumn:name="Pool_Manager_Failure",type="string",JSONPath=`.status.conditions[?(@.reason=='PoolManagerFailure')].message`,priority=1 +//+kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp",description="Time duration since creation of Repository" + +// 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"` +} + +func (r *Repository) SetConditions(conditions []metav1.Condition) { + r.Status.Conditions = conditions +} + +func (r *Repository) GetConditions() []metav1.Condition { + return r.Status.Conditions +} + +func (r *Repository) GetCredentialsName() string { + return r.Spec.CredentialsRef.Name +} + +func (r *Repository) GetID() string { + return r.Status.ID +} + +func (r *Repository) GetName() string { + return r.ObjectMeta.Name +} + +func (r *Repository) GetPoolManagerIsRunning() bool { + condition := conditions.Get(r, conditions.PoolManager) + if condition == nil { + return false + } + + return condition.Status == TrueAsString +} + +func (r *Repository) GetPoolManagerFailureReason() string { + condition := conditions.Get(r, conditions.PoolManager) + if condition == nil { + return "" + } + + if condition.Reason == string(conditions.PoolManagerFailureReason) { + return condition.Message + } + + return "" +} + +func (r *Repository) GetKind() string { + return r.Kind +} + +//+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/api/v1beta1/repository_webhook.go b/api/v1beta1/repository_webhook.go new file mode 100644 index 00000000..81646005 --- /dev/null +++ b/api/v1beta1/repository_webhook.go @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT + +package v1beta1 + +import ( + "fmt" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/validation/field" + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +// log is for logging in this package. +var repositorylog = logf.Log.WithName("repository-resource") + +func (r *Repository) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(r). + Complete() +} + +//+kubebuilder:webhook:path=/validate-garm-operator-mercedes-benz-com-v1alpha1-repository,mutating=false,failurePolicy=fail,sideEffects=None,groups=garm-operator.mercedes-benz.com,resources=repositories,verbs=create;update,versions=v1alpha1,name=validate.repository.garm-operator.mercedes-benz.com,admissionReviewVersions=v1 + +var _ webhook.Validator = &Repository{} + +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type +func (r *Repository) ValidateCreate() (admission.Warnings, error) { + repositorylog.Info("validate create", "name", r.Name, "namespace", r.Namespace) + return nil, nil +} + +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type +func (r *Repository) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { + repositorylog.Info("validate update", "name", r.Name, "namespace", r.Namespace) + + oldCRD, ok := old.(*Repository) + if !ok { + return nil, apierrors.NewBadRequest("failed to convert runtime.Object to Repository CRD") + } + + if err := r.validateRepoOwnerName(oldCRD); err != nil { + return nil, apierrors.NewInvalid( + schema.GroupKind{Group: GroupVersion.Group, Kind: "Repository"}, + r.Name, + field.ErrorList{err}, + ) + } + return nil, nil +} + +func (r *Repository) validateRepoOwnerName(old *Repository) *field.Error { + repositorylog.Info("validate spec.owner", "spec.owner", r.Spec.Owner) + fieldPath := field.NewPath("spec").Child("owner") + n := r.Spec.Owner + o := old.Spec.Owner + if n != o { + return field.Invalid( + fieldPath, + r.Spec.Owner, + fmt.Errorf("cannot change owner of repository resource. Old name: %s, new name: %s", o, n).Error(), + ) + } + return nil +} + +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type +func (r *Repository) ValidateDelete() (admission.Warnings, error) { + repositorylog.Info("validate delete", "name", r.Name, "namespace", r.Namespace) + return nil, nil +} diff --git a/api/v1beta1/runner_types.go b/api/v1beta1/runner_types.go new file mode 100644 index 00000000..55288156 --- /dev/null +++ b/api/v1beta1/runner_types.go @@ -0,0 +1,109 @@ +// SPDX-License-Identifier: MIT + +package v1beta1 + +import ( + commonParams "github.com/cloudbase/garm-provider-common/params" + "github.com/cloudbase/garm/params" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// RunnerSpec defines the desired state of Runner +type RunnerSpec struct{} + +// RunnerStatus defines the observed state of Runner +type RunnerStatus struct { + // ID is the database ID of this instance. + ID string `json:"id,omitempty"` + + // PeoviderID is the unique ID the provider associated + // with the compute instance. We use this to identify the + // instance in the provider. + ProviderID string `json:"providerId,omitempty"` + + // AgentID is the github runner agent ID. + AgentID int64 `json:"agentId"` + + // Name is the name associated with an instance. Depending on + // the provider, this may or may not be useful in the context of + // the provider, but we can use it internally to identify the + // instance. + Name string `json:"name,omitempty"` + + // OSType is the operating system type. For now, only Linux and + // Windows are supported. + OSType commonParams.OSType `json:"osType,omitempty"` + + // OSName is the name of the OS. Eg: ubuntu, centos, etc. + OSName string `json:"osName,omitempty"` + + // OSVersion is the version of the operating system. + OSVersion string `json:"osVersion,omitempty"` + + // OSArch is the operating system architecture. + OSArch commonParams.OSArch `json:"osArch,omitempty"` + + // Addresses is a list of IP addresses the provider reports + // for this instance. + Addresses []commonParams.Address `json:"addresses,omitempty"` + + // Status is the status of the instance inside the provider (eg: running, stopped, etc) + Status commonParams.InstanceStatus `json:"status,omitempty"` + + // RunnerStatus is the github runner status as it appears on GitHub. + InstanceStatus params.RunnerStatus `json:"instanceStatus,omitempty"` + + // PoolID is the ID of the garm pool to which a runner belongs. + PoolID string `json:"poolId,omitempty"` + + // ProviderFault holds any error messages captured from the IaaS provider that is + // responsible for managing the lifecycle of the runner. + ProviderFault string `json:"providerFault,omitempty"` + + // StatusMessages is a list of status messages sent back by the runner as it sets itself + // up. + + //// UpdatedAt is the timestamp of the last update to this runner. + // UpdatedAt time.Time `json:"updated_at"` + + // GithubRunnerGroup is the github runner group to which the runner belongs. + // The runner group must be created by someone with access to the enterprise. + GitHubRunnerGroup string `json:"githubRunnerGroup"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:resource:path=runners,scope=Namespaced,categories=garm,shortName=run +//+kubebuilder:subresource:status +//+kubebuilder:storageversion +//+kubebuilder:printcolumn:name="ID",type="string",JSONPath=".status.id",description="Runner ID" +//+kubebuilder:printcolumn:name="Pool",type="string",JSONPath=".status.poolId",description="Pool CR Name" +//+kubebuilder:printcolumn:name="Garm Runner Status",type="string",JSONPath=".status.status",description="Garm Runner Status" +//+kubebuilder:printcolumn:name="Provider Runner Status",type="string",JSONPath=".status.instanceStatus",description="Provider Runner Status" +//+kubebuilder:printcolumn:name="Provider ID",type="string",JSONPath=".status.providerId",description="Provider ID",priority=1 +//+kubebuilder:printcolumn:name="Agent ID",type="string",JSONPath=".status.agentId",description="Agent ID",priority=1 +//+kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" + +// Runner is the Schema for the runners API +type Runner struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec RunnerSpec `json:"spec,omitempty"` + Status RunnerStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// RunnerList contains a list of Runner +type RunnerList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Runner `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Runner{}, &RunnerList{}) +} diff --git a/api/v1beta1/shared.go b/api/v1beta1/shared.go new file mode 100644 index 00000000..90086eb6 --- /dev/null +++ b/api/v1beta1/shared.go @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT + +package v1beta1 + +import ( + "fmt" +) + +type GitHubScopeKind string + +const ( + EnterpriseScope GitHubScopeKind = "Enterprise" + OrganizationScope GitHubScopeKind = "Organization" + RepositoryScope GitHubScopeKind = "Repository" +) + +// +k8s:deepcopy-gen=false +type GitHubScope interface { + GetKind() string + GetCredentialsName() string + GetID() string + GetName() string + GetPoolManagerIsRunning() bool + GetPoolManagerFailureReason() string +} + +func ToGitHubScopeKind(kind string) (GitHubScopeKind, error) { + switch kind { + case string(EnterpriseScope), string(OrganizationScope), string(RepositoryScope): + return GitHubScopeKind(kind), nil + default: + return GitHubScopeKind(""), fmt.Errorf("can not convert kind %s to valid GitHubScopeKind: Enterprise, Organization, Repository", kind) + } +} + +type SecretRef struct { + // Name of the kubernetes secret to use + Name string `json:"name"` + // Key is the key in the secret's data map for this value + Key string `json:"key"` +} + +const ( + TrueAsString = "True" + FalseAsString = "False" +) diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go new file mode 100644 index 00000000..d78c7430 --- /dev/null +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -0,0 +1,902 @@ +//go:build !ignore_autogenerated + +// SPDX-License-Identifier: MIT + +// Code generated by controller-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "github.com/cloudbase/garm-provider-common/params" + "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Enterprise) DeepCopyInto(out *Enterprise) { + *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 Enterprise. +func (in *Enterprise) DeepCopy() *Enterprise { + if in == nil { + return nil + } + out := new(Enterprise) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Enterprise) 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 *EnterpriseList) DeepCopyInto(out *EnterpriseList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Enterprise, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnterpriseList. +func (in *EnterpriseList) DeepCopy() *EnterpriseList { + if in == nil { + return nil + } + out := new(EnterpriseList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *EnterpriseList) 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 *EnterpriseSpec) DeepCopyInto(out *EnterpriseSpec) { + *out = *in + in.CredentialsRef.DeepCopyInto(&out.CredentialsRef) + out.WebhookSecretRef = in.WebhookSecretRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EnterpriseSpec. +func (in *EnterpriseSpec) DeepCopy() *EnterpriseSpec { + if in == nil { + return nil + } + out := new(EnterpriseSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EnterpriseStatus) DeepCopyInto(out *EnterpriseStatus) { + *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 EnterpriseStatus. +func (in *EnterpriseStatus) DeepCopy() *EnterpriseStatus { + if in == nil { + return nil + } + out := new(EnterpriseStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GarmServerConfig) DeepCopyInto(out *GarmServerConfig) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GarmServerConfig. +func (in *GarmServerConfig) DeepCopy() *GarmServerConfig { + if in == nil { + return nil + } + out := new(GarmServerConfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GarmServerConfig) 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 *GarmServerConfigList) DeepCopyInto(out *GarmServerConfigList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]GarmServerConfig, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GarmServerConfigList. +func (in *GarmServerConfigList) DeepCopy() *GarmServerConfigList { + if in == nil { + return nil + } + out := new(GarmServerConfigList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GarmServerConfigList) 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 *GarmServerConfigSpec) DeepCopyInto(out *GarmServerConfigSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GarmServerConfigSpec. +func (in *GarmServerConfigSpec) DeepCopy() *GarmServerConfigSpec { + if in == nil { + return nil + } + out := new(GarmServerConfigSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GarmServerConfigStatus) DeepCopyInto(out *GarmServerConfigStatus) { + *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 GarmServerConfigStatus. +func (in *GarmServerConfigStatus) DeepCopy() *GarmServerConfigStatus { + if in == nil { + return nil + } + out := new(GarmServerConfigStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GitHubCredential) DeepCopyInto(out *GitHubCredential) { + *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 GitHubCredential. +func (in *GitHubCredential) DeepCopy() *GitHubCredential { + if in == nil { + return nil + } + out := new(GitHubCredential) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GitHubCredential) 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 *GitHubCredentialList) DeepCopyInto(out *GitHubCredentialList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]GitHubCredential, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitHubCredentialList. +func (in *GitHubCredentialList) DeepCopy() *GitHubCredentialList { + if in == nil { + return nil + } + out := new(GitHubCredentialList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GitHubCredentialList) 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 *GitHubCredentialSpec) DeepCopyInto(out *GitHubCredentialSpec) { + *out = *in + in.EndpointRef.DeepCopyInto(&out.EndpointRef) + out.SecretRef = in.SecretRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitHubCredentialSpec. +func (in *GitHubCredentialSpec) DeepCopy() *GitHubCredentialSpec { + if in == nil { + return nil + } + out := new(GitHubCredentialSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GitHubCredentialStatus) DeepCopyInto(out *GitHubCredentialStatus) { + *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 GitHubCredentialStatus. +func (in *GitHubCredentialStatus) DeepCopy() *GitHubCredentialStatus { + if in == nil { + return nil + } + out := new(GitHubCredentialStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GitHubEndpoint) DeepCopyInto(out *GitHubEndpoint) { + *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 GitHubEndpoint. +func (in *GitHubEndpoint) DeepCopy() *GitHubEndpoint { + if in == nil { + return nil + } + out := new(GitHubEndpoint) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GitHubEndpoint) 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 *GitHubEndpointList) DeepCopyInto(out *GitHubEndpointList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]GitHubEndpoint, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitHubEndpointList. +func (in *GitHubEndpointList) DeepCopy() *GitHubEndpointList { + if in == nil { + return nil + } + out := new(GitHubEndpointList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *GitHubEndpointList) 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 *GitHubEndpointSpec) DeepCopyInto(out *GitHubEndpointSpec) { + *out = *in + if in.CACertBundle != nil { + in, out := &in.CACertBundle, &out.CACertBundle + *out = make([]byte, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitHubEndpointSpec. +func (in *GitHubEndpointSpec) DeepCopy() *GitHubEndpointSpec { + if in == nil { + return nil + } + out := new(GitHubEndpointSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GitHubEndpointStatus) DeepCopyInto(out *GitHubEndpointStatus) { + *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 GitHubEndpointStatus. +func (in *GitHubEndpointStatus) DeepCopy() *GitHubEndpointStatus { + if in == nil { + return nil + } + out := new(GitHubEndpointStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Image) DeepCopyInto(out *Image) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Image. +func (in *Image) DeepCopy() *Image { + if in == nil { + return nil + } + out := new(Image) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Image) 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 *ImageList) DeepCopyInto(out *ImageList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Image, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageList. +func (in *ImageList) DeepCopy() *ImageList { + if in == nil { + return nil + } + out := new(ImageList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ImageList) 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 *ImageSpec) DeepCopyInto(out *ImageSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageSpec. +func (in *ImageSpec) DeepCopy() *ImageSpec { + if in == nil { + return nil + } + out := new(ImageSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ImageStatus) DeepCopyInto(out *ImageStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImageStatus. +func (in *ImageStatus) DeepCopy() *ImageStatus { + if in == nil { + return nil + } + out := new(ImageStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Organization) DeepCopyInto(out *Organization) { + *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 Organization. +func (in *Organization) DeepCopy() *Organization { + if in == nil { + return nil + } + out := new(Organization) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Organization) 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 *OrganizationList) DeepCopyInto(out *OrganizationList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Organization, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OrganizationList. +func (in *OrganizationList) DeepCopy() *OrganizationList { + if in == nil { + return nil + } + out := new(OrganizationList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *OrganizationList) 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 *OrganizationSpec) DeepCopyInto(out *OrganizationSpec) { + *out = *in + in.CredentialsRef.DeepCopyInto(&out.CredentialsRef) + out.WebhookSecretRef = in.WebhookSecretRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OrganizationSpec. +func (in *OrganizationSpec) DeepCopy() *OrganizationSpec { + if in == nil { + return nil + } + out := new(OrganizationSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OrganizationStatus) DeepCopyInto(out *OrganizationStatus) { + *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 OrganizationStatus. +func (in *OrganizationStatus) DeepCopy() *OrganizationStatus { + if in == nil { + return nil + } + out := new(OrganizationStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Pool) DeepCopyInto(out *Pool) { + *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 Pool. +func (in *Pool) DeepCopy() *Pool { + if in == nil { + return nil + } + out := new(Pool) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Pool) 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 *PoolList) DeepCopyInto(out *PoolList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Pool, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PoolList. +func (in *PoolList) DeepCopy() *PoolList { + if in == nil { + return nil + } + out := new(PoolList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PoolList) 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 *PoolSpec) DeepCopyInto(out *PoolSpec) { + *out = *in + in.GitHubScopeRef.DeepCopyInto(&out.GitHubScopeRef) + if in.Tags != nil { + in, out := &in.Tags, &out.Tags + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PoolSpec. +func (in *PoolSpec) DeepCopy() *PoolSpec { + if in == nil { + return nil + } + out := new(PoolSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PoolStatus) DeepCopyInto(out *PoolStatus) { + *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 PoolStatus. +func (in *PoolStatus) DeepCopy() *PoolStatus { + if in == nil { + return nil + } + out := new(PoolStatus) + 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 + in.CredentialsRef.DeepCopyInto(&out.CredentialsRef) + out.WebhookSecretRef = in.WebhookSecretRef +} + +// 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 *Runner) DeepCopyInto(out *Runner) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Runner. +func (in *Runner) DeepCopy() *Runner { + if in == nil { + return nil + } + out := new(Runner) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Runner) 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 *RunnerList) DeepCopyInto(out *RunnerList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Runner, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RunnerList. +func (in *RunnerList) DeepCopy() *RunnerList { + if in == nil { + return nil + } + out := new(RunnerList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *RunnerList) 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 *RunnerSpec) DeepCopyInto(out *RunnerSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RunnerSpec. +func (in *RunnerSpec) DeepCopy() *RunnerSpec { + if in == nil { + return nil + } + out := new(RunnerSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RunnerStatus) DeepCopyInto(out *RunnerStatus) { + *out = *in + if in.Addresses != nil { + in, out := &in.Addresses, &out.Addresses + *out = make([]params.Address, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RunnerStatus. +func (in *RunnerStatus) DeepCopy() *RunnerStatus { + if in == nil { + return nil + } + out := new(RunnerStatus) + 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/cmd/main.go b/cmd/main.go index b6fe64a8..f7821a9f 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -22,6 +22,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook" garmoperatorv1alpha1 "github.com/mercedes-benz/garm-operator/api/v1alpha1" + garmoperatorv1beta1 "github.com/mercedes-benz/garm-operator/api/v1beta1" garmcontroller "github.com/mercedes-benz/garm-operator/internal/controller" "github.com/mercedes-benz/garm-operator/pkg/client" "github.com/mercedes-benz/garm-operator/pkg/config" @@ -37,6 +38,7 @@ func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(garmoperatorv1alpha1.AddToScheme(scheme)) + utilruntime.Must(garmoperatorv1beta1.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme } @@ -71,7 +73,7 @@ func run() error { return nil } - ctrl.SetLogger(textlogger.NewLogger((textlogger.NewConfig(textlogger.Verbosity(config.Config.Operator.LogVerbosityLevel))))) + ctrl.SetLogger(textlogger.NewLogger(textlogger.NewConfig(textlogger.Verbosity(config.Config.Operator.LogVerbosityLevel)))) var watchNamespaces map[string]cache.Config if config.Config.Operator.WatchNamespace != "" { @@ -151,18 +153,6 @@ func run() error { return fmt.Errorf("unable to create controller Pool: %w", err) } - if err = (&garmoperatorv1alpha1.Pool{}).SetupWebhookWithManager(mgr); err != nil { - return fmt.Errorf("unable to create webhook Pool: %w", err) - } - - if err = (&garmoperatorv1alpha1.Image{}).SetupWebhookWithManager(mgr); err != nil { - return fmt.Errorf("unable to create webhook Image: %w", err) - } - - if err = (&garmoperatorv1alpha1.Repository{}).SetupWebhookWithManager(mgr); err != nil { - return fmt.Errorf("unable to create webhook Repository: %w", err) - } - if err = (&garmcontroller.OrganizationReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), @@ -188,14 +178,14 @@ func run() error { } if config.Config.Operator.RunnerReconciliation { - eventChan := make(chan event.GenericEvent) + runnerEvents := make(chan event.GenericEvent) runnerReconciler := &garmcontroller.RunnerReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), } - // setup controller so it can reconcile if events from eventChan are queued - if err = runnerReconciler.SetupWithManager(mgr, eventChan, + // setup controller so it can reconcile if events from runnerEvents are queued + if err = runnerReconciler.SetupWithManager(mgr, runnerEvents, controller.Options{ MaxConcurrentReconciles: config.Config.Operator.RunnerConcurrency, }, @@ -205,10 +195,70 @@ func run() error { // fetch runner instances periodically and enqueue reconcile events for runner ctrl if external system has changed ctx, cancel := context.WithCancel(ctx) - go runnerReconciler.PollRunnerInstances(ctx, eventChan) + go runnerReconciler.PollRunnerInstances(ctx, runnerEvents) defer cancel() } + if err = (&garmcontroller.GarmServerConfigReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor("garm-server-config-controller"), + }).SetupWithManager(mgr); err != nil { + return fmt.Errorf("unable to create controller GarmServerConfig: %w", err) + } + + if err = (&garmcontroller.GitHubEndpointReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor("github-endpoint-controller"), + }).SetupWithManager(mgr); err != nil { + return fmt.Errorf("unable to create controller GitHubEndpoint: %w", err) + } + + if err = (&garmcontroller.GitHubCredentialReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor("github-credentials-controller"), + }).SetupWithManager(mgr); err != nil { + return fmt.Errorf("unable to create controller GitHubCredential: %w", err) + } + + // webhooks + if err = (&garmoperatorv1alpha1.Enterprise{}).SetupWebhookWithManager(mgr); err != nil { + return fmt.Errorf("unable to create webhook Enterprise: %w", err) + } + + if err = (&garmoperatorv1alpha1.Organization{}).SetupWebhookWithManager(mgr); err != nil { + return fmt.Errorf("unable to create webhook Organization: %w", err) + } + + if err = (&garmoperatorv1alpha1.Pool{}).SetupWebhookWithManager(mgr); err != nil { + return fmt.Errorf("unable to create webhook Pool: %w", err) + } + + if err = (&garmoperatorv1beta1.Pool{}).SetupWebhookWithManager(mgr); err != nil { + return fmt.Errorf("unable to create webhook Pool: %w", err) + } + + if err = (&garmoperatorv1alpha1.Image{}).SetupWebhookWithManager(mgr); err != nil { + return fmt.Errorf("unable to create webhook Image: %w", err) + } + + if err = (&garmoperatorv1beta1.Image{}).SetupWebhookWithManager(mgr); err != nil { + return fmt.Errorf("unable to create webhook Image: %w", err) + } + + if err = (&garmoperatorv1alpha1.Repository{}).SetupWebhookWithManager(mgr); err != nil { + return fmt.Errorf("unable to create webhook Repository: %w", err) + } + + if err = (&garmoperatorv1beta1.Repository{}).SetupWebhookWithManager(mgr); err != nil { + return fmt.Errorf("unable to create webhook Repository: %w", err) + } + + if err = (&garmoperatorv1alpha1.Runner{}).SetupWebhookWithManager(mgr); err != nil { + return fmt.Errorf("unable to create webhook Runner: %w", err) + } //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/config/crd/bases/garm-operator.mercedes-benz.com_enterprises.yaml b/config/crd/bases/garm-operator.mercedes-benz.com_enterprises.yaml index e68f97d2..b63264db 100644 --- a/config/crd/bases/garm-operator.mercedes-benz.com_enterprises.yaml +++ b/config/crd/bases/garm-operator.mercedes-benz.com_enterprises.yaml @@ -163,6 +163,176 @@ spec: type: object type: object served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: Enterprise ID + jsonPath: .status.id + name: ID + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].message + name: Error + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.reason=='PoolManagerFailure')].message + name: Pool_Manager_Failure + priority: 1 + type: string + - description: Time duration since creation of Enterprise + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: Enterprise is the Schema for the enterprises 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: EnterpriseSpec defines the desired state of Enterprise + properties: + credentialsRef: + description: |- + TypedLocalObjectReference contains enough information to let you locate the + typed referenced object inside the same namespace. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + poolBalancerType: + type: string + webhookSecretRef: + description: WebhookSecretRef represents a secret that should be used + for the webhook + properties: + key: + description: Key is the key in the secret's data map for this + value + type: string + name: + description: Name of the kubernetes secret to use + type: string + required: + - key + - name + type: object + required: + - credentialsRef + - webhookSecretRef + type: object + status: + description: EnterpriseStatus defines the observed state of Enterprise + properties: + conditions: + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + 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 + id: + type: string + required: + - id + type: object + type: object + served: true storage: true subresources: status: {} diff --git a/config/crd/bases/garm-operator.mercedes-benz.com_garmserverconfigs.yaml b/config/crd/bases/garm-operator.mercedes-benz.com_garmserverconfigs.yaml new file mode 100644 index 00000000..13720ce4 --- /dev/null +++ b/config/crd/bases/garm-operator.mercedes-benz.com_garmserverconfigs.yaml @@ -0,0 +1,179 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: garmserverconfigs.garm-operator.mercedes-benz.com +spec: + group: garm-operator.mercedes-benz.com + names: + categories: + - garm + kind: GarmServerConfig + listKind: GarmServerConfigList + plural: garmserverconfigs + shortNames: + - server + singular: garmserverconfig + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Controller ID + jsonPath: .status.controllerId + name: ID + type: string + - description: Garm Version + jsonPath: .status.version + name: Version + type: string + - description: MetadataURL + jsonPath: .status.metadataUrl + name: MetadataURL + priority: 1 + type: string + - description: CallbackURL + jsonPath: .status.callbackUrl + name: CallbackURL + priority: 1 + type: string + - description: WebhookURL + jsonPath: .status.webhookUrl + name: WebhookURL + priority: 1 + type: string + - description: ControllerWebhookURL + jsonPath: .status.controllerWebhookUrl + name: ControllerWebhookURL + priority: 1 + type: string + - description: Time duration since creation of GarmServerConfig + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: GarmServerConfig is the Schema for the garmserverconfigs 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: GarmServerConfigSpec defines the desired state of GarmServerConfig + properties: + callbackUrl: + type: string + metadataUrl: + type: string + webhookUrl: + type: string + type: object + status: + description: GarmServerConfigStatus defines the observed state of GarmServerConfig + properties: + callbackUrl: + type: string + conditions: + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + 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 + controllerId: + type: string + controllerWebhookUrl: + type: string + hostname: + type: string + metadataUrl: + type: string + minimumJobAgeBackoff: + type: integer + version: + type: string + webhookUrl: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/garm-operator.mercedes-benz.com_githubcredential.yaml b/config/crd/bases/garm-operator.mercedes-benz.com_githubcredential.yaml new file mode 100644 index 00000000..81ee9818 --- /dev/null +++ b/config/crd/bases/garm-operator.mercedes-benz.com_githubcredential.yaml @@ -0,0 +1,214 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: githubcredential.garm-operator.mercedes-benz.com +spec: + group: garm-operator.mercedes-benz.com + names: + categories: + - garm + kind: GitHubCredential + listKind: GitHubCredentialList + plural: githubcredential + shortNames: + - creds + singular: githubcredential + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Credentials ID + jsonPath: .status.id + name: ID + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].message + name: Error + priority: 1 + type: string + - description: Authentication type + jsonPath: .spec.authType + name: AuthType + type: string + - description: GitHubEndpoint name these credentials are tied to + jsonPath: .spec.endpointRef.name + name: GitHubEndpoint + type: string + - description: Time duration since creation of GitHubCredential + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: GitHubCredential is the Schema for the githubcredential 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: GitHubCredentialSpec defines the desired state of GitHubCredential + properties: + appId: + description: if AuthType is app + format: int64 + type: integer + authType: + description: either pat or app + type: string + description: + type: string + endpointRef: + description: |- + TypedLocalObjectReference contains enough information to let you locate the + typed referenced object inside the same namespace. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + installationId: + format: int64 + type: integer + secretRef: + description: containing either privateKey or pat token + properties: + key: + description: Key is the key in the secret's data map for this + value + type: string + name: + description: Name of the kubernetes secret to use + type: string + required: + - key + - name + type: object + required: + - authType + - description + - endpointRef + type: object + status: + description: GitHubCredentialStatus defines the observed state of GitHubCredential + properties: + apiBaseUrl: + type: string + baseUrl: + type: string + conditions: + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + 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 + id: + format: int64 + type: integer + uploadBaseUrl: + type: string + required: + - apiBaseUrl + - baseUrl + - id + - uploadBaseUrl + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/garm-operator.mercedes-benz.com_githubcredentials.yaml b/config/crd/bases/garm-operator.mercedes-benz.com_githubcredentials.yaml new file mode 100644 index 00000000..a154795e --- /dev/null +++ b/config/crd/bases/garm-operator.mercedes-benz.com_githubcredentials.yaml @@ -0,0 +1,216 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: githubcredential.garm-operator.mercedes-benz.com +spec: + group: garm-operator.mercedes-benz.com + names: + categories: + - garm + kind: GitHubCredential + listKind: GitHubCredentialList + plural: githubcredential + shortNames: + - creds + singular: githubcredential + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: Credentials ID + jsonPath: .status.id + name: ID + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].message + name: Error + priority: 1 + type: string + - description: Authentication type + jsonPath: .spec.authType + name: AuthType + type: string + - description: GitHubEndpoint name these credentials are tied to + jsonPath: .spec.endpointRef.name + name: GitHubEndpoint + type: string + - description: Time duration since creation of GitHubCredential + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: GitHubCredential is the Schema for the githubcredential 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: GitHubCredentialSpec defines the desired state of GitHubCredential + properties: + appId: + description: if AuthType is app + format: int64 + type: integer + authType: + description: either pat or app + type: string + description: + type: string + endpointRef: + description: |- + TypedLocalObjectReference contains enough information to let you locate the + typed referenced object inside the same namespace. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + installationId: + format: int64 + type: integer + secretRef: + description: containing either privateKey or pat token + properties: + key: + description: + Key is the key in the secret's data map for this + value + type: string + name: + description: Name of the kubernetes secret to use + type: string + required: + - key + - name + type: object + required: + - authType + - description + - endpointRef + type: object + status: + description: GitHubCredentialStatus defines the observed state of GitHubCredential + properties: + apiBaseUrl: + type: string + baseUrl: + type: string + conditions: + items: + description: + "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + 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 + id: + format: int64 + type: integer + uploadBaseUrl: + type: string + required: + - apiBaseUrl + - baseUrl + - id + - uploadBaseUrl + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/garm-operator.mercedes-benz.com_githubendpoints.yaml b/config/crd/bases/garm-operator.mercedes-benz.com_githubendpoints.yaml new file mode 100644 index 00000000..bf26a54d --- /dev/null +++ b/config/crd/bases/garm-operator.mercedes-benz.com_githubendpoints.yaml @@ -0,0 +1,152 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.15.0 + name: githubendpoints.garm-operator.mercedes-benz.com +spec: + group: garm-operator.mercedes-benz.com + names: + categories: + - garm + kind: GitHubEndpoint + listKind: GitHubEndpointList + plural: githubendpoints + shortNames: + - gep + singular: githubendpoint + scope: Namespaced + versions: + - additionalPrinterColumns: + - description: API Base URL + jsonPath: .spec.apiBaseUrl + name: URL + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].message + name: Error + priority: 1 + type: string + - description: Time duration since creation of GitHubEndpoint + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: GitHubEndpoint is the Schema for the githubendpoints 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: GitHubEndpointSpec defines the desired state of GitHubEndpoint + properties: + apiBaseUrl: + type: string + baseUrl: + type: string + caCertBundle: + description: 'TODO: This should be a secret reference' + format: byte + type: string + description: + type: string + uploadBaseUrl: + type: string + type: object + status: + description: GitHubEndpointStatus defines the observed state of GitHubEndpoint + properties: + conditions: + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + 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 + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/garm-operator.mercedes-benz.com_images.yaml b/config/crd/bases/garm-operator.mercedes-benz.com_images.yaml index fff55165..38523e64 100644 --- a/config/crd/bases/garm-operator.mercedes-benz.com_images.yaml +++ b/config/crd/bases/garm-operator.mercedes-benz.com_images.yaml @@ -19,7 +19,6 @@ spec: - additionalPrinterColumns: - jsonPath: .spec.tag name: Tag - priority: 1 type: string - jsonPath: .metadata.creationTimestamp name: Age @@ -62,5 +61,52 @@ spec: type: object type: object served: true + storage: false + subresources: {} + - additionalPrinterColumns: + - jsonPath: .spec.tag + name: Tag + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: Image is the Schema for the images 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: ImageSpec defines the desired state of Image + properties: + tag: + description: |- + Tag is the Name of the image in its registry + e.g. + - in openstack it can be the image name or id + - in k8s it can be the docker image name + tag + type: string + type: object + status: + description: ImageStatus defines the observed state of Image + type: object + type: object + served: true storage: true subresources: {} diff --git a/config/crd/bases/garm-operator.mercedes-benz.com_organizations.yaml b/config/crd/bases/garm-operator.mercedes-benz.com_organizations.yaml index 034ec234..bcbcd51b 100644 --- a/config/crd/bases/garm-operator.mercedes-benz.com_organizations.yaml +++ b/config/crd/bases/garm-operator.mercedes-benz.com_organizations.yaml @@ -163,6 +163,176 @@ spec: type: object type: object served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: Organization ID + jsonPath: .status.id + name: ID + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].message + name: Error + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.reason=='PoolManagerFailure')].message + name: Pool_Manager_Failure + priority: 1 + type: string + - description: Time duration since creation of Organization + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: Organization is the Schema for the organizations 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: OrganizationSpec defines the desired state of Organization + properties: + credentialsRef: + description: |- + TypedLocalObjectReference contains enough information to let you locate the + typed referenced object inside the same namespace. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + poolBalancerType: + type: string + webhookSecretRef: + description: WebhookSecretRef represents a secret that should be used + for the webhook + properties: + key: + description: Key is the key in the secret's data map for this + value + type: string + name: + description: Name of the kubernetes secret to use + type: string + required: + - key + - name + type: object + required: + - credentialsRef + - webhookSecretRef + type: object + status: + description: OrganizationStatus defines the observed state of Organization + properties: + conditions: + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + 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 + id: + type: string + required: + - id + type: object + type: object + served: true storage: true subresources: status: {} diff --git a/config/crd/bases/garm-operator.mercedes-benz.com_pools.yaml b/config/crd/bases/garm-operator.mercedes-benz.com_pools.yaml index 007dda97..8fb76249 100644 --- a/config/crd/bases/garm-operator.mercedes-benz.com_pools.yaml +++ b/config/crd/bases/garm-operator.mercedes-benz.com_pools.yaml @@ -237,6 +237,234 @@ spec: type: object type: object served: true + storage: false + subresources: + scale: + labelSelectorPath: .status.selector + specReplicasPath: .spec.minIdleRunners + statusReplicasPath: .status.longRunningIdleRunners + status: {} + - additionalPrinterColumns: + - jsonPath: .status.id + name: ID + type: string + - jsonPath: .spec.minIdleRunners + name: MinIdleRunners + type: string + - jsonPath: .spec.maxRunners + name: MaxRunners + type: string + - jsonPath: .spec.imageName + name: ImageName + priority: 1 + type: string + - jsonPath: .spec.flavor + name: Flavor + priority: 1 + type: string + - jsonPath: .spec.providerName + name: Provider + priority: 1 + type: string + - jsonPath: .spec.githubScopeRef.kind + name: ScopeType + priority: 1 + type: string + - jsonPath: .spec.githubScopeRef.name + name: ScopeName + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].message + name: Error + priority: 1 + type: string + - jsonPath: .spec.enabled + name: Enabled + priority: 1 + type: boolean + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: Pool is the Schema for the pools 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: + properties: + enabled: + type: boolean + extraSpecs: + type: string + flavor: + type: string + githubRunnerGroup: + type: string + githubScopeRef: + description: Defines in which Scope Runners a registered. Has a reference + to either an Enterprise, Org or Repo CRD + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + imageName: + description: The name of the image resource, this image resource must + exists in the same namespace as the pool + type: string + maxRunners: + type: integer + minIdleRunners: + default: 0 + type: integer + osArch: + type: string + osType: + type: string + providerName: + type: string + runnerBootstrapTimeout: + type: integer + runnerPrefix: + type: string + tags: + items: + type: string + type: array + required: + - enabled + - flavor + - githubScopeRef + - imageName + - maxRunners + - minIdleRunners + - osArch + - osType + - providerName + - runnerBootstrapTimeout + - tags + type: object + x-kubernetes-validations: + - message: minIdleRunners must be less than or equal to maxRunners + rule: self.minIdleRunners <= self.maxRunners + status: + description: PoolStatus defines the observed state of Pool + properties: + conditions: + items: + description: "Condition contains details for one aspect of the current + state of this API Resource.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + 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 + id: + type: string + longRunningIdleRunners: + type: integer + selector: + type: string + required: + - id + - longRunningIdleRunners + - selector + type: object + type: object + served: true storage: true subresources: scale: diff --git a/config/crd/bases/garm-operator.mercedes-benz.com_repositories.yaml b/config/crd/bases/garm-operator.mercedes-benz.com_repositories.yaml index 16a09d3c..a7ac6bf0 100644 --- a/config/crd/bases/garm-operator.mercedes-benz.com_repositories.yaml +++ b/config/crd/bases/garm-operator.mercedes-benz.com_repositories.yaml @@ -166,6 +166,179 @@ spec: type: object type: object served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: Repository ID + jsonPath: .status.id + name: ID + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: Ready + type: string + - jsonPath: .status.conditions[?(@.type=='Ready')].message + name: Error + priority: 1 + type: string + - jsonPath: .status.conditions[?(@.reason=='PoolManagerFailure')].message + name: Pool_Manager_Failure + priority: 1 + type: string + - description: Time duration since creation of Repository + jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + 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: + credentialsRef: + description: |- + TypedLocalObjectReference contains enough information to let you locate the + typed referenced object inside the same namespace. + properties: + apiGroup: + description: |- + APIGroup is the group for the resource being referenced. + If APIGroup is not specified, the specified Kind must be in the core API group. + For any other third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - kind + - name + type: object + x-kubernetes-map-type: atomic + owner: + type: string + poolBalancerType: + type: string + webhookSecretRef: + description: WebhookSecretRef represents a secret that should be used + for the webhook + properties: + key: + description: Key is the key in the secret's data map for this + value + type: string + name: + description: Name of the kubernetes secret to use + type: string + required: + - key + - name + type: object + required: + - credentialsRef + - owner + - webhookSecretRef + 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.\n---\nThis struct is intended for + direct use as an array at the field path .status.conditions. For + example,\n\n\n\ttype FooStatus struct{\n\t // Represents the + observations of a foo's current state.\n\t // Known .status.conditions.type + are: \"Available\", \"Progressing\", and \"Degraded\"\n\t // + +patchMergeKey=type\n\t // +patchStrategy=merge\n\t // +listType=map\n\t + \ // +listMapKey=type\n\t Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"`\n\n\n\t + \ // other fields\n\t}" + 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 + id: + type: string + required: + - id + type: object + type: object + served: true storage: true subresources: status: {} diff --git a/config/crd/bases/garm-operator.mercedes-benz.com_runners.yaml b/config/crd/bases/garm-operator.mercedes-benz.com_runners.yaml index 9b0fbc88..d1a94786 100644 --- a/config/crd/bases/garm-operator.mercedes-benz.com_runners.yaml +++ b/config/crd/bases/garm-operator.mercedes-benz.com_runners.yaml @@ -152,6 +152,143 @@ spec: type: object type: object served: true + storage: false + subresources: + status: {} + - additionalPrinterColumns: + - description: Runner ID + jsonPath: .status.id + name: ID + type: string + - description: Pool CR Name + jsonPath: .status.poolId + name: Pool + type: string + - description: Garm Runner Status + jsonPath: .status.status + name: Garm Runner Status + type: string + - description: Provider Runner Status + jsonPath: .status.instanceStatus + name: Provider Runner Status + type: string + - description: Provider ID + jsonPath: .status.providerId + name: Provider ID + priority: 1 + type: string + - description: Agent ID + jsonPath: .status.agentId + name: Agent ID + priority: 1 + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1beta1 + schema: + openAPIV3Schema: + description: Runner is the Schema for the runners 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: RunnerSpec defines the desired state of Runner + type: object + status: + description: RunnerStatus defines the observed state of Runner + properties: + addresses: + description: |- + Addresses is a list of IP addresses the provider reports + for this instance. + items: + properties: + address: + type: string + type: + type: string + required: + - address + - type + type: object + type: array + agentId: + description: AgentID is the github runner agent ID. + format: int64 + type: integer + githubRunnerGroup: + description: |- + GithubRunnerGroup is the github runner group to which the runner belongs. + The runner group must be created by someone with access to the enterprise. + type: string + id: + description: ID is the database ID of this instance. + type: string + instanceStatus: + description: RunnerStatus is the github runner status as it appears + on GitHub. + type: string + name: + description: |- + Name is the name associated with an instance. Depending on + the provider, this may or may not be useful in the context of + the provider, but we can use it internally to identify the + instance. + type: string + osArch: + description: OSArch is the operating system architecture. + type: string + osName: + description: 'OSName is the name of the OS. Eg: ubuntu, centos, etc.' + type: string + osType: + description: |- + OSType is the operating system type. For now, only Linux and + Windows are supported. + type: string + osVersion: + description: OSVersion is the version of the operating system. + type: string + poolId: + description: PoolID is the ID of the garm pool to which a runner belongs. + type: string + providerFault: + description: |- + ProviderFault holds any error messages captured from the IaaS provider that is + responsible for managing the lifecycle of the runner. + type: string + providerId: + description: |- + PeoviderID is the unique ID the provider associated + with the compute instance. We use this to identify the + instance in the provider. + type: string + status: + description: 'Status is the status of the instance inside the provider + (eg: running, stopped, etc)' + type: string + required: + - agentId + - githubRunnerGroup + type: object + type: object + served: true storage: true subresources: status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 13048f1a..d86c9289 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -2,34 +2,52 @@ # since it depends on service name and namespace that are out of this kustomize package. # It should be run by config/default resources: -- bases/garm-operator.mercedes-benz.com_enterprises.yaml -- bases/garm-operator.mercedes-benz.com_pools.yaml -- bases/garm-operator.mercedes-benz.com_organizations.yaml -- bases/garm-operator.mercedes-benz.com_images.yaml -- bases/garm-operator.mercedes-benz.com_repositories.yaml -- bases/garm-operator.mercedes-benz.com_runners.yaml + - bases/garm-operator.mercedes-benz.com_enterprises.yaml + - bases/garm-operator.mercedes-benz.com_pools.yaml + - bases/garm-operator.mercedes-benz.com_organizations.yaml + - bases/garm-operator.mercedes-benz.com_images.yaml + - bases/garm-operator.mercedes-benz.com_repositories.yaml + - bases/garm-operator.mercedes-benz.com_runners.yaml + - bases/garm-operator.mercedes-benz.com_garmserverconfigs.yaml + - bases/garm-operator.mercedes-benz.com_githubendpoints.yaml + - bases/garm-operator.mercedes-benz.com_githubcredentials.yaml #+kubebuilder:scaffold:crdkustomizeresource patches: -# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. -# patches here are for enabling the conversion webhook for each CRD -- path: patches/webhook_in_enterprises.yaml -- path: patches/webhook_in_pools.yaml -- path: patches/webhook_in_organizations.yaml -- path: patches/webhook_in_repositories.yaml -#- path: patches/webhook_in_runners.yaml -#+kubebuilder:scaffold:crdkustomizewebhookpatch + # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. + # patches here are for enabling the conversion webhook for each CRD + - path: patches/webhook_in_enterprises.yaml + - path: patches/webhook_in_pools.yaml + - path: patches/webhook_in_organizations.yaml + - path: patches/webhook_in_repositories.yaml + #- path: patches/webhook_in_runners.yaml + #- path: patches/webhook_in_garmserverconfigs.yaml + #- path: patches/webhook_in_githubendpoints.yaml + #- path: patches/webhook_in_githubcredentials.yaml + #- path: patches/webhook_in_enterprises.yaml + #- path: patches/webhook_in_organizations.yaml + #- path: patches/webhook_in_repositories.yaml + #- path: patches/webhook_in_pools.yaml + #- path: patches/webhook_in_images.yaml + #+kubebuilder:scaffold:crdkustomizewebhookpatch -# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. -# patches here are for enabling the CA injection for each CRD -- path: patches/cainjection_in_enterprises.yaml -- path: patches/cainjection_in_pools.yaml -- path: patches/cainjection_in_organizations.yaml -#- path: patches/cainjection_in_images.yaml -- path: patches/cainjection_in_repositories.yaml + # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. + # patches here are for enabling the CA injection for each CRD + - path: patches/cainjection_in_enterprises.yaml + - path: patches/cainjection_in_pools.yaml + - path: patches/cainjection_in_organizations.yaml + #- path: patches/cainjection_in_images.yaml + - path: patches/cainjection_in_repositories.yaml #- path: patches/cainjection_in_runners.yaml +#- path: patches/cainjection_in_garmserverconfigs.yaml +#- path: patches/cainjection_in_githubendpoints.yaml +#- path: patches/cainjection_in_githubcredentials.yaml +#- path: patches/cainjection_in_enterprises.yaml +#- path: patches/cainjection_in_organizations.yaml +#- path: patches/cainjection_in_repositories.yaml +#- path: patches/cainjection_in_pools.yaml #+kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. configurations: -- kustomizeconfig.yaml + - kustomizeconfig.yaml diff --git a/config/crd/patches/cainjection_in_controllers.yaml b/config/crd/patches/cainjection_in_controllers.yaml new file mode 100644 index 00000000..cb1481fc --- /dev/null +++ b/config/crd/patches/cainjection_in_controllers.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: controllers.garm-operator.mercedes-benz.com diff --git a/config/crd/patches/cainjection_in_garmserverconfigs.yaml b/config/crd/patches/cainjection_in_garmserverconfigs.yaml new file mode 100644 index 00000000..d44d781b --- /dev/null +++ b/config/crd/patches/cainjection_in_garmserverconfigs.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: garmserverconfigs.garm-operator.mercedes-benz.com diff --git a/config/crd/patches/cainjection_in_githubcredentials.yaml b/config/crd/patches/cainjection_in_githubcredentials.yaml new file mode 100644 index 00000000..1443d50c --- /dev/null +++ b/config/crd/patches/cainjection_in_githubcredentials.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: githubcredential.garm-operator.mercedes-benz.com diff --git a/config/crd/patches/cainjection_in_githubendpoints.yaml b/config/crd/patches/cainjection_in_githubendpoints.yaml new file mode 100644 index 00000000..a7eec53d --- /dev/null +++ b/config/crd/patches/cainjection_in_githubendpoints.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: githubendpoints.garm-operator.mercedes-benz.com diff --git a/config/crd/patches/webhook_in_controllers.yaml b/config/crd/patches/webhook_in_controllers.yaml new file mode 100644 index 00000000..66b57a03 --- /dev/null +++ b/config/crd/patches/webhook_in_controllers.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: controllers.garm-operator.mercedes-benz.com +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/patches/webhook_in_garmserverconfigs.yaml b/config/crd/patches/webhook_in_garmserverconfigs.yaml new file mode 100644 index 00000000..933dd9dd --- /dev/null +++ b/config/crd/patches/webhook_in_garmserverconfigs.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: garmserverconfigs.garm-operator.mercedes-benz.com +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/patches/webhook_in_githubcredentials.yaml b/config/crd/patches/webhook_in_githubcredentials.yaml new file mode 100644 index 00000000..2d15f685 --- /dev/null +++ b/config/crd/patches/webhook_in_githubcredentials.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: githubcredential.garm-operator.mercedes-benz.com +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/crd/patches/webhook_in_githubendpoints.yaml b/config/crd/patches/webhook_in_githubendpoints.yaml new file mode 100644 index 00000000..eed1eba2 --- /dev/null +++ b/config/crd/patches/webhook_in_githubendpoints.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: githubendpoints.garm-operator.mercedes-benz.com +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 5c5f0b84..ad13e96b 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -1,2 +1,8 @@ resources: - manager.yaml +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +images: +- name: controller + newName: controller + newTag: latest diff --git a/config/rbac/garmserverconfig_editor_role.yaml b/config/rbac/garmserverconfig_editor_role.yaml new file mode 100644 index 00000000..80602278 --- /dev/null +++ b/config/rbac/garmserverconfig_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit garmserverconfigs. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: garmserverconfig-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: garm-operator + app.kubernetes.io/part-of: garm-operator + app.kubernetes.io/managed-by: kustomize + name: garmserverconfig-editor-role +rules: +- apiGroups: + - garm-operator.mercedes-benz.com + resources: + - garmserverconfigs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - garm-operator.mercedes-benz.com + resources: + - garmserverconfigs/status + verbs: + - get diff --git a/config/rbac/garmserverconfig_viewer_role.yaml b/config/rbac/garmserverconfig_viewer_role.yaml new file mode 100644 index 00000000..5147500f --- /dev/null +++ b/config/rbac/garmserverconfig_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view garmserverconfigs. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: garmserverconfig-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: garm-operator + app.kubernetes.io/part-of: garm-operator + app.kubernetes.io/managed-by: kustomize + name: garmserverconfig-viewer-role +rules: +- apiGroups: + - garm-operator.mercedes-benz.com + resources: + - garmserverconfigs + verbs: + - get + - list + - watch +- apiGroups: + - garm-operator.mercedes-benz.com + resources: + - garmserverconfigs/status + verbs: + - get diff --git a/config/rbac/githubcredentials_editor_role.yaml b/config/rbac/githubcredentials_editor_role.yaml new file mode 100644 index 00000000..a3371d07 --- /dev/null +++ b/config/rbac/githubcredentials_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit githubcredential. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: githubcredential-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: garm-operator + app.kubernetes.io/part-of: garm-operator + app.kubernetes.io/managed-by: kustomize + name: githubcredential-editor-role +rules: + - apiGroups: + - garm-operator.mercedes-benz.com + resources: + - githubcredential + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - garm-operator.mercedes-benz.com + resources: + - githubcredential/status + verbs: + - get diff --git a/config/rbac/githubcredentials_viewer_role.yaml b/config/rbac/githubcredentials_viewer_role.yaml new file mode 100644 index 00000000..dbb5e4f0 --- /dev/null +++ b/config/rbac/githubcredentials_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view githubcredential. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: githubcredential-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: garm-operator + app.kubernetes.io/part-of: garm-operator + app.kubernetes.io/managed-by: kustomize + name: githubcredential-viewer-role +rules: + - apiGroups: + - garm-operator.mercedes-benz.com + resources: + - githubcredential + verbs: + - get + - list + - watch + - apiGroups: + - garm-operator.mercedes-benz.com + resources: + - githubcredential/status + verbs: + - get diff --git a/config/rbac/githubendpoint_editor_role.yaml b/config/rbac/githubendpoint_editor_role.yaml new file mode 100644 index 00000000..0a7d2860 --- /dev/null +++ b/config/rbac/githubendpoint_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit githubendpoints. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: githubendpoint-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: garm-operator + app.kubernetes.io/part-of: garm-operator + app.kubernetes.io/managed-by: kustomize + name: githubendpoint-editor-role +rules: +- apiGroups: + - garm-operator.mercedes-benz.com + resources: + - githubendpoints + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - garm-operator.mercedes-benz.com + resources: + - githubendpoints/status + verbs: + - get diff --git a/config/rbac/githubendpoint_viewer_role.yaml b/config/rbac/githubendpoint_viewer_role.yaml new file mode 100644 index 00000000..e47ba359 --- /dev/null +++ b/config/rbac/githubendpoint_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view githubendpoints. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: githubendpoint-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: garm-operator + app.kubernetes.io/part-of: garm-operator + app.kubernetes.io/managed-by: kustomize + name: githubendpoint-viewer-role +rules: +- apiGroups: + - garm-operator.mercedes-benz.com + resources: + - githubendpoints + verbs: + - get + - list + - watch +- apiGroups: + - garm-operator.mercedes-benz.com + resources: + - githubendpoints/status + verbs: + - get diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index c787d4af..b390bd2d 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -52,6 +52,84 @@ rules: - get - patch - update +- apiGroups: + - garm-operator.mercedes-benz.com + resources: + - garmserverconfigs + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - garm-operator.mercedes-benz.com + resources: + - garmserverconfigs/finalizers + verbs: + - update +- apiGroups: + - garm-operator.mercedes-benz.com + resources: + - garmserverconfigs/status + verbs: + - get + - patch + - update +- apiGroups: + - garm-operator.mercedes-benz.com + resources: + - githubcredential + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - garm-operator.mercedes-benz.com + resources: + - githubcredential/finalizers + verbs: + - update +- apiGroups: + - garm-operator.mercedes-benz.com + resources: + - githubcredential/status + verbs: + - get + - patch + - update +- apiGroups: + - garm-operator.mercedes-benz.com + resources: + - githubendpoints + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - garm-operator.mercedes-benz.com + resources: + - githubendpoints/finalizers + verbs: + - update +- apiGroups: + - garm-operator.mercedes-benz.com + resources: + - githubendpoints/status + verbs: + - get + - patch + - update - apiGroups: - garm-operator.mercedes-benz.com resources: diff --git a/config/samples/garm-operator_v1alpha1_enterprise.yaml b/config/samples/garm-operator_v1alpha1_enterprise.yaml index 87e4f840..b7ba75a7 100644 --- a/config/samples/garm-operator_v1alpha1_enterprise.yaml +++ b/config/samples/garm-operator_v1alpha1_enterprise.yaml @@ -10,7 +10,10 @@ metadata: name: enterprise-sample namespace: garm-operator-system spec: - credentialsName: GitHub-Actions + credentialsRef: + apiGroup: garm-operator.mercedes-benz.com + kind: GitHubCredentials + name: github-pat webhookSecretRef: key: "webhookSecret" name: "enterprise-webhook-secret" diff --git a/config/samples/garm-operator_v1alpha1_image.yaml b/config/samples/garm-operator_v1alpha1_image.yaml index 920c22e7..7634d50f 100644 --- a/config/samples/garm-operator_v1alpha1_image.yaml +++ b/config/samples/garm-operator_v1alpha1_image.yaml @@ -10,4 +10,5 @@ metadata: name: runner-default namespace: garm-operator-system spec: - tag: default-runner:linux-ubuntu-22.04-amd64-main-e4de304 + tag: localhost:5000/runner:linux-ubuntu-22:04-x86_64 + diff --git a/config/samples/garm-operator_v1alpha1_organization.yaml b/config/samples/garm-operator_v1alpha1_organization.yaml index 5706116a..413d8300 100644 --- a/config/samples/garm-operator_v1alpha1_organization.yaml +++ b/config/samples/garm-operator_v1alpha1_organization.yaml @@ -13,7 +13,10 @@ spec: webhookSecretRef: key: "webhookSecret" name: "org-webhook-secret" - credentialsName: "GitHub-Actions" + credentialsRef: + apiGroup: garm-operator.mercedes-benz.com + kind: GitHubCredentials + name: github-pat --- apiVersion: v1 kind: Secret diff --git a/config/samples/garm-operator_v1alpha1_repository.yaml b/config/samples/garm-operator_v1alpha1_repository.yaml index 142884e2..d698330f 100644 --- a/config/samples/garm-operator_v1alpha1_repository.yaml +++ b/config/samples/garm-operator_v1alpha1_repository.yaml @@ -13,7 +13,10 @@ spec: webhookSecretRef: key: "webhookSecret" name: "org-webhook-secret" - credentialsName: "GitHub-Actions" + credentialsRef: + apiGroup: garm-operator.mercedes-benz.com + kind: GitHubCredentials + name: github-pat owner: "mercedes-benz" --- apiVersion: v1 diff --git a/config/samples/garm-operator_v1beta1_enterprise.yaml b/config/samples/garm-operator_v1beta1_enterprise.yaml new file mode 100644 index 00000000..90a1ea0f --- /dev/null +++ b/config/samples/garm-operator_v1beta1_enterprise.yaml @@ -0,0 +1,12 @@ +apiVersion: garm-operator.mercedes-benz.com/v1beta1 +kind: Enterprise +metadata: + labels: + app.kubernetes.io/name: enterprise + app.kubernetes.io/instance: enterprise-sample + app.kubernetes.io/part-of: garm-operator + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: garm-operator + name: enterprise-sample +spec: + # TODO(user): Add fields here diff --git a/config/samples/garm-operator_v1beta1_garmserverconfig.yaml b/config/samples/garm-operator_v1beta1_garmserverconfig.yaml new file mode 100644 index 00000000..3e7a1148 --- /dev/null +++ b/config/samples/garm-operator_v1beta1_garmserverconfig.yaml @@ -0,0 +1,12 @@ +apiVersion: garm-operator.mercedes-benz.com/v1beta1 +kind: GarmServerConfig +metadata: + labels: + app.kubernetes.io/name: garmserverconfig + app.kubernetes.io/instance: garmserverconfig-sample + app.kubernetes.io/part-of: garm-operator + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: garm-operator + name: garmserverconfig-sample +spec: + # TODO(user): Add fields here diff --git a/config/samples/garm-operator_v1beta1_githubcredentials.yaml b/config/samples/garm-operator_v1beta1_githubcredentials.yaml new file mode 100644 index 00000000..a588bda9 --- /dev/null +++ b/config/samples/garm-operator_v1beta1_githubcredentials.yaml @@ -0,0 +1,12 @@ +apiVersion: garm-operator.mercedes-benz.com/v1beta1 +kind: GitHubCredentials +metadata: + labels: + app.kubernetes.io/name: githubcredential + app.kubernetes.io/instance: githubcredential-sample + app.kubernetes.io/part-of: garm-operator + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: garm-operator + name: githubcredential-sample +spec: + # TODO(user): Add fields here diff --git a/config/samples/garm-operator_v1beta1_githubendpoint.yaml b/config/samples/garm-operator_v1beta1_githubendpoint.yaml new file mode 100644 index 00000000..76099a39 --- /dev/null +++ b/config/samples/garm-operator_v1beta1_githubendpoint.yaml @@ -0,0 +1,12 @@ +apiVersion: garm-operator.mercedes-benz.com/v1beta1 +kind: GitHubEndpoint +metadata: + labels: + app.kubernetes.io/name: githubendpoint + app.kubernetes.io/instance: githubendpoint-sample + app.kubernetes.io/part-of: garm-operator + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: garm-operator + name: githubendpoint-sample +spec: + # TODO(user): Add fields here diff --git a/config/samples/garm-operator_v1beta1_image.yaml b/config/samples/garm-operator_v1beta1_image.yaml new file mode 100644 index 00000000..c02275e4 --- /dev/null +++ b/config/samples/garm-operator_v1beta1_image.yaml @@ -0,0 +1,12 @@ +apiVersion: garm-operator.mercedes-benz.com/v1beta1 +kind: Image +metadata: + labels: + app.kubernetes.io/name: image + app.kubernetes.io/instance: image-sample + app.kubernetes.io/part-of: garm-operator + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: garm-operator + name: image-sample +spec: + # TODO(user): Add fields here diff --git a/config/samples/garm-operator_v1beta1_organization.yaml b/config/samples/garm-operator_v1beta1_organization.yaml new file mode 100644 index 00000000..af7d074b --- /dev/null +++ b/config/samples/garm-operator_v1beta1_organization.yaml @@ -0,0 +1,12 @@ +apiVersion: garm-operator.mercedes-benz.com/v1beta1 +kind: Organization +metadata: + labels: + app.kubernetes.io/name: organization + app.kubernetes.io/instance: organization-sample + app.kubernetes.io/part-of: garm-operator + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: garm-operator + name: organization-sample +spec: + # TODO(user): Add fields here diff --git a/config/samples/garm-operator_v1beta1_pool.yaml b/config/samples/garm-operator_v1beta1_pool.yaml new file mode 100644 index 00000000..c207e01d --- /dev/null +++ b/config/samples/garm-operator_v1beta1_pool.yaml @@ -0,0 +1,12 @@ +apiVersion: garm-operator.mercedes-benz.com/v1beta1 +kind: Pool +metadata: + labels: + app.kubernetes.io/name: pool + app.kubernetes.io/instance: pool-sample + app.kubernetes.io/part-of: garm-operator + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: garm-operator + name: pool-sample +spec: + # TODO(user): Add fields here diff --git a/config/samples/garm-operator_v1beta1_repository.yaml b/config/samples/garm-operator_v1beta1_repository.yaml new file mode 100644 index 00000000..9dcb8e53 --- /dev/null +++ b/config/samples/garm-operator_v1beta1_repository.yaml @@ -0,0 +1,12 @@ +apiVersion: garm-operator.mercedes-benz.com/v1beta1 +kind: Repository +metadata: + labels: + app.kubernetes.io/name: repository + app.kubernetes.io/instance: repository-sample + app.kubernetes.io/part-of: garm-operator + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: garm-operator + name: repository-sample +spec: + # TODO(user): Add fields here diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index a3b8f74a..0fef6a75 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -5,5 +5,12 @@ resources: - garm-operator_v1alpha1_organization.yaml - garm-operator_v1alpha1_image.yaml - garm-operator_v1alpha1_repository.yaml - - garm-operator_v1alpha1_runner.yaml -#+kubebuilder:scaffold:manifestskustomizesamples + - garm-operator_v1beta1_enterprise.yaml + - garm-operator_v1beta1_organization.yaml + - garm-operator_v1beta1_repository.yaml + - garm-operator_v1beta1_pool.yaml + - garm-operator_v1beta1_image.yaml + - garm-operator_v1beta1_githubendpoint.yaml + - garm-operator_v1beta1_githubcredential.yaml + - garm-operator_v1beta1_garmserverconfig.yaml + #+kubebuilder:scaffold:manifestskustomizesamples diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index 13b15fbd..f104c62e 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -65,3 +65,64 @@ webhooks: resources: - repositories sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-garm-operator-mercedes-benz-com-v1alpha1-image + failurePolicy: Fail + name: validate.image.garm-operator.mercedes-benz.com + rules: + - apiGroups: + - garm-operator.mercedes-benz.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + - DELETE + resources: + - images + sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-garm-operator-mercedes-benz-com-v1alpha1-pool + failurePolicy: Fail + name: validate.pool.garm-operator.mercedes-benz.com + rules: + - apiGroups: + - garm-operator.mercedes-benz.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - pools + sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-garm-operator-mercedes-benz-com-v1alpha1-repository + failurePolicy: Fail + name: validate.repository.garm-operator.mercedes-benz.com + rules: + - apiGroups: + - garm-operator.mercedes-benz.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - repositories + sideEffects: None diff --git a/go.mod b/go.mod index 63665ec2..5beb8f05 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,12 @@ module github.com/mercedes-benz/garm-operator go 1.22.5 require ( - github.com/cloudbase/garm v0.1.4 - github.com/cloudbase/garm-provider-common v0.1.2 + github.com/cloudbase/garm v0.1.5 + github.com/cloudbase/garm-provider-common v0.1.3 github.com/go-openapi/runtime v0.28.0 github.com/go-playground/validator/v10 v10.22.1 github.com/golang-jwt/jwt/v4 v4.5.0 + github.com/google/uuid v1.6.0 github.com/iancoleman/strcase v0.3.0 github.com/knadh/koanf/parsers/yaml v0.1.0 github.com/knadh/koanf/providers/env v1.0.0 @@ -30,8 +31,10 @@ require ( ) require ( + github.com/BurntSushi/toml v1.3.2 // indirect github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/bradleyfalzon/ghinstallation/v2 v2.10.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect @@ -60,9 +63,9 @@ require ( github.com/google/gnostic-models v0.6.8 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-github/v57 v57.0.0 // indirect + github.com/google/go-github/v60 v60.0.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/uuid v1.6.0 // indirect github.com/gorilla/handlers v1.5.2 // indirect github.com/imdario/mergo v0.3.6 // indirect github.com/josharian/intern v1.0.0 // indirect @@ -71,13 +74,14 @@ require ( github.com/knadh/koanf/maps v0.1.1 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect - github.com/minio/sio v0.3.1 // indirect + github.com/minio/sio v0.4.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect @@ -85,17 +89,17 @@ require ( github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569 // indirect - go.mongodb.org/mongo-driver v1.14.0 // indirect - go.opentelemetry.io/otel v1.24.0 // indirect - go.opentelemetry.io/otel/metric v1.24.0 // indirect - go.opentelemetry.io/otel/trace v1.24.0 // indirect - golang.org/x/crypto v0.24.0 // indirect + go.mongodb.org/mongo-driver v1.15.0 // indirect + go.opentelemetry.io/otel v1.25.0 // indirect + go.opentelemetry.io/otel/metric v1.25.0 // indirect + go.opentelemetry.io/otel/trace v1.25.0 // indirect + golang.org/x/crypto v0.25.0 // indirect golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect - golang.org/x/net v0.26.0 // indirect + golang.org/x/net v0.27.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sync v0.7.0 // indirect golang.org/x/sys v0.22.0 // indirect - golang.org/x/term v0.21.0 // indirect + golang.org/x/term v0.22.0 // indirect golang.org/x/text v0.16.0 // indirect golang.org/x/time v0.3.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 09a9101f..6ceb005f 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,17 @@ +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bradleyfalzon/ghinstallation/v2 v2.10.0 h1:XWuWBRFEpqVrHepQob9yPS3Xg4K3Wr9QCx4fu8HbUNg= +github.com/bradleyfalzon/ghinstallation/v2 v2.10.0/go.mod h1:qoGA4DxWPaYTgVCrmEspVSjlTu4WYAiSxMIhorMRXXc= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cloudbase/garm v0.1.4 h1:hknw8d4VPdHWGhnPkcKU2+tj3BjV6+M2mcjMU9z76yw= -github.com/cloudbase/garm v0.1.4/go.mod h1:yalSeTH2GX6bZ5L4I2YaynhBFqYIHW1ZaiaQ/tq0GIE= -github.com/cloudbase/garm-provider-common v0.1.2 h1:EqSpUjw9rzo4PiUmteHkFtZNWCnRi0QXHRKZ+VA1IPo= -github.com/cloudbase/garm-provider-common v0.1.2/go.mod h1:igxJRT3OlykERYc6ssdRQXcb+BCaeSfnucg6I0OSoDc= +github.com/cloudbase/garm v0.1.5 h1:PunOEqBBk0Hwmf8IEUoU2mc5hhF7+G8HaE5qGIHT0ig= +github.com/cloudbase/garm v0.1.5/go.mod h1:gnWVWqefhdOSWdn68GfMnfuNTA+F24TsoLceXe6M3CQ= +github.com/cloudbase/garm-provider-common v0.1.3 h1:8pHSRs2ljwLHgtDrge68dZ7ILUW97VF5h2ZA2fQubGQ= +github.com/cloudbase/garm-provider-common v0.1.3/go.mod h1:VIJzbcg5iwyD4ac99tnnwcActfwibn/VOt2MYOFjf2c= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -78,6 +82,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-github/v57 v57.0.0 h1:L+Y3UPTY8ALM8x+TV0lg+IEBI+upibemtBD8Q9u7zHs= github.com/google/go-github/v57 v57.0.0/go.mod h1:s0omdnye0hvK/ecLvpsGfJMiRt85PimQh4oygmLIxHw= +github.com/google/go-github/v60 v60.0.0 h1:oLG98PsLauFvvu4D/YPxq374jhSxFYdzQGNCyONLfn8= +github.com/google/go-github/v60 v60.0.0/go.mod h1:ByhX2dP9XT9o/ll2yXAu2VD8l5eNVg8hD4Cr0S/LmQk= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -123,8 +129,8 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/minio/sio v0.3.1 h1:d59r5RTHb1OsQaSl1EaTWurzMMDRLA5fgNmjzD4eVu4= -github.com/minio/sio v0.3.1/go.mod h1:S0ovgVgc+sTlQyhiXA1ppBLv7REM7TYi5yyq2qL/Y6o= +github.com/minio/sio v0.4.0 h1:u4SWVEm5lXSqU42ZWawV0D9I5AZ5YMmo2RXpEQ/kRhc= +github.com/minio/sio v0.4.0/go.mod h1:oBSjJeGbBdRMZZwna07sX9EFzZy+ywu5aofRiV1g79I= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -138,6 +144,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6FxaNu/BnU2OAaLF86eTVhP2hjTB6iMvItA= +github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8= github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo/v2 v2.14.0 h1:vSmGj2Z5YPb9JwCWT6z6ihcUvDhuXLc3sJiqd3jMKAY= @@ -164,6 +172,7 @@ github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUz github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= @@ -171,16 +180,16 @@ github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569 h1:xzABM9let0HLLq github.com/teris-io/shortid v0.0.0-20220617161101-71ec9f2aa569/go.mod h1:2Ly+NIftZN4de9zRmENdYbvPQeaVIYKWpLFStLFEBgI= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= -go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= -go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= -go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= -go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= -go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= +go.mongodb.org/mongo-driver v1.15.0 h1:rJCKC8eEliewXjZGf0ddURtl7tTVy1TK3bfl0gkUSLc= +go.mongodb.org/mongo-driver v1.15.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= +go.opentelemetry.io/otel v1.25.0 h1:gldB5FfhRl7OJQbUHt/8s0a7cE8fbsPAtdpRaApKy4k= +go.opentelemetry.io/otel v1.25.0/go.mod h1:Wa2ds5NOXEMkCmUou1WA7ZBfLTHWIsp034OVD7AO+Vg= +go.opentelemetry.io/otel/metric v1.25.0 h1:LUKbS7ArpFL/I2jJHdJcqMGxkRdxpPHE0VU/D4NuEwA= +go.opentelemetry.io/otel/metric v1.25.0/go.mod h1:rkDLUSd2lC5lq2dFNrX9LGAbINP5B7WBkC78RXCpH5s= go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw= go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg= -go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= -go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= +go.opentelemetry.io/otel/trace v1.25.0 h1:tqukZGLwQYRIFtSQM2u2+yfMVTgGVeqRLPUYx1Dq6RM= +go.opentelemetry.io/otel/trace v1.25.0/go.mod h1:hCCs70XM/ljO+BeQkyFnbK28SBIJ/Emuha+ccrCRT7I= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= @@ -192,8 +201,8 @@ go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -202,8 +211,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ= -golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= +golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -216,8 +225,8 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= -golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= diff --git a/hack/verify-license.sh b/hack/verify-license.sh index 2e369415..d1b20322 100755 --- a/hack/verify-license.sh +++ b/hack/verify-license.sh @@ -19,7 +19,7 @@ HEADER="SPDX-License-Identifier: MIT" all_files=() export IFS=$'\n' -while IFS='' read -r line; do all_error_files+=("$line"); done < <(git ls-files | grep -v -E '\.excalidraw$|\.jpeg$|\.jpg$|\.gif$|\.png$|\.yaml$|^(go.sum|LICENSE|hack/boilerplate.go.txt)$|.*zz_generated.deepcopy.go$') +while IFS='' read -r line; do all_error_files+=("$line"); done < <(git ls-files | grep -v -E '\.excalidraw$|\.jpeg$|\.jpg$|\.gif$|\.png$|\.yaml$|^(go.sum|LICENSE|PROJECT|hack/boilerplate.go.txt)$|.*zz_generated.deepcopy.go$|.*zz_generated.conversion.go$') unset IFS errors=() diff --git a/internal/controller/enterprise_controller.go b/internal/controller/enterprise_controller.go index 6aaac0aa..37ab0ad0 100644 --- a/internal/controller/enterprise_controller.go +++ b/internal/controller/enterprise_controller.go @@ -12,14 +12,19 @@ import ( "github.com/cloudbase/garm/params" apierrors "k8s.io/apimachinery/pkg/api/errors" "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/builder" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" - garmoperatorv1alpha1 "github.com/mercedes-benz/garm-operator/api/v1alpha1" + garmoperatorv1beta1 "github.com/mercedes-benz/garm-operator/api/v1beta1" "github.com/mercedes-benz/garm-operator/pkg/annotations" garmClient "github.com/mercedes-benz/garm-operator/pkg/client" "github.com/mercedes-benz/garm-operator/pkg/client/key" @@ -44,7 +49,7 @@ type EnterpriseReconciler struct { func (r *EnterpriseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := log.FromContext(ctx) - enterprise := &garmoperatorv1alpha1.Enterprise{} + enterprise := &garmoperatorv1beta1.Enterprise{} err := r.Get(ctx, req.NamespacedName, enterprise) if err != nil { if apierrors.IsNotFound(err) { @@ -70,7 +75,7 @@ func (r *EnterpriseReconciler) Reconcile(ctx context.Context, req ctrl.Request) return r.reconcileNormal(ctx, enterpriseClient, enterprise) } -func (r *EnterpriseReconciler) reconcileNormal(ctx context.Context, client garmClient.EnterpriseClient, enterprise *garmoperatorv1alpha1.Enterprise) (ctrl.Result, error) { +func (r *EnterpriseReconciler) reconcileNormal(ctx context.Context, client garmClient.EnterpriseClient, enterprise *garmoperatorv1beta1.Enterprise) (ctrl.Result, error) { log := log.FromContext(ctx) log.WithValues("enterprise", enterprise.Name) @@ -83,7 +88,10 @@ func (r *EnterpriseReconciler) reconcileNormal(ctx context.Context, client garmC if err != nil { conditions.MarkFalse(enterprise, conditions.ReadyCondition, conditions.FetchingSecretRefFailedReason, err.Error()) conditions.MarkFalse(enterprise, conditions.SecretReference, conditions.FetchingSecretRefFailedReason, err.Error()) - conditions.MarkUnknown(enterprise, conditions.PoolManager, conditions.UnknownReason, conditions.GarmServerNotReconciledYetMsg) + conditions.MarkUnknown(enterprise, conditions.CredentialsReference, conditions.UnknownReason, conditions.CredentialsNotReconciledYetMsg) + if conditions.Get(enterprise, conditions.PoolManager) == nil { + conditions.MarkUnknown(enterprise, conditions.PoolManager, conditions.UnknownReason, conditions.GarmServerNotReconciledYetMsg) + } if err := r.Status().Update(ctx, enterprise); err != nil { return ctrl.Result{}, err } @@ -91,6 +99,20 @@ func (r *EnterpriseReconciler) reconcileNormal(ctx context.Context, client garmC } conditions.MarkTrue(enterprise, conditions.SecretReference, conditions.FetchingSecretRefSuccessReason, "") + credentials, err := r.getCredentialsRef(ctx, enterprise) + if err != nil { + conditions.MarkFalse(enterprise, conditions.ReadyCondition, conditions.FetchingCredentialsRefFailedReason, err.Error()) + conditions.MarkFalse(enterprise, conditions.CredentialsReference, conditions.FetchingCredentialsRefFailedReason, err.Error()) + if conditions.Get(enterprise, conditions.PoolManager) == nil { + conditions.MarkUnknown(enterprise, conditions.PoolManager, conditions.UnknownReason, conditions.GarmServerNotReconciledYetMsg) + } + if err := r.Status().Update(ctx, enterprise); err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{}, err + } + conditions.MarkTrue(enterprise, conditions.CredentialsReference, conditions.FetchingCredentialsRefSuccessReason, "") + garmEnterprise, err := r.getExistingGarmEnterprise(ctx, client, enterprise) if err != nil { event.Error(r.Recorder, enterprise, err.Error()) @@ -115,7 +137,11 @@ func (r *EnterpriseReconciler) reconcileNormal(ctx context.Context, client garmC } // update enterprise anytime - garmEnterprise, err = r.updateEnterprise(ctx, client, garmEnterprise.ID, webhookSecret, enterprise.Spec.CredentialsName) + garmEnterprise, err = r.updateEnterprise(ctx, client, garmEnterprise.ID, params.UpdateEntityParams{ + CredentialsName: credentials.Name, + WebhookSecret: webhookSecret, + PoolBalancerType: enterprise.Spec.PoolBalancerType, + }) if err != nil { event.Error(r.Recorder, enterprise, err.Error()) conditions.MarkFalse(enterprise, conditions.ReadyCondition, conditions.GarmAPIErrorReason, err.Error()) @@ -143,7 +169,7 @@ func (r *EnterpriseReconciler) reconcileNormal(ctx context.Context, client garmC return ctrl.Result{}, nil } -func (r *EnterpriseReconciler) createEnterprise(ctx context.Context, client garmClient.EnterpriseClient, enterprise *garmoperatorv1alpha1.Enterprise, webhookSecret string) (params.Enterprise, error) { +func (r *EnterpriseReconciler) createEnterprise(ctx context.Context, client garmClient.EnterpriseClient, enterprise *garmoperatorv1beta1.Enterprise, webhookSecret string) (params.Enterprise, error) { log := log.FromContext(ctx) log.WithValues("enterprise", enterprise.Name) @@ -153,9 +179,10 @@ func (r *EnterpriseReconciler) createEnterprise(ctx context.Context, client garm retValue, err := client.CreateEnterprise( enterprises.NewCreateEnterpriseParams(). WithBody(params.CreateEnterpriseParams{ - Name: enterprise.Name, - CredentialsName: enterprise.Spec.CredentialsName, - WebhookSecret: webhookSecret, // gh hook secret + Name: enterprise.Name, + CredentialsName: enterprise.GetCredentialsName(), + WebhookSecret: webhookSecret, // gh hook secret + PoolBalancerType: enterprise.Spec.PoolBalancerType, })) if err != nil { log.V(1).Info(fmt.Sprintf("client.CreateEnterprise error: %s", err)) @@ -170,7 +197,7 @@ func (r *EnterpriseReconciler) createEnterprise(ctx context.Context, client garm return retValue.Payload, nil } -func (r *EnterpriseReconciler) updateEnterprise(ctx context.Context, client garmClient.EnterpriseClient, statusID, webhookSecret, credentialsName string) (params.Enterprise, error) { +func (r *EnterpriseReconciler) updateEnterprise(ctx context.Context, client garmClient.EnterpriseClient, statusID string, updateParams params.UpdateEntityParams) (params.Enterprise, error) { log := log.FromContext(ctx) log.V(1).Info("update credentials and webhook secret in garm enterprise") @@ -178,10 +205,7 @@ func (r *EnterpriseReconciler) updateEnterprise(ctx context.Context, client garm retValue, err := client.UpdateEnterprise( enterprises.NewUpdateEnterpriseParams(). WithEnterpriseID(statusID). - WithBody(params.UpdateEntityParams{ - CredentialsName: credentialsName, - WebhookSecret: webhookSecret, // gh hook secret - })) + WithBody(updateParams)) if err != nil { log.V(1).Info(fmt.Sprintf("client.UpdateEnterprise error: %s", err)) return params.Enterprise{}, err @@ -190,7 +214,7 @@ func (r *EnterpriseReconciler) updateEnterprise(ctx context.Context, client garm return retValue.Payload, nil } -func (r *EnterpriseReconciler) getExistingGarmEnterprise(ctx context.Context, client garmClient.EnterpriseClient, enterprise *garmoperatorv1alpha1.Enterprise) (params.Enterprise, error) { +func (r *EnterpriseReconciler) getExistingGarmEnterprise(ctx context.Context, client garmClient.EnterpriseClient, enterprise *garmoperatorv1beta1.Enterprise) (params.Enterprise, error) { log := log.FromContext(ctx) log.WithValues("enterprise", enterprise.Name) @@ -211,7 +235,7 @@ func (r *EnterpriseReconciler) getExistingGarmEnterprise(ctx context.Context, cl return params.Enterprise{}, nil } -func (r *EnterpriseReconciler) reconcileDelete(ctx context.Context, client garmClient.EnterpriseClient, enterprise *garmoperatorv1alpha1.Enterprise) (ctrl.Result, error) { +func (r *EnterpriseReconciler) reconcileDelete(ctx context.Context, client garmClient.EnterpriseClient, enterprise *garmoperatorv1beta1.Enterprise) (ctrl.Result, error) { log := log.FromContext(ctx) log.WithValues("enterprise", enterprise.Name) @@ -250,18 +274,61 @@ func (r *EnterpriseReconciler) reconcileDelete(ctx context.Context, client garmC return ctrl.Result{}, nil } -func (r *EnterpriseReconciler) ensureFinalizer(ctx context.Context, pool *garmoperatorv1alpha1.Enterprise) error { - if !controllerutil.ContainsFinalizer(pool, key.EnterpriseFinalizerName) { - controllerutil.AddFinalizer(pool, key.EnterpriseFinalizerName) - return r.Update(ctx, pool) +func (r *EnterpriseReconciler) getCredentialsRef(ctx context.Context, enterprise *garmoperatorv1beta1.Enterprise) (*garmoperatorv1beta1.GitHubCredential, error) { + creds := &garmoperatorv1beta1.GitHubCredential{} + err := r.Get(ctx, types.NamespacedName{ + Namespace: enterprise.Namespace, + Name: enterprise.Spec.CredentialsRef.Name, + }, creds) + if err != nil { + return creds, err + } + return creds, nil +} + +func (r *EnterpriseReconciler) ensureFinalizer(ctx context.Context, enterprise *garmoperatorv1beta1.Enterprise) error { + if !controllerutil.ContainsFinalizer(enterprise, key.EnterpriseFinalizerName) { + controllerutil.AddFinalizer(enterprise, key.EnterpriseFinalizerName) + return r.Update(ctx, enterprise) } return nil } +func (r *EnterpriseReconciler) findEnterprisesForCredentials(ctx context.Context, obj client.Object) []reconcile.Request { + credentials, ok := obj.(*garmoperatorv1beta1.GitHubCredential) + if !ok { + return nil + } + + var enterprises garmoperatorv1beta1.EnterpriseList + if err := r.List(ctx, &enterprises); err != nil { + return nil + } + + var requests []reconcile.Request + for _, enterprise := range enterprises.Items { + if enterprise.GetCredentialsName() == credentials.Name { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: enterprise.Namespace, + Name: enterprise.Name, + }, + }) + } + } + + return requests +} + // SetupWithManager sets up the controller with the Manager. func (r *EnterpriseReconciler) SetupWithManager(mgr ctrl.Manager, options controller.Options) error { return ctrl.NewControllerManagedBy(mgr). - For(&garmoperatorv1alpha1.Enterprise{}). + For(&garmoperatorv1beta1.Enterprise{}). + Watches( + &garmoperatorv1beta1.GitHubCredential{}, + handler.EnqueueRequestsFromMapFunc(r.findEnterprisesForCredentials), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ). WithOptions(options). Complete(r) } diff --git a/internal/controller/enterprise_controller_test.go b/internal/controller/enterprise_controller_test.go index 66f21626..d77225b8 100644 --- a/internal/controller/enterprise_controller_test.go +++ b/internal/controller/enterprise_controller_test.go @@ -19,7 +19,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - garmoperatorv1alpha1 "github.com/mercedes-benz/garm-operator/api/v1alpha1" + garmoperatorv1beta1 "github.com/mercedes-benz/garm-operator/api/v1beta1" "github.com/mercedes-benz/garm-operator/pkg/client/key" "github.com/mercedes-benz/garm-operator/pkg/client/mock" "github.com/mercedes-benz/garm-operator/pkg/conditions" @@ -35,11 +35,11 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { expectGarmRequest func(m *mock.MockEnterpriseClientMockRecorder) runtimeObjects []runtime.Object wantErr bool - expectedObject *garmoperatorv1alpha1.Enterprise + expectedObject *garmoperatorv1beta1.Enterprise }{ { name: "enterprise exist - update", - object: &garmoperatorv1alpha1.Enterprise{ + object: &garmoperatorv1beta1.Enterprise{ ObjectMeta: metav1.ObjectMeta{ Name: "existing-enterprise", Namespace: "default", @@ -47,14 +47,18 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { key.EnterpriseFinalizerName, }, }, - Spec: garmoperatorv1alpha1.EnterpriseSpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.EnterpriseSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.EnterpriseStatus{ + Status: garmoperatorv1beta1.EnterpriseStatus{ ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", }, }, @@ -68,8 +72,23 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { "webhookSecret": []byte("foobar"), }, }, + &garmoperatorv1beta1.GitHubCredential{ + ObjectMeta: metav1.ObjectMeta{ + Name: "github-creds", + Namespace: "default", + }, + Spec: garmoperatorv1beta1.GitHubCredentialSpec{ + Description: "github-creds", + EndpointRef: corev1.TypedLocalObjectReference{}, + AuthType: "pat", + SecretRef: garmoperatorv1beta1.SecretRef{ + Name: "github-secret", + Key: "token", + }, + }, + }, }, - expectedObject: &garmoperatorv1alpha1.Enterprise{ + expectedObject: &garmoperatorv1beta1.Enterprise{ ObjectMeta: metav1.ObjectMeta{ Name: "existing-enterprise", Namespace: "default", @@ -77,14 +96,18 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { key.EnterpriseFinalizerName, }, }, - Spec: garmoperatorv1alpha1.EnterpriseSpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.EnterpriseSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.EnterpriseStatus{ + Status: garmoperatorv1beta1.EnterpriseStatus{ ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", Conditions: []metav1.Condition{ { @@ -94,6 +117,13 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { Message: "Pool Manager is not running", LastTransitionTime: metav1.NewTime(time.Now()), }, + { + Type: string(conditions.CredentialsReference), + Reason: string(conditions.FetchingCredentialsRefSuccessReason), + Status: metav1.ConditionTrue, + Message: "", + LastTransitionTime: metav1.NewTime(time.Now()), + }, { Type: string(conditions.PoolManager), Reason: string(conditions.PoolManagerFailureReason), @@ -116,20 +146,20 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { { ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", Name: "existing-enterprise", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", }, }}, nil) m.UpdateEnterprise(enterprises.NewUpdateEnterpriseParams(). WithEnterpriseID("e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e"). WithBody(params.UpdateEntityParams{ - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", })).Return(&enterprises.UpdateEnterpriseOK{ Payload: params.Enterprise{ ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", Name: "existing-enterprise", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", }, }, nil) @@ -137,7 +167,7 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { }, { name: "enterprise exist but spec has changed - update", - object: &garmoperatorv1alpha1.Enterprise{ + object: &garmoperatorv1beta1.Enterprise{ ObjectMeta: metav1.ObjectMeta{ Name: "existing-enterprise", Namespace: "default", @@ -145,14 +175,18 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { key.EnterpriseFinalizerName, }, }, - Spec: garmoperatorv1alpha1.EnterpriseSpec{ - CredentialsName: "has-changed", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.EnterpriseSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "has-changed", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.EnterpriseStatus{ + Status: garmoperatorv1beta1.EnterpriseStatus{ ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", }, }, @@ -166,8 +200,23 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { "webhookSecret": []byte("has-changed"), }, }, + &garmoperatorv1beta1.GitHubCredential{ + ObjectMeta: metav1.ObjectMeta{ + Name: "has-changed", + Namespace: "default", + }, + Spec: garmoperatorv1beta1.GitHubCredentialSpec{ + Description: "github-creds", + EndpointRef: corev1.TypedLocalObjectReference{}, + AuthType: "pat", + SecretRef: garmoperatorv1beta1.SecretRef{ + Name: "github-secret", + Key: "token", + }, + }, + }, }, - expectedObject: &garmoperatorv1alpha1.Enterprise{ + expectedObject: &garmoperatorv1beta1.Enterprise{ ObjectMeta: metav1.ObjectMeta{ Name: "existing-enterprise", Namespace: "default", @@ -175,14 +224,18 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { key.EnterpriseFinalizerName, }, }, - Spec: garmoperatorv1alpha1.EnterpriseSpec{ - CredentialsName: "has-changed", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.EnterpriseSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "has-changed", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.EnterpriseStatus{ + Status: garmoperatorv1beta1.EnterpriseStatus{ ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", Conditions: []metav1.Condition{ { @@ -192,6 +245,13 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { Message: "Pool Manager is not running", LastTransitionTime: metav1.NewTime(time.Now()), }, + { + Type: string(conditions.CredentialsReference), + Reason: string(conditions.FetchingCredentialsRefSuccessReason), + Status: metav1.ConditionTrue, + Message: "", + LastTransitionTime: metav1.NewTime(time.Now()), + }, { Type: string(conditions.PoolManager), Reason: string(conditions.PoolManagerFailureReason), @@ -214,7 +274,7 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { { ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", Name: "existing-enterprise", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", }, }}, nil) @@ -235,7 +295,7 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { }, { name: "enterprise exist but pool status has changed - update", - object: &garmoperatorv1alpha1.Enterprise{ + object: &garmoperatorv1beta1.Enterprise{ ObjectMeta: metav1.ObjectMeta{ Name: "existing-enterprise", Namespace: "default", @@ -243,18 +303,22 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { key.EnterpriseFinalizerName, }, }, - Spec: garmoperatorv1alpha1.EnterpriseSpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.EnterpriseSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.EnterpriseStatus{ + Status: garmoperatorv1beta1.EnterpriseStatus{ ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", }, }, - expectedObject: &garmoperatorv1alpha1.Enterprise{ + expectedObject: &garmoperatorv1beta1.Enterprise{ ObjectMeta: metav1.ObjectMeta{ Name: "existing-enterprise", Namespace: "default", @@ -262,14 +326,18 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { key.EnterpriseFinalizerName, }, }, - Spec: garmoperatorv1alpha1.EnterpriseSpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.EnterpriseSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.EnterpriseStatus{ + Status: garmoperatorv1beta1.EnterpriseStatus{ ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", Conditions: []metav1.Condition{ { @@ -279,6 +347,13 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { Message: "Pool Manager is not running", LastTransitionTime: metav1.NewTime(time.Now()), }, + { + Type: string(conditions.CredentialsReference), + Reason: string(conditions.FetchingCredentialsRefSuccessReason), + Status: metav1.ConditionTrue, + Message: "", + LastTransitionTime: metav1.NewTime(time.Now()), + }, { Type: string(conditions.PoolManager), Reason: string(conditions.PoolManagerFailureReason), @@ -306,26 +381,41 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { "webhookSecret": []byte("foobar"), }, }, + &garmoperatorv1beta1.GitHubCredential{ + ObjectMeta: metav1.ObjectMeta{ + Name: "github-creds", + Namespace: "default", + }, + Spec: garmoperatorv1beta1.GitHubCredentialSpec{ + Description: "github-creds", + EndpointRef: corev1.TypedLocalObjectReference{}, + AuthType: "pat", + SecretRef: garmoperatorv1beta1.SecretRef{ + Name: "github-secret", + Key: "token", + }, + }, + }, }, expectGarmRequest: func(m *mock.MockEnterpriseClientMockRecorder) { m.ListEnterprises(enterprises.NewListEnterprisesParams()).Return(&enterprises.ListEnterprisesOK{Payload: params.Enterprises{ { ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", Name: "existing-enterprise", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", }, }}, nil) m.UpdateEnterprise(enterprises.NewUpdateEnterpriseParams(). WithEnterpriseID("e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e"). WithBody(params.UpdateEntityParams{ - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", })).Return(&enterprises.UpdateEnterpriseOK{ Payload: params.Enterprise{ ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", Name: "existing-enterprise", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", PoolManagerStatus: params.PoolManagerStatus{ IsRunning: false, @@ -337,20 +427,24 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { }, { name: "enterprise does not exist - create and update", - object: &garmoperatorv1alpha1.Enterprise{ + object: &garmoperatorv1beta1.Enterprise{ ObjectMeta: metav1.ObjectMeta{ Name: "new-enterprise", Namespace: "default", }, - Spec: garmoperatorv1alpha1.EnterpriseSpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.EnterpriseSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, }, - expectedObject: &garmoperatorv1alpha1.Enterprise{ + expectedObject: &garmoperatorv1beta1.Enterprise{ ObjectMeta: metav1.ObjectMeta{ Name: "new-enterprise", Namespace: "default", @@ -358,14 +452,18 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { key.EnterpriseFinalizerName, }, }, - Spec: garmoperatorv1alpha1.EnterpriseSpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.EnterpriseSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.EnterpriseStatus{ + Status: garmoperatorv1beta1.EnterpriseStatus{ ID: "9e0da3cb-130b-428d-aa8a-e314d955060e", Conditions: []metav1.Condition{ { @@ -375,6 +473,13 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { Message: "Pool Manager is not running", LastTransitionTime: metav1.NewTime(time.Now()), }, + { + Type: string(conditions.CredentialsReference), + Reason: string(conditions.FetchingCredentialsRefSuccessReason), + Status: metav1.ConditionTrue, + Message: "", + LastTransitionTime: metav1.NewTime(time.Now()), + }, { Type: string(conditions.PoolManager), Reason: string(conditions.PoolManagerFailureReason), @@ -402,39 +507,54 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { "webhookSecret": []byte("foobar"), }, }, + &garmoperatorv1beta1.GitHubCredential{ + ObjectMeta: metav1.ObjectMeta{ + Name: "github-creds", + Namespace: "default", + }, + Spec: garmoperatorv1beta1.GitHubCredentialSpec{ + Description: "github-creds", + EndpointRef: corev1.TypedLocalObjectReference{}, + AuthType: "pat", + SecretRef: garmoperatorv1beta1.SecretRef{ + Name: "github-secret", + Key: "token", + }, + }, + }, }, expectGarmRequest: func(m *mock.MockEnterpriseClientMockRecorder) { m.ListEnterprises(enterprises.NewListEnterprisesParams()).Return(&enterprises.ListEnterprisesOK{Payload: params.Enterprises{ { ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", Name: "existing-enterprise", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", }, }}, nil) m.CreateEnterprise(enterprises.NewCreateEnterpriseParams().WithBody( params.CreateEnterpriseParams{ Name: "new-enterprise", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", })).Return(&enterprises.CreateEnterpriseOK{ Payload: params.Enterprise{ ID: "9e0da3cb-130b-428d-aa8a-e314d955060e", Name: "new-enterprise", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", }, }, nil) m.UpdateEnterprise(enterprises.NewUpdateEnterpriseParams(). WithEnterpriseID("9e0da3cb-130b-428d-aa8a-e314d955060e"). WithBody(params.UpdateEntityParams{ - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", })).Return(&enterprises.UpdateEnterpriseOK{ Payload: params.Enterprise{ ID: "9e0da3cb-130b-428d-aa8a-e314d955060e", Name: "new-enterprise", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", }, }, nil) @@ -442,20 +562,24 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { }, { name: "enterprise already exist in garm - update", - object: &garmoperatorv1alpha1.Enterprise{ + object: &garmoperatorv1beta1.Enterprise{ ObjectMeta: metav1.ObjectMeta{ Name: "new-enterprise", Namespace: "default", }, - Spec: garmoperatorv1alpha1.EnterpriseSpec{ - CredentialsName: "totally-insecure", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.EnterpriseSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, }, - expectedObject: &garmoperatorv1alpha1.Enterprise{ + expectedObject: &garmoperatorv1beta1.Enterprise{ ObjectMeta: metav1.ObjectMeta{ Name: "new-enterprise", Namespace: "default", @@ -463,14 +587,18 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { key.EnterpriseFinalizerName, }, }, - Spec: garmoperatorv1alpha1.EnterpriseSpec{ - CredentialsName: "totally-insecure", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.EnterpriseSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.EnterpriseStatus{ + Status: garmoperatorv1beta1.EnterpriseStatus{ ID: "e1dbf9a6-a9f6-4594-a5ac-12345", Conditions: []metav1.Condition{ { @@ -480,6 +608,13 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { Message: "Pool Manager is not running", LastTransitionTime: metav1.NewTime(time.Now()), }, + { + Type: string(conditions.CredentialsReference), + Reason: string(conditions.FetchingCredentialsRefSuccessReason), + Status: metav1.ConditionTrue, + Message: "", + LastTransitionTime: metav1.NewTime(time.Now()), + }, { Type: string(conditions.PoolManager), Reason: string(conditions.PoolManagerFailureReason), @@ -507,6 +642,21 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { "webhookSecret": []byte("foobar"), }, }, + &garmoperatorv1beta1.GitHubCredential{ + ObjectMeta: metav1.ObjectMeta{ + Name: "github-creds", + Namespace: "default", + }, + Spec: garmoperatorv1beta1.GitHubCredentialSpec{ + Description: "github-creds", + EndpointRef: corev1.TypedLocalObjectReference{}, + AuthType: "pat", + SecretRef: garmoperatorv1beta1.SecretRef{ + Name: "github-secret", + Key: "token", + }, + }, + }, }, expectGarmRequest: func(m *mock.MockEnterpriseClientMockRecorder) { m.ListEnterprises(enterprises.NewListEnterprisesParams()).Return(&enterprises.ListEnterprisesOK{Payload: params.Enterprises{ @@ -519,13 +669,13 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { m.UpdateEnterprise(enterprises.NewUpdateEnterpriseParams(). WithEnterpriseID("e1dbf9a6-a9f6-4594-a5ac-12345"). WithBody(params.UpdateEntityParams{ - CredentialsName: "totally-insecure", + CredentialsName: "github-creds", WebhookSecret: "foobar", })).Return(&enterprises.UpdateEnterpriseOK{ Payload: params.Enterprise{ ID: "e1dbf9a6-a9f6-4594-a5ac-12345", Name: "new-enterprise", - CredentialsName: "totally-insecure", + CredentialsName: "github-creds", WebhookSecret: "foobar", }, }, nil) @@ -533,7 +683,7 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { }, { name: "enterprise does not exist in garm - create update", - object: &garmoperatorv1alpha1.Enterprise{ + object: &garmoperatorv1beta1.Enterprise{ ObjectMeta: metav1.ObjectMeta{ Name: "existing-enterprise", Namespace: "default", @@ -541,14 +691,18 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { key.EnterpriseFinalizerName, }, }, - Spec: garmoperatorv1alpha1.EnterpriseSpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.EnterpriseSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.EnterpriseStatus{ + Status: garmoperatorv1beta1.EnterpriseStatus{ ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", }, }, @@ -562,8 +716,23 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { "webhookSecret": []byte("foobar"), }, }, + &garmoperatorv1beta1.GitHubCredential{ + ObjectMeta: metav1.ObjectMeta{ + Name: "github-creds", + Namespace: "default", + }, + Spec: garmoperatorv1beta1.GitHubCredentialSpec{ + Description: "github-creds", + EndpointRef: corev1.TypedLocalObjectReference{}, + AuthType: "pat", + SecretRef: garmoperatorv1beta1.SecretRef{ + Name: "github-secret", + Key: "token", + }, + }, + }, }, - expectedObject: &garmoperatorv1alpha1.Enterprise{ + expectedObject: &garmoperatorv1beta1.Enterprise{ ObjectMeta: metav1.ObjectMeta{ Name: "existing-enterprise", Namespace: "default", @@ -571,14 +740,18 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { key.EnterpriseFinalizerName, }, }, - Spec: garmoperatorv1alpha1.EnterpriseSpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.EnterpriseSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.EnterpriseStatus{ + Status: garmoperatorv1beta1.EnterpriseStatus{ ID: "9e0da3cb-130b-428d-aa8a-e314d955060e", Conditions: []metav1.Condition{ { @@ -588,6 +761,13 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { Message: "Pool Manager is not running", LastTransitionTime: metav1.NewTime(time.Now()), }, + { + Type: string(conditions.CredentialsReference), + Reason: string(conditions.FetchingCredentialsRefSuccessReason), + Status: metav1.ConditionTrue, + Message: "", + LastTransitionTime: metav1.NewTime(time.Now()), + }, { Type: string(conditions.PoolManager), Reason: string(conditions.PoolManagerFailureReason), @@ -612,12 +792,12 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { m.CreateEnterprise(enterprises.NewCreateEnterpriseParams().WithBody( params.CreateEnterpriseParams{ Name: "existing-enterprise", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", })).Return(&enterprises.CreateEnterpriseOK{ Payload: params.Enterprise{ Name: "existing-enterprise", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", ID: "9e0da3cb-130b-428d-aa8a-e314d955060e", }, @@ -625,13 +805,13 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { m.UpdateEnterprise(enterprises.NewUpdateEnterpriseParams(). WithEnterpriseID("9e0da3cb-130b-428d-aa8a-e314d955060e"). WithBody(params.UpdateEntityParams{ - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", })).Return(&enterprises.UpdateEnterpriseOK{ Payload: params.Enterprise{ ID: "9e0da3cb-130b-428d-aa8a-e314d955060e", Name: "existing-enterprise", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", }, }, nil) @@ -639,7 +819,7 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { }, { name: "secret ref not found condition", - object: &garmoperatorv1alpha1.Enterprise{ + object: &garmoperatorv1beta1.Enterprise{ ObjectMeta: metav1.ObjectMeta{ Name: "new-enterprise", Namespace: "default", @@ -647,17 +827,21 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { key.EnterpriseFinalizerName, }, }, - Spec: garmoperatorv1alpha1.EnterpriseSpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.EnterpriseSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.EnterpriseStatus{}, + Status: garmoperatorv1beta1.EnterpriseStatus{}, }, runtimeObjects: []runtime.Object{}, - expectedObject: &garmoperatorv1alpha1.Enterprise{ + expectedObject: &garmoperatorv1beta1.Enterprise{ ObjectMeta: metav1.ObjectMeta{ Name: "new-enterprise", Namespace: "default", @@ -665,14 +849,18 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { key.EnterpriseFinalizerName, }, }, - Spec: garmoperatorv1alpha1.EnterpriseSpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.EnterpriseSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.EnterpriseStatus{ + Status: garmoperatorv1beta1.EnterpriseStatus{ Conditions: []metav1.Condition{ { Type: string(conditions.ReadyCondition), @@ -681,11 +869,18 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { Message: "secrets \"my-webhook-secret\" not found", LastTransitionTime: metav1.NewTime(time.Now()), }, + { + Type: string(conditions.CredentialsReference), + Reason: string(conditions.UnknownReason), + Status: metav1.ConditionUnknown, + Message: conditions.CredentialsNotReconciledYetMsg, + LastTransitionTime: metav1.NewTime(time.Now()), + }, { Type: string(conditions.PoolManager), Reason: string(conditions.UnknownReason), Status: metav1.ConditionUnknown, - Message: "GARM server not reconciled yet", + Message: conditions.GarmServerNotReconciledYetMsg, LastTransitionTime: metav1.NewTime(time.Now()), }, { @@ -705,7 +900,7 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { schemeBuilder := runtime.SchemeBuilder{ - garmoperatorv1alpha1.AddToScheme, + garmoperatorv1beta1.AddToScheme, } err := schemeBuilder.AddToScheme(scheme.Scheme) @@ -714,7 +909,7 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { } runtimeObjects := []runtime.Object{tt.object} runtimeObjects = append(runtimeObjects, tt.runtimeObjects...) - client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(runtimeObjects...).WithStatusSubresource(&garmoperatorv1alpha1.Enterprise{}).Build() + client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(runtimeObjects...).WithStatusSubresource(&garmoperatorv1beta1.Enterprise{}).Build() // create a fake reconciler reconciler := &EnterpriseReconciler{ @@ -722,7 +917,7 @@ func TestEnterpriseReconciler_reconcileNormal(t *testing.T) { Recorder: record.NewFakeRecorder(3), } - enterprise := tt.object.DeepCopyObject().(*garmoperatorv1alpha1.Enterprise) + enterprise := tt.object.DeepCopyObject().(*garmoperatorv1beta1.Enterprise) mockEnterprise := mock.NewMockEnterpriseClient(mockCtrl) tt.expectGarmRequest(mockEnterprise.EXPECT()) @@ -760,11 +955,11 @@ func TestEnterpriseReconciler_reconcileDelete(t *testing.T) { runtimeObjects []runtime.Object expectGarmRequest func(m *mock.MockEnterpriseClientMockRecorder) wantErr bool - expectedObject *garmoperatorv1alpha1.Enterprise + expectedObject *garmoperatorv1beta1.Enterprise }{ { name: "delete enterprise", - object: &garmoperatorv1alpha1.Enterprise{ + object: &garmoperatorv1beta1.Enterprise{ ObjectMeta: metav1.ObjectMeta{ Name: "delete-enterprise", Namespace: "default", @@ -772,14 +967,18 @@ func TestEnterpriseReconciler_reconcileDelete(t *testing.T) { key.EnterpriseFinalizerName, }, }, - Spec: garmoperatorv1alpha1.EnterpriseSpec{ - CredentialsName: "totally-insecure", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.EnterpriseSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.EnterpriseStatus{ + Status: garmoperatorv1beta1.EnterpriseStatus{ ID: "e1dbf9a6-a9f6-4594-a5ac-12345", }, }, @@ -793,6 +992,21 @@ func TestEnterpriseReconciler_reconcileDelete(t *testing.T) { "webhookSecret": []byte("foobar"), }, }, + &garmoperatorv1beta1.GitHubCredential{ + ObjectMeta: metav1.ObjectMeta{ + Name: "github-creds", + Namespace: "default", + }, + Spec: garmoperatorv1beta1.GitHubCredentialSpec{ + Description: "github-creds", + EndpointRef: corev1.TypedLocalObjectReference{}, + AuthType: "pat", + SecretRef: garmoperatorv1beta1.SecretRef{ + Name: "github-secret", + Key: "token", + }, + }, + }, }, expectGarmRequest: func(m *mock.MockEnterpriseClientMockRecorder) { m.DeleteEnterprise( @@ -805,7 +1019,7 @@ func TestEnterpriseReconciler_reconcileDelete(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { schemeBuilder := runtime.SchemeBuilder{ - garmoperatorv1alpha1.AddToScheme, + garmoperatorv1beta1.AddToScheme, } err := schemeBuilder.AddToScheme(scheme.Scheme) @@ -815,7 +1029,7 @@ func TestEnterpriseReconciler_reconcileDelete(t *testing.T) { runtimeObjects := []runtime.Object{tt.object} runtimeObjects = append(runtimeObjects, tt.runtimeObjects...) - client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(runtimeObjects...).WithStatusSubresource(&garmoperatorv1alpha1.Enterprise{}).Build() + client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(runtimeObjects...).WithStatusSubresource(&garmoperatorv1beta1.Enterprise{}).Build() // create a fake reconciler reconciler := &EnterpriseReconciler{ @@ -823,7 +1037,7 @@ func TestEnterpriseReconciler_reconcileDelete(t *testing.T) { Recorder: record.NewFakeRecorder(3), } - enterprise := tt.object.DeepCopyObject().(*garmoperatorv1alpha1.Enterprise) + enterprise := tt.object.DeepCopyObject().(*garmoperatorv1beta1.Enterprise) mockEnterprise := mock.NewMockEnterpriseClient(mockCtrl) tt.expectGarmRequest(mockEnterprise.EXPECT()) diff --git a/internal/controller/garmserverconfig_controller.go b/internal/controller/garmserverconfig_controller.go new file mode 100644 index 00000000..bd02a0af --- /dev/null +++ b/internal/controller/garmserverconfig_controller.go @@ -0,0 +1,180 @@ +// SPDX-License-Identifier: MIT + +package controller + +import ( + "context" + "errors" + "reflect" + + garmapiserverparams "github.com/cloudbase/garm/apiserver/params" + garmcontroller "github.com/cloudbase/garm/client/controller" + "github.com/cloudbase/garm/client/controller_info" + "github.com/cloudbase/garm/params" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "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/controller" + "sigs.k8s.io/controller-runtime/pkg/log" + + garmoperatorv1beta1 "github.com/mercedes-benz/garm-operator/api/v1beta1" + "github.com/mercedes-benz/garm-operator/pkg/annotations" + garmclient "github.com/mercedes-benz/garm-operator/pkg/client" + "github.com/mercedes-benz/garm-operator/pkg/util" +) + +// GarmServerConfigReconciler reconciles a GarmServerConfig object +type GarmServerConfigReconciler struct { + client.Client + Scheme *runtime.Scheme + Recorder record.EventRecorder +} + +//+kubebuilder:rbac:groups=core,resources=events,verbs=create;patch +//+kubebuilder:rbac:groups=garm-operator.mercedes-benz.com,namespace=xxxxx,resources=garmserverconfigs,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=garm-operator.mercedes-benz.com,namespace=xxxxx,resources=garmserverconfigs/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=garm-operator.mercedes-benz.com,namespace=xxxxx,resources=garmserverconfigs/finalizers,verbs=update + +func (r *GarmServerConfigReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := log.FromContext(ctx) + log.Info("Reconciling GarmServerConfig") + + controllerClient := garmclient.NewControllerClient() + + garmServerConfig := &garmoperatorv1beta1.GarmServerConfig{} + if err := r.Get(ctx, req.NamespacedName, garmServerConfig); err != nil { + if apierrors.IsNotFound(err) { + log.Info("object was not found") + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + + // Ignore objects that are paused + if annotations.IsPaused(garmServerConfig) { + log.Info("Reconciliation is paused for GarmServerConfig") + return ctrl.Result{}, nil + } + + return r.reconcileNormal(ctx, controllerClient, garmServerConfig) +} + +func (r *GarmServerConfigReconciler) reconcileNormal(ctx context.Context, controllerClient garmclient.ControllerClient, garmServerConfig *garmoperatorv1beta1.GarmServerConfig) (ctrl.Result, error) { + log := log.FromContext(ctx) + + controllerInfo, err := r.getControllerInfo(controllerClient) + if err != nil { + log.Error(err, "Failed to get controller info") + return ctrl.Result{}, err + } + + // sync applied spec with controller info in garm + newControllerInfo, err := r.updateControllerInfo(ctx, controllerClient, garmServerConfig, &controllerInfo) + if err != nil { + return ctrl.Result{}, err + } + + // update CR with new state from garm + if err := r.updateGarmServerConfigStatus(ctx, newControllerInfo, garmServerConfig); err != nil { + return ctrl.Result{}, err + } + + return ctrl.Result{}, nil +} + +func (r *GarmServerConfigReconciler) updateControllerInfo(ctx context.Context, client garmclient.ControllerClient, garmServerConfigCR *garmoperatorv1beta1.GarmServerConfig, controllerInfo *params.ControllerInfo) (*params.ControllerInfo, error) { + log := log.FromContext(ctx) + + if garmServerConfigCR.Spec.MetadataURL == controllerInfo.MetadataURL && + garmServerConfigCR.Spec.CallbackURL == controllerInfo.CallbackURL && + garmServerConfigCR.Spec.WebhookURL == controllerInfo.WebhookURL { + log.Info("Controller info is up to date") + return controllerInfo, nil + } + + updateParams := garmcontroller.NewUpdateControllerParams().WithBody(params.UpdateControllerParams{ + MetadataURL: util.StringPtr(garmServerConfigCR.Spec.MetadataURL), + CallbackURL: util.StringPtr(garmServerConfigCR.Spec.CallbackURL), + WebhookURL: util.StringPtr(garmServerConfigCR.Spec.WebhookURL), + }) + + log.Info("Updating controller info in garm") + response, err := client.UpdateController(updateParams) + if err != nil { + log.Error(err, "Failed to update controller info") + return nil, err + } + return &response.Payload, nil +} + +func (r *GarmServerConfigReconciler) updateGarmServerConfigStatus(ctx context.Context, controllerInfo *params.ControllerInfo, garmServerConfigCR *garmoperatorv1beta1.GarmServerConfig) error { + log := log.FromContext(ctx) + + if !r.needsStatusUpdate(controllerInfo, garmServerConfigCR) { + log.Info("GarmServerConfig CR up to date") + return nil + } + + log.Info("Updating GarmServerConfig CR") + garmServerConfigStatus := garmoperatorv1beta1.GarmServerConfigStatus{ + ControllerID: controllerInfo.ControllerID.String(), + Hostname: controllerInfo.Hostname, + MetadataURL: controllerInfo.MetadataURL, + CallbackURL: controllerInfo.CallbackURL, + WebhookURL: controllerInfo.WebhookURL, + ControllerWebhookURL: controllerInfo.ControllerWebhookURL, + MinimumJobAgeBackoff: controllerInfo.MinimumJobAgeBackoff, + Version: controllerInfo.Version, + } + + garmServerConfigCR.Status = garmServerConfigStatus + err := r.Status().Update(ctx, garmServerConfigCR) + if err != nil { + log.Error(err, "Failed to update GarmServerConfig CR") + return err + } + return nil +} + +func (r *GarmServerConfigReconciler) needsStatusUpdate(controllerInfo *params.ControllerInfo, garmServerConfigCR *garmoperatorv1beta1.GarmServerConfig) bool { + tempStatus := garmoperatorv1beta1.GarmServerConfigStatus{ + ControllerID: controllerInfo.ControllerID.String(), + Hostname: controllerInfo.Hostname, + MetadataURL: controllerInfo.MetadataURL, + CallbackURL: controllerInfo.CallbackURL, + WebhookURL: controllerInfo.WebhookURL, + ControllerWebhookURL: controllerInfo.ControllerWebhookURL, + MinimumJobAgeBackoff: controllerInfo.MinimumJobAgeBackoff, + Version: controllerInfo.Version, + } + + return !reflect.DeepEqual(garmServerConfigCR.Status, tempStatus) +} + +func (r *GarmServerConfigReconciler) getControllerInfo(client garmclient.ControllerClient) (params.ControllerInfo, error) { + controllerInfo, err := client.GetControllerInfo() + if err == nil { + return controllerInfo.Payload, nil + } + + var conflictErr *controller_info.ControllerInfoConflict + if !errors.As(err, &conflictErr) { + return params.ControllerInfo{}, err + } + + if reflect.DeepEqual(conflictErr.Payload, garmapiserverparams.URLsRequired) { + return params.ControllerInfo{}, nil + } + + return params.ControllerInfo{}, err +} + +// SetupWithManager sets up the controller with the Manager. +func (r *GarmServerConfigReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&garmoperatorv1beta1.GarmServerConfig{}). + WithOptions(controller.Options{}). + Complete(r) +} diff --git a/internal/controller/garmserverconfig_controller_test.go b/internal/controller/garmserverconfig_controller_test.go new file mode 100644 index 00000000..428f5504 --- /dev/null +++ b/internal/controller/garmserverconfig_controller_test.go @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: MIT +package controller + +import ( + "context" + "reflect" + "testing" + + "github.com/cloudbase/garm/client/controller_info" + "github.com/cloudbase/garm/params" + "github.com/google/uuid" + "go.uber.org/mock/gomock" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + garmoperatorv1beta1 "github.com/mercedes-benz/garm-operator/api/v1beta1" + "github.com/mercedes-benz/garm-operator/pkg/client/mock" + "github.com/mercedes-benz/garm-operator/pkg/conditions" +) + +var controllerID = uuid.New() + +func TestGarmServerConfig_reconcile(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + tests := []struct { + name string + object *garmoperatorv1beta1.GarmServerConfig + expectGarmRequest func(m *mock.MockControllerClientMockRecorder) + runtimeObjects []runtime.Object + wantErr bool + expectedObject *garmoperatorv1beta1.GarmServerConfig + }{ + { + name: "sync controller info to GarmServerConfig", + object: &garmoperatorv1beta1.GarmServerConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "garm-server-config", + Namespace: "default", + }, + Spec: garmoperatorv1beta1.GarmServerConfigSpec{ + MetadataURL: "http://garm-server.garm-server.svc:9997/api/v1/metadata", + CallbackURL: "http://garm-server.garm-server.svc:9997/api/v1/callbacks", + WebhookURL: "http://garm-server.garm-server.svc:9997/api/v1/webhook", + }, + }, + expectedObject: &garmoperatorv1beta1.GarmServerConfig{ + ObjectMeta: metav1.ObjectMeta{ + Name: "garm-server-config", + Namespace: "default", + }, + Spec: garmoperatorv1beta1.GarmServerConfigSpec{ + MetadataURL: "http://garm-server.garm-server.svc:9997/api/v1/metadata", + CallbackURL: "http://garm-server.garm-server.svc:9997/api/v1/callbacks", + WebhookURL: "http://garm-server.garm-server.svc:9997/api/v1/webhook", + }, + Status: garmoperatorv1beta1.GarmServerConfigStatus{ + ControllerID: controllerID.String(), + Hostname: "garm.server.com", + MetadataURL: "http://garm-server.garm-server.svc:9997/api/v1/metadata", + CallbackURL: "http://garm-server.garm-server.svc:9997/api/v1/callbacks", + WebhookURL: "http://garm-server.garm-server.svc:9997/api/v1/webhook", + ControllerWebhookURL: " http://garm-server.garm-server.svc:9997/api/v1/webhook/BE4B3620-D424-43AC-8EDD-5760DBD516BF", + MinimumJobAgeBackoff: 30, + Version: "v0.1.5", + }, + }, + runtimeObjects: []runtime.Object{}, + wantErr: false, + expectGarmRequest: func(m *mock.MockControllerClientMockRecorder) { + m.GetControllerInfo().Return(&controller_info.ControllerInfoOK{Payload: params.ControllerInfo{ + ControllerID: controllerID, + Hostname: "garm.server.com", + MetadataURL: "http://garm-server.garm-server.svc:9997/api/v1/metadata", + CallbackURL: "http://garm-server.garm-server.svc:9997/api/v1/callbacks", + WebhookURL: "http://garm-server.garm-server.svc:9997/api/v1/webhook", + ControllerWebhookURL: " http://garm-server.garm-server.svc:9997/api/v1/webhook/BE4B3620-D424-43AC-8EDD-5760DBD516BF", + MinimumJobAgeBackoff: 30, + Version: "v0.1.5", + }}, nil) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + schemeBuilder := runtime.SchemeBuilder{ + garmoperatorv1beta1.AddToScheme, + } + + err := schemeBuilder.AddToScheme(scheme.Scheme) + if err != nil { + t.Fatal(err) + } + runtimeObjects := []runtime.Object{tt.object} + runtimeObjects = append(runtimeObjects, tt.runtimeObjects...) + client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(runtimeObjects...).WithStatusSubresource(&garmoperatorv1beta1.GarmServerConfig{}).Build() + + // create a fake reconciler + reconciler := &GarmServerConfigReconciler{ + Client: client, + Recorder: record.NewFakeRecorder(3), + } + + garmServerConfig := tt.object.DeepCopyObject().(*garmoperatorv1beta1.GarmServerConfig) + + mockController := mock.NewMockControllerClient(mockCtrl) + tt.expectGarmRequest(mockController.EXPECT()) + + _, err = reconciler.reconcileNormal(context.Background(), mockController, garmServerConfig) + if (err != nil) != tt.wantErr { + t.Errorf("GarmServerConfigReconciler.reconcileNormal() error = %v, wantErr %v", err, tt.wantErr) + return + } + + // clear out annotations to avoid comparison errors + garmServerConfig.ObjectMeta.Annotations = nil + + // empty resource version to avoid comparison errors + garmServerConfig.ObjectMeta.ResourceVersion = "" + + // clear conditions lastTransitionTime to avoid comparison errors + conditions.NilLastTransitionTime(tt.expectedObject) + conditions.NilLastTransitionTime(garmServerConfig) + + if !reflect.DeepEqual(garmServerConfig, tt.expectedObject) { + t.Errorf("GarmServerConfigReconciler.reconcileNormal() \ngot = %#v\n want %#v", garmServerConfig, tt.expectedObject) + } + }) + } +} diff --git a/internal/controller/githubcredentials_controller.go b/internal/controller/githubcredentials_controller.go new file mode 100644 index 00000000..eb8ee25d --- /dev/null +++ b/internal/controller/githubcredentials_controller.go @@ -0,0 +1,340 @@ +// SPDX-License-Identifier: MIT + +package controller + +import ( + "context" + "fmt" + "reflect" + + garmcredentials "github.com/cloudbase/garm/client/credentials" + garmconfig "github.com/cloudbase/garm/config" + "github.com/cloudbase/garm/params" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "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/builder" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + garmoperatorv1beta1 "github.com/mercedes-benz/garm-operator/api/v1beta1" + "github.com/mercedes-benz/garm-operator/pkg/annotations" + garmClient "github.com/mercedes-benz/garm-operator/pkg/client" + "github.com/mercedes-benz/garm-operator/pkg/client/key" + "github.com/mercedes-benz/garm-operator/pkg/conditions" + "github.com/mercedes-benz/garm-operator/pkg/event" + "github.com/mercedes-benz/garm-operator/pkg/secret" + "github.com/mercedes-benz/garm-operator/pkg/util" +) + +// GitHubCredentialReconciler reconciles a GitHubCredential object +type GitHubCredentialReconciler struct { + client.Client + Scheme *runtime.Scheme + Recorder record.EventRecorder +} + +//+kubebuilder:rbac:groups=core,resources=events,verbs=create;patch +//+kubebuilder:rbac:groups=garm-operator.mercedes-benz.com,namespace=xxxxx,resources=githubcredential,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=garm-operator.mercedes-benz.com,namespace=xxxxx,resources=githubcredential/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=garm-operator.mercedes-benz.com,namespace=xxxxx,resources=githubcredential/finalizers,verbs=update +//+kubebuilder:rbac:groups="",namespace=xxxxx,resources=secrets,verbs=get;list;watch; + +func (r *GitHubCredentialReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := log.FromContext(ctx) + + credentials := &garmoperatorv1beta1.GitHubCredential{} + if err := r.Get(ctx, req.NamespacedName, credentials); err != nil { + if apierrors.IsNotFound(err) { + log.Info("GitHubCredential resource not found.") + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + + // Ignore objects that are paused + if annotations.IsPaused(credentials) { + log.Info("Reconciliation is paused for this object") + return ctrl.Result{}, nil + } + + credentialsClient := garmClient.NewCredentialsClient() + + // Handle deleted credentials + if !credentials.DeletionTimestamp.IsZero() { + return r.reconcileDelete(ctx, credentialsClient, credentials) + } + + return r.reconcileNormal(ctx, credentialsClient, credentials) +} + +func (r *GitHubCredentialReconciler) reconcileNormal(ctx context.Context, client garmClient.CredentialsClient, credentials *garmoperatorv1beta1.GitHubCredential) (ctrl.Result, error) { + log := log.FromContext(ctx) + log.WithValues("credentials", credentials.Name) + + // If the GitHubCredential doesn't have our finalizer, add it. + if err := r.ensureFinalizer(ctx, credentials); err != nil { + return ctrl.Result{}, err + } + + // get credentials in garm db with resource name + garmGitHubCreds, err := r.getExistingCredentials(client, credentials.Name) + if err != nil { + event.Error(r.Recorder, credentials, err.Error()) + conditions.MarkFalse(credentials, conditions.ReadyCondition, conditions.GarmAPIErrorReason, err.Error()) + if err := r.Status().Update(ctx, credentials); err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{}, err + } + + // fetch endpoint resource + endpoint, err := r.getEndpointRef(ctx, credentials) + if err != nil { + conditions.MarkFalse(credentials, conditions.ReadyCondition, conditions.FetchingEndpointRefFailedReason, err.Error()) + conditions.MarkFalse(credentials, conditions.EndpointReference, conditions.FetchingEndpointRefFailedReason, err.Error()) + if err := r.Status().Update(ctx, credentials); err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{}, err + } + conditions.MarkTrue(credentials, conditions.EndpointReference, conditions.FetchingEndpointRefSuccessReason, "Successfully fetched GitHubEndpoint CR Ref") + + // fetch secret + githubSecret, err := secret.FetchRef(ctx, r.Client, &credentials.Spec.SecretRef, credentials.Namespace) + if err != nil { + conditions.MarkFalse(credentials, conditions.ReadyCondition, conditions.FetchingSecretRefFailedReason, err.Error()) + conditions.MarkFalse(credentials, conditions.SecretReference, conditions.FetchingSecretRefFailedReason, err.Error()) + if err := r.Status().Update(ctx, credentials); err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{}, err + } + conditions.MarkTrue(credentials, conditions.SecretReference, conditions.FetchingSecretRefSuccessReason, "") + + // if not found, create credentials in garm db + if reflect.ValueOf(garmGitHubCreds).IsZero() { + garmGitHubCreds, err = r.createCredentials(ctx, client, credentials, endpoint.Name, githubSecret) + if err != nil { + event.Error(r.Recorder, credentials, err.Error()) + conditions.MarkFalse(credentials, conditions.ReadyCondition, conditions.GarmAPIErrorReason, err.Error()) + if err := r.Status().Update(ctx, credentials); err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{}, err + } + } + + // update credentials cr anytime the credentials in garm db changes + garmGitHubCreds, err = r.updateCredentials(ctx, client, int64(garmGitHubCreds.ID), credentials, githubSecret) + if err != nil { + event.Error(r.Recorder, credentials, err.Error()) + conditions.MarkFalse(credentials, conditions.ReadyCondition, conditions.GarmAPIErrorReason, err.Error()) + if err := r.Status().Update(ctx, credentials); err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{}, err + } + + // set and update credentials status + credentials.Status.ID = int64(garmGitHubCreds.ID) + credentials.Status.BaseURL = garmGitHubCreds.BaseURL + credentials.Status.APIBaseURL = garmGitHubCreds.APIBaseURL + credentials.Status.UploadBaseURL = garmGitHubCreds.UploadBaseURL + conditions.MarkTrue(credentials, conditions.ReadyCondition, conditions.SuccessfulReconcileReason, "") + if err := r.Status().Update(ctx, credentials); err != nil { + return ctrl.Result{}, err + } + + log.Info("reconciling credentials successfully done") + return ctrl.Result{}, nil +} + +func (r *GitHubCredentialReconciler) getExistingCredentials(client garmClient.CredentialsClient, name string) (params.GithubCredentials, error) { + credentials, err := client.ListCredentials(garmcredentials.NewListCredentialsParams()) + if err != nil { + return params.GithubCredentials{}, err + } + + for _, creds := range credentials.Payload { + if creds.Name == name { + return creds, nil + } + } + + return params.GithubCredentials{}, nil +} + +func (r *GitHubCredentialReconciler) createCredentials(ctx context.Context, client garmClient.CredentialsClient, credentials *garmoperatorv1beta1.GitHubCredential, endpoint, githubSecret string) (params.GithubCredentials, error) { + log := log.FromContext(ctx) + log.WithValues("credentials", credentials.Name) + + log.Info("GitHubCredential doesn't exist on garm side. Creating new credentials in garm.") + event.Creating(r.Recorder, credentials, "credentials doesn't exist on garm side") + + req := params.CreateGithubCredentialsParams{ + Name: credentials.Name, + Description: credentials.Spec.Description, + AuthType: credentials.Spec.AuthType, + Endpoint: endpoint, + } + + switch credentials.Spec.AuthType { + case params.GithubAuthType(garmconfig.GithubAuthTypePAT): + req.PAT.OAuth2Token = githubSecret + case params.GithubAuthType(garmconfig.GithubAuthTypeApp): + req.App.AppID = credentials.Spec.AppID + req.App.InstallationID = credentials.Spec.InstallationID + req.App.PrivateKeyBytes = []byte(githubSecret) + default: + return params.GithubCredentials{}, fmt.Errorf("invalid auth type %s", credentials.Spec.AuthType) + } + + garmCredentials, err := client.CreateCredentials(garmcredentials.NewCreateCredentialsParams().WithBody(req)) + if err != nil { + log.V(1).Info(fmt.Sprintf("client.CreateCredentials error: %s", err)) + return params.GithubCredentials{}, err + } + + log.V(1).Info(fmt.Sprintf("credentials %s created - return Value %v", credentials.Name, garmCredentials)) + + log.Info("creating credentials in garm succeeded") + event.Info(r.Recorder, credentials, "creating credentials in garm succeeded") + + return garmCredentials.Payload, nil +} + +func (r *GitHubCredentialReconciler) updateCredentials(ctx context.Context, client garmClient.CredentialsClient, credentialsID int64, credentials *garmoperatorv1beta1.GitHubCredential, githubSecret string) (params.GithubCredentials, error) { + log := log.FromContext(ctx) + log.V(1).Info("update credentials") + + req := params.UpdateGithubCredentialsParams{ + Name: util.StringPtr(credentials.Name), + Description: util.StringPtr(credentials.Spec.Description), + } + + switch credentials.Spec.AuthType { + case params.GithubAuthType(garmconfig.GithubAuthTypePAT): + req.PAT = ¶ms.GithubPAT{OAuth2Token: githubSecret} + case params.GithubAuthType(garmconfig.GithubAuthTypeApp): + req.App = ¶ms.GithubApp{ + AppID: credentials.Spec.AppID, + InstallationID: credentials.Spec.InstallationID, + PrivateKeyBytes: []byte(githubSecret), + } + default: + return params.GithubCredentials{}, fmt.Errorf("invalid auth type %s", credentials.Spec.AuthType) + } + + retValue, err := client.UpdateCredentials( + garmcredentials.NewUpdateCredentialsParams(). + WithID(credentialsID). + WithBody(req)) + if err != nil { + log.V(1).Info(fmt.Sprintf("client.UpdateCredentials error: %s", err)) + return params.GithubCredentials{}, err + } + + return retValue.Payload, nil +} + +func (r *GitHubCredentialReconciler) reconcileDelete(ctx context.Context, client garmClient.CredentialsClient, credentials *garmoperatorv1beta1.GitHubCredential) (ctrl.Result, error) { + log := log.FromContext(ctx) + log.WithValues("credentials", credentials.Name) + + log.Info("starting credentials deletion") + event.Deleting(r.Recorder, credentials, "starting credentials deletion") + conditions.MarkFalse(credentials, conditions.ReadyCondition, conditions.DeletingReason, conditions.DeletingCredentialsMsg) + if err := r.Status().Update(ctx, credentials); err != nil { + return ctrl.Result{}, err + } + + if err := client.DeleteCredentials( + garmcredentials.NewDeleteCredentialsParams(). + WithID(credentials.Status.ID), + ); err != nil { + log.V(1).Info(fmt.Sprintf("client.DeleteCredentials error: %s", err)) + event.Error(r.Recorder, credentials, err.Error()) + conditions.MarkFalse(credentials, conditions.ReadyCondition, conditions.GarmAPIErrorReason, err.Error()) + if err := r.Status().Update(ctx, credentials); err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{}, err + } + + if controllerutil.ContainsFinalizer(credentials, key.CredentialsFinalizerName) { + controllerutil.RemoveFinalizer(credentials, key.CredentialsFinalizerName) + if err := r.Update(ctx, credentials); err != nil { + return ctrl.Result{}, err + } + } + + log.Info("credentials deletion done") + + return ctrl.Result{}, nil +} + +func (r *GitHubCredentialReconciler) getEndpointRef(ctx context.Context, credentials *garmoperatorv1beta1.GitHubCredential) (*garmoperatorv1beta1.GitHubEndpoint, error) { + endpoint := &garmoperatorv1beta1.GitHubEndpoint{} + err := r.Get(ctx, types.NamespacedName{ + Namespace: credentials.Namespace, + Name: credentials.Spec.EndpointRef.Name, + }, endpoint) + if err != nil { + return endpoint, err + } + return endpoint, nil +} + +func (r *GitHubCredentialReconciler) ensureFinalizer(ctx context.Context, credentials *garmoperatorv1beta1.GitHubCredential) error { + if !controllerutil.ContainsFinalizer(credentials, key.CredentialsFinalizerName) { + controllerutil.AddFinalizer(credentials, key.CredentialsFinalizerName) + return r.Update(ctx, credentials) + } + return nil +} + +func (r *GitHubCredentialReconciler) findCredentialsForSecret(ctx context.Context, obj client.Object) []reconcile.Request { + secret, ok := obj.(*corev1.Secret) + if !ok { + return nil + } + + var creds garmoperatorv1beta1.GitHubCredentialList + if err := r.List(ctx, &creds); err != nil { + return nil + } + + var requests []reconcile.Request + for _, c := range creds.Items { + if c.Spec.SecretRef.Name == secret.Name { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: c.Namespace, + Name: c.Name, + }, + }) + } + } + + return requests +} + +// SetupWithManager sets up the controller with the Manager. +func (r *GitHubCredentialReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&garmoperatorv1beta1.GitHubCredential{}). + Watches( + &corev1.Secret{}, + handler.EnqueueRequestsFromMapFunc(r.findCredentialsForSecret), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ). + Complete(r) +} diff --git a/internal/controller/githubendpoint_controller.go b/internal/controller/githubendpoint_controller.go new file mode 100644 index 00000000..d76d5907 --- /dev/null +++ b/internal/controller/githubendpoint_controller.go @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: MIT + +package controller + +import ( + "context" + "fmt" + "reflect" + + "github.com/cloudbase/garm/client/endpoints" + "github.com/cloudbase/garm/params" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "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/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/log" + + garmoperatorv1beta1 "github.com/mercedes-benz/garm-operator/api/v1beta1" + "github.com/mercedes-benz/garm-operator/pkg/annotations" + garmClient "github.com/mercedes-benz/garm-operator/pkg/client" + "github.com/mercedes-benz/garm-operator/pkg/client/key" + "github.com/mercedes-benz/garm-operator/pkg/conditions" + "github.com/mercedes-benz/garm-operator/pkg/event" + "github.com/mercedes-benz/garm-operator/pkg/util" +) + +// GitHubEndpointReconciler reconciles a GitHubEndpoint object +type GitHubEndpointReconciler struct { + client.Client + Scheme *runtime.Scheme + Recorder record.EventRecorder +} + +//+kubebuilder:rbac:groups=core,resources=events,verbs=create;patch +//+kubebuilder:rbac:groups=garm-operator.mercedes-benz.com,namespace=xxxxx,resources=githubendpoints,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=garm-operator.mercedes-benz.com,namespace=xxxxx,resources=githubendpoints/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=garm-operator.mercedes-benz.com,namespace=xxxxx,resources=githubendpoints/finalizers,verbs=update +// +kubebuilder:rbac:groups="",namespace=xxxxx,resources=secrets,verbs=get;list;watch; + +func (r *GitHubEndpointReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + log := log.FromContext(ctx) + + endpoint := &garmoperatorv1beta1.GitHubEndpoint{} + if err := r.Get(ctx, req.NamespacedName, endpoint); err != nil { + if apierrors.IsNotFound(err) { + log.Info("GitHubEndpoint resource not found.") + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + + // Ignore objects that are paused + if annotations.IsPaused(endpoint) { + log.Info("Reconciliation is paused for this object") + return ctrl.Result{}, nil + } + + endpointClient := garmClient.NewEndpointClient() + + // Handle deleted endpoints + if !endpoint.DeletionTimestamp.IsZero() { + return r.reconcileDelete(ctx, endpointClient, endpoint) + } + + return r.reconcileNormal(ctx, endpointClient, endpoint) +} + +func (r *GitHubEndpointReconciler) reconcileNormal(ctx context.Context, client garmClient.EndpointClient, endpoint *garmoperatorv1beta1.GitHubEndpoint) (ctrl.Result, error) { + log := log.FromContext(ctx) + log.WithValues("endpoint", endpoint.Name) + + // If the GitHubEndpoint doesn't have our finalizer, add it. + if err := r.ensureFinalizer(ctx, endpoint); err != nil { + return ctrl.Result{}, err + } + + // get endpoint in garm db with resource name + garmEndpoint, err := r.getExistingEndpoint(client, endpoint.Name) + if err != nil { + event.Error(r.Recorder, endpoint, err.Error()) + conditions.MarkFalse(endpoint, conditions.ReadyCondition, conditions.GarmAPIErrorReason, err.Error()) + if err := r.Status().Update(ctx, endpoint); err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{}, err + } + + // if not found, create endpoint in garm db + if reflect.ValueOf(garmEndpoint).IsZero() { + garmEndpoint, err = r.createEndpoint(ctx, client, endpoint) // nolint:wastedassign + if err != nil { + event.Error(r.Recorder, endpoint, err.Error()) + conditions.MarkFalse(endpoint, conditions.ReadyCondition, conditions.GarmAPIErrorReason, err.Error()) + if err := r.Status().Update(ctx, endpoint); err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{}, err + } + } + + // update endpoint cr anytime the endpoint in garm db changes + garmEndpoint, err = r.updateEndpoint(ctx, client, endpoint) + if err != nil { + event.Error(r.Recorder, endpoint, err.Error()) + conditions.MarkFalse(endpoint, conditions.ReadyCondition, conditions.GarmAPIErrorReason, err.Error()) + if err := r.Status().Update(ctx, endpoint); err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{}, err + } + + // set and update endpoint status + conditions.MarkTrue(endpoint, conditions.ReadyCondition, conditions.SuccessfulReconcileReason, "") + log.Info("reconciling endpoint successfully done", "endpoint", garmEndpoint.Name) + if err := r.Status().Update(ctx, endpoint); err != nil { + return ctrl.Result{}, err + } + + log.Info("reconciling endpoint successfully done") + return ctrl.Result{}, nil +} + +func (r *GitHubEndpointReconciler) getExistingEndpoint(client garmClient.EndpointClient, name string) (params.GithubEndpoint, error) { + endpoint, err := client.GetEndpoint(endpoints.NewGetGithubEndpointParams().WithName(name)) + if err != nil && garmClient.IsNotFoundError(err) { + return params.GithubEndpoint{}, nil + } + + if err != nil { + return params.GithubEndpoint{}, err + } + + return endpoint.Payload, nil +} + +func (r *GitHubEndpointReconciler) createEndpoint(ctx context.Context, client garmClient.EndpointClient, endpoint *garmoperatorv1beta1.GitHubEndpoint) (params.GithubEndpoint, error) { + log := log.FromContext(ctx) + log.WithValues("endpoint", endpoint.Name) + + log.Info("GitHubEndpoint doesn't exist on garm side. Creating new endpoint in garm.") + event.Creating(r.Recorder, endpoint, "endpoint doesn't exist on garm side") + + retValue, err := client.CreateEndpoint(endpoints.NewCreateGithubEndpointParams().WithBody(params.CreateGithubEndpointParams{ + Name: endpoint.Name, + Description: endpoint.Spec.Description, + APIBaseURL: endpoint.Spec.APIBaseURL, + UploadBaseURL: endpoint.Spec.UploadBaseURL, + BaseURL: endpoint.Spec.BaseURL, + CACertBundle: endpoint.Spec.CACertBundle, + })) + if err != nil { + log.V(1).Info(fmt.Sprintf("client.CreateEndpoint error: %s", err)) + return params.GithubEndpoint{}, err + } + + log.V(1).Info(fmt.Sprintf("endpoint %s created - return Value %v", endpoint.Name, retValue)) + + log.Info("creating endpoint in garm succeeded") + event.Info(r.Recorder, endpoint, "creating endpoint in garm succeeded") + + return retValue.Payload, nil +} + +func (r *GitHubEndpointReconciler) updateEndpoint(ctx context.Context, client garmClient.EndpointClient, endpoint *garmoperatorv1beta1.GitHubEndpoint) (params.GithubEndpoint, error) { + log := log.FromContext(ctx) + log.V(1).Info("update endpoint") + + retValue, err := client.UpdateEndpoint( + endpoints.NewUpdateGithubEndpointParams(). + WithName(endpoint.Name). + WithBody(params.UpdateGithubEndpointParams{ + Description: util.StringPtr(endpoint.Spec.Description), + APIBaseURL: util.StringPtr(endpoint.Spec.APIBaseURL), + UploadBaseURL: util.StringPtr(endpoint.Spec.UploadBaseURL), + BaseURL: util.StringPtr(endpoint.Spec.BaseURL), + CACertBundle: endpoint.Spec.CACertBundle, + })) + if err != nil { + log.V(1).Info(fmt.Sprintf("client.UpdateEndpoint error: %s", err)) + return params.GithubEndpoint{}, err + } + + return retValue.Payload, nil +} + +func (r *GitHubEndpointReconciler) reconcileDelete(ctx context.Context, client garmClient.EndpointClient, endpoint *garmoperatorv1beta1.GitHubEndpoint) (ctrl.Result, error) { + log := log.FromContext(ctx) + log.WithValues("endpoint", endpoint.Name) + + log.Info("starting endpoint deletion") + event.Deleting(r.Recorder, endpoint, "starting endpoint deletion") + conditions.MarkFalse(endpoint, conditions.ReadyCondition, conditions.DeletingReason, conditions.DeletingEndpointMsg) + if err := r.Status().Update(ctx, endpoint); err != nil { + return ctrl.Result{}, err + } + + err := client.DeleteEndpoint( + endpoints.NewDeleteGithubEndpointParams(). + WithName(endpoint.Name), + ) + if err != nil { + log.V(1).Info(fmt.Sprintf("client.DeleteEndpoint error: %s", err)) + event.Error(r.Recorder, endpoint, err.Error()) + conditions.MarkFalse(endpoint, conditions.ReadyCondition, conditions.GarmAPIErrorReason, err.Error()) + if err := r.Status().Update(ctx, endpoint); err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{}, err + } + + if controllerutil.ContainsFinalizer(endpoint, key.GitHubEndpointFinalizerName) { + controllerutil.RemoveFinalizer(endpoint, key.GitHubEndpointFinalizerName) + if err := r.Update(ctx, endpoint); err != nil { + return ctrl.Result{}, err + } + } + + log.Info("endpoint deletion done") + + return ctrl.Result{}, nil +} + +func (r *GitHubEndpointReconciler) ensureFinalizer(ctx context.Context, endpoint *garmoperatorv1beta1.GitHubEndpoint) error { + if !controllerutil.ContainsFinalizer(endpoint, key.GitHubEndpointFinalizerName) { + controllerutil.AddFinalizer(endpoint, key.GitHubEndpointFinalizerName) + return r.Update(ctx, endpoint) + } + return nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *GitHubEndpointReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&garmoperatorv1beta1.GitHubEndpoint{}). + Complete(r) +} diff --git a/internal/controller/githubendpoint_controller_test.go b/internal/controller/githubendpoint_controller_test.go new file mode 100644 index 00000000..4e75244f --- /dev/null +++ b/internal/controller/githubendpoint_controller_test.go @@ -0,0 +1,496 @@ +// SPDX-License-Identifier: MIT + +package controller + +import ( + "context" + "reflect" + "testing" + "time" + + "github.com/cloudbase/garm/client/endpoints" + "github.com/cloudbase/garm/params" + "go.uber.org/mock/gomock" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + garmoperatorv1beta1 "github.com/mercedes-benz/garm-operator/api/v1beta1" + "github.com/mercedes-benz/garm-operator/pkg/client/key" + "github.com/mercedes-benz/garm-operator/pkg/client/mock" + "github.com/mercedes-benz/garm-operator/pkg/conditions" + "github.com/mercedes-benz/garm-operator/pkg/util" +) + +func TestGitHubEndpointReconciler_reconcileNormal(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + tests := []struct { + name string + object runtime.Object + expectGarmRequest func(m *mock.MockEndpointClientMockRecorder) + runtimeObjects []runtime.Object + wantErr bool + expectedObject *garmoperatorv1beta1.GitHubEndpoint + }{ + { + name: "github-endpoint exist - update", + object: &garmoperatorv1beta1.GitHubEndpoint{ + ObjectMeta: metav1.ObjectMeta{ + Name: "existing-github-endpoint", + Namespace: "default", + Finalizers: []string{ + key.GitHubEndpointFinalizerName, + }, + }, + Spec: garmoperatorv1beta1.GitHubEndpointSpec{ + Description: "existing-github-endpoint", + APIBaseURL: "https://api.github.com", + UploadBaseURL: "https://uploads.github.com", + BaseURL: "https://github.com", + CACertBundle: nil, + }, + }, + runtimeObjects: []runtime.Object{}, + expectedObject: &garmoperatorv1beta1.GitHubEndpoint{ + ObjectMeta: metav1.ObjectMeta{ + Name: "existing-github-endpoint", + Namespace: "default", + Finalizers: []string{ + key.GitHubEndpointFinalizerName, + }, + }, + Spec: garmoperatorv1beta1.GitHubEndpointSpec{ + Description: "existing-github-endpoint", + APIBaseURL: "https://api.github.com", + UploadBaseURL: "https://uploads.github.com", + BaseURL: "https://github.com", + CACertBundle: nil, + }, + Status: garmoperatorv1beta1.GitHubEndpointStatus{ + Conditions: []metav1.Condition{ + { + Type: string(conditions.ReadyCondition), + Reason: string(conditions.SuccessfulReconcileReason), + Status: metav1.ConditionTrue, + Message: "", + LastTransitionTime: metav1.NewTime(time.Now()), + }, + }, + }, + }, + expectGarmRequest: func(m *mock.MockEndpointClientMockRecorder) { + m.GetEndpoint(endpoints.NewGetGithubEndpointParams().WithName("existing-github-endpoint")).Return(&endpoints.GetGithubEndpointOK{ + Payload: params.GithubEndpoint{ + Name: "existing-github-endpoint", + Description: "existing-github-endpoint", + APIBaseURL: "https://api.github.com", + UploadBaseURL: "https://uploads.github.com", + BaseURL: "https://github.com", + CACertBundle: nil, + }, + }, nil) + m.UpdateEndpoint(endpoints.NewUpdateGithubEndpointParams(). + WithName("existing-github-endpoint"). + WithBody(params.UpdateGithubEndpointParams{ + Description: util.StringPtr("existing-github-endpoint"), + APIBaseURL: util.StringPtr("https://api.github.com"), + UploadBaseURL: util.StringPtr("https://uploads.github.com"), + BaseURL: util.StringPtr("https://github.com"), + CACertBundle: nil, + })).Return(&endpoints.UpdateGithubEndpointOK{ + Payload: params.GithubEndpoint{ + Name: "existing-github-endpoint", + Description: "existing-github-endpoint", + APIBaseURL: "https://api.github.com", + UploadBaseURL: "https://uploads.github.com", + BaseURL: "https://github.com", + CACertBundle: nil, + }, + }, nil) + }, + wantErr: false, + }, + { + name: "github-endpoint exist but spec has changed - update", + object: &garmoperatorv1beta1.GitHubEndpoint{ + ObjectMeta: metav1.ObjectMeta{ + Name: "existing-github-endpoint", + Namespace: "default", + Finalizers: []string{ + key.GitHubEndpointFinalizerName, + }, + }, + Spec: garmoperatorv1beta1.GitHubEndpointSpec{ + Description: "has-changed", + APIBaseURL: "https://api.github-enterprise.com", + UploadBaseURL: "https://uploads.github-enterprise.com", + BaseURL: "https://github-enterprise.com", + CACertBundle: nil, + }, + Status: garmoperatorv1beta1.GitHubEndpointStatus{ + Conditions: []metav1.Condition{ + { + Type: string(conditions.ReadyCondition), + Reason: string(conditions.SuccessfulReconcileReason), + Status: metav1.ConditionTrue, + Message: "", + LastTransitionTime: metav1.NewTime(time.Now()), + }, + }, + }, + }, + runtimeObjects: []runtime.Object{}, + expectedObject: &garmoperatorv1beta1.GitHubEndpoint{ + ObjectMeta: metav1.ObjectMeta{ + Name: "existing-github-endpoint", + Namespace: "default", + Finalizers: []string{ + key.GitHubEndpointFinalizerName, + }, + }, + Spec: garmoperatorv1beta1.GitHubEndpointSpec{ + Description: "has-changed", + APIBaseURL: "https://api.github-enterprise.com", + UploadBaseURL: "https://uploads.github-enterprise.com", + BaseURL: "https://github-enterprise.com", + CACertBundle: nil, + }, + Status: garmoperatorv1beta1.GitHubEndpointStatus{ + Conditions: []metav1.Condition{ + { + Type: string(conditions.ReadyCondition), + Reason: string(conditions.SuccessfulReconcileReason), + Status: metav1.ConditionTrue, + Message: "", + LastTransitionTime: metav1.NewTime(time.Now()), + }, + }, + }, + }, + expectGarmRequest: func(m *mock.MockEndpointClientMockRecorder) { + m.GetEndpoint(endpoints.NewGetGithubEndpointParams(). + WithName("existing-github-endpoint")). + Return(&endpoints.GetGithubEndpointOK{ + Payload: params.GithubEndpoint{ + Name: "existing-github-endpoint", + Description: "existing-github-endpoint", + APIBaseURL: "https://api.github.com", + UploadBaseURL: "https://uploads.github.com", + BaseURL: "https://github.com", + CACertBundle: nil, + }, + }, nil) + m.UpdateEndpoint(endpoints.NewUpdateGithubEndpointParams(). + WithName("existing-github-endpoint"). + WithBody(params.UpdateGithubEndpointParams{ + Description: util.StringPtr("has-changed"), + APIBaseURL: util.StringPtr("https://api.github-enterprise.com"), + UploadBaseURL: util.StringPtr("https://uploads.github-enterprise.com"), + BaseURL: util.StringPtr("https://github-enterprise.com"), + CACertBundle: nil, + })).Return(&endpoints.UpdateGithubEndpointOK{ + Payload: params.GithubEndpoint{ + Name: "existing-github-endpoint", + Description: "has-changed", + APIBaseURL: "https://api.github-enterprise.com", + UploadBaseURL: "https://uploads.github-enterprise.com", + BaseURL: "https://github-enterprise.com", + CACertBundle: nil, + }, + }, nil) + }, + wantErr: false, + }, + { + name: "github-endpoint does not exist - create and update", + object: &garmoperatorv1beta1.GitHubEndpoint{ + ObjectMeta: metav1.ObjectMeta{ + Name: "new-github-endpoint", + Namespace: "default", + }, + Spec: garmoperatorv1beta1.GitHubEndpointSpec{ + Description: "new github endpoint", + APIBaseURL: "https://api.github.com", + UploadBaseURL: "https://uploads.github.com", + BaseURL: "https://github.com", + CACertBundle: nil, + }, + }, + expectedObject: &garmoperatorv1beta1.GitHubEndpoint{ + ObjectMeta: metav1.ObjectMeta{ + Name: "new-github-endpoint", + Namespace: "default", + Finalizers: []string{ + key.GitHubEndpointFinalizerName, + }, + }, + Spec: garmoperatorv1beta1.GitHubEndpointSpec{ + Description: "new github endpoint", + APIBaseURL: "https://api.github.com", + UploadBaseURL: "https://uploads.github.com", + BaseURL: "https://github.com", + CACertBundle: nil, + }, + Status: garmoperatorv1beta1.GitHubEndpointStatus{ + Conditions: []metav1.Condition{ + { + Type: string(conditions.ReadyCondition), + Reason: string(conditions.SuccessfulReconcileReason), + Status: metav1.ConditionTrue, + Message: "", + LastTransitionTime: metav1.NewTime(time.Now()), + }, + }, + }, + }, + runtimeObjects: []runtime.Object{}, + expectGarmRequest: func(m *mock.MockEndpointClientMockRecorder) { + m.GetEndpoint(endpoints.NewGetGithubEndpointParams(). + WithName("new-github-endpoint")). + Return(nil, endpoints.NewGetGithubEndpointDefault(404)) + m.CreateEndpoint(endpoints.NewCreateGithubEndpointParams(). + WithBody(params.CreateGithubEndpointParams{ + Name: "new-github-endpoint", + Description: "new github endpoint", + APIBaseURL: "https://api.github.com", + UploadBaseURL: "https://uploads.github.com", + BaseURL: "https://github.com", + CACertBundle: nil, + })).Return(&endpoints.CreateGithubEndpointOK{ + Payload: params.GithubEndpoint{ + Name: "new-github-endpoint", + Description: "new github endpoint", + APIBaseURL: "https://api.github.com", + UploadBaseURL: "https://uploads.github.com", + BaseURL: "https://github.com", + CACertBundle: nil, + }, + }, nil) + m.UpdateEndpoint(endpoints.NewUpdateGithubEndpointParams(). + WithName("new-github-endpoint"). + WithBody(params.UpdateGithubEndpointParams{ + Description: util.StringPtr("new github endpoint"), + APIBaseURL: util.StringPtr("https://api.github.com"), + UploadBaseURL: util.StringPtr("https://uploads.github.com"), + BaseURL: util.StringPtr("https://github.com"), + CACertBundle: nil, + })).Return(&endpoints.UpdateGithubEndpointOK{ + Payload: params.GithubEndpoint{ + Name: "new-github-endpoint", + Description: "new github endpoint", + APIBaseURL: "https://api.github.com", + UploadBaseURL: "https://uploads.github.com", + BaseURL: "https://github.com", + CACertBundle: nil, + }, + }, nil) + }, + wantErr: false, + }, + { + name: "github-endpoint update - bad request error", + object: &garmoperatorv1beta1.GitHubEndpoint{ + ObjectMeta: metav1.ObjectMeta{ + Name: "existing-github-endpoint", + Namespace: "default", + }, + Spec: garmoperatorv1beta1.GitHubEndpointSpec{ + Description: "", + APIBaseURL: "", + UploadBaseURL: "", + BaseURL: "", + CACertBundle: nil, + }, + }, + expectedObject: &garmoperatorv1beta1.GitHubEndpoint{ + ObjectMeta: metav1.ObjectMeta{ + Name: "existing-github-endpoint", + Namespace: "default", + Finalizers: []string{ + key.GitHubEndpointFinalizerName, + }, + }, + Spec: garmoperatorv1beta1.GitHubEndpointSpec{}, + Status: garmoperatorv1beta1.GitHubEndpointStatus{ + Conditions: []metav1.Condition{ + { + Type: string(conditions.ReadyCondition), + Reason: string(conditions.GarmAPIErrorReason), + Status: metav1.ConditionFalse, + Message: "[PUT /github/endpoints/{name}][400] UpdateGithubEndpoint default {\"error\":\"\",\"details\":\"\"}", + LastTransitionTime: metav1.NewTime(time.Now()), + }, + }, + }, + }, + runtimeObjects: []runtime.Object{}, + expectGarmRequest: func(m *mock.MockEndpointClientMockRecorder) { + m.GetEndpoint(endpoints.NewGetGithubEndpointParams(). + WithName("existing-github-endpoint")). + Return(&endpoints.GetGithubEndpointOK{ + Payload: params.GithubEndpoint{ + Name: "existing-github-endpoint", + Description: "existing-github-endpoint", + APIBaseURL: "https://api.github.com", + UploadBaseURL: "https://uploads.github.com", + BaseURL: "https://github.com", + CACertBundle: nil, + }, + }, nil) + m.UpdateEndpoint(endpoints.NewUpdateGithubEndpointParams(). + WithName("existing-github-endpoint"). + WithBody(params.UpdateGithubEndpointParams{ + Description: util.StringPtr(""), + APIBaseURL: util.StringPtr(""), + UploadBaseURL: util.StringPtr(""), + BaseURL: util.StringPtr(""), + CACertBundle: nil, + })).Return(nil, endpoints.NewUpdateGithubEndpointDefault(400)) + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + schemeBuilder := runtime.SchemeBuilder{ + garmoperatorv1beta1.AddToScheme, + } + + err := schemeBuilder.AddToScheme(scheme.Scheme) + if err != nil { + t.Fatal(err) + } + runtimeObjects := []runtime.Object{tt.object} + runtimeObjects = append(runtimeObjects, tt.runtimeObjects...) + client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(runtimeObjects...).WithStatusSubresource(&garmoperatorv1beta1.GitHubEndpoint{}).Build() + + // create a fake reconciler + reconciler := &GitHubEndpointReconciler{ + Client: client, + Recorder: record.NewFakeRecorder(3), + } + + githubEndpoint := tt.object.DeepCopyObject().(*garmoperatorv1beta1.GitHubEndpoint) + + mockGitHubEndpoint := mock.NewMockEndpointClient(mockCtrl) + tt.expectGarmRequest(mockGitHubEndpoint.EXPECT()) + + _, err = reconciler.reconcileNormal(context.Background(), mockGitHubEndpoint, githubEndpoint) + if (err != nil) != tt.wantErr { + t.Errorf("GitHubEndpointReconciler.reconcileNormal() error = %v, wantErr %v", err, tt.wantErr) + return + } + + // clear out annotations to avoid comparison errors + githubEndpoint.ObjectMeta.Annotations = nil + + // empty resource version to avoid comparison errors + githubEndpoint.ObjectMeta.ResourceVersion = "" + + // clear conditions lastTransitionTime to avoid comparison errors + conditions.NilLastTransitionTime(tt.expectedObject) + conditions.NilLastTransitionTime(githubEndpoint) + + if !reflect.DeepEqual(githubEndpoint, tt.expectedObject) { + t.Errorf("GitHubEndpointReconciler.reconcileNormal() \ngot = %#v\n want %#v", githubEndpoint, tt.expectedObject) + } + }) + } +} + +func TestGitHubEndpointReconciler_reconcileDelete(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + tests := []struct { + name string + object runtime.Object + runtimeObjects []runtime.Object + expectGarmRequest func(m *mock.MockEndpointClientMockRecorder) + wantErr bool + expectedObject *garmoperatorv1beta1.GitHubEndpoint + }{ + { + name: "delete github-endpoint", + object: &garmoperatorv1beta1.GitHubEndpoint{ + ObjectMeta: metav1.ObjectMeta{ + Name: "existing-github-endpoint", + Namespace: "default", + Finalizers: []string{ + key.GitHubEndpointFinalizerName, + }, + }, + Spec: garmoperatorv1beta1.GitHubEndpointSpec{ + Description: "existing-github-endpoint", + APIBaseURL: "https://api.github.com", + UploadBaseURL: "https://uploads.github.com", + BaseURL: "https://github.com", + CACertBundle: []byte(""), + }, + Status: garmoperatorv1beta1.GitHubEndpointStatus{ + Conditions: []metav1.Condition{ + { + Type: string(conditions.ReadyCondition), + Reason: string(conditions.SuccessfulReconcileReason), + Status: metav1.ConditionTrue, + Message: "", + LastTransitionTime: metav1.NewTime(time.Now()), + }, + }, + }, + }, + runtimeObjects: []runtime.Object{}, + expectGarmRequest: func(m *mock.MockEndpointClientMockRecorder) { + m.DeleteEndpoint( + endpoints.NewDeleteGithubEndpointParams(). + WithName("existing-github-endpoint"), + ).Return(nil) + }, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + schemeBuilder := runtime.SchemeBuilder{ + garmoperatorv1beta1.AddToScheme, + } + + err := schemeBuilder.AddToScheme(scheme.Scheme) + if err != nil { + t.Fatal(err) + } + + runtimeObjects := []runtime.Object{tt.object} + runtimeObjects = append(runtimeObjects, tt.runtimeObjects...) + client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(runtimeObjects...).WithStatusSubresource(&garmoperatorv1beta1.GitHubEndpoint{}).Build() + + // create a fake reconciler + reconciler := &GitHubEndpointReconciler{ + Client: client, + Recorder: record.NewFakeRecorder(3), + } + + githubEndpoint := tt.object.DeepCopyObject().(*garmoperatorv1beta1.GitHubEndpoint) + + mockGitHubEndpoint := mock.NewMockEndpointClient(mockCtrl) + tt.expectGarmRequest(mockGitHubEndpoint.EXPECT()) + + _, err = reconciler.reconcileDelete(context.Background(), mockGitHubEndpoint, githubEndpoint) + if (err != nil) != tt.wantErr { + t.Errorf("GitHubEndpointReconciler.reconcileDelete() error = %v, wantErr %v", err, tt.wantErr) + return + } + + // check for mandatory finalizer + if controllerutil.ContainsFinalizer(githubEndpoint, key.GitHubEndpointFinalizerName) { + t.Errorf("GitHubEndpointReconciler.Reconcile() finalizer still exist") + return + } + }) + } +} diff --git a/internal/controller/organization_controller.go b/internal/controller/organization_controller.go index 1eb4b2f2..aa4d37d4 100644 --- a/internal/controller/organization_controller.go +++ b/internal/controller/organization_controller.go @@ -12,14 +12,19 @@ import ( "github.com/cloudbase/garm/params" apierrors "k8s.io/apimachinery/pkg/api/errors" "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/builder" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" - garmoperatorv1alpha1 "github.com/mercedes-benz/garm-operator/api/v1alpha1" + garmoperatorv1beta1 "github.com/mercedes-benz/garm-operator/api/v1beta1" "github.com/mercedes-benz/garm-operator/pkg/annotations" garmClient "github.com/mercedes-benz/garm-operator/pkg/client" "github.com/mercedes-benz/garm-operator/pkg/client/key" @@ -43,7 +48,7 @@ type OrganizationReconciler struct { func (r *OrganizationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := log.FromContext(ctx) - organization := &garmoperatorv1alpha1.Organization{} + organization := &garmoperatorv1beta1.Organization{} err := r.Get(ctx, req.NamespacedName, organization) if err != nil { if apierrors.IsNotFound(err) { @@ -69,7 +74,7 @@ func (r *OrganizationReconciler) Reconcile(ctx context.Context, req ctrl.Request return r.reconcileNormal(ctx, organizationClient, organization) } -func (r *OrganizationReconciler) reconcileNormal(ctx context.Context, client garmClient.OrganizationClient, organization *garmoperatorv1alpha1.Organization) (ctrl.Result, error) { +func (r *OrganizationReconciler) reconcileNormal(ctx context.Context, client garmClient.OrganizationClient, organization *garmoperatorv1beta1.Organization) (ctrl.Result, error) { log := log.FromContext(ctx) log.WithValues("organization", organization.Name) @@ -83,6 +88,7 @@ func (r *OrganizationReconciler) reconcileNormal(ctx context.Context, client gar conditions.MarkFalse(organization, conditions.ReadyCondition, conditions.FetchingSecretRefFailedReason, err.Error()) conditions.MarkFalse(organization, conditions.SecretReference, conditions.FetchingSecretRefFailedReason, err.Error()) conditions.MarkUnknown(organization, conditions.PoolManager, conditions.UnknownReason, conditions.GarmServerNotReconciledYetMsg) + conditions.MarkUnknown(organization, conditions.CredentialsReference, conditions.UnknownReason, conditions.CredentialsNotReconciledYetMsg) if err := r.Status().Update(ctx, organization); err != nil { return ctrl.Result{}, err } @@ -90,6 +96,20 @@ func (r *OrganizationReconciler) reconcileNormal(ctx context.Context, client gar } conditions.MarkTrue(organization, conditions.SecretReference, conditions.FetchingSecretRefSuccessReason, "") + credentials, err := r.getCredentialsRef(ctx, organization) + if err != nil { + conditions.MarkFalse(organization, conditions.ReadyCondition, conditions.FetchingCredentialsRefFailedReason, err.Error()) + conditions.MarkFalse(organization, conditions.CredentialsReference, conditions.FetchingCredentialsRefFailedReason, err.Error()) + if conditions.Get(organization, conditions.PoolManager) == nil { + conditions.MarkUnknown(organization, conditions.PoolManager, conditions.UnknownReason, conditions.GarmServerNotReconciledYetMsg) + } + if err := r.Status().Update(ctx, organization); err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{}, err + } + conditions.MarkTrue(organization, conditions.CredentialsReference, conditions.FetchingCredentialsRefSuccessReason, "") + garmOrganization, err := r.getExistingGarmOrg(ctx, client, organization) if err != nil { event.Error(r.Recorder, organization, err.Error()) @@ -114,7 +134,11 @@ func (r *OrganizationReconciler) reconcileNormal(ctx context.Context, client gar } // update organization anytime - garmOrganization, err = r.updateOrganization(ctx, client, garmOrganization.ID, webhookSecret, organization.Spec.CredentialsName) + garmOrganization, err = r.updateOrganization(ctx, client, garmOrganization.ID, params.UpdateEntityParams{ + CredentialsName: credentials.Name, + WebhookSecret: webhookSecret, + PoolBalancerType: organization.Spec.PoolBalancerType, + }) if err != nil { event.Error(r.Recorder, organization, err.Error()) conditions.MarkFalse(organization, conditions.ReadyCondition, conditions.GarmAPIErrorReason, err.Error()) @@ -143,7 +167,7 @@ func (r *OrganizationReconciler) reconcileNormal(ctx context.Context, client gar return ctrl.Result{}, nil } -func (r *OrganizationReconciler) createOrganization(ctx context.Context, client garmClient.OrganizationClient, organization *garmoperatorv1alpha1.Organization, webhookSecret string) (params.Organization, error) { +func (r *OrganizationReconciler) createOrganization(ctx context.Context, client garmClient.OrganizationClient, organization *garmoperatorv1beta1.Organization, webhookSecret string) (params.Organization, error) { log := log.FromContext(ctx) log.WithValues("organization", organization.Name) @@ -153,9 +177,10 @@ func (r *OrganizationReconciler) createOrganization(ctx context.Context, client retValue, err := client.CreateOrganization( organizations.NewCreateOrgParams(). WithBody(params.CreateOrgParams{ - Name: organization.Name, - CredentialsName: organization.Spec.CredentialsName, - WebhookSecret: webhookSecret, // gh hook secret + Name: organization.Name, + CredentialsName: organization.GetCredentialsName(), + WebhookSecret: webhookSecret, // gh hook secret + PoolBalancerType: organization.Spec.PoolBalancerType, })) if err != nil { log.V(1).Info(fmt.Sprintf("client.CreateOrganization error: %s", err)) @@ -170,7 +195,7 @@ func (r *OrganizationReconciler) createOrganization(ctx context.Context, client return retValue.Payload, nil } -func (r *OrganizationReconciler) updateOrganization(ctx context.Context, client garmClient.OrganizationClient, statusID, webhookSecret, credentialsName string) (params.Organization, error) { +func (r *OrganizationReconciler) updateOrganization(ctx context.Context, client garmClient.OrganizationClient, statusID string, updateParams params.UpdateEntityParams) (params.Organization, error) { log := log.FromContext(ctx) log.V(1).Info("update credentials and webhook secret in garm organization") @@ -178,10 +203,7 @@ func (r *OrganizationReconciler) updateOrganization(ctx context.Context, client retValue, err := client.UpdateOrganization( organizations.NewUpdateOrgParams(). WithOrgID(statusID). - WithBody(params.UpdateEntityParams{ - CredentialsName: credentialsName, - WebhookSecret: webhookSecret, // gh hook secret - })) + WithBody(updateParams)) if err != nil { log.V(1).Info(fmt.Sprintf("client.UpdateOrganization error: %s", err)) return params.Organization{}, err @@ -190,7 +212,7 @@ func (r *OrganizationReconciler) updateOrganization(ctx context.Context, client return retValue.Payload, nil } -func (r *OrganizationReconciler) getExistingGarmOrg(ctx context.Context, client garmClient.OrganizationClient, organization *garmoperatorv1alpha1.Organization) (params.Organization, error) { +func (r *OrganizationReconciler) getExistingGarmOrg(ctx context.Context, client garmClient.OrganizationClient, organization *garmoperatorv1beta1.Organization) (params.Organization, error) { log := log.FromContext(ctx) log.WithValues("organization", organization.Name) @@ -211,7 +233,7 @@ func (r *OrganizationReconciler) getExistingGarmOrg(ctx context.Context, client return params.Organization{}, nil } -func (r *OrganizationReconciler) reconcileDelete(ctx context.Context, client garmClient.OrganizationClient, organization *garmoperatorv1alpha1.Organization) (ctrl.Result, error) { +func (r *OrganizationReconciler) reconcileDelete(ctx context.Context, client garmClient.OrganizationClient, organization *garmoperatorv1beta1.Organization) (ctrl.Result, error) { log := log.FromContext(ctx) log.WithValues("organization", organization.Name) @@ -250,18 +272,61 @@ func (r *OrganizationReconciler) reconcileDelete(ctx context.Context, client gar return ctrl.Result{}, nil } -func (r *OrganizationReconciler) ensureFinalizer(ctx context.Context, pool *garmoperatorv1alpha1.Organization) error { - if !controllerutil.ContainsFinalizer(pool, key.OrganizationFinalizerName) { - controllerutil.AddFinalizer(pool, key.OrganizationFinalizerName) - return r.Update(ctx, pool) +func (r *OrganizationReconciler) getCredentialsRef(ctx context.Context, org *garmoperatorv1beta1.Organization) (*garmoperatorv1beta1.GitHubCredential, error) { + creds := &garmoperatorv1beta1.GitHubCredential{} + err := r.Get(ctx, types.NamespacedName{ + Namespace: org.Namespace, + Name: org.Spec.CredentialsRef.Name, + }, creds) + if err != nil { + return creds, err + } + return creds, nil +} + +func (r *OrganizationReconciler) ensureFinalizer(ctx context.Context, org *garmoperatorv1beta1.Organization) error { + if !controllerutil.ContainsFinalizer(org, key.OrganizationFinalizerName) { + controllerutil.AddFinalizer(org, key.OrganizationFinalizerName) + return r.Update(ctx, org) } return nil } +func (r *OrganizationReconciler) findOrgsForCredentials(ctx context.Context, obj client.Object) []reconcile.Request { + credentials, ok := obj.(*garmoperatorv1beta1.GitHubCredential) + if !ok { + return nil + } + + var orgs garmoperatorv1beta1.OrganizationList + if err := r.List(ctx, &orgs); err != nil { + return nil + } + + var requests []reconcile.Request + for _, org := range orgs.Items { + if org.GetCredentialsName() == credentials.Name { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: org.Namespace, + Name: org.Name, + }, + }) + } + } + + return requests +} + // SetupWithManager sets up the controller with the Manager. func (r *OrganizationReconciler) SetupWithManager(mgr ctrl.Manager, options controller.Options) error { return ctrl.NewControllerManagedBy(mgr). - For(&garmoperatorv1alpha1.Organization{}). + For(&garmoperatorv1beta1.Organization{}). + Watches( + &garmoperatorv1beta1.GitHubCredential{}, + handler.EnqueueRequestsFromMapFunc(r.findOrgsForCredentials), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ). WithOptions(options). Complete(r) } diff --git a/internal/controller/organization_controller_test.go b/internal/controller/organization_controller_test.go index 713777fa..09a8ea4f 100644 --- a/internal/controller/organization_controller_test.go +++ b/internal/controller/organization_controller_test.go @@ -19,7 +19,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - garmoperatorv1alpha1 "github.com/mercedes-benz/garm-operator/api/v1alpha1" + garmoperatorv1beta1 "github.com/mercedes-benz/garm-operator/api/v1beta1" "github.com/mercedes-benz/garm-operator/pkg/client/key" "github.com/mercedes-benz/garm-operator/pkg/client/mock" "github.com/mercedes-benz/garm-operator/pkg/conditions" @@ -35,11 +35,11 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { runtimeObjects []runtime.Object expectGarmRequest func(m *mock.MockOrganizationClientMockRecorder) wantErr bool - expectedObject *garmoperatorv1alpha1.Organization + expectedObject *garmoperatorv1beta1.Organization }{ { name: "organization exist - update", - object: &garmoperatorv1alpha1.Organization{ + object: &garmoperatorv1beta1.Organization{ ObjectMeta: metav1.ObjectMeta{ Name: "existing-organization", Namespace: "default", @@ -47,14 +47,18 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { key.OrganizationFinalizerName, }, }, - Spec: garmoperatorv1alpha1.OrganizationSpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.OrganizationSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.OrganizationStatus{ + Status: garmoperatorv1beta1.OrganizationStatus{ ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", }, }, @@ -68,8 +72,23 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { "webhookSecret": []byte("foobar"), }, }, + &garmoperatorv1beta1.GitHubCredential{ + ObjectMeta: metav1.ObjectMeta{ + Name: "github-creds", + Namespace: "default", + }, + Spec: garmoperatorv1beta1.GitHubCredentialSpec{ + Description: "github-creds", + EndpointRef: corev1.TypedLocalObjectReference{}, + AuthType: "pat", + SecretRef: garmoperatorv1beta1.SecretRef{ + Name: "github-secret", + Key: "token", + }, + }, + }, }, - expectedObject: &garmoperatorv1alpha1.Organization{ + expectedObject: &garmoperatorv1beta1.Organization{ ObjectMeta: metav1.ObjectMeta{ Name: "existing-organization", Namespace: "default", @@ -77,14 +96,18 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { key.OrganizationFinalizerName, }, }, - Spec: garmoperatorv1alpha1.OrganizationSpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.OrganizationSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.OrganizationStatus{ + Status: garmoperatorv1beta1.OrganizationStatus{ ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", Conditions: []metav1.Condition{ { @@ -94,6 +117,13 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { LastTransitionTime: metav1.NewTime(time.Now()), Message: "Pool Manager is not running", }, + { + Type: string(conditions.CredentialsReference), + Reason: string(conditions.FetchingCredentialsRefSuccessReason), + Status: metav1.ConditionTrue, + Message: "", + LastTransitionTime: metav1.NewTime(time.Now()), + }, { Type: string(conditions.PoolManager), Reason: string(conditions.PoolManagerFailureReason), @@ -123,13 +153,13 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { m.UpdateOrganization(organizations.NewUpdateOrgParams(). WithOrgID("e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e"). WithBody(params.UpdateEntityParams{ - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", })).Return(&organizations.UpdateOrgOK{ Payload: params.Organization{ ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", Name: "existing-organization", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", }, }, nil) @@ -137,7 +167,7 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { }, { name: "organization exist but spec has changed - update", - object: &garmoperatorv1alpha1.Organization{ + object: &garmoperatorv1beta1.Organization{ ObjectMeta: metav1.ObjectMeta{ Name: "existing-organization", Namespace: "default", @@ -145,14 +175,18 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { key.OrganizationFinalizerName, }, }, - Spec: garmoperatorv1alpha1.OrganizationSpec{ - CredentialsName: "has-changed", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.OrganizationSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "has-changed", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.OrganizationStatus{ + Status: garmoperatorv1beta1.OrganizationStatus{ ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", }, }, @@ -166,8 +200,23 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { "webhookSecret": []byte("has-changed"), }, }, + &garmoperatorv1beta1.GitHubCredential{ + ObjectMeta: metav1.ObjectMeta{ + Name: "has-changed", + Namespace: "default", + }, + Spec: garmoperatorv1beta1.GitHubCredentialSpec{ + Description: "github-creds", + EndpointRef: corev1.TypedLocalObjectReference{}, + AuthType: "pat", + SecretRef: garmoperatorv1beta1.SecretRef{ + Name: "github-secret", + Key: "token", + }, + }, + }, }, - expectedObject: &garmoperatorv1alpha1.Organization{ + expectedObject: &garmoperatorv1beta1.Organization{ ObjectMeta: metav1.ObjectMeta{ Name: "existing-organization", Namespace: "default", @@ -175,14 +224,18 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { key.OrganizationFinalizerName, }, }, - Spec: garmoperatorv1alpha1.OrganizationSpec{ - CredentialsName: "has-changed", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.OrganizationSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "has-changed", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.OrganizationStatus{ + Status: garmoperatorv1beta1.OrganizationStatus{ ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", Conditions: []metav1.Condition{ { @@ -192,6 +245,13 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { LastTransitionTime: metav1.NewTime(time.Now()), Message: "Pool Manager is not running", }, + { + Type: string(conditions.CredentialsReference), + Reason: string(conditions.FetchingCredentialsRefSuccessReason), + Status: metav1.ConditionTrue, + Message: "", + LastTransitionTime: metav1.NewTime(time.Now()), + }, { Type: string(conditions.PoolManager), Reason: string(conditions.PoolManagerFailureReason), @@ -235,7 +295,7 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { }, { name: "organization exist but pool status has changed - update", - object: &garmoperatorv1alpha1.Organization{ + object: &garmoperatorv1beta1.Organization{ ObjectMeta: metav1.ObjectMeta{ Name: "existing-organization", Namespace: "default", @@ -243,14 +303,18 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { key.OrganizationFinalizerName, }, }, - Spec: garmoperatorv1alpha1.OrganizationSpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.OrganizationSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.OrganizationStatus{ + Status: garmoperatorv1beta1.OrganizationStatus{ ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", }, }, @@ -264,8 +328,23 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { "webhookSecret": []byte("foobar"), }, }, + &garmoperatorv1beta1.GitHubCredential{ + ObjectMeta: metav1.ObjectMeta{ + Name: "github-creds", + Namespace: "default", + }, + Spec: garmoperatorv1beta1.GitHubCredentialSpec{ + Description: "github-creds", + EndpointRef: corev1.TypedLocalObjectReference{}, + AuthType: "pat", + SecretRef: garmoperatorv1beta1.SecretRef{ + Name: "github-secret", + Key: "token", + }, + }, + }, }, - expectedObject: &garmoperatorv1alpha1.Organization{ + expectedObject: &garmoperatorv1beta1.Organization{ ObjectMeta: metav1.ObjectMeta{ Name: "existing-organization", Namespace: "default", @@ -273,14 +352,18 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { key.OrganizationFinalizerName, }, }, - Spec: garmoperatorv1alpha1.OrganizationSpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.OrganizationSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.OrganizationStatus{ + Status: garmoperatorv1beta1.OrganizationStatus{ ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", Conditions: []metav1.Condition{ { @@ -290,6 +373,13 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { LastTransitionTime: metav1.NewTime(time.Now()), Message: "Pool Manager is not running", }, + { + Type: string(conditions.CredentialsReference), + Reason: string(conditions.FetchingCredentialsRefSuccessReason), + Status: metav1.ConditionTrue, + Message: "", + LastTransitionTime: metav1.NewTime(time.Now()), + }, { Type: string(conditions.PoolManager), Reason: string(conditions.PoolManagerFailureReason), @@ -312,20 +402,20 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { { ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", Name: "existing-organization", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", }, }}, nil) m.UpdateOrganization(organizations.NewUpdateOrgParams(). WithOrgID("e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e"). WithBody(params.UpdateEntityParams{ - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", })).Return(&organizations.UpdateOrgOK{ Payload: params.Organization{ ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", Name: "existing-organization", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", PoolManagerStatus: params.PoolManagerStatus{ IsRunning: false, @@ -337,14 +427,18 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { }, { name: "organization does not exist - create and update", - object: &garmoperatorv1alpha1.Organization{ + object: &garmoperatorv1beta1.Organization{ ObjectMeta: metav1.ObjectMeta{ Name: "new-organization", Namespace: "default", }, - Spec: garmoperatorv1alpha1.OrganizationSpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.OrganizationSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, @@ -360,8 +454,23 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { "webhookSecret": []byte("foobar"), }, }, + &garmoperatorv1beta1.GitHubCredential{ + ObjectMeta: metav1.ObjectMeta{ + Name: "github-creds", + Namespace: "default", + }, + Spec: garmoperatorv1beta1.GitHubCredentialSpec{ + Description: "github-creds", + EndpointRef: corev1.TypedLocalObjectReference{}, + AuthType: "pat", + SecretRef: garmoperatorv1beta1.SecretRef{ + Name: "github-secret", + Key: "token", + }, + }, + }, }, - expectedObject: &garmoperatorv1alpha1.Organization{ + expectedObject: &garmoperatorv1beta1.Organization{ ObjectMeta: metav1.ObjectMeta{ Name: "new-organization", Namespace: "default", @@ -369,14 +478,18 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { key.OrganizationFinalizerName, }, }, - Spec: garmoperatorv1alpha1.OrganizationSpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.OrganizationSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.OrganizationStatus{ + Status: garmoperatorv1beta1.OrganizationStatus{ ID: "9e0da3cb-130b-428d-aa8a-e314d955060e", Conditions: []metav1.Condition{ { @@ -386,6 +499,13 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { LastTransitionTime: metav1.NewTime(time.Now()), Message: "Pool Manager is not running", }, + { + Type: string(conditions.CredentialsReference), + Reason: string(conditions.FetchingCredentialsRefSuccessReason), + Status: metav1.ConditionTrue, + Message: "", + LastTransitionTime: metav1.NewTime(time.Now()), + }, { Type: string(conditions.PoolManager), Reason: string(conditions.PoolManagerFailureReason), @@ -415,12 +535,12 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { m.CreateOrganization(organizations.NewCreateOrgParams().WithBody( params.CreateOrgParams{ Name: "new-organization", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", })).Return(&organizations.CreateOrgOK{ Payload: params.Organization{ Name: "new-organization", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", ID: "9e0da3cb-130b-428d-aa8a-e314d955060e", }, @@ -428,13 +548,13 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { m.UpdateOrganization(organizations.NewUpdateOrgParams(). WithOrgID("9e0da3cb-130b-428d-aa8a-e314d955060e"). WithBody(params.UpdateEntityParams{ - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", })).Return(&organizations.UpdateOrgOK{ Payload: params.Organization{ ID: "9e0da3cb-130b-428d-aa8a-e314d955060e", Name: "new-organization", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", }, }, nil) @@ -442,14 +562,18 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { }, { name: "organization already exist in garm - update", - object: &garmoperatorv1alpha1.Organization{ + object: &garmoperatorv1beta1.Organization{ ObjectMeta: metav1.ObjectMeta{ Name: "new-organization", Namespace: "default", }, - Spec: garmoperatorv1alpha1.OrganizationSpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.OrganizationSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, @@ -465,8 +589,23 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { "webhookSecret": []byte("foobar"), }, }, + &garmoperatorv1beta1.GitHubCredential{ + ObjectMeta: metav1.ObjectMeta{ + Name: "github-creds", + Namespace: "default", + }, + Spec: garmoperatorv1beta1.GitHubCredentialSpec{ + Description: "github-creds", + EndpointRef: corev1.TypedLocalObjectReference{}, + AuthType: "pat", + SecretRef: garmoperatorv1beta1.SecretRef{ + Name: "github-secret", + Key: "token", + }, + }, + }, }, - expectedObject: &garmoperatorv1alpha1.Organization{ + expectedObject: &garmoperatorv1beta1.Organization{ ObjectMeta: metav1.ObjectMeta{ Name: "new-organization", Namespace: "default", @@ -474,14 +613,18 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { key.OrganizationFinalizerName, }, }, - Spec: garmoperatorv1alpha1.OrganizationSpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.OrganizationSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.OrganizationStatus{ + Status: garmoperatorv1beta1.OrganizationStatus{ ID: "e1dbf9a6-a9f6-4594-a5ac-12345", Conditions: []metav1.Condition{ { @@ -491,6 +634,13 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { LastTransitionTime: metav1.NewTime(time.Now()), Message: "Pool Manager is not running", }, + { + Type: string(conditions.CredentialsReference), + Reason: string(conditions.FetchingCredentialsRefSuccessReason), + Status: metav1.ConditionTrue, + Message: "", + LastTransitionTime: metav1.NewTime(time.Now()), + }, { Type: string(conditions.PoolManager), Reason: string(conditions.PoolManagerFailureReason), @@ -519,13 +669,13 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { m.UpdateOrganization(organizations.NewUpdateOrgParams(). WithOrgID("e1dbf9a6-a9f6-4594-a5ac-12345"). WithBody(params.UpdateEntityParams{ - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", })).Return(&organizations.UpdateOrgOK{ Payload: params.Organization{ ID: "e1dbf9a6-a9f6-4594-a5ac-12345", Name: "new-organization", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", }, }, nil) @@ -533,7 +683,7 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { }, { name: "organization does not exist in garm - create and update", - object: &garmoperatorv1alpha1.Organization{ + object: &garmoperatorv1beta1.Organization{ ObjectMeta: metav1.ObjectMeta{ Name: "existing-organization", Namespace: "default", @@ -541,14 +691,18 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { key.OrganizationFinalizerName, }, }, - Spec: garmoperatorv1alpha1.OrganizationSpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.OrganizationSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.OrganizationStatus{ + Status: garmoperatorv1beta1.OrganizationStatus{ ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", }, }, @@ -562,8 +716,23 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { "webhookSecret": []byte("foobar"), }, }, + &garmoperatorv1beta1.GitHubCredential{ + ObjectMeta: metav1.ObjectMeta{ + Name: "github-creds", + Namespace: "default", + }, + Spec: garmoperatorv1beta1.GitHubCredentialSpec{ + Description: "github-creds", + EndpointRef: corev1.TypedLocalObjectReference{}, + AuthType: "pat", + SecretRef: garmoperatorv1beta1.SecretRef{ + Name: "github-secret", + Key: "token", + }, + }, + }, }, - expectedObject: &garmoperatorv1alpha1.Organization{ + expectedObject: &garmoperatorv1beta1.Organization{ ObjectMeta: metav1.ObjectMeta{ Name: "existing-organization", Namespace: "default", @@ -571,14 +740,18 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { key.OrganizationFinalizerName, }, }, - Spec: garmoperatorv1alpha1.OrganizationSpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.OrganizationSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.OrganizationStatus{ + Status: garmoperatorv1beta1.OrganizationStatus{ ID: "9e0da3cb-130b-428d-aa8a-e314d955060e", Conditions: []metav1.Condition{ { @@ -588,6 +761,13 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { LastTransitionTime: metav1.NewTime(time.Now()), Message: "Pool Manager is not running", }, + { + Type: string(conditions.CredentialsReference), + Reason: string(conditions.FetchingCredentialsRefSuccessReason), + Status: metav1.ConditionTrue, + Message: "", + LastTransitionTime: metav1.NewTime(time.Now()), + }, { Type: string(conditions.PoolManager), Reason: string(conditions.PoolManagerFailureReason), @@ -612,12 +792,12 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { m.CreateOrganization(organizations.NewCreateOrgParams().WithBody( params.CreateOrgParams{ Name: "existing-organization", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", })).Return(&organizations.CreateOrgOK{ Payload: params.Organization{ Name: "existing-organization", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", ID: "9e0da3cb-130b-428d-aa8a-e314d955060e", }, @@ -625,13 +805,13 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { m.UpdateOrganization(organizations.NewUpdateOrgParams(). WithOrgID("9e0da3cb-130b-428d-aa8a-e314d955060e"). WithBody(params.UpdateEntityParams{ - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", })).Return(&organizations.UpdateOrgOK{ Payload: params.Organization{ ID: "9e0da3cb-130b-428d-aa8a-e314d955060e", Name: "existing-organization", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", }, }, nil) @@ -639,7 +819,7 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { }, { name: "secret ref not found condition", - object: &garmoperatorv1alpha1.Organization{ + object: &garmoperatorv1beta1.Organization{ ObjectMeta: metav1.ObjectMeta{ Name: "new-organization", Namespace: "default", @@ -647,17 +827,21 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { key.OrganizationFinalizerName, }, }, - Spec: garmoperatorv1alpha1.OrganizationSpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.OrganizationSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.OrganizationStatus{}, + Status: garmoperatorv1beta1.OrganizationStatus{}, }, runtimeObjects: []runtime.Object{}, - expectedObject: &garmoperatorv1alpha1.Organization{ + expectedObject: &garmoperatorv1beta1.Organization{ ObjectMeta: metav1.ObjectMeta{ Name: "new-organization", Namespace: "default", @@ -665,14 +849,18 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { key.OrganizationFinalizerName, }, }, - Spec: garmoperatorv1alpha1.OrganizationSpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.OrganizationSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.OrganizationStatus{ + Status: garmoperatorv1beta1.OrganizationStatus{ Conditions: []metav1.Condition{ { Type: string(conditions.ReadyCondition), @@ -681,6 +869,13 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { Message: "secrets \"my-webhook-secret\" not found", LastTransitionTime: metav1.NewTime(time.Now()), }, + { + Type: string(conditions.CredentialsReference), + Reason: string(conditions.UnknownReason), + Status: metav1.ConditionUnknown, + Message: "credentials not reconciled yet", + LastTransitionTime: metav1.NewTime(time.Now()), + }, { Type: string(conditions.PoolManager), Reason: string(conditions.UnknownReason), @@ -705,7 +900,7 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { schemeBuilder := runtime.SchemeBuilder{ - garmoperatorv1alpha1.AddToScheme, + garmoperatorv1beta1.AddToScheme, } err := schemeBuilder.AddToScheme(scheme.Scheme) @@ -714,7 +909,7 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { } runtimeObjects := []runtime.Object{tt.object} runtimeObjects = append(runtimeObjects, tt.runtimeObjects...) - client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(runtimeObjects...).WithStatusSubresource(&garmoperatorv1alpha1.Organization{}).Build() + client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(runtimeObjects...).WithStatusSubresource(&garmoperatorv1beta1.Organization{}).Build() // create a fake reconciler reconciler := &OrganizationReconciler{ @@ -722,7 +917,7 @@ func TestOrganizationReconciler_reconcileNormal(t *testing.T) { Recorder: record.NewFakeRecorder(3), } - organization := tt.object.DeepCopyObject().(*garmoperatorv1alpha1.Organization) + organization := tt.object.DeepCopyObject().(*garmoperatorv1beta1.Organization) mockOrganization := mock.NewMockOrganizationClient(mockCtrl) tt.expectGarmRequest(mockOrganization.EXPECT()) @@ -760,11 +955,11 @@ func TestOrganizationReconciler_reconcileDelete(t *testing.T) { runtimeObjects []runtime.Object expectGarmRequest func(m *mock.MockOrganizationClientMockRecorder) wantErr bool - expectedObject *garmoperatorv1alpha1.Organization + expectedObject *garmoperatorv1beta1.Organization }{ { name: "delete organization", - object: &garmoperatorv1alpha1.Organization{ + object: &garmoperatorv1beta1.Organization{ ObjectMeta: metav1.ObjectMeta{ Name: "delete-organization", Namespace: "default", @@ -772,14 +967,18 @@ func TestOrganizationReconciler_reconcileDelete(t *testing.T) { key.OrganizationFinalizerName, }, }, - Spec: garmoperatorv1alpha1.OrganizationSpec{ - CredentialsName: "totally-insecure", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.OrganizationSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.OrganizationStatus{ + Status: garmoperatorv1beta1.OrganizationStatus{ ID: "e1dbf9a6-a9f6-4594-a5ac-12345", }, }, @@ -793,6 +992,21 @@ func TestOrganizationReconciler_reconcileDelete(t *testing.T) { "webhookSecret": []byte("foobar"), }, }, + &garmoperatorv1beta1.GitHubCredential{ + ObjectMeta: metav1.ObjectMeta{ + Name: "github-creds", + Namespace: "default", + }, + Spec: garmoperatorv1beta1.GitHubCredentialSpec{ + Description: "github-creds", + EndpointRef: corev1.TypedLocalObjectReference{}, + AuthType: "pat", + SecretRef: garmoperatorv1beta1.SecretRef{ + Name: "github-secret", + Key: "token", + }, + }, + }, }, expectGarmRequest: func(m *mock.MockOrganizationClientMockRecorder) { m.DeleteOrganization( @@ -805,7 +1019,7 @@ func TestOrganizationReconciler_reconcileDelete(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { schemeBuilder := runtime.SchemeBuilder{ - garmoperatorv1alpha1.AddToScheme, + garmoperatorv1beta1.AddToScheme, } err := schemeBuilder.AddToScheme(scheme.Scheme) @@ -814,7 +1028,7 @@ func TestOrganizationReconciler_reconcileDelete(t *testing.T) { } runtimeObjects := []runtime.Object{tt.object} runtimeObjects = append(runtimeObjects, tt.runtimeObjects...) - client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(runtimeObjects...).WithStatusSubresource(&garmoperatorv1alpha1.Organization{}).Build() + client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(runtimeObjects...).WithStatusSubresource(&garmoperatorv1beta1.Organization{}).Build() // create a fake reconciler reconciler := &OrganizationReconciler{ @@ -822,7 +1036,7 @@ func TestOrganizationReconciler_reconcileDelete(t *testing.T) { Recorder: record.NewFakeRecorder(3), } - organization := tt.object.DeepCopyObject().(*garmoperatorv1alpha1.Organization) + organization := tt.object.DeepCopyObject().(*garmoperatorv1beta1.Organization) mockOrganization := mock.NewMockOrganizationClient(mockCtrl) tt.expectGarmRequest(mockOrganization.EXPECT()) diff --git a/internal/controller/pool_controller.go b/internal/controller/pool_controller.go index d398d4b8..3ccc8df2 100644 --- a/internal/controller/pool_controller.go +++ b/internal/controller/pool_controller.go @@ -27,7 +27,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" - garmoperatorv1alpha1 "github.com/mercedes-benz/garm-operator/api/v1alpha1" + garmoperatorv1beta1 "github.com/mercedes-benz/garm-operator/api/v1beta1" "github.com/mercedes-benz/garm-operator/pkg/annotations" garmClient "github.com/mercedes-benz/garm-operator/pkg/client" "github.com/mercedes-benz/garm-operator/pkg/client/key" @@ -58,7 +58,7 @@ const ( func (r *PoolReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := log.FromContext(ctx) - pool := &garmoperatorv1alpha1.Pool{} + pool := &garmoperatorv1beta1.Pool{} if err := r.Get(ctx, req.NamespacedName, pool); err != nil { if apierrors.IsNotFound(err) { return ctrl.Result{}, nil @@ -85,7 +85,7 @@ func (r *PoolReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl. return r.reconcileNormal(ctx, poolClient, pool, instanceClient) } -func (r *PoolReconciler) reconcileNormal(ctx context.Context, poolClient garmClient.PoolClient, pool *garmoperatorv1alpha1.Pool, instanceClient garmClient.InstanceClient) (ctrl.Result, error) { +func (r *PoolReconciler) reconcileNormal(ctx context.Context, poolClient garmClient.PoolClient, pool *garmoperatorv1beta1.Pool, instanceClient garmClient.InstanceClient) (ctrl.Result, error) { gitHubScopeRef, err := r.fetchGitHubScopeCRD(ctx, pool) if err != nil { conditions.MarkFalse(pool, conditions.ScopeReference, conditions.FetchingScopeRefFailedReason, err.Error()) @@ -112,7 +112,7 @@ func (r *PoolReconciler) reconcileNormal(ctx context.Context, poolClient garmCli return r.reconcileCreate(ctx, poolClient, pool, gitHubScopeRef) } -func (r *PoolReconciler) reconcileCreate(ctx context.Context, garmClient garmClient.PoolClient, pool *garmoperatorv1alpha1.Pool, gitHubScopeRef garmoperatorv1alpha1.GitHubScope) (ctrl.Result, error) { +func (r *PoolReconciler) reconcileCreate(ctx context.Context, garmClient garmClient.PoolClient, pool *garmoperatorv1beta1.Pool, gitHubScopeRef garmoperatorv1beta1.GitHubScope) (ctrl.Result, error) { log := log.FromContext(ctx) log.Info("Pool doesn't exist on garm side. Creating new pool in garm") @@ -124,33 +124,7 @@ func (r *PoolReconciler) reconcileCreate(ctx context.Context, garmClient garmCli } conditions.MarkTrue(pool, conditions.ImageReference, conditions.FetchingImageRefSuccessReason, "Successfully fetched Image CR Ref") - // check for duplicate pools - duplicate, duplicateName, err := pool.CheckDuplicate(ctx, r.Client, image) - if err != nil { - return r.handleUpdateError(ctx, pool, err, conditions.ReconcileErrorReason) - } - - if duplicate { - err := fmt.Errorf("pool with same image, flavor, provider and github scope already exists: %s", duplicateName) - return r.handleUpdateError(ctx, pool, err, conditions.DuplicatePoolReason) - } - - // check if there is already a pool with the same spec on garm side - matchingGarmPool, err := poolUtil.GetGarmPoolBySpecs(ctx, garmClient, pool, image, gitHubScopeRef) - if err != nil { - return r.handleUpdateError(ctx, pool, err, conditions.ReconcileErrorReason) - } - - if matchingGarmPool != nil { - log.Info("Found garm pool with matching specs, syncing IDs", "garmID", matchingGarmPool.ID) - event.Creating(r.Recorder, pool, fmt.Sprintf("found garm pool with matching specs, syncing IDs: %s", matchingGarmPool.ID)) - - pool.Status.ID = matchingGarmPool.ID - pool.Status.LongRunningIdleRunners = matchingGarmPool.MinIdleRunners - return r.handleSuccessfulUpdate(ctx, pool) - } - - // create new pool in garm + // always create new pool in garm garmPool, err := poolUtil.CreatePool(ctx, garmClient, pool, image, gitHubScopeRef) if err != nil { return r.handleUpdateError(ctx, pool, fmt.Errorf("failed creating pool %s: %s", pool.Name, err.Error()), conditions.ReconcileErrorReason) @@ -164,7 +138,7 @@ func (r *PoolReconciler) reconcileCreate(ctx context.Context, garmClient garmCli return r.handleSuccessfulUpdate(ctx, pool) } -func (r *PoolReconciler) reconcileUpdate(ctx context.Context, garmClient garmClient.PoolClient, pool *garmoperatorv1alpha1.Pool, instanceClient garmClient.InstanceClient) (ctrl.Result, error) { +func (r *PoolReconciler) reconcileUpdate(ctx context.Context, garmClient garmClient.PoolClient, pool *garmoperatorv1beta1.Pool, instanceClient garmClient.InstanceClient) (ctrl.Result, error) { log := log.FromContext(ctx). WithName("reconcileUpdate") log.Info("pool on garm side found", "id", pool.Status.ID, "name", pool.Name) @@ -239,7 +213,7 @@ func (r *PoolReconciler) reconcileUpdate(ctx context.Context, garmClient garmCli return r.handleSuccessfulUpdate(ctx, pool) } -func (r *PoolReconciler) reconcileDelete(ctx context.Context, garmClient garmClient.PoolClient, pool *garmoperatorv1alpha1.Pool, instanceClient garmClient.InstanceClient) (ctrl.Result, error) { +func (r *PoolReconciler) reconcileDelete(ctx context.Context, garmClient garmClient.PoolClient, pool *garmoperatorv1beta1.Pool, instanceClient garmClient.InstanceClient) (ctrl.Result, error) { // pool does not exist in garm database yet as ID in Status is empty, so we can safely delete it log := log.FromContext(ctx) log.Info("Deleting Pool", "pool", pool.Name) @@ -311,7 +285,7 @@ func (r *PoolReconciler) reconcileDelete(ctx context.Context, garmClient garmCli return ctrl.Result{}, nil } -func (r *PoolReconciler) updatePoolCRStatus(ctx context.Context, pool *garmoperatorv1alpha1.Pool) error { +func (r *PoolReconciler) updatePoolCRStatus(ctx context.Context, pool *garmoperatorv1beta1.Pool) error { log := log.FromContext(ctx) if err := r.Status().Update(ctx, pool); err != nil { log.Error(err, "unable to update Pool status") @@ -320,7 +294,7 @@ func (r *PoolReconciler) updatePoolCRStatus(ctx context.Context, pool *garmopera return nil } -func (r *PoolReconciler) handleUpdateError(ctx context.Context, pool *garmoperatorv1alpha1.Pool, err error, conditionReason conditions.ConditionReason) (ctrl.Result, error) { +func (r *PoolReconciler) handleUpdateError(ctx context.Context, pool *garmoperatorv1beta1.Pool, err error, conditionReason conditions.ConditionReason) (ctrl.Result, error) { log := log.FromContext(ctx) log.Error(err, "error") @@ -338,7 +312,7 @@ func (r *PoolReconciler) handleUpdateError(ctx context.Context, pool *garmoperat return ctrl.Result{}, err } -func (r *PoolReconciler) handleSuccessfulUpdate(ctx context.Context, pool *garmoperatorv1alpha1.Pool) (ctrl.Result, error) { +func (r *PoolReconciler) handleSuccessfulUpdate(ctx context.Context, pool *garmoperatorv1beta1.Pool) (ctrl.Result, error) { conditions.MarkTrue(pool, conditions.ReadyCondition, conditions.SuccessfulReconcileReason, "") if err := r.updatePoolCRStatus(ctx, pool); err != nil { @@ -348,7 +322,7 @@ func (r *PoolReconciler) handleSuccessfulUpdate(ctx context.Context, pool *garmo return ctrl.Result{}, nil } -func (r *PoolReconciler) ensureFinalizer(ctx context.Context, pool *garmoperatorv1alpha1.Pool) error { +func (r *PoolReconciler) ensureFinalizer(ctx context.Context, pool *garmoperatorv1beta1.Pool) error { if !controllerutil.ContainsFinalizer(pool, key.PoolFinalizerName) { controllerutil.AddFinalizer(pool, key.PoolFinalizerName) return r.Update(ctx, pool) @@ -356,7 +330,7 @@ func (r *PoolReconciler) ensureFinalizer(ctx context.Context, pool *garmoperator return nil } -func (r *PoolReconciler) comparePoolSpecs(ctx context.Context, pool *garmoperatorv1alpha1.Pool, imageTag string, poolClient garmClient.PoolClient) (bool, []params.Instance, error) { +func (r *PoolReconciler) comparePoolSpecs(ctx context.Context, pool *garmoperatorv1beta1.Pool, imageTag string, poolClient garmClient.PoolClient) (bool, []params.Instance, error) { log := log.FromContext(ctx). WithName("comparePoolSpecs") @@ -407,13 +381,13 @@ func (r *PoolReconciler) comparePoolSpecs(ctx context.Context, pool *garmoperato } switch gitHubScopeRef.GetKind() { - case string(garmoperatorv1alpha1.EnterpriseScope): + case string(garmoperatorv1beta1.EnterpriseScope): tmpGarmPool.EnterpriseID = gitHubScopeRef.GetID() tmpGarmPool.EnterpriseName = gitHubScopeRef.GetName() - case string(garmoperatorv1alpha1.OrganizationScope): + case string(garmoperatorv1beta1.OrganizationScope): tmpGarmPool.OrgID = gitHubScopeRef.GetID() tmpGarmPool.OrgName = gitHubScopeRef.GetName() - case string(garmoperatorv1alpha1.RepositoryScope): + case string(garmoperatorv1beta1.RepositoryScope): tmpGarmPool.RepoID = gitHubScopeRef.GetID() tmpGarmPool.RepoName = gitHubScopeRef.GetName() } @@ -427,7 +401,7 @@ func (r *PoolReconciler) comparePoolSpecs(ctx context.Context, pool *garmoperato return reflect.DeepEqual(tmpGarmPool, garmPool.Payload), idleInstances, nil } -func (r *PoolReconciler) fetchGitHubScopeCRD(ctx context.Context, pool *garmoperatorv1alpha1.Pool) (garmoperatorv1alpha1.GitHubScope, error) { +func (r *PoolReconciler) fetchGitHubScopeCRD(ctx context.Context, pool *garmoperatorv1beta1.Pool) (garmoperatorv1beta1.GitHubScope, error) { gitHubScopeNamespacedName := types.NamespacedName{ Namespace: pool.Namespace, Name: pool.Spec.GitHubScopeRef.Name, @@ -436,20 +410,20 @@ func (r *PoolReconciler) fetchGitHubScopeCRD(ctx context.Context, pool *garmoper var gitHubScope client.Object switch pool.Spec.GitHubScopeRef.Kind { - case string(garmoperatorv1alpha1.EnterpriseScope): - gitHubScope = &garmoperatorv1alpha1.Enterprise{} + case string(garmoperatorv1beta1.EnterpriseScope): + gitHubScope = &garmoperatorv1beta1.Enterprise{} if err := r.Get(ctx, gitHubScopeNamespacedName, gitHubScope); err != nil { return nil, err } - case string(garmoperatorv1alpha1.OrganizationScope): - gitHubScope = &garmoperatorv1alpha1.Organization{} + case string(garmoperatorv1beta1.OrganizationScope): + gitHubScope = &garmoperatorv1beta1.Organization{} if err := r.Get(ctx, gitHubScopeNamespacedName, gitHubScope); err != nil { return nil, err } - case string(garmoperatorv1alpha1.RepositoryScope): - gitHubScope = &garmoperatorv1alpha1.Repository{} + case string(garmoperatorv1beta1.RepositoryScope): + gitHubScope = &garmoperatorv1beta1.Repository{} if err := r.Get(ctx, gitHubScopeNamespacedName, gitHubScope); err != nil { return nil, err } @@ -458,16 +432,16 @@ func (r *PoolReconciler) fetchGitHubScopeCRD(ctx context.Context, pool *garmoper return nil, fmt.Errorf("unsupported GitHubScopeRef kind: %s", pool.Spec.GitHubScopeRef.Kind) } - return gitHubScope.(garmoperatorv1alpha1.GitHubScope), nil + return gitHubScope.(garmoperatorv1beta1.GitHubScope), nil } func (r *PoolReconciler) findPoolsForImage(ctx context.Context, obj client.Object) []reconcile.Request { - image, ok := obj.(*garmoperatorv1alpha1.Image) + image, ok := obj.(*garmoperatorv1beta1.Image) if !ok { return nil } - var pools garmoperatorv1alpha1.PoolList + var pools garmoperatorv1beta1.PoolList if err := r.List(ctx, &pools); err != nil { return nil } @@ -490,8 +464,8 @@ func (r *PoolReconciler) findPoolsForImage(ctx context.Context, obj client.Objec // SetupWithManager sets up the controller with the Manager. func (r *PoolReconciler) SetupWithManager(mgr ctrl.Manager, options controller.Options) error { // setup index for image - if err := mgr.GetFieldIndexer().IndexField(context.Background(), &garmoperatorv1alpha1.Pool{}, imageField, func(rawObj client.Object) []string { - pool := rawObj.(*garmoperatorv1alpha1.Pool) + if err := mgr.GetFieldIndexer().IndexField(context.Background(), &garmoperatorv1beta1.Pool{}, imageField, func(rawObj client.Object) []string { + pool := rawObj.(*garmoperatorv1beta1.Pool) if pool.Spec.ImageName == "" { return nil } @@ -501,9 +475,9 @@ func (r *PoolReconciler) SetupWithManager(mgr ctrl.Manager, options controller.O } return ctrl.NewControllerManagedBy(mgr). - For(&garmoperatorv1alpha1.Pool{}). + For(&garmoperatorv1beta1.Pool{}). Watches( - &garmoperatorv1alpha1.Image{}, + &garmoperatorv1beta1.Image{}, handler.EnqueueRequestsFromMapFunc(r.findPoolsForImage), builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), ). diff --git a/internal/controller/pool_controller_test.go b/internal/controller/pool_controller_test.go index 97b25d5f..d7558e31 100644 --- a/internal/controller/pool_controller_test.go +++ b/internal/controller/pool_controller_test.go @@ -24,7 +24,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" - garmoperatorv1alpha1 "github.com/mercedes-benz/garm-operator/api/v1alpha1" + garmoperatorv1beta1 "github.com/mercedes-benz/garm-operator/api/v1beta1" "github.com/mercedes-benz/garm-operator/pkg/client/key" "github.com/mercedes-benz/garm-operator/pkg/client/mock" "github.com/mercedes-benz/garm-operator/pkg/conditions" @@ -37,7 +37,6 @@ func TestPoolController_ReconcileCreate(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() - outdatedPoolID := "a9f48897-77c3-4293-8462-732a22a908f1" poolID := "fb2bceeb-f74d-435d-9648-626c75cb23ce" enterpriseID := "93068607-2d0d-4b76-a950-0e40d31955b8" enterpriseName := "test-enterprise" @@ -52,23 +51,23 @@ func TestPoolController_ReconcileCreate(t *testing.T) { runtimeObjects []runtime.Object expectGarmRequest func(_ *mock.MockPoolClientMockRecorder, instanceClient *mock.MockInstanceClientMockRecorder) wantErr bool - expectedObject *garmoperatorv1alpha1.Pool + expectedObject *garmoperatorv1beta1.Pool }{ { name: "pool does not exist in garm - create", - object: &garmoperatorv1alpha1.Pool{ + object: &garmoperatorv1beta1.Pool{ TypeMeta: metav1.TypeMeta{ Kind: "Pool", - APIVersion: garmoperatorv1alpha1.GroupVersion.Group + "/" + garmoperatorv1alpha1.GroupVersion.Version, + APIVersion: garmoperatorv1beta1.GroupVersion.Group + "/" + garmoperatorv1beta1.GroupVersion.Version, }, ObjectMeta: metav1.ObjectMeta{ Name: "my-enterprise-pool", Namespace: namespaceName, }, - Spec: garmoperatorv1alpha1.PoolSpec{ + Spec: garmoperatorv1beta1.PoolSpec{ GitHubScopeRef: corev1.TypedLocalObjectReference{ - APIGroup: &garmoperatorv1alpha1.GroupVersion.Group, - Kind: string(garmoperatorv1alpha1.EnterpriseScope), + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: string(garmoperatorv1beta1.EnterpriseScope), Name: enterpriseName, }, ProviderName: "kubernetes_external", @@ -85,10 +84,10 @@ func TestPoolController_ReconcileCreate(t *testing.T) { GitHubRunnerGroup: "", }, }, - expectedObject: &garmoperatorv1alpha1.Pool{ + expectedObject: &garmoperatorv1beta1.Pool{ TypeMeta: metav1.TypeMeta{ Kind: "Pool", - APIVersion: garmoperatorv1alpha1.GroupVersion.Group + "/" + garmoperatorv1alpha1.GroupVersion.Version, + APIVersion: garmoperatorv1beta1.GroupVersion.Group + "/" + garmoperatorv1beta1.GroupVersion.Version, }, ObjectMeta: metav1.ObjectMeta{ Name: "my-enterprise-pool", @@ -97,10 +96,10 @@ func TestPoolController_ReconcileCreate(t *testing.T) { key.PoolFinalizerName, }, }, - Spec: garmoperatorv1alpha1.PoolSpec{ + Spec: garmoperatorv1beta1.PoolSpec{ GitHubScopeRef: corev1.TypedLocalObjectReference{ - APIGroup: &garmoperatorv1alpha1.GroupVersion.Group, - Kind: string(garmoperatorv1alpha1.EnterpriseScope), + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: string(garmoperatorv1beta1.EnterpriseScope), Name: enterpriseName, }, ProviderName: "kubernetes_external", @@ -116,7 +115,7 @@ func TestPoolController_ReconcileCreate(t *testing.T) { ExtraSpecs: "", GitHubRunnerGroup: "", }, - Status: garmoperatorv1alpha1.PoolStatus{ + Status: garmoperatorv1beta1.PoolStatus{ ID: poolID, LongRunningIdleRunners: 3, Selector: "", @@ -148,32 +147,36 @@ func TestPoolController_ReconcileCreate(t *testing.T) { "webhookSecret": []byte("supersecretvalue"), }, }, - &garmoperatorv1alpha1.Image{ + &garmoperatorv1beta1.Image{ ObjectMeta: metav1.ObjectMeta{ Name: "ubuntu-image", Namespace: namespaceName, }, - Spec: garmoperatorv1alpha1.ImageSpec{ + Spec: garmoperatorv1beta1.ImageSpec{ Tag: "linux-ubuntu-22.04-arm64", }, }, - &garmoperatorv1alpha1.Enterprise{ + &garmoperatorv1beta1.Enterprise{ TypeMeta: metav1.TypeMeta{ Kind: "Enterprise", - APIVersion: garmoperatorv1alpha1.GroupVersion.Group + "/" + garmoperatorv1alpha1.GroupVersion.Version, + APIVersion: garmoperatorv1beta1.GroupVersion.Group + "/" + garmoperatorv1beta1.GroupVersion.Version, }, ObjectMeta: metav1.ObjectMeta{ Name: enterpriseName, Namespace: namespaceName, }, - Spec: garmoperatorv1alpha1.EnterpriseSpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.EnterpriseSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.EnterpriseStatus{ + Status: garmoperatorv1beta1.EnterpriseStatus{ ID: enterpriseID, Conditions: []metav1.Condition{ { @@ -195,8 +198,6 @@ func TestPoolController_ReconcileCreate(t *testing.T) { }, }, expectGarmRequest: func(poolClient *mock.MockPoolClientMockRecorder, _ *mock.MockInstanceClientMockRecorder) { - poolClient.ListAllPools(pools.NewListPoolsParams()).Return(&pools.ListPoolsOK{Payload: params.Pools{}}, nil) - extraSpecs := json.RawMessage([]byte{}) poolClient.CreateEnterprisePool( enterprises.NewCreateEnterprisePoolParams().WithEnterpriseID(enterpriseID).WithBody( @@ -260,210 +261,21 @@ func TestPoolController_ReconcileCreate(t *testing.T) { }, nil) }, }, - { - name: "pool with matching specs exists in garm, outdated garmId in pool.Status - sync ids", - object: &garmoperatorv1alpha1.Pool{ - TypeMeta: metav1.TypeMeta{ - Kind: "Pool", - APIVersion: garmoperatorv1alpha1.GroupVersion.Group + "/" + garmoperatorv1alpha1.GroupVersion.Version, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "my-enterprise-pool", - Namespace: namespaceName, - }, - Spec: garmoperatorv1alpha1.PoolSpec{ - GitHubScopeRef: corev1.TypedLocalObjectReference{ - APIGroup: &garmoperatorv1alpha1.GroupVersion.Group, - Kind: string(garmoperatorv1alpha1.EnterpriseScope), - Name: enterpriseName, - }, - ProviderName: "kubernetes_external", - MaxRunners: 5, - MinIdleRunners: 3, - ImageName: "ubuntu-image", - Flavor: "medium", - OSType: "linux", - OSArch: "arm64", - Tags: []string{"kubernetes", "linux", "arm64", "ubuntu"}, - Enabled: true, - RunnerBootstrapTimeout: 20, - ExtraSpecs: "", - GitHubRunnerGroup: "", - }, - Status: garmoperatorv1alpha1.PoolStatus{ - ID: outdatedPoolID, - }, - }, - expectedObject: &garmoperatorv1alpha1.Pool{ - TypeMeta: metav1.TypeMeta{ - Kind: "Pool", - APIVersion: garmoperatorv1alpha1.GroupVersion.Group + "/" + garmoperatorv1alpha1.GroupVersion.Version, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "my-enterprise-pool", - Namespace: namespaceName, - Finalizers: []string{ - key.PoolFinalizerName, - }, - }, - Spec: garmoperatorv1alpha1.PoolSpec{ - GitHubScopeRef: corev1.TypedLocalObjectReference{ - APIGroup: &garmoperatorv1alpha1.GroupVersion.Group, - Kind: string(garmoperatorv1alpha1.EnterpriseScope), - Name: enterpriseName, - }, - ProviderName: "kubernetes_external", - MaxRunners: 5, - MinIdleRunners: 3, - ImageName: "ubuntu-image", - Flavor: "medium", - OSType: "linux", - OSArch: "arm64", - Tags: []string{"kubernetes", "linux", "arm64", "ubuntu"}, - Enabled: true, - RunnerBootstrapTimeout: 20, - ExtraSpecs: "", - GitHubRunnerGroup: "", - }, - Status: garmoperatorv1alpha1.PoolStatus{ - ID: poolID, - LongRunningIdleRunners: 3, - Selector: "", - Conditions: []metav1.Condition{ - { - Type: string(conditions.ReadyCondition), - Status: metav1.ConditionTrue, - LastTransitionTime: metav1.NewTime(time.Now()), - Reason: string(conditions.SuccessfulReconcileReason), - Message: "", - }, - { - Type: string(conditions.ImageReference), - Status: metav1.ConditionTrue, - Message: "Successfully fetched Image CR Ref", - Reason: string(conditions.FetchingImageRefSuccessReason), - LastTransitionTime: metav1.NewTime(time.Now()), - }, - }, - }, - }, - runtimeObjects: []runtime.Object{ - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespaceName, - Name: "my-webhook-secret", - }, - Data: map[string][]byte{ - "webhookSecret": []byte("supersecretvalue"), - }, - }, - &garmoperatorv1alpha1.Image{ - ObjectMeta: metav1.ObjectMeta{ - Name: "ubuntu-image", - Namespace: namespaceName, - }, - Spec: garmoperatorv1alpha1.ImageSpec{ - Tag: "linux-ubuntu-22.04-arm64", - }, - }, - &garmoperatorv1alpha1.Enterprise{ - TypeMeta: metav1.TypeMeta{ - Kind: "Enterprise", - APIVersion: garmoperatorv1alpha1.GroupVersion.Group + "/" + garmoperatorv1alpha1.GroupVersion.Version, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: enterpriseName, - Namespace: namespaceName, - }, - Spec: garmoperatorv1alpha1.EnterpriseSpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ - Name: "my-webhook-secret", - Key: "webhookSecret", - }, - }, - Status: garmoperatorv1alpha1.EnterpriseStatus{ - ID: enterpriseID, - Conditions: []metav1.Condition{ - { - Type: string(conditions.ReadyCondition), - Reason: string(conditions.SuccessfulReconcileReason), - Status: metav1.ConditionTrue, - Message: "", - LastTransitionTime: metav1.NewTime(time.Now()), - }, - { - Type: string(conditions.PoolManager), - Reason: string(conditions.PoolManagerFailureReason), - Status: metav1.ConditionFalse, - Message: "no resources available", - LastTransitionTime: metav1.NewTime(time.Now()), - }, - }, - }, - }, - }, - expectGarmRequest: func(poolClient *mock.MockPoolClientMockRecorder, _ *mock.MockInstanceClientMockRecorder) { - poolClient.GetPool(pools.NewGetPoolParams().WithPoolID(outdatedPoolID)).Return(&pools.GetPoolOK{Payload: params.Pool{}}, nil) - - poolClient.ListAllPools(pools.NewListPoolsParams()).Return(&pools.ListPoolsOK{Payload: params.Pools{ - { - RunnerPrefix: params.RunnerPrefix{ - Prefix: "", - }, - ID: poolID, - ProviderName: "kubernetes_external", - MaxRunners: 5, - MinIdleRunners: 3, - Image: "linux-ubuntu-22.04-arm64", - Flavor: "medium", - OSType: "linux", - OSArch: "arm64", - Tags: []params.Tag{ - { - ID: "b3ea9882-a25c-4eb1-94ba-6c70b9abb6da", - Name: "kubernetes", - }, - { - ID: "b3ea9882-a25c-4eb1-94ba-6c70b9abb6db", - Name: "linux", - }, - { - ID: "b3ea9882-a25c-4eb1-94ba-6c70b9abb6dc", - Name: "arm64", - }, - { - ID: "b3ea9882-a25c-4eb1-94ba-6c70b9abb6dd", - Name: "ubuntu", - }, - }, - Enabled: true, - Instances: []params.Instance{}, - RepoID: "", - RepoName: "", - OrgID: "", - OrgName: "", - EnterpriseID: enterpriseID, - EnterpriseName: enterpriseName, - }, - }}, nil) - }, - }, { name: "pool.Status has matching id in garm database, pool.Specs changed - update pool in garm", - object: &garmoperatorv1alpha1.Pool{ + object: &garmoperatorv1beta1.Pool{ TypeMeta: metav1.TypeMeta{ Kind: "Pool", - APIVersion: garmoperatorv1alpha1.GroupVersion.Group + "/" + garmoperatorv1alpha1.GroupVersion.Version, + APIVersion: garmoperatorv1beta1.GroupVersion.Group + "/" + garmoperatorv1beta1.GroupVersion.Version, }, ObjectMeta: metav1.ObjectMeta{ Name: "my-enterprise-pool", Namespace: namespaceName, }, - Spec: garmoperatorv1alpha1.PoolSpec{ + Spec: garmoperatorv1beta1.PoolSpec{ GitHubScopeRef: corev1.TypedLocalObjectReference{ - APIGroup: &garmoperatorv1alpha1.GroupVersion.Group, - Kind: string(garmoperatorv1alpha1.EnterpriseScope), + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: string(garmoperatorv1beta1.EnterpriseScope), Name: enterpriseName, }, ProviderName: "kubernetes_external", @@ -479,15 +291,14 @@ func TestPoolController_ReconcileCreate(t *testing.T) { ExtraSpecs: "", GitHubRunnerGroup: "", }, - Status: garmoperatorv1alpha1.PoolStatus{ + Status: garmoperatorv1beta1.PoolStatus{ ID: poolID, - // LastSyncError: "", }, }, - expectedObject: &garmoperatorv1alpha1.Pool{ + expectedObject: &garmoperatorv1beta1.Pool{ TypeMeta: metav1.TypeMeta{ Kind: "Pool", - APIVersion: garmoperatorv1alpha1.GroupVersion.Group + "/" + garmoperatorv1alpha1.GroupVersion.Version, + APIVersion: garmoperatorv1beta1.GroupVersion.Group + "/" + garmoperatorv1beta1.GroupVersion.Version, }, ObjectMeta: metav1.ObjectMeta{ Name: "my-enterprise-pool", @@ -496,10 +307,10 @@ func TestPoolController_ReconcileCreate(t *testing.T) { key.PoolFinalizerName, }, }, - Spec: garmoperatorv1alpha1.PoolSpec{ + Spec: garmoperatorv1beta1.PoolSpec{ GitHubScopeRef: corev1.TypedLocalObjectReference{ - APIGroup: &garmoperatorv1alpha1.GroupVersion.Group, - Kind: string(garmoperatorv1alpha1.EnterpriseScope), + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: string(garmoperatorv1beta1.EnterpriseScope), Name: enterpriseName, }, ProviderName: "kubernetes_external", @@ -515,7 +326,7 @@ func TestPoolController_ReconcileCreate(t *testing.T) { ExtraSpecs: "", GitHubRunnerGroup: "", }, - Status: garmoperatorv1alpha1.PoolStatus{ + Status: garmoperatorv1beta1.PoolStatus{ ID: poolID, Conditions: []metav1.Condition{ { @@ -545,32 +356,36 @@ func TestPoolController_ReconcileCreate(t *testing.T) { "webhookSecret": []byte("supersecretvalue"), }, }, - &garmoperatorv1alpha1.Image{ + &garmoperatorv1beta1.Image{ ObjectMeta: metav1.ObjectMeta{ Name: "ubuntu-image", Namespace: namespaceName, }, - Spec: garmoperatorv1alpha1.ImageSpec{ + Spec: garmoperatorv1beta1.ImageSpec{ Tag: "linux-ubuntu-22.04-arm64", }, }, - &garmoperatorv1alpha1.Enterprise{ + &garmoperatorv1beta1.Enterprise{ TypeMeta: metav1.TypeMeta{ Kind: "Enterprise", - APIVersion: garmoperatorv1alpha1.GroupVersion.Group + "/" + garmoperatorv1alpha1.GroupVersion.Version, + APIVersion: garmoperatorv1beta1.GroupVersion.Group + "/" + garmoperatorv1beta1.GroupVersion.Version, }, ObjectMeta: metav1.ObjectMeta{ Name: enterpriseName, Namespace: namespaceName, }, - Spec: garmoperatorv1alpha1.EnterpriseSpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.EnterpriseSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.EnterpriseStatus{ + Status: garmoperatorv1beta1.EnterpriseStatus{ ID: enterpriseID, Conditions: []metav1.Condition{ { @@ -736,19 +551,19 @@ func TestPoolController_ReconcileCreate(t *testing.T) { }, { name: "scaling idleRunners down to 2 - expect deletion of two old instances", - object: &garmoperatorv1alpha1.Pool{ + object: &garmoperatorv1beta1.Pool{ TypeMeta: metav1.TypeMeta{ Kind: "Pool", - APIVersion: garmoperatorv1alpha1.GroupVersion.Group + "/" + garmoperatorv1alpha1.GroupVersion.Version, + APIVersion: garmoperatorv1beta1.GroupVersion.Group + "/" + garmoperatorv1beta1.GroupVersion.Version, }, ObjectMeta: metav1.ObjectMeta{ Name: "my-enterprise-pool", Namespace: namespaceName, }, - Spec: garmoperatorv1alpha1.PoolSpec{ + Spec: garmoperatorv1beta1.PoolSpec{ GitHubScopeRef: corev1.TypedLocalObjectReference{ - APIGroup: &garmoperatorv1alpha1.GroupVersion.Group, - Kind: string(garmoperatorv1alpha1.EnterpriseScope), + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: string(garmoperatorv1beta1.EnterpriseScope), Name: enterpriseName, }, ProviderName: "kubernetes_external", @@ -764,16 +579,15 @@ func TestPoolController_ReconcileCreate(t *testing.T) { ExtraSpecs: "", GitHubRunnerGroup: "", }, - Status: garmoperatorv1alpha1.PoolStatus{ + Status: garmoperatorv1beta1.PoolStatus{ ID: poolID, LongRunningIdleRunners: 3, - // LastSyncError: "", }, }, - expectedObject: &garmoperatorv1alpha1.Pool{ + expectedObject: &garmoperatorv1beta1.Pool{ TypeMeta: metav1.TypeMeta{ Kind: "Pool", - APIVersion: garmoperatorv1alpha1.GroupVersion.Group + "/" + garmoperatorv1alpha1.GroupVersion.Version, + APIVersion: garmoperatorv1beta1.GroupVersion.Group + "/" + garmoperatorv1beta1.GroupVersion.Version, }, ObjectMeta: metav1.ObjectMeta{ Name: "my-enterprise-pool", @@ -782,10 +596,10 @@ func TestPoolController_ReconcileCreate(t *testing.T) { key.PoolFinalizerName, }, }, - Spec: garmoperatorv1alpha1.PoolSpec{ + Spec: garmoperatorv1beta1.PoolSpec{ GitHubScopeRef: corev1.TypedLocalObjectReference{ - APIGroup: &garmoperatorv1alpha1.GroupVersion.Group, - Kind: string(garmoperatorv1alpha1.EnterpriseScope), + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: string(garmoperatorv1beta1.EnterpriseScope), Name: enterpriseName, }, ProviderName: "kubernetes_external", @@ -801,7 +615,7 @@ func TestPoolController_ReconcileCreate(t *testing.T) { ExtraSpecs: "", GitHubRunnerGroup: "", }, - Status: garmoperatorv1alpha1.PoolStatus{ + Status: garmoperatorv1beta1.PoolStatus{ ID: poolID, LongRunningIdleRunners: 2, Conditions: []metav1.Condition{ @@ -832,32 +646,36 @@ func TestPoolController_ReconcileCreate(t *testing.T) { "webhookSecret": []byte("supersecretvalue"), }, }, - &garmoperatorv1alpha1.Image{ + &garmoperatorv1beta1.Image{ ObjectMeta: metav1.ObjectMeta{ Name: "ubuntu-image", Namespace: namespaceName, }, - Spec: garmoperatorv1alpha1.ImageSpec{ + Spec: garmoperatorv1beta1.ImageSpec{ Tag: "linux-ubuntu-22.04-arm64", }, }, - &garmoperatorv1alpha1.Enterprise{ + &garmoperatorv1beta1.Enterprise{ TypeMeta: metav1.TypeMeta{ Kind: "Enterprise", - APIVersion: garmoperatorv1alpha1.GroupVersion.Group + "/" + garmoperatorv1alpha1.GroupVersion.Version, + APIVersion: garmoperatorv1beta1.GroupVersion.Group + "/" + garmoperatorv1beta1.GroupVersion.Version, }, ObjectMeta: metav1.ObjectMeta{ Name: enterpriseName, Namespace: namespaceName, }, - Spec: garmoperatorv1alpha1.EnterpriseSpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.EnterpriseSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.EnterpriseStatus{ + Status: garmoperatorv1beta1.EnterpriseStatus{ ID: enterpriseID, Conditions: []metav1.Condition{ { @@ -1133,19 +951,19 @@ func TestPoolController_ReconcileCreate(t *testing.T) { }, { name: "pool does not exist in garm - error no image cr found", - object: &garmoperatorv1alpha1.Pool{ + object: &garmoperatorv1beta1.Pool{ TypeMeta: metav1.TypeMeta{ Kind: "Pool", - APIVersion: garmoperatorv1alpha1.GroupVersion.Group + "/" + garmoperatorv1alpha1.GroupVersion.Version, + APIVersion: garmoperatorv1beta1.GroupVersion.Group + "/" + garmoperatorv1beta1.GroupVersion.Version, }, ObjectMeta: metav1.ObjectMeta{ Name: "my-enterprise-pool", Namespace: namespaceName, }, - Spec: garmoperatorv1alpha1.PoolSpec{ + Spec: garmoperatorv1beta1.PoolSpec{ GitHubScopeRef: corev1.TypedLocalObjectReference{ - APIGroup: &garmoperatorv1alpha1.GroupVersion.Group, - Kind: string(garmoperatorv1alpha1.EnterpriseScope), + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: string(garmoperatorv1beta1.EnterpriseScope), Name: enterpriseName, }, ProviderName: "kubernetes_external", @@ -1162,10 +980,10 @@ func TestPoolController_ReconcileCreate(t *testing.T) { GitHubRunnerGroup: "", }, }, - expectedObject: &garmoperatorv1alpha1.Pool{ + expectedObject: &garmoperatorv1beta1.Pool{ TypeMeta: metav1.TypeMeta{ Kind: "Pool", - APIVersion: garmoperatorv1alpha1.GroupVersion.Group + "/" + garmoperatorv1alpha1.GroupVersion.Version, + APIVersion: garmoperatorv1beta1.GroupVersion.Group + "/" + garmoperatorv1beta1.GroupVersion.Version, }, ObjectMeta: metav1.ObjectMeta{ Name: "my-enterprise-pool", @@ -1174,10 +992,10 @@ func TestPoolController_ReconcileCreate(t *testing.T) { key.PoolFinalizerName, }, }, - Spec: garmoperatorv1alpha1.PoolSpec{ + Spec: garmoperatorv1beta1.PoolSpec{ GitHubScopeRef: corev1.TypedLocalObjectReference{ - APIGroup: &garmoperatorv1alpha1.GroupVersion.Group, - Kind: string(garmoperatorv1alpha1.EnterpriseScope), + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: string(garmoperatorv1beta1.EnterpriseScope), Name: enterpriseName, }, ProviderName: "kubernetes_external", @@ -1193,7 +1011,7 @@ func TestPoolController_ReconcileCreate(t *testing.T) { ExtraSpecs: "", GitHubRunnerGroup: "", }, - Status: garmoperatorv1alpha1.PoolStatus{ + Status: garmoperatorv1beta1.PoolStatus{ ID: "", LongRunningIdleRunners: 0, Selector: "", @@ -1225,23 +1043,27 @@ func TestPoolController_ReconcileCreate(t *testing.T) { "webhookSecret": []byte("supersecretvalue"), }, }, - &garmoperatorv1alpha1.Enterprise{ + &garmoperatorv1beta1.Enterprise{ TypeMeta: metav1.TypeMeta{ Kind: "Enterprise", - APIVersion: garmoperatorv1alpha1.GroupVersion.Group + "/" + garmoperatorv1alpha1.GroupVersion.Version, + APIVersion: garmoperatorv1beta1.GroupVersion.Group + "/" + garmoperatorv1beta1.GroupVersion.Version, }, ObjectMeta: metav1.ObjectMeta{ Name: enterpriseName, Namespace: namespaceName, }, - Spec: garmoperatorv1alpha1.EnterpriseSpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.EnterpriseSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.EnterpriseStatus{ + Status: garmoperatorv1beta1.EnterpriseStatus{ ID: enterpriseID, Conditions: []metav1.Condition{ { @@ -1268,19 +1090,19 @@ func TestPoolController_ReconcileCreate(t *testing.T) { }, { name: "pool.Status has matching id in garm database, pool.Specs changed to not existent image cr ref - error no image cr found", - object: &garmoperatorv1alpha1.Pool{ + object: &garmoperatorv1beta1.Pool{ TypeMeta: metav1.TypeMeta{ Kind: "Pool", - APIVersion: garmoperatorv1alpha1.GroupVersion.Group + "/" + garmoperatorv1alpha1.GroupVersion.Version, + APIVersion: garmoperatorv1beta1.GroupVersion.Group + "/" + garmoperatorv1beta1.GroupVersion.Version, }, ObjectMeta: metav1.ObjectMeta{ Name: "my-enterprise-pool", Namespace: namespaceName, }, - Spec: garmoperatorv1alpha1.PoolSpec{ + Spec: garmoperatorv1beta1.PoolSpec{ GitHubScopeRef: corev1.TypedLocalObjectReference{ - APIGroup: &garmoperatorv1alpha1.GroupVersion.Group, - Kind: string(garmoperatorv1alpha1.EnterpriseScope), + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: string(garmoperatorv1beta1.EnterpriseScope), Name: enterpriseName, }, ProviderName: "kubernetes_external", @@ -1296,15 +1118,14 @@ func TestPoolController_ReconcileCreate(t *testing.T) { ExtraSpecs: "", GitHubRunnerGroup: "", }, - Status: garmoperatorv1alpha1.PoolStatus{ + Status: garmoperatorv1beta1.PoolStatus{ ID: poolID, - // LastSyncError: "", }, }, - expectedObject: &garmoperatorv1alpha1.Pool{ + expectedObject: &garmoperatorv1beta1.Pool{ TypeMeta: metav1.TypeMeta{ Kind: "Pool", - APIVersion: garmoperatorv1alpha1.GroupVersion.Group + "/" + garmoperatorv1alpha1.GroupVersion.Version, + APIVersion: garmoperatorv1beta1.GroupVersion.Group + "/" + garmoperatorv1beta1.GroupVersion.Version, }, ObjectMeta: metav1.ObjectMeta{ Name: "my-enterprise-pool", @@ -1313,10 +1134,10 @@ func TestPoolController_ReconcileCreate(t *testing.T) { key.PoolFinalizerName, }, }, - Spec: garmoperatorv1alpha1.PoolSpec{ + Spec: garmoperatorv1beta1.PoolSpec{ GitHubScopeRef: corev1.TypedLocalObjectReference{ - APIGroup: &garmoperatorv1alpha1.GroupVersion.Group, - Kind: string(garmoperatorv1alpha1.EnterpriseScope), + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: string(garmoperatorv1beta1.EnterpriseScope), Name: enterpriseName, }, ProviderName: "kubernetes_external", @@ -1332,7 +1153,7 @@ func TestPoolController_ReconcileCreate(t *testing.T) { ExtraSpecs: "", GitHubRunnerGroup: "", }, - Status: garmoperatorv1alpha1.PoolStatus{ + Status: garmoperatorv1beta1.PoolStatus{ ID: poolID, Conditions: []metav1.Condition{ { @@ -1362,32 +1183,36 @@ func TestPoolController_ReconcileCreate(t *testing.T) { "webhookSecret": []byte("supersecretvalue"), }, }, - &garmoperatorv1alpha1.Image{ + &garmoperatorv1beta1.Image{ ObjectMeta: metav1.ObjectMeta{ Name: "ubuntu-image", Namespace: namespaceName, }, - Spec: garmoperatorv1alpha1.ImageSpec{ + Spec: garmoperatorv1beta1.ImageSpec{ Tag: "linux-ubuntu-22.04-arm64", }, }, - &garmoperatorv1alpha1.Enterprise{ + &garmoperatorv1beta1.Enterprise{ TypeMeta: metav1.TypeMeta{ Kind: "Enterprise", - APIVersion: garmoperatorv1alpha1.GroupVersion.Group + "/" + garmoperatorv1alpha1.GroupVersion.Version, + APIVersion: garmoperatorv1beta1.GroupVersion.Group + "/" + garmoperatorv1beta1.GroupVersion.Version, }, ObjectMeta: metav1.ObjectMeta{ Name: enterpriseName, Namespace: namespaceName, }, - Spec: garmoperatorv1alpha1.EnterpriseSpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.EnterpriseSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.EnterpriseStatus{ + Status: garmoperatorv1beta1.EnterpriseStatus{ ID: enterpriseID, Conditions: []metav1.Condition{ { @@ -1451,203 +1276,12 @@ func TestPoolController_ReconcileCreate(t *testing.T) { }}, nil) }, }, - { - name: "pool with same image, provider, flavor and github scope applied - do not sync pool", - object: &garmoperatorv1alpha1.Pool{ - TypeMeta: metav1.TypeMeta{ - Kind: "Pool", - APIVersion: garmoperatorv1alpha1.GroupVersion.Group + "/" + garmoperatorv1alpha1.GroupVersion.Version, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "my-enterprise-pool", - Namespace: namespaceName, - }, - Spec: garmoperatorv1alpha1.PoolSpec{ - GitHubScopeRef: corev1.TypedLocalObjectReference{ - APIGroup: &garmoperatorv1alpha1.GroupVersion.Group, - Kind: string(garmoperatorv1alpha1.EnterpriseScope), - Name: enterpriseName, - }, - ProviderName: "kubernetes_external", - MaxRunners: 1, - MinIdleRunners: 0, - ImageName: "ubuntu-image", - Flavor: "medium", - OSType: "linux", - OSArch: "arm64", - Tags: []string{"kubernetes", "linux", "arm64", "ubuntu"}, - Enabled: true, - RunnerBootstrapTimeout: 20, - ExtraSpecs: "", - GitHubRunnerGroup: "", - }, - }, - expectedObject: &garmoperatorv1alpha1.Pool{ - TypeMeta: metav1.TypeMeta{ - Kind: "Pool", - APIVersion: garmoperatorv1alpha1.GroupVersion.Group + "/" + garmoperatorv1alpha1.GroupVersion.Version, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "my-enterprise-pool", - Namespace: namespaceName, - Finalizers: []string{ - key.PoolFinalizerName, - }, - }, - Spec: garmoperatorv1alpha1.PoolSpec{ - GitHubScopeRef: corev1.TypedLocalObjectReference{ - APIGroup: &garmoperatorv1alpha1.GroupVersion.Group, - Kind: string(garmoperatorv1alpha1.EnterpriseScope), - Name: enterpriseName, - }, - ProviderName: "kubernetes_external", - MaxRunners: 1, - MinIdleRunners: 0, - ImageName: "ubuntu-image", - Flavor: "medium", - OSType: "linux", - OSArch: "arm64", - Tags: []string{"kubernetes", "linux", "arm64", "ubuntu"}, - Enabled: true, - RunnerBootstrapTimeout: 20, - ExtraSpecs: "", - GitHubRunnerGroup: "", - }, - Status: garmoperatorv1alpha1.PoolStatus{ - Conditions: []metav1.Condition{ - { - Type: string(conditions.ReadyCondition), - Reason: string(conditions.DuplicatePoolReason), - Status: metav1.ConditionFalse, - Message: "pool with same image, flavor, provider and github scope already exists: test-namespace/duplicate-pool", - LastTransitionTime: metav1.NewTime(time.Now()), - }, - { - Type: string(conditions.ImageReference), - Status: metav1.ConditionTrue, - Message: "Successfully fetched Image CR Ref", - Reason: string(conditions.FetchingImageRefSuccessReason), - LastTransitionTime: metav1.NewTime(time.Now()), - }, - }, - }, - }, - runtimeObjects: []runtime.Object{ - &garmoperatorv1alpha1.Pool{ - TypeMeta: metav1.TypeMeta{ - Kind: "Pool", - APIVersion: garmoperatorv1alpha1.GroupVersion.Group + "/" + garmoperatorv1alpha1.GroupVersion.Version, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: "duplicate-pool", - Namespace: namespaceName, - Finalizers: []string{ - key.PoolFinalizerName, - }, - }, - Spec: garmoperatorv1alpha1.PoolSpec{ - GitHubScopeRef: corev1.TypedLocalObjectReference{ - APIGroup: &garmoperatorv1alpha1.GroupVersion.Group, - Kind: string(garmoperatorv1alpha1.EnterpriseScope), - Name: enterpriseName, - }, - ProviderName: "kubernetes_external", - MaxRunners: 1, - MinIdleRunners: 0, - ImageName: "ubuntu-image", - Flavor: "medium", - OSType: "linux", - OSArch: "arm64", - Tags: []string{"kubernetes", "linux", "arm64", "ubuntu"}, - Enabled: true, - RunnerBootstrapTimeout: 20, - ExtraSpecs: "", - GitHubRunnerGroup: "", - }, - Status: garmoperatorv1alpha1.PoolStatus{ - ID: poolID, - Conditions: []metav1.Condition{ - { - Type: string(conditions.ReadyCondition), - Reason: string(conditions.SuccessfulReconcileReason), - Status: metav1.ConditionFalse, - Message: "", - LastTransitionTime: metav1.NewTime(time.Now()), - }, - { - Type: string(conditions.ImageReference), - Status: metav1.ConditionTrue, - Message: "Successfully fetched Image CR Ref", - Reason: string(conditions.FetchingImageRefSuccessReason), - LastTransitionTime: metav1.NewTime(time.Now()), - }, - }, - }, - }, - &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Namespace: namespaceName, - Name: "my-webhook-secret", - }, - Data: map[string][]byte{ - "webhookSecret": []byte("supersecretvalue"), - }, - }, - &garmoperatorv1alpha1.Image{ - ObjectMeta: metav1.ObjectMeta{ - Name: "ubuntu-image", - Namespace: namespaceName, - }, - Spec: garmoperatorv1alpha1.ImageSpec{ - Tag: "linux-ubuntu-22.04-arm64", - }, - }, - &garmoperatorv1alpha1.Enterprise{ - TypeMeta: metav1.TypeMeta{ - Kind: "Enterprise", - APIVersion: garmoperatorv1alpha1.GroupVersion.Group + "/" + garmoperatorv1alpha1.GroupVersion.Version, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: enterpriseName, - Namespace: namespaceName, - }, - Spec: garmoperatorv1alpha1.EnterpriseSpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ - Name: "my-webhook-secret", - Key: "webhookSecret", - }, - }, - Status: garmoperatorv1alpha1.EnterpriseStatus{ - ID: enterpriseID, - Conditions: []metav1.Condition{ - { - Type: string(conditions.ReadyCondition), - Reason: string(conditions.SuccessfulReconcileReason), - Status: metav1.ConditionTrue, - Message: "", - LastTransitionTime: metav1.NewTime(time.Now()), - }, - { - Type: string(conditions.PoolManager), - Reason: string(conditions.PoolManagerFailureReason), - Status: metav1.ConditionFalse, - Message: "no resources available", - LastTransitionTime: metav1.NewTime(time.Now()), - }, - }, - }, - }, - }, - wantErr: true, - expectGarmRequest: func(_ *mock.MockPoolClientMockRecorder, _ *mock.MockInstanceClientMockRecorder) {}, - }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { schemeBuilder := runtime.SchemeBuilder{ - garmoperatorv1alpha1.AddToScheme, + garmoperatorv1beta1.AddToScheme, } err := schemeBuilder.AddToScheme(scheme.Scheme) @@ -1656,7 +1290,7 @@ func TestPoolController_ReconcileCreate(t *testing.T) { } runtimeObjects := []runtime.Object{tt.object} runtimeObjects = append(runtimeObjects, tt.runtimeObjects...) - client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(runtimeObjects...).WithStatusSubresource(&garmoperatorv1alpha1.Pool{}).Build() + client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(runtimeObjects...).WithStatusSubresource(&garmoperatorv1beta1.Pool{}).Build() // create a fake reconciler reconciler := &PoolReconciler{ @@ -1664,7 +1298,7 @@ func TestPoolController_ReconcileCreate(t *testing.T) { Recorder: record.NewFakeRecorder(3), } - pool := tt.object.DeepCopyObject().(*garmoperatorv1alpha1.Pool) + pool := tt.object.DeepCopyObject().(*garmoperatorv1beta1.Pool) mockPoolClient := mock.NewMockPoolClient(mockCtrl) mockInstanceClient := mock.NewMockInstanceClient(mockCtrl) @@ -1717,14 +1351,14 @@ func TestPoolController_ReconcileDelete(t *testing.T) { runtimeObjects []runtime.Object expectGarmRequest func(_ *mock.MockPoolClientMockRecorder, _ *mock.MockInstanceClientMockRecorder) wantErr bool - expectedObject *garmoperatorv1alpha1.Pool + expectedObject *garmoperatorv1beta1.Pool }{ { name: "delete pool - scaling down runners", - object: &garmoperatorv1alpha1.Pool{ + object: &garmoperatorv1beta1.Pool{ TypeMeta: metav1.TypeMeta{ Kind: "Pool", - APIVersion: garmoperatorv1alpha1.GroupVersion.Group + "/" + garmoperatorv1alpha1.GroupVersion.Version, + APIVersion: garmoperatorv1beta1.GroupVersion.Group + "/" + garmoperatorv1beta1.GroupVersion.Version, }, ObjectMeta: metav1.ObjectMeta{ Name: "my-enterprise-pool", @@ -1733,10 +1367,10 @@ func TestPoolController_ReconcileDelete(t *testing.T) { key.PoolFinalizerName, }, }, - Spec: garmoperatorv1alpha1.PoolSpec{ + Spec: garmoperatorv1beta1.PoolSpec{ GitHubScopeRef: corev1.TypedLocalObjectReference{ - APIGroup: &garmoperatorv1alpha1.GroupVersion.Group, - Kind: string(garmoperatorv1alpha1.EnterpriseScope), + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: string(garmoperatorv1beta1.EnterpriseScope), Name: enterpriseName, }, ProviderName: "kubernetes_external", @@ -1752,24 +1386,23 @@ func TestPoolController_ReconcileDelete(t *testing.T) { ExtraSpecs: "", GitHubRunnerGroup: "", }, - Status: garmoperatorv1alpha1.PoolStatus{ + Status: garmoperatorv1beta1.PoolStatus{ ID: poolID, - // LastSyncError: "", }, }, - expectedObject: &garmoperatorv1alpha1.Pool{ + expectedObject: &garmoperatorv1beta1.Pool{ TypeMeta: metav1.TypeMeta{ Kind: "Pool", - APIVersion: garmoperatorv1alpha1.GroupVersion.Group + "/" + garmoperatorv1alpha1.GroupVersion.Version, + APIVersion: garmoperatorv1beta1.GroupVersion.Group + "/" + garmoperatorv1beta1.GroupVersion.Version, }, ObjectMeta: metav1.ObjectMeta{ Name: "my-enterprise-pool", Namespace: namespaceName, }, - Spec: garmoperatorv1alpha1.PoolSpec{ + Spec: garmoperatorv1beta1.PoolSpec{ GitHubScopeRef: corev1.TypedLocalObjectReference{ - // APIGroup: &garmoperatorv1alpha1.GroupVersion.Group, - Kind: string(garmoperatorv1alpha1.EnterpriseScope), + // APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: string(garmoperatorv1beta1.EnterpriseScope), Name: enterpriseName, }, ProviderName: "kubernetes_external", @@ -1785,7 +1418,7 @@ func TestPoolController_ReconcileDelete(t *testing.T) { ExtraSpecs: "", GitHubRunnerGroup: "", }, - Status: garmoperatorv1alpha1.PoolStatus{ + Status: garmoperatorv1beta1.PoolStatus{ ID: poolID, LongRunningIdleRunners: 0, Conditions: []metav1.Condition{ @@ -1809,32 +1442,36 @@ func TestPoolController_ReconcileDelete(t *testing.T) { "webhookSecret": []byte("supersecretvalue"), }, }, - &garmoperatorv1alpha1.Image{ + &garmoperatorv1beta1.Image{ ObjectMeta: metav1.ObjectMeta{ Name: "ubuntu-image", Namespace: namespaceName, }, - Spec: garmoperatorv1alpha1.ImageSpec{ + Spec: garmoperatorv1beta1.ImageSpec{ Tag: "linux-ubuntu-22.04-arm64", }, }, - &garmoperatorv1alpha1.Enterprise{ + &garmoperatorv1beta1.Enterprise{ TypeMeta: metav1.TypeMeta{ Kind: "Enterprise", - APIVersion: garmoperatorv1alpha1.GroupVersion.Group + "/" + garmoperatorv1alpha1.GroupVersion.Version, + APIVersion: garmoperatorv1beta1.GroupVersion.Group + "/" + garmoperatorv1beta1.GroupVersion.Version, }, ObjectMeta: metav1.ObjectMeta{ Name: enterpriseName, Namespace: namespaceName, }, - Spec: garmoperatorv1alpha1.EnterpriseSpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.EnterpriseSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.EnterpriseStatus{ + Status: garmoperatorv1beta1.EnterpriseStatus{ ID: enterpriseID, Conditions: []metav1.Condition{ { @@ -1934,10 +1571,10 @@ func TestPoolController_ReconcileDelete(t *testing.T) { }, { name: "delete pool - deleting garm resource", - object: &garmoperatorv1alpha1.Pool{ + object: &garmoperatorv1beta1.Pool{ TypeMeta: metav1.TypeMeta{ Kind: "Pool", - APIVersion: garmoperatorv1alpha1.GroupVersion.Group + "/" + garmoperatorv1alpha1.GroupVersion.Version, + APIVersion: garmoperatorv1beta1.GroupVersion.Group + "/" + garmoperatorv1beta1.GroupVersion.Version, }, ObjectMeta: metav1.ObjectMeta{ Name: "my-enterprise-pool", @@ -1946,10 +1583,10 @@ func TestPoolController_ReconcileDelete(t *testing.T) { key.PoolFinalizerName, }, }, - Spec: garmoperatorv1alpha1.PoolSpec{ + Spec: garmoperatorv1beta1.PoolSpec{ GitHubScopeRef: corev1.TypedLocalObjectReference{ - APIGroup: &garmoperatorv1alpha1.GroupVersion.Group, - Kind: string(garmoperatorv1alpha1.EnterpriseScope), + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: string(garmoperatorv1beta1.EnterpriseScope), Name: enterpriseName, }, ProviderName: "kubernetes_external", @@ -1965,25 +1602,24 @@ func TestPoolController_ReconcileDelete(t *testing.T) { ExtraSpecs: "", GitHubRunnerGroup: "", }, - Status: garmoperatorv1alpha1.PoolStatus{ - ID: poolID, - // LastSyncError: "", + Status: garmoperatorv1beta1.PoolStatus{ + ID: poolID, LongRunningIdleRunners: 0, }, }, - expectedObject: &garmoperatorv1alpha1.Pool{ + expectedObject: &garmoperatorv1beta1.Pool{ TypeMeta: metav1.TypeMeta{ Kind: "Pool", - APIVersion: garmoperatorv1alpha1.GroupVersion.Group + "/" + garmoperatorv1alpha1.GroupVersion.Version, + APIVersion: garmoperatorv1beta1.GroupVersion.Group + "/" + garmoperatorv1beta1.GroupVersion.Version, }, ObjectMeta: metav1.ObjectMeta{ Name: "my-enterprise-pool", Namespace: namespaceName, }, - Spec: garmoperatorv1alpha1.PoolSpec{ + Spec: garmoperatorv1beta1.PoolSpec{ GitHubScopeRef: corev1.TypedLocalObjectReference{ - // APIGroup: &garmoperatorv1alpha1.GroupVersion.Group, - Kind: string(garmoperatorv1alpha1.EnterpriseScope), + // APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: string(garmoperatorv1beta1.EnterpriseScope), Name: enterpriseName, }, ProviderName: "kubernetes_external", @@ -1999,7 +1635,7 @@ func TestPoolController_ReconcileDelete(t *testing.T) { ExtraSpecs: "", GitHubRunnerGroup: "", }, - Status: garmoperatorv1alpha1.PoolStatus{ + Status: garmoperatorv1beta1.PoolStatus{ ID: poolID, Conditions: []metav1.Condition{ { @@ -2022,32 +1658,36 @@ func TestPoolController_ReconcileDelete(t *testing.T) { "webhookSecret": []byte("supersecretvalue"), }, }, - &garmoperatorv1alpha1.Image{ + &garmoperatorv1beta1.Image{ ObjectMeta: metav1.ObjectMeta{ Name: "ubuntu-image", Namespace: namespaceName, }, - Spec: garmoperatorv1alpha1.ImageSpec{ + Spec: garmoperatorv1beta1.ImageSpec{ Tag: "linux-ubuntu-22.04-arm64", }, }, - &garmoperatorv1alpha1.Enterprise{ + &garmoperatorv1beta1.Enterprise{ TypeMeta: metav1.TypeMeta{ Kind: "Enterprise", - APIVersion: garmoperatorv1alpha1.GroupVersion.Group + "/" + garmoperatorv1alpha1.GroupVersion.Version, + APIVersion: garmoperatorv1beta1.GroupVersion.Group + "/" + garmoperatorv1beta1.GroupVersion.Version, }, ObjectMeta: metav1.ObjectMeta{ Name: enterpriseName, Namespace: namespaceName, }, - Spec: garmoperatorv1alpha1.EnterpriseSpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.EnterpriseSpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.EnterpriseStatus{ + Status: garmoperatorv1beta1.EnterpriseStatus{ ID: enterpriseID, Conditions: []metav1.Condition{ { @@ -2132,7 +1772,7 @@ func TestPoolController_ReconcileDelete(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { schemeBuilder := runtime.SchemeBuilder{ - garmoperatorv1alpha1.AddToScheme, + garmoperatorv1beta1.AddToScheme, } err := schemeBuilder.AddToScheme(scheme.Scheme) @@ -2141,7 +1781,7 @@ func TestPoolController_ReconcileDelete(t *testing.T) { } runtimeObjects := []runtime.Object{tt.object} runtimeObjects = append(runtimeObjects, tt.runtimeObjects...) - client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(runtimeObjects...).WithStatusSubresource(&garmoperatorv1alpha1.Pool{}).Build() + client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(runtimeObjects...).WithStatusSubresource(&garmoperatorv1beta1.Pool{}).Build() // create a fake reconciler reconciler := &PoolReconciler{ @@ -2149,7 +1789,7 @@ func TestPoolController_ReconcileDelete(t *testing.T) { Recorder: record.NewFakeRecorder(3), } - pool := tt.object.DeepCopyObject().(*garmoperatorv1alpha1.Pool) + pool := tt.object.DeepCopyObject().(*garmoperatorv1beta1.Pool) mockPoolClient := mock.NewMockPoolClient(mockCtrl) mockInstanceClient := mock.NewMockInstanceClient(mockCtrl) diff --git a/internal/controller/repository_controller.go b/internal/controller/repository_controller.go index 4e4e6396..cf8c92ce 100644 --- a/internal/controller/repository_controller.go +++ b/internal/controller/repository_controller.go @@ -12,14 +12,19 @@ import ( "github.com/cloudbase/garm/params" apierrors "k8s.io/apimachinery/pkg/api/errors" "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/builder" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/predicate" + "sigs.k8s.io/controller-runtime/pkg/reconcile" - garmoperatorv1alpha1 "github.com/mercedes-benz/garm-operator/api/v1alpha1" + garmoperatorv1beta1 "github.com/mercedes-benz/garm-operator/api/v1beta1" "github.com/mercedes-benz/garm-operator/pkg/annotations" garmClient "github.com/mercedes-benz/garm-operator/pkg/client" "github.com/mercedes-benz/garm-operator/pkg/client/key" @@ -43,7 +48,7 @@ type RepositoryReconciler struct { func (r *RepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { log := log.FromContext(ctx) - repository := &garmoperatorv1alpha1.Repository{} + repository := &garmoperatorv1beta1.Repository{} err := r.Get(ctx, req.NamespacedName, repository) if err != nil { if apierrors.IsNotFound(err) { @@ -69,7 +74,7 @@ func (r *RepositoryReconciler) Reconcile(ctx context.Context, req ctrl.Request) return r.reconcileNormal(ctx, repositoryClient, repository) } -func (r *RepositoryReconciler) reconcileNormal(ctx context.Context, client garmClient.RepositoryClient, repository *garmoperatorv1alpha1.Repository) (ctrl.Result, error) { +func (r *RepositoryReconciler) reconcileNormal(ctx context.Context, client garmClient.RepositoryClient, repository *garmoperatorv1beta1.Repository) (ctrl.Result, error) { log := log.FromContext(ctx) log.WithValues("repository", repository.Name) @@ -83,6 +88,7 @@ func (r *RepositoryReconciler) reconcileNormal(ctx context.Context, client garmC conditions.MarkFalse(repository, conditions.ReadyCondition, conditions.FetchingSecretRefFailedReason, err.Error()) conditions.MarkFalse(repository, conditions.SecretReference, conditions.FetchingSecretRefFailedReason, err.Error()) conditions.MarkUnknown(repository, conditions.PoolManager, conditions.UnknownReason, conditions.GarmServerNotReconciledYetMsg) + conditions.MarkUnknown(repository, conditions.CredentialsReference, conditions.UnknownReason, conditions.CredentialsNotReconciledYetMsg) if err := r.Status().Update(ctx, repository); err != nil { return ctrl.Result{}, err } @@ -90,6 +96,20 @@ func (r *RepositoryReconciler) reconcileNormal(ctx context.Context, client garmC } conditions.MarkTrue(repository, conditions.SecretReference, conditions.FetchingSecretRefSuccessReason, "") + credentials, err := r.getCredentialsRef(ctx, repository) + if err != nil { + conditions.MarkFalse(repository, conditions.ReadyCondition, conditions.FetchingCredentialsRefFailedReason, err.Error()) + conditions.MarkFalse(repository, conditions.CredentialsReference, conditions.FetchingCredentialsRefFailedReason, err.Error()) + if conditions.Get(repository, conditions.PoolManager) == nil { + conditions.MarkUnknown(repository, conditions.PoolManager, conditions.UnknownReason, conditions.GarmServerNotReconciledYetMsg) + } + if err := r.Status().Update(ctx, repository); err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{}, err + } + conditions.MarkTrue(repository, conditions.CredentialsReference, conditions.FetchingCredentialsRefSuccessReason, "") + garmRepository, err := r.getExistingGarmRepo(ctx, client, repository) if err != nil { event.Error(r.Recorder, repository, err.Error()) @@ -114,7 +134,11 @@ func (r *RepositoryReconciler) reconcileNormal(ctx context.Context, client garmC } // update repository anytime - garmRepository, err = r.updateRepository(ctx, client, garmRepository.ID, webhookSecret, repository.Spec.CredentialsName) + garmRepository, err = r.updateRepository(ctx, client, garmRepository.ID, params.UpdateEntityParams{ + CredentialsName: credentials.Name, + WebhookSecret: webhookSecret, + PoolBalancerType: repository.Spec.PoolBalancerType, + }) if err != nil { event.Error(r.Recorder, repository, err.Error()) conditions.MarkFalse(repository, conditions.ReadyCondition, conditions.GarmAPIErrorReason, err.Error()) @@ -143,7 +167,7 @@ func (r *RepositoryReconciler) reconcileNormal(ctx context.Context, client garmC return ctrl.Result{}, nil } -func (r *RepositoryReconciler) createRepository(ctx context.Context, client garmClient.RepositoryClient, repository *garmoperatorv1alpha1.Repository, webhookSecret string) (params.Repository, error) { +func (r *RepositoryReconciler) createRepository(ctx context.Context, client garmClient.RepositoryClient, repository *garmoperatorv1beta1.Repository, webhookSecret string) (params.Repository, error) { log := log.FromContext(ctx) log.WithValues("repository", repository.Name) @@ -153,10 +177,11 @@ func (r *RepositoryReconciler) createRepository(ctx context.Context, client garm retValue, err := client.CreateRepository( repositories.NewCreateRepoParams(). WithBody(params.CreateRepoParams{ - Name: repository.Name, - CredentialsName: repository.Spec.CredentialsName, - Owner: repository.Spec.Owner, - WebhookSecret: webhookSecret, // gh hook secret + Name: repository.Name, + CredentialsName: repository.GetCredentialsName(), + Owner: repository.Spec.Owner, + WebhookSecret: webhookSecret, // gh hook secret + PoolBalancerType: repository.Spec.PoolBalancerType, })) if err != nil { log.V(1).Info(fmt.Sprintf("client.CreateRepository error: %s", err)) @@ -171,7 +196,7 @@ func (r *RepositoryReconciler) createRepository(ctx context.Context, client garm return retValue.Payload, nil } -func (r *RepositoryReconciler) updateRepository(ctx context.Context, client garmClient.RepositoryClient, statusID, webhookSecret, credentialsName string) (params.Repository, error) { +func (r *RepositoryReconciler) updateRepository(ctx context.Context, client garmClient.RepositoryClient, statusID string, updateParams params.UpdateEntityParams) (params.Repository, error) { log := log.FromContext(ctx) log.V(1).Info("update credentials and webhook secret in garm repository") @@ -179,10 +204,7 @@ func (r *RepositoryReconciler) updateRepository(ctx context.Context, client garm retValue, err := client.UpdateRepository( repositories.NewUpdateRepoParams(). WithRepoID(statusID). - WithBody(params.UpdateEntityParams{ - CredentialsName: credentialsName, - WebhookSecret: webhookSecret, // gh hook secret - })) + WithBody(updateParams)) if err != nil { log.V(1).Info(fmt.Sprintf("client.UpdateRepository error: %s", err)) return params.Repository{}, err @@ -191,7 +213,7 @@ func (r *RepositoryReconciler) updateRepository(ctx context.Context, client garm return retValue.Payload, nil } -func (r *RepositoryReconciler) getExistingGarmRepo(ctx context.Context, client garmClient.RepositoryClient, repository *garmoperatorv1alpha1.Repository) (params.Repository, error) { +func (r *RepositoryReconciler) getExistingGarmRepo(ctx context.Context, client garmClient.RepositoryClient, repository *garmoperatorv1beta1.Repository) (params.Repository, error) { log := log.FromContext(ctx) log.WithValues("repository", repository.Name) @@ -212,7 +234,7 @@ func (r *RepositoryReconciler) getExistingGarmRepo(ctx context.Context, client g return params.Repository{}, nil } -func (r *RepositoryReconciler) reconcileDelete(ctx context.Context, client garmClient.RepositoryClient, repository *garmoperatorv1alpha1.Repository) (ctrl.Result, error) { +func (r *RepositoryReconciler) reconcileDelete(ctx context.Context, client garmClient.RepositoryClient, repository *garmoperatorv1beta1.Repository) (ctrl.Result, error) { log := log.FromContext(ctx) log.WithValues("repository", repository.Name) @@ -251,7 +273,19 @@ func (r *RepositoryReconciler) reconcileDelete(ctx context.Context, client garmC return ctrl.Result{}, nil } -func (r *RepositoryReconciler) ensureFinalizer(ctx context.Context, pool *garmoperatorv1alpha1.Repository) error { +func (r *RepositoryReconciler) getCredentialsRef(ctx context.Context, repository *garmoperatorv1beta1.Repository) (*garmoperatorv1beta1.GitHubCredential, error) { + creds := &garmoperatorv1beta1.GitHubCredential{} + err := r.Get(ctx, types.NamespacedName{ + Namespace: repository.Namespace, + Name: repository.Spec.CredentialsRef.Name, + }, creds) + if err != nil { + return creds, err + } + return creds, nil +} + +func (r *RepositoryReconciler) ensureFinalizer(ctx context.Context, pool *garmoperatorv1beta1.Repository) error { if !controllerutil.ContainsFinalizer(pool, key.RepositoryFinalizerName) { controllerutil.AddFinalizer(pool, key.RepositoryFinalizerName) return r.Update(ctx, pool) @@ -259,10 +293,41 @@ func (r *RepositoryReconciler) ensureFinalizer(ctx context.Context, pool *garmop return nil } +func (r *RepositoryReconciler) findReposForCredentials(ctx context.Context, obj client.Object) []reconcile.Request { + credentials, ok := obj.(*garmoperatorv1beta1.GitHubCredential) + if !ok { + return nil + } + + var repos garmoperatorv1beta1.RepositoryList + if err := r.List(ctx, &repos); err != nil { + return nil + } + + var requests []reconcile.Request + for _, repo := range repos.Items { + if repo.GetCredentialsName() == credentials.Name { + requests = append(requests, reconcile.Request{ + NamespacedName: types.NamespacedName{ + Namespace: repo.Namespace, + Name: repo.Name, + }, + }) + } + } + + return requests +} + // SetupWithManager sets up the controller with the Manager. func (r *RepositoryReconciler) SetupWithManager(mgr ctrl.Manager, options controller.Options) error { return ctrl.NewControllerManagedBy(mgr). - For(&garmoperatorv1alpha1.Repository{}). + For(&garmoperatorv1beta1.Repository{}). + Watches( + &garmoperatorv1beta1.GitHubCredential{}, + handler.EnqueueRequestsFromMapFunc(r.findReposForCredentials), + builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}), + ). WithOptions(options). Complete(r) } diff --git a/internal/controller/repository_controller_test.go b/internal/controller/repository_controller_test.go index dd0b933e..2c421c39 100644 --- a/internal/controller/repository_controller_test.go +++ b/internal/controller/repository_controller_test.go @@ -19,7 +19,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - garmoperatorv1alpha1 "github.com/mercedes-benz/garm-operator/api/v1alpha1" + garmoperatorv1beta1 "github.com/mercedes-benz/garm-operator/api/v1beta1" "github.com/mercedes-benz/garm-operator/pkg/client/key" "github.com/mercedes-benz/garm-operator/pkg/client/mock" "github.com/mercedes-benz/garm-operator/pkg/conditions" @@ -35,11 +35,11 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { runtimeObjects []runtime.Object expectGarmRequest func(m *mock.MockRepositoryClientMockRecorder) wantErr bool - expectedObject *garmoperatorv1alpha1.Repository + expectedObject *garmoperatorv1beta1.Repository }{ { name: "repository exist - update", - object: &garmoperatorv1alpha1.Repository{ + object: &garmoperatorv1beta1.Repository{ ObjectMeta: metav1.ObjectMeta{ Name: "existing-repository", Namespace: "default", @@ -47,15 +47,19 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { key.RepositoryFinalizerName, }, }, - Spec: garmoperatorv1alpha1.RepositorySpec{ - CredentialsName: "foobar", - Owner: "test-repo", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.RepositorySpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + Owner: "test-repo", + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.RepositoryStatus{ + Status: garmoperatorv1beta1.RepositoryStatus{ ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", }, }, @@ -69,8 +73,23 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { "webhookSecret": []byte("foobar"), }, }, + &garmoperatorv1beta1.GitHubCredential{ + ObjectMeta: metav1.ObjectMeta{ + Name: "github-creds", + Namespace: "default", + }, + Spec: garmoperatorv1beta1.GitHubCredentialSpec{ + Description: "github-creds", + EndpointRef: corev1.TypedLocalObjectReference{}, + AuthType: "pat", + SecretRef: garmoperatorv1beta1.SecretRef{ + Name: "github-secret", + Key: "token", + }, + }, + }, }, - expectedObject: &garmoperatorv1alpha1.Repository{ + expectedObject: &garmoperatorv1beta1.Repository{ ObjectMeta: metav1.ObjectMeta{ Name: "existing-repository", Namespace: "default", @@ -78,15 +97,19 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { key.RepositoryFinalizerName, }, }, - Spec: garmoperatorv1alpha1.RepositorySpec{ - CredentialsName: "foobar", - Owner: "test-repo", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.RepositorySpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + Owner: "test-repo", + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.RepositoryStatus{ + Status: garmoperatorv1beta1.RepositoryStatus{ ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", Conditions: []metav1.Condition{ { @@ -96,6 +119,13 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { LastTransitionTime: metav1.NewTime(time.Now()), Message: "Pool Manager is not running", }, + { + Type: string(conditions.CredentialsReference), + Reason: string(conditions.FetchingCredentialsRefSuccessReason), + Status: metav1.ConditionTrue, + Message: "", + LastTransitionTime: metav1.NewTime(time.Now()), + }, { Type: string(conditions.PoolManager), Reason: string(conditions.PoolManagerFailureReason), @@ -119,21 +149,21 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", Name: "existing-repository", Owner: "test-repo", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", }, }}, nil) m.UpdateRepository(repositories.NewUpdateRepoParams(). WithRepoID("e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e"). WithBody(params.UpdateEntityParams{ - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", })).Return(&repositories.UpdateRepoOK{ Payload: params.Repository{ ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", Name: "existing-repository", Owner: "test-repo", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", }, }, nil) @@ -141,7 +171,7 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { }, { name: "repository exist but spec has changed - update", - object: &garmoperatorv1alpha1.Repository{ + object: &garmoperatorv1beta1.Repository{ ObjectMeta: metav1.ObjectMeta{ Name: "existing-repository", Namespace: "default", @@ -149,15 +179,19 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { key.RepositoryFinalizerName, }, }, - Spec: garmoperatorv1alpha1.RepositorySpec{ - CredentialsName: "has-changed", - Owner: "test-repo", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.RepositorySpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "has-changed", + }, + Owner: "test-repo", + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.RepositoryStatus{ + Status: garmoperatorv1beta1.RepositoryStatus{ ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", }, }, @@ -171,8 +205,23 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { "webhookSecret": []byte("has-changed"), }, }, + &garmoperatorv1beta1.GitHubCredential{ + ObjectMeta: metav1.ObjectMeta{ + Name: "has-changed", + Namespace: "default", + }, + Spec: garmoperatorv1beta1.GitHubCredentialSpec{ + Description: "github-creds", + EndpointRef: corev1.TypedLocalObjectReference{}, + AuthType: "pat", + SecretRef: garmoperatorv1beta1.SecretRef{ + Name: "github-secret", + Key: "token", + }, + }, + }, }, - expectedObject: &garmoperatorv1alpha1.Repository{ + expectedObject: &garmoperatorv1beta1.Repository{ ObjectMeta: metav1.ObjectMeta{ Name: "existing-repository", Namespace: "default", @@ -180,15 +229,19 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { key.RepositoryFinalizerName, }, }, - Spec: garmoperatorv1alpha1.RepositorySpec{ - CredentialsName: "has-changed", - Owner: "test-repo", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.RepositorySpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "has-changed", + }, + Owner: "test-repo", + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.RepositoryStatus{ + Status: garmoperatorv1beta1.RepositoryStatus{ ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", Conditions: []metav1.Condition{ { @@ -198,6 +251,13 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { LastTransitionTime: metav1.NewTime(time.Now()), Message: "Pool Manager is not running", }, + { + Type: string(conditions.CredentialsReference), + Reason: string(conditions.FetchingCredentialsRefSuccessReason), + Status: metav1.ConditionTrue, + Message: "", + LastTransitionTime: metav1.NewTime(time.Now()), + }, { Type: string(conditions.PoolManager), Reason: string(conditions.PoolManagerFailureReason), @@ -221,7 +281,7 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", Name: "existing-repository", Owner: "test-repo", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", }, }}, nil) @@ -243,7 +303,7 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { }, { name: "repository exist but pool status has changed - update", - object: &garmoperatorv1alpha1.Repository{ + object: &garmoperatorv1beta1.Repository{ ObjectMeta: metav1.ObjectMeta{ Name: "existing-repository", Namespace: "default", @@ -251,15 +311,19 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { key.RepositoryFinalizerName, }, }, - Spec: garmoperatorv1alpha1.RepositorySpec{ - CredentialsName: "foobar", - Owner: "test-repo", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.RepositorySpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + Owner: "test-repo", + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.RepositoryStatus{ + Status: garmoperatorv1beta1.RepositoryStatus{ ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", }, }, @@ -273,8 +337,23 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { "webhookSecret": []byte("foobar"), }, }, + &garmoperatorv1beta1.GitHubCredential{ + ObjectMeta: metav1.ObjectMeta{ + Name: "github-creds", + Namespace: "default", + }, + Spec: garmoperatorv1beta1.GitHubCredentialSpec{ + Description: "github-creds", + EndpointRef: corev1.TypedLocalObjectReference{}, + AuthType: "pat", + SecretRef: garmoperatorv1beta1.SecretRef{ + Name: "github-secret", + Key: "token", + }, + }, + }, }, - expectedObject: &garmoperatorv1alpha1.Repository{ + expectedObject: &garmoperatorv1beta1.Repository{ ObjectMeta: metav1.ObjectMeta{ Name: "existing-repository", Namespace: "default", @@ -282,15 +361,19 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { key.RepositoryFinalizerName, }, }, - Spec: garmoperatorv1alpha1.RepositorySpec{ - CredentialsName: "foobar", - Owner: "test-repo", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.RepositorySpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + Owner: "test-repo", + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.RepositoryStatus{ + Status: garmoperatorv1beta1.RepositoryStatus{ ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", Conditions: []metav1.Condition{ { @@ -300,6 +383,13 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { LastTransitionTime: metav1.NewTime(time.Now()), Message: "Pool Manager is not running", }, + { + Type: string(conditions.CredentialsReference), + Reason: string(conditions.FetchingCredentialsRefSuccessReason), + Status: metav1.ConditionTrue, + Message: "", + LastTransitionTime: metav1.NewTime(time.Now()), + }, { Type: string(conditions.PoolManager), Reason: string(conditions.PoolManagerFailureReason), @@ -323,21 +413,21 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", Name: "existing-repository", Owner: "test-repo", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", }, }}, nil) m.UpdateRepository(repositories.NewUpdateRepoParams(). WithRepoID("e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e"). WithBody(params.UpdateEntityParams{ - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", })).Return(&repositories.UpdateRepoOK{ Payload: params.Repository{ ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", Name: "existing-repository", Owner: "test-repo", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", PoolManagerStatus: params.PoolManagerStatus{ IsRunning: false, @@ -349,15 +439,19 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { }, { name: "repository does not exist - create and update", - object: &garmoperatorv1alpha1.Repository{ + object: &garmoperatorv1beta1.Repository{ ObjectMeta: metav1.ObjectMeta{ Name: "new-repository", Namespace: "default", }, - Spec: garmoperatorv1alpha1.RepositorySpec{ - CredentialsName: "foobar", - Owner: "test-repo", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.RepositorySpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + Owner: "test-repo", + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, @@ -373,8 +467,23 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { "webhookSecret": []byte("foobar"), }, }, + &garmoperatorv1beta1.GitHubCredential{ + ObjectMeta: metav1.ObjectMeta{ + Name: "github-creds", + Namespace: "default", + }, + Spec: garmoperatorv1beta1.GitHubCredentialSpec{ + Description: "github-creds", + EndpointRef: corev1.TypedLocalObjectReference{}, + AuthType: "pat", + SecretRef: garmoperatorv1beta1.SecretRef{ + Name: "github-secret", + Key: "token", + }, + }, + }, }, - expectedObject: &garmoperatorv1alpha1.Repository{ + expectedObject: &garmoperatorv1beta1.Repository{ ObjectMeta: metav1.ObjectMeta{ Name: "new-repository", Namespace: "default", @@ -382,15 +491,19 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { key.RepositoryFinalizerName, }, }, - Spec: garmoperatorv1alpha1.RepositorySpec{ - CredentialsName: "foobar", - Owner: "test-repo", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.RepositorySpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + Owner: "test-repo", + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.RepositoryStatus{ + Status: garmoperatorv1beta1.RepositoryStatus{ ID: "9e0da3cb-130b-428d-aa8a-e314d955060e", Conditions: []metav1.Condition{ { @@ -400,6 +513,13 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { LastTransitionTime: metav1.NewTime(time.Now()), Message: "Pool Manager is not running", }, + { + Type: string(conditions.CredentialsReference), + Reason: string(conditions.FetchingCredentialsRefSuccessReason), + Status: metav1.ConditionTrue, + Message: "", + LastTransitionTime: metav1.NewTime(time.Now()), + }, { Type: string(conditions.PoolManager), Reason: string(conditions.PoolManagerFailureReason), @@ -423,14 +543,14 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", Name: "existing-repository", Owner: "test-repo", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", }, }}, nil) m.CreateRepository(repositories.NewCreateRepoParams().WithBody( params.CreateRepoParams{ Name: "new-repository", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", Owner: "test-repo", })).Return(&repositories.CreateRepoOK{ @@ -438,21 +558,21 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { Name: "new-repository", ID: "9e0da3cb-130b-428d-aa8a-e314d955060e", Owner: "test-repo", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", }, }, nil) m.UpdateRepository(repositories.NewUpdateRepoParams(). WithRepoID("9e0da3cb-130b-428d-aa8a-e314d955060e"). WithBody(params.UpdateEntityParams{ - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", })).Return(&repositories.UpdateRepoOK{ Payload: params.Repository{ ID: "9e0da3cb-130b-428d-aa8a-e314d955060e", Name: "new-repository", Owner: "test-repo", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", }, }, nil) @@ -460,15 +580,19 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { }, { name: "repository already exist in garm - update", - object: &garmoperatorv1alpha1.Repository{ + object: &garmoperatorv1beta1.Repository{ ObjectMeta: metav1.ObjectMeta{ Name: "new-repository", Namespace: "default", }, - Spec: garmoperatorv1alpha1.RepositorySpec{ - CredentialsName: "foobar", - Owner: "test-repo", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.RepositorySpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + Owner: "test-repo", + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, @@ -484,8 +608,23 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { "webhookSecret": []byte("foobar"), }, }, + &garmoperatorv1beta1.GitHubCredential{ + ObjectMeta: metav1.ObjectMeta{ + Name: "github-creds", + Namespace: "default", + }, + Spec: garmoperatorv1beta1.GitHubCredentialSpec{ + Description: "github-creds", + EndpointRef: corev1.TypedLocalObjectReference{}, + AuthType: "pat", + SecretRef: garmoperatorv1beta1.SecretRef{ + Name: "github-secret", + Key: "token", + }, + }, + }, }, - expectedObject: &garmoperatorv1alpha1.Repository{ + expectedObject: &garmoperatorv1beta1.Repository{ ObjectMeta: metav1.ObjectMeta{ Name: "new-repository", Namespace: "default", @@ -493,15 +632,19 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { key.RepositoryFinalizerName, }, }, - Spec: garmoperatorv1alpha1.RepositorySpec{ - CredentialsName: "foobar", - Owner: "test-repo", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.RepositorySpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + Owner: "test-repo", + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.RepositoryStatus{ + Status: garmoperatorv1beta1.RepositoryStatus{ ID: "e1dbf9a6-a9f6-4594-a5ac-12345", Conditions: []metav1.Condition{ { @@ -511,6 +654,13 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { LastTransitionTime: metav1.NewTime(time.Now()), Message: "Pool Manager is not running", }, + { + Type: string(conditions.CredentialsReference), + Reason: string(conditions.FetchingCredentialsRefSuccessReason), + Status: metav1.ConditionTrue, + Message: "", + LastTransitionTime: metav1.NewTime(time.Now()), + }, { Type: string(conditions.PoolManager), Reason: string(conditions.PoolManagerFailureReason), @@ -540,14 +690,14 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { m.UpdateRepository(repositories.NewUpdateRepoParams(). WithRepoID("e1dbf9a6-a9f6-4594-a5ac-12345"). WithBody(params.UpdateEntityParams{ - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", })).Return(&repositories.UpdateRepoOK{ Payload: params.Repository{ ID: "e1dbf9a6-a9f6-4594-a5ac-12345", Name: "new-repository", Owner: "test-repo", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", }, }, nil) @@ -555,7 +705,7 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { }, { name: "repository does not exist in garm - create and update", - object: &garmoperatorv1alpha1.Repository{ + object: &garmoperatorv1beta1.Repository{ ObjectMeta: metav1.ObjectMeta{ Name: "existing-repository", Namespace: "default", @@ -563,15 +713,19 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { key.RepositoryFinalizerName, }, }, - Spec: garmoperatorv1alpha1.RepositorySpec{ - CredentialsName: "foobar", - Owner: "test-repo", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.RepositorySpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + Owner: "test-repo", + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.RepositoryStatus{ + Status: garmoperatorv1beta1.RepositoryStatus{ ID: "e1dbf9a6-a9f6-4594-a5ac-ae78a8f27a3e", }, }, @@ -585,8 +739,23 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { "webhookSecret": []byte("foobar"), }, }, + &garmoperatorv1beta1.GitHubCredential{ + ObjectMeta: metav1.ObjectMeta{ + Name: "github-creds", + Namespace: "default", + }, + Spec: garmoperatorv1beta1.GitHubCredentialSpec{ + Description: "github-creds", + EndpointRef: corev1.TypedLocalObjectReference{}, + AuthType: "pat", + SecretRef: garmoperatorv1beta1.SecretRef{ + Name: "github-secret", + Key: "token", + }, + }, + }, }, - expectedObject: &garmoperatorv1alpha1.Repository{ + expectedObject: &garmoperatorv1beta1.Repository{ ObjectMeta: metav1.ObjectMeta{ Name: "existing-repository", Namespace: "default", @@ -594,15 +763,19 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { key.RepositoryFinalizerName, }, }, - Spec: garmoperatorv1alpha1.RepositorySpec{ - CredentialsName: "foobar", - Owner: "test-repo", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.RepositorySpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + Owner: "test-repo", + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.RepositoryStatus{ + Status: garmoperatorv1beta1.RepositoryStatus{ ID: "9e0da3cb-130b-428d-aa8a-e314d955060e", Conditions: []metav1.Condition{ { @@ -612,6 +785,13 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { LastTransitionTime: metav1.NewTime(time.Now()), Message: "Pool Manager is not running", }, + { + Type: string(conditions.CredentialsReference), + Reason: string(conditions.FetchingCredentialsRefSuccessReason), + Status: metav1.ConditionTrue, + Message: "", + LastTransitionTime: metav1.NewTime(time.Now()), + }, { Type: string(conditions.PoolManager), Status: metav1.ConditionFalse, @@ -636,7 +816,7 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { m.CreateRepository(repositories.NewCreateRepoParams().WithBody( params.CreateRepoParams{ Name: "existing-repository", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", Owner: "test-repo", })).Return(&repositories.CreateRepoOK{ @@ -644,21 +824,21 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { Name: "existing-repository", ID: "9e0da3cb-130b-428d-aa8a-e314d955060e", Owner: "test-repo", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", }, }, nil) m.UpdateRepository(repositories.NewUpdateRepoParams(). WithRepoID("9e0da3cb-130b-428d-aa8a-e314d955060e"). WithBody(params.UpdateEntityParams{ - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", })).Return(&repositories.UpdateRepoOK{ Payload: params.Repository{ ID: "9e0da3cb-130b-428d-aa8a-e314d955060e", Name: "existing-repository", Owner: "test-repo", - CredentialsName: "foobar", + CredentialsName: "github-creds", WebhookSecret: "foobar", }, }, nil) @@ -666,7 +846,7 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { }, { name: "secret ref not found condition", - object: &garmoperatorv1alpha1.Repository{ + object: &garmoperatorv1beta1.Repository{ ObjectMeta: metav1.ObjectMeta{ Name: "new-repository", Namespace: "default", @@ -674,17 +854,21 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { key.RepositoryFinalizerName, }, }, - Spec: garmoperatorv1alpha1.RepositorySpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.RepositorySpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.RepositoryStatus{}, + Status: garmoperatorv1beta1.RepositoryStatus{}, }, runtimeObjects: []runtime.Object{}, - expectedObject: &garmoperatorv1alpha1.Repository{ + expectedObject: &garmoperatorv1beta1.Repository{ ObjectMeta: metav1.ObjectMeta{ Name: "new-repository", Namespace: "default", @@ -692,14 +876,18 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { key.RepositoryFinalizerName, }, }, - Spec: garmoperatorv1alpha1.RepositorySpec{ - CredentialsName: "foobar", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.RepositorySpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.RepositoryStatus{ + Status: garmoperatorv1beta1.RepositoryStatus{ Conditions: []metav1.Condition{ { Type: string(conditions.ReadyCondition), @@ -708,11 +896,18 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { Message: "secrets \"my-webhook-secret\" not found", LastTransitionTime: metav1.NewTime(time.Now()), }, + { + Type: string(conditions.CredentialsReference), + Reason: string(conditions.UnknownReason), + Status: metav1.ConditionUnknown, + Message: conditions.CredentialsNotReconciledYetMsg, + LastTransitionTime: metav1.NewTime(time.Now()), + }, { Type: string(conditions.PoolManager), Reason: string(conditions.UnknownReason), Status: metav1.ConditionUnknown, - Message: "GARM server not reconciled yet", + Message: conditions.GarmServerNotReconciledYetMsg, LastTransitionTime: metav1.NewTime(time.Now()), }, { @@ -732,7 +927,7 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { schemeBuilder := runtime.SchemeBuilder{ - garmoperatorv1alpha1.AddToScheme, + garmoperatorv1beta1.AddToScheme, } err := schemeBuilder.AddToScheme(scheme.Scheme) @@ -741,7 +936,7 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { } runtimeObjects := []runtime.Object{tt.object} runtimeObjects = append(runtimeObjects, tt.runtimeObjects...) - client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(runtimeObjects...).WithStatusSubresource(&garmoperatorv1alpha1.Repository{}).Build() + client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(runtimeObjects...).WithStatusSubresource(&garmoperatorv1beta1.Repository{}).Build() // create a fake reconciler reconciler := &RepositoryReconciler{ @@ -749,7 +944,7 @@ func TestRepositoryReconciler_reconcileNormal(t *testing.T) { Recorder: record.NewFakeRecorder(3), } - repository := tt.object.DeepCopyObject().(*garmoperatorv1alpha1.Repository) + repository := tt.object.DeepCopyObject().(*garmoperatorv1beta1.Repository) mockRepository := mock.NewMockRepositoryClient(mockCtrl) tt.expectGarmRequest(mockRepository.EXPECT()) @@ -787,11 +982,11 @@ func TestRepositoryReconciler_reconcileDelete(t *testing.T) { runtimeObjects []runtime.Object expectGarmRequest func(m *mock.MockRepositoryClientMockRecorder) wantErr bool - expectedObject *garmoperatorv1alpha1.Repository + expectedObject *garmoperatorv1beta1.Repository }{ { name: "delete repository", - object: &garmoperatorv1alpha1.Repository{ + object: &garmoperatorv1beta1.Repository{ ObjectMeta: metav1.ObjectMeta{ Name: "delete-repository", Namespace: "default", @@ -799,14 +994,18 @@ func TestRepositoryReconciler_reconcileDelete(t *testing.T) { key.RepositoryFinalizerName, }, }, - Spec: garmoperatorv1alpha1.RepositorySpec{ - CredentialsName: "totally-insecure", - WebhookSecretRef: garmoperatorv1alpha1.SecretRef{ + Spec: garmoperatorv1beta1.RepositorySpec{ + CredentialsRef: corev1.TypedLocalObjectReference{ + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: "GitHubCredential", + Name: "github-creds", + }, + WebhookSecretRef: garmoperatorv1beta1.SecretRef{ Name: "my-webhook-secret", Key: "webhookSecret", }, }, - Status: garmoperatorv1alpha1.RepositoryStatus{ + Status: garmoperatorv1beta1.RepositoryStatus{ ID: "e1dbf9a6-a9f6-4594-a5ac-12345", }, }, @@ -820,6 +1019,21 @@ func TestRepositoryReconciler_reconcileDelete(t *testing.T) { "webhookSecret": []byte("foobar"), }, }, + &garmoperatorv1beta1.GitHubCredential{ + ObjectMeta: metav1.ObjectMeta{ + Name: "github-creds", + Namespace: "default", + }, + Spec: garmoperatorv1beta1.GitHubCredentialSpec{ + Description: "github-creds", + EndpointRef: corev1.TypedLocalObjectReference{}, + AuthType: "pat", + SecretRef: garmoperatorv1beta1.SecretRef{ + Name: "github-secret", + Key: "token", + }, + }, + }, }, expectGarmRequest: func(m *mock.MockRepositoryClientMockRecorder) { m.DeleteRepository( @@ -832,7 +1046,7 @@ func TestRepositoryReconciler_reconcileDelete(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { schemeBuilder := runtime.SchemeBuilder{ - garmoperatorv1alpha1.AddToScheme, + garmoperatorv1beta1.AddToScheme, } err := schemeBuilder.AddToScheme(scheme.Scheme) @@ -841,7 +1055,7 @@ func TestRepositoryReconciler_reconcileDelete(t *testing.T) { } runtimeObjects := []runtime.Object{tt.object} runtimeObjects = append(runtimeObjects, tt.runtimeObjects...) - client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(runtimeObjects...).WithStatusSubresource(&garmoperatorv1alpha1.Repository{}).Build() + client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(runtimeObjects...).WithStatusSubresource(&garmoperatorv1beta1.Repository{}).Build() // create a fake reconciler reconciler := &RepositoryReconciler{ @@ -849,7 +1063,7 @@ func TestRepositoryReconciler_reconcileDelete(t *testing.T) { Recorder: record.NewFakeRecorder(3), } - repository := tt.object.DeepCopyObject().(*garmoperatorv1alpha1.Repository) + repository := tt.object.DeepCopyObject().(*garmoperatorv1beta1.Repository) mockRepository := mock.NewMockRepositoryClient(mockCtrl) tt.expectGarmRequest(mockRepository.EXPECT()) diff --git a/internal/controller/runner_controller.go b/internal/controller/runner_controller.go index b8c13114..c423087b 100644 --- a/internal/controller/runner_controller.go +++ b/internal/controller/runner_controller.go @@ -24,7 +24,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/source" - garmoperatorv1alpha1 "github.com/mercedes-benz/garm-operator/api/v1alpha1" + garmoperatorv1beta1 "github.com/mercedes-benz/garm-operator/api/v1beta1" garmClient "github.com/mercedes-benz/garm-operator/pkg/client" "github.com/mercedes-benz/garm-operator/pkg/client/key" "github.com/mercedes-benz/garm-operator/pkg/config" @@ -56,7 +56,7 @@ func (r *RunnerReconciler) reconcile(ctx context.Context, req ctrl.Request, inst } // only create RunnerCR if it does not yet exist - runner := &garmoperatorv1alpha1.Runner{} + runner := &garmoperatorv1beta1.Runner{} if err := r.Get(ctx, types.NamespacedName{Namespace: req.Namespace, Name: strings.ToLower(req.Name)}, runner); err != nil { return r.handleCreateRunnerCR(ctx, req, err, garmRunner) } @@ -88,12 +88,12 @@ func (r *RunnerReconciler) createRunnerCR(ctx context.Context, garmRunner *param log := log.FromContext(ctx) log.Info("Creating Runner", "Runner", garmRunner.Name) - runnerObj := &garmoperatorv1alpha1.Runner{ + runnerObj := &garmoperatorv1beta1.Runner{ ObjectMeta: metav1.ObjectMeta{ Name: strings.ToLower(garmRunner.Name), Namespace: namespace, }, - Spec: garmoperatorv1alpha1.RunnerSpec{}, + Spec: garmoperatorv1beta1.RunnerSpec{}, } if err := r.Create(ctx, runnerObj); err != nil { @@ -137,7 +137,7 @@ func (r *RunnerReconciler) getGarmRunnerInstanceByName(client garmClient.Instanc return &filteredInstances[0], nil } -func (r *RunnerReconciler) ensureFinalizer(ctx context.Context, runner *garmoperatorv1alpha1.Runner) error { +func (r *RunnerReconciler) ensureFinalizer(ctx context.Context, runner *garmoperatorv1beta1.Runner) error { if !controllerutil.ContainsFinalizer(runner, key.RunnerFinalizerName) { controllerutil.AddFinalizer(runner, key.RunnerFinalizerName) return r.Update(ctx, runner) @@ -145,7 +145,7 @@ func (r *RunnerReconciler) ensureFinalizer(ctx context.Context, runner *garmoper return nil } -func (r *RunnerReconciler) updateRunnerStatus(ctx context.Context, runner *garmoperatorv1alpha1.Runner, garmRunner *params.Instance) (ctrl.Result, error) { +func (r *RunnerReconciler) updateRunnerStatus(ctx context.Context, runner *garmoperatorv1beta1.Runner, garmRunner *params.Instance) (ctrl.Result, error) { if garmRunner == nil { return ctrl.Result{}, nil } @@ -158,10 +158,10 @@ func (r *RunnerReconciler) updateRunnerStatus(ctx context.Context, runner *garmo log.Info("Update runner status...") poolName := garmRunner.PoolID - pools := &garmoperatorv1alpha1.PoolList{} + pools := &garmoperatorv1beta1.PoolList{} err := r.List(ctx, pools) if err == nil { - filteredPools := filter.Match(pools.Items, garmoperatorv1alpha1.MatchesID(garmRunner.PoolID)) + filteredPools := filter.Match(pools.Items, garmoperatorv1beta1.MatchesID(garmRunner.PoolID)) if len(filteredPools) > 0 { poolName = filteredPools[0].Name @@ -194,7 +194,7 @@ func (r *RunnerReconciler) updateRunnerStatus(ctx context.Context, runner *garmo // SetupWithManager sets up the controller with the Manager. func (r *RunnerReconciler) SetupWithManager(mgr ctrl.Manager, eventChan chan event.GenericEvent, options controller.Options) error { c, err := ctrl.NewControllerManagedBy(mgr). - For(&garmoperatorv1alpha1.Runner{}). + For(&garmoperatorv1beta1.Runner{}). WithOptions(options). Build(r) if err != nil { @@ -248,7 +248,7 @@ func (r *RunnerReconciler) EnqueueRunnerInstances(ctx context.Context, instanceC func enqeueRunnerEvents(garmRunnerInstances params.Instances, eventChan chan event.GenericEvent) { for _, runner := range garmRunnerInstances { - runnerObj := garmoperatorv1alpha1.Runner{ + runnerObj := garmoperatorv1beta1.Runner{ ObjectMeta: metav1.ObjectMeta{ Name: strings.ToLower(runner.Name), Namespace: config.Config.Operator.WatchNamespace, @@ -264,7 +264,7 @@ func enqeueRunnerEvents(garmRunnerInstances params.Instances, eventChan chan eve } func (r *RunnerReconciler) cleanUpNotMatchingRunnerCRs(ctx context.Context, garmRunnerInstances params.Instances) error { - runnerCRList := &garmoperatorv1alpha1.RunnerList{} + runnerCRList := &garmoperatorv1beta1.RunnerList{} err := r.List(ctx, runnerCRList) if err != nil { return err @@ -284,7 +284,7 @@ func (r *RunnerReconciler) cleanUpNotMatchingRunnerCRs(ctx context.Context, garm log.Log.V(1).Info("Deleting runners: ", "Runners", runnersToDelete) for _, runnerName := range runnersToDelete { - runner := &garmoperatorv1alpha1.Runner{} + runner := &garmoperatorv1beta1.Runner{} err := r.Get(ctx, types.NamespacedName{Namespace: config.Config.Operator.WatchNamespace, Name: runnerName}, runner) if err != nil { return err @@ -313,8 +313,8 @@ func (r *RunnerReconciler) cleanUpNotMatchingRunnerCRs(ctx context.Context, garm return nil } -func (r *RunnerReconciler) fetchPools(ctx context.Context) (*garmoperatorv1alpha1.PoolList, error) { - pools := &garmoperatorv1alpha1.PoolList{} +func (r *RunnerReconciler) fetchPools(ctx context.Context) (*garmoperatorv1beta1.PoolList, error) { + pools := &garmoperatorv1beta1.PoolList{} err := r.List(ctx, pools) if err != nil { return nil, err @@ -322,7 +322,7 @@ func (r *RunnerReconciler) fetchPools(ctx context.Context) (*garmoperatorv1alpha return pools, nil } -func (r *RunnerReconciler) fetchRunnerInstancesByNamespacedPools(instanceClient garmClient.InstanceClient, pools *garmoperatorv1alpha1.PoolList) (params.Instances, error) { +func (r *RunnerReconciler) fetchRunnerInstancesByNamespacedPools(instanceClient garmClient.InstanceClient, pools *garmoperatorv1beta1.PoolList) (params.Instances, error) { garmRunnerInstances := params.Instances{} for _, p := range pools.Items { if p.Status.ID == "" { @@ -337,9 +337,9 @@ func (r *RunnerReconciler) fetchRunnerInstancesByNamespacedPools(instanceClient return garmRunnerInstances, nil } -func (r *RunnerReconciler) runnerSpecsEqual(runner garmoperatorv1alpha1.Runner, garmRunner *params.Instance) bool { +func (r *RunnerReconciler) runnerSpecsEqual(runner garmoperatorv1beta1.Runner, garmRunner *params.Instance) bool { runner.Status.PoolID = "" - tmpRunnerStatus := garmoperatorv1alpha1.RunnerStatus{ + tmpRunnerStatus := garmoperatorv1beta1.RunnerStatus{ ID: garmRunner.ID, ProviderID: garmRunner.ProviderID, AgentID: garmRunner.AgentID, diff --git a/internal/controller/runner_controller_test.go b/internal/controller/runner_controller_test.go index 5f15ca80..30e8a781 100644 --- a/internal/controller/runner_controller_test.go +++ b/internal/controller/runner_controller_test.go @@ -24,7 +24,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/event" - garmoperatorv1alpha1 "github.com/mercedes-benz/garm-operator/api/v1alpha1" + garmoperatorv1beta1 "github.com/mercedes-benz/garm-operator/api/v1beta1" "github.com/mercedes-benz/garm-operator/pkg/client/key" "github.com/mercedes-benz/garm-operator/pkg/client/mock" "github.com/mercedes-benz/garm-operator/pkg/config" @@ -40,7 +40,7 @@ func TestRunnerReconciler_reconcileCreate(t *testing.T) { runtimeObjects []runtime.Object expectGarmRequest func(m *mock.MockInstanceClientMockRecorder) wantErr bool - expectedObject *garmoperatorv1alpha1.Runner + expectedObject *garmoperatorv1beta1.Runner }{ { name: "Create Runner CR", @@ -69,7 +69,7 @@ func TestRunnerReconciler_reconcileCreate(t *testing.T) { m.ListInstances(instances.NewListInstancesParams()).Return(&instances.ListInstancesOK{Payload: response}, nil) }, wantErr: false, - expectedObject: &garmoperatorv1alpha1.Runner{ + expectedObject: &garmoperatorv1beta1.Runner{ ObjectMeta: metav1.ObjectMeta{ Name: "road-runner-k8s-fy5snjcv5dzn", Namespace: "runner", @@ -77,8 +77,8 @@ func TestRunnerReconciler_reconcileCreate(t *testing.T) { key.RunnerFinalizerName, }, }, - Spec: garmoperatorv1alpha1.RunnerSpec{}, - Status: garmoperatorv1alpha1.RunnerStatus{ + Spec: garmoperatorv1beta1.RunnerSpec{}, + Status: garmoperatorv1beta1.RunnerStatus{ Name: "road-runner-k8s-FY5snJcv5dzn", AgentID: 120, ID: "8215f6c6-486e-4893-84df-3231b185a148", @@ -96,7 +96,7 @@ func TestRunnerReconciler_reconcileCreate(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { schemeBuilder := runtime.SchemeBuilder{ - garmoperatorv1alpha1.AddToScheme, + garmoperatorv1beta1.AddToScheme, } err := schemeBuilder.AddToScheme(scheme.Scheme) @@ -105,7 +105,7 @@ func TestRunnerReconciler_reconcileCreate(t *testing.T) { } var runtimeObjects []runtime.Object runtimeObjects = append(runtimeObjects, tt.runtimeObjects...) - client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(runtimeObjects...).WithStatusSubresource(&garmoperatorv1alpha1.Runner{}).Build() + client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(runtimeObjects...).WithStatusSubresource(&garmoperatorv1beta1.Runner{}).Build() // create a fake reconciler reconciler := &RunnerReconciler{ @@ -122,7 +122,7 @@ func TestRunnerReconciler_reconcileCreate(t *testing.T) { return } - runner := &garmoperatorv1alpha1.Runner{} + runner := &garmoperatorv1beta1.Runner{} err = client.Get(context.Background(), types.NamespacedName{Namespace: tt.req.Namespace, Name: strings.ToLower(tt.req.Name)}, runner) if (err != nil) != tt.wantErr { t.Errorf("RunnerReconciler.reconcile() error = %v, wantErr %v", err, tt.wantErr) @@ -153,7 +153,7 @@ func TestRunnerReconciler_reconcileDeleteGarmRunner(t *testing.T) { runtimeObjects []runtime.Object expectGarmRequest func(m *mock.MockInstanceClientMockRecorder) wantErr bool - expectedObject *garmoperatorv1alpha1.Runner + expectedObject *garmoperatorv1beta1.Runner }{ { name: "Delete Runner in Garm DB, when Runner CR is marked with deletion timestamp", @@ -164,10 +164,10 @@ func TestRunnerReconciler_reconcileDeleteGarmRunner(t *testing.T) { }, }, runtimeObjects: []runtime.Object{ - &garmoperatorv1alpha1.Runner{ + &garmoperatorv1beta1.Runner{ TypeMeta: metav1.TypeMeta{ Kind: "Runner", - APIVersion: garmoperatorv1alpha1.GroupVersion.Group + "/" + garmoperatorv1alpha1.GroupVersion.Version, + APIVersion: garmoperatorv1beta1.GroupVersion.Group + "/" + garmoperatorv1beta1.GroupVersion.Version, }, ObjectMeta: metav1.ObjectMeta{ Name: "road-runner-k8s-fy5snjcv5dzn", @@ -177,8 +177,8 @@ func TestRunnerReconciler_reconcileDeleteGarmRunner(t *testing.T) { }, DeletionTimestamp: &now, }, - Spec: garmoperatorv1alpha1.RunnerSpec{}, - Status: garmoperatorv1alpha1.RunnerStatus{ + Spec: garmoperatorv1beta1.RunnerSpec{}, + Status: garmoperatorv1beta1.RunnerStatus{ Name: "road-runner-k8s-FY5snJcv5dzn", AgentID: 120, ID: "8215f6c6-486e-4893-84df-3231b185a148", @@ -211,7 +211,7 @@ func TestRunnerReconciler_reconcileDeleteGarmRunner(t *testing.T) { m.DeleteInstance(instances.NewDeleteInstanceParams().WithInstanceName("road-runner-k8s-FY5snJcv5dzn")).Return(nil) }, wantErr: false, - expectedObject: &garmoperatorv1alpha1.Runner{ + expectedObject: &garmoperatorv1beta1.Runner{ ObjectMeta: metav1.ObjectMeta{ Name: "road-runner-k8s-fy5snjcv5dzn", Namespace: "runner", @@ -220,8 +220,8 @@ func TestRunnerReconciler_reconcileDeleteGarmRunner(t *testing.T) { }, DeletionTimestamp: &now, }, - Spec: garmoperatorv1alpha1.RunnerSpec{}, - Status: garmoperatorv1alpha1.RunnerStatus{ + Spec: garmoperatorv1beta1.RunnerSpec{}, + Status: garmoperatorv1beta1.RunnerStatus{ Name: "road-runner-k8s-FY5snJcv5dzn", AgentID: 120, ID: "8215f6c6-486e-4893-84df-3231b185a148", @@ -239,7 +239,7 @@ func TestRunnerReconciler_reconcileDeleteGarmRunner(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { schemeBuilder := runtime.SchemeBuilder{ - garmoperatorv1alpha1.AddToScheme, + garmoperatorv1beta1.AddToScheme, } err := schemeBuilder.AddToScheme(scheme.Scheme) @@ -248,7 +248,7 @@ func TestRunnerReconciler_reconcileDeleteGarmRunner(t *testing.T) { } var runtimeObjects []runtime.Object runtimeObjects = append(runtimeObjects, tt.runtimeObjects...) - client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(runtimeObjects...).WithStatusSubresource(&garmoperatorv1alpha1.Runner{}).Build() + client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(runtimeObjects...).WithStatusSubresource(&garmoperatorv1beta1.Runner{}).Build() // create a fake reconciler reconciler := &RunnerReconciler{ @@ -265,7 +265,7 @@ func TestRunnerReconciler_reconcileDeleteGarmRunner(t *testing.T) { return } - runner := &garmoperatorv1alpha1.Runner{} + runner := &garmoperatorv1beta1.Runner{} err = client.Get(context.Background(), types.NamespacedName{Namespace: tt.req.Namespace, Name: strings.ToLower(tt.req.Name)}, runner) if (err != nil) != tt.wantErr { t.Errorf("RunnerReconciler.reconcile() error = %v, wantErr %v", err, tt.wantErr) @@ -298,10 +298,10 @@ func TestRunnerReconciler_reconcileDeleteCR(t *testing.T) { }, }, runtimeObjects: []runtime.Object{ - &garmoperatorv1alpha1.Runner{ + &garmoperatorv1beta1.Runner{ TypeMeta: metav1.TypeMeta{ Kind: "Runner", - APIVersion: garmoperatorv1alpha1.GroupVersion.Group + "/" + garmoperatorv1alpha1.GroupVersion.Version, + APIVersion: garmoperatorv1beta1.GroupVersion.Group + "/" + garmoperatorv1beta1.GroupVersion.Version, }, ObjectMeta: metav1.ObjectMeta{ Name: "road-runner-k8s-fy5snjcv5dzn", @@ -310,8 +310,8 @@ func TestRunnerReconciler_reconcileDeleteCR(t *testing.T) { key.RunnerFinalizerName, }, }, - Spec: garmoperatorv1alpha1.RunnerSpec{}, - Status: garmoperatorv1alpha1.RunnerStatus{ + Spec: garmoperatorv1beta1.RunnerSpec{}, + Status: garmoperatorv1beta1.RunnerStatus{ Name: "road-runner-k8s-FY5snJcv5dzn", AgentID: 120, ID: "8215f6c6-486e-4893-84df-3231b185a148", @@ -323,10 +323,10 @@ func TestRunnerReconciler_reconcileDeleteCR(t *testing.T) { InstanceStatus: params.RunnerIdle, }, }, - &garmoperatorv1alpha1.Runner{ + &garmoperatorv1beta1.Runner{ TypeMeta: metav1.TypeMeta{ Kind: "Runner", - APIVersion: garmoperatorv1alpha1.GroupVersion.Group + "/" + garmoperatorv1alpha1.GroupVersion.Version, + APIVersion: garmoperatorv1beta1.GroupVersion.Group + "/" + garmoperatorv1beta1.GroupVersion.Version, }, ObjectMeta: metav1.ObjectMeta{ Name: "road-runner-k8s-n6kq2mt3k4qr", @@ -335,8 +335,8 @@ func TestRunnerReconciler_reconcileDeleteCR(t *testing.T) { key.RunnerFinalizerName, }, }, - Spec: garmoperatorv1alpha1.RunnerSpec{}, - Status: garmoperatorv1alpha1.RunnerStatus{ + Spec: garmoperatorv1beta1.RunnerSpec{}, + Status: garmoperatorv1beta1.RunnerStatus{ Name: "road-runner-k8s-n6KQ2Mt3k4qr", AgentID: 130, ID: "13d31cad-588b-4ea8-8015-052a76ad3dd3", @@ -348,19 +348,19 @@ func TestRunnerReconciler_reconcileDeleteCR(t *testing.T) { InstanceStatus: params.RunnerIdle, }, }, - &garmoperatorv1alpha1.Pool{ + &garmoperatorv1beta1.Pool{ TypeMeta: metav1.TypeMeta{ Kind: "Pool", - APIVersion: garmoperatorv1alpha1.GroupVersion.Group + "/" + garmoperatorv1alpha1.GroupVersion.Version, + APIVersion: garmoperatorv1beta1.GroupVersion.Group + "/" + garmoperatorv1beta1.GroupVersion.Version, }, ObjectMeta: metav1.ObjectMeta{ Name: "my-enterprise-pool", Namespace: "test-namespace", }, - Spec: garmoperatorv1alpha1.PoolSpec{ + Spec: garmoperatorv1beta1.PoolSpec{ GitHubScopeRef: corev1.TypedLocalObjectReference{ - APIGroup: &garmoperatorv1alpha1.GroupVersion.Group, - Kind: string(garmoperatorv1alpha1.EnterpriseScope), + APIGroup: &garmoperatorv1beta1.GroupVersion.Group, + Kind: string(garmoperatorv1beta1.EnterpriseScope), Name: "my-enterprise", }, ProviderName: "kubernetes_external", @@ -376,7 +376,7 @@ func TestRunnerReconciler_reconcileDeleteCR(t *testing.T) { ExtraSpecs: "", GitHubRunnerGroup: "", }, - Status: garmoperatorv1alpha1.PoolStatus{ + Status: garmoperatorv1beta1.PoolStatus{ ID: "a46553c6-ad87-454b-b5f5-a1c468d78c1e", }, }, @@ -400,7 +400,7 @@ func TestRunnerReconciler_reconcileDeleteCR(t *testing.T) { wantErr: false, expectedEvents: []event.GenericEvent{ { - Object: &garmoperatorv1alpha1.Runner{ + Object: &garmoperatorv1beta1.Runner{ ObjectMeta: metav1.ObjectMeta{ Name: "road-runner-k8s-fy5snjcv5dzn", Namespace: "test-namespace", @@ -414,7 +414,7 @@ func TestRunnerReconciler_reconcileDeleteCR(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { schemeBuilder := runtime.SchemeBuilder{ - garmoperatorv1alpha1.AddToScheme, + garmoperatorv1beta1.AddToScheme, } err := schemeBuilder.AddToScheme(scheme.Scheme) @@ -423,7 +423,7 @@ func TestRunnerReconciler_reconcileDeleteCR(t *testing.T) { } var runtimeObjects []runtime.Object runtimeObjects = append(runtimeObjects, tt.runtimeObjects...) - client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(runtimeObjects...).WithStatusSubresource(&garmoperatorv1alpha1.Runner{}).Build() + client := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithRuntimeObjects(runtimeObjects...).WithStatusSubresource(&garmoperatorv1beta1.Runner{}).Build() // create a fake reconciler reconciler := &RunnerReconciler{ diff --git a/pkg/client/client.go b/pkg/client/client.go index 46eb2837..292beb55 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -192,6 +192,14 @@ func IsUnauthenticatedError(err interface{}) bool { return apiErr.IsCode(401) } +func IsNotFoundError(err interface{}) bool { + apiErr, ok := err.(runtime.ClientResponseStatus) + if !ok { + return false + } + return apiErr.IsCode(404) +} + type Func[T interface{}] func() (T, error) func EnsureAuth[T interface{}](f Func[T]) (T, error) { diff --git a/pkg/client/controller.go b/pkg/client/controller.go new file mode 100644 index 00000000..ba493810 --- /dev/null +++ b/pkg/client/controller.go @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +package client + +import ( + "github.com/cloudbase/garm/client/controller" + "github.com/cloudbase/garm/client/controller_info" + + "github.com/mercedes-benz/garm-operator/pkg/metrics" +) + +type ControllerClient interface { + GetControllerInfo() (*controller_info.ControllerInfoOK, error) + UpdateController(params *controller.UpdateControllerParams) (*controller.UpdateControllerOK, error) +} + +type controllerClient struct { + GarmClient +} + +func NewControllerClient() ControllerClient { + return &controllerClient{ + Client, + } +} + +func (s *controllerClient) GetControllerInfo() (*controller_info.ControllerInfoOK, error) { + return EnsureAuth(func() (*controller_info.ControllerInfoOK, error) { + metrics.TotalGarmCalls.WithLabelValues("controller.Info").Inc() + controllerInfo, err := s.GarmAPI().ControllerInfo.ControllerInfo(&controller_info.ControllerInfoParams{}, s.Token()) + if err != nil { + metrics.GarmCallErrors.WithLabelValues("controller.Info").Inc() + return nil, err + } + return controllerInfo, nil + }) +} + +func (s *controllerClient) UpdateController(param *controller.UpdateControllerParams) (*controller.UpdateControllerOK, error) { + return EnsureAuth(func() (*controller.UpdateControllerOK, error) { + metrics.TotalGarmCalls.WithLabelValues("controller.Update").Inc() + enterprise, err := s.GarmAPI().Controller.UpdateController(param, s.Token()) + if err != nil { + metrics.GarmCallErrors.WithLabelValues("controller.Update").Inc() + return nil, err + } + return enterprise, nil + }) +} diff --git a/pkg/client/credentials.go b/pkg/client/credentials.go new file mode 100644 index 00000000..6118fc14 --- /dev/null +++ b/pkg/client/credentials.go @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT +package client + +import ( + "github.com/cloudbase/garm/client/credentials" + + "github.com/mercedes-benz/garm-operator/pkg/metrics" +) + +type CredentialsClient interface { + GetCredentials(params *credentials.GetCredentialsParams) (*credentials.GetCredentialsOK, error) + ListCredentials(params *credentials.ListCredentialsParams) (*credentials.ListCredentialsOK, error) + CreateCredentials(params *credentials.CreateCredentialsParams) (*credentials.CreateCredentialsOK, error) + UpdateCredentials(params *credentials.UpdateCredentialsParams) (*credentials.UpdateCredentialsOK, error) + DeleteCredentials(params *credentials.DeleteCredentialsParams) error +} + +type credentialClient struct { + GarmClient +} + +func (e *credentialClient) GetCredentials(params *credentials.GetCredentialsParams) (*credentials.GetCredentialsOK, error) { + return EnsureAuth(func() (*credentials.GetCredentialsOK, error) { + metrics.TotalGarmCalls.WithLabelValues("credentials.Get").Inc() + endpoint, err := e.GarmAPI().Credentials.GetCredentials(params, e.Token()) + if err != nil { + metrics.GarmCallErrors.WithLabelValues("credentials.Get").Inc() + return nil, err + } + return endpoint, nil + }) +} + +func (e *credentialClient) ListCredentials(params *credentials.ListCredentialsParams) (*credentials.ListCredentialsOK, error) { + return EnsureAuth(func() (*credentials.ListCredentialsOK, error) { + metrics.TotalGarmCalls.WithLabelValues("credentials.List").Inc() + credentials, err := e.GarmAPI().Credentials.ListCredentials(params, e.Token()) + if err != nil { + metrics.GarmCallErrors.WithLabelValues("credentials.List").Inc() + return nil, err + } + return credentials, nil + }) +} + +func (e *credentialClient) CreateCredentials(params *credentials.CreateCredentialsParams) (*credentials.CreateCredentialsOK, error) { + return EnsureAuth(func() (*credentials.CreateCredentialsOK, error) { + metrics.TotalGarmCalls.WithLabelValues("credentials.Create").Inc() + endpoint, err := e.GarmAPI().Credentials.CreateCredentials(params, e.Token()) + if err != nil { + metrics.GarmCallErrors.WithLabelValues("credentials.Create").Inc() + return nil, err + } + return endpoint, nil + }) +} + +func (e *credentialClient) UpdateCredentials(params *credentials.UpdateCredentialsParams) (*credentials.UpdateCredentialsOK, error) { + return EnsureAuth(func() (*credentials.UpdateCredentialsOK, error) { + metrics.TotalGarmCalls.WithLabelValues("credentials.Update").Inc() + endpoint, err := e.GarmAPI().Credentials.UpdateCredentials(params, e.Token()) + if err != nil { + metrics.GarmCallErrors.WithLabelValues("credentials.Update").Inc() + return nil, err + } + return endpoint, nil + }) +} + +func (e *credentialClient) DeleteCredentials(params *credentials.DeleteCredentialsParams) error { + _, err := EnsureAuth(func() (interface{}, error) { + metrics.TotalGarmCalls.WithLabelValues("credentials.Delete").Inc() + if err := e.GarmAPI().Credentials.DeleteCredentials(params, e.Token()); err != nil { + metrics.GarmCallErrors.WithLabelValues("credentials.Delete").Inc() + return nil, err + } + return nil, nil + }) + return err +} + +func NewCredentialsClient() CredentialsClient { + return &credentialClient{ + Client, + } +} diff --git a/pkg/client/endpoint.go b/pkg/client/endpoint.go new file mode 100644 index 00000000..29fe1962 --- /dev/null +++ b/pkg/client/endpoint.go @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT +package client + +import ( + "github.com/cloudbase/garm/client/endpoints" + + "github.com/mercedes-benz/garm-operator/pkg/metrics" +) + +type EndpointClient interface { + GetEndpoint(params *endpoints.GetGithubEndpointParams) (*endpoints.GetGithubEndpointOK, error) + ListEndpoints(params *endpoints.ListGithubEndpointsParams) (*endpoints.ListGithubEndpointsOK, error) + CreateEndpoint(params *endpoints.CreateGithubEndpointParams) (*endpoints.CreateGithubEndpointOK, error) + UpdateEndpoint(params *endpoints.UpdateGithubEndpointParams) (*endpoints.UpdateGithubEndpointOK, error) + DeleteEndpoint(params *endpoints.DeleteGithubEndpointParams) error +} + +type endpointClient struct { + GarmClient +} + +func (e *endpointClient) GetEndpoint(params *endpoints.GetGithubEndpointParams) (*endpoints.GetGithubEndpointOK, error) { + return EnsureAuth(func() (*endpoints.GetGithubEndpointOK, error) { + metrics.TotalGarmCalls.WithLabelValues("endpoints.Get").Inc() + endpoint, err := e.GarmAPI().Endpoints.GetGithubEndpoint(params, e.Token()) + if err != nil { + metrics.GarmCallErrors.WithLabelValues("endpoints.Get").Inc() + return nil, err + } + return endpoint, nil + }) +} + +func (e *endpointClient) ListEndpoints(params *endpoints.ListGithubEndpointsParams) (*endpoints.ListGithubEndpointsOK, error) { + return EnsureAuth(func() (*endpoints.ListGithubEndpointsOK, error) { + metrics.TotalGarmCalls.WithLabelValues("endpoints.List").Inc() + endpoints, err := e.GarmAPI().Endpoints.ListGithubEndpoints(params, e.Token()) + if err != nil { + metrics.GarmCallErrors.WithLabelValues("endpoints.List").Inc() + return nil, err + } + return endpoints, nil + }) +} + +func (e *endpointClient) CreateEndpoint(params *endpoints.CreateGithubEndpointParams) (*endpoints.CreateGithubEndpointOK, error) { + return EnsureAuth(func() (*endpoints.CreateGithubEndpointOK, error) { + metrics.TotalGarmCalls.WithLabelValues("endpoints.Create").Inc() + endpoint, err := e.GarmAPI().Endpoints.CreateGithubEndpoint(params, e.Token()) + if err != nil { + metrics.GarmCallErrors.WithLabelValues("endpoints.Create").Inc() + return nil, err + } + return endpoint, nil + }) +} + +func (e *endpointClient) UpdateEndpoint(params *endpoints.UpdateGithubEndpointParams) (*endpoints.UpdateGithubEndpointOK, error) { + return EnsureAuth(func() (*endpoints.UpdateGithubEndpointOK, error) { + metrics.TotalGarmCalls.WithLabelValues("endpoints.Update").Inc() + endpoint, err := e.GarmAPI().Endpoints.UpdateGithubEndpoint(params, e.Token()) + if err != nil { + metrics.GarmCallErrors.WithLabelValues("endpoints.Update").Inc() + return nil, err + } + return endpoint, nil + }) +} + +func (e *endpointClient) DeleteEndpoint(params *endpoints.DeleteGithubEndpointParams) error { + _, err := EnsureAuth(func() (interface{}, error) { + metrics.TotalGarmCalls.WithLabelValues("endpoints.Delete").Inc() + if err := e.GarmAPI().Endpoints.DeleteGithubEndpoint(params, e.Token()); err != nil { + metrics.GarmCallErrors.WithLabelValues("endpoints.Delete").Inc() + return nil, err + } + return nil, nil + }) + return err +} + +func NewEndpointClient() EndpointClient { + return &endpointClient{ + Client, + } +} diff --git a/pkg/client/key/key.go b/pkg/client/key/key.go index b6358d36..fd7787ae 100644 --- a/pkg/client/key/key.go +++ b/pkg/client/key/key.go @@ -3,12 +3,13 @@ package key const ( - groupName = "garm-operator.mercedes-benz.com" - EnterpriseFinalizerName = groupName + "/enterprise" - OrganizationFinalizerName = groupName + "/organization" - RepositoryFinalizerName = groupName + "/repository" - PoolFinalizerName = groupName + "/pool" - RunnerFinalizerName = groupName + "/runner" - PausedAnnotation = groupName + "/paused" - LastSyncTimeAnnotation = groupName + "/last-sync-time" + groupName = "garm-operator.mercedes-benz.com" + EnterpriseFinalizerName = groupName + "/enterprise" + OrganizationFinalizerName = groupName + "/organization" + RepositoryFinalizerName = groupName + "/repository" + PoolFinalizerName = groupName + "/pool" + RunnerFinalizerName = groupName + "/runner" + GitHubEndpointFinalizerName = groupName + "/endpoint" + CredentialsFinalizerName = groupName + "/credentials" + PausedAnnotation = groupName + "/paused" ) diff --git a/pkg/client/mock/controller.go b/pkg/client/mock/controller.go new file mode 100644 index 00000000..ef4031c1 --- /dev/null +++ b/pkg/client/mock/controller.go @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT +// Code generated by MockGen. DO NOT EDIT. +// Source: ../controller.go +// +// Generated by this command: +// +// mockgen -package mock -destination=controller.go -source=../controller.go GarmServerConfig +// + +// Package mock is a generated GoMock package. +package mock + +import ( + reflect "reflect" + + controller "github.com/cloudbase/garm/client/controller" + controller_info "github.com/cloudbase/garm/client/controller_info" + gomock "go.uber.org/mock/gomock" +) + +// MockControllerClient is a mock of ControllerClient interface. +type MockControllerClient struct { + ctrl *gomock.Controller + recorder *MockControllerClientMockRecorder +} + +// MockControllerClientMockRecorder is the mock recorder for MockControllerClient. +type MockControllerClientMockRecorder struct { + mock *MockControllerClient +} + +// NewMockControllerClient creates a new mock instance. +func NewMockControllerClient(ctrl *gomock.Controller) *MockControllerClient { + mock := &MockControllerClient{ctrl: ctrl} + mock.recorder = &MockControllerClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockControllerClient) EXPECT() *MockControllerClientMockRecorder { + return m.recorder +} + +// GetControllerInfo mocks base method. +func (m *MockControllerClient) GetControllerInfo() (*controller_info.ControllerInfoOK, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetControllerInfo") + ret0, _ := ret[0].(*controller_info.ControllerInfoOK) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetControllerInfo indicates an expected call of GetControllerInfo. +func (mr *MockControllerClientMockRecorder) GetControllerInfo() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetControllerInfo", reflect.TypeOf((*MockControllerClient)(nil).GetControllerInfo)) +} + +// UpdateController mocks base method. +func (m *MockControllerClient) UpdateController(params *controller.UpdateControllerParams) (*controller.UpdateControllerOK, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateController", params) + ret0, _ := ret[0].(*controller.UpdateControllerOK) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateController indicates an expected call of UpdateController. +func (mr *MockControllerClientMockRecorder) UpdateController(params any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateController", reflect.TypeOf((*MockControllerClient)(nil).UpdateController), params) +} diff --git a/pkg/client/mock/credentials.go b/pkg/client/mock/credentials.go new file mode 100644 index 00000000..7f2bc8f2 --- /dev/null +++ b/pkg/client/mock/credentials.go @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: MIT +// Code generated by MockGen. DO NOT EDIT. +// Source: ../credentials.go +// +// Generated by this command: +// +// mockgen -package mock -destination=credentials.go -source=../credentials.go GithubCredentials +// + +// Package mock is a generated GoMock package. +package mock + +import ( + reflect "reflect" + + credentials "github.com/cloudbase/garm/client/credentials" + gomock "go.uber.org/mock/gomock" +) + +// MockCredentialsClient is a mock of CredentialsClient interface. +type MockCredentialsClient struct { + ctrl *gomock.Controller + recorder *MockCredentialsClientMockRecorder +} + +// MockCredentialsClientMockRecorder is the mock recorder for MockCredentialsClient. +type MockCredentialsClientMockRecorder struct { + mock *MockCredentialsClient +} + +// NewMockCredentialsClient creates a new mock instance. +func NewMockCredentialsClient(ctrl *gomock.Controller) *MockCredentialsClient { + mock := &MockCredentialsClient{ctrl: ctrl} + mock.recorder = &MockCredentialsClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockCredentialsClient) EXPECT() *MockCredentialsClientMockRecorder { + return m.recorder +} + +// CreateCredentials mocks base method. +func (m *MockCredentialsClient) CreateCredentials(params *credentials.CreateCredentialsParams) (*credentials.CreateCredentialsOK, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateCredentials", params) + ret0, _ := ret[0].(*credentials.CreateCredentialsOK) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateCredentials indicates an expected call of CreateCredentials. +func (mr *MockCredentialsClientMockRecorder) CreateCredentials(params any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateCredentials", reflect.TypeOf((*MockCredentialsClient)(nil).CreateCredentials), params) +} + +// DeleteCredentials mocks base method. +func (m *MockCredentialsClient) DeleteCredentials(params *credentials.DeleteCredentialsParams) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteCredentials", params) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteCredentials indicates an expected call of DeleteCredentials. +func (mr *MockCredentialsClientMockRecorder) DeleteCredentials(params any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteCredentials", reflect.TypeOf((*MockCredentialsClient)(nil).DeleteCredentials), params) +} + +// GetCredentials mocks base method. +func (m *MockCredentialsClient) GetCredentials(params *credentials.GetCredentialsParams) (*credentials.GetCredentialsOK, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetCredentials", params) + ret0, _ := ret[0].(*credentials.GetCredentialsOK) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetCredentials indicates an expected call of GetCredentials. +func (mr *MockCredentialsClientMockRecorder) GetCredentials(params any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCredentials", reflect.TypeOf((*MockCredentialsClient)(nil).GetCredentials), params) +} + +// ListCredentials mocks base method. +func (m *MockCredentialsClient) ListCredentials(params *credentials.ListCredentialsParams) (*credentials.ListCredentialsOK, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListCredentials", params) + ret0, _ := ret[0].(*credentials.ListCredentialsOK) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListCredentials indicates an expected call of ListCredentials. +func (mr *MockCredentialsClientMockRecorder) ListCredentials(params any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListCredentials", reflect.TypeOf((*MockCredentialsClient)(nil).ListCredentials), params) +} + +// UpdateCredentials mocks base method. +func (m *MockCredentialsClient) UpdateCredentials(params *credentials.UpdateCredentialsParams) (*credentials.UpdateCredentialsOK, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateCredentials", params) + ret0, _ := ret[0].(*credentials.UpdateCredentialsOK) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateCredentials indicates an expected call of UpdateCredentials. +func (mr *MockCredentialsClientMockRecorder) UpdateCredentials(params any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateCredentials", reflect.TypeOf((*MockCredentialsClient)(nil).UpdateCredentials), params) +} diff --git a/pkg/client/mock/doc.go b/pkg/client/mock/doc.go index 38566751..356873b2 100644 --- a/pkg/client/mock/doc.go +++ b/pkg/client/mock/doc.go @@ -18,3 +18,9 @@ package mock //go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt repository.go > _repository.go && mv _repository.go repository.go" //go:generate ../../../bin/mockgen -package mock -destination=client.go -source=../client.go GarmClient //go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt client.go > _client.go && mv _client.go client.go" +//go:generate ../../../bin/mockgen -package mock -destination=endpoint.go -source=../endpoint.go Endpoint +//go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt endpoint.go > _endpoint.go && mv _endpoint.go endpoint.go" +//go:generate ../../../bin/mockgen -package mock -destination=credentials.go -source=../credentials.go GithubCredentials +//go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt credentials.go > _credentials.go && mv _credentials.go credentials.go" +//go:generate ../../../bin/mockgen -package mock -destination=controller.go -source=../controller.go GarmServerConfig +//go:generate /usr/bin/env bash -c "cat ../../../hack/boilerplate.go.txt controller.go > _controller.go && mv _controller.go controller.go" diff --git a/pkg/client/mock/endpoint.go b/pkg/client/mock/endpoint.go new file mode 100644 index 00000000..2574e9d3 --- /dev/null +++ b/pkg/client/mock/endpoint.go @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: MIT +// Code generated by MockGen. DO NOT EDIT. +// Source: ../endpoint.go +// +// Generated by this command: +// +// mockgen -package mock -destination=endpoint.go -source=../endpoint.go Endpoint +// + +// Package mock is a generated GoMock package. +package mock + +import ( + reflect "reflect" + + endpoints "github.com/cloudbase/garm/client/endpoints" + gomock "go.uber.org/mock/gomock" +) + +// MockEndpointClient is a mock of EndpointClient interface. +type MockEndpointClient struct { + ctrl *gomock.Controller + recorder *MockEndpointClientMockRecorder +} + +// MockEndpointClientMockRecorder is the mock recorder for MockEndpointClient. +type MockEndpointClientMockRecorder struct { + mock *MockEndpointClient +} + +// NewMockEndpointClient creates a new mock instance. +func NewMockEndpointClient(ctrl *gomock.Controller) *MockEndpointClient { + mock := &MockEndpointClient{ctrl: ctrl} + mock.recorder = &MockEndpointClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockEndpointClient) EXPECT() *MockEndpointClientMockRecorder { + return m.recorder +} + +// CreateEndpoint mocks base method. +func (m *MockEndpointClient) CreateEndpoint(params *endpoints.CreateGithubEndpointParams) (*endpoints.CreateGithubEndpointOK, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateEndpoint", params) + ret0, _ := ret[0].(*endpoints.CreateGithubEndpointOK) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateEndpoint indicates an expected call of CreateEndpoint. +func (mr *MockEndpointClientMockRecorder) CreateEndpoint(params any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateEndpoint", reflect.TypeOf((*MockEndpointClient)(nil).CreateEndpoint), params) +} + +// DeleteEndpoint mocks base method. +func (m *MockEndpointClient) DeleteEndpoint(params *endpoints.DeleteGithubEndpointParams) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteEndpoint", params) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteEndpoint indicates an expected call of DeleteEndpoint. +func (mr *MockEndpointClientMockRecorder) DeleteEndpoint(params any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteEndpoint", reflect.TypeOf((*MockEndpointClient)(nil).DeleteEndpoint), params) +} + +// GetEndpoint mocks base method. +func (m *MockEndpointClient) GetEndpoint(params *endpoints.GetGithubEndpointParams) (*endpoints.GetGithubEndpointOK, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetEndpoint", params) + ret0, _ := ret[0].(*endpoints.GetGithubEndpointOK) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetEndpoint indicates an expected call of GetEndpoint. +func (mr *MockEndpointClientMockRecorder) GetEndpoint(params any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEndpoint", reflect.TypeOf((*MockEndpointClient)(nil).GetEndpoint), params) +} + +// ListEndpoints mocks base method. +func (m *MockEndpointClient) ListEndpoints(params *endpoints.ListGithubEndpointsParams) (*endpoints.ListGithubEndpointsOK, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListEndpoints", params) + ret0, _ := ret[0].(*endpoints.ListGithubEndpointsOK) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListEndpoints indicates an expected call of ListEndpoints. +func (mr *MockEndpointClientMockRecorder) ListEndpoints(params any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListEndpoints", reflect.TypeOf((*MockEndpointClient)(nil).ListEndpoints), params) +} + +// UpdateEndpoint mocks base method. +func (m *MockEndpointClient) UpdateEndpoint(params *endpoints.UpdateGithubEndpointParams) (*endpoints.UpdateGithubEndpointOK, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateEndpoint", params) + ret0, _ := ret[0].(*endpoints.UpdateGithubEndpointOK) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateEndpoint indicates an expected call of UpdateEndpoint. +func (mr *MockEndpointClientMockRecorder) UpdateEndpoint(params any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateEndpoint", reflect.TypeOf((*MockEndpointClient)(nil).UpdateEndpoint), params) +} diff --git a/pkg/conditions/condition_types.go b/pkg/conditions/condition_types.go index 561e2d8d..097ae173 100644 --- a/pkg/conditions/condition_types.go +++ b/pkg/conditions/condition_types.go @@ -40,12 +40,26 @@ const ( SecretReference ConditionType = "SecretReference" FetchingSecretRefSuccessReason ConditionReason = "FetchingSecretRefSuccess" FetchingSecretRefFailedReason ConditionReason = "FetchingSecretRefFailed" + + CredentialsReference ConditionType = "CredentialsReference" + FetchingCredentialsRefSuccessReason ConditionReason = "CredentialsRefSuccess" + FetchingCredentialsRefFailedReason ConditionReason = "CredentialsRefFailed" +) + +// Credential Conditions +const ( + EndpointReference ConditionType = "EndpointReference" + FetchingEndpointRefSuccessReason ConditionReason = "FetchingEndpointRefSuccess" + FetchingEndpointRefFailedReason ConditionReason = "FetchingEndpointRefFailed" ) const ( - GarmServerNotReconciledYetMsg string = "GARM server not reconciled yet" - DeletingEnterpriseMsg string = "Deleting enterprise" - DeletingOrgMsg string = "Deleting organization" - DeletingRepoMsg string = "Deleting repository" - DeletingPoolMsg string = "Deleting pool" + GarmServerNotReconciledYetMsg string = "GARM server not reconciled yet" + CredentialsNotReconciledYetMsg string = "credentials not reconciled yet" + DeletingEnterpriseMsg string = "Deleting enterprise" + DeletingOrgMsg string = "Deleting organization" + DeletingRepoMsg string = "Deleting repository" + DeletingPoolMsg string = "Deleting pool" + DeletingEndpointMsg string = "Deleting endpoint" + DeletingCredentialsMsg string = "Deleting credentials" // #nosec G101 ) diff --git a/pkg/pools/pools.go b/pkg/pools/pools.go index 5ae19c07..76484c54 100644 --- a/pkg/pools/pools.go +++ b/pkg/pools/pools.go @@ -15,18 +15,18 @@ import ( "github.com/cloudbase/garm/params" "sigs.k8s.io/controller-runtime/pkg/log" - garmoperatorv1alpha1 "github.com/mercedes-benz/garm-operator/api/v1alpha1" + garmoperatorv1beta1 "github.com/mercedes-benz/garm-operator/api/v1beta1" garmClient "github.com/mercedes-benz/garm-operator/pkg/client" "github.com/mercedes-benz/garm-operator/pkg/filter" ) -func GetGarmPoolBySpecs(ctx context.Context, garmClient garmClient.PoolClient, pool *garmoperatorv1alpha1.Pool, image *garmoperatorv1alpha1.Image, gitHubScopeRef garmoperatorv1alpha1.GitHubScope) (*params.Pool, error) { +func GetGarmPoolBySpecs(ctx context.Context, garmClient garmClient.PoolClient, pool *garmoperatorv1beta1.Pool, image *garmoperatorv1beta1.Image, gitHubScopeRef garmoperatorv1beta1.GitHubScope) (*params.Pool, error) { log := log.FromContext(ctx) log.Info("Getting existing garm pools by pool.spec") githubScopeRefID := gitHubScopeRef.GetID() githubScopeRefName := gitHubScopeRef.GetName() - scope, err := garmoperatorv1alpha1.ToGitHubScopeKind(gitHubScopeRef.GetKind()) + scope, err := garmoperatorv1beta1.ToGitHubScopeKind(gitHubScopeRef.GetKind()) if err != nil { return nil, err } @@ -67,7 +67,7 @@ func GetGarmPoolBySpecs(ctx context.Context, garmClient garmClient.PoolClient, p return nil, nil } -func UpdatePool(ctx context.Context, garmClient garmClient.PoolClient, pool *garmoperatorv1alpha1.Pool, image *garmoperatorv1alpha1.Image) error { +func UpdatePool(ctx context.Context, garmClient garmClient.PoolClient, pool *garmoperatorv1beta1.Pool, image *garmoperatorv1beta1.Image) error { log := log.FromContext(ctx). WithName("UpdatePool") @@ -100,7 +100,7 @@ func UpdatePool(ctx context.Context, garmClient garmClient.PoolClient, pool *gar return nil } -func CreatePool(ctx context.Context, garmClient garmClient.PoolClient, pool *garmoperatorv1alpha1.Pool, image *garmoperatorv1alpha1.Image, gitHubScopeRef garmoperatorv1alpha1.GitHubScope) (params.Pool, error) { +func CreatePool(ctx context.Context, garmClient garmClient.PoolClient, pool *garmoperatorv1beta1.Pool, image *garmoperatorv1beta1.Image, gitHubScopeRef garmoperatorv1beta1.GitHubScope) (params.Pool, error) { log := log.FromContext(ctx). WithName("CreatePool") log.Info("creating pool", "pool", pool.Name) @@ -108,7 +108,7 @@ func CreatePool(ctx context.Context, garmClient garmClient.PoolClient, pool *gar poolResult := params.Pool{} id := gitHubScopeRef.GetID() - scope, err := garmoperatorv1alpha1.ToGitHubScopeKind(gitHubScopeRef.GetKind()) + scope, err := garmoperatorv1beta1.ToGitHubScopeKind(gitHubScopeRef.GetKind()) if err != nil { return poolResult, err } @@ -140,7 +140,7 @@ func CreatePool(ctx context.Context, garmClient garmClient.PoolClient, pool *gar } switch scope { - case garmoperatorv1alpha1.EnterpriseScope: + case garmoperatorv1beta1.EnterpriseScope: result, err := garmClient.CreateEnterprisePool( enterprises.NewCreateEnterprisePoolParams(). WithEnterpriseID(id). @@ -150,7 +150,7 @@ func CreatePool(ctx context.Context, garmClient garmClient.PoolClient, pool *gar } poolResult = result.Payload - case garmoperatorv1alpha1.OrganizationScope: + case garmoperatorv1beta1.OrganizationScope: result, err := garmClient.CreateOrgPool( organizations.NewCreateOrgPoolParams(). WithOrgID(id). @@ -159,7 +159,7 @@ func CreatePool(ctx context.Context, garmClient garmClient.PoolClient, pool *gar return params.Pool{}, err } poolResult = result.Payload - case garmoperatorv1alpha1.RepositoryScope: + case garmoperatorv1beta1.RepositoryScope: result, err := garmClient.CreateRepoPool( repositories.NewCreateRepoPoolParams(). WithRepoID(id). @@ -176,7 +176,7 @@ func CreatePool(ctx context.Context, garmClient garmClient.PoolClient, pool *gar return poolResult, nil } -func GarmPoolExists(garmClient garmClient.PoolClient, pool *garmoperatorv1alpha1.Pool) bool { +func GarmPoolExists(garmClient garmClient.PoolClient, pool *garmoperatorv1beta1.Pool) bool { result, err := garmClient.GetPool(pools.NewGetPoolParams().WithPoolID(pool.Status.ID)) if err != nil { return false diff --git a/pkg/pools/predicate.go b/pkg/pools/predicate.go index 47409090..51c99dc5 100644 --- a/pkg/pools/predicate.go +++ b/pkg/pools/predicate.go @@ -5,21 +5,21 @@ package pools import ( "github.com/cloudbase/garm/params" - garmoperatorv1alpha1 "github.com/mercedes-benz/garm-operator/api/v1alpha1" + garmoperatorv1beta1 "github.com/mercedes-benz/garm-operator/api/v1beta1" "github.com/mercedes-benz/garm-operator/pkg/filter" ) -func MatchesGitHubScope(scope garmoperatorv1alpha1.GitHubScopeKind, id string) filter.Predicate[params.Pool] { +func MatchesGitHubScope(scope garmoperatorv1beta1.GitHubScopeKind, id string) filter.Predicate[params.Pool] { return func(p params.Pool) bool { - if scope == garmoperatorv1alpha1.EnterpriseScope { + if scope == garmoperatorv1beta1.EnterpriseScope { return p.EnterpriseID == id } - if scope == garmoperatorv1alpha1.OrganizationScope { + if scope == garmoperatorv1beta1.OrganizationScope { return p.OrgID == id } - if scope == garmoperatorv1alpha1.RepositoryScope { + if scope == garmoperatorv1beta1.RepositoryScope { return p.RepoID == id } return false diff --git a/pkg/runners/runners.go b/pkg/runners/runners.go index 5f8380c9..7a00e2f4 100644 --- a/pkg/runners/runners.go +++ b/pkg/runners/runners.go @@ -11,11 +11,11 @@ import ( "github.com/cloudbase/garm/params" "sigs.k8s.io/controller-runtime/pkg/log" - garmoperatorv1alpha1 "github.com/mercedes-benz/garm-operator/api/v1alpha1" + garmoperatorv1beta1 "github.com/mercedes-benz/garm-operator/api/v1beta1" garmClient "github.com/mercedes-benz/garm-operator/pkg/client" ) -func GetRunnersByPoolID(ctx context.Context, pool *garmoperatorv1alpha1.Pool, instanceClient garmClient.InstanceClient) ([]params.Instance, error) { +func GetRunnersByPoolID(ctx context.Context, pool *garmoperatorv1beta1.Pool, instanceClient garmClient.InstanceClient) ([]params.Instance, error) { log := log.FromContext(ctx) log.Info("discover idle runners", "pool", pool.Name) diff --git a/pkg/secret/secret.go b/pkg/secret/secret.go index 0c2b57ec..9c24f973 100644 --- a/pkg/secret/secret.go +++ b/pkg/secret/secret.go @@ -10,11 +10,11 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" "sigs.k8s.io/controller-runtime/pkg/client" - garmoperatorv1alpha1 "github.com/mercedes-benz/garm-operator/api/v1alpha1" + garmoperatorv1beta1 "github.com/mercedes-benz/garm-operator/api/v1beta1" ) // FetchRef fetches a secret for a given garmoperatorv1alpha1.SecretRef and namespace -func FetchRef(ctx context.Context, c client.Client, ref *garmoperatorv1alpha1.SecretRef, namespace string) (string, error) { +func FetchRef(ctx context.Context, c client.Client, ref *garmoperatorv1beta1.SecretRef, namespace string) (string, error) { if ref == nil { return "", nil } diff --git a/pkg/util/util.go b/pkg/util/util.go new file mode 100644 index 00000000..0960a729 --- /dev/null +++ b/pkg/util/util.go @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +package util + +func StringPtr(s string) *string { + return &s +}