diff --git a/pkg/config/config.go b/pkg/config/config.go index b17c2dd35..6d9285895 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -821,8 +821,9 @@ func (m *Manifest) Equal(n *Manifest) bool { if n == nil { return n == m } - repositoryEq := reflect.DeepEqual(m.Repositories, n.Repositories) - return repositoryEq && m.TargetPath == n.TargetPath + repositoryEq := reflect.DeepEqual(m.Repository, n.Repository) + extraReposEq := reflect.DeepEqual(m.ExtraRepositories, n.ExtraRepositories) + return repositoryEq && extraReposEq && m.TargetPath == n.TargetPath } func (m *Manifest) String() string { @@ -833,30 +834,6 @@ func (m *Manifest) String() string { return string(yamlData) } -// Repository functions -func (r *Repository) Equal(s *Repository) bool { - if s == nil { - return r == s - } - var urlMatches bool - if r.Url != nil && s.Url != nil { - urlMatches = r.Url.String() == s.Url.String() - } else { - // this catches cases where one or both are nil - urlMatches = r.Url == s.Url - } - return urlMatches && - r.Username == s.Username && - r.TargetPath == s.TargetPath -} -func (r *Repository) String() string { - yamlData, err := yaml.Marshal(&r) - if err != nil { - return "" - } - return string(yamlData) -} - // Modules functions func (m *Modules) Equal(n *Modules) bool { if n == nil { diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index 0bb92ea4b..ae1e7f080 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -68,6 +68,14 @@ func TestString(t *testing.T) { name: "repository", stringer: DummyRepository(), }, + { + name: "repo-auth", + stringer: DummyRepoAuth(), + }, + { + name: "repo-checkout", + stringer: DummyRepoCheckout(), + }, { name: "bootstrap", stringer: DummyBootstrap(), @@ -157,12 +165,28 @@ func TestEqual(t *testing.T) { }) t.Run("repository-equal", func(t *testing.T) { - testRepository1 := &Repository{TargetPath: "same"} - testRepository2 := &Repository{TargetPath: "different"} + testRepository1 := &Repository{URLString: "same"} + testRepository2 := &Repository{URLString: "different"} assert.True(t, testRepository1.Equal(testRepository1)) assert.False(t, testRepository1.Equal(testRepository2)) assert.False(t, testRepository1.Equal(nil)) }) + t.Run("auth-equal", func(t *testing.T) { + testSpec1 := &RepoAuth{} + testSpec2 := &RepoAuth{} + testSpec2.Type = "ssh-key" + assert.True(t, testSpec1.Equal(testSpec1)) + assert.False(t, testSpec1.Equal(testSpec2)) + assert.False(t, testSpec1.Equal(nil)) + }) + t.Run("checkout-equal", func(t *testing.T) { + testSpec1 := &RepoCheckout{} + testSpec2 := &RepoCheckout{} + testSpec2.Branch = "Master" + assert.True(t, testSpec1.Equal(testSpec1)) + assert.False(t, testSpec1.Equal(testSpec2)) + assert.False(t, testSpec1.Equal(nil)) + }) t.Run("modules-equal", func(t *testing.T) { testModules1 := NewModules() diff --git a/pkg/config/errors.go b/pkg/config/errors.go index d5815183d..c2a82a8f2 100644 --- a/pkg/config/errors.go +++ b/pkg/config/errors.go @@ -2,8 +2,54 @@ package config import ( "fmt" + "strings" ) +// Repo errors + +// ErrMutuallyExclusiveAuthSSHPass is returned when ssh-pass type +// is selected and http-pass, ssh-key or key-pass options are defined +type ErrIncompatibleAuthOptions struct { + ForbiddenOptions []string + AuthType string +} + +func NewErrIncompetibleAuthOptions(fo []string, ao string) error { + return ErrIncompatibleAuthOptions{ + ForbiddenOptions: fo, + AuthType: ao, + } +} + +func (e ErrIncompatibleAuthOptions) Error() string { + return fmt.Sprintf("Can not use %s options, with auth type %s", e.ForbiddenOptions, e.AuthType) +} + +// ErrAuthTypeNotSupported is returned with wrong AuthType is provided +type ErrAuthTypeNotSupported struct { +} + +func (e ErrAuthTypeNotSupported) Error() string { + return "Invalid auth, allowed types: " + strings.Join(AllowedAuthTypes, ",") +} + +// ErrRepoSpecRequiresURL is returned when repository URL is not specified +type ErrRepoSpecRequiresURL struct { +} + +func (e ErrRepoSpecRequiresURL) Error() string { + return "Repostory spec requires url" +} + +// ErrMutuallyExclusiveCheckout is returned if +// mutually exclusive options are given as checkout options +type ErrMutuallyExclusiveCheckout struct { +} + +func (e ErrMutuallyExclusiveCheckout) Error() string { + return "Chekout mutually execlusive, use either: commit-hash, branch or tag" +} + // ErrBootstrapInfoNotFound returned if bootstrap // information is not found for cluster type ErrBootstrapInfoNotFound struct { diff --git a/pkg/config/repo.go b/pkg/config/repo.go new file mode 100644 index 000000000..ca49be7c6 --- /dev/null +++ b/pkg/config/repo.go @@ -0,0 +1,202 @@ +package config + +import ( + "fmt" + "reflect" + + "gopkg.in/src-d/go-git.v4" + "gopkg.in/src-d/go-git.v4/plumbing" + "gopkg.in/src-d/go-git.v4/plumbing/transport" + "gopkg.in/src-d/go-git.v4/plumbing/transport/http" + "gopkg.in/src-d/go-git.v4/plumbing/transport/ssh" + "sigs.k8s.io/yaml" + + "opendev.org/airship/airshipctl/pkg/errors" +) + +const ( + SSHAuth = "ssh-key" + SSHPass = "ssh-pass" + HTTPBasic = "http-basic" +) + +// RepoCheckout methods + +func (c *RepoCheckout) Equal(s *RepoCheckout) bool { + if s == nil { + return s == c + } + return c.CommitHash == s.CommitHash && + c.Branch == s.Branch && + c.Tag == s.Tag && + c.RemoteRef == s.RemoteRef +} + +func (r *RepoCheckout) String() string { + yaml, err := yaml.Marshal(&r) + if err != nil { + return "" + } + return string(yaml) +} + +func (c *RepoCheckout) Validate() error { + possibleValues := []string{c.CommitHash, c.Branch, c.Tag, c.RemoteRef} + var count int + for _, val := range possibleValues { + if val != "" { + count++ + } + } + if count > 1 { + return ErrMutuallyExclusiveCheckout{} + } + if c.RemoteRef != "" { + return fmt.Errorf("Repository checkout by RemoteRef is not yet implemented\n%w", errors.ErrNotImplemented{}) + } + return nil +} + +// RepoAuth methods +var ( + AllowedAuthTypes = []string{SSHAuth, SSHPass, HTTPBasic} +) + +func (auth *RepoAuth) Equal(s *RepoAuth) bool { + if s == nil { + return s == auth + } + return auth.Type == s.Type && + auth.KeyPassword == s.KeyPassword && + auth.KeyPath == s.KeyPath && + auth.SSHPassword == s.SSHPassword && + auth.Username == s.Username +} + +func (r *RepoAuth) String() string { + yaml, err := yaml.Marshal(&r) + if err != nil { + return "" + } + return string(yaml) +} + +func (auth *RepoAuth) Validate() error { + if !stringInSlice(auth.Type, AllowedAuthTypes) { + return ErrAuthTypeNotSupported{} + } + + switch auth.Type { + case SSHAuth: + if auth.HTTPPassword != "" || auth.SSHPassword != "" { + return NewErrIncompetibleAuthOptions([]string{"http-pass, ssh-pass"}, auth.Type) + } + case HTTPBasic: + if auth.SSHPassword != "" || auth.KeyPath != "" || auth.KeyPassword != "" { + return NewErrIncompetibleAuthOptions([]string{"ssh-pass, ssh-key, key-pass"}, auth.Type) + } + case SSHPass: + if auth.KeyPath != "" || auth.KeyPassword != "" || auth.HTTPPassword != "" { + return NewErrIncompetibleAuthOptions([]string{"ssh-key, key-pass, http-pass"}, auth.Type) + } + } + return nil +} + +func stringInSlice(a string, list []string) bool { + for _, b := range list { + if b == a { + return true + } + } + return false +} + +// Repository functions +// Equal compares repository specs +func (repo *Repository) Equal(s *Repository) bool { + if s == nil { + return s == repo + } + + return repo.URLString == s.URLString && + reflect.DeepEqual(s.Auth, repo.Auth) && + reflect.DeepEqual(s.CheckoutOptions, repo.CheckoutOptions) +} + +func (r *Repository) String() string { + yaml, err := yaml.Marshal(&r) + if err != nil { + return "" + } + return string(yaml) +} + +func (spec *Repository) Validate() error { + if spec.URLString == "" { + return ErrRepoSpecRequiresURL{} + } + + if spec.Auth != nil { + err := spec.Auth.Validate() + if err != nil { + return err + } + } + + if spec.CheckoutOptions != nil { + err := spec.CheckoutOptions.Validate() + if err != nil { + return err + } + } + + return nil +} + +func (repo *Repository) ToAuth() (transport.AuthMethod, error) { + if repo.Auth == nil { + return nil, nil + } + switch repo.Auth.Type { + case SSHAuth: + return ssh.NewPublicKeysFromFile(repo.Auth.Username, repo.Auth.KeyPath, repo.Auth.KeyPassword) + case SSHPass: + return &ssh.Password{User: repo.Auth.Username, Password: repo.Auth.HTTPPassword}, nil + case HTTPBasic: + return &http.BasicAuth{Username: repo.Auth.Username, Password: repo.Auth.HTTPPassword}, nil + default: + return nil, fmt.Errorf("Error building auth opts, repo\n%s\n: %w", repo.String(), errors.ErrNotImplemented{}) + } +} + +func (repo *Repository) ToCheckoutOptions(force bool) *git.CheckoutOptions { + co := &git.CheckoutOptions{ + Force: force, + } + switch { + case repo.CheckoutOptions == nil: + case repo.CheckoutOptions.Branch != "": + co.Branch = plumbing.NewBranchReferenceName(repo.CheckoutOptions.Branch) + case repo.CheckoutOptions.Tag != "": + co.Branch = plumbing.NewTagReferenceName(repo.CheckoutOptions.Tag) + case repo.CheckoutOptions.CommitHash != "": + co.Hash = plumbing.NewHash(repo.CheckoutOptions.CommitHash) + } + return co +} + +func (repo *Repository) ToCloneOptions(auth transport.AuthMethod) *git.CloneOptions { + return &git.CloneOptions{ + Auth: auth, + URL: repo.URLString, + } +} + +func (repo *Repository) ToFetchOptions(auth transport.AuthMethod) *git.FetchOptions { + return &git.FetchOptions{Auth: auth} +} + +func (repo *Repository) URL() string { + return repo.URLString +} diff --git a/pkg/config/repo_test.go b/pkg/config/repo_test.go new file mode 100644 index 000000000..8f83c969c --- /dev/null +++ b/pkg/config/repo_test.go @@ -0,0 +1,261 @@ +package config + +import ( + "testing" + + "sigs.k8s.io/yaml" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +const ( + validateTestName = "ToCheckout" + validateFailuresTestName = "Validate" + toAuthTestName = "ToAuth" + toAuthNilTestName = "ToAuthNil" + ToFetchOptionsTestName = "ToFetchOptions" + toAuthNilError = "toAuthNilError" + URLTestName = "URLTest" + StringTestData = `test-data: + no-auth: + url: https://github.com/src-d/go-git.git + checkout: + tag: v3.0.0 + ssh-key-auth: + url: git@github.com:src-d/go-git.git + auth: + type: ssh-key + ssh-key: "testdata/test-key.pem" + username: git + checkout: + branch: master + ssh-pass: + url: /home/ubuntu/some-gitrepo + auth: + type: ssh-pass + ssh-pass: "qwerty123" + username: deployer + checkout: + commit-hash: 01c4f7f32beb9851ae8f119a6b8e497d2b1e2bb8 + http-basic-auth: + url: /home/ubuntu/some-gitrepo + auth: + type: http-basic + http-pass: "qwerty123" + username: deployer + checkout: + commit-hash: 01c4f7f32beb9851ae8f119a6b8e497d2b1e2bb8 + empty-checkout: + url: /home/ubuntu/some-gitrepo + auth: + type: http-basic + http-pass: "qwerty123" + username: deployer + wrong-type-auth: + url: /home/ubuntu/some-gitrepo + auth: + type: wrong-type + http-pass: "qwerty123" + username: deployer + checkout: + commit-hash: 01c4f7f32beb9851ae8f119a6b8e497d2b1e2bb8 + mutually-exclusive-auth-opts: + url: /home/ubuntu/some-gitrepo + auth: + type: http-basic + ssh-key: "/path-to-key" + username: deployer + mutually-exclusive-checkout-opts: + url: /home/ubuntu/some-gitrepo + checkout: + commit-hash: 01c4f7f32beb9851ae8f119a6b8e497d2b1e2bb8 + branch: master + mutually-exclusive-auth-opts-ssh-key: + url: /home/ubuntu/some-gitrepo + auth: + type: ssh-key + http-pass: "qwerty123" + ssh-key: "/path-to-key" + username: deployer + checkout: + commit-hash: 01c4f7f32beb9851ae8f119a6b8e497d2b1e2bb8 + mutually-exclusive-auth-opts-ssh-pass: + url: /home/ubuntu/some-gitrepo + auth: + type: ssh-pass + ssh-pass: "qwerty123" + http-pass: "qwerty123" + ssh-key: "/path-to-key" + username: deployer + checkout: + commit-hash: 01c4f7f32beb9851ae8f119a6b8e497d2b1e2bb8` +) + +var ( + TestCaseMap = map[string]*TestCase{ + validateTestName: { + expectError: false, + dataMapEntry: []string{"http-basic-auth", "ssh-key-auth", "no-auth", "empty-checkout"}, + expectedNil: false, + }, + validateFailuresTestName: { + expectError: true, + dataMapEntry: []string{"wrong-type-auth", + "mutually-exclusive-auth-opts", + "mutually-exclusive-checkout-opts", + "mutually-exclusive-auth-opts-ssh-key", + "mutually-exclusive-auth-opts-ssh-pass"}, + expectedNil: false, + }, + toAuthTestName: { + expectError: false, + dataMapEntry: []string{"ssh-key-auth", + "http-basic-auth", + "ssh-pass"}, + + expectedNil: false, + }, + toAuthNilError: { + expectError: true, + dataMapEntry: []string{"wrong-type-auth"}, + expectedNil: true, + }, + toAuthNilTestName: { + expectError: false, + dataMapEntry: []string{"no-auth"}, + expectedNil: true, + }, + ToFetchOptionsTestName: { + expectError: false, + dataMapEntry: []string{"no-auth"}, + expectedNil: false, + }, + URLTestName: { + expectError: false, + expectedNil: false, + dataMapEntry: []string{"no-auth"}, + }, + } +) + +type TestCase struct { + expectError bool + // this maps to TestData map in TestRepos struct + dataMapEntry []string + expectedNil bool +} + +type TestRepos struct { + TestData map[string]*Repository `json:"test-data"` +} + +func TestToCheckout(t *testing.T) { + data := &TestRepos{} + err := yaml.Unmarshal([]byte(StringTestData), data) + require.NoError(t, err) + + testCase := TestCaseMap[validateTestName] + + for _, name := range testCase.dataMapEntry { + repo := data.TestData[name] + require.NotNil(t, repo) + co := repo.ToCheckoutOptions(false) + if testCase.expectedNil { + assert.Nil(t, co) + } else { + assert.NotNil(t, co) + assert.NoError(t, co.Validate()) + } + } +} + +func TestToAuth(t *testing.T) { + data := &TestRepos{} + err := yaml.Unmarshal([]byte(StringTestData), data) + require.NoError(t, err) + + for _, testCaseName := range []string{toAuthTestName, toAuthNilTestName, toAuthNilError} { + testCase := TestCaseMap[testCaseName] + for _, name := range testCase.dataMapEntry { + repo := data.TestData[name] + auth, authErr := repo.ToAuth() + if testCase.expectError { + assert.Error(t, authErr) + } else { + assert.NoError(t, authErr) + } + if testCase.expectedNil { + assert.Nil(t, auth) + } else { + assert.NotNil(t, auth) + } + } + } +} + +func TestValidateRepository(t *testing.T) { + data := &TestRepos{} + err := yaml.Unmarshal([]byte(StringTestData), data) + require.NoError(t, err) + + for _, testCaseName := range []string{validateTestName, validateFailuresTestName} { + testCase := TestCaseMap[testCaseName] + for _, name := range testCase.dataMapEntry { + repo := data.TestData[name] + err := repo.Validate() + if testCase.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + if testCase.expectedNil { + assert.Nil(t, repo) + } else { + assert.NotNil(t, repo) + } + } + } +} + +func TestToFetchOptions(t *testing.T) { + data := &TestRepos{} + err := yaml.Unmarshal([]byte(StringTestData), data) + require.NoError(t, err) + + testCase := TestCaseMap[ToFetchOptionsTestName] + + for _, name := range testCase.dataMapEntry { + repo := data.TestData[name] + require.NotNil(t, repo) + assert.NotNil(t, repo.ToFetchOptions(nil)) + } +} + +func TestToCloneOptions(t *testing.T) { + data := &TestRepos{} + err := yaml.Unmarshal([]byte(StringTestData), data) + require.NoError(t, err) + + testCase := TestCaseMap[ToFetchOptionsTestName] + + for _, name := range testCase.dataMapEntry { + repo := data.TestData[name] + require.NotNil(t, repo) + assert.NotNil(t, repo.ToCloneOptions(nil)) + } +} + +func TestURL(t *testing.T) { + data := &TestRepos{} + err := yaml.Unmarshal([]byte(StringTestData), data) + require.NoError(t, err) + + testCase := TestCaseMap[URLTestName] + + for _, name := range testCase.dataMapEntry { + repo := data.TestData[name] + require.NotNil(t, repo) + assert.Equal(t, repo.URLString, repo.URL()) + } +} diff --git a/pkg/config/test_utils.go b/pkg/config/test_utils.go index 3e63d240d..ccffd3648 100644 --- a/pkg/config/test_utils.go +++ b/pkg/config/test_utils.go @@ -18,7 +18,6 @@ package config import ( "io/ioutil" - "net/url" "path/filepath" "testing" @@ -86,19 +85,34 @@ func DummyCluster() *Cluster { func DummyManifest() *Manifest { m := NewManifest() // Repositories is the map of repository adddressable by a name - m.Repositories["dummy"] = DummyRepository() + m.Repository = DummyRepository() m.TargetPath = "/var/tmp/" return m } func DummyRepository() *Repository { - // TODO(howell): handle this error - //nolint: errcheck - parsedUrl, _ := url.Parse("http://dummy.url.com") return &Repository{ - Url: parsedUrl, - Username: "dummy_user", - TargetPath: "dummy_targetpath", + URLString: "http://dummy.url.com", + CheckoutOptions: &RepoCheckout{ + Tag: "v1.0.1", + }, + Auth: &RepoAuth{ + Type: "ssh-key", + KeyPath: "testdata/test-key.pem", + }, + } +} + +func DummyRepoAuth() *RepoAuth { + return &RepoAuth{ + Type: "ssh-key", + KeyPath: "testdata/test-key.pem", + } +} + +func DummyRepoCheckout() *RepoCheckout { + return &RepoCheckout{ + Tag: "v1.0.1", } } diff --git a/pkg/config/testdata/config-string.yaml b/pkg/config/testdata/config-string.yaml index 7274cd532..4a29ec358 100644 --- a/pkg/config/testdata/config-string.yaml +++ b/pkg/config/testdata/config-string.yaml @@ -16,20 +16,15 @@ current-context: dummy_context kind: Config manifests: dummy_manifest: - repositories: - dummy: - target-path: dummy_targetpath - url: - ForceQuery: false - Fragment: "" - Host: dummy.url.com - Opaque: "" - Path: "" - RawPath: "" - RawQuery: "" - Scheme: http - User: null - username: dummy_user + repository: + auth: + ssh-key: testdata/test-key.pem + type: ssh-key + checkout: + branch: "" + remote-ref: "" + tag: v1.0.1 + url: http://dummy.url.com target-path: /var/tmp/ modules-config: bootstrapInfo: diff --git a/pkg/config/testdata/manifest-string.yaml b/pkg/config/testdata/manifest-string.yaml index 6e71e47e6..df8299cd1 100644 --- a/pkg/config/testdata/manifest-string.yaml +++ b/pkg/config/testdata/manifest-string.yaml @@ -1,15 +1,10 @@ -repositories: - dummy: - target-path: dummy_targetpath - url: - ForceQuery: false - Fragment: "" - Host: dummy.url.com - Opaque: "" - Path: "" - RawPath: "" - RawQuery: "" - Scheme: http - User: null - username: dummy_user +repository: + auth: + ssh-key: testdata/test-key.pem + type: ssh-key + checkout: + branch: "" + remote-ref: "" + tag: v1.0.1 + url: http://dummy.url.com target-path: /var/tmp/ diff --git a/pkg/config/testdata/repo-auth-string.yaml b/pkg/config/testdata/repo-auth-string.yaml new file mode 100644 index 000000000..6760b3bc3 --- /dev/null +++ b/pkg/config/testdata/repo-auth-string.yaml @@ -0,0 +1,2 @@ +ssh-key: testdata/test-key.pem +type: ssh-key diff --git a/pkg/config/testdata/repo-checkout-string.yaml b/pkg/config/testdata/repo-checkout-string.yaml new file mode 100644 index 000000000..4bb8a0a5b --- /dev/null +++ b/pkg/config/testdata/repo-checkout-string.yaml @@ -0,0 +1,3 @@ +branch: "" +remote-ref: "" +tag: v1.0.1 diff --git a/pkg/config/testdata/repository-string.yaml b/pkg/config/testdata/repository-string.yaml index d2ad53154..580b6fd6b 100644 --- a/pkg/config/testdata/repository-string.yaml +++ b/pkg/config/testdata/repository-string.yaml @@ -1,12 +1,8 @@ -target-path: dummy_targetpath -url: - ForceQuery: false - Fragment: "" - Host: dummy.url.com - Opaque: "" - Path: "" - RawPath: "" - RawQuery: "" - Scheme: http - User: null -username: dummy_user +auth: + ssh-key: testdata/test-key.pem + type: ssh-key +checkout: + branch: "" + remote-ref: "" + tag: v1.0.1 +url: http://dummy.url.com diff --git a/pkg/config/testdata/test-key.pem b/pkg/config/testdata/test-key.pem new file mode 100644 index 000000000..bf21eb756 --- /dev/null +++ b/pkg/config/testdata/test-key.pem @@ -0,0 +1,28 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEA7Myn0IKrKpR3oORtrk7lblDT5EurDMt0BW1wJ21wD8+vXaIh6LcR +KMoBsus/lo7gPHPckl5nBp9fUThxqMMS3YEJBdDUFgE7cAo8O5zL4KjRVKvELuz+CqYUT7 +uZLtWQXtAFBwwMKktrkP3td2KlTIthF8MdCBoXwuj3I/Mw/PDavvtoW2uWPm769GLl9gAf +RWCexSpjfiOW2Uw3m68yzI4ET/AVAXSATkAEmB0r4+SCZnfoC+Nha3Y3TjLD08B35/RwAF +r53Zh4vKhkTnIbb2ks1zr0MdH3usuAc2xVRmjz0PU/ckcBsZRVp/KVCtrCyEHW6FNVHeAx +zzGe5Sz2YQAAA+hFhfzsRYX87AAAAAdzc2gtcnNhAAABAQDszKfQgqsqlHeg5G2uTuVuUN +PkS6sMy3QFbXAnbXAPz69doiHotxEoygGy6z+WjuA8c9ySXmcGn19ROHGowxLdgQkF0NQW +ATtwCjw7nMvgqNFUq8Qu7P4KphRPu5ku1ZBe0AUHDAwqS2uQ/e13YqVMi2EXwx0IGhfC6P +cj8zD88Nq++2hba5Y+bvr0YuX2AB9FYJ7FKmN+I5bZTDebrzLMjgRP8BUBdIBOQASYHSvj +5IJmd+gL42FrdjdOMsPTwHfn9HAAWvndmHi8qGROchtvaSzXOvQx0fe6y4BzbFVGaPPQ9T +9yRwGxlFWn8pUK2sLIQdboU1Ud4DHPMZ7lLPZhAAAAAwEAAQAAAQATfkl2Rbt3dt9eNE+/ +IKmMakT3Ly92jy0O4VJxPHYUJyGlkJpAAQn9lJuNMgZ7C2n0MAmBVxoeFnKPShk5Lk3YRC +4M94LuCM3uzDjnI2I5LUyGLtmoj0PedouHgMb8bwJCe9deHCTIOosxVWX+BPXclkC45wv1 +xcgc+HaX1AY9XCHpC9UEJ6oNIX990W+1D4wbsXjw3nal2jzaIe6we/FUwrrF0QRM8CHpB+ +GAZN7Z8GjzVidmLRiS88EDDMnjhkfww362WrKd53THCtahZma8nfvJonKAsT8fWEXf8WMK +QKquNLJoVgHydPnH8S2+R61W7r3wuiuVEUbgB8VbCAN9AAAAgQCvrmEiDJyiB0n2fn4POt +WPHHYjzshtTau+vVcnDd218TTRHOzwn243+q9wQwEKTtTFKMMhqDLwqkUkoJppg/zXtfFj +zawnF3fBrqlqHCxfzH/hqqf5s+Xwm5wflivhgg+XH3Hm9RzX9QfVTovrnYgPGVpNN/LLCB +t579gzKjM/5wAAAIEA/HKnVXNNVwcA7JfDSF4E+dp1FaGNn9qHm0zfBKZt9owl+dLjMUOV +0DXZdgwIRNsumMrgEKFhn4pSk3HlfKyjPKTKz3Z3zA4Nkc5u+Bd+nXpPWtMXoewQfMjBM+ +U3w/ksVFywa7A+JtqyCqrBEHAwZh4mrmOftvWtk7WVjbJ0MNMAAACBAPAhomm+R/6M0PZu +a9I9t2zSDT5W736tY8RMMArv0LyrQ5KyBBsGTrpKI7hWuoshOwTPNxnI6VsArcXMLYkwsP +kQy8sP6wKBq+VXTMA9WwY8n5EGpEmhNNff5SJcap2Prr8cOoxv4GmxfvdJ7JYLY9HjR/VX +8yP1Yk8UN4wC99t7AAAAMGtrYWxpbm92c2tpeUB2cG4tNjgtOTAtMTA1LTE0Ny52cG4uc2 +JjaXMuc2JjLmNvbQEC +-----END OPENSSH PRIVATE KEY----- diff --git a/pkg/config/types.go b/pkg/config/types.go index f38989d29..13d0bea71 100644 --- a/pkg/config/types.go +++ b/pkg/config/types.go @@ -17,8 +17,6 @@ limitations under the License. package config import ( - "net/url" - kubeconfig "k8s.io/client-go/tools/clientcmd/api" ) @@ -119,9 +117,10 @@ type AuthInfo struct { // find the yaml manifests that airship uses to perform its operations) type Manifest struct { // Repositories is the map of repository adddressable by a name - Repositories map[string]*Repository `json:"repositories"` - - // Local Target path for working or home directory for all Manifest Cloned/Returned/Generated + Repository *Repository `json:"repository"` + // ExtraRepositories is the map of extra repositories addressable by a name + ExtraRepositories map[string]*Repository `json:"extra-repositories,omitempty"` + // TargetPath Local Target path for working or home dirctory for all Manifest Cloned/Returned/Generated TargetPath string `json:"target-path"` } @@ -129,16 +128,45 @@ type Manifest struct { // Information such as location, authentication info, // as well as details of what to get such as branch, tag, commit it, etc. type Repository struct { - // URL for Repository - Url *url.URL `json:"url"` + // URLString for Repository + URLString string `json:"url"` + // Auth holds authentication options against remote + Auth *RepoAuth `json:"auth,omitempty"` + // CheckoutOptions holds options to checkout repository + CheckoutOptions *RepoCheckout `json:"checkout,omitempty"` +} - // Username is the username for authentication to the repository . - // +optional +// RepoAuth struct describes method of authentication agaist given repository +type RepoAuth struct { + // Type of authentication method to be used with given repository + // supported types are "ssh-key", "ssh-pass", "http-basic" + Type string `json:"type,omitempty"` + //KeyPassword is a password decrypt ssh private key (used with ssh-key auth type) + KeyPassword string `json:"key-pass,omitempty"` + // KeyPath is path to private ssh key on disk (used with ssh-key auth type) + KeyPath string `json:"ssh-key,omitempty"` + //HTTPPassword is password for basic http authentication (used with http-basic auth type) + HTTPPassword string `json:"http-pass,omitempty"` + // SSHPassword is password for ssh password authentication (used with ssh-pass) + SSHPassword string `json:"ssh-pass,omitempty"` + // Username to authenticate against git remote (used with any type) Username string `json:"username,omitempty"` +} - // Clone To Name Should always be relative to the setting of Manifest TargetPath. - // Defines where ths repo will be cloned to locally. - TargetPath string `json:"target-path"` +// RepoCheckout container holds information how to checkout repository +// Each field is mutually exclusive +type RepoCheckout struct { + // CommitHash is full hash of the commit that will be used to checkout + CommitHash string `json:"commit-hash,omitempty"` + // Branch is the branch name to checkout + Branch string `json:"branch"` + // Tag is the tag name to checkout + Tag string `json:"tag"` + // RemoteRef is not supported currently TODO + // RemoteRef is used for remote checkouts such as gerrit change requests/github pull request + // for example refs/changes/04/691202/5 + // TODO Add support for fetching remote refs + RemoteRef string `json:"remote-ref"` } // Holds the complex cluster name information diff --git a/pkg/config/utils.go b/pkg/config/utils.go index 668ea0c68..52ff7218a 100644 --- a/pkg/config/utils.go +++ b/pkg/config/utils.go @@ -43,7 +43,8 @@ func NewCluster() *Cluster { // object with non-nil maps func NewManifest() *Manifest { return &Manifest{ - Repositories: make(map[string]*Repository), + Repository: NewRepository(), + ExtraRepositories: make(map[string]*Repository), } }