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

Drive generation of the YAML for setting up the cluster #11

Merged
merged 28 commits into from
Feb 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
9f496e8
added two flags github-token and quay-io-auth-json
ishitasequeira Feb 17, 2020
05144a3
made changes in the flag description quayIOAuthFileName
ishitasequeira Feb 17, 2020
c8dbf13
added logic to get the github token filename and auth.json file name
ishitasequeira Feb 17, 2020
de06981
made the requested changes mentioned in the PR
ishitasequeira Feb 18, 2020
875ecd7
made the requested changes mentioned in the PR and removed the unwant…
ishitasequeira Feb 18, 2020
d8c5436
created 4 required-flags quay-username, github-token, dockerconfigjso…
ishitasequeira Feb 18, 2020
c576a5d
Update test cases for creating opaque secret
ishitasequeira Feb 19, 2020
424497f
made the required changes mentioned in the PR
ishitasequeira Feb 19, 2020
82dc70e
Merge branch 'bootstrap' of github.com:rhd-gitops-example/odo into gi…
ishitasequeira Feb 19, 2020
d976637
Add functions to generate Tekton task objects
chetan-rns Feb 17, 2020
c87bd1a
Replace objects in task_tests with suitable assertive functions
chetan-rns Feb 18, 2020
91dbafb
Add secret name as parameter to Generate function
chetan-rns Feb 19, 2020
fe85c9e
pass ServiceAccount as object to createRoleBinding
wtam2018 Feb 19, 2020
196fa43
pass ServiceAccount as object to createRoleBinding
wtam2018 Feb 19, 2020
e760348
added two flags github-token and quay-io-auth-json
ishitasequeira Feb 17, 2020
b2feae0
made changes in the flag description quayIOAuthFileName
ishitasequeira Feb 17, 2020
cf773b0
added logic to get the github token filename and auth.json file name
ishitasequeira Feb 17, 2020
0e18162
Rework the new code around the base branch.
bigkevmcd Feb 20, 2020
612657e
made the required changes mentioned in the PR
ishitasequeira Feb 20, 2020
2a58fa1
Merge branch 'bootstrap' into gitops-issue-37
bigkevmcd Feb 20, 2020
5194304
removed the functions which were not required from bootstrap.go
ishitasequeira Feb 20, 2020
6c07e0e
resolve merge conflicts
wtam2018 Feb 20, 2020
0d92c66
removed the unwanted test case from bootstrap.go
ishitasequeira Feb 20, 2020
414195e
Merge branch 'gitops-issue-37' of github.com:rhd-gitops-example/odo i…
ishitasequeira Feb 20, 2020
e5c6f78
Added the test file < odo/pkg/pipelines/bootstrap_test.go>
gaganhegde Feb 21, 2020
664612e
Test case corrected and moved to < /pkg/odo/cli/pipelines/bootstrap_t…
gaganhegde Feb 21, 2020
b45f5ef
made all the changes asked in PR
ishitasequeira Feb 21, 2020
6f1659a
pass a signel struct to Bootstrap()
wtam2018 Feb 21, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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