From 5f1ac575b82d92a4e5d5e427b8fdb399224a4a0d Mon Sep 17 00:00:00 2001 From: Rohith Jayawardene Date: Wed, 7 Jun 2023 20:57:38 +0100 Subject: [PATCH] [BUGFIX] - Submodules Source Fix (#799) Fixing the source to correctly handle submodules --- cmd/source/source.go | 64 +++++++++++------- cmd/source/source_test.go | 137 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 177 insertions(+), 24 deletions(-) create mode 100644 cmd/source/source_test.go diff --git a/cmd/source/source.go b/cmd/source/source.go index 030c9fdda..30afb06ad 100644 --- a/cmd/source/source.go +++ b/cmd/source/source.go @@ -89,11 +89,6 @@ func Run(ctx context.Context, source, destination string, timeout time.Duration, return errors.New("timeout can not be less than zero") } - unprefixed := strings.TrimPrefix(source, "git::") - uri, err := url.Parse(unprefixed) - if err != nil { - return fmt.Errorf("failed to parse source url: %w", err) - } location := source // @step: check for an ssh key in the environment variables and provision a configuration @@ -121,28 +116,13 @@ func Run(ctx context.Context, source, destination string, timeout time.Duration, } case os.Getenv("GIT_PASSWORD") != "" || os.Getenv("GIT_USERNAME") != "": - var src string - - switch { - case os.Getenv("GIT_PASSWORD") != "" && os.Getenv("GIT_USERNAME") == "": - src = fmt.Sprintf("%s://%s@%s%s", - uri.Scheme, - os.Getenv("GIT_PASSWORD"), - uri.Hostname(), uri.Path, - ) - - default: - src = fmt.Sprintf("%s://%s:%s@%s%s", - uri.Scheme, - os.Getenv("GIT_USERNAME"), - os.Getenv("GIT_PASSWORD"), - uri.Hostname(), uri.Path, - ) + src, dest, err := sanitizeSource(location) + if err != nil { + return fmt.Errorf("failed to parse source url: %w", err) } - data, err := template.New(gitConfig, map[string]string{ "Source": src, - "Destination": unprefixed, + "Destination": dest, }) if err != nil { return fmt.Errorf("failed to create git config template: %w", err) @@ -265,3 +245,39 @@ func Run(ctx context.Context, source, destination string, timeout time.Duration, //nolint:gosec return exec.Command("cp", []string{"-rT", "/tmp/source/", destination}...).Run() } + +// sanitizeSource is responsible for sanitizing the source url +func sanitizeSource(location string) (string, string, error) { + var source, destination string + + uri, err := url.Parse(strings.TrimPrefix(location, "git::")) + if err != nil { + return "", "", err + } + path := uri.Path + + if strings.Contains(uri.Path, "//") { + path = strings.Split(uri.Path, "//")[0] + } + + destination = fmt.Sprintf("%s://%s%s", uri.Scheme, uri.Host, path) + + switch { + case os.Getenv("GIT_PASSWORD") != "" && os.Getenv("GIT_USERNAME") == "": + source = fmt.Sprintf("%s://%s@%s%s", + uri.Scheme, + os.Getenv("GIT_PASSWORD"), + uri.Hostname(), path, + ) + + default: + source = fmt.Sprintf("%s://%s:%s@%s%s", + uri.Scheme, + os.Getenv("GIT_USERNAME"), + os.Getenv("GIT_PASSWORD"), + uri.Hostname(), path, + ) + } + + return source, destination, nil +} diff --git a/cmd/source/source_test.go b/cmd/source/source_test.go new file mode 100644 index 000000000..cae50e306 --- /dev/null +++ b/cmd/source/source_test.go @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2023 Appvia Ltd + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package main + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSantizeSource(t *testing.T) { + /* + These will sanitize to the gitconfig urls in such a way. Note, subpaths are ignored + + [url "https://GIT_PASSWORD@github.com/appvia/terranetes-controller"] + insteadOf = https://github.com/appvia/terranetes-controller + [url "https://GIT_USERNAME:GIT_PASSWORD@github.com/appvia/terranetes-controller"] + insteadOf = https://github.com/appvia/terranetes-controller + */ + + cases := []struct { + Location string + Source string + Destination string + ExpectError bool + Environment map[string]string + }{ + { + Location: "https://github.com/appvia/terranetes-controller.git", + Source: "https://user:pass@github.com/appvia/terranetes-controller.git", + Destination: "https://github.com/appvia/terranetes-controller.git", + Environment: map[string]string{ + "GIT_USERNAME": "user", + "GIT_PASSWORD": "pass", + }, + }, + { + Location: "https://github.com/appvia/terranetes-controller.git//module/test", + Source: "https://user:pass@github.com/appvia/terranetes-controller.git", + Destination: "https://github.com/appvia/terranetes-controller.git", + Environment: map[string]string{ + "GIT_USERNAME": "user", + "GIT_PASSWORD": "pass", + }, + }, + { + Location: "git::https://github.com/appvia/terranetes-controller.git//module/test", + Source: "https://user:pass@github.com/appvia/terranetes-controller.git", + Destination: "https://github.com/appvia/terranetes-controller.git", + Environment: map[string]string{ + "GIT_USERNAME": "user", + "GIT_PASSWORD": "pass", + }, + }, + { + Location: "git::https://github.com/appvia/terranetes-controller.git", + Source: "https://token@github.com/appvia/terranetes-controller.git", + Destination: "https://github.com/appvia/terranetes-controller.git", + Environment: map[string]string{ + "GIT_PASSWORD": "token", + }, + }, + { + Location: "https://github.com/appvia/terranetes-controller.git", + Source: "https://token@github.com/appvia/terranetes-controller.git", + Destination: "https://github.com/appvia/terranetes-controller.git", + Environment: map[string]string{ + "GIT_PASSWORD": "token", + }, + }, + { + Location: "https://github.com/appvia/terranetes-controller.git//module/source", + Source: "https://token@github.com/appvia/terranetes-controller.git", + Destination: "https://github.com/appvia/terranetes-controller.git", + Environment: map[string]string{ + "GIT_PASSWORD": "token", + }, + }, + { + Location: "git::https://github.com/appvia/terranetes-controller.git//module/source", + Source: "https://token@github.com/appvia/terranetes-controller.git", + Destination: "https://github.com/appvia/terranetes-controller.git", + Environment: map[string]string{ + "GIT_PASSWORD": "token", + }, + }, + { + Location: "git::https://dev.azure.com/gambol99/terranetes-controller/_git/e2e//module/submodule", + Source: "https://token@dev.azure.com/gambol99/terranetes-controller/_git/e2e", + Destination: "https://dev.azure.com/gambol99/terranetes-controller/_git/e2e", + Environment: map[string]string{ + "GIT_PASSWORD": "token", + }, + }, + { + Location: "git::https://dev.azure.com/gambol99/terranetes-controller/_git/e2e//module/submodule", + Source: "https://user:token@dev.azure.com/gambol99/terranetes-controller/_git/e2e", + Destination: "https://dev.azure.com/gambol99/terranetes-controller/_git/e2e", + Environment: map[string]string{ + "GIT_USERNAME": "user", + "GIT_PASSWORD": "token", + }, + }, + } + for i, c := range cases { + os.Unsetenv("GIT_PASSORD") + os.Unsetenv("GIT_USERNAME") + for k, v := range c.Environment { + assert.NoError(t, os.Setenv(k, v)) + } + + source, destination, err := sanitizeSource(c.Location) + if c.ExpectError { + assert.Error(t, err, "case %d, expected an error", i) + } else { + assert.NoError(t, err, "case %d, expected no error", i) + } + assert.Equal(t, c.Source, source, "case %d, expected source to match", i) + assert.Equal(t, c.Destination, destination, "case %d, expected destination to match", i) + } +}