diff --git a/docker/label.go b/docker/label.go index a5888cd0..b7213ba9 100644 --- a/docker/label.go +++ b/docker/label.go @@ -1,9 +1,12 @@ package docker import ( + "fmt" "strings" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/terraform" ) func labelToPair(label map[string]interface{}) (string, string) { @@ -56,6 +59,22 @@ var labelSchema = &schema.Resource{ }, } +//unfortunately, _one_ of the several place that the old label schema was +//used specified that the keys had to be strings, while the others allowed +//any type of key and coerced them into strings. +func upgradeLabelMapFromV0ToV1(labelMap map[string]interface{}) []map[string]string { + var migratedState []map[string]string + + for l, v := range labelMap { + migratedState = append(migratedState, map[string]string{ + "label": l, + "value": fmt.Sprintf("%v", v), + }) + } + + return migratedState +} + //gatherImmediateSubkeys given an incomplete attribute identifier, find all //the strings (if any) that appear after this one in the various dot-separated //identifiers. @@ -87,3 +106,22 @@ func getLabelMapForPartialKey(attrs map[string]string, partialKey string) map[st return labelMap } + +func testCheckLabelMap(name string, partialKey string, expectedLabels map[string]string) resource.TestCheckFunc { + return func(s *terraform.State) error { + attrs := s.RootModule().Resources[name].Primary.Attributes + labelMap := getLabelMapForPartialKey(attrs, partialKey) + + if len(labelMap) != len(expectedLabels) { + return fmt.Errorf("expected %v labels, found %v", len(expectedLabels), len(labelMap)) + } + + for l, v := range expectedLabels { + if labelMap[l] != v { + return fmt.Errorf("expected value %v for label %v, got %v", v, l, labelMap[v]) + } + } + + return nil + } +} diff --git a/docker/resource_docker_network.go b/docker/resource_docker_network.go index aaf6a99f..9d4ebba3 100644 --- a/docker/resource_docker_network.go +++ b/docker/resource_docker_network.go @@ -15,6 +15,130 @@ func resourceDockerNetwork() *schema.Resource { Read: resourceDockerNetworkRead, Delete: resourceDockerNetworkDelete, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + + "labels": { + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: labelSchema, + }, + + "check_duplicate": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + + "driver": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Computed: true, + }, + + "options": { + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + Computed: true, + }, + + "internal": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + ForceNew: true, + }, + + "attachable": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + + "ingress": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + + "ipv6": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + + "ipam_driver": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "ipam_config": { + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "subnet": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "ip_range": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "gateway": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + }, + + "aux_address": { + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + }, + }, + }, + Set: resourceDockerIpamConfigHash, + }, + + "scope": { + Type: schema.TypeString, + Computed: true, + }, + }, + SchemaVersion: 1, + StateUpgraders: []schema.StateUpgrader{ + { + Version: 0, + Type: resourceDockerNetworkV0().CoreConfigSchema().ImpliedType(), + Upgrade: func(rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) { + labelMap := rawState["labels"].(map[string]interface{}) + rawState["labels"] = upgradeLabelMapFromV0ToV1(labelMap) + + return rawState, nil + }, + }, + }, + } +} + +func resourceDockerNetworkV0() *schema.Resource { + return &schema.Resource{ + //This is only used for state migration, so the CRUD + //callbacks are no longer relevant Schema: map[string]*schema.Schema{ "name": { Type: schema.TypeString, diff --git a/docker/resource_docker_network_funcs.go b/docker/resource_docker_network_funcs.go index dac6acb2..48ddbbd5 100644 --- a/docker/resource_docker_network_funcs.go +++ b/docker/resource_docker_network_funcs.go @@ -20,7 +20,7 @@ func resourceDockerNetworkCreate(d *schema.ResourceData, meta interface{}) error createOpts := types.NetworkCreate{} if v, ok := d.GetOk("labels"); ok { - createOpts.Labels = mapTypeMapValsToString(v.(map[string]interface{})) + createOpts.Labels = labelSetToMap(v.(*schema.Set)) } if v, ok := d.GetOk("check_duplicate"); ok { createOpts.CheckDuplicate = v.(bool) diff --git a/docker/resource_docker_network_test.go b/docker/resource_docker_network_test.go index 058aacf3..354a0b80 100644 --- a/docker/resource_docker_network_test.go +++ b/docker/resource_docker_network_test.go @@ -5,6 +5,7 @@ import ( "testing" "context" + "github.com/docker/docker/api/types" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/terraform" @@ -260,8 +261,12 @@ func TestAccDockerNetwork_labels(t *testing.T) { Config: testAccDockerNetworkLabelsConfig, Check: resource.ComposeTestCheckFunc( testAccNetwork("docker_network.foo", &n), - testAccNetworkLabel(&n, "com.docker.compose.network", "foo"), - testAccNetworkLabel(&n, "com.docker.compose.project", "test"), + testCheckLabelMap("docker_network.foo", "labels", + map[string]string{ + "com.docker.compose.network": "foo", + "com.docker.compose.project": "test", + }, + ), ), }, }, @@ -280,9 +285,13 @@ func testAccNetworkLabel(network *types.NetworkResource, name string, value stri const testAccDockerNetworkLabelsConfig = ` resource "docker_network" "foo" { name = "test_foo" - labels = { - "com.docker.compose.network" = "foo" - "com.docker.compose.project" = "test" + labels { + label = "com.docker.compose.network" + value = "foo" + } + labels { + label = "com.docker.compose.project" + value = "test" } } ` diff --git a/docker/resource_docker_secret.go b/docker/resource_docker_secret.go index 14e4960a..35c8f6bb 100644 --- a/docker/resource_docker_secret.go +++ b/docker/resource_docker_secret.go @@ -40,6 +40,49 @@ func resourceDockerSecret() *schema.Resource { Elem: labelSchema, }, }, + SchemaVersion: 1, + StateUpgraders: []schema.StateUpgrader{ + { + Version: 0, + Type: resourceDockerSecretV0().CoreConfigSchema().ImpliedType(), + Upgrade: func(rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) { + labelMap := rawState["labels"].(map[string]interface{}) + rawState["labels"] = upgradeLabelMapFromV0ToV1(labelMap) + + return rawState, nil + }, + }, + }, + } +} + +func resourceDockerSecretV0() *schema.Resource { + return &schema.Resource{ + //This is only used for state migration, so the CRUD + //callbacks are no longer relevant + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Description: "User-defined name of the secret", + Required: true, + ForceNew: true, + }, + + "data": { + Type: schema.TypeString, + Description: "User-defined name of the secret", + Required: true, + Sensitive: true, + ForceNew: true, + ValidateFunc: validateStringIsBase64Encoded(), + }, + + "labels": { + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + }, + }, } } diff --git a/docker/resource_docker_secret_test.go b/docker/resource_docker_secret_test.go index 810c6ba9..f6a089c7 100644 --- a/docker/resource_docker_secret_test.go +++ b/docker/resource_docker_secret_test.go @@ -95,18 +95,10 @@ func TestAccDockerSecret_labels(t *testing.T) { } } `, - Check: resource.ComposeTestCheckFunc( - func(s *terraform.State) error { - attrs := s.RootModule().Resources["docker_secret.foo"].Primary.Attributes - labelMap := getLabelMapForPartialKey(attrs, "labels") - - if len(labelMap) != 2 || - labelMap["test1"] != "foo" || - labelMap["test2"] != "bar" { - return fmt.Errorf("label map had unexpected structure: %v", labelMap) - } - - return nil + Check: testCheckLabelMap("docker_secret.foo", "labels", + map[string]string{ + "test1": "foo", + "test2": "bar", }, ), },