From 6c0434e4882cbe7f28856fb0fb18a273846357b0 Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Tue, 22 Dec 2020 20:31:15 +0900 Subject: [PATCH 01/36] feat: support Docker plugin --- docker/provider.go | 1 + docker/resource_docker_plugin.go | 28 ++++++++ docker/resource_docker_plugin_funcs.go | 95 ++++++++++++++++++++++++++ website/docs/r/plugin.html.markdown | 36 ++++++++++ 4 files changed, 160 insertions(+) create mode 100644 docker/resource_docker_plugin.go create mode 100644 docker/resource_docker_plugin_funcs.go create mode 100644 website/docs/r/plugin.html.markdown diff --git a/docker/provider.go b/docker/provider.go index 2421f04a5..6bf9188b5 100644 --- a/docker/provider.go +++ b/docker/provider.go @@ -109,6 +109,7 @@ func Provider() terraform.ResourceProvider { "docker_config": resourceDockerConfig(), "docker_secret": resourceDockerSecret(), "docker_service": resourceDockerService(), + "docker_plugin": resourceDockerPlugin(), }, DataSourcesMap: map[string]*schema.Resource{ diff --git a/docker/resource_docker_plugin.go b/docker/resource_docker_plugin.go new file mode 100644 index 000000000..437e61f2e --- /dev/null +++ b/docker/resource_docker_plugin.go @@ -0,0 +1,28 @@ +package docker + +import ( + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func resourceDockerPlugin() *schema.Resource { + return &schema.Resource{ + Create: resourceDockerPluginCreate, + Read: resourceDockerPluginRead, + Update: resourceDockerPluginUpdate, + Delete: resourceDockerPluginDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "disabled": { + Type: schema.TypeBool, + Optional: true, + }, + }, + } +} diff --git a/docker/resource_docker_plugin_funcs.go b/docker/resource_docker_plugin_funcs.go new file mode 100644 index 000000000..6425c499f --- /dev/null +++ b/docker/resource_docker_plugin_funcs.go @@ -0,0 +1,95 @@ +package docker + +import ( + "context" + "fmt" + "io/ioutil" + + "github.com/docker/docker/api/types" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func resourceDockerPluginCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ProviderConfig).DockerClient + ctx := context.Background() + pluginName := d.Get("name").(string) + // TODO + body, err := client.PluginInstall(ctx, pluginName, types.PluginInstallOptions{ + RemoteRef: pluginName, + AcceptAllPermissions: true, + Disabled: d.Get("disabled").(bool), + }) + if err != nil { + return fmt.Errorf("install a Docker plugin "+pluginName+": %w", err) + } + _, _ = ioutil.ReadAll(body) + plugin, _, err := client.PluginInspectWithRaw(ctx, pluginName) + if err != nil { + return fmt.Errorf("inspect a Docker plugin "+pluginName+": %w", err) + } + setDockerPlugin(d, plugin) + return nil +} + +func setDockerPlugin(d *schema.ResourceData, plugin *types.Plugin) { + d.SetId(plugin.ID) + d.Set("name", plugin.Name) + d.Set("disabled", !plugin.Enabled) +} + +func resourceDockerPluginRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ProviderConfig).DockerClient + ctx := context.Background() + pluginID := d.Id() + plugin, _, err := client.PluginInspectWithRaw(ctx, pluginID) + if err != nil { + return fmt.Errorf("inspect a Docker plugin "+pluginID+": %w", err) + } + setDockerPlugin(d, plugin) + // TODO set values + return nil +} + +func resourceDockerPluginUpdate(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ProviderConfig).DockerClient + ctx := context.Background() + pluginID := d.Id() + // TODO + if d.HasChange("disabled") { + if d.Get("disabled").(bool) { + if err := client.PluginDisable(ctx, pluginID, types.PluginDisableOptions{}); err != nil { + return fmt.Errorf("disable the Docker plugin "+pluginID+": %w", err) + } + } else { + if err := client.PluginEnable(ctx, pluginID, types.PluginEnableOptions{}); err != nil { + return fmt.Errorf("enable the Docker plugin "+pluginID+": %w", err) + } + } + } + if err := client.PluginSet(ctx, pluginID, nil); err != nil { + return fmt.Errorf("modifiy settings for the Docker plugin "+pluginID+": %w", err) + } + // call the read function to update the resource's state. + // https://learn.hashicorp.com/tutorials/terraform/provider-update?in=terraform/providers#implement-update + return resourceDockerPluginRead(d, meta) +} + +func resourceDockerPluginDelete(d *schema.ResourceData, meta interface{}) error { + client := meta.(*ProviderConfig).DockerClient + ctx := context.Background() + pluginID := d.Id() + plugin, _, err := client.PluginInspectWithRaw(ctx, pluginID) + if err != nil { + return fmt.Errorf("inspect a Docker plugin "+pluginID+": %w", err) + } + if plugin.Enabled { + // disable the plugin before removing + if err := client.PluginDisable(ctx, pluginID, types.PluginDisableOptions{}); err != nil { + return fmt.Errorf("disable the Docker plugin "+pluginID+": %w", err) + } + } + if err := client.PluginRemove(ctx, pluginID, types.PluginRemoveOptions{}); err != nil { + return fmt.Errorf("remove the Docker plugin "+pluginID+": %w", err) + } + return nil +} diff --git a/website/docs/r/plugin.html.markdown b/website/docs/r/plugin.html.markdown new file mode 100644 index 000000000..5c07bb27d --- /dev/null +++ b/website/docs/r/plugin.html.markdown @@ -0,0 +1,36 @@ +--- +layout: "docker" +page_title: "Docker: docker_plugin" +sidebar_current: "docs-docker-resource-plugin" +description: |- + Manages the lifecycle of a Docker plugin. +--- + +# docker\_plugin + +Manages the lifecycle of a Docker plugin. + +## Example Usage + +```hcl +resource "docker_plugin" "sample-volume-plugin" { + name = "tiborvass/sample-volume-plugin:latest" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (Required, string) The name of the Docker plugin. +* `disabled` - (Optional, boolean) If true, the plugin is disabled. + +## Attributes Reference + +## Import + +Docker plugins can be imported using the long id, e.g. for a plugin `tiborvass/sample-volume-plugin:latest`: + +``` +$ terraform import docker_plugin.sample-volume-plugin $(docker plugin inspect -f "{{.ID}}" tiborvass/sample-volume-plugin:latest) +``` From 2238594c5f64109b95c8dff3ffa49d124d346c21 Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Tue, 29 Dec 2020 11:34:56 +0900 Subject: [PATCH 02/36] feat: add destroy_option --- docker/resource_docker_plugin.go | 13 +++++++++++++ docker/resource_docker_plugin_funcs.go | 25 +++++++++++++------------ 2 files changed, 26 insertions(+), 12 deletions(-) diff --git a/docker/resource_docker_plugin.go b/docker/resource_docker_plugin.go index 437e61f2e..865cae454 100644 --- a/docker/resource_docker_plugin.go +++ b/docker/resource_docker_plugin.go @@ -23,6 +23,19 @@ func resourceDockerPlugin() *schema.Resource { Type: schema.TypeBool, Optional: true, }, + "destroy_option": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "force": { + Type: schema.TypeBool, + Optional: true, + }, + }, + }, + }, }, } } diff --git a/docker/resource_docker_plugin_funcs.go b/docker/resource_docker_plugin_funcs.go index 6425c499f..1b6db2cf8 100644 --- a/docker/resource_docker_plugin_funcs.go +++ b/docker/resource_docker_plugin_funcs.go @@ -66,9 +66,9 @@ func resourceDockerPluginUpdate(d *schema.ResourceData, meta interface{}) error } } } - if err := client.PluginSet(ctx, pluginID, nil); err != nil { - return fmt.Errorf("modifiy settings for the Docker plugin "+pluginID+": %w", err) - } + // if err := client.PluginSet(ctx, pluginID, nil); err != nil { + // return fmt.Errorf("modifiy settings for the Docker plugin "+pluginID+": %w", err) + // } // call the read function to update the resource's state. // https://learn.hashicorp.com/tutorials/terraform/provider-update?in=terraform/providers#implement-update return resourceDockerPluginRead(d, meta) @@ -78,17 +78,18 @@ func resourceDockerPluginDelete(d *schema.ResourceData, meta interface{}) error client := meta.(*ProviderConfig).DockerClient ctx := context.Background() pluginID := d.Id() - plugin, _, err := client.PluginInspectWithRaw(ctx, pluginID) - if err != nil { - return fmt.Errorf("inspect a Docker plugin "+pluginID+": %w", err) - } - if plugin.Enabled { - // disable the plugin before removing - if err := client.PluginDisable(ctx, pluginID, types.PluginDisableOptions{}); err != nil { - return fmt.Errorf("disable the Docker plugin "+pluginID+": %w", err) + destroyOptions := d.Get("destroy_option").([]interface{}) + force := false + if len(destroyOptions) == 1 { + destroyOption := destroyOptions[0].(map[string]interface{}) + f, ok := destroyOption["force"] + if ok { + force = f.(bool) } } - if err := client.PluginRemove(ctx, pluginID, types.PluginRemoveOptions{}); err != nil { + if err := client.PluginRemove(ctx, pluginID, types.PluginRemoveOptions{ + Force: force, + }); err != nil { return fmt.Errorf("remove the Docker plugin "+pluginID+": %w", err) } return nil From c0fc9db02fad2b9eeca560aaece50d85483f562b Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Tue, 29 Dec 2020 16:26:34 +0900 Subject: [PATCH 03/36] feat: enhance docker plugin --- docker/resource_docker_plugin.go | 53 +++++++++---- docker/resource_docker_plugin_funcs.go | 103 +++++++++++++++++++------ website/docs/r/plugin.html.markdown | 27 ++++++- 3 files changed, 141 insertions(+), 42 deletions(-) diff --git a/docker/resource_docker_plugin.go b/docker/resource_docker_plugin.go index 865cae454..3fc9a3d6a 100644 --- a/docker/resource_docker_plugin.go +++ b/docker/resource_docker_plugin.go @@ -14,27 +14,50 @@ func resourceDockerPlugin() *schema.Resource { State: schema.ImportStatePassthrough, }, Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, + "plugin_reference": { + Type: schema.TypeString, + Description: "Docker Plugin Reference.", + Required: true, + ForceNew: true, + }, + "alias": { + Type: schema.TypeString, + Computed: true, + Optional: true, + Description: "Docker Plugin alias.", }, "disabled": { Type: schema.TypeBool, Optional: true, }, - "destroy_option": { - Type: schema.TypeList, + "grant_all_permissions": { + Type: schema.TypeBool, + Optional: true, + Description: "If true, grant all permissions necessary to run the plugin", + }, + "disable_when_set": { + Type: schema.TypeBool, + Optional: true, + Description: "If true, the plugin becomes disabled temporarily when the plugin setting is updated", + }, + "force_destroy": { + Type: schema.TypeBool, + Optional: true, + }, + "env": { + Type: schema.TypeSet, Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "force": { - Type: schema.TypeBool, - Optional: true, - }, - }, - }, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "enable_timeout": { + Type: schema.TypeInt, + Optional: true, + Description: "HTTP client timeout to enable the plugin", + }, + "force_disable": { + Type: schema.TypeBool, + Optional: true, + Description: "If true, then the plugin is disabled forcely when the plugin is disabled.", }, }, } diff --git a/docker/resource_docker_plugin_funcs.go b/docker/resource_docker_plugin_funcs.go index 1b6db2cf8..a872b9ac2 100644 --- a/docker/resource_docker_plugin_funcs.go +++ b/docker/resource_docker_plugin_funcs.go @@ -6,26 +6,44 @@ import ( "io/ioutil" "github.com/docker/docker/api/types" + "github.com/docker/docker/client" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" ) +func getDockerPluginEnv(src interface{}) []string { + if src == nil { + return nil + } + b := src.(*schema.Set) + env := make([]string, b.Len()) + for i, a := range b.List() { + env[i] = a.(string) + } + return env +} + func resourceDockerPluginCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ProviderConfig).DockerClient ctx := context.Background() - pluginName := d.Get("name").(string) - // TODO - body, err := client.PluginInstall(ctx, pluginName, types.PluginInstallOptions{ - RemoteRef: pluginName, - AcceptAllPermissions: true, + pluginRef := d.Get("plugin_reference").(string) + alias := d.Get("alias").(string) + body, err := client.PluginInstall(ctx, alias, types.PluginInstallOptions{ + RemoteRef: pluginRef, + AcceptAllPermissions: d.Get("grant_all_permissions").(bool), Disabled: d.Get("disabled").(bool), + Args: getDockerPluginEnv(d.Get("env")), }) if err != nil { - return fmt.Errorf("install a Docker plugin "+pluginName+": %w", err) + return fmt.Errorf("install a Docker plugin "+pluginRef+": %w", err) } _, _ = ioutil.ReadAll(body) - plugin, _, err := client.PluginInspectWithRaw(ctx, pluginName) + key := pluginRef + if alias != "" { + key = alias + } + plugin, _, err := client.PluginInspectWithRaw(ctx, key) if err != nil { - return fmt.Errorf("inspect a Docker plugin "+pluginName+": %w", err) + return fmt.Errorf("inspect a Docker plugin "+key+": %w", err) } setDockerPlugin(d, plugin) return nil @@ -33,8 +51,10 @@ func resourceDockerPluginCreate(d *schema.ResourceData, meta interface{}) error func setDockerPlugin(d *schema.ResourceData, plugin *types.Plugin) { d.SetId(plugin.ID) - d.Set("name", plugin.Name) + d.Set("plugin_reference", plugin.PluginReference) + d.Set("alias", plugin.Name) d.Set("disabled", !plugin.Enabled) + d.Set("env", plugin.Settings.Env) } func resourceDockerPluginRead(d *schema.ResourceData, meta interface{}) error { @@ -50,25 +70,67 @@ func resourceDockerPluginRead(d *schema.ResourceData, meta interface{}) error { return nil } +func setPluginEnv(ctx context.Context, d *schema.ResourceData, client *client.Client) error { + if !d.HasChange("env") { + return nil + } + pluginID := d.Id() + if err := client.PluginSet(ctx, pluginID, getDockerPluginEnv(d.Get("env"))); err != nil { + return fmt.Errorf("modifiy settings for the Docker plugin "+pluginID+": %w", err) + } + return nil +} + func resourceDockerPluginUpdate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ProviderConfig).DockerClient ctx := context.Background() pluginID := d.Id() - // TODO + skipEnv := false if d.HasChange("disabled") { if d.Get("disabled").(bool) { - if err := client.PluginDisable(ctx, pluginID, types.PluginDisableOptions{}); err != nil { + if err := client.PluginDisable(ctx, pluginID, types.PluginDisableOptions{ + Force: d.Get("force_disable").(bool), + }); err != nil { return fmt.Errorf("disable the Docker plugin "+pluginID+": %w", err) } } else { - if err := client.PluginEnable(ctx, pluginID, types.PluginEnableOptions{}); err != nil { + if err := setPluginEnv(ctx, d, client); err != nil { + return err + } + skipEnv = true + if err := client.PluginEnable(ctx, pluginID, types.PluginEnableOptions{ + Timeout: d.Get("enable_timeout").(int), + }); err != nil { + return fmt.Errorf("enable the Docker plugin "+pluginID+": %w", err) + } + } + } + if !skipEnv { + plugin, _, err := client.PluginInspectWithRaw(ctx, pluginID) + if err != nil { + return fmt.Errorf("inspect a Docker plugin "+pluginID+": %w", err) + } + f := false + if plugin.Enabled && d.Get("disable_when_set").(bool) { + // temporarily disable the plugin before updating the plugin setting + if err := client.PluginDisable(ctx, pluginID, types.PluginDisableOptions{ + Force: d.Get("force_disable").(bool), + }); err != nil { + return fmt.Errorf("disable the Docker plugin "+pluginID+": %w", err) + } + f = true + } + if err := setPluginEnv(ctx, d, client); err != nil { + return err + } + if f { + if err := client.PluginEnable(ctx, pluginID, types.PluginEnableOptions{ + Timeout: d.Get("enable_timeout").(int), + }); err != nil { return fmt.Errorf("enable the Docker plugin "+pluginID+": %w", err) } } } - // if err := client.PluginSet(ctx, pluginID, nil); err != nil { - // return fmt.Errorf("modifiy settings for the Docker plugin "+pluginID+": %w", err) - // } // call the read function to update the resource's state. // https://learn.hashicorp.com/tutorials/terraform/provider-update?in=terraform/providers#implement-update return resourceDockerPluginRead(d, meta) @@ -78,17 +140,8 @@ func resourceDockerPluginDelete(d *schema.ResourceData, meta interface{}) error client := meta.(*ProviderConfig).DockerClient ctx := context.Background() pluginID := d.Id() - destroyOptions := d.Get("destroy_option").([]interface{}) - force := false - if len(destroyOptions) == 1 { - destroyOption := destroyOptions[0].(map[string]interface{}) - f, ok := destroyOption["force"] - if ok { - force = f.(bool) - } - } if err := client.PluginRemove(ctx, pluginID, types.PluginRemoveOptions{ - Force: force, + Force: d.Get("force_destroy").(bool), }); err != nil { return fmt.Errorf("remove the Docker plugin "+pluginID+": %w", err) } diff --git a/website/docs/r/plugin.html.markdown b/website/docs/r/plugin.html.markdown index 5c07bb27d..38aee765f 100644 --- a/website/docs/r/plugin.html.markdown +++ b/website/docs/r/plugin.html.markdown @@ -14,7 +14,23 @@ Manages the lifecycle of a Docker plugin. ```hcl resource "docker_plugin" "sample-volume-plugin" { - name = "tiborvass/sample-volume-plugin:latest" + plugin_reference = "docker.io/tiborvass/sample-volume-plugin:latest" +} +``` + +```hcl +resource "docker_plugin" "sample-volume-plugin" { + plugin_reference = "docker.io/tiborvass/sample-volume-plugin:latest" + alias = "sample-volume-plugin:latest" + disabled = true + grant_all_permissions = true + disable_when_set = true + force_destroy = true + enable_timeout = 60 + force_disable = true + env = [ + "DEBUG=1" + ] } ``` @@ -22,8 +38,15 @@ resource "docker_plugin" "sample-volume-plugin" { The following arguments are supported: -* `name` - (Required, string) The name of the Docker plugin. +* `plugin_reference` - (Required, string) The plugin reference. The registry path and image tag should not be omitted. +* `alias` - (Optional, string) The alias of the Docker plugin. The image tag should not be omitted. * `disabled` - (Optional, boolean) If true, the plugin is disabled. +* `grant_all_permissions` - (Optional, boolean) If true, grant all permissions necessary to run the plugin. +* `disable_when_set` - (Optional, boolean) If true, the plugin becomes disabled temporarily when the plugin setting is updated. +* `force_destroy` - (Optional, boolean) If true, the plugin becomes disabled temporarily when the plugin setting is updated. +* `env` - (Optional, set of string) +* `enable_timeout` - (Optional, int) HTTP client timeout to enable the plugin. +* `force_disable` - (Optional, boolean) If true, then the plugin is disabled forcely when the plugin is disabled. ## Attributes Reference From ceae7b17297475eb72043237e12e21ebb12c1213 Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Tue, 29 Dec 2020 16:37:14 +0900 Subject: [PATCH 04/36] fix: make docker_plugin.alias force new --- docker/resource_docker_plugin.go | 12 +++++++----- website/docs/r/plugin.html.markdown | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/docker/resource_docker_plugin.go b/docker/resource_docker_plugin.go index 3fc9a3d6a..63d0445f8 100644 --- a/docker/resource_docker_plugin.go +++ b/docker/resource_docker_plugin.go @@ -24,6 +24,7 @@ func resourceDockerPlugin() *schema.Resource { Type: schema.TypeString, Computed: true, Optional: true, + ForceNew: true, Description: "Docker Plugin alias.", }, "disabled": { @@ -35,6 +36,12 @@ func resourceDockerPlugin() *schema.Resource { Optional: true, Description: "If true, grant all permissions necessary to run the plugin", }, + "env": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "disable_when_set": { Type: schema.TypeBool, Optional: true, @@ -44,11 +51,6 @@ func resourceDockerPlugin() *schema.Resource { Type: schema.TypeBool, Optional: true, }, - "env": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, "enable_timeout": { Type: schema.TypeInt, Optional: true, diff --git a/website/docs/r/plugin.html.markdown b/website/docs/r/plugin.html.markdown index 38aee765f..0e60d714b 100644 --- a/website/docs/r/plugin.html.markdown +++ b/website/docs/r/plugin.html.markdown @@ -38,8 +38,8 @@ resource "docker_plugin" "sample-volume-plugin" { The following arguments are supported: -* `plugin_reference` - (Required, string) The plugin reference. The registry path and image tag should not be omitted. -* `alias` - (Optional, string) The alias of the Docker plugin. The image tag should not be omitted. +* `plugin_reference` - (Required, string, Forces new resource) The plugin reference. The registry path and image tag should not be omitted. +* `alias` - (Optional, string, Forces new resource) The alias of the Docker plugin. The image tag should not be omitted. * `disabled` - (Optional, boolean) If true, the plugin is disabled. * `grant_all_permissions` - (Optional, boolean) If true, grant all permissions necessary to run the plugin. * `disable_when_set` - (Optional, boolean) If true, the plugin becomes disabled temporarily when the plugin setting is updated. From 1c1ba51f9b871e131d14634e6ec01e210ad606a0 Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Tue, 29 Dec 2020 17:13:11 +0900 Subject: [PATCH 05/36] fix: make docker_plugin.env computed --- docker/resource_docker_plugin.go | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/resource_docker_plugin.go b/docker/resource_docker_plugin.go index 63d0445f8..20cb17cd6 100644 --- a/docker/resource_docker_plugin.go +++ b/docker/resource_docker_plugin.go @@ -39,6 +39,7 @@ func resourceDockerPlugin() *schema.Resource { "env": { Type: schema.TypeSet, Optional: true, + Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, }, From 963699bb3d337d731f9c63de940d939bae595637 Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Tue, 29 Dec 2020 17:13:38 +0900 Subject: [PATCH 06/36] fix: remove the plugin from state when it failed to inspect the plugin --- docker/resource_docker_plugin_funcs.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docker/resource_docker_plugin_funcs.go b/docker/resource_docker_plugin_funcs.go index a872b9ac2..ac73b9dd3 100644 --- a/docker/resource_docker_plugin_funcs.go +++ b/docker/resource_docker_plugin_funcs.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io/ioutil" + "log" "github.com/docker/docker/api/types" "github.com/docker/docker/client" @@ -63,7 +64,9 @@ func resourceDockerPluginRead(d *schema.ResourceData, meta interface{}) error { pluginID := d.Id() plugin, _, err := client.PluginInspectWithRaw(ctx, pluginID) if err != nil { - return fmt.Errorf("inspect a Docker plugin "+pluginID+": %w", err) + log.Printf("[DEBUG] Inspect a Docker plugin "+pluginID+": %w", err) + d.SetId("") + return nil } setDockerPlugin(d, plugin) // TODO set values From bbd50ed68ee4a88f2fa3d17323dc008d731b0a35 Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Tue, 29 Dec 2020 17:15:29 +0900 Subject: [PATCH 07/36] test: add test of docker_plugin --- docker/resource_docker_plugin_test.go | 34 +++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 docker/resource_docker_plugin_test.go diff --git a/docker/resource_docker_plugin_test.go b/docker/resource_docker_plugin_test.go new file mode 100644 index 000000000..91f20f836 --- /dev/null +++ b/docker/resource_docker_plugin_test.go @@ -0,0 +1,34 @@ +package docker + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccDockerPlugin_basic(t *testing.T) { + const resourceName = "docker_plugin.test" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + ResourceName: resourceName, + Config: testAccDockerPluginConfig, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "plugin_reference", "docker.io/tiborvass/sample-volume-plugin:latest"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + }, + }, + }) +} + +const testAccDockerPluginConfig = ` +resource "docker_plugin" "test" { + plugin_reference = "docker.io/tiborvass/sample-volume-plugin:latest" + force_destroy = true +}` From 260a2efe9b51cd4943fc103bd99f309d6783e0ee Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Tue, 29 Dec 2020 18:04:07 +0900 Subject: [PATCH 08/36] docs: fix a lint error ``` website/docs/r/plugin.html.markdown:58 MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"] ``` --- website/docs/r/plugin.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/plugin.html.markdown b/website/docs/r/plugin.html.markdown index 0e60d714b..09cd3b2cb 100644 --- a/website/docs/r/plugin.html.markdown +++ b/website/docs/r/plugin.html.markdown @@ -54,6 +54,6 @@ The following arguments are supported: Docker plugins can be imported using the long id, e.g. for a plugin `tiborvass/sample-volume-plugin:latest`: -``` +```sh $ terraform import docker_plugin.sample-volume-plugin $(docker plugin inspect -f "{{.ID}}" tiborvass/sample-volume-plugin:latest) ``` From 1f7e286f4a3356b34bd26c0c2af7658cf26d6dfc Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Tue, 29 Dec 2020 18:32:17 +0900 Subject: [PATCH 09/36] test: add tests of docker_plugin --- docker/resource_docker_plugin_test.go | 79 ++++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 3 deletions(-) diff --git a/docker/resource_docker_plugin_test.go b/docker/resource_docker_plugin_test.go index 91f20f836..d04ee81ae 100644 --- a/docker/resource_docker_plugin_test.go +++ b/docker/resource_docker_plugin_test.go @@ -14,9 +14,47 @@ func TestAccDockerPlugin_basic(t *testing.T) { Steps: []resource.TestStep{ { ResourceName: resourceName, - Config: testAccDockerPluginConfig, + Config: testAccDockerPluginMinimum, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "plugin_reference", "docker.io/tiborvass/sample-volume-plugin:latest"), + resource.TestCheckResourceAttr(resourceName, "alias", "tiborvass/sample-volume-plugin:latest"), + resource.TestCheckResourceAttr(resourceName, "disabled", "false"), + ), + }, + { + ResourceName: resourceName, + Config: testAccDockerPluginAlias, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "plugin_reference", "docker.io/tiborvass/sample-volume-plugin:latest"), + resource.TestCheckResourceAttr(resourceName, "alias", "sample:latest"), + resource.TestCheckResourceAttr(resourceName, "disabled", "false"), + ), + }, + { + ResourceName: resourceName, + Config: testAccDockerPluginDisableWhenSet, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "plugin_reference", "docker.io/tiborvass/sample-volume-plugin:latest"), + resource.TestCheckResourceAttr(resourceName, "alias", "sample:latest"), + resource.TestCheckResourceAttr(resourceName, "disabled", "false"), + resource.TestCheckResourceAttr(resourceName, "grant_all_permissions", "true"), + resource.TestCheckResourceAttr(resourceName, "disable_when_set", "true"), + resource.TestCheckResourceAttr(resourceName, "force_destroy", "true"), + resource.TestCheckResourceAttr(resourceName, "enable_timeout", "60"), + ), + }, + { + ResourceName: resourceName, + Config: testAccDockerPluginDisabled, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "plugin_reference", "docker.io/tiborvass/sample-volume-plugin:latest"), + resource.TestCheckResourceAttr(resourceName, "alias", "sample:latest"), + resource.TestCheckResourceAttr(resourceName, "disabled", "true"), + resource.TestCheckResourceAttr(resourceName, "grant_all_permissions", "true"), + resource.TestCheckResourceAttr(resourceName, "disable_when_set", "true"), + resource.TestCheckResourceAttr(resourceName, "force_destroy", "true"), + resource.TestCheckResourceAttr(resourceName, "enable_timeout", "60"), + resource.TestCheckResourceAttr(resourceName, "force_disable", "true"), ), }, { @@ -27,8 +65,43 @@ func TestAccDockerPlugin_basic(t *testing.T) { }) } -const testAccDockerPluginConfig = ` +const testAccDockerPluginMinimum = ` +resource "docker_plugin" "test" { + plugin_reference = "docker.io/tiborvass/sample-volume-plugin:latest" + force_destroy = true +}` + +const testAccDockerPluginAlias = ` resource "docker_plugin" "test" { plugin_reference = "docker.io/tiborvass/sample-volume-plugin:latest" - force_destroy = true + alias = "sample:latest" + force_destroy = true +}` + +const testAccDockerPluginDisableWhenSet = ` +resource "docker_plugin" "test" { + plugin_reference = "docker.io/tiborvass/sample-volume-plugin:latest" + alias = "sample:latest" + grant_all_permissions = true + disable_when_set = true + force_destroy = true + enable_timeout = 60 + env = [ + "DEBUG=1" + ] +}` + +const testAccDockerPluginDisabled = ` +resource "docker_plugin" "test" { + plugin_reference = "docker.io/tiborvass/sample-volume-plugin:latest" + alias = "sample:latest" + disabled = true + grant_all_permissions = true + disable_when_set = true + force_destroy = true + force_disable = true + enable_timeout = 60 + env = [ + "DEBUG=1" + ] }` From 731342ad4299f0f5754e5a9397919953a674fb11 Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Tue, 29 Dec 2020 18:44:13 +0900 Subject: [PATCH 10/36] docs: format document ``` $ make website-lint-fix ``` --- website/docs/r/plugin.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/plugin.html.markdown b/website/docs/r/plugin.html.markdown index 09cd3b2cb..f85b697c5 100644 --- a/website/docs/r/plugin.html.markdown +++ b/website/docs/r/plugin.html.markdown @@ -14,7 +14,7 @@ Manages the lifecycle of a Docker plugin. ```hcl resource "docker_plugin" "sample-volume-plugin" { - plugin_reference = "docker.io/tiborvass/sample-volume-plugin:latest" + plugin_reference = "docker.io/tiborvass/sample-volume-plugin:latest" } ``` From 593269b9bd2464612b8ae0bc586570b95018ba5d Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Tue, 29 Dec 2020 19:15:37 +0900 Subject: [PATCH 11/36] style: remove TODO comment --- docker/resource_docker_plugin_funcs.go | 1 - 1 file changed, 1 deletion(-) diff --git a/docker/resource_docker_plugin_funcs.go b/docker/resource_docker_plugin_funcs.go index ac73b9dd3..903b901b3 100644 --- a/docker/resource_docker_plugin_funcs.go +++ b/docker/resource_docker_plugin_funcs.go @@ -69,7 +69,6 @@ func resourceDockerPluginRead(d *schema.ResourceData, meta interface{}) error { return nil } setDockerPlugin(d, plugin) - // TODO set values return nil } From 84dd122c420cbcffac8c9a9f7065e944ae89ad84 Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Tue, 29 Dec 2020 19:24:31 +0900 Subject: [PATCH 12/36] fix: rename env to args https://docs.docker.com/engine/reference/commandline/plugin_set/#extended-description --- docker/resource_docker_plugin.go | 2 +- docker/resource_docker_plugin_funcs.go | 33 +++++++++++++++----------- docker/resource_docker_plugin_test.go | 4 ++-- website/docs/r/plugin.html.markdown | 4 ++-- 4 files changed, 24 insertions(+), 19 deletions(-) diff --git a/docker/resource_docker_plugin.go b/docker/resource_docker_plugin.go index 20cb17cd6..fa8032c6b 100644 --- a/docker/resource_docker_plugin.go +++ b/docker/resource_docker_plugin.go @@ -36,7 +36,7 @@ func resourceDockerPlugin() *schema.Resource { Optional: true, Description: "If true, grant all permissions necessary to run the plugin", }, - "env": { + "args": { Type: schema.TypeSet, Optional: true, Computed: true, diff --git a/docker/resource_docker_plugin_funcs.go b/docker/resource_docker_plugin_funcs.go index 903b901b3..15e488341 100644 --- a/docker/resource_docker_plugin_funcs.go +++ b/docker/resource_docker_plugin_funcs.go @@ -11,16 +11,16 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/helper/schema" ) -func getDockerPluginEnv(src interface{}) []string { +func getDockerPluginArgs(src interface{}) []string { if src == nil { return nil } b := src.(*schema.Set) - env := make([]string, b.Len()) + args := make([]string, b.Len()) for i, a := range b.List() { - env[i] = a.(string) + args[i] = a.(string) } - return env + return args } func resourceDockerPluginCreate(d *schema.ResourceData, meta interface{}) error { @@ -32,7 +32,7 @@ func resourceDockerPluginCreate(d *schema.ResourceData, meta interface{}) error RemoteRef: pluginRef, AcceptAllPermissions: d.Get("grant_all_permissions").(bool), Disabled: d.Get("disabled").(bool), - Args: getDockerPluginEnv(d.Get("env")), + Args: getDockerPluginArgs(d.Get("args")), }) if err != nil { return fmt.Errorf("install a Docker plugin "+pluginRef+": %w", err) @@ -55,7 +55,12 @@ func setDockerPlugin(d *schema.ResourceData, plugin *types.Plugin) { d.Set("plugin_reference", plugin.PluginReference) d.Set("alias", plugin.Name) d.Set("disabled", !plugin.Enabled) - d.Set("env", plugin.Settings.Env) + // TODO support other settings + // https://docs.docker.com/engine/reference/commandline/plugin_set/#extended-description + // source of mounts .Settings.Mounts + // path of devices .Settings.Devices + // args .Settings.Args + d.Set("args", plugin.Settings.Env) } func resourceDockerPluginRead(d *schema.ResourceData, meta interface{}) error { @@ -72,12 +77,12 @@ func resourceDockerPluginRead(d *schema.ResourceData, meta interface{}) error { return nil } -func setPluginEnv(ctx context.Context, d *schema.ResourceData, client *client.Client) error { - if !d.HasChange("env") { +func setPluginArgs(ctx context.Context, d *schema.ResourceData, client *client.Client) error { + if !d.HasChange("args") { return nil } pluginID := d.Id() - if err := client.PluginSet(ctx, pluginID, getDockerPluginEnv(d.Get("env"))); err != nil { + if err := client.PluginSet(ctx, pluginID, getDockerPluginArgs(d.Get("args"))); err != nil { return fmt.Errorf("modifiy settings for the Docker plugin "+pluginID+": %w", err) } return nil @@ -87,7 +92,7 @@ func resourceDockerPluginUpdate(d *schema.ResourceData, meta interface{}) error client := meta.(*ProviderConfig).DockerClient ctx := context.Background() pluginID := d.Id() - skipEnv := false + skipArgs := false if d.HasChange("disabled") { if d.Get("disabled").(bool) { if err := client.PluginDisable(ctx, pluginID, types.PluginDisableOptions{ @@ -96,10 +101,10 @@ func resourceDockerPluginUpdate(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("disable the Docker plugin "+pluginID+": %w", err) } } else { - if err := setPluginEnv(ctx, d, client); err != nil { + if err := setPluginArgs(ctx, d, client); err != nil { return err } - skipEnv = true + skipArgs = true if err := client.PluginEnable(ctx, pluginID, types.PluginEnableOptions{ Timeout: d.Get("enable_timeout").(int), }); err != nil { @@ -107,7 +112,7 @@ func resourceDockerPluginUpdate(d *schema.ResourceData, meta interface{}) error } } } - if !skipEnv { + if !skipArgs { plugin, _, err := client.PluginInspectWithRaw(ctx, pluginID) if err != nil { return fmt.Errorf("inspect a Docker plugin "+pluginID+": %w", err) @@ -122,7 +127,7 @@ func resourceDockerPluginUpdate(d *schema.ResourceData, meta interface{}) error } f = true } - if err := setPluginEnv(ctx, d, client); err != nil { + if err := setPluginArgs(ctx, d, client); err != nil { return err } if f { diff --git a/docker/resource_docker_plugin_test.go b/docker/resource_docker_plugin_test.go index d04ee81ae..d34f8cee1 100644 --- a/docker/resource_docker_plugin_test.go +++ b/docker/resource_docker_plugin_test.go @@ -86,7 +86,7 @@ resource "docker_plugin" "test" { disable_when_set = true force_destroy = true enable_timeout = 60 - env = [ + args = [ "DEBUG=1" ] }` @@ -101,7 +101,7 @@ resource "docker_plugin" "test" { force_destroy = true force_disable = true enable_timeout = 60 - env = [ + args = [ "DEBUG=1" ] }` diff --git a/website/docs/r/plugin.html.markdown b/website/docs/r/plugin.html.markdown index f85b697c5..036372d73 100644 --- a/website/docs/r/plugin.html.markdown +++ b/website/docs/r/plugin.html.markdown @@ -28,7 +28,7 @@ resource "docker_plugin" "sample-volume-plugin" { force_destroy = true enable_timeout = 60 force_disable = true - env = [ + args = [ "DEBUG=1" ] } @@ -44,7 +44,7 @@ The following arguments are supported: * `grant_all_permissions` - (Optional, boolean) If true, grant all permissions necessary to run the plugin. * `disable_when_set` - (Optional, boolean) If true, the plugin becomes disabled temporarily when the plugin setting is updated. * `force_destroy` - (Optional, boolean) If true, the plugin becomes disabled temporarily when the plugin setting is updated. -* `env` - (Optional, set of string) +* `args` - (Optional, set of string) * `enable_timeout` - (Optional, int) HTTP client timeout to enable the plugin. * `force_disable` - (Optional, boolean) If true, then the plugin is disabled forcely when the plugin is disabled. From 3b7ab72b009f46e6998f61571efb46f2f5b0eb37 Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Tue, 29 Dec 2020 19:47:25 +0900 Subject: [PATCH 13/36] feat: add debug logs --- docker/resource_docker_plugin_funcs.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docker/resource_docker_plugin_funcs.go b/docker/resource_docker_plugin_funcs.go index 15e488341..71d13f79d 100644 --- a/docker/resource_docker_plugin_funcs.go +++ b/docker/resource_docker_plugin_funcs.go @@ -28,6 +28,7 @@ func resourceDockerPluginCreate(d *schema.ResourceData, meta interface{}) error ctx := context.Background() pluginRef := d.Get("plugin_reference").(string) alias := d.Get("alias").(string) + log.Printf("[DEBUG] Install a Docker plugin " + pluginRef) body, err := client.PluginInstall(ctx, alias, types.PluginInstallOptions{ RemoteRef: pluginRef, AcceptAllPermissions: d.Get("grant_all_permissions").(bool), @@ -82,6 +83,7 @@ func setPluginArgs(ctx context.Context, d *schema.ResourceData, client *client.C return nil } pluginID := d.Id() + log.Printf("[DEBUG] Update settings of a Docker plugin " + pluginID) if err := client.PluginSet(ctx, pluginID, getDockerPluginArgs(d.Get("args"))); err != nil { return fmt.Errorf("modifiy settings for the Docker plugin "+pluginID+": %w", err) } @@ -95,6 +97,7 @@ func resourceDockerPluginUpdate(d *schema.ResourceData, meta interface{}) error skipArgs := false if d.HasChange("disabled") { if d.Get("disabled").(bool) { + log.Printf("[DEBUG] Disable a Docker plugin " + pluginID) if err := client.PluginDisable(ctx, pluginID, types.PluginDisableOptions{ Force: d.Get("force_disable").(bool), }); err != nil { @@ -105,6 +108,7 @@ func resourceDockerPluginUpdate(d *schema.ResourceData, meta interface{}) error return err } skipArgs = true + log.Printf("[DEBUG] Enable a Docker plugin " + pluginID) if err := client.PluginEnable(ctx, pluginID, types.PluginEnableOptions{ Timeout: d.Get("enable_timeout").(int), }); err != nil { @@ -119,7 +123,8 @@ func resourceDockerPluginUpdate(d *schema.ResourceData, meta interface{}) error } f := false if plugin.Enabled && d.Get("disable_when_set").(bool) { - // temporarily disable the plugin before updating the plugin setting + // temporarily disable the plugin before updating the plugin settings + log.Printf("[DEBUG] Disable a Docker plugin " + pluginID + " temporarily before updating teh pugin settings") if err := client.PluginDisable(ctx, pluginID, types.PluginDisableOptions{ Force: d.Get("force_disable").(bool), }); err != nil { @@ -131,6 +136,7 @@ func resourceDockerPluginUpdate(d *schema.ResourceData, meta interface{}) error return err } if f { + log.Printf("[DEBUG] Enable a Docker plugin " + pluginID) if err := client.PluginEnable(ctx, pluginID, types.PluginEnableOptions{ Timeout: d.Get("enable_timeout").(int), }); err != nil { @@ -147,6 +153,7 @@ func resourceDockerPluginDelete(d *schema.ResourceData, meta interface{}) error client := meta.(*ProviderConfig).DockerClient ctx := context.Background() pluginID := d.Id() + log.Printf("[DEBUG] Remove a Docker plugin " + pluginID) if err := client.PluginRemove(ctx, pluginID, types.PluginRemoveOptions{ Force: d.Get("force_destroy").(bool), }); err != nil { From a9662ade763f0c6ed4be6d573a098b67952f54d9 Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Tue, 29 Dec 2020 20:20:58 +0900 Subject: [PATCH 14/36] docs: update plugin document --- website/docs/r/plugin.html.markdown | 64 ++++++++++++++++++++++++++--- 1 file changed, 59 insertions(+), 5 deletions(-) diff --git a/website/docs/r/plugin.html.markdown b/website/docs/r/plugin.html.markdown index 036372d73..552feddfc 100644 --- a/website/docs/r/plugin.html.markdown +++ b/website/docs/r/plugin.html.markdown @@ -38,16 +38,70 @@ resource "docker_plugin" "sample-volume-plugin" { The following arguments are supported: -* `plugin_reference` - (Required, string, Forces new resource) The plugin reference. The registry path and image tag should not be omitted. -* `alias` - (Optional, string, Forces new resource) The alias of the Docker plugin. The image tag should not be omitted. +* `plugin_reference` - (Required, string, Forces new resource) The plugin reference. The registry path and image tag should not be omitted. See [plugin_references, alias](#plugin-references-alias-1) below for details. +* `alias` - (Optional, string, Forces new resource) The alias of the Docker plugin. The image tag should not be omitted. See [plugin_references, alias](#plugin-references-alias-1) below for details. * `disabled` - (Optional, boolean) If true, the plugin is disabled. * `grant_all_permissions` - (Optional, boolean) If true, grant all permissions necessary to run the plugin. -* `disable_when_set` - (Optional, boolean) If true, the plugin becomes disabled temporarily when the plugin setting is updated. -* `force_destroy` - (Optional, boolean) If true, the plugin becomes disabled temporarily when the plugin setting is updated. -* `args` - (Optional, set of string) +* `args` - (Optional, set of string). Currently, only environment variables are supported. +* `disable_when_set` - (Optional, boolean) If true, the plugin becomes disabled temporarily when the plugin setting is updated. See [disable_when_set](#disable-when-set-1) below for details. +* `force_destroy` - (Optional, boolean) If true, the plugin is removed forcely when the plugin is removed. * `enable_timeout` - (Optional, int) HTTP client timeout to enable the plugin. * `force_disable` - (Optional, boolean) If true, then the plugin is disabled forcely when the plugin is disabled. + +## plugin_reference, alias + +`plugin_reference` and `alias` must be full path. Otherwise, after `terraform apply` is run, there would be diffs of them. + +For example, + +```hcl +resource "docker_plugin" "sample-volume-plugin" { + plugin_reference = "tiborvass/sample-volume-plugin" # must be "docker.io/tiborvass/sample-volume-plugin:latest" + alias = "sample" # must be "sample:latest" +} +``` + +```sh +$ terraform apply # a plugin is installed + +Apply complete! Resources: 1 added, 0 changed, 0 destroyed. + +$ terraform plan + +An execution plan has been generated and is shown below. +Resource actions are indicated with the following symbols: +-/+ destroy and then create replacement + +Terraform will perform the following actions: + + # docker_plugin.sample-volume-plugin must be replaced +-/+ resource "docker_plugin" "sample-volume-plugin" { + ~ alias = "sample:latest" -> "sample" # forces replacement + - disabled = false -> null + ~ env = [ + - "DEBUG=0", + ] -> (known after apply) + ~ id = "27784976e1471c1e473a901a0a02055ddc8bc1c9dec9c44d81a49d516c0c28f9" -> (known after apply) + ~ plugin_reference = "docker.io/tiborvass/sample-volume-plugin:latest" -> "tiborvass/sample-volume-plugin" # forces replacement + } + +Plan: 1 to add, 0 to change, 1 to destroy. +``` + + +## disable_when_set + +To update the plugin settings, the plugin must be disabled. +Otherwise, it failed to update the plugin settings as the following. + +```sh +$ docker plugin set tiborvass/sample-volume-plugin:latest DEBUG=2 +Error response from daemon: cannot set on an active plugin, disable plugin before setting +``` + +If `disable_when_set` is true, then the plugin becomes disabled temporarily before the attribute `args` is updated and after `args` is updated the plugin becomes enabled again. + ## Attributes Reference ## Import From 6522a35a4d22fbeb02da6a68228dba0f51286b89 Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Tue, 29 Dec 2020 20:34:20 +0900 Subject: [PATCH 15/36] fix: enable plugin after disable it temporarily with defer --- docker/resource_docker_plugin_funcs.go | 46 ++++++++++++++------------ 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/docker/resource_docker_plugin_funcs.go b/docker/resource_docker_plugin_funcs.go index 71d13f79d..d29f31781 100644 --- a/docker/resource_docker_plugin_funcs.go +++ b/docker/resource_docker_plugin_funcs.go @@ -78,11 +78,32 @@ func resourceDockerPluginRead(d *schema.ResourceData, meta interface{}) error { return nil } -func setPluginArgs(ctx context.Context, d *schema.ResourceData, client *client.Client) error { +func setPluginArgs(ctx context.Context, d *schema.ResourceData, client *client.Client, disabled bool) (gErr error) { if !d.HasChange("args") { return nil } pluginID := d.Id() + if disabled { + // temporarily disable the plugin before updating the plugin settings + log.Printf("[DEBUG] Disable a Docker plugin " + pluginID + " temporarily before updating teh pugin settings") + if err := client.PluginDisable(ctx, pluginID, types.PluginDisableOptions{ + Force: d.Get("force_disable").(bool), + }); err != nil { + return fmt.Errorf("disable the Docker plugin "+pluginID+": %w", err) + } + defer func() { + log.Print("[DEBUG] Enable a Docker plugin " + pluginID) + if err := client.PluginEnable(ctx, pluginID, types.PluginEnableOptions{ + Timeout: d.Get("enable_timeout").(int), + }); err != nil { + if gErr == nil { + gErr = fmt.Errorf("enable the Docker plugin "+pluginID+": %w", err) + return + } + log.Printf("[ERROR] enable the Docker plugin "+pluginID+": %w", err) + } + }() + } log.Printf("[DEBUG] Update settings of a Docker plugin " + pluginID) if err := client.PluginSet(ctx, pluginID, getDockerPluginArgs(d.Get("args"))); err != nil { return fmt.Errorf("modifiy settings for the Docker plugin "+pluginID+": %w", err) @@ -104,7 +125,7 @@ func resourceDockerPluginUpdate(d *schema.ResourceData, meta interface{}) error return fmt.Errorf("disable the Docker plugin "+pluginID+": %w", err) } } else { - if err := setPluginArgs(ctx, d, client); err != nil { + if err := setPluginArgs(ctx, d, client, false); err != nil { return err } skipArgs = true @@ -121,28 +142,9 @@ func resourceDockerPluginUpdate(d *schema.ResourceData, meta interface{}) error if err != nil { return fmt.Errorf("inspect a Docker plugin "+pluginID+": %w", err) } - f := false - if plugin.Enabled && d.Get("disable_when_set").(bool) { - // temporarily disable the plugin before updating the plugin settings - log.Printf("[DEBUG] Disable a Docker plugin " + pluginID + " temporarily before updating teh pugin settings") - if err := client.PluginDisable(ctx, pluginID, types.PluginDisableOptions{ - Force: d.Get("force_disable").(bool), - }); err != nil { - return fmt.Errorf("disable the Docker plugin "+pluginID+": %w", err) - } - f = true - } - if err := setPluginArgs(ctx, d, client); err != nil { + if err := setPluginArgs(ctx, d, client, plugin.Enabled && d.Get("disable_when_set").(bool)); err != nil { return err } - if f { - log.Printf("[DEBUG] Enable a Docker plugin " + pluginID) - if err := client.PluginEnable(ctx, pluginID, types.PluginEnableOptions{ - Timeout: d.Get("enable_timeout").(int), - }); err != nil { - return fmt.Errorf("enable the Docker plugin "+pluginID+": %w", err) - } - } } // call the read function to update the resource's state. // https://learn.hashicorp.com/tutorials/terraform/provider-update?in=terraform/providers#implement-update From 7a79c28c88fd51de0cdb2edd843c63da55ab047e Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Wed, 30 Dec 2020 06:53:46 +0900 Subject: [PATCH 16/36] docs: fix forcely to forcibly https://github.com/kreuzwerker/terraform-provider-docker/pull/35#discussion_r549841610 --- docker/resource_docker_plugin.go | 2 +- website/docs/r/image.html.markdown | 2 +- website/docs/r/plugin.html.markdown | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/resource_docker_plugin.go b/docker/resource_docker_plugin.go index fa8032c6b..8e1689b44 100644 --- a/docker/resource_docker_plugin.go +++ b/docker/resource_docker_plugin.go @@ -60,7 +60,7 @@ func resourceDockerPlugin() *schema.Resource { "force_disable": { Type: schema.TypeBool, Optional: true, - Description: "If true, then the plugin is disabled forcely when the plugin is disabled.", + Description: "If true, then the plugin is disabled forcibly when the plugin is disabled.", }, }, } diff --git a/website/docs/r/image.html.markdown b/website/docs/r/image.html.markdown index 6dcfcc330..8b0538f6c 100644 --- a/website/docs/r/image.html.markdown +++ b/website/docs/r/image.html.markdown @@ -52,7 +52,7 @@ The following arguments are supported: registry when using the `docker_registry_image` [data source](/docs/providers/docker/d/registry_image.html) to trigger an image update. * `pull_trigger` - **Deprecated**, use `pull_triggers` instead. -* `force_remove` - (Optional, boolean) If true, then the image is removed Forcely when the resource is destroyed. +* `force_remove` - (Optional, boolean) If true, then the image is removed forcibly when the resource is destroyed. * `build` - (Optional, block) See [Build](#build-1) below for details. diff --git a/website/docs/r/plugin.html.markdown b/website/docs/r/plugin.html.markdown index 552feddfc..b541be8f2 100644 --- a/website/docs/r/plugin.html.markdown +++ b/website/docs/r/plugin.html.markdown @@ -44,9 +44,9 @@ The following arguments are supported: * `grant_all_permissions` - (Optional, boolean) If true, grant all permissions necessary to run the plugin. * `args` - (Optional, set of string). Currently, only environment variables are supported. * `disable_when_set` - (Optional, boolean) If true, the plugin becomes disabled temporarily when the plugin setting is updated. See [disable_when_set](#disable-when-set-1) below for details. -* `force_destroy` - (Optional, boolean) If true, the plugin is removed forcely when the plugin is removed. +* `force_destroy` - (Optional, boolean) If true, the plugin is removed forcibly when the plugin is removed. * `enable_timeout` - (Optional, int) HTTP client timeout to enable the plugin. -* `force_disable` - (Optional, boolean) If true, then the plugin is disabled forcely when the plugin is disabled. +* `force_disable` - (Optional, boolean) If true, then the plugin is disabled forcibly when the plugin is disabled. ## plugin_reference, alias From 908fd7c13b61f78d14bb377b25ea34b1d0bbf5f9 Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Wed, 30 Dec 2020 07:36:03 +0900 Subject: [PATCH 17/36] feat: add a data source docker_plugin --- docker/data_source_docker_plugin.go | 75 +++++++++++++++++++++++++++++ docker/provider.go | 1 + 2 files changed, 76 insertions(+) create mode 100644 docker/data_source_docker_plugin.go diff --git a/docker/data_source_docker_plugin.go b/docker/data_source_docker_plugin.go new file mode 100644 index 000000000..8d066e838 --- /dev/null +++ b/docker/data_source_docker_plugin.go @@ -0,0 +1,75 @@ +package docker + +import ( + "context" + "errors" + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func dataSourceDockerPlugin() *schema.Resource { + return &schema.Resource{ + Read: dataSourceDockerPluginRead, + + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Optional: true, + }, + "alias": { + Type: schema.TypeString, + Optional: true, + Description: "Docker Plugin alias.", + }, + + "plugin_reference": { + Type: schema.TypeString, + Description: "Docker Plugin Reference.", + Optional: true, + }, + "disabled": { + Type: schema.TypeBool, + Optional: true, + }, + "grant_all_permissions": { + Type: schema.TypeBool, + Optional: true, + Description: "If true, grant all permissions necessary to run the plugin", + }, + "args": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func getDataSourcePluginKey(d *schema.ResourceData) string { + if id, ok := d.GetOk("id"); ok { + return id.(string) + } + if alias, ok := d.GetOk("alias"); ok { + return alias.(string) + } + return "" +} + +var errDataSourceKeyIsMissing = errors.New("One of id or alias must be assigned") + +func dataSourceDockerPluginRead(d *schema.ResourceData, meta interface{}) error { + key := getDataSourcePluginKey(d) + if key == "" { + return errDataSourceKeyIsMissing + } + client := meta.(*ProviderConfig).DockerClient + ctx := context.Background() + plugin, _, err := client.PluginInspectWithRaw(ctx, key) + if err != nil { + return fmt.Errorf("inspect a Docker plugin "+key+": %w", err) + } + + setDockerPlugin(d, plugin) + return nil +} diff --git a/docker/provider.go b/docker/provider.go index 6bf9188b5..4d52fd700 100644 --- a/docker/provider.go +++ b/docker/provider.go @@ -115,6 +115,7 @@ func Provider() terraform.ResourceProvider { DataSourcesMap: map[string]*schema.Resource{ "docker_registry_image": dataSourceDockerRegistryImage(), "docker_network": dataSourceDockerNetwork(), + "docker_plugin": dataSourceDockerPlugin(), }, ConfigureFunc: providerConfigure, From 9df6394f115b3ede6920f78ebb4801149e70e008 Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Wed, 30 Dec 2020 07:50:17 +0900 Subject: [PATCH 18/36] docs: add document of data.docker_plugin --- website/docs/d/plugin.html.markdown | 37 +++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 website/docs/d/plugin.html.markdown diff --git a/website/docs/d/plugin.html.markdown b/website/docs/d/plugin.html.markdown new file mode 100644 index 000000000..2fa731a0c --- /dev/null +++ b/website/docs/d/plugin.html.markdown @@ -0,0 +1,37 @@ +--- +layout: "docker" +page_title: "Docker: docker_plugin" +sidebar_current: "docs-docker-datasource-plugin" +description: |- + Reads the local Docker pluign. +--- + +# docker\_plugin + +Reads the local Docker plugin. The plugin must be installed locally. + +## Example Usage + +```hcl +data "docker_plugin" "sample-volume-plugin" { + alias = "sample-volume-plugin:latest" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `id` - (Optional, string) The Docker plugin ID. +* `alias` - (Optional, string) The alias of the Docker plugin. + +One of `id` or `alias` must be assigned. + +## Attributes Reference + +The following attributes are exported in addition to the above configuration: + +* `plugin_reference` - (Optional, string, Forces new resource) The plugin reference. +* `disabled` - (Optional, boolean) If true, the plugin is disabled. +* `grant_all_permissions` - (Optional, boolean) If true, grant all permissions necessary to run the plugin. +* `args` - (Optional, set of string). Currently, only environment variables are supported. From e52b26430a0f6b1416dda16f7f5255711eaed32e Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Wed, 30 Dec 2020 07:54:45 +0900 Subject: [PATCH 19/36] fix: raise an error if both id and alias are specified. --- docker/data_source_docker_plugin.go | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/docker/data_source_docker_plugin.go b/docker/data_source_docker_plugin.go index 8d066e838..a683d7e49 100644 --- a/docker/data_source_docker_plugin.go +++ b/docker/data_source_docker_plugin.go @@ -46,22 +46,27 @@ func dataSourceDockerPlugin() *schema.Resource { } } -func getDataSourcePluginKey(d *schema.ResourceData) string { - if id, ok := d.GetOk("id"); ok { - return id.(string) +var errDataSourceKeyIsMissing = errors.New("One of id or alias must be assigned") + +func getDataSourcePluginKey(d *schema.ResourceData) (string, error) { + id, idOK := d.GetOk("id") + alias, aliasOK := d.GetOk("alias") + if idOK { + if aliasOK { + return "", errDataSourceKeyIsMissing + } + return id.(string), nil } - if alias, ok := d.GetOk("alias"); ok { - return alias.(string) + if aliasOK { + return alias.(string), nil } - return "" + return "", errDataSourceKeyIsMissing } -var errDataSourceKeyIsMissing = errors.New("One of id or alias must be assigned") - func dataSourceDockerPluginRead(d *schema.ResourceData, meta interface{}) error { - key := getDataSourcePluginKey(d) - if key == "" { - return errDataSourceKeyIsMissing + key, err := getDataSourcePluginKey(d) + if err != nil { + return err } client := meta.(*ProviderConfig).DockerClient ctx := context.Background() From 5f925d659961469a11fa1c16c474475ad15f17a9 Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Wed, 30 Dec 2020 08:50:39 +0900 Subject: [PATCH 20/36] fix: replace docker_plugin.disabled to enabled --- docker/data_source_docker_plugin.go | 2 +- docker/resource_docker_plugin.go | 3 ++- docker/resource_docker_plugin_funcs.go | 22 +++++++++++----------- docker/resource_docker_plugin_test.go | 10 +++++----- website/docs/r/plugin.html.markdown | 6 +++--- 5 files changed, 22 insertions(+), 21 deletions(-) diff --git a/docker/data_source_docker_plugin.go b/docker/data_source_docker_plugin.go index a683d7e49..828ab12e7 100644 --- a/docker/data_source_docker_plugin.go +++ b/docker/data_source_docker_plugin.go @@ -28,7 +28,7 @@ func dataSourceDockerPlugin() *schema.Resource { Description: "Docker Plugin Reference.", Optional: true, }, - "disabled": { + "enabled": { Type: schema.TypeBool, Optional: true, }, diff --git a/docker/resource_docker_plugin.go b/docker/resource_docker_plugin.go index 8e1689b44..92098a652 100644 --- a/docker/resource_docker_plugin.go +++ b/docker/resource_docker_plugin.go @@ -27,9 +27,10 @@ func resourceDockerPlugin() *schema.Resource { ForceNew: true, Description: "Docker Plugin alias.", }, - "disabled": { + "enabled": { Type: schema.TypeBool, Optional: true, + Default: true, }, "grant_all_permissions": { Type: schema.TypeBool, diff --git a/docker/resource_docker_plugin_funcs.go b/docker/resource_docker_plugin_funcs.go index d29f31781..edcfab367 100644 --- a/docker/resource_docker_plugin_funcs.go +++ b/docker/resource_docker_plugin_funcs.go @@ -32,7 +32,7 @@ func resourceDockerPluginCreate(d *schema.ResourceData, meta interface{}) error body, err := client.PluginInstall(ctx, alias, types.PluginInstallOptions{ RemoteRef: pluginRef, AcceptAllPermissions: d.Get("grant_all_permissions").(bool), - Disabled: d.Get("disabled").(bool), + Disabled: !d.Get("enabled").(bool), Args: getDockerPluginArgs(d.Get("args")), }) if err != nil { @@ -55,7 +55,7 @@ func setDockerPlugin(d *schema.ResourceData, plugin *types.Plugin) { d.SetId(plugin.ID) d.Set("plugin_reference", plugin.PluginReference) d.Set("alias", plugin.Name) - d.Set("disabled", !plugin.Enabled) + d.Set("enabled", plugin.Enabled) // TODO support other settings // https://docs.docker.com/engine/reference/commandline/plugin_set/#extended-description // source of mounts .Settings.Mounts @@ -116,15 +116,8 @@ func resourceDockerPluginUpdate(d *schema.ResourceData, meta interface{}) error ctx := context.Background() pluginID := d.Id() skipArgs := false - if d.HasChange("disabled") { - if d.Get("disabled").(bool) { - log.Printf("[DEBUG] Disable a Docker plugin " + pluginID) - if err := client.PluginDisable(ctx, pluginID, types.PluginDisableOptions{ - Force: d.Get("force_disable").(bool), - }); err != nil { - return fmt.Errorf("disable the Docker plugin "+pluginID+": %w", err) - } - } else { + if d.HasChange("enabled") { + if d.Get("enabled").(bool) { if err := setPluginArgs(ctx, d, client, false); err != nil { return err } @@ -135,6 +128,13 @@ func resourceDockerPluginUpdate(d *schema.ResourceData, meta interface{}) error }); err != nil { return fmt.Errorf("enable the Docker plugin "+pluginID+": %w", err) } + } else { + log.Printf("[DEBUG] Disable a Docker plugin " + pluginID) + if err := client.PluginDisable(ctx, pluginID, types.PluginDisableOptions{ + Force: d.Get("force_disable").(bool), + }); err != nil { + return fmt.Errorf("disable the Docker plugin "+pluginID+": %w", err) + } } } if !skipArgs { diff --git a/docker/resource_docker_plugin_test.go b/docker/resource_docker_plugin_test.go index d34f8cee1..006ec7a67 100644 --- a/docker/resource_docker_plugin_test.go +++ b/docker/resource_docker_plugin_test.go @@ -18,7 +18,7 @@ func TestAccDockerPlugin_basic(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "plugin_reference", "docker.io/tiborvass/sample-volume-plugin:latest"), resource.TestCheckResourceAttr(resourceName, "alias", "tiborvass/sample-volume-plugin:latest"), - resource.TestCheckResourceAttr(resourceName, "disabled", "false"), + resource.TestCheckResourceAttr(resourceName, "enabled", "true"), ), }, { @@ -27,7 +27,7 @@ func TestAccDockerPlugin_basic(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "plugin_reference", "docker.io/tiborvass/sample-volume-plugin:latest"), resource.TestCheckResourceAttr(resourceName, "alias", "sample:latest"), - resource.TestCheckResourceAttr(resourceName, "disabled", "false"), + resource.TestCheckResourceAttr(resourceName, "enabled", "true"), ), }, { @@ -36,7 +36,7 @@ func TestAccDockerPlugin_basic(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "plugin_reference", "docker.io/tiborvass/sample-volume-plugin:latest"), resource.TestCheckResourceAttr(resourceName, "alias", "sample:latest"), - resource.TestCheckResourceAttr(resourceName, "disabled", "false"), + resource.TestCheckResourceAttr(resourceName, "enabled", "true"), resource.TestCheckResourceAttr(resourceName, "grant_all_permissions", "true"), resource.TestCheckResourceAttr(resourceName, "disable_when_set", "true"), resource.TestCheckResourceAttr(resourceName, "force_destroy", "true"), @@ -49,7 +49,7 @@ func TestAccDockerPlugin_basic(t *testing.T) { Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "plugin_reference", "docker.io/tiborvass/sample-volume-plugin:latest"), resource.TestCheckResourceAttr(resourceName, "alias", "sample:latest"), - resource.TestCheckResourceAttr(resourceName, "disabled", "true"), + resource.TestCheckResourceAttr(resourceName, "enabled", "false"), resource.TestCheckResourceAttr(resourceName, "grant_all_permissions", "true"), resource.TestCheckResourceAttr(resourceName, "disable_when_set", "true"), resource.TestCheckResourceAttr(resourceName, "force_destroy", "true"), @@ -95,7 +95,7 @@ const testAccDockerPluginDisabled = ` resource "docker_plugin" "test" { plugin_reference = "docker.io/tiborvass/sample-volume-plugin:latest" alias = "sample:latest" - disabled = true + enabled = false grant_all_permissions = true disable_when_set = true force_destroy = true diff --git a/website/docs/r/plugin.html.markdown b/website/docs/r/plugin.html.markdown index b541be8f2..c41dd2268 100644 --- a/website/docs/r/plugin.html.markdown +++ b/website/docs/r/plugin.html.markdown @@ -22,7 +22,7 @@ resource "docker_plugin" "sample-volume-plugin" { resource "docker_plugin" "sample-volume-plugin" { plugin_reference = "docker.io/tiborvass/sample-volume-plugin:latest" alias = "sample-volume-plugin:latest" - disabled = true + enabled = false grant_all_permissions = true disable_when_set = true force_destroy = true @@ -40,7 +40,7 @@ The following arguments are supported: * `plugin_reference` - (Required, string, Forces new resource) The plugin reference. The registry path and image tag should not be omitted. See [plugin_references, alias](#plugin-references-alias-1) below for details. * `alias` - (Optional, string, Forces new resource) The alias of the Docker plugin. The image tag should not be omitted. See [plugin_references, alias](#plugin-references-alias-1) below for details. -* `disabled` - (Optional, boolean) If true, the plugin is disabled. +* `enabled` - (Optional, boolean) If true, the plugin is enabled. The default value is `true`. * `grant_all_permissions` - (Optional, boolean) If true, grant all permissions necessary to run the plugin. * `args` - (Optional, set of string). Currently, only environment variables are supported. * `disable_when_set` - (Optional, boolean) If true, the plugin becomes disabled temporarily when the plugin setting is updated. See [disable_when_set](#disable-when-set-1) below for details. @@ -78,7 +78,7 @@ Terraform will perform the following actions: # docker_plugin.sample-volume-plugin must be replaced -/+ resource "docker_plugin" "sample-volume-plugin" { ~ alias = "sample:latest" -> "sample" # forces replacement - - disabled = false -> null + - enabled = false -> null ~ env = [ - "DEBUG=0", ] -> (known after apply) From 54629ccca71de62d7e1e5b0e5a7fbeceed086039 Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Wed, 30 Dec 2020 09:16:31 +0900 Subject: [PATCH 21/36] test: add test of data.docker_plugin --- docker/data_source_docker_plugin_test.go | 39 ++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 docker/data_source_docker_plugin_test.go diff --git a/docker/data_source_docker_plugin_test.go b/docker/data_source_docker_plugin_test.go new file mode 100644 index 000000000..f9a13d1a8 --- /dev/null +++ b/docker/data_source_docker_plugin_test.go @@ -0,0 +1,39 @@ +package docker + +import ( + "os/exec" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccDockerPluginDataSource_basic(t *testing.T) { + pluginName := "tiborvass/sample-volume-plugin" + // This fails if the plugin is already installed. + if err := exec.Command("docker", "plugin", "install", pluginName).Run(); err != nil { + t.Fatal(err) + } + defer func() { + if err := exec.Command("docker", "plugin", "rm", "-f", pluginName).Run(); err != nil { + t.Logf("failed to remove the Docker plugin %s: %v", pluginName, err) + } + }() + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDockerPluginDataSourceTest, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.docker_plugin.test", "plugin_reference", "docker.io/tiborvass/sample-volume-plugin:latest"), + ), + }, + }, + }) +} + +const testAccDockerPluginDataSourceTest = ` +data "docker_plugin" "test" { + alias = "tiborvass/sample-volume-plugin:latest" +} +` From afcb76c6409c3c2aa78ba25f32d431a38c99600a Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Wed, 30 Dec 2020 09:24:30 +0900 Subject: [PATCH 22/36] test: test docker_plugin.grant_all_permissions --- docker/resource_docker_plugin_test.go | 31 +++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/docker/resource_docker_plugin_test.go b/docker/resource_docker_plugin_test.go index 006ec7a67..1e8daba4c 100644 --- a/docker/resource_docker_plugin_test.go +++ b/docker/resource_docker_plugin_test.go @@ -65,6 +65,29 @@ func TestAccDockerPlugin_basic(t *testing.T) { }) } +func TestAccDockerPlugin_grantAllPermissions(t *testing.T) { + const resourceName = "docker_plugin.test" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + ResourceName: resourceName, + Config: testAccDockerPluginGrantAllPermissions, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "plugin_reference", "docker.io/vieux/sshfs:latest"), + resource.TestCheckResourceAttr(resourceName, "alias", "vieux/sshfs:latest"), + resource.TestCheckResourceAttr(resourceName, "grant_all_permissions", "true"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + }, + }, + }) +} + const testAccDockerPluginMinimum = ` resource "docker_plugin" "test" { plugin_reference = "docker.io/tiborvass/sample-volume-plugin:latest" @@ -105,3 +128,11 @@ resource "docker_plugin" "test" { "DEBUG=1" ] }` + +// To install this plugin, it is required to grant required permissions. +const testAccDockerPluginGrantAllPermissions = ` +resource "docker_plugin" "test" { + plugin_reference = "docker.io/vieux/sshfs:latest" + grant_all_permissions = true + force_destroy = true +}` From 3ec71c5bc968af149cd565aba0e145d14234eaa7 Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Wed, 30 Dec 2020 10:05:02 +0900 Subject: [PATCH 23/36] refactor: remove skipArgs to make code clear https://github.com/kreuzwerker/terraform-provider-docker/pull/35#discussion_r549844670 --- docker/resource_docker_plugin_funcs.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docker/resource_docker_plugin_funcs.go b/docker/resource_docker_plugin_funcs.go index edcfab367..657350a48 100644 --- a/docker/resource_docker_plugin_funcs.go +++ b/docker/resource_docker_plugin_funcs.go @@ -115,13 +115,11 @@ func resourceDockerPluginUpdate(d *schema.ResourceData, meta interface{}) error client := meta.(*ProviderConfig).DockerClient ctx := context.Background() pluginID := d.Id() - skipArgs := false if d.HasChange("enabled") { if d.Get("enabled").(bool) { if err := setPluginArgs(ctx, d, client, false); err != nil { return err } - skipArgs = true log.Printf("[DEBUG] Enable a Docker plugin " + pluginID) if err := client.PluginEnable(ctx, pluginID, types.PluginEnableOptions{ Timeout: d.Get("enable_timeout").(int), @@ -137,7 +135,7 @@ func resourceDockerPluginUpdate(d *schema.ResourceData, meta interface{}) error } } } - if !skipArgs { + if !(d.HasChange("enabled") && d.Get("enabled").(bool)) { plugin, _, err := client.PluginInspectWithRaw(ctx, pluginID) if err != nil { return fmt.Errorf("inspect a Docker plugin "+pluginID+": %w", err) From c8b9c7fc8c21c4a40def5e3c0d21ede4b0d87db3 Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Wed, 30 Dec 2020 10:47:16 +0900 Subject: [PATCH 24/36] fix: make attributes `Computed: true` --- docker/data_source_docker_plugin.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/data_source_docker_plugin.go b/docker/data_source_docker_plugin.go index 828ab12e7..12a9835d7 100644 --- a/docker/data_source_docker_plugin.go +++ b/docker/data_source_docker_plugin.go @@ -26,20 +26,20 @@ func dataSourceDockerPlugin() *schema.Resource { "plugin_reference": { Type: schema.TypeString, Description: "Docker Plugin Reference.", - Optional: true, + Computed: true, }, "enabled": { Type: schema.TypeBool, - Optional: true, + Computed: true, }, "grant_all_permissions": { Type: schema.TypeBool, - Optional: true, + Computed: true, Description: "If true, grant all permissions necessary to run the plugin", }, "args": { Type: schema.TypeSet, - Optional: true, + Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, }, }, From 967f2ba3f82d1e185a7f329d0a294d1801604125 Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Wed, 30 Dec 2020 14:03:52 +0900 Subject: [PATCH 25/36] fix: replace args to env --- docker/data_source_docker_plugin.go | 2 +- docker/resource_docker_plugin.go | 2 +- docker/resource_docker_plugin_funcs.go | 23 +++++++++++++++-------- docker/resource_docker_plugin_test.go | 4 ++-- website/docs/r/plugin.html.markdown | 6 +++--- 5 files changed, 22 insertions(+), 15 deletions(-) diff --git a/docker/data_source_docker_plugin.go b/docker/data_source_docker_plugin.go index 12a9835d7..b7d2e9740 100644 --- a/docker/data_source_docker_plugin.go +++ b/docker/data_source_docker_plugin.go @@ -37,7 +37,7 @@ func dataSourceDockerPlugin() *schema.Resource { Computed: true, Description: "If true, grant all permissions necessary to run the plugin", }, - "args": { + "env": { Type: schema.TypeSet, Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, diff --git a/docker/resource_docker_plugin.go b/docker/resource_docker_plugin.go index 92098a652..64e79f171 100644 --- a/docker/resource_docker_plugin.go +++ b/docker/resource_docker_plugin.go @@ -37,7 +37,7 @@ func resourceDockerPlugin() *schema.Resource { Optional: true, Description: "If true, grant all permissions necessary to run the plugin", }, - "args": { + "env": { Type: schema.TypeSet, Optional: true, Computed: true, diff --git a/docker/resource_docker_plugin_funcs.go b/docker/resource_docker_plugin_funcs.go index 657350a48..9cfdb1f42 100644 --- a/docker/resource_docker_plugin_funcs.go +++ b/docker/resource_docker_plugin_funcs.go @@ -11,16 +11,16 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/helper/schema" ) -func getDockerPluginArgs(src interface{}) []string { +func getDockerPluginEnv(src interface{}) []string { if src == nil { return nil } b := src.(*schema.Set) - args := make([]string, b.Len()) + envs := make([]string, b.Len()) for i, a := range b.List() { - args[i] = a.(string) + envs[i] = a.(string) } - return args + return envs } func resourceDockerPluginCreate(d *schema.ResourceData, meta interface{}) error { @@ -33,7 +33,8 @@ func resourceDockerPluginCreate(d *schema.ResourceData, meta interface{}) error RemoteRef: pluginRef, AcceptAllPermissions: d.Get("grant_all_permissions").(bool), Disabled: !d.Get("enabled").(bool), - Args: getDockerPluginArgs(d.Get("args")), + // TODO support other settings + Args: getDockerPluginEnv(d.Get("env")), }) if err != nil { return fmt.Errorf("install a Docker plugin "+pluginRef+": %w", err) @@ -61,7 +62,7 @@ func setDockerPlugin(d *schema.ResourceData, plugin *types.Plugin) { // source of mounts .Settings.Mounts // path of devices .Settings.Devices // args .Settings.Args - d.Set("args", plugin.Settings.Env) + d.Set("env", plugin.Settings.Env) } func resourceDockerPluginRead(d *schema.ResourceData, meta interface{}) error { @@ -79,7 +80,7 @@ func resourceDockerPluginRead(d *schema.ResourceData, meta interface{}) error { } func setPluginArgs(ctx context.Context, d *schema.ResourceData, client *client.Client, disabled bool) (gErr error) { - if !d.HasChange("args") { + if !d.HasChange("env") { return nil } pluginID := d.Id() @@ -105,7 +106,13 @@ func setPluginArgs(ctx context.Context, d *schema.ResourceData, client *client.C }() } log.Printf("[DEBUG] Update settings of a Docker plugin " + pluginID) - if err := client.PluginSet(ctx, pluginID, getDockerPluginArgs(d.Get("args"))); err != nil { + // currently, only environment variables are supported. + // TODO support other args + // https://docs.docker.com/engine/reference/commandline/plugin_set/#extended-description + // source of mounts .Settings.Mounts + // path of devices .Settings.Devices + // args .Settings.Args + if err := client.PluginSet(ctx, pluginID, getDockerPluginEnv(d.Get("env"))); err != nil { return fmt.Errorf("modifiy settings for the Docker plugin "+pluginID+": %w", err) } return nil diff --git a/docker/resource_docker_plugin_test.go b/docker/resource_docker_plugin_test.go index 1e8daba4c..723f65cac 100644 --- a/docker/resource_docker_plugin_test.go +++ b/docker/resource_docker_plugin_test.go @@ -109,7 +109,7 @@ resource "docker_plugin" "test" { disable_when_set = true force_destroy = true enable_timeout = 60 - args = [ + env = [ "DEBUG=1" ] }` @@ -124,7 +124,7 @@ resource "docker_plugin" "test" { force_destroy = true force_disable = true enable_timeout = 60 - args = [ + env = [ "DEBUG=1" ] }` diff --git a/website/docs/r/plugin.html.markdown b/website/docs/r/plugin.html.markdown index c41dd2268..1d3410234 100644 --- a/website/docs/r/plugin.html.markdown +++ b/website/docs/r/plugin.html.markdown @@ -28,7 +28,7 @@ resource "docker_plugin" "sample-volume-plugin" { force_destroy = true enable_timeout = 60 force_disable = true - args = [ + env = [ "DEBUG=1" ] } @@ -42,7 +42,7 @@ The following arguments are supported: * `alias` - (Optional, string, Forces new resource) The alias of the Docker plugin. The image tag should not be omitted. See [plugin_references, alias](#plugin-references-alias-1) below for details. * `enabled` - (Optional, boolean) If true, the plugin is enabled. The default value is `true`. * `grant_all_permissions` - (Optional, boolean) If true, grant all permissions necessary to run the plugin. -* `args` - (Optional, set of string). Currently, only environment variables are supported. +* `env` - (Optional, set of string). The environment variables. * `disable_when_set` - (Optional, boolean) If true, the plugin becomes disabled temporarily when the plugin setting is updated. See [disable_when_set](#disable-when-set-1) below for details. * `force_destroy` - (Optional, boolean) If true, the plugin is removed forcibly when the plugin is removed. * `enable_timeout` - (Optional, int) HTTP client timeout to enable the plugin. @@ -100,7 +100,7 @@ $ docker plugin set tiborvass/sample-volume-plugin:latest DEBUG=2 Error response from daemon: cannot set on an active plugin, disable plugin before setting ``` -If `disable_when_set` is true, then the plugin becomes disabled temporarily before the attribute `args` is updated and after `args` is updated the plugin becomes enabled again. +If `disable_when_set` is true, then the plugin becomes disabled temporarily before the attribute `env` is updated and after `env` is updated the plugin becomes enabled again. ## Attributes Reference From 03b2187d0222d35f701770ca58809282ad561b2d Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Thu, 31 Dec 2020 08:44:14 +0900 Subject: [PATCH 26/36] feat: add an attribute "docker_plugin.grant_permissions" https://github.com/kreuzwerker/terraform-provider-docker/pull/35/files#r549843380 --- docker/resource_docker_plugin.go | 30 +++++++++++++++++-- docker/resource_docker_plugin_funcs.go | 40 ++++++++++++++++++++++++-- 2 files changed, 65 insertions(+), 5 deletions(-) diff --git a/docker/resource_docker_plugin.go b/docker/resource_docker_plugin.go index 64e79f171..8a506d07e 100644 --- a/docker/resource_docker_plugin.go +++ b/docker/resource_docker_plugin.go @@ -33,9 +33,33 @@ func resourceDockerPlugin() *schema.Resource { Default: true, }, "grant_all_permissions": { - Type: schema.TypeBool, - Optional: true, - Description: "If true, grant all permissions necessary to run the plugin", + Type: schema.TypeBool, + Optional: true, + Description: "If true, grant all permissions necessary to run the plugin", + ConflictsWith: []string{"grant_permissions"}, + }, + "grant_permissions": { + Type: schema.TypeSet, + Optional: true, + ConflictsWith: []string{"grant_all_permissions"}, + Set: func(v interface{}) int { + return schema.HashString(v.(map[string]interface{})["name"].(string)) + }, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + "value": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, }, "env": { Type: schema.TypeSet, diff --git a/docker/resource_docker_plugin_funcs.go b/docker/resource_docker_plugin_funcs.go index 9cfdb1f42..2dfce1e79 100644 --- a/docker/resource_docker_plugin_funcs.go +++ b/docker/resource_docker_plugin_funcs.go @@ -5,6 +5,7 @@ import ( "fmt" "io/ioutil" "log" + "strings" "github.com/docker/docker/api/types" "github.com/docker/docker/client" @@ -23,19 +24,54 @@ func getDockerPluginEnv(src interface{}) []string { return envs } +func getDockerPluginGrantPermissions(src interface{}) func(types.PluginPrivileges) (bool, error) { + grantPermissionsSet := src.(*schema.Set) + grantPermissions := make(map[string]map[string]struct{}, grantPermissionsSet.Len()) + for _, b := range grantPermissionsSet.List() { + c := b.(map[string]interface{}) + name := c["name"].(string) + values := c["value"].(*schema.Set) + grantPermission := make(map[string]struct{}, values.Len()) + for _, value := range values.List() { + grantPermission[value.(string)] = struct{}{} + } + grantPermissions[name] = grantPermission + } + return func(privileges types.PluginPrivileges) (bool, error) { + for _, privilege := range privileges { + grantPermission, nameOK := grantPermissions[privilege.Name] + if !nameOK { + log.Print("[DEBUG] to install the plugin, the following permissions are required: " + privilege.Name + " [" + strings.Join(privilege.Value, ", ") + "]") + return false, nil + } + for _, value := range privilege.Value { + if _, ok := grantPermission[value]; !ok { + log.Print("[DEBUG] to install the plugin, the following permissions are required: " + privilege.Name + " [" + strings.Join(privilege.Value, ", ") + "]") + return false, nil + } + } + } + return true, nil + } +} + func resourceDockerPluginCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ProviderConfig).DockerClient ctx := context.Background() pluginRef := d.Get("plugin_reference").(string) alias := d.Get("alias").(string) log.Printf("[DEBUG] Install a Docker plugin " + pluginRef) - body, err := client.PluginInstall(ctx, alias, types.PluginInstallOptions{ + opts := types.PluginInstallOptions{ RemoteRef: pluginRef, AcceptAllPermissions: d.Get("grant_all_permissions").(bool), Disabled: !d.Get("enabled").(bool), // TODO support other settings Args: getDockerPluginEnv(d.Get("env")), - }) + } + if v, ok := d.GetOk("grant_permissions"); ok { + opts.AcceptPermissionsFunc = getDockerPluginGrantPermissions(v) + } + body, err := client.PluginInstall(ctx, alias, opts) if err != nil { return fmt.Errorf("install a Docker plugin "+pluginRef+": %w", err) } From 735df8a68885c9938e11eedf5e79a5d6c499373b Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Thu, 31 Dec 2020 09:02:35 +0900 Subject: [PATCH 27/36] docs: add document of plugin.grant_permissions --- website/docs/r/plugin.html.markdown | 45 ++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/website/docs/r/plugin.html.markdown b/website/docs/r/plugin.html.markdown index 1d3410234..5fc6cd35b 100644 --- a/website/docs/r/plugin.html.markdown +++ b/website/docs/r/plugin.html.markdown @@ -41,7 +41,8 @@ The following arguments are supported: * `plugin_reference` - (Required, string, Forces new resource) The plugin reference. The registry path and image tag should not be omitted. See [plugin_references, alias](#plugin-references-alias-1) below for details. * `alias` - (Optional, string, Forces new resource) The alias of the Docker plugin. The image tag should not be omitted. See [plugin_references, alias](#plugin-references-alias-1) below for details. * `enabled` - (Optional, boolean) If true, the plugin is enabled. The default value is `true`. -* `grant_all_permissions` - (Optional, boolean) If true, grant all permissions necessary to run the plugin. +* `grant_all_permissions` - (Optional, boolean) If true, grant all permissions necessary to run the plugin. This attribute conflicts with `grant_permissions`. +* `grant_permissions` - (Optional, block) grant permissions necessary to run the plugin. This attribute conflicts with `grant_all_permissions`. See [grant_permissions](#grant-permissions-1) below for details. * `env` - (Optional, set of string). The environment variables. * `disable_when_set` - (Optional, boolean) If true, the plugin becomes disabled temporarily when the plugin setting is updated. See [disable_when_set](#disable-when-set-1) below for details. * `force_destroy` - (Optional, boolean) If true, the plugin is removed forcibly when the plugin is removed. @@ -89,6 +90,48 @@ Terraform will perform the following actions: Plan: 1 to add, 0 to change, 1 to destroy. ``` + +## grant_permissions + +`grant_permissions` is a block within the configuration that can be repeated to grant permissions to install the plugin. Each `grant_permissions` block supports +the following: + +* `name` - (Required, string) +* `value` - (Required, list of string) + +Example: + +```hcl +resource "docker_plugin" "sshfs" { + plugin_reference = "docker.io/vieux/sshfs:latest" + grant_permissions { + name = "network" + value = [ + "host" + ] + } + grant_permissions { + name = "mount" + value = [ + "", + "/var/lib/docker/plugins/" + ] + } + grant_permissions { + name = "device" + value = [ + "/dev/fuse" + ] + } + grant_permissions { + name = "capabilities" + value = [ + "CAP_SYS_ADMIN" + ] + } +} +``` + ## disable_when_set From 0d3149860a2c004003b0e3b3eb9d6bf9faee6cce Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Thu, 31 Dec 2020 09:07:59 +0900 Subject: [PATCH 28/36] test: test docker_plugin.grant_permissions --- docker/resource_docker_plugin_test.go | 54 +++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/docker/resource_docker_plugin_test.go b/docker/resource_docker_plugin_test.go index 723f65cac..352e64e29 100644 --- a/docker/resource_docker_plugin_test.go +++ b/docker/resource_docker_plugin_test.go @@ -88,6 +88,28 @@ func TestAccDockerPlugin_grantAllPermissions(t *testing.T) { }) } +func TestAccDockerPlugin_grantPermissions(t *testing.T) { + const resourceName = "docker_plugin.test" + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + ResourceName: resourceName, + Config: testAccDockerPluginGrantPermissions, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "plugin_reference", "docker.io/vieux/sshfs:latest"), + resource.TestCheckResourceAttr(resourceName, "alias", "vieux/sshfs:latest"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + }, + }, + }) +} + const testAccDockerPluginMinimum = ` resource "docker_plugin" "test" { plugin_reference = "docker.io/tiborvass/sample-volume-plugin:latest" @@ -136,3 +158,35 @@ resource "docker_plugin" "test" { grant_all_permissions = true force_destroy = true }` + +// To install this plugin, it is required to grant required permissions. +const testAccDockerPluginGrantPermissions = ` +resource "docker_plugin" "test" { + plugin_reference = "docker.io/vieux/sshfs:latest" + force_destroy = true + grant_permissions { + name = "network" + value = [ + "host" + ] + } + grant_permissions { + name = "mount" + value = [ + "", + "/var/lib/docker/plugins/" + ] + } + grant_permissions { + name = "device" + value = [ + "/dev/fuse" + ] + } + grant_permissions { + name = "capabilities" + value = [ + "CAP_SYS_ADMIN" + ] + } +}` From bc2a157aeba47ef7ea306850debe76a124b8248e Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Thu, 31 Dec 2020 09:43:04 +0900 Subject: [PATCH 29/36] test: test getDockerPluginEnv --- docker/resource_docker_plugin_test.go | 30 +++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/docker/resource_docker_plugin_test.go b/docker/resource_docker_plugin_test.go index 352e64e29..da969eaef 100644 --- a/docker/resource_docker_plugin_test.go +++ b/docker/resource_docker_plugin_test.go @@ -1,11 +1,41 @@ package docker import ( + "reflect" "testing" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" ) +func Test_getDockerPluginEnv(t *testing.T) { + t.Parallel() + data := []struct { + title string + src interface{} + exp []string + }{ + { + title: "nil", + }, + { + title: "basic", + src: schema.NewSet(schema.HashString, []interface{}{"DEBUG=1"}), + exp: []string{"DEBUG=1"}, + }, + } + for _, d := range data { + d := d + t.Run(d.title, func(t *testing.T) { + t.Parallel() + envs := getDockerPluginEnv(d.src) + if !reflect.DeepEqual(d.exp, envs) { + t.Fatalf("want %v, got %v", d.exp, envs) + } + }) + } +} + func TestAccDockerPlugin_basic(t *testing.T) { const resourceName = "docker_plugin.test" resource.Test(t, resource.TestCase{ From 3a8e2b0fe887fd54638d112d6f535e5ac48d21a0 Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Thu, 31 Dec 2020 10:21:48 +0900 Subject: [PATCH 30/36] test: test getDockerPluginGrantPermissions --- docker/resource_docker_plugin.go | 4 +- docker/resource_docker_plugin_funcs.go | 9 ++- docker/resource_docker_plugin_test.go | 105 +++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 6 deletions(-) diff --git a/docker/resource_docker_plugin.go b/docker/resource_docker_plugin.go index 8a506d07e..542bc302b 100644 --- a/docker/resource_docker_plugin.go +++ b/docker/resource_docker_plugin.go @@ -42,9 +42,7 @@ func resourceDockerPlugin() *schema.Resource { Type: schema.TypeSet, Optional: true, ConflictsWith: []string{"grant_all_permissions"}, - Set: func(v interface{}) int { - return schema.HashString(v.(map[string]interface{})["name"].(string)) - }, + Set: dockerPluginGrantPermissionsSetFunc, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "name": { diff --git a/docker/resource_docker_plugin_funcs.go b/docker/resource_docker_plugin_funcs.go index 2dfce1e79..b302b8ec8 100644 --- a/docker/resource_docker_plugin_funcs.go +++ b/docker/resource_docker_plugin_funcs.go @@ -5,7 +5,6 @@ import ( "fmt" "io/ioutil" "log" - "strings" "github.com/docker/docker/api/types" "github.com/docker/docker/client" @@ -24,6 +23,10 @@ func getDockerPluginEnv(src interface{}) []string { return envs } +func dockerPluginGrantPermissionsSetFunc(v interface{}) int { + return schema.HashString(v.(map[string]interface{})["name"].(string)) +} + func getDockerPluginGrantPermissions(src interface{}) func(types.PluginPrivileges) (bool, error) { grantPermissionsSet := src.(*schema.Set) grantPermissions := make(map[string]map[string]struct{}, grantPermissionsSet.Len()) @@ -41,12 +44,12 @@ func getDockerPluginGrantPermissions(src interface{}) func(types.PluginPrivilege for _, privilege := range privileges { grantPermission, nameOK := grantPermissions[privilege.Name] if !nameOK { - log.Print("[DEBUG] to install the plugin, the following permissions are required: " + privilege.Name + " [" + strings.Join(privilege.Value, ", ") + "]") + log.Print("[DEBUG] to install the plugin, the following permissions are required: " + privilege.Name) return false, nil } for _, value := range privilege.Value { if _, ok := grantPermission[value]; !ok { - log.Print("[DEBUG] to install the plugin, the following permissions are required: " + privilege.Name + " [" + strings.Join(privilege.Value, ", ") + "]") + log.Print("[DEBUG] to install the plugin, the following permissions are required: " + privilege.Name + " " + value) return false, nil } } diff --git a/docker/resource_docker_plugin_test.go b/docker/resource_docker_plugin_test.go index da969eaef..f8a8ba544 100644 --- a/docker/resource_docker_plugin_test.go +++ b/docker/resource_docker_plugin_test.go @@ -4,6 +4,7 @@ import ( "reflect" "testing" + "github.com/docker/docker/api/types" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" ) @@ -36,6 +37,110 @@ func Test_getDockerPluginEnv(t *testing.T) { } } +func Test_getDockerPluginGrantPermissions(t *testing.T) { + t.Parallel() + data := []struct { + title string + src interface{} + privileges types.PluginPrivileges + exp bool + isErr bool + }{ + { + title: "no privilege", + src: schema.NewSet(dockerPluginGrantPermissionsSetFunc, []interface{}{ + map[string]interface{}{ + "name": "network", + "value": schema.NewSet(schema.HashString, []interface{}{"host"}), + }, + }), + exp: true, + }, + { + title: "basic", + src: schema.NewSet(dockerPluginGrantPermissionsSetFunc, []interface{}{ + map[string]interface{}{ + "name": "network", + "value": schema.NewSet(schema.HashString, []interface{}{"host"}), + }, + }), + privileges: types.PluginPrivileges{ + { + Name: "network", + Value: []string{"host"}, + }, + }, + exp: true, + }, + { + title: "permission denied 1", + src: schema.NewSet(dockerPluginGrantPermissionsSetFunc, []interface{}{ + map[string]interface{}{ + "name": "network", + "value": schema.NewSet(schema.HashString, []interface{}{ + "host", + }), + }, + }), + privileges: types.PluginPrivileges{ + { + Name: "device", + Value: []string{"/dev/fuse"}, + }, + }, + exp: false, + }, + { + title: "permission denied 2", + src: schema.NewSet(dockerPluginGrantPermissionsSetFunc, []interface{}{ + map[string]interface{}{ + "name": "network", + "value": schema.NewSet(schema.HashString, []interface{}{ + "host", + }), + }, + map[string]interface{}{ + "name": "mount", + "value": schema.NewSet(schema.HashString, []interface{}{ + "/var/lib/docker/plugins/", + }), + }, + }), + privileges: types.PluginPrivileges{ + { + Name: "network", + Value: []string{"host"}, + }, + { + Name: "mount", + Value: []string{"", "/var/lib/docker/plugins/"}, + }, + }, + exp: false, + }, + } + for _, d := range data { + d := d + t.Run(d.title, func(t *testing.T) { + t.Parallel() + f := getDockerPluginGrantPermissions(d.src) + b, err := f(d.privileges) + if d.isErr { + if err == nil { + t.Fatal("error must be returned") + } + return + } + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(d.exp, b) { + t.Fatalf("want %v, got %v", d.exp, b) + } + }) + } +} + func TestAccDockerPlugin_basic(t *testing.T) { const resourceName = "docker_plugin.test" resource.Test(t, resource.TestCase{ From abec1d568c9dae0066d4a7268e74747c999c6c8f Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Tue, 5 Jan 2021 07:44:38 +0900 Subject: [PATCH 31/36] fix: remove docker_plugin.disable_when_set https://github.com/kreuzwerker/terraform-provider-docker/pull/35#discussion_r551531650 --- docker/resource_docker_plugin.go | 5 ----- docker/resource_docker_plugin_funcs.go | 2 +- docker/resource_docker_plugin_test.go | 4 ---- website/docs/r/plugin.html.markdown | 15 --------------- 4 files changed, 1 insertion(+), 25 deletions(-) diff --git a/docker/resource_docker_plugin.go b/docker/resource_docker_plugin.go index 542bc302b..4908c3848 100644 --- a/docker/resource_docker_plugin.go +++ b/docker/resource_docker_plugin.go @@ -66,11 +66,6 @@ func resourceDockerPlugin() *schema.Resource { Elem: &schema.Schema{Type: schema.TypeString}, }, - "disable_when_set": { - Type: schema.TypeBool, - Optional: true, - Description: "If true, the plugin becomes disabled temporarily when the plugin setting is updated", - }, "force_destroy": { Type: schema.TypeBool, Optional: true, diff --git a/docker/resource_docker_plugin_funcs.go b/docker/resource_docker_plugin_funcs.go index b302b8ec8..036fee9bd 100644 --- a/docker/resource_docker_plugin_funcs.go +++ b/docker/resource_docker_plugin_funcs.go @@ -186,7 +186,7 @@ func resourceDockerPluginUpdate(d *schema.ResourceData, meta interface{}) error if err != nil { return fmt.Errorf("inspect a Docker plugin "+pluginID+": %w", err) } - if err := setPluginArgs(ctx, d, client, plugin.Enabled && d.Get("disable_when_set").(bool)); err != nil { + if err := setPluginArgs(ctx, d, client, plugin.Enabled); err != nil { return err } } diff --git a/docker/resource_docker_plugin_test.go b/docker/resource_docker_plugin_test.go index f8a8ba544..6afb5c616 100644 --- a/docker/resource_docker_plugin_test.go +++ b/docker/resource_docker_plugin_test.go @@ -173,7 +173,6 @@ func TestAccDockerPlugin_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "alias", "sample:latest"), resource.TestCheckResourceAttr(resourceName, "enabled", "true"), resource.TestCheckResourceAttr(resourceName, "grant_all_permissions", "true"), - resource.TestCheckResourceAttr(resourceName, "disable_when_set", "true"), resource.TestCheckResourceAttr(resourceName, "force_destroy", "true"), resource.TestCheckResourceAttr(resourceName, "enable_timeout", "60"), ), @@ -186,7 +185,6 @@ func TestAccDockerPlugin_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "alias", "sample:latest"), resource.TestCheckResourceAttr(resourceName, "enabled", "false"), resource.TestCheckResourceAttr(resourceName, "grant_all_permissions", "true"), - resource.TestCheckResourceAttr(resourceName, "disable_when_set", "true"), resource.TestCheckResourceAttr(resourceName, "force_destroy", "true"), resource.TestCheckResourceAttr(resourceName, "enable_timeout", "60"), resource.TestCheckResourceAttr(resourceName, "force_disable", "true"), @@ -263,7 +261,6 @@ resource "docker_plugin" "test" { plugin_reference = "docker.io/tiborvass/sample-volume-plugin:latest" alias = "sample:latest" grant_all_permissions = true - disable_when_set = true force_destroy = true enable_timeout = 60 env = [ @@ -277,7 +274,6 @@ resource "docker_plugin" "test" { alias = "sample:latest" enabled = false grant_all_permissions = true - disable_when_set = true force_destroy = true force_disable = true enable_timeout = 60 diff --git a/website/docs/r/plugin.html.markdown b/website/docs/r/plugin.html.markdown index 5fc6cd35b..14b2df150 100644 --- a/website/docs/r/plugin.html.markdown +++ b/website/docs/r/plugin.html.markdown @@ -24,7 +24,6 @@ resource "docker_plugin" "sample-volume-plugin" { alias = "sample-volume-plugin:latest" enabled = false grant_all_permissions = true - disable_when_set = true force_destroy = true enable_timeout = 60 force_disable = true @@ -44,7 +43,6 @@ The following arguments are supported: * `grant_all_permissions` - (Optional, boolean) If true, grant all permissions necessary to run the plugin. This attribute conflicts with `grant_permissions`. * `grant_permissions` - (Optional, block) grant permissions necessary to run the plugin. This attribute conflicts with `grant_all_permissions`. See [grant_permissions](#grant-permissions-1) below for details. * `env` - (Optional, set of string). The environment variables. -* `disable_when_set` - (Optional, boolean) If true, the plugin becomes disabled temporarily when the plugin setting is updated. See [disable_when_set](#disable-when-set-1) below for details. * `force_destroy` - (Optional, boolean) If true, the plugin is removed forcibly when the plugin is removed. * `enable_timeout` - (Optional, int) HTTP client timeout to enable the plugin. * `force_disable` - (Optional, boolean) If true, then the plugin is disabled forcibly when the plugin is disabled. @@ -132,19 +130,6 @@ resource "docker_plugin" "sshfs" { } ``` - -## disable_when_set - -To update the plugin settings, the plugin must be disabled. -Otherwise, it failed to update the plugin settings as the following. - -```sh -$ docker plugin set tiborvass/sample-volume-plugin:latest DEBUG=2 -Error response from daemon: cannot set on an active plugin, disable plugin before setting -``` - -If `disable_when_set` is true, then the plugin becomes disabled temporarily before the attribute `env` is updated and after `env` is updated the plugin becomes enabled again. - ## Attributes Reference ## Import From 17d8fc6651c17640a7e259885833ecc565678682 Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Tue, 5 Jan 2021 09:55:30 +0900 Subject: [PATCH 32/36] refactor: refactor resourceDockerPluginUpdate --- docker/resource_docker_plugin_funcs.go | 114 ++++++++++++++----------- 1 file changed, 65 insertions(+), 49 deletions(-) diff --git a/docker/resource_docker_plugin_funcs.go b/docker/resource_docker_plugin_funcs.go index 036fee9bd..8c7ff762e 100644 --- a/docker/resource_docker_plugin_funcs.go +++ b/docker/resource_docker_plugin_funcs.go @@ -118,32 +118,30 @@ func resourceDockerPluginRead(d *schema.ResourceData, meta interface{}) error { return nil } -func setPluginArgs(ctx context.Context, d *schema.ResourceData, client *client.Client, disabled bool) (gErr error) { - if !d.HasChange("env") { - return nil +func disablePlugin(ctx context.Context, d *schema.ResourceData, cl *client.Client) error { + pluginID := d.Id() + log.Printf("[DEBUG] Disable a Docker plugin " + pluginID) + if err := cl.PluginDisable(ctx, pluginID, types.PluginDisableOptions{ + Force: d.Get("force_disable").(bool), + }); err != nil { + return fmt.Errorf("disable the Docker plugin "+pluginID+": %w", err) } + return nil +} + +func enablePlugin(ctx context.Context, d *schema.ResourceData, cl *client.Client) error { pluginID := d.Id() - if disabled { - // temporarily disable the plugin before updating the plugin settings - log.Printf("[DEBUG] Disable a Docker plugin " + pluginID + " temporarily before updating teh pugin settings") - if err := client.PluginDisable(ctx, pluginID, types.PluginDisableOptions{ - Force: d.Get("force_disable").(bool), - }); err != nil { - return fmt.Errorf("disable the Docker plugin "+pluginID+": %w", err) - } - defer func() { - log.Print("[DEBUG] Enable a Docker plugin " + pluginID) - if err := client.PluginEnable(ctx, pluginID, types.PluginEnableOptions{ - Timeout: d.Get("enable_timeout").(int), - }); err != nil { - if gErr == nil { - gErr = fmt.Errorf("enable the Docker plugin "+pluginID+": %w", err) - return - } - log.Printf("[ERROR] enable the Docker plugin "+pluginID+": %w", err) - } - }() + log.Print("[DEBUG] Enable a Docker plugin " + pluginID) + if err := cl.PluginEnable(ctx, pluginID, types.PluginEnableOptions{ + Timeout: d.Get("enable_timeout").(int), + }); err != nil { + return fmt.Errorf("enable the Docker plugin "+pluginID+": %w", err) } + return nil +} + +func pluginSet(ctx context.Context, d *schema.ResourceData, cl *client.Client) error { + pluginID := d.Id() log.Printf("[DEBUG] Update settings of a Docker plugin " + pluginID) // currently, only environment variables are supported. // TODO support other args @@ -151,45 +149,63 @@ func setPluginArgs(ctx context.Context, d *schema.ResourceData, client *client.C // source of mounts .Settings.Mounts // path of devices .Settings.Devices // args .Settings.Args - if err := client.PluginSet(ctx, pluginID, getDockerPluginEnv(d.Get("env"))); err != nil { + if err := cl.PluginSet(ctx, pluginID, getDockerPluginEnv(d.Get("env"))); err != nil { return fmt.Errorf("modifiy settings for the Docker plugin "+pluginID+": %w", err) } return nil } -func resourceDockerPluginUpdate(d *schema.ResourceData, meta interface{}) error { - client := meta.(*ProviderConfig).DockerClient - ctx := context.Background() - pluginID := d.Id() - if d.HasChange("enabled") { - if d.Get("enabled").(bool) { - if err := setPluginArgs(ctx, d, client, false); err != nil { +func pluginUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) (gErr error) { + cl := meta.(*ProviderConfig).DockerClient + o, n := d.GetChange("enabled") + oldEnabled, newEnabled := o.(bool), n.(bool) + if d.HasChange("env") { + if oldEnabled { + // To update the plugin setttings, the plugin must be disabled + if err := disablePlugin(ctx, d, cl); err != nil { return err } - log.Printf("[DEBUG] Enable a Docker plugin " + pluginID) - if err := client.PluginEnable(ctx, pluginID, types.PluginEnableOptions{ - Timeout: d.Get("enable_timeout").(int), - }); err != nil { - return fmt.Errorf("enable the Docker plugin "+pluginID+": %w", err) + if newEnabled { + defer func() { + if err := enablePlugin(ctx, d, cl); err != nil { + if gErr == nil { + gErr = err + return + } + } + }() } - } else { - log.Printf("[DEBUG] Disable a Docker plugin " + pluginID) - if err := client.PluginDisable(ctx, pluginID, types.PluginDisableOptions{ - Force: d.Get("force_disable").(bool), - }); err != nil { - return fmt.Errorf("disable the Docker plugin "+pluginID+": %w", err) + } + if err := pluginSet(ctx, d, cl); err != nil { + return err + } + if !oldEnabled && newEnabled { + if err := enablePlugin(ctx, d, cl); err != nil { + return err } } + return nil } - if !(d.HasChange("enabled") && d.Get("enabled").(bool)) { - plugin, _, err := client.PluginInspectWithRaw(ctx, pluginID) - if err != nil { - return fmt.Errorf("inspect a Docker plugin "+pluginID+": %w", err) - } - if err := setPluginArgs(ctx, d, client, plugin.Enabled); err != nil { - return err + // update only "enabled" + if d.HasChange("enabled") { + if newEnabled { + if err := enablePlugin(ctx, d, cl); err != nil { + return err + } + } else { + if err := disablePlugin(ctx, d, cl); err != nil { + return err + } } } + return nil +} + +func resourceDockerPluginUpdate(d *schema.ResourceData, meta interface{}) error { + ctx := context.Background() + if err := pluginUpdate(ctx, d, meta); err != nil { + return err + } // call the read function to update the resource's state. // https://learn.hashicorp.com/tutorials/terraform/provider-update?in=terraform/providers#implement-update return resourceDockerPluginRead(d, meta) From e3384e9401656632da53dfcd42b1f06c3562847b Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Fri, 8 Jan 2021 09:00:30 +0900 Subject: [PATCH 33/36] style: remove "." in the description ``` $ cd docker $ ag -l 'Description:.*\.",' | xargs -n 1 sed -i 's/Description:\(.*\)\.",/Description:\1",/' ``` --- docker/data_source_docker_plugin.go | 4 ++-- docker/resource_docker_container.go | 2 +- docker/resource_docker_container_v1.go | 2 +- docker/resource_docker_plugin.go | 6 +++--- docker/resource_docker_service.go | 24 ++++++++++++------------ 5 files changed, 19 insertions(+), 19 deletions(-) diff --git a/docker/data_source_docker_plugin.go b/docker/data_source_docker_plugin.go index b7d2e9740..9bcb0a3fd 100644 --- a/docker/data_source_docker_plugin.go +++ b/docker/data_source_docker_plugin.go @@ -20,12 +20,12 @@ func dataSourceDockerPlugin() *schema.Resource { "alias": { Type: schema.TypeString, Optional: true, - Description: "Docker Plugin alias.", + Description: "Docker Plugin alias", }, "plugin_reference": { Type: schema.TypeString, - Description: "Docker Plugin Reference.", + Description: "Docker Plugin Reference", Computed: true, }, "enabled": { diff --git a/docker/resource_docker_container.go b/docker/resource_docker_container.go index 7f0531ac3..154206dc5 100644 --- a/docker/resource_docker_container.go +++ b/docker/resource_docker_container.go @@ -310,7 +310,7 @@ func resourceDockerContainer() *schema.Resource { }, "driver_name": { Type: schema.TypeString, - Description: "Name of the driver to use to create the volume.", + Description: "Name of the driver to use to create the volume", Optional: true, }, "driver_options": { diff --git a/docker/resource_docker_container_v1.go b/docker/resource_docker_container_v1.go index 9372b6be6..fa7234835 100644 --- a/docker/resource_docker_container_v1.go +++ b/docker/resource_docker_container_v1.go @@ -275,7 +275,7 @@ func resourceDockerContainerV1() *schema.Resource { }, "driver_name": { Type: schema.TypeString, - Description: "Name of the driver to use to create the volume.", + Description: "Name of the driver to use to create the volume", Optional: true, }, "driver_options": { diff --git a/docker/resource_docker_plugin.go b/docker/resource_docker_plugin.go index 4908c3848..ddfae0b3d 100644 --- a/docker/resource_docker_plugin.go +++ b/docker/resource_docker_plugin.go @@ -16,7 +16,7 @@ func resourceDockerPlugin() *schema.Resource { Schema: map[string]*schema.Schema{ "plugin_reference": { Type: schema.TypeString, - Description: "Docker Plugin Reference.", + Description: "Docker Plugin Reference", Required: true, ForceNew: true, }, @@ -25,7 +25,7 @@ func resourceDockerPlugin() *schema.Resource { Computed: true, Optional: true, ForceNew: true, - Description: "Docker Plugin alias.", + Description: "Docker Plugin alias", }, "enabled": { Type: schema.TypeBool, @@ -78,7 +78,7 @@ func resourceDockerPlugin() *schema.Resource { "force_disable": { Type: schema.TypeBool, Optional: true, - Description: "If true, then the plugin is disabled forcibly when the plugin is disabled.", + Description: "If true, then the plugin is disabled forcibly when the plugin is disabled", }, }, } diff --git a/docker/resource_docker_service.go b/docker/resource_docker_service.go index fc3801853..d1bd711ad 100644 --- a/docker/resource_docker_service.go +++ b/docker/resource_docker_service.go @@ -260,7 +260,7 @@ func resourceDockerService() *schema.Resource { }, "driver_name": { Type: schema.TypeString, - Description: "Name of the driver to use to create the volume.", + Description: "Name of the driver to use to create the volume", Optional: true, }, "driver_options": { @@ -354,7 +354,7 @@ func resourceDockerService() *schema.Resource { }, "hosts": { Type: schema.TypeSet, - Description: "A list of hostname/IP mappings to add to the container's hosts file.", + Description: "A list of hostname/IP mappings to add to the container's hosts file", Optional: true, ForceNew: true, Elem: &schema.Resource{ @@ -597,7 +597,7 @@ func resourceDockerService() *schema.Resource { }, "restart_policy": { Type: schema.TypeMap, - Description: "Specification for the restart policy which applies to containers created as part of this service.", + Description: "Specification for the restart policy which applies to containers created as part of this service", Optional: true, Computed: true, Elem: &schema.Resource{ @@ -689,7 +689,7 @@ func resourceDockerService() *schema.Resource { }, "networks": { Type: schema.TypeSet, - Description: "Ids of the networks in which the container will be put in.", + Description: "Ids of the networks in which the container will be put in", Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, Set: schema.HashString, @@ -877,7 +877,7 @@ func resourceDockerService() *schema.Resource { }, "ports": { Type: schema.TypeList, - Description: "List of exposed ports that this service is accessible on from the outside. Ports can only be provided if 'vip' resolution mode is used.", + Description: "List of exposed ports that this service is accessible on from the outside. Ports can only be provided if 'vip' resolution mode is used", Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -900,7 +900,7 @@ func resourceDockerService() *schema.Resource { }, "published_port": { Type: schema.TypeInt, - Description: "The port on the swarm hosts.", + Description: "The port on the swarm hosts", Optional: true, Computed: true, }, @@ -1199,7 +1199,7 @@ func resourceDockerServiceV0() *schema.Resource { }, "driver_name": { Type: schema.TypeString, - Description: "Name of the driver to use to create the volume.", + Description: "Name of the driver to use to create the volume", Optional: true, }, "driver_options": { @@ -1293,7 +1293,7 @@ func resourceDockerServiceV0() *schema.Resource { }, "hosts": { Type: schema.TypeSet, - Description: "A list of hostname/IP mappings to add to the container's hosts file.", + Description: "A list of hostname/IP mappings to add to the container's hosts file", Optional: true, ForceNew: true, Elem: &schema.Resource{ @@ -1498,7 +1498,7 @@ func resourceDockerServiceV0() *schema.Resource { }, "restart_policy": { Type: schema.TypeMap, - Description: "Specification for the restart policy which applies to containers created as part of this service.", + Description: "Specification for the restart policy which applies to containers created as part of this service", Optional: true, Computed: true, Elem: &schema.Resource{ @@ -1590,7 +1590,7 @@ func resourceDockerServiceV0() *schema.Resource { }, "networks": { Type: schema.TypeSet, - Description: "Ids of the networks in which the container will be put in.", + Description: "Ids of the networks in which the container will be put in", Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, Set: schema.HashString, @@ -1778,7 +1778,7 @@ func resourceDockerServiceV0() *schema.Resource { }, "ports": { Type: schema.TypeSet, - Description: "List of exposed ports that this service is accessible on from the outside. Ports can only be provided if 'vip' resolution mode is used.", + Description: "List of exposed ports that this service is accessible on from the outside. Ports can only be provided if 'vip' resolution mode is used", Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -1801,7 +1801,7 @@ func resourceDockerServiceV0() *schema.Resource { }, "published_port": { Type: schema.TypeInt, - Description: "The port on the swarm hosts.", + Description: "The port on the swarm hosts", Optional: true, }, "publish_mode": { From 7a7aee3025ac7893b3b81638559cc669da077e29 Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Fri, 8 Jan 2021 11:02:55 +0900 Subject: [PATCH 34/36] fix: implement DiffSuppressFunc of docker_plugin.alias complement the latest tag. --- docker/resource_docker_plugin.go | 3 +++ docker/resource_docker_plugin_funcs.go | 8 ++++++++ website/docs/r/plugin.html.markdown | 10 +++++----- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/docker/resource_docker_plugin.go b/docker/resource_docker_plugin.go index ddfae0b3d..cfb3712cb 100644 --- a/docker/resource_docker_plugin.go +++ b/docker/resource_docker_plugin.go @@ -26,6 +26,9 @@ func resourceDockerPlugin() *schema.Resource { Optional: true, ForceNew: true, Description: "Docker Plugin alias", + DiffSuppressFunc: func(k, oldV, newV string, d *schema.ResourceData) bool { + return complementTag(oldV) == complementTag(newV) + }, }, "enabled": { Type: schema.TypeBool, diff --git a/docker/resource_docker_plugin_funcs.go b/docker/resource_docker_plugin_funcs.go index 8c7ff762e..171c7a5f0 100644 --- a/docker/resource_docker_plugin_funcs.go +++ b/docker/resource_docker_plugin_funcs.go @@ -5,6 +5,7 @@ import ( "fmt" "io/ioutil" "log" + "strings" "github.com/docker/docker/api/types" "github.com/docker/docker/client" @@ -27,6 +28,13 @@ func dockerPluginGrantPermissionsSetFunc(v interface{}) int { return schema.HashString(v.(map[string]interface{})["name"].(string)) } +func complementTag(image string) string { + if strings.Contains(image, ":") { + return image + } + return image + ":latest" +} + func getDockerPluginGrantPermissions(src interface{}) func(types.PluginPrivileges) (bool, error) { grantPermissionsSet := src.(*schema.Set) grantPermissions := make(map[string]map[string]struct{}, grantPermissionsSet.Len()) diff --git a/website/docs/r/plugin.html.markdown b/website/docs/r/plugin.html.markdown index 14b2df150..27cd250d6 100644 --- a/website/docs/r/plugin.html.markdown +++ b/website/docs/r/plugin.html.markdown @@ -38,7 +38,7 @@ resource "docker_plugin" "sample-volume-plugin" { The following arguments are supported: * `plugin_reference` - (Required, string, Forces new resource) The plugin reference. The registry path and image tag should not be omitted. See [plugin_references, alias](#plugin-references-alias-1) below for details. -* `alias` - (Optional, string, Forces new resource) The alias of the Docker plugin. The image tag should not be omitted. See [plugin_references, alias](#plugin-references-alias-1) below for details. +* `alias` - (Optional, string, Forces new resource) The alias of the Docker plugin. If the tag is omitted, `:latest` is complemented to the attribute value. * `enabled` - (Optional, boolean) If true, the plugin is enabled. The default value is `true`. * `grant_all_permissions` - (Optional, boolean) If true, grant all permissions necessary to run the plugin. This attribute conflicts with `grant_permissions`. * `grant_permissions` - (Optional, block) grant permissions necessary to run the plugin. This attribute conflicts with `grant_all_permissions`. See [grant_permissions](#grant-permissions-1) below for details. @@ -48,16 +48,16 @@ The following arguments are supported: * `force_disable` - (Optional, boolean) If true, then the plugin is disabled forcibly when the plugin is disabled. -## plugin_reference, alias +## plugin_reference -`plugin_reference` and `alias` must be full path. Otherwise, after `terraform apply` is run, there would be diffs of them. +`plugin_reference` must be full path. Otherwise, after `terraform apply` is run, there would be diffs of them. For example, ```hcl resource "docker_plugin" "sample-volume-plugin" { plugin_reference = "tiborvass/sample-volume-plugin" # must be "docker.io/tiborvass/sample-volume-plugin:latest" - alias = "sample" # must be "sample:latest" + alias = "sample" } ``` @@ -76,7 +76,7 @@ Terraform will perform the following actions: # docker_plugin.sample-volume-plugin must be replaced -/+ resource "docker_plugin" "sample-volume-plugin" { - ~ alias = "sample:latest" -> "sample" # forces replacement + ~ alias = "sample:latest" -> "sample:latest" - enabled = false -> null ~ env = [ - "DEBUG=0", From b82956b7603c8bb7e0ef7aa887e89de5c7d6043e Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Fri, 8 Jan 2021 12:09:28 +0900 Subject: [PATCH 35/36] feat: add a field docker_plugin.name --- docker/resource_docker_plugin.go | 17 ++++++--- docker/resource_docker_plugin_funcs.go | 39 ++++++++++++++++--- docker/resource_docker_plugin_test.go | 42 +++++++++++--------- go.mod | 2 +- website/docs/r/plugin.html.markdown | 53 ++++---------------------- 5 files changed, 78 insertions(+), 75 deletions(-) diff --git a/docker/resource_docker_plugin.go b/docker/resource_docker_plugin.go index cfb3712cb..e4207374c 100644 --- a/docker/resource_docker_plugin.go +++ b/docker/resource_docker_plugin.go @@ -14,11 +14,13 @@ func resourceDockerPlugin() *schema.Resource { State: schema.ImportStatePassthrough, }, Schema: map[string]*schema.Schema{ - "plugin_reference": { - Type: schema.TypeString, - Description: "Docker Plugin Reference", - Required: true, - ForceNew: true, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Docker Plugin name", + DiffSuppressFunc: diffSuppressFuncPluginName, + ValidateFunc: validateFuncPluginName, }, "alias": { Type: schema.TypeString, @@ -68,6 +70,11 @@ func resourceDockerPlugin() *schema.Resource { Computed: true, Elem: &schema.Schema{Type: schema.TypeString}, }, + "plugin_reference": { + Type: schema.TypeString, + Description: "Docker Plugin Reference", + Computed: true, + }, "force_destroy": { Type: schema.TypeBool, diff --git a/docker/resource_docker_plugin_funcs.go b/docker/resource_docker_plugin_funcs.go index 171c7a5f0..6b15efe9f 100644 --- a/docker/resource_docker_plugin_funcs.go +++ b/docker/resource_docker_plugin_funcs.go @@ -7,6 +7,7 @@ import ( "log" "strings" + "github.com/docker/distribution/reference" "github.com/docker/docker/api/types" "github.com/docker/docker/client" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" @@ -35,6 +36,33 @@ func complementTag(image string) string { return image + ":latest" } +func normalizePluginName(name string) (string, error) { + ref, err := reference.ParseAnyReference(name) + if err != nil { + return "", fmt.Errorf("parse the plugin name: %w", err) + } + return complementTag(ref.String()), nil +} + +func diffSuppressFuncPluginName(k, oldV, newV string, d *schema.ResourceData) bool { + o, err := normalizePluginName(oldV) + if err != nil { + return false + } + n, err := normalizePluginName(newV) + if err != nil { + return false + } + return o == n +} + +func validateFuncPluginName(val interface{}, key string) (warns []string, errs []error) { + if _, err := normalizePluginName(val.(string)); err != nil { + return warns, append(errs, fmt.Errorf("%s is invalid: %w", key, err)) + } + return +} + func getDockerPluginGrantPermissions(src interface{}) func(types.PluginPrivileges) (bool, error) { grantPermissionsSet := src.(*schema.Set) grantPermissions := make(map[string]map[string]struct{}, grantPermissionsSet.Len()) @@ -69,11 +97,11 @@ func getDockerPluginGrantPermissions(src interface{}) func(types.PluginPrivilege func resourceDockerPluginCreate(d *schema.ResourceData, meta interface{}) error { client := meta.(*ProviderConfig).DockerClient ctx := context.Background() - pluginRef := d.Get("plugin_reference").(string) + pluginName := d.Get("name").(string) alias := d.Get("alias").(string) - log.Printf("[DEBUG] Install a Docker plugin " + pluginRef) + log.Printf("[DEBUG] Install a Docker plugin " + pluginName) opts := types.PluginInstallOptions{ - RemoteRef: pluginRef, + RemoteRef: pluginName, AcceptAllPermissions: d.Get("grant_all_permissions").(bool), Disabled: !d.Get("enabled").(bool), // TODO support other settings @@ -84,10 +112,10 @@ func resourceDockerPluginCreate(d *schema.ResourceData, meta interface{}) error } body, err := client.PluginInstall(ctx, alias, opts) if err != nil { - return fmt.Errorf("install a Docker plugin "+pluginRef+": %w", err) + return fmt.Errorf("install a Docker plugin "+pluginName+": %w", err) } _, _ = ioutil.ReadAll(body) - key := pluginRef + key := pluginName if alias != "" { key = alias } @@ -103,6 +131,7 @@ func setDockerPlugin(d *schema.ResourceData, plugin *types.Plugin) { d.SetId(plugin.ID) d.Set("plugin_reference", plugin.PluginReference) d.Set("alias", plugin.Name) + d.Set("name", plugin.PluginReference) d.Set("enabled", plugin.Enabled) // TODO support other settings // https://docs.docker.com/engine/reference/commandline/plugin_set/#extended-description diff --git a/docker/resource_docker_plugin_test.go b/docker/resource_docker_plugin_test.go index 6afb5c616..e57b96223 100644 --- a/docker/resource_docker_plugin_test.go +++ b/docker/resource_docker_plugin_test.go @@ -151,6 +151,7 @@ func TestAccDockerPlugin_basic(t *testing.T) { ResourceName: resourceName, Config: testAccDockerPluginMinimum, Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", "docker.io/tiborvass/sample-volume-plugin:latest"), resource.TestCheckResourceAttr(resourceName, "plugin_reference", "docker.io/tiborvass/sample-volume-plugin:latest"), resource.TestCheckResourceAttr(resourceName, "alias", "tiborvass/sample-volume-plugin:latest"), resource.TestCheckResourceAttr(resourceName, "enabled", "true"), @@ -160,6 +161,7 @@ func TestAccDockerPlugin_basic(t *testing.T) { ResourceName: resourceName, Config: testAccDockerPluginAlias, Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", "docker.io/tiborvass/sample-volume-plugin:latest"), resource.TestCheckResourceAttr(resourceName, "plugin_reference", "docker.io/tiborvass/sample-volume-plugin:latest"), resource.TestCheckResourceAttr(resourceName, "alias", "sample:latest"), resource.TestCheckResourceAttr(resourceName, "enabled", "true"), @@ -169,6 +171,7 @@ func TestAccDockerPlugin_basic(t *testing.T) { ResourceName: resourceName, Config: testAccDockerPluginDisableWhenSet, Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", "docker.io/tiborvass/sample-volume-plugin:latest"), resource.TestCheckResourceAttr(resourceName, "plugin_reference", "docker.io/tiborvass/sample-volume-plugin:latest"), resource.TestCheckResourceAttr(resourceName, "alias", "sample:latest"), resource.TestCheckResourceAttr(resourceName, "enabled", "true"), @@ -181,6 +184,7 @@ func TestAccDockerPlugin_basic(t *testing.T) { ResourceName: resourceName, Config: testAccDockerPluginDisabled, Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", "docker.io/tiborvass/sample-volume-plugin:latest"), resource.TestCheckResourceAttr(resourceName, "plugin_reference", "docker.io/tiborvass/sample-volume-plugin:latest"), resource.TestCheckResourceAttr(resourceName, "alias", "sample:latest"), resource.TestCheckResourceAttr(resourceName, "enabled", "false"), @@ -208,6 +212,7 @@ func TestAccDockerPlugin_grantAllPermissions(t *testing.T) { ResourceName: resourceName, Config: testAccDockerPluginGrantAllPermissions, Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", "docker.io/vieux/sshfs:latest"), resource.TestCheckResourceAttr(resourceName, "plugin_reference", "docker.io/vieux/sshfs:latest"), resource.TestCheckResourceAttr(resourceName, "alias", "vieux/sshfs:latest"), resource.TestCheckResourceAttr(resourceName, "grant_all_permissions", "true"), @@ -231,6 +236,7 @@ func TestAccDockerPlugin_grantPermissions(t *testing.T) { ResourceName: resourceName, Config: testAccDockerPluginGrantPermissions, Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", "docker.io/vieux/sshfs:latest"), resource.TestCheckResourceAttr(resourceName, "plugin_reference", "docker.io/vieux/sshfs:latest"), resource.TestCheckResourceAttr(resourceName, "alias", "vieux/sshfs:latest"), ), @@ -245,24 +251,24 @@ func TestAccDockerPlugin_grantPermissions(t *testing.T) { const testAccDockerPluginMinimum = ` resource "docker_plugin" "test" { - plugin_reference = "docker.io/tiborvass/sample-volume-plugin:latest" - force_destroy = true + name = "docker.io/tiborvass/sample-volume-plugin:latest" + force_destroy = true }` const testAccDockerPluginAlias = ` resource "docker_plugin" "test" { - plugin_reference = "docker.io/tiborvass/sample-volume-plugin:latest" + name = "docker.io/tiborvass/sample-volume-plugin:latest" alias = "sample:latest" force_destroy = true }` const testAccDockerPluginDisableWhenSet = ` resource "docker_plugin" "test" { - plugin_reference = "docker.io/tiborvass/sample-volume-plugin:latest" - alias = "sample:latest" - grant_all_permissions = true - force_destroy = true - enable_timeout = 60 + name = "docker.io/tiborvass/sample-volume-plugin:latest" + alias = "sample:latest" + grant_all_permissions = true + force_destroy = true + enable_timeout = 60 env = [ "DEBUG=1" ] @@ -270,13 +276,13 @@ resource "docker_plugin" "test" { const testAccDockerPluginDisabled = ` resource "docker_plugin" "test" { - plugin_reference = "docker.io/tiborvass/sample-volume-plugin:latest" - alias = "sample:latest" - enabled = false - grant_all_permissions = true - force_destroy = true - force_disable = true - enable_timeout = 60 + name = "docker.io/tiborvass/sample-volume-plugin:latest" + alias = "sample:latest" + enabled = false + grant_all_permissions = true + force_destroy = true + force_disable = true + enable_timeout = 60 env = [ "DEBUG=1" ] @@ -285,7 +291,7 @@ resource "docker_plugin" "test" { // To install this plugin, it is required to grant required permissions. const testAccDockerPluginGrantAllPermissions = ` resource "docker_plugin" "test" { - plugin_reference = "docker.io/vieux/sshfs:latest" + name = "docker.io/vieux/sshfs:latest" grant_all_permissions = true force_destroy = true }` @@ -293,8 +299,8 @@ resource "docker_plugin" "test" { // To install this plugin, it is required to grant required permissions. const testAccDockerPluginGrantPermissions = ` resource "docker_plugin" "test" { - plugin_reference = "docker.io/vieux/sshfs:latest" - force_destroy = true + name = "vieux/sshfs" + force_destroy = true grant_permissions { name = "network" value = [ diff --git a/go.mod b/go.mod index ade4195fe..07b48e294 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ require ( github.com/Microsoft/hcsshim v0.8.9 // indirect github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe // indirect github.com/docker/cli v0.0.0-20200303215952-eb310fca4956 // v19.03.8 - github.com/docker/distribution v0.0.0-20180522175653-f0cc92778478 // indirect + github.com/docker/distribution v0.0.0-20180522175653-f0cc92778478 github.com/docker/docker v0.7.3-0.20190525203055-f25e0c6f3093 github.com/docker/docker-credential-helpers v0.6.3 github.com/docker/go-connections v0.4.0 diff --git a/website/docs/r/plugin.html.markdown b/website/docs/r/plugin.html.markdown index 27cd250d6..8cd4489b0 100644 --- a/website/docs/r/plugin.html.markdown +++ b/website/docs/r/plugin.html.markdown @@ -14,14 +14,14 @@ Manages the lifecycle of a Docker plugin. ```hcl resource "docker_plugin" "sample-volume-plugin" { - plugin_reference = "docker.io/tiborvass/sample-volume-plugin:latest" + name = "docker.io/tiborvass/sample-volume-plugin:latest" } ``` ```hcl resource "docker_plugin" "sample-volume-plugin" { - plugin_reference = "docker.io/tiborvass/sample-volume-plugin:latest" - alias = "sample-volume-plugin:latest" + name = "tiborvass/sample-volume-plugin" + alias = "sample-volume-plugin" enabled = false grant_all_permissions = true force_destroy = true @@ -37,7 +37,7 @@ resource "docker_plugin" "sample-volume-plugin" { The following arguments are supported: -* `plugin_reference` - (Required, string, Forces new resource) The plugin reference. The registry path and image tag should not be omitted. See [plugin_references, alias](#plugin-references-alias-1) below for details. +* `name` - (Required, string, Forces new resource) The plugin name. If the tag is omitted, `:latest` is complemented to the attribute value. * `alias` - (Optional, string, Forces new resource) The alias of the Docker plugin. If the tag is omitted, `:latest` is complemented to the attribute value. * `enabled` - (Optional, boolean) If true, the plugin is enabled. The default value is `true`. * `grant_all_permissions` - (Optional, boolean) If true, grant all permissions necessary to run the plugin. This attribute conflicts with `grant_permissions`. @@ -47,47 +47,6 @@ The following arguments are supported: * `enable_timeout` - (Optional, int) HTTP client timeout to enable the plugin. * `force_disable` - (Optional, boolean) If true, then the plugin is disabled forcibly when the plugin is disabled. - -## plugin_reference - -`plugin_reference` must be full path. Otherwise, after `terraform apply` is run, there would be diffs of them. - -For example, - -```hcl -resource "docker_plugin" "sample-volume-plugin" { - plugin_reference = "tiborvass/sample-volume-plugin" # must be "docker.io/tiborvass/sample-volume-plugin:latest" - alias = "sample" -} -``` - -```sh -$ terraform apply # a plugin is installed - -Apply complete! Resources: 1 added, 0 changed, 0 destroyed. - -$ terraform plan - -An execution plan has been generated and is shown below. -Resource actions are indicated with the following symbols: --/+ destroy and then create replacement - -Terraform will perform the following actions: - - # docker_plugin.sample-volume-plugin must be replaced --/+ resource "docker_plugin" "sample-volume-plugin" { - ~ alias = "sample:latest" -> "sample:latest" - - enabled = false -> null - ~ env = [ - - "DEBUG=0", - ] -> (known after apply) - ~ id = "27784976e1471c1e473a901a0a02055ddc8bc1c9dec9c44d81a49d516c0c28f9" -> (known after apply) - ~ plugin_reference = "docker.io/tiborvass/sample-volume-plugin:latest" -> "tiborvass/sample-volume-plugin" # forces replacement - } - -Plan: 1 to add, 0 to change, 1 to destroy. -``` - ## grant_permissions @@ -101,7 +60,7 @@ Example: ```hcl resource "docker_plugin" "sshfs" { - plugin_reference = "docker.io/vieux/sshfs:latest" + name = "docker.io/vieux/sshfs:latest" grant_permissions { name = "network" value = [ @@ -132,6 +91,8 @@ resource "docker_plugin" "sshfs" { ## Attributes Reference +* `plugin_reference` - (string) The plugin reference. + ## Import Docker plugins can be imported using the long id, e.g. for a plugin `tiborvass/sample-volume-plugin:latest`: From e0ce386736588db3e87cf2b3f0123074959d09fa Mon Sep 17 00:00:00 2001 From: Shunsuke Suzuki Date: Fri, 8 Jan 2021 12:40:36 +0900 Subject: [PATCH 36/36] test: test normalizePluginName and complementTag --- docker/resource_docker_plugin_test.go | 85 +++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/docker/resource_docker_plugin_test.go b/docker/resource_docker_plugin_test.go index e57b96223..8ad0f9d49 100644 --- a/docker/resource_docker_plugin_test.go +++ b/docker/resource_docker_plugin_test.go @@ -37,6 +37,91 @@ func Test_getDockerPluginEnv(t *testing.T) { } } +func Test_complementTag(t *testing.T) { + t.Parallel() + data := []struct { + title string + image string + exp string + }{ + { + title: "alpine:3.11.6", + image: "alpine:3.11.6", + exp: "alpine:3.11.6", + }, + { + title: "alpine", + image: "alpine", + exp: "alpine:latest", + }, + } + for _, d := range data { + d := d + t.Run(d.title, func(t *testing.T) { + t.Parallel() + image := complementTag(d.image) + if image != d.exp { + t.Fatalf("want %v, got %v", d.exp, image) + } + }) + } +} + +func Test_normalizePluginName(t *testing.T) { + t.Parallel() + data := []struct { + title string + image string + isErr bool + exp string + }{ + { + title: "alpine:3.11.6", + image: "alpine:3.11.6", + exp: "docker.io/library/alpine:3.11.6", + }, + { + title: "alpine", + image: "alpine", + exp: "docker.io/library/alpine:latest", + }, + { + title: "vieux/sshfs", + image: "vieux/sshfs", + exp: "docker.io/vieux/sshfs:latest", + }, + { + title: "docker.io/vieux/sshfs:latest", + image: "docker.io/vieux/sshfs:latest", + exp: "docker.io/vieux/sshfs:latest", + }, + { + title: "docker.io/vieux/sshfs", + image: "docker.io/vieux/sshfs", + exp: "docker.io/vieux/sshfs:latest", + }, + } + for _, d := range data { + d := d + t.Run(d.title, func(t *testing.T) { + t.Parallel() + image, err := normalizePluginName(d.image) + if d.isErr { + if err == nil { + t.Fatal("error should be returned") + } + return + } + if err != nil { + t.Fatal(err) + } + if image != d.exp { + t.Fatalf("want %v, got %v", d.exp, image) + } + }) + } +} + func Test_getDockerPluginGrantPermissions(t *testing.T) { t.Parallel() data := []struct {