diff --git a/drivers/google/compute_util.go b/drivers/google/compute_util.go index 554521b6c3d450c5da6e3d5f5d1d2d5f4874f82f..d3bb691828ae840e01aed5ac747b2e0e7de839a9 100644 --- a/drivers/google/compute_util.go +++ b/drivers/google/compute_util.go @@ -253,6 +253,11 @@ func (c *ComputeUtil) createInstance(d *Driver) error { net = c.globalURL + "/networks/" + d.Network } + metadata, err := prepareMetadata(d) + if err != nil { + return err + } + instance := &raw.Instance{ Name: c.instanceName, Description: "docker host vm", @@ -284,7 +289,7 @@ func (c *ComputeUtil) createInstance(d *Driver) error { Preemptible: c.preemptible, }, Labels: parseLabels(d), - Metadata: prepareMetadata(d.Metadata), + Metadata: metadata, } if strings.Contains(c.subnetwork, "/subnetworks/") { @@ -403,21 +408,53 @@ func (c *ComputeUtil) uploadSSHKey(instance *raw.Instance, sshKeyPath string) er } // prepareMetadata prepares instance metadata entries from provided configuration -func prepareMetadata(values metadataMap) *raw.Metadata { +func prepareMetadata(d *Driver) (*raw.Metadata, error) { metadata := &raw.Metadata{ Items: make([]*raw.MetadataItems, 0), } - for key := range values { - value := values[key] - item := &raw.MetadataItems{ - Key: key, - Value: &value, + for key, value := range d.Metadata { + appendMetadata(metadata, key, value) + } + + for key, filePath := range d.MetadataFromFile { + value, err := readMetadataFile(filePath) + if err != nil { + return nil, err } - metadata.Items = append(metadata.Items, item) + + appendMetadata(metadata, key, value) + } + + return metadata, nil +} + +// readMetadataFile reads the data from the provided file +func readMetadataFile(filePath string) (string, error) { + if filePath == "" { + return "", nil + } + + data, err := ioutil.ReadFile(filePath) + if err != nil { + return "", err + } + + return string(data), nil +} + +// appendMetadata creates a new item from provided key and value and appends it to the provided metadata object +func appendMetadata(metadata *raw.Metadata, key string, value string) { + if value == "" { + return + } + + item := &raw.MetadataItems{ + Key: key, + Value: &value, } - return metadata + metadata.Items = append(metadata.Items, item) } // parseTags computes the tags for the instance. diff --git a/drivers/google/compute_util_test.go b/drivers/google/compute_util_test.go index 65e51f77abc900bd81834d4c381596fc84d34675..99360df39f9d6f8471e9e1d343cabf26b8b737bf 100644 --- a/drivers/google/compute_util_test.go +++ b/drivers/google/compute_util_test.go @@ -2,6 +2,9 @@ package google import ( "errors" + "fmt" + "io/ioutil" + "os" "testing" "time" @@ -230,60 +233,177 @@ func TestWaitForOpBackOff(t *testing.T) { } } -func TestPrepareMetadata_NoConfiguration(t *testing.T) { - m := prepareMetadata(nil) - if !assert.NotNil(t, m) { - t.FailNow() - } - assert.Empty(t, m.Items) -} +func TestPrepareMetadata(t *testing.T) { + const ( + metadataKey1 = "key_1" + metadataValue1 = "value_1" + metadataKey2 = "key_2" + metadataValue2 = "value_2" + metadataKey3 = "key_3" + fileContent3 = "file-content-3" + metadataKey4 = "key_4" + fileContent4 = "file-content-4" + ) -func TestPrepareMetadata_WithConfiguration(t *testing.T) { - metadataKey1 := "key_1" - metadataValue1 := "value_1" - metadataKey2 := "key_2" - metadataValue2 := "value_2" metadata := metadataMap{ metadataKey1: metadataValue1, metadataKey2: metadataValue2, } + metadataFiles := [][]string{ + {metadataKey3, fileContent3}, + {metadataKey4, fileContent4}, + } - assertMetadata := func(t *testing.T, m *raw.Metadata, key string, value string) { + noMetadataFile := func(_ *testing.T) (metadataMap, func()) { return metadataMap{}, func() {} } + failingMetadataFile := func(_ *testing.T) (metadataMap, func()) { + return metadataMap{"non-existing": "non-existing"}, func() {} + } + missingMetadataFilePath := func(_ *testing.T) (metadataMap, func()) { + return metadataMap{"non-existing": ""}, func() {} + } + emptyMetadata := func(t *testing.T, m *raw.Metadata) { if !assert.NotNil(t, m) { t.FailNow() } - found := false - for _, item := range m.Items { - if item.Key != key { - continue - } - - found = true - - if !assert.NotNil(t, item.Value) { - t.FailNow() - } - assert.Equal(t, value, *item.Value) - } - assert.True(t, found, "not found the metadata item %q=%q", key, value) + assert.Empty(t, m.Items) } tests := map[string]struct { metadata metadataMap + metadataFiles func(t *testing.T) (metadataMap, func()) + expectedError bool assertMetadata func(t *testing.T, m *raw.Metadata) }{ - "metadata provided": { - metadata: metadata, + "error on metadata file reading": { + metadataFiles: failingMetadataFile, + expectedError: true, + assertMetadata: emptyMetadata, + }, + "missing metadata file path": { + metadataFiles: missingMetadataFilePath, + expectedError: false, + assertMetadata: emptyMetadata, + }, + "missing metadata value": { + metadata: metadataMap{"key": ""}, + metadataFiles: noMetadataFile, + expectedError: false, + assertMetadata: emptyMetadata, + }, + "no metadata configuration": { + metadata: nil, + metadataFiles: noMetadataFile, + expectedError: false, + assertMetadata: emptyMetadata, + }, + "only metadata passed": { + metadata: metadata, + metadataFiles: noMetadataFile, + expectedError: false, assertMetadata: func(t *testing.T, m *raw.Metadata) { assertMetadata(t, m, metadataKey1, metadataValue1) assertMetadata(t, m, metadataKey2, metadataValue2) }, }, + "only metadata file passed": { + metadataFiles: prepareMetadataFiles(metadataFiles), + expectedError: false, + assertMetadata: func(t *testing.T, m *raw.Metadata) { + assertMetadata(t, m, metadataKey3, fileContent3) + assertMetadata(t, m, metadataKey4, fileContent4) + }, + }, + "both metadata and metadata file passed": { + metadata: metadata, + metadataFiles: prepareMetadataFiles(metadataFiles), + expectedError: false, + assertMetadata: func(t *testing.T, m *raw.Metadata) { + assertMetadata(t, m, metadataKey1, metadataValue1) + assertMetadata(t, m, metadataKey2, metadataValue2) + assertMetadata(t, m, metadataKey3, fileContent3) + assertMetadata(t, m, metadataKey4, fileContent4) + }, + }, } for tn, tt := range tests { t.Run(tn, func(t *testing.T) { - tt.assertMetadata(t, prepareMetadata(tt.metadata)) + metadataFiles, cleanup := tt.metadataFiles(t) + defer cleanup() + + metadata, err := prepareMetadata(&Driver{ + Metadata: tt.metadata, + MetadataFromFile: metadataFiles, + }) + + if tt.expectedError { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + tt.assertMetadata(t, metadata) }) } } + +func assertMetadata(t *testing.T, m *raw.Metadata, key string, value string) { + if !assert.NotNil(t, m) { + t.FailNow() + } + found := false + for _, item := range m.Items { + if item.Key != key { + continue + } + + found = true + + if !assert.NotNil(t, item.Value) { + t.FailNow() + } + assert.Equal(t, value, *item.Value) + } + assert.True(t, found, "not found the metadata item %q=%q", key, value) +} + +func prepareMetadataFiles(configuration [][]string) func(t *testing.T) (metadataMap, func()) { + return func(t *testing.T) (metadataMap, func()) { + metadata := make(metadataMap, 0) + files := make([]string, 0) + + for _, entry := range configuration { + file := prepareMetadataFile(t, entry[0], entry[1]) + + files = append(files, file.Name()) + metadata[entry[0]] = file.Name() + } + + cleanup := func() { + for _, file := range files { + err := os.RemoveAll(file) + if !assert.NoError(t, err) { + t.FailNow() + } + } + } + + return metadata, cleanup + } +} + +func prepareMetadataFile(t *testing.T, key string, content string) *os.File { + file, err := ioutil.TempFile("", "metadata-file-"+key) + if !assert.NoError(t, err) { + t.FailNow() + } + + defer file.Close() + + _, err = fmt.Fprint(file, content) + if !assert.NoError(t, err) { + t.FailNow() + } + + return file +} diff --git a/drivers/google/google.go b/drivers/google/google.go index 8237622ac60a2b33e33087cfd1d3d3db71c8e658..28a628ba65ec4b9f59ef59861c703dd9336e19ac 100644 --- a/drivers/google/google.go +++ b/drivers/google/google.go @@ -60,6 +60,7 @@ type Driver struct { OpenPorts []string Labels []string Metadata metadataMap + MetadataFromFile metadataMap OperationBackoffFactory *backoffFactory } @@ -227,6 +228,10 @@ func (d *Driver) GetCreateFlags() []mcnflag.Flag { Name: "google-metadata", Usage: "Custom metadata value passed in key=value form. Use multiple times for multiple settings", }, + mcnflag.StringSliceFlag{ + Name: "google-metadata-from-file", + Usage: "Path to a file containing the metadata value inform of key=path/to/file. Use multiple times for multiple settings", + }, } } @@ -297,6 +302,7 @@ func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error { d.OpenPorts = flags.StringSlice("google-open-port") d.Labels = flags.StringSlice("google-label") d.Metadata = metadataMapFromStringSlice(flags.StringSlice("google-metadata")) + d.MetadataFromFile = metadataMapFromStringSlice(flags.StringSlice("google-metadata-from-file")) } d.SSHUser = flags.String("google-username") d.SSHPort = 22