Skip to content

Commit ae640d1

Browse files
committed
rework of credentials to use interfaces+structs
1 parent 85ccea6 commit ae640d1

12 files changed

+283
-155
lines changed

internal/app/bridgr/config/docker.go

+2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package config
22

33
import (
4+
"bridgr/internal/app/bridgr"
45
"fmt"
56
"log"
67
"path"
@@ -50,6 +51,7 @@ func parseDocker(conf tempConfig) Docker {
5051
default:
5152
log.Printf("DEBUG: Unknown configuration section for Docker: %+v", cfgBlock)
5253
}
54+
bridgr.Debugf("Final Docker configuration %+v", d)
5355
return d
5456
}
5557

internal/app/bridgr/config/files.go

+13-15
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,8 @@ type Files struct {
1616

1717
// FileItem is a discreet file definition object
1818
type FileItem struct {
19-
Source string
20-
Target string
21-
Protocol string
19+
Source *url.URL
20+
Target string
2221
}
2322

2423
// BaseDir is the top-level directory name for all objects written out under the Files worker
@@ -50,17 +49,24 @@ func parseFiles(conf tempConfig) Files {
5049
}
5150

5251
func (f *FileItem) parseSimple(s string) error {
53-
f.Protocol = getFileProtocol(s)
54-
f.Source = s
52+
url, err := url.Parse(s)
53+
if err != nil {
54+
return err
55+
}
56+
// setProtocol(url)
57+
f.Source = url
5558
f.Target = getFileTarget(s)
5659
return nil
5760
}
5861

5962
func (f *FileItem) parseComplex(s map[interface{}]interface{}) error {
6063
source := s["source"].(string)
6164
target := s["target"].(string)
62-
f.Protocol = getFileProtocol(source)
63-
f.Source = source
65+
url, err := url.Parse(source)
66+
if err != nil {
67+
return err
68+
}
69+
f.Source = url
6470
if strings.HasSuffix(target, "/") {
6571
f.Target = filepath.Join(new(Files).BaseDir(), target, filepath.Base(source))
6672
} else {
@@ -69,14 +75,6 @@ func (f *FileItem) parseComplex(s map[interface{}]interface{}) error {
6975
return nil
7076
}
7177

72-
func getFileProtocol(src string) string {
73-
url, _ := url.Parse(src)
74-
if url.Scheme == "" {
75-
return "file"
76-
}
77-
return url.Scheme
78-
}
79-
8078
func getFileTarget(src string) string {
8179
return filepath.Join(new(Files).BaseDir(), filepath.Base(src))
8280
}

internal/app/bridgr/config/files_internal_test.go

+35-44
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
package config
22

33
import (
4+
"net/url"
45
"os"
56
"path"
67
"testing"
8+
9+
"github.com/google/go-cmp/cmp"
710
)
811

12+
var rootFile, _ = url.Parse("/afile.xyz")
13+
var relativeFile, _ = url.Parse("my/file.abc")
14+
var file, _ = url.Parse("file://some/file/toget.zip")
15+
var httpFile, _ = url.Parse("http://mysite.com/file.gz")
16+
var httpsFile, _ = url.Parse("https://mysite.com/archive.tar")
17+
var blahFile, _ = url.Parse("blah://mysite.com/file.js")
918
var cwd, _ = os.Getwd()
1019
var dir = path.Join(cwd, "packages", "files")
1120

@@ -23,6 +32,7 @@ func TestParseFiles(t *testing.T) {
2332
{"nil", tempConfig{Files: nil}, 0, 0, 0},
2433
{"non string", tempConfig{Files: []interface{}{2}}, 0, 0, 0},
2534
{"multiple entries", tempConfig{Files: []interface{}{"file1.zip", "file2.tar"}}, 2, 2, 0},
35+
{"error - bad url", tempConfig{Files: []interface{}{"\x7f"}}, 0, 0, 0},
2636
}
2737

2838
for _, test := range tests {
@@ -40,24 +50,26 @@ func TestParseSimple(t *testing.T) {
4050
tests := []struct {
4151
given string
4252
expected FileItem
53+
isError bool
4354
}{
44-
{"/afile.zyx", FileItem{"/afile.zyx", path.Join(dir, "afile.zyx"), "file"}},
45-
{"my/file.abc", FileItem{"my/file.abc", path.Join(dir, "file.abc"), "file"}},
46-
{"file://some/file/toget.zip", FileItem{"file://some/file/toget.zip", path.Join(dir, "toget.zip"), "file"}},
47-
{"http://mysite.com/file.gz", FileItem{"http://mysite.com/file.gz", path.Join(dir, "file.gz"), "http"}},
48-
{"https://mysite.com/archive.tar", FileItem{"https://mysite.com/archive.tar", path.Join(dir, "archive.tar"), "https"}},
49-
{"blah://mysite.com/file.js", FileItem{"blah://mysite.com/file.js", path.Join(dir, "file.js"), "blah"}},
55+
{rootFile.String(), FileItem{rootFile, path.Join(dir, "afile.xyz")}, false},
56+
{relativeFile.String(), FileItem{relativeFile, path.Join(dir, "file.abc")}, false},
57+
{file.String(), FileItem{file, path.Join(dir, "toget.zip")}, false},
58+
{httpFile.String(), FileItem{httpFile, path.Join(dir, "file.gz")}, false},
59+
{httpsFile.String(), FileItem{httpsFile, path.Join(dir, "archive.tar")}, false},
60+
{blahFile.String(), FileItem{blahFile, path.Join(dir, "file.js")}, false},
61+
{"\x7f", FileItem{}, true},
5062
}
5163

5264
for _, test := range tests {
5365
t.Run(test.given, func(t *testing.T) {
5466
result := FileItem{}
5567
err := result.parseSimple(test.given)
56-
if err != nil {
57-
t.Errorf("Got error from parseSimple: %s", err)
68+
if err != nil && !test.isError {
69+
t.Error(err)
5870
}
59-
if result != test.expected {
60-
t.Errorf("Expected %+v from parseSimple(), got %+v", test.expected, result)
71+
if !cmp.Equal(result, test.expected) {
72+
t.Errorf("unexpected result from parseSimple() %s", cmp.Diff(result, test.expected))
6173
}
6274
})
6375
}
@@ -67,48 +79,27 @@ func TestParseComplex(t *testing.T) {
6779
tests := []struct {
6880
given map[interface{}]interface{}
6981
expected FileItem
82+
isError bool
7083
}{
71-
{map[interface{}]interface{}{"source": "/afile.zyx", "target": "/afile.zyx"}, FileItem{"/afile.zyx", path.Join(dir, "afile.zyx"), "file"}},
72-
{map[interface{}]interface{}{"source": "my/file.abc", "target": "file.xyz"}, FileItem{"my/file.abc", path.Join(dir, "file.xyz"), "file"}},
73-
{map[interface{}]interface{}{"source": "my/file.bac", "target": "myfolder/"}, FileItem{"my/file.bac", path.Join(dir, "myfolder", "file.bac"), "file"}},
74-
{map[interface{}]interface{}{"source": "file://some/file/toget.zip", "target": "file.toget.zip"}, FileItem{"file://some/file/toget.zip", path.Join(dir, "file.toget.zip"), "file"}},
75-
{map[interface{}]interface{}{"source": "http://mysite.com/file.gz", "target": "file.gz"}, FileItem{"http://mysite.com/file.gz", path.Join(dir, "file.gz"), "http"}},
76-
{map[interface{}]interface{}{"source": "https://mysite.com/archive.tar", "target": "archive.tgz"}, FileItem{"https://mysite.com/archive.tar", path.Join(dir, "archive.tgz"), "https"}},
77-
{map[interface{}]interface{}{"source": "blah://mysite.com/file.js", "target": "myfile.js"}, FileItem{"blah://mysite.com/file.js", path.Join(dir, "myfile.js"), "blah"}},
84+
{map[interface{}]interface{}{"source": rootFile.String(), "target": "/afile.zyx"}, FileItem{rootFile, path.Join(dir, "afile.zyx")}, false},
85+
{map[interface{}]interface{}{"source": relativeFile.String(), "target": "file.xyz"}, FileItem{relativeFile, path.Join(dir, "file.xyz")}, false},
86+
{map[interface{}]interface{}{"source": relativeFile.String(), "target": "myfolder/"}, FileItem{relativeFile, path.Join(dir, "myfolder", "file.abc")}, false},
87+
{map[interface{}]interface{}{"source": file.String(), "target": "file.toget.zip"}, FileItem{file, path.Join(dir, "file.toget.zip")}, false},
88+
{map[interface{}]interface{}{"source": httpFile.String(), "target": "file.gz"}, FileItem{httpFile, path.Join(dir, "file.gz")}, false},
89+
{map[interface{}]interface{}{"source": httpsFile.String(), "target": "archive.tgz"}, FileItem{httpsFile, path.Join(dir, "archive.tgz")}, false},
90+
{map[interface{}]interface{}{"source": blahFile.String(), "target": "myfile.js"}, FileItem{blahFile, path.Join(dir, "myfile.js")}, false},
91+
{map[interface{}]interface{}{"source": "\x7f", "target": "*shrug*"}, FileItem{}, true},
7892
}
7993

8094
for _, test := range tests {
8195
t.Run(test.given["source"].(string), func(t *testing.T) {
8296
result := FileItem{}
8397
err := result.parseComplex(test.given)
84-
if err != nil {
85-
t.Errorf("Got error from parseComplex: %s", err)
98+
if err != nil && !test.isError {
99+
t.Error(err)
86100
}
87-
if result != test.expected {
88-
t.Errorf("Expected %s from parseComplex(), got %s", test.expected, result)
89-
}
90-
})
91-
}
92-
}
93-
94-
func TestGetFileProtocol(t *testing.T) {
95-
tests := []struct {
96-
given string
97-
expected string
98-
}{
99-
{"/afile.zyx", "file"},
100-
{"my/file.abc", "file"},
101-
{"file://some/file/toget.zip", "file"},
102-
{"http://mysite.com/file.gz", "http"},
103-
{"https://mysite.com/archive.tar", "https"},
104-
{"blah://mysite.com/file.js", "blah"},
105-
}
106-
107-
for _, test := range tests {
108-
t.Run(test.given, func(t *testing.T) {
109-
result := getFileProtocol(test.given)
110-
if result != test.expected {
111-
t.Errorf("Expected %s from getFileProtocol(), got %s", test.expected, result)
101+
if !cmp.Equal(result, test.expected) {
102+
t.Errorf("unexpected result from parseComplex() %s", cmp.Diff(result, test.expected))
112103
}
113104
})
114105
}
+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package workers
2+
3+
import (
4+
"encoding/base64"
5+
"net/url"
6+
)
7+
8+
// CredentialReader is the interface that wraps a Credential Read method
9+
// The Reader should get credential information from somewhere (usually the runtime environment), and returns
10+
// a Credential struct as well as a boolean that indicates whether the reading of credential pieces (ie, Username, Password and/or Token) was successful.
11+
// Success is not strictly whether the pieces of information were found, but whether they existed in the environment to be read. A Credential struct with
12+
// empty strings for its fields is still a valid Credential
13+
type CredentialReader interface {
14+
Read(*url.URL) (Credential, bool)
15+
}
16+
17+
// CredentialWriter is the interface wrapping the Write method for Credentials
18+
// Write may write a credential to any form (string, another struct), and any medium (memory, file, etc).
19+
type CredentialWriter interface {
20+
Write(Credential) error
21+
}
22+
23+
// CredentialReaderWriter is an interface that combines both the CredentialReader and CredentialWriter
24+
type CredentialReaderWriter interface {
25+
CredentialReader
26+
CredentialWriter
27+
}
28+
29+
// Credential encapsulates a username/password pair
30+
type Credential struct {
31+
Username string
32+
Password string
33+
}
34+
35+
// Conjoin returns a string with the Credential content joined by a ':' (colon character)
36+
func (c *Credential) Conjoin() string {
37+
return c.Username + ":" + c.Password
38+
}
39+
40+
// Base64 returns a string with the provided Credential content base64 encoded after being joined by ':' (colon character)
41+
func (c *Credential) Base64() string {
42+
value := c.Conjoin()
43+
if value == ":" {
44+
return ""
45+
}
46+
return base64.StdEncoding.EncodeToString([]byte(value))
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package workers_test
2+
3+
import (
4+
"bridgr/internal/app/bridgr/workers"
5+
"testing"
6+
)
7+
8+
func TestConjoined(t *testing.T) {
9+
tests := []struct {
10+
name string
11+
cred workers.Credential
12+
expect string
13+
}{
14+
{"user and password", workers.Credential{Username: "me", Password: "myself"}, "me:myself"},
15+
{"empty credential", workers.Credential{}, ":"},
16+
}
17+
18+
for _, test := range tests {
19+
t.Run(test.name, func(t *testing.T) {
20+
result := test.cred.Conjoin()
21+
if result != test.expect {
22+
t.Errorf("Expected %s from Conjoin() but got %s", test.expect, result)
23+
}
24+
})
25+
}
26+
}
27+
28+
func TestBase64(t *testing.T) {
29+
tests := []struct {
30+
name string
31+
cred workers.Credential
32+
expect string
33+
}{
34+
{"user and password", workers.Credential{Username: "me", Password: "myself"}, "bWU6bXlzZWxm"},
35+
{"empty creds", workers.Credential{}, ""},
36+
{"only username", workers.Credential{Username: "michael"}, "bWljaGFlbDo="},
37+
{"only password", workers.Credential{Password: "bluth"}, "OmJsdXRo"},
38+
}
39+
40+
for _, test := range tests {
41+
t.Run(test.name, func(t *testing.T) {
42+
result := test.cred.Base64()
43+
if result != test.expect {
44+
t.Errorf("Expected %s but got %s", test.expect, result)
45+
}
46+
})
47+
}
48+
}

internal/app/bridgr/workers/docker.go

+21
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"bridgr/internal/app/bridgr"
55
"bridgr/internal/app/bridgr/config"
66
"context"
7+
"encoding/base64"
8+
"encoding/json"
79
"io"
810
"io/ioutil"
911
"os"
@@ -22,6 +24,11 @@ type Docker struct {
2224
Cli client.ImageAPIClient
2325
}
2426

27+
type dockerCredential struct {
28+
types.AuthConfig
29+
workerCredentialReader
30+
}
31+
2532
// NewDocker creates a Docker worker from the configuration object
2633
func NewDocker(conf *config.BridgrConf) Worker {
2734
_ = os.MkdirAll(conf.Docker.BaseDir(), os.ModePerm)
@@ -116,3 +123,17 @@ func (d *Docker) tagForRemote(local reference.Named) string {
116123
_ = d.Cli.ImageTag(context.Background(), local.Name(), remoteTag)
117124
return remoteTag
118125
}
126+
127+
func (credWriter *dockerCredential) Write(c Credential) error {
128+
credWriter.Username = c.Username
129+
credWriter.Password = c.Password
130+
return nil
131+
}
132+
133+
func (credWriter *dockerCredential) String() string {
134+
if credWriter.Username == "" && credWriter.Password == "" {
135+
return ""
136+
}
137+
jsonAuth, _ := json.Marshal(credWriter)
138+
return base64.URLEncoding.EncodeToString(jsonAuth)
139+
}

0 commit comments

Comments
 (0)