Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove tekton for cloning git repos #302

Merged
merged 20 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/e2e-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ jobs:
name: Build Binaries
run: |
go build -o "bin/gitjob"
go build -o bin/gitcloner ./cmd/gitcloner
-
name: Build Docker Images
run: |
Expand Down
7 changes: 6 additions & 1 deletion .github/workflows/integrationtests-ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ jobs:
go-version-file: 'go.mod'
check-latest: true

- name: integration-test
- name: gitjob integration-test
run: go test -v ./integrationtests/...
env:
TESTCONTAINERS_RYUK_DISABLED: true

- name: git cloner integration-test
run: go test -v ./cmd/gitcloner/integrationtests/...
env:
TESTCONTAINERS_RYUK_DISABLED: true
5 changes: 3 additions & 2 deletions chart/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ spec:
- image: "{{ template "system_default_registry" . }}{{ .Values.gitjob.repository }}:{{ .Values.gitjob.tag }}"
name: gitjob
args:
- gitjob
raulcabello marked this conversation as resolved.
Show resolved Hide resolved
- --gitjob-image
- "{{ template "system_default_registry" . }}{{ .Values.gitjob.repository }}:{{ .Values.gitjob.tag }}"
{{- if .Values.debug }}
- --debug
{{- end }}
- --tekton-image
- "{{ template "system_default_registry" . }}{{ .Values.tekton.repository }}:{{ .Values.tekton.tag }}"
env:
- name: NAMESPACE
valueFrom:
Expand Down
4 changes: 0 additions & 4 deletions chart/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,6 @@ gitjob:
repository: rancher/gitjob
tag: ${VERSION}

tekton:
repository: rancher/tekton-utils
tag: v0.1.36

global:
cattle:
systemDefaultRegistry: ""
Expand Down
49 changes: 49 additions & 0 deletions cmd/gitcloner/cmd/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package cmd

import (
"github.com/spf13/cobra"
)

type GitCloner interface {
CloneRepo(opts *Options) error
}

type Options struct {
Repo string
Path string
Branch string
Revision string
CABundleFile string
Username string
PasswordFile string
SSHPrivateKeyFile string
InsecureSkipTLS bool
KnownHostsFile string
}

var opts *Options

func New(gitCloner GitCloner) *cobra.Command {
aruiz14 marked this conversation as resolved.
Show resolved Hide resolved
cmd := &cobra.Command{
Use: "gitcloner [REPO] [PATH]",
Short: "Clones a git repository",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
weyfonk marked this conversation as resolved.
Show resolved Hide resolved
opts.Repo = args[0]
opts.Path = args[1]

return gitCloner.CloneRepo(opts)
},
}
opts = &Options{}
cmd.Flags().StringVarP(&opts.Branch, "branch", "b", "", "git branch")
cmd.Flags().StringVar(&opts.Revision, "revision", "", "git revision")
cmd.Flags().StringVar(&opts.CABundleFile, "ca-bundle-file", "", "CA bundle file")
cmd.Flags().StringVarP(&opts.Username, "username", "u", "", "user name for basic auth")
cmd.Flags().StringVar(&opts.PasswordFile, "password-file", "", "password file for basic auth")
cmd.Flags().StringVar(&opts.SSHPrivateKeyFile, "ssh-private-key-file", "", "ssh private key file path")
cmd.Flags().BoolVar(&opts.InsecureSkipTLS, "insecure-skip-tls", false, "do not verify tls certificates")
cmd.Flags().StringVar(&opts.KnownHostsFile, "known-hosts-file", "", "known hosts file")

return cmd
}
53 changes: 53 additions & 0 deletions cmd/gitcloner/cmd/cmd_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package cmd

import (
"testing"
)

func TestArgsAreSet(t *testing.T) {
mock := &clonerMock{}
cmd := New(mock)
cmd.SetArgs([]string{"test-repo", "test-path", "--branch", "master", "--revision", "v0.1.0", "--ca-bundle-file", "caFile", "--username", "user",
"--password-file", "passwordFile", "--ssh-private-key-file", "sshFile", "--insecure-skip-tls", "--known-hosts-file", "knownFile"})
err := cmd.Execute()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if mock.opts.Repo != "test-repo" {
t.Fatalf("expected repo test-repo, got %v", mock.opts.Repo)
}
if mock.opts.Path != "test-path" {
t.Fatalf("expected path test-path, got %v", mock.opts.Path)
}
if mock.opts.Branch != "master" {
t.Fatalf("expected branch master, got %v", mock.opts.Branch)
}
if mock.opts.Revision != "v0.1.0" {
t.Fatalf("expected revision v0.1.0, got %v", mock.opts.Revision)
}
if mock.opts.CABundleFile != "caFile" {
t.Fatalf("expected CABundleFile caFile, got %v", mock.opts.CABundleFile)
}
if mock.opts.Username != "user" {
t.Fatalf("expected Username user, got %v", mock.opts.Username)
}
if mock.opts.PasswordFile != "passwordFile" {
t.Fatalf("expected PasswordFile passwordFile, got %v", mock.opts.PasswordFile)
}
if !mock.opts.InsecureSkipTLS {
t.Fatalf("expected InsecureSkipTLS to be true")
}
if mock.opts.KnownHostsFile != "knownFile" {
t.Fatalf("expected KnownHostsFile knownFile, got %v", mock.opts.KnownHostsFile)
}
}

type clonerMock struct {
opts *Options
}

func (m *clonerMock) CloneRepo(opts *Options) error {
m.opts = opts

return nil
}
172 changes: 172 additions & 0 deletions cmd/gitcloner/gogit/cloner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package gogit

import (
"fmt"
"os"

"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/transport"
httpgit "github.com/go-git/go-git/v5/plumbing/transport/http"
gossh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
"github.com/rancher/gitjob/cmd/gitcloner/cmd"
"github.com/sirupsen/logrus"
giturls "github.com/whilp/git-urls"
"golang.org/x/crypto/ssh"
)

const defaultBranch = "master"

var (
plainClone = git.PlainClone
readFile = os.ReadFile
raulcabello marked this conversation as resolved.
Show resolved Hide resolved
)

type Cloner struct{}

type Options struct {
Repo string
Branch string
Auth transport.AuthMethod
InsecureSkipTLS bool
CABundle []byte
}

func NewCloner() *Cloner {
return &Cloner{}
}

func (c *Cloner) CloneRepo(opts *cmd.Options) error {
auth, err := createAuthFromOpts(opts)
if err != nil {
return err
}
caBundle, err := getCABundleFromFile(opts.CABundleFile)
if err != nil {
return err
}

if opts.Branch == "" && opts.Revision == "" {
opts.Branch = defaultBranch
return cloneBranch(opts, auth, caBundle)
}

if opts.Branch != "" {
if opts.Revision != "" {
logrus.Warn("Using branch for cloning the repo. Revision will be skipped.")
}
return cloneBranch(opts, auth, caBundle)
aruiz14 marked this conversation as resolved.
Show resolved Hide resolved
}

return cloneRevision(opts, auth, caBundle)
}

func cloneBranch(opts *cmd.Options, auth transport.AuthMethod, caBundle []byte) error {
_, err := plainClone(opts.Path, false, &git.CloneOptions{
URL: opts.Repo,
Auth: auth,
InsecureSkipTLS: opts.InsecureSkipTLS,
CABundle: caBundle,
SingleBranch: true,
ReferenceName: plumbing.ReferenceName(opts.Branch),
})

return err
}

func cloneRevision(opts *cmd.Options, auth transport.AuthMethod, caBundle []byte) error {
r, err := plainClone(opts.Path, false, &git.CloneOptions{
weyfonk marked this conversation as resolved.
Show resolved Hide resolved
URL: opts.Repo,
Auth: auth,
InsecureSkipTLS: opts.InsecureSkipTLS,
CABundle: caBundle,
})
if err != nil {
return err
}
h, err := r.ResolveRevision(plumbing.Revision(opts.Revision))
if err != nil {
return err
}
w, err := r.Worktree()
if err != nil {
return err
}

return w.Checkout(&git.CheckoutOptions{
Hash: *h,
})
}

func getCABundleFromFile(path string) ([]byte, error) {
if path == "" {
return nil, nil
}
return readFile(path)
}

// createAuthFromOpts adds auth for cloning git repos based on the parameters provided in opts.
func createAuthFromOpts(opts *cmd.Options) (transport.AuthMethod, error) {
if opts.SSHPrivateKeyFile != "" {
privateKey, err := readFile(opts.SSHPrivateKeyFile)
if err != nil {
return nil, err
}
gitURL, err := giturls.Parse(opts.Repo)
if err != nil {
return nil, err
}
auth, err := gossh.NewPublicKeys(gitURL.User.Username(), privateKey, "")
if err != nil {
return nil, err
}
if opts.KnownHostsFile != "" {
knownHosts, err := readFile(opts.KnownHostsFile)
if err != nil {
return nil, err
}
knownHostsCallBack, err := createKnownHostsCallBack(knownHosts)
if err != nil {
return nil, err
}
auth.HostKeyCallback = knownHostsCallBack
} else {
//nolint G106: Use of ssh InsecureIgnoreHostKey should be audited
//this will run in an init-container, so there is no persistence
auth.HostKeyCallback = ssh.InsecureIgnoreHostKey()
}
return auth, nil
}

if opts.Username != "" && opts.PasswordFile != "" {
password, err := readFile(opts.PasswordFile)
if err != nil {
return nil, err
}

return &httpgit.BasicAuth{
Username: opts.Username,
Password: string(password),
}, nil
}

return nil, nil
}

func createKnownHostsCallBack(knownHosts []byte) (ssh.HostKeyCallback, error) {
f, err := os.CreateTemp("", "known_hosts")
if err != nil {
return nil, err
}
defer os.RemoveAll(f.Name())
aruiz14 marked this conversation as resolved.
Show resolved Hide resolved
defer f.Close()

if _, err := f.Write(knownHosts); err != nil {
return nil, err
}
if err := f.Close(); err != nil {
return nil, fmt.Errorf("closing knownHosts file %s: %w", f.Name(), err)
}

return gossh.NewKnownHostsCallback(f.Name())
}