diff --git a/api/v1alpha1/gitrepo_types.go b/api/v1alpha1/gitrepo_types.go index bd170ea9..2bd45f30 100644 --- a/api/v1alpha1/gitrepo_types.go +++ b/api/v1alpha1/gitrepo_types.go @@ -65,6 +65,8 @@ type GitRepoTemplate struct { // DeployKeys optional list of SSH deploy keys. If not set, not deploy keys will be configured DeployKeys map[string]DeployKey `json:"deployKeys,omitempty"` // Path to Git repository + GeneratedDeployKeys map[string]DeployKeyTemplate `json:"generatedDeployKeys,omitempty"` + // Path to Git repository Path string `json:"path,omitempty"` // RepoName name of Git repository RepoName string `json:"repoName,omitempty"` @@ -153,6 +155,21 @@ type DeployKey struct { WriteAccess bool `json:"writeAccess,omitempty"` } +// DeployKeyTemplate defines an SSH key to be generated for git operations. +type DeployKeyTemplate struct { + // Type defines what type the key is. For key generation, currently only `ssh-rsa` and `ssh-ed25519` are supported. + Type string `json:"type,omitempty"` + // WriteAccess if the key has RW access or not + WriteAccess bool `json:"writeAccess,omitempty"` +} + +// DeployKeyStatus tracks the status for a generated Deploy Key +type DeployKeyStatus struct { + DeployKey `json:",inline"` + // SecretRef is the name of the secret in which the SSH keypair is stored. + SecretRef corev1.LocalObjectReference `json:"secretRef,omitempty"` +} + // GitRepoStatus defines the observed state of GitRepo type GitRepoStatus struct { // Updated by Operator with current phase. The GitPhase enum will be used for application logic @@ -166,6 +183,8 @@ type GitRepoStatus struct { HostKeys string `json:"hostKeys,omitempty"` // LastAppliedCIVariables contains the last applied CI variables as a json string LastAppliedCIVariables string `json:"lastAppliedCIVariables,omitempty"` + // GeneratedDeployKeys contains all SSH deploy keys that were generated for the git repo + GeneratedDeployKeys map[string]DeployKeyStatus `json:"generatedDeployKeys,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 2ea14521..35064891 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -282,6 +282,38 @@ func (in *DeployKey) DeepCopy() *DeployKey { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeployKeyStatus) DeepCopyInto(out *DeployKeyStatus) { + *out = *in + out.DeployKey = in.DeployKey + out.SecretRef = in.SecretRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeployKeyStatus. +func (in *DeployKeyStatus) DeepCopy() *DeployKeyStatus { + if in == nil { + return nil + } + out := new(DeployKeyStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *DeployKeyTemplate) DeepCopyInto(out *DeployKeyTemplate) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DeployKeyTemplate. +func (in *DeployKeyTemplate) DeepCopy() *DeployKeyTemplate { + if in == nil { + return nil + } + out := new(DeployKeyTemplate) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *EnvVar) DeepCopyInto(out *EnvVar) { *out = *in @@ -443,6 +475,13 @@ func (in *GitRepoStatus) DeepCopyInto(out *GitRepoStatus) { *out = new(GitPhase) **out = **in } + if in.GeneratedDeployKeys != nil { + in, out := &in.GeneratedDeployKeys, &out.GeneratedDeployKeys + *out = make(map[string]DeployKeyStatus, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GitRepoStatus. @@ -466,6 +505,13 @@ func (in *GitRepoTemplate) DeepCopyInto(out *GitRepoTemplate) { (*out)[key] = val } } + if in.GeneratedDeployKeys != nil { + in, out := &in.GeneratedDeployKeys, &out.GeneratedDeployKeys + *out = make(map[string]DeployKeyTemplate, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } if in.TemplateFiles != nil { in, out := &in.TemplateFiles, &out.TemplateFiles *out = make(map[string]string, len(*in)) diff --git a/config/crd/bases/syn.tools_clusters.yaml b/config/crd/bases/syn.tools_clusters.yaml index 84857500..be5fd39c 100644 --- a/config/crd/bases/syn.tools_clusters.yaml +++ b/config/crd/bases/syn.tools_clusters.yaml @@ -227,6 +227,22 @@ spec: displayName: description: DisplayName of Git repository type: string + generatedDeployKeys: + additionalProperties: + description: DeployKeyTemplate defines an SSH key to be generated + for git operations. + properties: + type: + description: Type defines what type the key is. For key + generation, currently only `ssh-rsa` and `ssh-ed25519` + are supported. + type: string + writeAccess: + description: WriteAccess if the key has RW access or not + type: boolean + type: object + description: Path to Git repository + type: object path: description: Path to Git repository type: string diff --git a/config/crd/bases/syn.tools_gitrepos.yaml b/config/crd/bases/syn.tools_gitrepos.yaml index 322d59df..8faae025 100644 --- a/config/crd/bases/syn.tools_gitrepos.yaml +++ b/config/crd/bases/syn.tools_gitrepos.yaml @@ -186,6 +186,21 @@ spec: displayName: description: DisplayName of Git repository type: string + generatedDeployKeys: + additionalProperties: + description: DeployKeyTemplate defines an SSH key to be generated + for git operations. + properties: + type: + description: Type defines what type the key is. For key generation, + currently only `ssh-rsa` and `ssh-ed25519` are supported. + type: string + writeAccess: + description: WriteAccess if the key has RW access or not + type: boolean + type: object + description: Path to Git repository + type: object path: description: Path to Git repository type: string @@ -225,6 +240,40 @@ spec: status: description: GitRepoStatus defines the observed state of GitRepo properties: + generatedDeployKeys: + additionalProperties: + description: DeployKeyStatus tracks the status for a generated Deploy + Key + properties: + key: + description: Key is the actual key + type: string + secretRef: + description: SecretRef is the name of the secret in which the + SSH keypair is stored. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + type: + description: Type defines what type the key is (rsa, ed25519, + etc...) + type: string + writeAccess: + description: WriteAccess if the key has RW access or not + type: boolean + type: object + description: GeneratedDeployKeys contains all SSH deploy keys that + were generated for the git repo + type: object hostKeys: description: SSH HostKeys of the git server type: string diff --git a/config/crd/bases/syn.tools_tenants.yaml b/config/crd/bases/syn.tools_tenants.yaml index c5e2aa81..0e4fbefe 100644 --- a/config/crd/bases/syn.tools_tenants.yaml +++ b/config/crd/bases/syn.tools_tenants.yaml @@ -233,6 +233,23 @@ spec: displayName: description: DisplayName of Git repository type: string + generatedDeployKeys: + additionalProperties: + description: DeployKeyTemplate defines an SSH key to be + generated for git operations. + properties: + type: + description: Type defines what type the key is. For + key generation, currently only `ssh-rsa` and `ssh-ed25519` + are supported. + type: string + writeAccess: + description: WriteAccess if the key has RW access or + not + type: boolean + type: object + description: Path to Git repository + type: object path: description: Path to Git repository type: string @@ -476,6 +493,22 @@ spec: displayName: description: DisplayName of Git repository type: string + generatedDeployKeys: + additionalProperties: + description: DeployKeyTemplate defines an SSH key to be generated + for git operations. + properties: + type: + description: Type defines what type the key is. For key + generation, currently only `ssh-rsa` and `ssh-ed25519` + are supported. + type: string + writeAccess: + description: WriteAccess if the key has RW access or not + type: boolean + type: object + description: Path to Git repository + type: object path: description: Path to Git repository type: string diff --git a/config/crd/bases/syn.tools_tenanttemplates.yaml b/config/crd/bases/syn.tools_tenanttemplates.yaml index c824035f..5038e9d0 100644 --- a/config/crd/bases/syn.tools_tenanttemplates.yaml +++ b/config/crd/bases/syn.tools_tenanttemplates.yaml @@ -233,6 +233,23 @@ spec: displayName: description: DisplayName of Git repository type: string + generatedDeployKeys: + additionalProperties: + description: DeployKeyTemplate defines an SSH key to be + generated for git operations. + properties: + type: + description: Type defines what type the key is. For + key generation, currently only `ssh-rsa` and `ssh-ed25519` + are supported. + type: string + writeAccess: + description: WriteAccess if the key has RW access or + not + type: boolean + type: object + description: Path to Git repository + type: object path: description: Path to Git repository type: string @@ -476,6 +493,22 @@ spec: displayName: description: DisplayName of Git repository type: string + generatedDeployKeys: + additionalProperties: + description: DeployKeyTemplate defines an SSH key to be generated + for git operations. + properties: + type: + description: Type defines what type the key is. For key + generation, currently only `ssh-rsa` and `ssh-ed25519` + are supported. + type: string + writeAccess: + description: WriteAccess if the key has RW access or not + type: boolean + type: object + description: Path to Git repository + type: object path: description: Path to Git repository type: string diff --git a/controllers/gitrepo/steps.go b/controllers/gitrepo/steps.go index 6ff45688..81990591 100644 --- a/controllers/gitrepo/steps.go +++ b/controllers/gitrepo/steps.go @@ -4,14 +4,17 @@ import ( "context" "errors" "fmt" + "strings" "time" + "github.com/charmbracelet/keygen" "github.com/go-logr/logr" synv1alpha1 "github.com/projectsyn/lieutenant-operator/api/v1alpha1" "go.uber.org/multierr" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/json" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/utils/ptr" @@ -25,11 +28,17 @@ import ( "github.com/projectsyn/lieutenant-operator/pipeline" ) +const DEPLOY_KEY_NAME_INFIX = "-deploy-key-" +const DEPLOY_KEY_SECRET_PUBKEY = "publicKey" +const DEPLOY_KEY_SECRET_PRIVKEY = "privateKey" +const DEPLOY_KEY_SECRET_TYPE = "type" +const DEPLOY_KEY_GENERATED_PREFIX = "generated-" + func Steps(obj pipeline.Object, data *pipeline.Context) pipeline.Result { return steps(obj, data, manager.GetGitClient) } -type gitClientFactory func(ctx context.Context, instance *synv1alpha1.GitRepoTemplate, namespace string, reqLogger logr.Logger, client client.Client) (manager.Repo, string, error) +type gitClientFactory func(ctx context.Context, instance *synv1alpha1.GitRepo, reqLogger logr.Logger, client client.Client) (manager.Repo, string, error) func steps(obj pipeline.Object, data *pipeline.Context, getGitClient gitClientFactory) pipeline.Result { instance, ok := obj.(*synv1alpha1.GitRepo) @@ -47,7 +56,13 @@ func steps(obj pipeline.Object, data *pipeline.Context, getGitClient gitClientFa return pipeline.Result{} } - repo, hostKeys, err := getGitClient(data.Context, &instance.Spec.GitRepoTemplate, instance.GetNamespace(), data.Log, data.Client) + // NOTE(aa): Generate deploy keys before creating Git repo client, since the list of + // deploy keys which the client is aware of is frozen at client-creation time. + if err := ensureGeneratedDeployKeys(data.Context, data.Client, instance); err != nil { + return pipeline.Result{Err: handleRepoError(data.Context, fmt.Errorf("ensure generated deploy keys: %w", err), instance, data.Client)} + } + + repo, hostKeys, err := getGitClient(data.Context, instance, data.Log, data.Client) if err != nil { return pipeline.Result{Err: fmt.Errorf("get Git client: %w", err)} } @@ -252,6 +267,83 @@ func ensureCIVariables(ctx context.Context, cli client.Client, instance *synv1al return nil } +// ensureGeneratedDeployKeys ensures that the repo's `generateDeployKey` entries +// all have a corresponding `deployKey` entry, generating SSH keys as required +// and storing them in individual secrets. +func ensureGeneratedDeployKeys(ctx context.Context, cli client.Client, instance *synv1alpha1.GitRepo) error { + for genKey, settings := range instance.Spec.GeneratedDeployKeys { + secretName := instance.Name + DEPLOY_KEY_NAME_INFIX + genKey + secretNSName := types.NamespacedName{Name: secretName, Namespace: instance.Namespace} + secret := &corev1.Secret{} + + err := cli.Get(ctx, secretNSName, secret) + if err != nil { + if !apierrors.IsNotFound(err) { + return err + } + err = generateNewDeployKeySecret(ctx, cli, settings, secretNSName, instance, secret) + if err != nil { + return err + } + } + + pubkeyB, ok := secret.Data[DEPLOY_KEY_SECRET_PUBKEY] + if !ok { + return fmt.Errorf("could not retrieve deploy key from secret: missing key: %s", DEPLOY_KEY_SECRET_PUBKEY) + } + pubkey := string(pubkeyB) + parts := strings.Split(pubkey, " ") + + keyName := DEPLOY_KEY_GENERATED_PREFIX + genKey + + oldKey, ok := instance.Status.GeneratedDeployKeys[keyName] + if !ok || oldKey.Key != parts[1] { + if instance.Status.GeneratedDeployKeys == nil { + instance.Status.GeneratedDeployKeys = make(map[string]synv1alpha1.DeployKeyStatus) + } + instance.Status.GeneratedDeployKeys[keyName] = synv1alpha1.DeployKeyStatus{ + DeployKey: synv1alpha1.DeployKey{ + Type: parts[0], + Key: parts[1], + WriteAccess: settings.WriteAccess, + }, + SecretRef: corev1.LocalObjectReference{Name: secretName}, + } + } + } + + return nil +} + +func generateNewDeployKeySecret(ctx context.Context, cli client.Client, settings synv1alpha1.DeployKeyTemplate, secretName types.NamespacedName, owner *synv1alpha1.GitRepo, secretRef *corev1.Secret) error { + keyType := keygen.Ed25519 + if settings.Type == "ssh-rsa" { + keyType = keygen.RSA + } + + kp, err := keygen.New( + "/tmp/"+secretName.Name, + keygen.WithKeyType(keyType), + ) + if err != nil { + return err + } + + secretRef.Name = secretName.Name + secretRef.Namespace = secretName.Namespace + + secretRef.ObjectMeta.OwnerReferences = []metav1.OwnerReference{ + *metav1.NewControllerRef(owner, owner.GroupVersionKind()), + } + + secretRef.Data = make(map[string][]byte) + + secretRef.Data[DEPLOY_KEY_SECRET_PUBKEY] = kp.RawAuthorizedKey() + secretRef.Data[DEPLOY_KEY_SECRET_PRIVKEY] = kp.RawPrivateKey() + + return cli.Create(ctx, secretRef) +} + // valueFromEnvVar returns the value of an envVar. It returns an error if the envVar is invalid or the value cannot be retrieved. // EnvVars with both value and valueFrom are invalid. // An envVar with no value and no valueFrom returns an empty string. diff --git a/controllers/gitrepo/steps_test.go b/controllers/gitrepo/steps_test.go index e00bdd05..d7e15d80 100644 --- a/controllers/gitrepo/steps_test.go +++ b/controllers/gitrepo/steps_test.go @@ -4,6 +4,7 @@ import ( "context" "errors" "net/url" + "strings" "testing" "time" @@ -363,6 +364,102 @@ func TestStepsCreationFailure(t *testing.T) { } } +func TestSteps_GenerateDeployKeys(t *testing.T) { + scheme := runtime.NewScheme() + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + utilruntime.Must(synv1alpha1.AddToScheme(scheme)) + + repo := &synv1alpha1.GitRepo{ + ObjectMeta: metav1.ObjectMeta{ + Name: "c-bar", + Namespace: "foo", + }, + Spec: synv1alpha1.GitRepoSpec{ + GitRepoTemplate: synv1alpha1.GitRepoTemplate{ + GeneratedDeployKeys: map[string]synv1alpha1.DeployKeyTemplate{ + "testkey": { + Type: "ssh-rsa", + WriteAccess: true, + }, + "fookey": { + Type: "ssh-ed25519", + WriteAccess: false, + }, + "existing": { + Type: "ssh-ed25519", + WriteAccess: false, + }, + }, + }, + }, + } + + existingSecret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "c-bar-deploy-key-existing", + Namespace: "foo", + }, + Data: map[string][]byte{ + "privateKey": []byte("itsasecret"), + "publicKey": []byte("ssh-ecdsa foo"), + }, + } + + c := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(repo, existingSecret). + WithStatusSubresource(&synv1alpha1.GitRepo{}). + Build() + pContext := &pipeline.Context{ + Context: context.TODO(), + FinalizerName: "foo", + Client: c, + Log: testr.New(t), + } + fr := &fakeRepo{ + exists: true, + url: new(url.URL), + } + gc := fakeGitClientFactory(fr) + res := steps(repo, pContext, gc) + assert.NoError(t, res.Err) + + secret := &corev1.Secret{} + err := c.Get(context.TODO(), types.NamespacedName{Namespace: "foo", Name: "c-bar-deploy-key-testkey"}, secret) + assert.NoError(t, err) + + assert.Equal(t, "c-bar", secret.ObjectMeta.OwnerReferences[0].Name) + + secretPubkey := string(secret.Data["publicKey"]) + spParts := strings.Split(secretPubkey, " ") + + assert.Equal(t, "ssh-rsa", repo.Status.GeneratedDeployKeys["generated-testkey"].Type) + assert.Equal(t, "ssh-rsa", spParts[0]) + assert.Equal(t, spParts[1], repo.Status.GeneratedDeployKeys["generated-testkey"].Key) + assert.Equal(t, "c-bar-deploy-key-testkey", repo.Status.GeneratedDeployKeys["generated-testkey"].SecretRef.Name) + assert.True(t, repo.Status.GeneratedDeployKeys["generated-testkey"].WriteAccess) + + err = c.Get(context.TODO(), types.NamespacedName{Namespace: "foo", Name: "c-bar-deploy-key-fookey"}, secret) + assert.NoError(t, err) + + assert.Equal(t, "c-bar", secret.ObjectMeta.OwnerReferences[0].Name) + + secretPubkey = string(secret.Data["publicKey"]) + spParts = strings.Split(secretPubkey, " ") + + assert.Equal(t, "ssh-ed25519", repo.Status.GeneratedDeployKeys["generated-fookey"].Type) + assert.Equal(t, "ssh-ed25519", spParts[0]) + assert.Equal(t, spParts[1], repo.Status.GeneratedDeployKeys["generated-fookey"].Key) + assert.Equal(t, "c-bar-deploy-key-fookey", repo.Status.GeneratedDeployKeys["generated-fookey"].SecretRef.Name) + assert.False(t, repo.Status.GeneratedDeployKeys["generated-fookey"].WriteAccess) + + assert.Equal(t, "ssh-ecdsa", repo.Status.GeneratedDeployKeys["generated-existing"].Type) + assert.Equal(t, "foo", repo.Status.GeneratedDeployKeys["generated-existing"].Key) + assert.Equal(t, "c-bar-deploy-key-existing", repo.Status.GeneratedDeployKeys["generated-existing"].SecretRef.Name) + assert.False(t, repo.Status.GeneratedDeployKeys["generated-existing"].WriteAccess) + +} + func TestSteps_CIVariables(t *testing.T) { scheme := runtime.NewScheme() utilruntime.Must(clientgoscheme.AddToScheme(scheme)) @@ -511,7 +608,7 @@ func TestSteps_CIVariables(t *testing.T) { } func fakeGitClientFactory(r *fakeRepo) gitClientFactory { - return func(ctx context.Context, instance *synv1alpha1.GitRepoTemplate, namespace string, reqLogger logr.Logger, client client.Client) (manager.Repo, string, error) { + return func(ctx context.Context, instance *synv1alpha1.GitRepo, reqLogger logr.Logger, client client.Client) (manager.Repo, string, error) { return r, "", nil } } diff --git a/docs/modules/ROOT/pages/references/api-reference.adoc b/docs/modules/ROOT/pages/references/api-reference.adoc index 977e8e7b..d131a35a 100644 --- a/docs/modules/ROOT/pages/references/api-reference.adoc +++ b/docs/modules/ROOT/pages/references/api-reference.adoc @@ -256,6 +256,7 @@ DeployKey defines an SSH key to be used for git operations. .Appears In: **** +- xref:{anchor_prefix}-github-com-projectsyn-lieutenant-operator-api-v1alpha1-deploykeystatus[$$DeployKeyStatus$$] - xref:{anchor_prefix}-github-com-projectsyn-lieutenant-operator-api-v1alpha1-gitrepospec[$$GitRepoSpec$$] - xref:{anchor_prefix}-github-com-projectsyn-lieutenant-operator-api-v1alpha1-gitrepotemplate[$$GitRepoTemplate$$] **** @@ -269,6 +270,45 @@ DeployKey defines an SSH key to be used for git operations. |=== +[id="{anchor_prefix}-github-com-projectsyn-lieutenant-operator-api-v1alpha1-deploykeystatus"] +=== DeployKeyStatus + +DeployKeyStatus tracks the status for a generated Deploy Key + +.Appears In: +**** +- xref:{anchor_prefix}-github-com-projectsyn-lieutenant-operator-api-v1alpha1-gitrepostatus[$$GitRepoStatus$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`type`* __string__ | Type defines what type the key is (rsa, ed25519, etc...) +| *`key`* __string__ | Key is the actual key +| *`writeAccess`* __boolean__ | WriteAccess if the key has RW access or not +| *`secretRef`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#localobjectreference-v1-core[$$LocalObjectReference$$]__ | SecretRef is the name of the secret in which the SSH keypair is stored. +|=== + + +[id="{anchor_prefix}-github-com-projectsyn-lieutenant-operator-api-v1alpha1-deploykeytemplate"] +=== DeployKeyTemplate + +DeployKeyTemplate defines an SSH key to be generated for git operations. + +.Appears In: +**** +- xref:{anchor_prefix}-github-com-projectsyn-lieutenant-operator-api-v1alpha1-gitrepospec[$$GitRepoSpec$$] +- xref:{anchor_prefix}-github-com-projectsyn-lieutenant-operator-api-v1alpha1-gitrepotemplate[$$GitRepoTemplate$$] +**** + +[cols="25a,75a", options="header"] +|=== +| Field | Description +| *`type`* __string__ | Type defines what type the key is. For key generation, currently only `ssh-rsa` and `ssh-ed25519` are supported. +| *`writeAccess`* __boolean__ | WriteAccess if the key has RW access or not +|=== + + [id="{anchor_prefix}-github-com-projectsyn-lieutenant-operator-api-v1alpha1-envvar"] === EnvVar @@ -388,6 +428,7 @@ GitRepoSpec defines the desired state of GitRepo | Field | Description | *`apiSecretRef`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#secretreference-v1-core[$$SecretReference$$]__ | APISecretRef reference to secret containing connection information | *`deployKeys`* __object (keys:string, values:xref:{anchor_prefix}-github-com-projectsyn-lieutenant-operator-api-v1alpha1-deploykey[$$DeployKey$$])__ | DeployKeys optional list of SSH deploy keys. If not set, not deploy keys will be configured +| *`generatedDeployKeys`* __object (keys:string, values:xref:{anchor_prefix}-github-com-projectsyn-lieutenant-operator-api-v1alpha1-deploykeytemplate[$$DeployKeyTemplate$$])__ | Path to Git repository | *`path`* __string__ | Path to Git repository | *`repoName`* __string__ | RepoName name of Git repository | *`repoType`* __xref:{anchor_prefix}-github-com-projectsyn-lieutenant-operator-api-v1alpha1-repotype[$$RepoType$$]__ | RepoType specifies if a repo should be managed by the git controller. A value of 'unmanaged' means it's not manged by the controller @@ -432,6 +473,7 @@ controller creating the template instance. | Field | Description | *`apiSecretRef`* __link:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.20/#secretreference-v1-core[$$SecretReference$$]__ | APISecretRef reference to secret containing connection information | *`deployKeys`* __object (keys:string, values:xref:{anchor_prefix}-github-com-projectsyn-lieutenant-operator-api-v1alpha1-deploykey[$$DeployKey$$])__ | DeployKeys optional list of SSH deploy keys. If not set, not deploy keys will be configured +| *`generatedDeployKeys`* __object (keys:string, values:xref:{anchor_prefix}-github-com-projectsyn-lieutenant-operator-api-v1alpha1-deploykeytemplate[$$DeployKeyTemplate$$])__ | Path to Git repository | *`path`* __string__ | Path to Git repository | *`repoName`* __string__ | RepoName name of Git repository | *`repoType`* __xref:{anchor_prefix}-github-com-projectsyn-lieutenant-operator-api-v1alpha1-repotype[$$RepoType$$]__ | RepoType specifies if a repo should be managed by the git controller. A value of 'unmanaged' means it's not manged by the controller diff --git a/git/manager/manager.go b/git/manager/manager.go index d51f760c..f955a1eb 100644 --- a/git/manager/manager.go +++ b/git/manager/manager.go @@ -183,15 +183,15 @@ type CommitFile struct { // GetGitClient will return a git client from a provided template. This does a lot more // plumbing than the simple NewClient() call. If you're needing a git client from a // reconcile function, this is the way to go. -func GetGitClient(ctx context.Context, instance *synv1alpha1.GitRepoTemplate, namespace string, reqLogger logr.Logger, client client.Client) (Repo, string, error) { +func GetGitClient(ctx context.Context, instance *synv1alpha1.GitRepo, reqLogger logr.Logger, client client.Client) (Repo, string, error) { secret := &corev1.Secret{} namespacedName := types.NamespacedName{ - Name: instance.APISecretRef.Name, - Namespace: namespace, + Name: instance.Spec.APISecretRef.Name, + Namespace: instance.Namespace, } - if len(instance.APISecretRef.Namespace) > 0 { - namespacedName.Namespace = instance.APISecretRef.Namespace + if len(instance.Spec.APISecretRef.Namespace) > 0 { + namespacedName.Namespace = instance.Spec.APISecretRef.Namespace } err := client.Get(ctx, namespacedName, secret) @@ -212,7 +212,7 @@ func GetGitClient(ctx context.Context, instance *synv1alpha1.GitRepoTemplate, na return nil, "", fmt.Errorf("secret %s does not contain token", secret.GetName()) } - repoURL, err := url.Parse(string(secret.Data[SecretEndpointName]) + "/" + instance.Path + "/" + instance.RepoName) + repoURL, err := url.Parse(string(secret.Data[SecretEndpointName]) + "/" + instance.Spec.Path + "/" + instance.Spec.RepoName) if err != nil { return nil, "", err } @@ -226,19 +226,27 @@ func GetGitClient(ctx context.Context, instance *synv1alpha1.GitRepoTemplate, na sshHost = parsed } + deployKeysMerged := make(map[string]synv1alpha1.DeployKey) + for dk, dkc := range instance.Status.GeneratedDeployKeys { + deployKeysMerged[dk] = dkc.DeployKey + } + for dk, dkc := range instance.Spec.DeployKeys { + deployKeysMerged[dk] = dkc + } + repoOptions := RepoOptions{ Credentials: Credentials{ Token: string(secret.Data[SecretTokenName]), }, - DeployKeys: instance.DeployKeys, + DeployKeys: deployKeysMerged, Logger: reqLogger, - Path: instance.Path, - RepoName: instance.RepoName, - DisplayName: instance.DisplayName, + Path: instance.Spec.Path, + RepoName: instance.Spec.RepoName, + DisplayName: instance.Spec.DisplayName, URL: repoURL, SSHHost: sshHost, - TemplateFiles: instance.TemplateFiles, - DeletionPolicy: instance.DeletionPolicy, + TemplateFiles: instance.Spec.TemplateFiles, + DeletionPolicy: instance.Spec.DeletionPolicy, } repo, err := NewRepo(repoOptions) diff --git a/go.mod b/go.mod index c0d4466c..e9ca8c94 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.26.1 require ( dario.cat/mergo v1.0.2 github.com/banzaicloud/bank-vaults/pkg/sdk v0.8.3 + github.com/charmbracelet/keygen v0.5.4 github.com/go-logr/logr v1.4.3 github.com/go-logr/zapr v1.3.0 github.com/hashicorp/vault/api v1.23.0 diff --git a/go.sum b/go.sum index e30f4e2e..10161e5c 100644 --- a/go.sum +++ b/go.sum @@ -93,6 +93,8 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 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/charmbracelet/keygen v0.5.4 h1:XQYgf6UEaTGgQSSmiPpIQ78WfseNQp4Pz8N/c1OsrdA= +github.com/charmbracelet/keygen v0.5.4/go.mod h1:t4oBRr41bvK7FaJsAaAQhhkUuHslzFXVjOBwA55CZNM= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=