Skip to content

Commit bbd39d3

Browse files
committed
feat: support username password in http giturls
1 parent 44bfe66 commit bbd39d3

File tree

6 files changed

+244
-10
lines changed

6 files changed

+244
-10
lines changed

cmd/template_gitcredential.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"net/url"
6+
"os"
7+
8+
"github.com/spf13/cobra"
9+
generator "github.com/uselagoon/build-deploy-tool/internal/generator"
10+
"github.com/uselagoon/build-deploy-tool/internal/helpers"
11+
"github.com/uselagoon/build-deploy-tool/internal/lagoon"
12+
)
13+
14+
var templateGitCredential = &cobra.Command{
15+
Use: "git-credential",
16+
Aliases: []string{"gitcred", "gc"},
17+
Short: "Create a git credential file",
18+
RunE: func(cmd *cobra.Command, args []string) error {
19+
credFile, err := cmd.Flags().GetString("credential-file")
20+
if err != nil {
21+
return fmt.Errorf("error reading credential-file flag: %v", err)
22+
}
23+
gitURL, err := TemplateGitCredential(credFile)
24+
if err != nil {
25+
return err
26+
}
27+
fmt.Println(gitURL)
28+
return nil
29+
},
30+
}
31+
32+
// TemplateGitCredential creates a git credential secret if required
33+
func TemplateGitCredential(file string) (string, error) {
34+
// get the source repository from the build pod environment variables
35+
sourceRepository := helpers.GetEnv("SOURCE_REPOSITORY", "", false)
36+
37+
// because this runs before anything has been checked out in the repo, we have to query variables directly
38+
// and handle any merging that is usually done in generator
39+
variables := generator.GetLagoonEnvVars()
40+
41+
// parse the repository into a url struct so the username and password can be injected into http/https based urls
42+
u, err := url.Parse(sourceRepository)
43+
if err != nil {
44+
return "", fmt.Errorf("unable to parse provided gitUrl")
45+
}
46+
cred := url.URL{}
47+
// if this is a http or https based url, it may need a username and password
48+
if helpers.Contains([]string{"http", "https"}, u.Scheme) {
49+
// since the provided source repository could be public or private, check for the `LAGOON_GIT_HTTPS_X`
50+
// variables, ignore errors for these lookups as they're in most cases going to be "not found"
51+
username, _ := lagoon.GetLagoonVariable("LAGOON_GIT_HTTPS_USERNAME", []string{"build"}, variables)
52+
password, _ := lagoon.GetLagoonVariable("LAGOON_GIT_HTTPS_PASSWORD", []string{"build"}, variables)
53+
if username != nil && password == nil {
54+
return "", fmt.Errorf("LAGOON_GIT_HTTPS_USERNAME was provided, but not LAGOON_GIT_HTTPS_PASSWORD")
55+
}
56+
if username == nil && password != nil {
57+
return "", fmt.Errorf("LAGOON_GIT_HTTPS_PASSWORD was provided, but not LAGOON_GIT_HTTPS_USERNAME")
58+
}
59+
// if both are found, set the user auth into the url
60+
if username != nil && password != nil {
61+
u.User = url.UserPassword(username.Value, password.Value)
62+
cred.Scheme = u.Scheme
63+
cred.Host = u.Host
64+
cred.User = u.User
65+
err := os.WriteFile(file, []byte(cred.String()), 0644)
66+
if err != nil {
67+
return "", err
68+
}
69+
// return the new url only if it was modified with a username and password
70+
return "store", nil
71+
}
72+
}
73+
// otherwise return whatever was provided to the build
74+
return "", nil
75+
}
76+
77+
func init() {
78+
templateCmd.AddCommand(templateGitCredential)
79+
templateGitCredential.Flags().String("credential-file", "", "The file to store the credential in")
80+
}

cmd/template_gitcredential_test.go

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"os"
6+
"reflect"
7+
"testing"
8+
9+
"github.com/andreyvit/diff"
10+
"github.com/uselagoon/build-deploy-tool/internal/helpers"
11+
12+
// changes the testing to source from root so paths to test resources must be defined from repo root
13+
_ "github.com/uselagoon/build-deploy-tool/internal/testing"
14+
)
15+
16+
func TestTemplateGitCredential(t *testing.T) {
17+
tests := []struct {
18+
name string
19+
vars []helpers.EnvironmentVariable
20+
want string
21+
resultFilename string
22+
testResultfilename string
23+
wantFile bool
24+
wantErr bool
25+
}{
26+
{
27+
name: "test1 check if variables are defined",
28+
vars: []helpers.EnvironmentVariable{
29+
{
30+
Name: "SOURCE_REPOSITORY",
31+
Value: "https://example.com/lagoon-demo.git",
32+
},
33+
{
34+
Name: "LAGOON_ENVIRONMENT_VARIABLES",
35+
Value: `[{"name":"LAGOON_GIT_HTTPS_USERNAME","scope":"build","value":"user1"},{"name":"LAGOON_GIT_HTTPS_PASSWORD","scope":"build","value":"somep@ssword"}]`,
36+
},
37+
},
38+
resultFilename: "test1",
39+
testResultfilename: "internal/testdata/git-credentials/test1",
40+
wantFile: true,
41+
want: "store",
42+
},
43+
{
44+
name: "test2 check if variable are defined (no username)",
45+
vars: []helpers.EnvironmentVariable{
46+
{
47+
Name: "SOURCE_REPOSITORY",
48+
Value: "https://example.com/lagoon-demo.git",
49+
},
50+
{
51+
Name: "LAGOON_ENVIRONMENT_VARIABLES",
52+
Value: `[{"name":"LAGOON_GIT_HTTPS_PASSWORD","scope":"build","value":"somep@ssword"}]`,
53+
},
54+
},
55+
wantErr: true,
56+
want: "",
57+
},
58+
{
59+
name: "test3 check if variable are defined (no password)",
60+
vars: []helpers.EnvironmentVariable{
61+
{
62+
Name: "SOURCE_REPOSITORY",
63+
Value: "https://example.com/lagoon-demo.git",
64+
},
65+
{
66+
Name: "LAGOON_ENVIRONMENT_VARIABLES",
67+
Value: `[{"name":"LAGOON_GIT_HTTPS_USERNAME","scope":"build","value":"user1"}]`,
68+
},
69+
},
70+
wantErr: true,
71+
want: "",
72+
},
73+
{
74+
name: "test4 no username or password",
75+
vars: []helpers.EnvironmentVariable{
76+
{
77+
Name: "SOURCE_REPOSITORY",
78+
Value: "https://example.com/lagoon-demo.git",
79+
},
80+
{
81+
Name: "LAGOON_ENVIRONMENT_VARIABLES",
82+
Value: `[]`,
83+
},
84+
},
85+
want: "",
86+
},
87+
{
88+
name: "test5 ssh pass through",
89+
vars: []helpers.EnvironmentVariable{
90+
{
91+
Name: "SOURCE_REPOSITORY",
92+
Value: "ssh://git@example.com/lagoon-demo.git",
93+
},
94+
{
95+
Name: "LAGOON_ENVIRONMENT_VARIABLES",
96+
Value: `[]`,
97+
},
98+
},
99+
want: "",
100+
},
101+
}
102+
for _, tt := range tests {
103+
t.Run(tt.name, func(t *testing.T) {
104+
for _, envVar := range tt.vars {
105+
err := os.Setenv(envVar.Name, envVar.Value)
106+
if err != nil {
107+
t.Errorf("%v", err)
108+
}
109+
}
110+
tempResults := "testoutput"
111+
err := os.MkdirAll(tempResults, 0755)
112+
if err != nil {
113+
t.Errorf("couldn't create directory %v: %v", tempResults, err)
114+
}
115+
defer os.RemoveAll(tempResults)
116+
got, err := TemplateGitCredential(fmt.Sprintf("%s/%s", tempResults, tt.resultFilename))
117+
if (err != nil) != tt.wantErr {
118+
t.Errorf("TemplateGitCredential() error = %v, wantErr %v", err, tt.wantErr)
119+
return
120+
}
121+
if !reflect.DeepEqual(got, tt.want) {
122+
t.Errorf("TemplateGitCredential() = %v, want %v", got, tt.want)
123+
}
124+
if tt.wantFile {
125+
f1, err := os.ReadFile(fmt.Sprintf("%s/%s", tempResults, tt.resultFilename))
126+
if err != nil {
127+
t.Errorf("couldn't read file %v: %v", tempResults, err)
128+
}
129+
r1, err := os.ReadFile(tt.testResultfilename)
130+
if err != nil {
131+
t.Errorf("couldn't read file %v: %v", tt.wantFile, err)
132+
}
133+
if !reflect.DeepEqual(f1, r1) {
134+
t.Errorf("TemplateGitCredential() = \n%v", diff.LineDiff(string(r1), string(f1)))
135+
}
136+
}
137+
t.Cleanup(func() {
138+
helpers.UnsetEnvVars(tt.vars)
139+
})
140+
})
141+
}
142+
}

internal/generator/generator.go

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,6 @@ func NewGenerator(
161161
// if k8upv2 (k8up.io/v1) version is specified by remote, set that here
162162
buildValues.Backup.K8upVersion = "v2"
163163
}
164-
// get the project and environment variables
165-
projectVariables := helpers.GetEnv("LAGOON_PROJECT_VARIABLES", generator.ProjectVariables, generator.Debug)
166-
environmentVariables := helpers.GetEnv("LAGOON_ENVIRONMENT_VARIABLES", generator.EnvironmentVariables, generator.Debug)
167164

168165
// read the .lagoon.yml file and the LAGOON_YAML_OVERRIDE if set
169166
if err := LoadAndUnmarshalLagoonYml(generator.LagoonYAML, generator.LagoonYAMLOverride, "LAGOON_YAML_OVERRIDE", lYAML, projectName, generator.Debug); err != nil {
@@ -267,14 +264,9 @@ func NewGenerator(
267264
buildValues.DynamicDBaaSSecrets = strings.Split(dynamicDBaaSSecrets, ",")
268265
}
269266

270-
// unmarshal and then merge the two so there is only 1 set of variables to iterate over
271-
projectVars := []lagoon.EnvironmentVariable{}
272-
envVars := []lagoon.EnvironmentVariable{}
273-
json.Unmarshal([]byte(projectVariables), &projectVars)
274-
json.Unmarshal([]byte(environmentVariables), &envVars)
275-
267+
// get the project and environment variables
276268
// set the environment variables to all the known merged variables so far
277-
buildValues.EnvironmentVariables = lagoon.MergeVariables(projectVars, envVars)
269+
buildValues.EnvironmentVariables = GetLagoonEnvVars()
278270

279271
// if the core version is provided from the API, set the buildvalues LagoonVersion to this instead
280272
lagoonCoreVersion, _ := lagoon.GetLagoonVariable("LAGOON_SYSTEM_CORE_VERSION", []string{"internal_system"}, buildValues.EnvironmentVariables)

internal/generator/helpers_generator.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212

1313
"github.com/spf13/cobra"
1414
"github.com/uselagoon/build-deploy-tool/internal/dbaasclient"
15+
"github.com/uselagoon/build-deploy-tool/internal/helpers"
1516
"github.com/uselagoon/build-deploy-tool/internal/lagoon"
1617
"k8s.io/apimachinery/pkg/api/resource"
1718
)
@@ -314,3 +315,17 @@ func determineRefreshImage(serviceName, imageName string, envVars []lagoon.Envir
314315
}
315316
return parsed, errs
316317
}
318+
319+
// GetLagoonEnvVars will get the project and environment variables from the build
320+
// unmarshal them and then merge them down, where environment will override project
321+
func GetLagoonEnvVars() []lagoon.EnvironmentVariable {
322+
projectVariables := helpers.GetEnv("LAGOON_PROJECT_VARIABLES", "", false)
323+
environmentVariables := helpers.GetEnv("LAGOON_ENVIRONMENT_VARIABLES", "", false)
324+
// unmarshal and then merge the two so there is only 1 set of variables to iterate over
325+
projectVars := []lagoon.EnvironmentVariable{}
326+
envVars := []lagoon.EnvironmentVariable{}
327+
_ = json.Unmarshal([]byte(projectVariables), &projectVars)
328+
_ = json.Unmarshal([]byte(environmentVariables), &envVars)
329+
// set the environment variables to all the known merged variables so far
330+
return lagoon.MergeVariables(projectVars, envVars)
331+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
https://user1:somep%40ssword@example.com

legacy/build-deploy.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
1111
LAGOON_VERSION=$(cat /lagoon/version)
1212

1313
echo -e "##############################################\nBEGIN Checkout Repository\n##############################################"
14+
# check if a http/https url is defined, and if a username/password are supplied for it
15+
if [ "$(build-deploy-tool template git-credential --credential-file /home/.git-credentials)" == "store" ]; then
16+
git config --global credential.helper 'store --file /home/.git-credentials'
17+
fi
1418
if [ "$BUILD_TYPE" == "pullrequest" ]; then
1519
/kubectl-build-deploy/scripts/git-checkout-pull-merge.sh "$SOURCE_REPOSITORY" "$PR_HEAD_SHA" "$PR_BASE_SHA"
1620
else

0 commit comments

Comments
 (0)