diff --git a/docker/data_source_docker_plugin.go b/docker/data_source_docker_plugin.go
new file mode 100644
index 000000000..9bcb0a3fd
--- /dev/null
+++ b/docker/data_source_docker_plugin.go
@@ -0,0 +1,80 @@
+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",
+ Computed: true,
+ },
+ "enabled": {
+ Type: schema.TypeBool,
+ Computed: true,
+ },
+ "grant_all_permissions": {
+ Type: schema.TypeBool,
+ Computed: true,
+ Description: "If true, grant all permissions necessary to run the plugin",
+ },
+ "env": {
+ Type: schema.TypeSet,
+ Computed: true,
+ Elem: &schema.Schema{Type: schema.TypeString},
+ },
+ },
+ }
+}
+
+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 aliasOK {
+ return alias.(string), nil
+ }
+ return "", errDataSourceKeyIsMissing
+}
+
+func dataSourceDockerPluginRead(d *schema.ResourceData, meta interface{}) error {
+ key, err := getDataSourcePluginKey(d)
+ if err != nil {
+ return err
+ }
+ 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/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"
+}
+`
diff --git a/docker/provider.go b/docker/provider.go
index 2421f04a5..4d52fd700 100644
--- a/docker/provider.go
+++ b/docker/provider.go
@@ -109,11 +109,13 @@ func Provider() terraform.ResourceProvider {
"docker_config": resourceDockerConfig(),
"docker_secret": resourceDockerSecret(),
"docker_service": resourceDockerService(),
+ "docker_plugin": resourceDockerPlugin(),
},
DataSourcesMap: map[string]*schema.Resource{
"docker_registry_image": dataSourceDockerRegistryImage(),
"docker_network": dataSourceDockerNetwork(),
+ "docker_plugin": dataSourceDockerPlugin(),
},
ConfigureFunc: providerConfigure,
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
new file mode 100644
index 000000000..e4207374c
--- /dev/null
+++ b/docker/resource_docker_plugin.go
@@ -0,0 +1,95 @@
+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,
+ Description: "Docker Plugin name",
+ DiffSuppressFunc: diffSuppressFuncPluginName,
+ ValidateFunc: validateFuncPluginName,
+ },
+ "alias": {
+ Type: schema.TypeString,
+ Computed: true,
+ 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,
+ Optional: true,
+ Default: true,
+ },
+ "grant_all_permissions": {
+ 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: dockerPluginGrantPermissionsSetFunc,
+ 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,
+ Optional: true,
+ Computed: true,
+ Elem: &schema.Schema{Type: schema.TypeString},
+ },
+ "plugin_reference": {
+ Type: schema.TypeString,
+ Description: "Docker Plugin Reference",
+ Computed: true,
+ },
+
+ "force_destroy": {
+ Type: schema.TypeBool,
+ Optional: true,
+ },
+ "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 forcibly when the plugin is disabled",
+ },
+ },
+ }
+}
diff --git a/docker/resource_docker_plugin_funcs.go b/docker/resource_docker_plugin_funcs.go
new file mode 100644
index 000000000..6b15efe9f
--- /dev/null
+++ b/docker/resource_docker_plugin_funcs.go
@@ -0,0 +1,262 @@
+package docker
+
+import (
+ "context"
+ "fmt"
+ "io/ioutil"
+ "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"
+)
+
+func getDockerPluginEnv(src interface{}) []string {
+ if src == nil {
+ return nil
+ }
+ b := src.(*schema.Set)
+ envs := make([]string, b.Len())
+ for i, a := range b.List() {
+ envs[i] = a.(string)
+ }
+ return envs
+}
+
+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 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())
+ 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)
+ 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 + " " + value)
+ return false, nil
+ }
+ }
+ }
+ return true, nil
+ }
+}
+
+func resourceDockerPluginCreate(d *schema.ResourceData, meta interface{}) error {
+ client := meta.(*ProviderConfig).DockerClient
+ ctx := context.Background()
+ pluginName := d.Get("name").(string)
+ alias := d.Get("alias").(string)
+ log.Printf("[DEBUG] Install a Docker plugin " + pluginName)
+ opts := types.PluginInstallOptions{
+ RemoteRef: pluginName,
+ 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 "+pluginName+": %w", err)
+ }
+ _, _ = ioutil.ReadAll(body)
+ key := pluginName
+ if alias != "" {
+ key = alias
+ }
+ plugin, _, err := client.PluginInspectWithRaw(ctx, key)
+ if err != nil {
+ return fmt.Errorf("inspect a Docker plugin "+key+": %w", err)
+ }
+ setDockerPlugin(d, plugin)
+ return nil
+}
+
+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
+ // source of mounts .Settings.Mounts
+ // path of devices .Settings.Devices
+ // args .Settings.Args
+ d.Set("env", plugin.Settings.Env)
+}
+
+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 {
+ log.Printf("[DEBUG] Inspect a Docker plugin "+pluginID+": %w", err)
+ d.SetId("")
+ return nil
+ }
+ setDockerPlugin(d, plugin)
+ 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()
+ 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
+ // 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 := 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 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
+ }
+ if newEnabled {
+ defer func() {
+ if err := enablePlugin(ctx, d, cl); err != nil {
+ if gErr == nil {
+ gErr = err
+ return
+ }
+ }
+ }()
+ }
+ }
+ 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
+ }
+ // 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)
+}
+
+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 {
+ return fmt.Errorf("remove the Docker plugin "+pluginID+": %w", err)
+ }
+ return nil
+}
diff --git a/docker/resource_docker_plugin_test.go b/docker/resource_docker_plugin_test.go
new file mode 100644
index 000000000..8ad0f9d49
--- /dev/null
+++ b/docker/resource_docker_plugin_test.go
@@ -0,0 +1,414 @@
+package docker
+
+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"
+)
+
+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 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 {
+ 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{
+ PreCheck: func() { testAccPreCheck(t) },
+ Providers: testAccProviders,
+ Steps: []resource.TestStep{
+ {
+ 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"),
+ ),
+ },
+ {
+ 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"),
+ ),
+ },
+ {
+ 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"),
+ resource.TestCheckResourceAttr(resourceName, "grant_all_permissions", "true"),
+ resource.TestCheckResourceAttr(resourceName, "force_destroy", "true"),
+ resource.TestCheckResourceAttr(resourceName, "enable_timeout", "60"),
+ ),
+ },
+ {
+ 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"),
+ resource.TestCheckResourceAttr(resourceName, "grant_all_permissions", "true"),
+ resource.TestCheckResourceAttr(resourceName, "force_destroy", "true"),
+ resource.TestCheckResourceAttr(resourceName, "enable_timeout", "60"),
+ resource.TestCheckResourceAttr(resourceName, "force_disable", "true"),
+ ),
+ },
+ {
+ ResourceName: resourceName,
+ ImportState: true,
+ },
+ },
+ })
+}
+
+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, "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"),
+ ),
+ },
+ {
+ ResourceName: resourceName,
+ ImportState: true,
+ },
+ },
+ })
+}
+
+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, "name", "docker.io/vieux/sshfs:latest"),
+ 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" {
+ name = "docker.io/tiborvass/sample-volume-plugin:latest"
+ force_destroy = true
+}`
+
+const testAccDockerPluginAlias = `
+resource "docker_plugin" "test" {
+ name = "docker.io/tiborvass/sample-volume-plugin:latest"
+ alias = "sample:latest"
+ force_destroy = true
+}`
+
+const testAccDockerPluginDisableWhenSet = `
+resource "docker_plugin" "test" {
+ name = "docker.io/tiborvass/sample-volume-plugin:latest"
+ alias = "sample:latest"
+ grant_all_permissions = true
+ force_destroy = true
+ enable_timeout = 60
+ env = [
+ "DEBUG=1"
+ ]
+}`
+
+const testAccDockerPluginDisabled = `
+resource "docker_plugin" "test" {
+ 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"
+ ]
+}`
+
+// To install this plugin, it is required to grant required permissions.
+const testAccDockerPluginGrantAllPermissions = `
+resource "docker_plugin" "test" {
+ name = "docker.io/vieux/sshfs:latest"
+ grant_all_permissions = true
+ force_destroy = true
+}`
+
+// To install this plugin, it is required to grant required permissions.
+const testAccDockerPluginGrantPermissions = `
+resource "docker_plugin" "test" {
+ name = "vieux/sshfs"
+ 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"
+ ]
+ }
+}`
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": {
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/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.
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
new file mode 100644
index 000000000..8cd4489b0
--- /dev/null
+++ b/website/docs/r/plugin.html.markdown
@@ -0,0 +1,102 @@
+---
+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 = "docker.io/tiborvass/sample-volume-plugin:latest"
+}
+```
+
+```hcl
+resource "docker_plugin" "sample-volume-plugin" {
+ name = "tiborvass/sample-volume-plugin"
+ alias = "sample-volume-plugin"
+ enabled = false
+ grant_all_permissions = true
+ force_destroy = true
+ enable_timeout = 60
+ force_disable = true
+ env = [
+ "DEBUG=1"
+ ]
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `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`.
+* `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.
+* `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.
+
+
+## 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" {
+ name = "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"
+ ]
+ }
+}
+```
+
+## 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`:
+
+```sh
+$ terraform import docker_plugin.sample-volume-plugin $(docker plugin inspect -f "{{.ID}}" tiborvass/sample-volume-plugin:latest)
+```