Skip to content

Commit b2e6407

Browse files
authored
Merge pull request #234 from glours/add-ssh-build
🥳
2 parents 98c14dd + 814bb73 commit b2e6407

File tree

6 files changed

+178
-20
lines changed

6 files changed

+178
-20
lines changed

loader/full-example.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ services:
77
dockerfile: Dockerfile
88
args:
99
foo: bar
10+
ssh:
11+
- default
1012
target: foo
1113
network: foo
1214
cache_from:

loader/full-struct_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ func services(workingDir, homeDir string) []types.ServiceConfig {
5353
Context: "./dir",
5454
Dockerfile: "Dockerfile",
5555
Args: map[string]*string{"foo": strPtr("bar")},
56+
SSH: []types.SSHKey{{ID: "default", Path: ""}},
5657
Target: "foo",
5758
Network: "foo",
5859
CacheFrom: []string{"foo", "bar"},
@@ -569,6 +570,8 @@ services:
569570
dockerfile: Dockerfile
570571
args:
571572
foo: bar
573+
ssh:
574+
- default
572575
labels:
573576
FOO: BAR
574577
cache_from:
@@ -1082,6 +1085,9 @@ func fullExampleJSON(workingDir, homeDir string) string {
10821085
"args": {
10831086
"foo": "bar"
10841087
},
1088+
"ssh": [
1089+
"default"
1090+
],
10851091
"labels": {
10861092
"FOO": "BAR"
10871093
},

loader/loader.go

Lines changed: 55 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,7 @@ func createTransformHook(additionalTransformers ...Transformer) mapstructure.Dec
390390
reflect.TypeOf(types.DependsOnConfig{}): transformDependsOnConfig,
391391
reflect.TypeOf(types.ExtendsConfig{}): transformExtendsConfig,
392392
reflect.TypeOf(types.DeviceRequest{}): transformServiceDeviceRequest,
393+
reflect.TypeOf(types.SSHConfig{}): transformSSHConfig,
393394
}
394395

395396
for _, transformer := range additionalTransformers {
@@ -978,6 +979,35 @@ var transformServiceNetworkMap TransformerFunc = func(value interface{}) (interf
978979
return value, nil
979980
}
980981

982+
var transformSSHConfig TransformerFunc = func(data interface{}) (interface{}, error) {
983+
switch value := data.(type) {
984+
case map[string]interface{}:
985+
var result []types.SSHKey
986+
for key, val := range value {
987+
if val == nil {
988+
val = ""
989+
}
990+
result = append(result, types.SSHKey{ID: key, Path: val.(string)})
991+
}
992+
return result, nil
993+
case []interface{}:
994+
var result []types.SSHKey
995+
for _, v := range value {
996+
key, val := transformValueToMapEntry(v.(string), "=", false)
997+
result = append(result, types.SSHKey{ID: key, Path: val.(string)})
998+
}
999+
return result, nil
1000+
case string:
1001+
if value == "" {
1002+
value = "default"
1003+
}
1004+
key, val := transformValueToMapEntry(value, "=", false)
1005+
result := []types.SSHKey{{ID: key, Path: val.(string)}}
1006+
return result, nil
1007+
}
1008+
return nil, errors.Errorf("expected a sting, map or a list, got %T: %#v", data, data)
1009+
}
1010+
9811011
var transformStringOrNumberList TransformerFunc = func(value interface{}) (interface{}, error) {
9821012
list := value.([]interface{})
9831013
result := make([]string, len(list))
@@ -1000,47 +1030,52 @@ var transformStringList TransformerFunc = func(data interface{}) (interface{}, e
10001030

10011031
func transformMappingOrListFunc(sep string, allowNil bool) TransformerFunc {
10021032
return func(data interface{}) (interface{}, error) {
1003-
return transformMappingOrList(data, sep, allowNil), nil
1033+
return transformMappingOrList(data, sep, allowNil)
10041034
}
10051035
}
10061036

10071037
func transformListOrMappingFunc(sep string, allowNil bool) TransformerFunc {
10081038
return func(data interface{}) (interface{}, error) {
1009-
return transformListOrMapping(data, sep, allowNil), nil
1039+
return transformListOrMapping(data, sep, allowNil)
10101040
}
10111041
}
10121042

1013-
func transformListOrMapping(listOrMapping interface{}, sep string, allowNil bool) interface{} {
1043+
func transformListOrMapping(listOrMapping interface{}, sep string, allowNil bool) (interface{}, error) {
10141044
switch value := listOrMapping.(type) {
10151045
case map[string]interface{}:
1016-
return toStringList(value, sep, allowNil)
1046+
return toStringList(value, sep, allowNil), nil
10171047
case []interface{}:
1018-
return listOrMapping
1048+
return listOrMapping, nil
10191049
}
1020-
panic(errors.Errorf("expected a map or a list, got %T: %#v", listOrMapping, listOrMapping))
1050+
return nil, errors.Errorf("expected a map or a list, got %T: %#v", listOrMapping, listOrMapping)
10211051
}
10221052

1023-
func transformMappingOrList(mappingOrList interface{}, sep string, allowNil bool) interface{} {
1053+
func transformMappingOrList(mappingOrList interface{}, sep string, allowNil bool) (interface{}, error) {
10241054
switch value := mappingOrList.(type) {
10251055
case map[string]interface{}:
1026-
return toMapStringString(value, allowNil)
1056+
return toMapStringString(value, allowNil), nil
10271057
case []interface{}:
10281058
result := make(map[string]interface{})
10291059
for _, value := range value {
1030-
parts := strings.SplitN(value.(string), sep, 2)
1031-
key := parts[0]
1032-
switch {
1033-
case len(parts) == 1 && allowNil:
1034-
result[key] = nil
1035-
case len(parts) == 1 && !allowNil:
1036-
result[key] = ""
1037-
default:
1038-
result[key] = parts[1]
1039-
}
1060+
key, val := transformValueToMapEntry(value.(string), sep, allowNil)
1061+
result[key] = val
10401062
}
1041-
return result
1063+
return result, nil
1064+
}
1065+
return nil, errors.Errorf("expected a map or a list, got %T: %#v", mappingOrList, mappingOrList)
1066+
}
1067+
1068+
func transformValueToMapEntry(value string, separator string, allowNil bool) (string, interface{}) {
1069+
parts := strings.SplitN(value, separator, 2)
1070+
key := parts[0]
1071+
switch {
1072+
case len(parts) == 1 && allowNil:
1073+
return key, nil
1074+
case len(parts) == 1 && !allowNil:
1075+
return key, ""
1076+
default:
1077+
return key, parts[1]
10421078
}
1043-
panic(errors.Errorf("expected a map or a list, got %T: %#v", mappingOrList, mappingOrList))
10441079
}
10451080

10461081
var transformShellCommand TransformerFunc = func(value interface{}) (interface{}, error) {

loader/loader_test.go

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1867,3 +1867,83 @@ func TestLoadServiceWithVolumes(t *testing.T) {
18671867
assert.Equal(t, len(s.Secrets), 1)
18681868
assert.Equal(t, "/path 4", s.Secrets[0].Target)
18691869
}
1870+
1871+
func TestLoadNoSSHInBuildConfig(t *testing.T) {
1872+
actual, err := loadYAML(`
1873+
services:
1874+
test:
1875+
build:
1876+
context: .
1877+
`)
1878+
assert.NilError(t, err)
1879+
svc, err := actual.GetService("test")
1880+
assert.NilError(t, err)
1881+
assert.Check(t, nil == svc.Build.SSH)
1882+
}
1883+
1884+
func TestLoadSSHWithoutValueInBuildConfig(t *testing.T) {
1885+
_, err := loadYAML(`
1886+
services:
1887+
test:
1888+
build:
1889+
context: .
1890+
ssh:
1891+
`)
1892+
assert.ErrorContains(t, err, "services.test.build.ssh must be a mapping")
1893+
}
1894+
1895+
func TestLoadSSHWithDefaultValueInBuildConfig(t *testing.T) {
1896+
actual, err := loadYAML(`
1897+
services:
1898+
test:
1899+
build:
1900+
context: .
1901+
ssh: [default]
1902+
`)
1903+
assert.NilError(t, err)
1904+
svc, err := actual.GetService("test")
1905+
assert.NilError(t, err)
1906+
sshValue, err := svc.Build.SSH.Get("default")
1907+
assert.NilError(t, err)
1908+
assert.Equal(t, "", sshValue)
1909+
}
1910+
1911+
func TestLoadSSHWithKeyValueInBuildConfig(t *testing.T) {
1912+
actual, err := loadYAML(`
1913+
services:
1914+
test:
1915+
build:
1916+
context: .
1917+
ssh:
1918+
key1: value1
1919+
`)
1920+
assert.NilError(t, err)
1921+
svc, err := actual.GetService("test")
1922+
assert.NilError(t, err)
1923+
sshValue, err := svc.Build.SSH.Get("key1")
1924+
assert.NilError(t, err)
1925+
assert.Equal(t, "value1", sshValue)
1926+
}
1927+
1928+
func TestLoadSSHWithKeysValuesInBuildConfig(t *testing.T) {
1929+
actual, err := loadYAML(`
1930+
services:
1931+
test:
1932+
build:
1933+
context: .
1934+
ssh:
1935+
- key1=value1
1936+
- key2=value2
1937+
`)
1938+
assert.NilError(t, err)
1939+
svc, err := actual.GetService("test")
1940+
assert.NilError(t, err)
1941+
1942+
sshValue, err := svc.Build.SSH.Get("key1")
1943+
assert.NilError(t, err)
1944+
assert.Equal(t, "value1", sshValue)
1945+
1946+
sshValue, err = svc.Build.SSH.Get("key2")
1947+
assert.NilError(t, err)
1948+
assert.Equal(t, "value2", sshValue)
1949+
}

schema/compose-spec.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
"context": {"type": "string"},
9292
"dockerfile": {"type": "string"},
9393
"args": {"$ref": "#/definitions/list_or_dict"},
94+
"ssh": {"$ref": "#/definitions/list_or_dict"},
9495
"labels": {"$ref": "#/definitions/list_or_dict"},
9596
"cache_from": {"type": "array", "items": {"type": "string"}},
9697
"cache_to": {"type": "array", "items": {"type": "string"}},

types/types.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ type BuildConfig struct {
294294
Context string `yaml:",omitempty" json:"context,omitempty"`
295295
Dockerfile string `yaml:",omitempty" json:"dockerfile,omitempty"`
296296
Args MappingWithEquals `yaml:",omitempty" json:"args,omitempty"`
297+
SSH SSHConfig `yaml:"ssh,omitempty" json:"ssh,omitempty"`
297298
Labels Labels `yaml:",omitempty" json:"labels,omitempty"`
298299
CacheFrom StringList `mapstructure:"cache_from" yaml:"cache_from,omitempty" json:"cache_from,omitempty"`
299300
CacheTo StringList `mapstructure:"cache_to" yaml:"cache_to,omitempty" json:"cache_to,omitempty"`
@@ -426,6 +427,39 @@ func (l Labels) Add(key, value string) Labels {
426427
return l
427428
}
428429

430+
type SSHKey struct {
431+
ID string
432+
Path string
433+
}
434+
435+
// SSHConfig is a mapping type for SSH build config
436+
type SSHConfig []SSHKey
437+
438+
func (s SSHConfig) Get(id string) (string, error) {
439+
for _, sshKey := range s {
440+
if sshKey.ID == id {
441+
return sshKey.Path, nil
442+
}
443+
}
444+
return "", fmt.Errorf("ID %s not found in SSH keys", id)
445+
}
446+
447+
// MarshalYAML makes SSHKey implement yaml.Marshaller
448+
func (s SSHKey) MarshalYAML() (interface{}, error) {
449+
if s.Path == "" {
450+
return s.ID, nil
451+
}
452+
return fmt.Sprintf("%s: %s", s.ID, s.Path), nil
453+
}
454+
455+
// MarshalJSON makes SSHKey implement json.Marshaller
456+
func (s SSHKey) MarshalJSON() ([]byte, error) {
457+
if s.Path == "" {
458+
return []byte(fmt.Sprintf(`"%s"`, s.ID)), nil
459+
}
460+
return []byte(fmt.Sprintf(`"%s": %s`, s.ID, s.Path)), nil
461+
}
462+
429463
// MappingWithColon is a mapping type that can be converted from a list of
430464
// 'key: value' strings
431465
type MappingWithColon map[string]string

0 commit comments

Comments
 (0)