Skip to content
This repository has been archived by the owner on Sep 22, 2020. It is now read-only.

Commit

Permalink
Drive generation of the YAML for setting up the cluster (#11)
Browse files Browse the repository at this point in the history
*  added two flags github-token and quay-io-auth-json

* made changes in the flag description quayIOAuthFileName

* added logic to get the github token filename and auth.json file name

* made the requested changes mentioned in the PR

* made the requested changes mentioned in the PR and removed the unwanted comments

* created 4 required-flags quay-username, github-token, dockerconfigjson,base-repository

* Update test cases for creating opaque secret

* made the required changes mentioned in the PR

*  added two flags github-token and quay-io-auth-json

* Add functions to generate Tekton task objects

- Added functions and test cases to create
1) deploy-from-source-task
2) deploy-using-kubectl-task
3) github-status-task

Co-Authored-By: Gagan Hegde <ghegde@redhat.com>

* made changes in the flag description quayIOAuthFileName

* added logic to get the github token filename and auth.json file name

made the requested changes mentioned in the PR

made the requested changes mentioned in the PR and removed the unwanted comments

created 4 required-flags quay-username, github-token, dockerconfigjson,base-repository

Update test cases for creating opaque secret

made the required changes mentioned in the PR

* Replace objects in task_tests with suitable assertive functions

- Removed stuttering function names in tasks,routes and eventlisteners package.
- Objects in task_tests are replaced by functions to improve readability.

* Add secret name as parameter to Generate function

* pass ServiceAccount as object to createRoleBinding

* pass ServiceAccount as object to createRoleBinding

* Rework the new code around the base branch.

Explicitly marshal the yaml to os.Stdout for now.

Co-Authored-By: ishitasequeira <isequeir@redhat.com>

* made the required changes mentioned in the PR

* removed the functions which were not required from bootstrap.go

* removed the unwanted test case from bootstrap.go

* Added the test file < odo/pkg/pipelines/bootstrap_test.go>

* Test case corrected and moved to < /pkg/odo/cli/pipelines/bootstrap_test.go>

* made all the changes asked in PR

* pass a signel struct to Bootstrap()

Co-authored-by: Ishita Sequeira <46771830+ishitasequeira@users.noreply.github.com>
Co-authored-by: Chetan Banavikalmutt <chetanrns1997@gmail.com>
Co-authored-by: gaganeggday <48808456+gaganeggday@users.noreply.github.com>
Co-authored-by: Kevin McDermott <bigkevmcd@gmail.com>
  • Loading branch information
5 people authored Feb 22, 2020
1 parent 7a264a0 commit 84163e7
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 88 deletions.
36 changes: 24 additions & 12 deletions pkg/odo/cli/pipelines/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import (
"strings"

"github.com/openshift/odo/pkg/odo/genericclioptions"
"github.com/openshift/odo/pkg/pipelines"
"github.com/spf13/cobra"

"github.com/openshift/odo/pkg/pipelines"
ktemplates "k8s.io/kubernetes/pkg/kubectl/util/templates"
)

Expand All @@ -28,10 +28,11 @@ var (
// BootstrapOptions encapsulates the options for the odo pipelines bootstrap
// command.
type BootstrapOptions struct {
quayUsername string
baseRepo string // e.g. tekton/triggers
prefix string // used to generate the environments in a shared cluster

quayUsername string
gitRepo string // e.g. tekton/triggers
prefix string // used to generate the environments in a shared cluster
githubToken string
quayIOAuthFilename string
// generic context options common to all commands
*genericclioptions.Context
}
Expand All @@ -46,9 +47,6 @@ func NewBootstrapOptions() *BootstrapOptions {
// 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 {
bo.quayUsername = args[0]
bo.baseRepo = args[1]

if bo.prefix != "" && !strings.HasSuffix(bo.prefix, "-") {
bo.prefix = bo.prefix + "-"
}
Expand All @@ -58,15 +56,22 @@ func (bo *BootstrapOptions) Complete(name string, cmd *cobra.Command, args []str
// Validate validates the parameters of the BootstrapOptions.
func (bo *BootstrapOptions) Validate() error {
// TODO: this won't work with GitLab as the repo can have more path elements.
if len(strings.Split(bo.baseRepo, "/")) != 2 {
return fmt.Errorf("repo must be org/repo: %s", bo.baseRepo)
if len(strings.Split(bo.gitRepo, "/")) != 2 {
return fmt.Errorf("repo must be org/repo: %s", bo.gitRepo)
}
return nil
}

// Run runs the project bootstrap command.
func (bo *BootstrapOptions) Run() error {
return pipelines.Bootstrap(bo.quayUsername, bo.baseRepo, bo.prefix)
options := pipelines.BootstrapOptions{
GithubToken: bo.githubToken,
GitRepo: bo.gitRepo,
Prefix: bo.prefix,
QuayAuthFileName: bo.quayIOAuthFilename,
QuayUserName: bo.quayUsername,
}
return pipelines.Bootstrap(&options)
}

// NewCmdBootstrap creates the project bootstrap command.
Expand All @@ -78,12 +83,19 @@ func NewCmdBootstrap(name, fullName string) *cobra.Command {
Short: bootstrapShortDesc,
Long: bootstrapLongDesc,
Example: fmt.Sprintf(bootstrapExample, fullName),
Args: cobra.ExactArgs(2),
Run: func(cmd *cobra.Command, args []string) {
genericclioptions.GenericRun(o, cmd, args)
},
}

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.gitRepo, "git-repository", "", "provide the base repository")
bootstrapCmd.MarkFlagRequired("git-repository")
return bootstrapCmd
}
79 changes: 53 additions & 26 deletions pkg/odo/cli/pipelines/bootstrap_test.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,51 @@
package pipelines

import (
"bytes"
"regexp"
"testing"

"github.com/spf13/cobra"
)

func TestCompleteBootstrapOptions(t *testing.T) {
completeTests := []struct {
name string
prefix string
wantPrefix string
}{
{"no prefix", "", ""},
{"prefix with hyphen", "test-", "test-"},
{"prefix without hyphen", "test", "test-"},
}

for _, tt := range completeTests {
o := BootstrapOptions{prefix: tt.prefix}

err := o.Complete("test", &cobra.Command{}, []string{"test", "test/repo"})

if err != nil {
t.Errorf("Complete() %#v failed: ", err)
}

if o.prefix != tt.wantPrefix {
t.Errorf("Complete() %#v prefix: got %s, want %s", tt.name, o.prefix, tt.wantPrefix)
}
}
}

func TestValidateBootstrapOptions(t *testing.T) {
optionTests := []struct {
name string
baseRepo string
errMsg string
name string
gitRepo string
errMsg string
}{
{"invalid repo", "test", "repo must be org/repo"},
{"valid repo", "test/repo", ""},
}

for _, tt := range optionTests {
o := BootstrapOptions{quayUsername: "testing", baseRepo: tt.baseRepo, prefix: "test"}
o := BootstrapOptions{quayUsername: "testing", gitRepo: tt.gitRepo, prefix: "test"}

err := o.Validate()

Expand All @@ -32,34 +59,34 @@ func TestValidateBootstrapOptions(t *testing.T) {
}
}
}

// TODO: set up for complete BootstrapOptions instead of just prefix.
func TestCompleteBootstrapOptions(t *testing.T) {
completeTests := []struct {
name string
prefix string
wantPrefix string
func TestBootstrapCommandWithMissingParams(t *testing.T) {
cmdTests := []struct {
args []string
wantErr string
}{
{"no prefix", "", ""},
{"prefix with hyphen", "test-", "test-"},
{"prefix without hyphen", "test", "test-"},
{[]string{"quay-username", "example", "github-token", "abc123", "dockerconfigjson", "~/"}, `Required flag(s) "git-repository" have/has not been set`},
{[]string{"quay-username", "example", "github-token", "abc123", "git-repository", "example/repo"}, `Required flag(s) "dockerconfigjson" have/has not been set`},
{[]string{"quay-username", "example", "dockerconfigjson", "~/", "git-repository", "example/repo"}, `Required flag(s) "github-token" have/has not been set`},
{[]string{"github-token", "abc123", "dockerconfigjson", "~/", "git-repository", "example/repo"}, `Required flag(s) "quay-username" have/has not been set`},
}

for _, tt := range completeTests {
o := BootstrapOptions{prefix: tt.prefix}

err := o.Complete("test", &cobra.Command{}, []string{"test", "test/repo"})

if err != nil {
t.Fatalf("Complete() %#v failed: ", err)
}

if o.prefix != tt.wantPrefix {
t.Errorf("Complete() %#v prefix: got %s, want %s", tt.name, o.prefix, tt.wantPrefix)
for _, tt := range cmdTests {
_, _, err := executeCommand(NewCmdBootstrap("bootstrap", "odo pipelines bootstrap"), tt.args...)
if err.Error() != tt.wantErr {
t.Errorf("got %s, want %s", err, tt.wantErr)
}
}
}

func executeCommand(cmd *cobra.Command, args ...string) (c *cobra.Command, output string, err error) {
buf := new(bytes.Buffer)
cmd.SetOutput(buf)
cmd.Flags().Set(args[0], args[1])
cmd.Flags().Set(args[2], args[3])
cmd.Flags().Set(args[4], args[5])
c, err = cmd.ExecuteC()
return c, buf.String(), err
}

func matchError(t *testing.T, s string, e error) bool {
t.Helper()
if s == "" && e == nil {
Expand Down
80 changes: 37 additions & 43 deletions pkg/pipelines/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package pipelines
import (
"errors"
"fmt"
"io"
"os"
"path"

corev1 "k8s.io/api/core/v1"
v1rbac "k8s.io/api/rbac/v1"
Expand Down Expand Up @@ -37,10 +37,18 @@ var (
}
)

// BootstrapOptions is a struct that provides the optional flags
type BootstrapOptions struct {
GithubToken string
GitRepo string
Prefix string
QuayAuthFileName string
QuayUserName string
}

// Bootstrap is the main driver for getting OpenShift pipelines for GitOps
// configured with a basic configuration.
func Bootstrap(quayUsername, baseRepo, prefix string) error {

func Bootstrap(o *BootstrapOptions) error {
// First, check for Tekton. We proceed only if Tekton is installed
installed, err := checkTektonInstall()
if err != nil {
Expand All @@ -52,28 +60,30 @@ func Bootstrap(quayUsername, baseRepo, prefix string) error {

outputs := make([]interface{}, 0)

// Create GitHub Secret
githubAuth, err := createGithubSecret()
githubAuth, err := createOpaqueSecret("github-auth", o.GithubToken)
if err != nil {
return err
}
outputs = append(outputs, githubAuth)

// Create Docker Secret
dockerSecret, err := createDockerSecret(quayUsername)
dockerSecret, err := createDockerSecret(o.QuayAuthFileName)
if err != nil {
return err
}
outputs = append(outputs, dockerSecret)

// Create Tasks
tasks := tasks.Generate(githubAuth.GetName())
for _, task := range tasks {
outputs = append(outputs, task)
}

eventListener := eventlisteners.Generate(baseRepo)
// Create Event Listener
eventListener := eventlisteners.Generate(o.GitRepo)
outputs = append(outputs, eventListener)

// Create route
route := routes.Generate()
outputs = append(outputs, route)

Expand All @@ -85,48 +95,20 @@ func Bootstrap(quayUsername, baseRepo, prefix string) error {
outputs = append(outputs, createRoleBinding(roleBindingName, &sa, role.Kind, role.Name))
outputs = append(outputs, createRoleBinding("edit-clusterrole-binding", &sa, "ClusterRole", "edit"))

// Marshall
for _, r := range outputs {
data, err := yaml.Marshal(r)
if err != nil {
return err
}
fmt.Printf("%s---\n", data)
}

return nil
}

// createGithubSecret creates Github secret
func createGithubSecret() (*corev1.Secret, error) {
tokenPath, err := pathToDownloadedFile("token")
if err != nil {
return nil, fmt.Errorf("failed to generate path to file: %w", err)
}
f, err := os.Open(tokenPath)
if err != nil {
return nil, fmt.Errorf("failed to read token file %s due to %w", tokenPath, err)
}
defer f.Close()

githubAuth, err := createOpaqueSecret("github-auth", f)
if err != nil {
return nil, err
}

return githubAuth, nil
return marshalOutputs(os.Stdout, outputs)
}

// createDockerSecret creates Docker secret
func createDockerSecret(quayUsername string) (*corev1.Secret, error) {
authJSONPath, err := pathToDownloadedFile(quayUsername + "-auth.json")
func createDockerSecret(quayIOAuthFilename string) (*corev1.Secret, error) {

authJSONPath, err := homedir.Expand(quayIOAuthFilename)
if err != nil {
return nil, fmt.Errorf("failed to generate path to file: %w", err)
}

f, err := os.Open(authJSONPath)
if err != nil {
return nil, fmt.Errorf("failed to read docker file '%s' due to %w", authJSONPath, err)
return nil, fmt.Errorf("failed to read docker file '%s' : %w", authJSONPath, err)
}
defer f.Close()

Expand All @@ -138,9 +120,6 @@ func createDockerSecret(quayUsername string) (*corev1.Secret, error) {
return dockerSecret, nil

}
func pathToDownloadedFile(fname string) (string, error) {
return homedir.Expand(path.Join("~/Downloads/", fname))
}

// create and invoke a Tekton Checker
func checkTektonInstall() (bool, error) {
Expand All @@ -150,3 +129,18 @@ func checkTektonInstall() (bool, error) {
}
return tektonChecker.checkInstall()
}

// marshalOutputs marshal outputs to given writer
func marshalOutputs(out io.Writer, outputs []interface{}) error {
for _, r := range outputs {
data, err := yaml.Marshal(r)
if err != nil {
return fmt.Errorf("failed to marshal data: %w", err)
}
_, err = fmt.Fprintf(out, "%s---\n", data)
if err != nil {
return fmt.Errorf("failed to write data: %w", err)
}
}
return nil
}
7 changes: 5 additions & 2 deletions pkg/pipelines/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@ import (
"fmt"
"io"
"io/ioutil"
"strings"

corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// createOpaqueSecret creates a Kubernetes v1/Secret with the provided name and
// body, and type Opaque.
func createOpaqueSecret(name string, in io.Reader) (*corev1.Secret, error) {
return createSecret(name, "token", corev1.SecretTypeOpaque, in)
func createOpaqueSecret(name, data string) (*corev1.Secret, error) {
r := strings.NewReader(data)

return createSecret(name, "token", corev1.SecretTypeOpaque, r)
}

// createDockerConfigSecret creates a Kubernetes v1/Secret with the provided name and
Expand Down
10 changes: 5 additions & 5 deletions pkg/pipelines/secrets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import (
)

func TestCreateOpaqueSecret(t *testing.T) {
data := []byte(`abcdefghijklmnop`)
secret, err := createOpaqueSecret("github-auth", bytes.NewReader(data))
data := "abcdefghijklmnop"
secret, err := createOpaqueSecret("github-auth", data)
if err != nil {
t.Fatal(err)
}
Expand All @@ -28,7 +28,7 @@ func TestCreateOpaqueSecret(t *testing.T) {
},
Type: corev1.SecretTypeOpaque,
Data: map[string][]byte{
"token": data,
"token": []byte(data),
},
}

Expand All @@ -37,9 +37,9 @@ func TestCreateOpaqueSecret(t *testing.T) {
}
}

func TestCreateOpaqueSecretWithErrorReading(t *testing.T) {
func TestCreateDockerConfigSecretWithErrorReading(t *testing.T) {
testErr := errors.New("test failure")
_, err := createOpaqueSecret("github-auth", errorReader{testErr})
_, err := createDockerConfigSecret("github-auth", errorReader{testErr})
if !matchError(t, "failed to read .* test failure", err) {
t.Fatalf("got an unexpected error: %#v", err)
}
Expand Down

0 comments on commit 84163e7

Please sign in to comment.