-
Notifications
You must be signed in to change notification settings - Fork 83
/
utils.go
129 lines (115 loc) · 4.47 KB
/
utils.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package pipelines
import (
"context"
"fmt"
"net/url"
"os/exec"
"path/filepath"
"strings"
"github.com/jenkins-x/go-scm/scm"
"github.com/redhat-developer/kam/pkg/pipelines/ioutils"
"github.com/spf13/afero"
)
const defaultRepoDescription = "Bootstrapped GitOps Repository"
type clientFactory = func(string) (*scm.Client, error)
type executor interface {
execute(baseDir, command string, args ...string) ([]byte, error)
}
// BootstrapRepository creates a new empty Git repository in the upstream git
// hosting service from the GitOpsRepoURL.
func BootstrapRepository(o *BootstrapOptions, f clientFactory, e executor, appFs afero.Fs) error {
if o.GitHostAccessToken == "" {
return nil
}
u, err := url.Parse(o.GitOpsRepoURL)
if err != nil {
return fmt.Errorf("failed to parse GitOps repo URL %q: %w", o.GitOpsRepoURL, err)
}
parts := strings.Split(u.Path, "/")
org := parts[1]
repoName := strings.TrimSuffix(strings.Join(parts[2:], "/"), ".git")
u.User = url.UserPassword("", o.GitHostAccessToken)
client, err := f(u.String())
if err != nil {
return fmt.Errorf("failed to create a client to access %q: %w", o.GitOpsRepoURL, err)
}
ctx := context.Background()
// If we're creating the repository in a personal user's account, it's a
// different API call that's made, clearing the org triggers go-scm to use
// the "create repo in personal account" endpoint.
currentUser, _, err := client.Users.Find(ctx)
if err != nil {
return fmt.Errorf("failed to get the user with their auth token: %w", err)
}
if currentUser.Login == org {
org = ""
}
ri := &scm.RepositoryInput{
Private: true,
Description: defaultRepoDescription,
Namespace: org,
Name: repoName,
}
created, _, err := client.Repositories.Create(context.Background(), ri)
if err != nil {
repo := fmt.Sprintf("%s/%s", org, repoName)
if org == "" {
repo = fmt.Sprintf("%s/%s", currentUser.Login, repoName)
}
if _, resp, err := client.Repositories.Find(context.Background(), repo); err == nil && resp.Status == 200 {
return fmt.Errorf("failed to create repository, repo already exists")
}
return fmt.Errorf("failed to create repository %q in namespace %q: %w", repoName, org, err)
}
if err := pushRepository(o, created.CloneSSH, e, appFs); err != nil {
return fmt.Errorf("failed to push bootstrapped resources: %s", err)
}
return err
}
func pushRepository(o *BootstrapOptions, remote string, e executor, appFs afero.Fs) error {
if exists, _ := ioutils.IsExisting(appFs, filepath.Join(o.OutputPath, ".git")); exists {
if err := appFs.RemoveAll(filepath.Join(o.OutputPath, ".git")); err != nil {
return fmt.Errorf("failed to remove existing .git folder in %q: %s", o.OutputPath, err)
}
}
if out, err := e.execute(o.OutputPath, "git", "init", "."); err != nil {
return fmt.Errorf("failed to initialize git repository in %q %q: %s", o.OutputPath, string(out), err)
}
if out, err := e.execute(o.OutputPath, "git", "add", "pipelines.yaml", "config", "environments"); err != nil {
return fmt.Errorf("failed to add pipelines.yaml to repository in %q %q: %s", o.OutputPath, string(out), err)
}
if out, err := e.execute(o.OutputPath, "git", "commit", "-m", "Bootstrapped commit"); err != nil {
return fmt.Errorf("failed to commit files to repository in %q %q: %s", o.OutputPath, string(out), err)
}
if out, err := e.execute(o.OutputPath, "git", "branch", "-m", "main"); err != nil {
return fmt.Errorf("failed to switch to branch 'main' in repository in %q %q: %s", o.OutputPath, string(out), err)
}
if out, err := e.execute(o.OutputPath, "git", "remote", "add", "origin", remote); err != nil {
return fmt.Errorf("failed add remote 'origin' %q to repository in %q %q: %s", remote, o.OutputPath, string(out), err)
}
if out, err := e.execute(o.OutputPath, "git", "push", "-u", "origin", "main"); err != nil {
return fmt.Errorf("failed push remote to repository %q %q: %s", remote, string(out), err)
}
return nil
}
func repoURL(u string) (string, error) {
parsed, err := url.Parse(u)
if err != nil {
return "", fmt.Errorf("failed to parse %q: %w", u, err)
}
parsed.Path = ""
parsed.User = nil
return parsed.String(), nil
}
// NewCmdExecutor creates and returns an executor implementation that uses
// exec.Command to execute the commands.
func NewCmdExecutor() cmdExecutor {
return cmdExecutor{}
}
type cmdExecutor struct {
}
func (e cmdExecutor) execute(baseDir, command string, args ...string) ([]byte, error) {
c := exec.Command(command, args...)
c.Dir = baseDir
return c.CombinedOutput()
}