Repository: compose-spec/compose-go
Pull request #289 (2022-07-15 22:26:36)

Add missing LinkLocalIPs to ServiceNetworkConfig
Signed-off-by: Jeremiah Millay <jmillay@fastly.com>

This addresses https://github.com/compose-spec/compose-go/issues/288 and I believe is a prerequisite to fixing https://github.com/docker/compose/issues/9659 (happy to take care of that as well once this change is approved/merged).


Patch:
diff --git a/dotenv/fixtures/plain.env b/dotenv/fixtures/plain.env
index 0fa4c34..430914d 100644
--- a/dotenv/fixtures/plain.env
+++ b/dotenv/fixtures/plain.env
@@ -3,7 +3,6 @@ OPTION_B=2
 OPTION_C= 3
 OPTION_D =4
 OPTION_E = 5
-456 = ABC
 OPTION_F = 
 OPTION_G=
 OPTION_H = my string # Inline comment
diff --git a/dotenv/godotenv.go b/dotenv/godotenv.go
index 50886f2..e994235 100644
--- a/dotenv/godotenv.go
+++ b/dotenv/godotenv.go
@@ -93,30 +93,26 @@ func load(overload bool, filenames ...string) (err error) {
 	return
 }
 
-var startsWithDigitRegex = regexp.MustCompile(`^\s*\d.*`) // Keys starting with numbers are ignored
-
 // ReadWithLookup gets all env vars from the files and/or lookup function and return values as
 // a map rather than automatically writing values into env
-func ReadWithLookup(lookupFn LookupFn, filenames ...string) (map[string]string, error) {
+func ReadWithLookup(lookupFn LookupFn, filenames ...string) (envMap map[string]string, err error) {
 	filenames = filenamesOrDefault(filenames)
-	envMap := make(map[string]string)
+	envMap = make(map[string]string)
 
 	for _, filename := range filenames {
 		individualEnvMap, individualErr := readFile(filename, lookupFn)
 
 		if individualErr != nil {
-			return envMap, individualErr
+			err = individualErr
+			return // return early on a spazout
 		}
 
 		for key, value := range individualEnvMap {
-			if startsWithDigitRegex.MatchString(key) {
-				continue
-			}
 			envMap[key] = value
 		}
 	}
 
-	return envMap, nil
+	return
 }
 
 // Read all env (with same file loading semantics as Load) but return values as
@@ -187,7 +183,7 @@ func Marshal(envMap map[string]string) (string, error) {
 		if d, err := strconv.Atoi(v); err == nil {
 			lines = append(lines, fmt.Sprintf(`%s=%d`, k, d))
 		} else {
-			lines = append(lines, fmt.Sprintf(`%s="%s"`, k, doubleQuoteEscape(v))) // nolint // Cannot use %q here
+			lines = append(lines, fmt.Sprintf(`%s="%s"`, k, doubleQuoteEscape(v))) //nolint // Cannot use %q here
 		}
 	}
 	sort.Strings(lines)
diff --git a/types/types.go b/types/types.go
index 1b62505..fc5ac95 100644
--- a/types/types.go
+++ b/types/types.go
@@ -617,10 +617,11 @@ type PlacementPreferences struct {
 
 // ServiceNetworkConfig is the network configuration for a service
 type ServiceNetworkConfig struct {
-	Priority    int      `yaml:",omitempty" json:"priotirt,omitempty"`
-	Aliases     []string `yaml:",omitempty" json:"aliases,omitempty"`
-	Ipv4Address string   `mapstructure:"ipv4_address" yaml:"ipv4_address,omitempty" json:"ipv4_address,omitempty"`
-	Ipv6Address string   `mapstructure:"ipv6_address" yaml:"ipv6_address,omitempty" json:"ipv6_address,omitempty"`
+	Priority     int      `yaml:",omitempty" json:"priority,omitempty"`
+	Aliases      []string `yaml:",omitempty" json:"aliases,omitempty"`
+	Ipv4Address  string   `mapstructure:"ipv4_address" yaml:"ipv4_address,omitempty" json:"ipv4_address,omitempty"`
+	Ipv6Address  string   `mapstructure:"ipv6_address" yaml:"ipv6_address,omitempty" json:"ipv6_address,omitempty"`
+	LinkLocalIPs []string `mapstructure:"link_local_ips" yaml:"link_local_ips,omitempty" json:"link_local_ips,omitempty"`
 
 	Extensions map[string]interface{} `yaml:",inline" json:"-"`
 }


Test patch:
diff --git a/loader/loader_test.go b/loader/loader_test.go
index a4b5dd0..f51c0e8 100644
--- a/loader/loader_test.go
+++ b/loader/loader_test.go
@@ -1484,6 +1484,67 @@ networks:
 	assert.DeepEqual(t, config, expected, cmpopts.EquateEmpty())
 }
 
+func TestLoadNetworkLinkLocalIPs(t *testing.T) {
+	config, err := loadYAML(`
+services:
+  foo:
+    image: alpine
+    networks:
+      network1:
+        ipv4_address: 10.1.0.100
+        ipv6_address: 2001:db8:0:1::100
+        link_local_ips:
+          - fe80::1:95ff:fe20:100
+networks:
+  network1:
+    driver: bridge
+    enable_ipv6: true
+    name: network1
+    ipam:
+      config:
+        - subnet: 10.1.0.0/16
+        - subnet: 2001:db8:0:1::/64
+`)
+	assert.NilError(t, err)
+
+	workingDir, err := os.Getwd()
+	assert.NilError(t, err)
+	expected := &types.Project{
+		Name:       "",
+		WorkingDir: workingDir,
+		Services: types.Services{
+			{
+				Name:  "foo",
+				Image: "alpine",
+				Scale: 1,
+				Networks: map[string]*types.ServiceNetworkConfig{
+					"network1": {
+						Ipv4Address: "10.1.0.100",
+						Ipv6Address: "2001:db8:0:1::100",
+						LinkLocalIPs: []string{
+							"fe80::1:95ff:fe20:100",
+						},
+					},
+				},
+			},
+		},
+		Networks: map[string]types.NetworkConfig{
+			"network1": {
+				Name:       "network1",
+				Driver:     "bridge",
+				EnableIPv6: true,
+				Ipam: types.IPAMConfig{
+					Config: []*types.IPAMPool{
+						{Subnet: "10.1.0.0/16"},
+						{Subnet: "2001:db8:0:1::/64"},
+					},
+				},
+			},
+		},
+	}
+	assert.DeepEqual(t, config, expected, cmpopts.EquateEmpty())
+}
+
 func TestLoadInit(t *testing.T) {
 	booleanTrue := true
 	booleanFalse := false