From 46f64fea6ab4952998978455f250a80a0731fce6 Mon Sep 17 00:00:00 2001 From: Andras Szerdahelyi Date: Tue, 31 May 2022 07:04:03 -0700 Subject: [PATCH] Seed job SCM may now be configured with github-branch-source/GitHubAppCredentials (#719) * Now allowing seed jobs to be configured with GithubAppCredential * fmt * Validation for GithubAppCredentials type seed job SCM secret * GithubAppCredentials validation error messages were not referring to actual GithubAppCredentials fields * cleanup --- api/v1alpha2/jenkins_types.go | 2 + pkg/configuration/user/seedjobs/seedjobs.go | 2 + pkg/configuration/user/seedjobs/validate.go | 30 ++- .../user/seedjobs/validate_test.go | 207 ++++++++++++++++++ 4 files changed, 240 insertions(+), 1 deletion(-) diff --git a/api/v1alpha2/jenkins_types.go b/api/v1alpha2/jenkins_types.go index 92e4b038..7de4f09b 100644 --- a/api/v1alpha2/jenkins_types.go +++ b/api/v1alpha2/jenkins_types.go @@ -527,6 +527,7 @@ const ( BasicSSHCredentialType JenkinsCredentialType = "basicSSHUserPrivateKey" // UsernamePasswordCredentialType define username & password Jenkins credential type UsernamePasswordCredentialType JenkinsCredentialType = "usernamePassword" + GithubAppCredentialType JenkinsCredentialType = "githubApp" // ExternalCredentialType defines other credential type ExternalCredentialType JenkinsCredentialType = "external" ) @@ -536,6 +537,7 @@ var AllowedJenkinsCredentialMap = map[string]string{ string(NoJenkinsCredentialCredentialType): "", string(BasicSSHCredentialType): "", string(UsernamePasswordCredentialType): "", + string(GithubAppCredentialType): "", string(ExternalCredentialType): "", } diff --git a/pkg/configuration/user/seedjobs/seedjobs.go b/pkg/configuration/user/seedjobs/seedjobs.go index dadc3e97..2b19d736 100644 --- a/pkg/configuration/user/seedjobs/seedjobs.go +++ b/pkg/configuration/user/seedjobs/seedjobs.go @@ -36,6 +36,8 @@ const ( // PrivateKeySecretKey is private key data key in Kubernetes secret used to create Jenkins SSH credential PrivateKeySecretKey = "privateKey" + AppIDSecretKey = "appId" + // JenkinsCredentialTypeLabelName is label for kubernetes-credentials-provider-plugin which determine Jenkins // credential type JenkinsCredentialTypeLabelName = "jenkins.io/credentials-type" diff --git a/pkg/configuration/user/seedjobs/validate.go b/pkg/configuration/user/seedjobs/validate.go index 5e656bd3..572c81c0 100644 --- a/pkg/configuration/user/seedjobs/validate.go +++ b/pkg/configuration/user/seedjobs/validate.go @@ -55,7 +55,8 @@ func (s *seedJobs) ValidateSeedJobs(jenkins v1alpha2.Jenkins) ([]string, error) } if seedJob.JenkinsCredentialType == v1alpha2.BasicSSHCredentialType || - seedJob.JenkinsCredentialType == v1alpha2.UsernamePasswordCredentialType { + seedJob.JenkinsCredentialType == v1alpha2.UsernamePasswordCredentialType || + seedJob.JenkinsCredentialType == v1alpha2.GithubAppCredentialType { secret := &v1.Secret{} namespaceName := types.NamespacedName{Namespace: jenkins.Namespace, Name: seedJob.CredentialID} err := s.Client.Get(context.TODO(), namespaceName, secret) @@ -79,6 +80,13 @@ func (s *seedJobs) ValidateSeedJobs(jenkins v1alpha2.Jenkins) ([]string, error) } } } + if seedJob.JenkinsCredentialType == v1alpha2.GithubAppCredentialType { + if msg := validateGithubAppSecret(*secret); len(msg) > 0 { + for _, m := range msg { + messages = append(messages, fmt.Sprintf("seedJob `%s` %s", seedJob.ID, m)) + } + } + } } if len(seedJob.BuildPeriodically) > 0 { @@ -219,6 +227,26 @@ func validateUsernamePasswordSecret(secret v1.Secret) []string { return messages } +func validateGithubAppSecret(secret v1.Secret) []string { + var messages []string + appid, exists := secret.Data[AppIDSecretKey] + if !exists { + messages = append(messages, fmt.Sprintf("required data '%s' not found in secret '%s'", AppIDSecretKey, secret.ObjectMeta.Name)) + } + if len(appid) == 0 { + messages = append(messages, fmt.Sprintf("required data '%s' is empty in secret '%s'", AppIDSecretKey, secret.ObjectMeta.Name)) + } + pkey, exists := secret.Data[PrivateKeySecretKey] + if !exists { + messages = append(messages, fmt.Sprintf("required data '%s' not found in secret '%s'", PrivateKeySecretKey, secret.ObjectMeta.Name)) + } + if len(pkey) == 0 { + messages = append(messages, fmt.Sprintf("required data '%s' is empty in secret '%s'", PrivateKeySecretKey, secret.ObjectMeta.Name)) + } + + return messages +} + func validatePrivateKey(privateKey string) error { _, err := ssh.ParseRawPrivateKey([]byte(privateKey)) if err != nil { diff --git a/pkg/configuration/user/seedjobs/validate_test.go b/pkg/configuration/user/seedjobs/validate_test.go index 1f94d147..015bb2b3 100644 --- a/pkg/configuration/user/seedjobs/validate_test.go +++ b/pkg/configuration/user/seedjobs/validate_test.go @@ -676,6 +676,213 @@ func TestValidateSeedJobs(t *testing.T) { assert.Equal(t, result, []string{"seedJob `example` required data 'password' not found in secret 'deploy-keys'", "seedJob `example` required data 'password' is empty in secret 'deploy-keys'"}) }) + t.Run("Valid with appId and privateKey", func(t *testing.T) { + jenkins := v1alpha2.Jenkins{ + ObjectMeta: jenkinsObjectMeta, + Spec: v1alpha2.JenkinsSpec{ + SeedJobs: []v1alpha2.SeedJob{ + { + ID: "example", + CredentialID: "deploy-keys", + JenkinsCredentialType: v1alpha2.GithubAppCredentialType, + Targets: "cicd/jobs/*.jenkins", + RepositoryBranch: "master", + RepositoryURL: "https://github.com/jenkinsci/kubernetes-operator.git", + }, + }, + }, + } + secret := &corev1.Secret{ + TypeMeta: secretTypeMeta, + ObjectMeta: secretObjectMeta, + Data: map[string][]byte{ + AppIDSecretKey: []byte("some-id"), + PrivateKeySecretKey: []byte("some-key"), + }, + } + fakeClient := fake.NewClientBuilder().Build() + err := fakeClient.Create(context.TODO(), secret) + assert.NoError(t, err) + + config := configuration.Configuration{ + Client: fakeClient, + ClientSet: kubernetes.Clientset{}, + Notifications: nil, + Jenkins: &v1alpha2.Jenkins{}, + } + + seedJobs := New(nil, config) + result, err := seedJobs.ValidateSeedJobs(jenkins) + + assert.NoError(t, err) + assert.Nil(t, result) + }) + t.Run("Invalid with empty app id", func(t *testing.T) { + jenkins := v1alpha2.Jenkins{ + ObjectMeta: jenkinsObjectMeta, + Spec: v1alpha2.JenkinsSpec{ + SeedJobs: []v1alpha2.SeedJob{ + { + ID: "example", + CredentialID: "deploy-keys", + JenkinsCredentialType: v1alpha2.GithubAppCredentialType, + Targets: "cicd/jobs/*.jenkins", + RepositoryBranch: "master", + RepositoryURL: "https://github.com/jenkinsci/kubernetes-operator.git", + }, + }, + }, + } + secret := &corev1.Secret{ + TypeMeta: secretTypeMeta, + ObjectMeta: secretObjectMeta, + Data: map[string][]byte{ + AppIDSecretKey: []byte(""), + PrivateKeySecretKey: []byte("some-key"), + }, + } + fakeClient := fake.NewClientBuilder().Build() + err := fakeClient.Create(context.TODO(), secret) + assert.NoError(t, err) + + config := configuration.Configuration{ + Client: fakeClient, + ClientSet: kubernetes.Clientset{}, + Notifications: nil, + Jenkins: &v1alpha2.Jenkins{}, + } + + seedJobs := New(nil, config) + result, err := seedJobs.ValidateSeedJobs(jenkins) + + assert.NoError(t, err) + + assert.Equal(t, result, []string{"seedJob `example` required data 'appId' is empty in secret 'deploy-keys'"}) + }) + t.Run("Invalid with empty private key", func(t *testing.T) { + jenkins := v1alpha2.Jenkins{ + ObjectMeta: jenkinsObjectMeta, + Spec: v1alpha2.JenkinsSpec{ + SeedJobs: []v1alpha2.SeedJob{ + { + ID: "example", + CredentialID: "deploy-keys", + JenkinsCredentialType: v1alpha2.GithubAppCredentialType, + Targets: "cicd/jobs/*.jenkins", + RepositoryBranch: "master", + RepositoryURL: "https://github.com/jenkinsci/kubernetes-operator.git", + }, + }, + }, + } + secret := &corev1.Secret{ + TypeMeta: secretTypeMeta, + ObjectMeta: secretObjectMeta, + Data: map[string][]byte{ + AppIDSecretKey: []byte("some-id"), + PrivateKeySecretKey: []byte(""), + }, + } + fakeClient := fake.NewClientBuilder().Build() + err := fakeClient.Create(context.TODO(), secret) + assert.NoError(t, err) + + config := configuration.Configuration{ + Client: fakeClient, + ClientSet: kubernetes.Clientset{}, + Notifications: nil, + Jenkins: &v1alpha2.Jenkins{}, + } + + seedJobs := New(nil, config) + result, err := seedJobs.ValidateSeedJobs(jenkins) + + assert.NoError(t, err) + + assert.Equal(t, result, []string{"seedJob `example` required data 'privateKey' is empty in secret 'deploy-keys'"}) + }) + t.Run("Invalid without app id", func(t *testing.T) { + jenkins := v1alpha2.Jenkins{ + ObjectMeta: jenkinsObjectMeta, + Spec: v1alpha2.JenkinsSpec{ + SeedJobs: []v1alpha2.SeedJob{ + { + ID: "example", + CredentialID: "deploy-keys", + JenkinsCredentialType: v1alpha2.GithubAppCredentialType, + Targets: "cicd/jobs/*.jenkins", + RepositoryBranch: "master", + RepositoryURL: "https://github.com/jenkinsci/kubernetes-operator.git", + }, + }, + }, + } + secret := &corev1.Secret{ + TypeMeta: secretTypeMeta, + ObjectMeta: secretObjectMeta, + Data: map[string][]byte{ + PrivateKeySecretKey: []byte("some-key"), + }, + } + fakeClient := fake.NewClientBuilder().Build() + err := fakeClient.Create(context.TODO(), secret) + assert.NoError(t, err) + + config := configuration.Configuration{ + Client: fakeClient, + ClientSet: kubernetes.Clientset{}, + Notifications: nil, + Jenkins: &v1alpha2.Jenkins{}, + } + + seedJobs := New(nil, config) + result, err := seedJobs.ValidateSeedJobs(jenkins) + + assert.NoError(t, err) + + assert.Equal(t, result, []string{"seedJob `example` required data 'appId' not found in secret 'deploy-keys'", "seedJob `example` required data 'appId' is empty in secret 'deploy-keys'"}) + }) + t.Run("Invalid without private key", func(t *testing.T) { + jenkins := v1alpha2.Jenkins{ + ObjectMeta: jenkinsObjectMeta, + Spec: v1alpha2.JenkinsSpec{ + SeedJobs: []v1alpha2.SeedJob{ + { + ID: "example", + CredentialID: "deploy-keys", + JenkinsCredentialType: v1alpha2.GithubAppCredentialType, + Targets: "cicd/jobs/*.jenkins", + RepositoryBranch: "master", + RepositoryURL: "https://github.com/jenkinsci/kubernetes-operator.git", + }, + }, + }, + } + secret := &corev1.Secret{ + TypeMeta: secretTypeMeta, + ObjectMeta: secretObjectMeta, + Data: map[string][]byte{ + AppIDSecretKey: []byte("some-username"), + }, + } + fakeClient := fake.NewClientBuilder().Build() + err := fakeClient.Create(context.TODO(), secret) + assert.NoError(t, err) + + config := configuration.Configuration{ + Client: fakeClient, + ClientSet: kubernetes.Clientset{}, + Notifications: nil, + Jenkins: &v1alpha2.Jenkins{}, + } + + seedJobs := New(nil, config) + result, err := seedJobs.ValidateSeedJobs(jenkins) + + assert.NoError(t, err) + + assert.Equal(t, result, []string{"seedJob `example` required data 'privateKey' not found in secret 'deploy-keys'", "seedJob `example` required data 'privateKey' is empty in secret 'deploy-keys'"}) + }) t.Run("Invalid with wrong cron spec", func(t *testing.T) { jenkins := v1alpha2.Jenkins{ Spec: v1alpha2.JenkinsSpec{