diff --git a/glide.lock b/glide.lock index ef2c87b372f..114ac7db69b 100644 --- a/glide.lock +++ b/glide.lock @@ -462,7 +462,6 @@ imports: - pkg/apis/pipeline - pkg/apis/pipeline/pod - pkg/apis/pipeline/v1alpha1 - - pkg/apis/pipeline/v1alpha2 - pkg/apis/resource/v1alpha1 - pkg/apis/validate - pkg/contexts diff --git a/pkg/odo/cli/pipelines/bootstrap.go b/pkg/odo/cli/pipelines/bootstrap.go index 56c8c3565db..52739e17b65 100644 --- a/pkg/odo/cli/pipelines/bootstrap.go +++ b/pkg/odo/cli/pipelines/bootstrap.go @@ -25,38 +25,39 @@ var ( bootstrapShortDesc = `Bootstrap pipelines` ) -// BootstrapOptions encapsulates the options for the odo pipelines bootstrap +// BootstrapParameters encapsulates the paratmeters for the odo pipelines bootstrap // command. -type BootstrapOptions struct { - deploymentPath string - quayUsername string - gitRepo string // e.g. tekton/triggers - prefix string // used to generate the environments in a shared cluster - githubToken string - quayIOAuthFilename string - skipChecks bool +type BootstrapParameters struct { + deploymentPath string + githubToken string + gitRepo string // e.g. tekton/triggers + imageRepo string + internalRegistryHostname string + prefix string // used to generate the environments in a shared cluster + dockerConfigJSONFileName string + skipChecks bool // generic context options common to all commands *genericclioptions.Context } -// NewBootstrapOptions bootstraps a BootstrapOptions instance. -func NewBootstrapOptions() *BootstrapOptions { - return &BootstrapOptions{} +// NewBootstrapParameters bootstraps a BootstrapParameters instance. +func NewBootstrapParameters() *BootstrapParameters { + return &BootstrapParameters{} } -// Complete completes BootstrapOptions after they've been created. +// Complete completes BootstrapParameters after they've been created. // // If the prefix provided doesn't have a "-" then one is added, this makes the // generated environment names nicer to read. -func (bo *BootstrapOptions) Complete(name string, cmd *cobra.Command, args []string) error { +func (bo *BootstrapParameters) Complete(name string, cmd *cobra.Command, args []string) error { if bo.prefix != "" && !strings.HasSuffix(bo.prefix, "-") { bo.prefix = bo.prefix + "-" } return nil } -// Validate validates the parameters of the BootstrapOptions. -func (bo *BootstrapOptions) Validate() error { +// Validate validates the parameters of the BootstrapParameters. +func (bo *BootstrapParameters) Validate() error { // TODO: this won't work with GitLab as the repo can have more path elements. if len(strings.Split(bo.gitRepo, "/")) != 2 { return fmt.Errorf("repo must be org/repo: %s", bo.gitRepo) @@ -65,22 +66,24 @@ func (bo *BootstrapOptions) Validate() error { } // Run runs the project bootstrap command. -func (bo *BootstrapOptions) Run() error { - options := pipelines.BootstrapOptions{ - DeploymentPath: bo.deploymentPath, - GithubToken: bo.githubToken, - GitRepo: bo.gitRepo, - Prefix: bo.prefix, - QuayAuthFileName: bo.quayIOAuthFilename, - QuayUserName: bo.quayUsername, - SkipChecks: bo.skipChecks, +func (bo *BootstrapParameters) Run() error { + options := pipelines.BootstrapParameters{ + DeploymentPath: bo.deploymentPath, + GithubToken: bo.githubToken, + GitRepo: bo.gitRepo, + ImageRepo: bo.imageRepo, + InternalRegistryHostname: bo.internalRegistryHostname, + Prefix: bo.prefix, + DockerConfigJSONFileName: bo.dockerConfigJSONFileName, + SkipChecks: bo.skipChecks, } + return pipelines.Bootstrap(&options) } // NewCmdBootstrap creates the project bootstrap command. func NewCmdBootstrap(name, fullName string) *cobra.Command { - o := NewBootstrapOptions() + o := NewBootstrapParameters() bootstrapCmd := &cobra.Command{ Use: name, @@ -93,16 +96,17 @@ func NewCmdBootstrap(name, fullName string) *cobra.Command { } bootstrapCmd.Flags().StringVarP(&o.prefix, "prefix", "p", "", "add a prefix to the environment names") - bootstrapCmd.Flags().StringVar(&o.quayUsername, "quay-username", "", "image registry username") - bootstrapCmd.MarkFlagRequired("quay-username") bootstrapCmd.Flags().StringVar(&o.githubToken, "github-token", "", "provide the Github token") bootstrapCmd.MarkFlagRequired("github-token") - bootstrapCmd.Flags().StringVar(&o.quayIOAuthFilename, "dockerconfigjson", "", "Docker configuration json filename") - bootstrapCmd.MarkFlagRequired("dockerconfigjson") + bootstrapCmd.Flags().StringVar(&o.dockerConfigJSONFileName, "dockerconfigjson", "", "Docker configuration json filename") bootstrapCmd.Flags().StringVar(&o.gitRepo, "git-repo", "", "git repository in this form /") bootstrapCmd.MarkFlagRequired("git-repo") + bootstrapCmd.Flags().StringVar(&o.imageRepo, "image-repo", "", "image repository in this form // or / for internal registry") + bootstrapCmd.MarkFlagRequired("image-repo") bootstrapCmd.Flags().StringVar(&o.deploymentPath, "deployment-path", "", "deployment folder path name") bootstrapCmd.MarkFlagRequired("deployment-path") bootstrapCmd.Flags().BoolVarP(&o.skipChecks, "skip-checks", "b", false, "skip Tekton installation checks") + bootstrapCmd.Flags().StringVar(&o.internalRegistryHostname, "internal-registry-hostname", "image-registry.openshift-image-registry.svc:5000", "internal image registry hostname") + return bootstrapCmd } diff --git a/pkg/odo/cli/pipelines/bootstrap_test.go b/pkg/odo/cli/pipelines/bootstrap_test.go index 4f3ec7bee8d..5fa686bbf7e 100644 --- a/pkg/odo/cli/pipelines/bootstrap_test.go +++ b/pkg/odo/cli/pipelines/bootstrap_test.go @@ -13,7 +13,7 @@ type keyValuePair struct { value string } -func TestCompleteBootstrapOptions(t *testing.T) { +func TestCompleteBootstrapParameters(t *testing.T) { completeTests := []struct { name string prefix string @@ -25,7 +25,7 @@ func TestCompleteBootstrapOptions(t *testing.T) { } for _, tt := range completeTests { - o := BootstrapOptions{prefix: tt.prefix} + o := BootstrapParameters{prefix: tt.prefix} err := o.Complete("test", &cobra.Command{}, []string{"test", "test/repo"}) @@ -39,7 +39,7 @@ func TestCompleteBootstrapOptions(t *testing.T) { } } -func TestValidateBootstrapOptions(t *testing.T) { +func TestValidateBootstrapParameters(t *testing.T) { optionTests := []struct { name string gitRepo string @@ -50,7 +50,7 @@ func TestValidateBootstrapOptions(t *testing.T) { } for _, tt := range optionTests { - o := BootstrapOptions{quayUsername: "testing", gitRepo: tt.gitRepo, prefix: "test"} + o := BootstrapParameters{gitRepo: tt.gitRepo, prefix: "test"} err := o.Validate() @@ -64,25 +64,33 @@ func TestValidateBootstrapOptions(t *testing.T) { } } } + func TestBootstrapCommandWithMissingParams(t *testing.T) { cmdTests := []struct { + desc string flags []keyValuePair wantErr string }{ - {[]keyValuePair{flag("quay-username", "example"), flag("github-token", "abc123"), flag("dockerconfigjson", "~/"), - flag("deployment-path", "foo")}, `Required flag(s) "git-repo" have/has not been set`}, - {[]keyValuePair{flag("quay-username", "example"), flag("github-token", "abc123"), flag("git-repo", "example/repo"), - flag("deployment-path", "foo")}, `Required flag(s) "dockerconfigjson" have/has not been set`}, - {[]keyValuePair{flag("quay-username", "example"), flag("dockerconfigjson", "~/"), flag("git-repo", "example/repo"), - flag("deployment-path", "foo")}, `Required flag(s) "github-token" have/has not been set`}, - {[]keyValuePair{flag("github-token", "abc123"), flag("dockerconfigjson", "~/"), flag("git-repo", "example/repo"), - flag("deployment-path", "foo")}, `Required flag(s) "quay-username" have/has not been set`}, + {"Missing git-repo flag", + []keyValuePair{flag("github-token", "abc123"), + flag("dockerconfigjson", "~/"), flag("image-repo", "foo/bar/bar"), flag("deployment-path", "foo")}, + `Required flag(s) "git-repo" have/has not been set`}, + {"Missing github-token flag", + []keyValuePair{flag("dockerconfigjson", "~/"), + flag("git-repo", "example/repo"), flag("image-repo", "foo/bar/bar"), flag("deployment-path", "foo")}, + `Required flag(s) "github-token" have/has not been set`}, + {"Missing image-repo", + []keyValuePair{flag("github-token", "abc123"), + flag("dockerconfigjson", "~/"), flag("git-repo", "example/repo"), flag("deployment-path", "foo")}, + `Required flag(s) "image-repo" have/has not been set`}, } for _, tt := range cmdTests { - _, _, err := executeCommand(NewCmdBootstrap("bootstrap", "odo pipelines bootstrap"), tt.flags...) - if err.Error() != tt.wantErr { - t.Errorf("got %s, want %s", err, tt.wantErr) - } + t.Run(tt.desc, func(t *testing.T) { + _, _, err := executeCommand(NewCmdBootstrap("bootstrap", "odo pipelines bootstrap"), tt.flags...) + if err.Error() != tt.wantErr { + t.Errorf("got %s, want %s", err, tt.wantErr) + } + }) } } @@ -97,17 +105,19 @@ func TestBypassChecks(t *testing.T) { } for _, test := range tests { - o := BootstrapOptions{skipChecks: test.skipChecks} + t.Run(test.description, func(t *testing.T) { + o := BootstrapParameters{skipChecks: test.skipChecks} - err := o.Complete("test", &cobra.Command{}, []string{"test", "test/repo"}) + err := o.Complete("test", &cobra.Command{}, []string{"test", "test/repo"}) - if err != nil { - t.Errorf("Complete() %#v failed: ", err) - } + if err != nil { + t.Errorf("Complete() %#v failed: ", err) + } - if o.skipChecks != test.wantedBypassChecks { - t.Errorf("Complete() %#v bypassChecks flag: got %v, want %v", test.description, o.skipChecks, test.wantedBypassChecks) - } + if o.skipChecks != test.wantedBypassChecks { + t.Errorf("Complete() %#v bypassChecks flag: got %v, want %v", test.description, o.skipChecks, test.wantedBypassChecks) + } + }) } } diff --git a/pkg/pipelines/bootstrap.go b/pkg/pipelines/bootstrap.go index 29ccc93753a..09951177688 100644 --- a/pkg/pipelines/bootstrap.go +++ b/pkg/pipelines/bootstrap.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "os" + "strings" corev1 "k8s.io/api/core/v1" v1rbac "k8s.io/api/rbac/v1" @@ -40,21 +41,22 @@ var ( } ) -// BootstrapOptions is a struct that provides the optional flags -type BootstrapOptions struct { - DeploymentPath string - GithubToken string - GitRepo string - Prefix string - QuayAuthFileName string - QuayUserName string - SkipChecks bool +// BootstrapParameters is a struct that provides the optional flags +type BootstrapParameters struct { + DeploymentPath string + GithubToken string + GitRepo string + InternalRegistryHostname string + ImageRepo string + Prefix string + DockerConfigJSONFileName string + SkipChecks bool } // Bootstrap is the main driver for getting OpenShift pipelines for GitOps // configured with a basic configuration. -func Bootstrap(o *BootstrapOptions) error { - // First, check for Tekton. We proceed only if Tekton is installed +func Bootstrap(o *BootstrapParameters) error { + if !o.SkipChecks { installed, err := checkTektonInstall() if err != nil { @@ -64,6 +66,12 @@ func Bootstrap(o *BootstrapOptions) error { return errors.New("failed due to Tekton Pipelines or Triggers are not installed") } } + + isInternalRegistry, imageRepo, err := validateImageRepo(o) + if err != nil { + return err + } + outputs := make([]interface{}, 0) namespaces := namespaceNames(o.Prefix) for _, n := range createNamespaces(values(namespaces)) { @@ -76,21 +84,14 @@ func Bootstrap(o *BootstrapOptions) error { } outputs = append(outputs, githubAuth) - // Create Docker Secret - dockerSecret, err := createDockerSecret(o.QuayAuthFileName, namespaces["cicd"]) - if err != nil { - return err - } - outputs = append(outputs, dockerSecret) - // Create Tasks - tasks := tasks.Generate(githubAuth.GetName(), namespaces["cicd"]) + tasks := tasks.Generate(githubAuth.GetName(), namespaces["cicd"], isInternalRegistry) for _, task := range tasks { outputs = append(outputs, task) } // Create trigger templates - templates := triggers.GenerateTemplates(namespaces["cicd"], saName) + templates := triggers.GenerateTemplates(namespaces["cicd"], saName, imageRepo) for _, template := range templates { outputs = append(outputs, template) } @@ -112,16 +113,23 @@ func Bootstrap(o *BootstrapOptions) error { route := routes.Generate(namespaces["cicd"]) outputs = append(outputs, route) - // Create Service Account, Role, Role Bindings, and ClusterRole Bindings - outputs = append(outputs, createRoleBindings(namespaces)...) + // Create secret, role binding, namespaces for using image repo + sa := createServiceAccount(meta.NamespacedName(namespaces["cicd"], saName)) + manifests, err := createManifestsForImageRepo(sa, isInternalRegistry, imageRepo, o, namespaces) + if err != nil { + return err + } + outputs = append(outputs, manifests...) + + // Create Role, Role Bindings, and ClusterRole Bindings + outputs = append(outputs, createRoleBindings(namespaces, sa)...) return marshalOutputs(os.Stdout, outputs) } -func createRoleBindings(ns map[string]string) []interface{} { +func createRoleBindings(ns map[string]string, sa *corev1.ServiceAccount) []interface{} { out := make([]interface{}, 0) - sa := createServiceAccount(meta.NamespacedName(ns["cicd"], saName), dockerSecretName) - out = append(out, sa) + role := createRole(meta.NamespacedName(ns["cicd"], roleName), rules) out = append(out, role) out = append(out, createRoleBinding(meta.NamespacedName(ns["cicd"], roleBindingName), sa, role.Kind, role.Name)) @@ -132,6 +140,43 @@ func createRoleBindings(ns map[string]string) []interface{} { return out } +// createManifestsForImageRepo creates manifests like namespaces, secret, and role bindng for using image repo +func createManifestsForImageRepo(sa *corev1.ServiceAccount, isInternalRegistry bool, imageRepo string, o *BootstrapParameters, namespaces map[string]string) ([]interface{}, error) { + out := make([]interface{}, 0) + + if isInternalRegistry { + // add sa to outputs + out = append(out, sa) + // Provide access to service account for using internal registry + internalRegistryNamespace := strings.Split(imageRepo, "/")[1] + + clientSet, err := getClientSet() + if err != nil { + return nil, err + } + namespaceExists, err := checkNamespace(clientSet, internalRegistryNamespace) + if err != nil { + return nil, err + } + if !namespaceExists { + out = append(out, createNamespace(internalRegistryNamespace)) + } + + out = append(out, createRoleBinding(meta.NamespacedName(internalRegistryNamespace, "internal-registry-binding"), sa, "ClusterRole", "edit")) + } else { + // Add secret to service account if external registry is used + dockerSecret, err := createDockerSecret(o.DockerConfigJSONFileName, namespaces["cicd"]) + if err != nil { + return nil, err + } + out = append(out, dockerSecret) + // add secret and sa to outputs + out = append(out, addSecretToSA(sa, dockerSecretName)) + } + + return out, nil +} + func createPipelines(ns map[string]string, deploymentPath string) []interface{} { out := make([]interface{}, 0) out = append(out, createDevCIPipeline(meta.NamespacedName(ns["cicd"], "dev-ci-pipeline"))) @@ -143,9 +188,12 @@ func createPipelines(ns map[string]string, deploymentPath string) []interface{} } // createDockerSecret creates Docker secret -func createDockerSecret(quayIOAuthFilename, ns string) (*corev1.Secret, error) { +func createDockerSecret(dockerConfigJSONFileName, ns string) (*corev1.Secret, error) { + if dockerConfigJSONFileName == "" { + return nil, errors.New("failed to generate path to file: --dockerconfigjson flag is not provided") + } - authJSONPath, err := homedir.Expand(quayIOAuthFilename) + authJSONPath, err := homedir.Expand(dockerConfigJSONFileName) if err != nil { return nil, fmt.Errorf("failed to generate path to file: %w", err) } @@ -197,3 +245,47 @@ func marshalOutputs(out io.Writer, outputs []interface{}) error { } return nil } + +// validateImageRepo validates the input image repo. It determines if it is +// for internal registry and prepend internal registry hostname if neccessary. +func validateImageRepo(o *BootstrapParameters) (bool, string, error) { + components := strings.Split(o.ImageRepo, "/") + + // repo url has minimum of 2 components + if len(components) < 2 { + return false, "", imageRepoValidationError(o.ImageRepo) + } + + for _, v := range components { + // check for empty components + if strings.TrimSpace(v) == "" { + return false, "", imageRepoValidationError(o.ImageRepo) + } + // check for white spaces + if len(v) > len(strings.TrimSpace(v)) { + return false, "", imageRepoValidationError(o.ImageRepo) + } + } + + if len(components) == 2 { + if components[0] == "docker.io" || components[0] == "quay.io" { + // we recognize docker.io and quay.io. It is missing one component + return false, "", imageRepoValidationError(o.ImageRepo) + } + // We have format like / which is an internal registry. + // We prepend the internal registry hostname. + return true, o.InternalRegistryHostname + "/" + o.ImageRepo, nil + } + + // Check the first component to see if it is an internal registry + if len(components) == 3 { + return components[0] == o.InternalRegistryHostname, o.ImageRepo, nil + } + + // > 3 components. invalid repo + return false, "", imageRepoValidationError(o.ImageRepo) +} + +func imageRepoValidationError(imageRepo string) error { + return fmt.Errorf("failed to parse image repo:%s, expected image repository in the form // or / for internal registry", imageRepo) +} diff --git a/pkg/pipelines/bootstrap_test.go b/pkg/pipelines/bootstrap_test.go index 6af4c599417..68233ea9f6a 100644 --- a/pkg/pipelines/bootstrap_test.go +++ b/pkg/pipelines/bootstrap_test.go @@ -1 +1,180 @@ package pipelines + +import ( + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestValidateImageRepo(t *testing.T) { + + errorMsg := "failed to parse image repo:%s, expected image repository in the form // or / for internal registry" + + tests := []struct { + description string + options BootstrapParameters + expectedError string + expectedIsInternalRegistry bool + expectedImageRepo string + }{ + { + "Valid image regsitry URL", + BootstrapParameters{ + InternalRegistryHostname: "image-registry.openshift-image-registry.svc:5000", + ImageRepo: "quay.io/sample-user/sample-repo", + }, + "", + false, + "quay.io/sample-user/sample-repo", + }, + { + "Valid image regsitry URL random registry", + BootstrapParameters{ + InternalRegistryHostname: "image-registry.openshift-image-registry.svc:5000", + ImageRepo: "random.io/sample-user/sample-repo", + }, + "", + false, + "random.io/sample-user/sample-repo", + }, + { + "Valid image regsitry URL docker.io", + BootstrapParameters{ + InternalRegistryHostname: "image-registry.openshift-image-registry.svc:5000", + ImageRepo: "docker.io/sample-user/sample-repo", + }, + "", + false, + "docker.io/sample-user/sample-repo", + }, + { + "Invalid image registry URL with missing repo name", + BootstrapParameters{ + InternalRegistryHostname: "image-registry.openshift-image-registry.svc:5000", + ImageRepo: "quay.io/sample-user", + }, + fmt.Sprintf(errorMsg, "quay.io/sample-user"), + false, + "", + }, + { + "Invalid image registry URL with missing repo name docker.io", + BootstrapParameters{ + InternalRegistryHostname: "image-registry.openshift-image-registry.svc:5000", + ImageRepo: "docker.io/sample-user", + }, + fmt.Sprintf(errorMsg, "docker.io/sample-user"), + false, + "", + }, + { + "Invalid image registry URL with whitespaces", + BootstrapParameters{ + InternalRegistryHostname: "image-registry.openshift-image-registry.svc:5000", + ImageRepo: "quay.io/sample-user/ ", + }, + fmt.Sprintf(errorMsg, "quay.io/sample-user/ "), + false, + "", + }, + { + "Invalid image registry URL with whitespaces in between", + BootstrapParameters{ + InternalRegistryHostname: "image-registry.openshift-image-registry.svc:5000", + ImageRepo: "quay.io/sam\tple-user/", + }, + fmt.Sprintf(errorMsg, "quay.io/sam\tple-user/"), + false, + "", + }, + { + "Invalid image registry URL with leading whitespaces", + BootstrapParameters{ + InternalRegistryHostname: "image-registry.openshift-image-registry.svc:5000", + ImageRepo: "quay.io/ sample-user/", + }, + fmt.Sprintf(errorMsg, "quay.io/ sample-user/"), + false, + "", + }, + { + "Valid internal registry URL", + BootstrapParameters{ + InternalRegistryHostname: "image-registry.openshift-image-registry.svc:5000", + ImageRepo: "image-registry.openshift-image-registry.svc:5000/project/app", + }, + "", + true, + "image-registry.openshift-image-registry.svc:5000/project/app", + }, + { + "Invalid internal registry URL implicit starts with '/'", + BootstrapParameters{ + InternalRegistryHostname: "image-registry.openshift-image-registry.svc:5000", + ImageRepo: "/project/app", + }, + fmt.Sprintf(errorMsg, "/project/app"), + false, + "", + }, + { + "Valid internal registry URL implicit", + BootstrapParameters{ + InternalRegistryHostname: "image-registry.openshift-image-registry.svc:5000", + ImageRepo: "project/app", + }, + "", + true, + "image-registry.openshift-image-registry.svc:5000/project/app", + }, + { + "Invalid too many URL components docker", + BootstrapParameters{ + InternalRegistryHostname: "image-registry.openshift-image-registry.svc:5000", + ImageRepo: "docker.io/foo/project/app", + }, + fmt.Sprintf(errorMsg, "docker.io/foo/project/app"), + false, + "", + }, + { + "Invalid too many URL components internal", + BootstrapParameters{ + InternalRegistryHostname: "image-registry.openshift-image-registry.svc:5000", + ImageRepo: "image-registry.openshift-image-registry.svc:5000/project/app/foo", + }, + fmt.Sprintf(errorMsg, "image-registry.openshift-image-registry.svc:5000/project/app/foo"), + false, + "", + }, + { + "Invalid not enough URL components, no slash", + BootstrapParameters{ + InternalRegistryHostname: "image-registry.openshift-image-registry.svc:5000", + ImageRepo: "docker.io", + }, + fmt.Sprintf(errorMsg, "docker.io"), + false, + "", + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + isInternalRegistry, imageRepo, error := validateImageRepo(&test.options) + if diff := cmp.Diff(isInternalRegistry, test.expectedIsInternalRegistry); diff != "" { + t.Errorf("validateImageRepo() failed:\n%s", diff) + } + if diff := cmp.Diff(imageRepo, test.expectedImageRepo); diff != "" { + t.Errorf("validateImageRepo() failed:\n%s", diff) + } + errorString := "" + if error != nil { + errorString = error.Error() + } + if diff := cmp.Diff(errorString, test.expectedError); diff != "" { + t.Errorf("validateImageRepo() failed:\n%s", diff) + } + }) + } +} diff --git a/pkg/pipelines/namespaces.go b/pkg/pipelines/namespaces.go index b55a86b8218..6513d11ddc5 100644 --- a/pkg/pipelines/namespaces.go +++ b/pkg/pipelines/namespaces.go @@ -6,6 +6,7 @@ import ( "github.com/openshift/odo/pkg/pipelines/meta" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" ) var namespaceBaseNames = map[string]string{ @@ -39,3 +40,23 @@ func createNamespace(name string) *corev1.Namespace { } return ns } + +func getClientSet() (*kubernetes.Clientset, error) { + clientConfig, err := getClientConfig() + if err != nil { + return nil, fmt.Errorf("failed to get client config due to %w", err) + } + clientSet, err := kubernetes.NewForConfig(clientConfig) + if err != nil { + return nil, fmt.Errorf("failed to get APIs client due to %w", err) + } + return clientSet, nil +} + +func checkNamespace(clientSet kubernetes.Interface, name string) (bool, error) { + _, err := clientSet.CoreV1().Namespaces().Get(name, metav1.GetOptions{}) + if err != nil { + return false, nil + } + return true, nil +} diff --git a/pkg/pipelines/namespaces_test.go b/pkg/pipelines/namespaces_test.go index 19962591387..40f4625a605 100644 --- a/pkg/pipelines/namespaces_test.go +++ b/pkg/pipelines/namespaces_test.go @@ -6,6 +6,7 @@ import ( "github.com/google/go-cmp/cmp" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + testclient "k8s.io/client-go/kubernetes/fake" ) func TestCreateNamespace(t *testing.T) { @@ -52,3 +53,32 @@ func TestCreateNamespaces(t *testing.T) { t.Fatalf("createNamespaces() failed got\n%s", diff) } } + +func TestCheckNamespace(t *testing.T) { + tests := []struct { + desc string + namespace string + valid bool + }{ + { + "Namespace sample already exists", + "sample", + true, + }, + { + "Namespace test doesn't exist", + "test", + false, + }, + } + validNamespace := createNamespace("sample") + for _, test := range tests { + t.Run(test.desc, func(t *testing.T) { + cs := testclient.NewSimpleClientset(validNamespace) + namespaceExists, _ := checkNamespace(cs, test.namespace) + if diff := cmp.Diff(namespaceExists, test.valid); diff != "" { + t.Fatalf("checkNamespace() failed:\n%v", diff) + } + }) + } +} diff --git a/pkg/pipelines/pipelines.go b/pkg/pipelines/pipelines.go index 20d24057fc7..89a2b1fbecf 100644 --- a/pkg/pipelines/pipelines.go +++ b/pkg/pipelines/pipelines.go @@ -25,16 +25,13 @@ func createDevCIPipeline(name types.NamespacedName) *pipelinev1.Pipeline { createParamSpec("REPO", "string"), createParamSpec("COMMIT_SHA", "string"), }, - Resources: []pipelinev1.PipelineDeclaredResource{ createPipelineDeclaredResource("source-repo", "git"), createPipelineDeclaredResource("runtime-image", "image"), }, Tasks: []pipelinev1.PipelineTask{ - createGitHubStatusTask("create-pending-status", "", "pending", "Starting dev-ci-pipeline"), createBuildImageTask("build-image"), - createGitHubStatusTask("create-success-status", "build-image", "success", "Completed dev-ci-pipeline"), }, }, } @@ -100,29 +97,10 @@ func createPipelineDeclaredResource(name string, resourceType string) pipelinev1 return pipelinev1.PipelineDeclaredResource{Name: name, Type: resourceType} } -func createGitHubStatusTask(name, runAfter, state, description string) pipelinev1.PipelineTask { - t := pipelinev1.PipelineTask{ - Name: name, - TaskRef: createTaskRef("create-github-status-task"), - Params: []pipelinev1.Param{ - createTaskParam("REPO", "$(params.REPO)"), - createTaskParam("COMMIT_SHA", "$(params.COMMIT_SHA)"), - createTaskParam("STATE", state), - createTaskParam("DESCRIPTION", description), - createTaskParam("CONTEXT", "dev-ci-pipeline"), - }, - } - if runAfter != "" { - t.RunAfter = []string{runAfter} - } - return t -} - func createBuildImageTask(name string) pipelinev1.PipelineTask { return pipelinev1.PipelineTask{ - Name: name, - TaskRef: createTaskRef("buildah-task"), - RunAfter: []string{"create-pending-status"}, + Name: name, + TaskRef: createTaskRef("buildah-task"), Resources: &pipelinev1.PipelineTaskResources{ Inputs: []pipelinev1.PipelineTaskInputResource{createInputTaskResource("source", "source-repo")}, Outputs: []pipelinev1.PipelineTaskOutputResource{createOutputTaskResource("image", "runtime-image")}, diff --git a/pkg/pipelines/pipelines_test.go b/pkg/pipelines/pipelines_test.go index 384cc0157bf..d9ea3249abf 100644 --- a/pkg/pipelines/pipelines_test.go +++ b/pkg/pipelines/pipelines_test.go @@ -42,26 +42,12 @@ func TestCreateDevCIPipeline(t *testing.T) { }, }, Tasks: []pipelinev1.PipelineTask{ - pipelinev1.PipelineTask{ - Name: "create-pending-status", - TaskRef: &pipelinev1.TaskRef{ - Name: "create-github-status-task", - }, - Params: []pipelinev1.Param{ - {Name: "REPO", Value: pipelinev1.ArrayOrString{Type: "string", StringVal: "$(params.REPO)"}}, - {Name: "COMMIT_SHA", Value: pipelinev1.ArrayOrString{Type: "string", StringVal: "$(params.COMMIT_SHA)"}}, - {Name: "STATE", Value: pipelinev1.ArrayOrString{Type: "string", StringVal: "pending"}}, - {Name: "DESCRIPTION", Value: pipelinev1.ArrayOrString{Type: "string", StringVal: "Starting dev-ci-pipeline"}}, - {Name: "CONTEXT", Value: pipelinev1.ArrayOrString{Type: "string", StringVal: "dev-ci-pipeline"}}, - }, - }, pipelinev1.PipelineTask{ Name: "build-image", TaskRef: &pipelinev1.TaskRef{ Name: "buildah-task", }, - RunAfter: []string{"create-pending-status"}, Resources: &pipelinev1.PipelineTaskResources{ Inputs: []pipelinev1.PipelineTaskInputResource{ {Name: "source", @@ -73,21 +59,6 @@ func TestCreateDevCIPipeline(t *testing.T) { }, }, }, - - pipelinev1.PipelineTask{ - Name: "create-success-status", - TaskRef: &pipelinev1.TaskRef{ - Name: "create-github-status-task", - }, - RunAfter: []string{"build-image"}, - Params: []pipelinev1.Param{ - {Name: "REPO", Value: pipelinev1.ArrayOrString{Type: "string", StringVal: "$(params.REPO)"}}, - {Name: "COMMIT_SHA", Value: pipelinev1.ArrayOrString{Type: "string", StringVal: "$(params.COMMIT_SHA)"}}, - {Name: "STATE", Value: pipelinev1.ArrayOrString{Type: "string", StringVal: "success"}}, - {Name: "DESCRIPTION", Value: pipelinev1.ArrayOrString{Type: "string", StringVal: "Completed dev-ci-pipeline"}}, - {Name: "CONTEXT", Value: pipelinev1.ArrayOrString{Type: "string", StringVal: "dev-ci-pipeline"}}, - }, - }, }, }, } diff --git a/pkg/pipelines/service_rolebinding.go b/pkg/pipelines/service_rolebinding.go index 1b5f8715855..0f0e647a712 100644 --- a/pkg/pipelines/service_rolebinding.go +++ b/pkg/pipelines/service_rolebinding.go @@ -7,17 +7,19 @@ import ( "k8s.io/apimachinery/pkg/types" ) -// createServiceAccount creates a ServiceAccount given name and secretName -func createServiceAccount(name types.NamespacedName, secretName string) *corev1.ServiceAccount { +func createServiceAccount(name types.NamespacedName) *corev1.ServiceAccount { return &corev1.ServiceAccount{ TypeMeta: meta.TypeMeta("ServiceAccount", "v1"), ObjectMeta: meta.ObjectMeta(name), - Secrets: []corev1.ObjectReference{ - corev1.ObjectReference{Name: secretName}, - }, + Secrets: []corev1.ObjectReference{}, } } +func addSecretToSA(sa *corev1.ServiceAccount, secretName string) *corev1.ServiceAccount { + sa.Secrets = append(sa.Secrets, corev1.ObjectReference{Name: secretName}) + return sa +} + // createRoleBinding creates a RoleBinding given name, sa, roleKind, and roleName func createRoleBinding(name types.NamespacedName, sa *corev1.ServiceAccount, roleKind, roleName string) *v1rbac.RoleBinding { return &v1rbac.RoleBinding{ diff --git a/pkg/pipelines/service_rolebinding_test.go b/pkg/pipelines/service_rolebinding_test.go index 6b388696364..c5bf2a3c691 100644 --- a/pkg/pipelines/service_rolebinding_test.go +++ b/pkg/pipelines/service_rolebinding_test.go @@ -87,8 +87,30 @@ func TestServiceAccount(t *testing.T) { }, }, } - servicetask := createServiceAccount(meta.NamespacedName("", "pipeline"), "regcred") + servicetask := createServiceAccount(meta.NamespacedName("", "pipeline")) + servicetask = addSecretToSA(servicetask, "regcred") if diff := cmp.Diff(servicetask, want); diff != "" { t.Errorf("TestServiceAccount() failed:\n%s", diff) } } + +func TestAddSecretToSA(t *testing.T) { + validSA := &corev1.ServiceAccount{ + TypeMeta: v1.TypeMeta{ + Kind: "ServiceAccount", + APIVersion: "v1", + }, + ObjectMeta: v1.ObjectMeta{ + Name: "pipeline", + }, + } + validSecrets := []corev1.ObjectReference{ + corev1.ObjectReference{ + Name: "regcred", + }, + } + sa := addSecretToSA(validSA, "regcred") + if diff := cmp.Diff(sa.Secrets, validSecrets); diff != "" { + t.Errorf("addSecretToSA() failed:\n%s", diff) + } +} diff --git a/pkg/pipelines/tasks/buildah_task.go b/pkg/pipelines/tasks/buildah_task.go index 81754aaeb35..19c72381dbc 100644 --- a/pkg/pipelines/tasks/buildah_task.go +++ b/pkg/pipelines/tasks/buildah_task.go @@ -16,12 +16,12 @@ var ( } ) -func generateBuildahTask(ns string) pipelinev1.Task { +func generateBuildahTask(ns string, usingInternalRegistry bool) pipelinev1.Task { return pipelinev1.Task{ TypeMeta: createTaskTypeMeta(), ObjectMeta: meta.CreateObjectMeta(ns, "buildah-task"), Spec: pipelinev1.TaskSpec{ - Inputs: createInputsForBuildah(), + Inputs: createInputsForBuildah(usingInternalRegistry), Outputs: createOutputsForBuildah(), TaskSpec: v1alpha2.TaskSpec{ Steps: createStepsForBuildah(), @@ -62,7 +62,17 @@ func createOutputsForBuildah() *pipelinev1.Outputs { } } -func createInputsForBuildah() *pipelinev1.Inputs { +// Returns string value for TLSVERIFY parameter based on usingInternalRegistry boolean +// If internal registry is used, we need to disable TLS verification +func getTLSVerify(usingInternalRegistry bool) string { + if usingInternalRegistry { + return "false" + } + return "true" +} + +func createInputsForBuildah(usingInternalRegistry bool) *pipelinev1.Inputs { + return &pipelinev1.Inputs{ Params: []pipelinev1.ParamSpec{ createTaskParamWithDefault( @@ -81,7 +91,7 @@ func createInputsForBuildah() *pipelinev1.Inputs { "TLSVERIFY", "Verify the TLS on the registry endpoint (for push/pull to a non-TLS registry)", pipelinev1.ParamTypeString, - "true", + getTLSVerify(usingInternalRegistry), ), }, Resources: []pipelinev1.TaskResource{ diff --git a/pkg/pipelines/tasks/tasks.go b/pkg/pipelines/tasks/tasks.go index 9cb3e5679db..34cb6ae509f 100644 --- a/pkg/pipelines/tasks/tasks.go +++ b/pkg/pipelines/tasks/tasks.go @@ -7,10 +7,9 @@ import ( ) // Generate will return a slice of tasks -func Generate(secretName, ns string) []pipelinev1.Task { +func Generate(secretName, ns string, usingInternalRegistry bool) []pipelinev1.Task { return []pipelinev1.Task{ - generateBuildahTask(ns), - generateGithubStatusTask(secretName, ns), + generateBuildahTask(ns, usingInternalRegistry), generateDeployFromSourceTask(ns), generateDeployUsingKubectlTask(ns), } diff --git a/pkg/pipelines/tasks/tasks_test.go b/pkg/pipelines/tasks/tasks_test.go index 9b2c92fcce5..e92501b9741 100644 --- a/pkg/pipelines/tasks/tasks_test.go +++ b/pkg/pipelines/tasks/tasks_test.go @@ -12,43 +12,6 @@ import ( const testNS = "testing-ns" -func TestGithubStatusTask(t *testing.T) { - wantedTask := pipelinev1.Task{ - TypeMeta: v1.TypeMeta{ - Kind: "Task", - APIVersion: "tekton.dev/v1alpha1", - }, - ObjectMeta: v1.ObjectMeta{ - Name: "create-github-status-task", - Namespace: testNS, - }, - Spec: pipelinev1.TaskSpec{ - Inputs: createInputsForGithubStatusTask(), - TaskSpec: v1alpha2.TaskSpec{ - Steps: []pipelinev1.Step{ - pipelinev1.Step{ - Container: corev1.Container{ - Name: "start-status", - Image: "quay.io/kmcdermo/github-tool:latest", - WorkingDir: "/workspace/source", - Env: []corev1.EnvVar{ - createEnvFromSecret("GITHUB_TOKEN", "github-auth", "token"), - }, - Command: []string{"github-tools"}, - Args: argsForStartStatusStep, - }, - }, - }, - }, - }, - } - - githubStatusTask := generateGithubStatusTask("github-auth", testNS) - if diff := cmp.Diff(wantedTask, githubStatusTask); diff != "" { - t.Fatalf("GenerateGithubStatusTask() failed:\n%s", diff) - } -} - func TestDeployFromSourceTask(t *testing.T) { wantedTask := pipelinev1.Task{ TypeMeta: v1.TypeMeta{ @@ -209,7 +172,7 @@ func TestGenerateBuildahTask(t *testing.T) { Name: "buildah-task", }, Spec: pipelinev1.TaskSpec{ - Inputs: createInputsForBuildah(), + Inputs: createInputsForBuildah(false), Outputs: createOutputsForBuildah(), TaskSpec: v1alpha2.TaskSpec{ Steps: createStepsForBuildah(), @@ -217,7 +180,7 @@ func TestGenerateBuildahTask(t *testing.T) { }, }, } - buildahTask := generateBuildahTask("") + buildahTask := generateBuildahTask("", false) if diff := cmp.Diff(validTask, buildahTask); diff != "" { t.Fatalf("generateBuildahTask() failed:\n%s", diff) } diff --git a/pkg/pipelines/triggers/pipelinerun.go b/pkg/pipelines/triggers/pipelinerun.go index 4c5d31c5638..ebf919e454e 100644 --- a/pkg/pipelines/triggers/pipelinerun.go +++ b/pkg/pipelines/triggers/pipelinerun.go @@ -12,19 +12,19 @@ var ( } ) -func createDevCDPipelineRun(saName string) pipelinev1.PipelineRun { +func createDevCDPipelineRun(saName, imageRepo string) pipelinev1.PipelineRun { return pipelinev1.PipelineRun{ TypeMeta: pipelineRunTypeMeta, ObjectMeta: createObjectMeta("dev-cd-pipeline-run-$(uid)"), Spec: pipelinev1.PipelineRunSpec{ ServiceAccountName: saName, PipelineRef: createPipelineRef("dev-cd-pipeline"), - Resources: createDevResource("REPLACE_IMAGE:$(params.gitref)"), + Resources: createDevResource(imageRepo + ":$(params.gitref)"), }, } - } -func createDevCIPipelineRun(saName string) pipelinev1.PipelineRun { + +func createDevCIPipelineRun(saName, imageRepo string) pipelinev1.PipelineRun { return pipelinev1.PipelineRun{ TypeMeta: pipelineRunTypeMeta, ObjectMeta: createObjectMeta("dev-ci-pipeline-run-$(uid)"), @@ -35,7 +35,7 @@ func createDevCIPipelineRun(saName string) pipelinev1.PipelineRun { createBindingParam("REPO", "$(params.fullname)"), createBindingParam("COMMIT_SHA", "$(params.gitsha)"), }, - Resources: createDevResource("REPLACE_IMAGE:$(params.gitref)-$(params.gitsha)"), + Resources: createDevResource(imageRepo + ":$(params.gitref)-$(params.gitsha)"), }, } diff --git a/pkg/pipelines/triggers/pipelinerun_test.go b/pkg/pipelines/triggers/pipelinerun_test.go index dfd1005662b..9a9cb64e99a 100644 --- a/pkg/pipelines/triggers/pipelinerun_test.go +++ b/pkg/pipelines/triggers/pipelinerun_test.go @@ -18,10 +18,10 @@ func TestCreateDevCDPipelineRun(t *testing.T) { Spec: pipelinev1.PipelineRunSpec{ ServiceAccountName: sName, PipelineRef: createPipelineRef("dev-cd-pipeline"), - Resources: createDevResource("REPLACE_IMAGE:$(params.gitref)"), + Resources: createDevResource("example.com:5000/testing/testing:$(params.gitref)"), }, } - template := createDevCDPipelineRun(sName) + template := createDevCDPipelineRun(sName, "example.com:5000/testing/testing") if diff := cmp.Diff(validDevCDPipeline, template); diff != "" { t.Fatalf("createDevCDPipelineRun failed:\n%s", diff) } @@ -39,10 +39,10 @@ func TestCreateDevCIPipelineRun(t *testing.T) { createBindingParam("REPO", "$(params.fullname)"), createBindingParam("COMMIT_SHA", "$(params.gitsha)"), }, - Resources: createDevResource("REPLACE_IMAGE:$(params.gitref)-$(params.gitsha)"), + Resources: createDevResource("example.com:5000/testing/testing:$(params.gitref)-$(params.gitsha)"), }, } - template := createDevCIPipelineRun(sName) + template := createDevCIPipelineRun(sName, "example.com:5000/testing/testing") if diff := cmp.Diff(validDevCIPipelineRun, template); diff != "" { t.Fatalf("createDevCIPipelineRun failed:\n%s", diff) } diff --git a/pkg/pipelines/triggers/template_test.go b/pkg/pipelines/triggers/template_test.go index 6787c4313c1..9e11b121e87 100644 --- a/pkg/pipelines/triggers/template_test.go +++ b/pkg/pipelines/triggers/template_test.go @@ -35,13 +35,13 @@ func TestCreateDevCDDeployTemplate(t *testing.T) { ResourceTemplates: []triggersv1.TriggerResourceTemplate{ triggersv1.TriggerResourceTemplate{ - RawMessage: createDevCDResourcetemplate(serviceAccName), + RawMessage: createDevCDResourcetemplate(serviceAccName, "example.com:5000/testing/testing"), }, }, }, } - template := createDevCDDeployTemplate("testns", serviceAccName) + template := createDevCDDeployTemplate("testns", serviceAccName, "example.com:5000/testing/testing") if diff := cmp.Diff(validDevCDTemplate, template); diff != "" { t.Fatalf("CreateDevCDDeployTemplate failed:\n%s", diff) } @@ -74,12 +74,12 @@ func TestCreatedevCIBuildPRTemplate(t *testing.T) { }, ResourceTemplates: []triggersv1.TriggerResourceTemplate{ triggersv1.TriggerResourceTemplate{ - RawMessage: createDevCIResourceTemplate(serviceAccName), + RawMessage: createDevCIResourceTemplate(serviceAccName, "example.com:5000/testing/testing"), }, }, }, } - template := createDevCIBuildPRTemplate("testns", serviceAccName) + template := createDevCIBuildPRTemplate("testns", serviceAccName, "example.com:5000/testing/testing") if diff := cmp.Diff(validdevCIPRTemplate, template); diff != "" { t.Fatalf("CreatedevCIBuildPRTemplate failed:\n%s", diff) } diff --git a/pkg/pipelines/triggers/templates.go b/pkg/pipelines/triggers/templates.go index 6f164779d2f..53a7a428cbb 100644 --- a/pkg/pipelines/triggers/templates.go +++ b/pkg/pipelines/triggers/templates.go @@ -17,16 +17,16 @@ var ( ) // GenerateTemplates will return a slice of trigger templates -func GenerateTemplates(ns, saName string) []triggersv1.TriggerTemplate { +func GenerateTemplates(ns, saName, imageRepo string) []triggersv1.TriggerTemplate { return []triggersv1.TriggerTemplate{ - createDevCDDeployTemplate(ns, saName), - createDevCIBuildPRTemplate(ns, saName), + createDevCDDeployTemplate(ns, saName, imageRepo), + createDevCIBuildPRTemplate(ns, saName, imageRepo), createStageCDPushTemplate(ns, saName), createStageCIdryrunptemplate(ns, saName), } } -func createDevCDDeployTemplate(ns, saName string) triggersv1.TriggerTemplate { +func createDevCDDeployTemplate(ns, saName, imageRepo string) triggersv1.TriggerTemplate { return triggersv1.TriggerTemplate{ TypeMeta: triggerTemplateTypeMeta, ObjectMeta: meta.CreateObjectMeta(ns, "dev-cd-deploy-from-master-template"), @@ -37,14 +37,14 @@ func createDevCDDeployTemplate(ns, saName string) triggersv1.TriggerTemplate { }, ResourceTemplates: []triggersv1.TriggerResourceTemplate{ triggersv1.TriggerResourceTemplate{ - RawMessage: createDevCDResourcetemplate(saName), + RawMessage: createDevCDResourcetemplate(saName, imageRepo), }, }, }, } } -func createDevCIBuildPRTemplate(ns, saName string) triggersv1.TriggerTemplate { +func createDevCIBuildPRTemplate(ns, saName, imageRepo string) triggersv1.TriggerTemplate { return triggersv1.TriggerTemplate{ TypeMeta: triggerTemplateTypeMeta, ObjectMeta: meta.CreateObjectMeta(ns, "dev-ci-build-from-pr-template"), @@ -58,7 +58,7 @@ func createDevCIBuildPRTemplate(ns, saName string) triggersv1.TriggerTemplate { }, ResourceTemplates: []triggersv1.TriggerResourceTemplate{ triggersv1.TriggerResourceTemplate{ - RawMessage: createDevCIResourceTemplate(saName), + RawMessage: createDevCIResourceTemplate(saName, imageRepo), }, }, }, @@ -123,13 +123,13 @@ func createTemplateParamSpec(name string, description string) pipelinev1.ParamSp } -func createDevCDResourcetemplate(saName string) []byte { - byteTemplate, _ := json.Marshal(createDevCDPipelineRun(saName)) +func createDevCDResourcetemplate(saName, imageRepo string) []byte { + byteTemplate, _ := json.Marshal(createDevCDPipelineRun(saName, imageRepo)) return []byte(string(byteTemplate)) } -func createDevCIResourceTemplate(saName string) []byte { - byteTemplateCI, _ := json.Marshal(createDevCIPipelineRun(saName)) +func createDevCIResourceTemplate(saName, imageRepo string) []byte { + byteTemplateCI, _ := json.Marshal(createDevCIPipelineRun(saName, imageRepo)) return []byte(string(byteTemplateCI)) }