feat: supports Docker plugin (#35)

Closes #24
This commit is contained in:
Shunsuke Suzuki 2021-01-09 00:38:30 +09:00 committed by GitHub
parent 0ef85ad13f
commit c927a8c19f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 1047 additions and 16 deletions

View file

@ -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
}

View file

@ -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"
}
`

View file

@ -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,

View file

@ -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": {

View file

@ -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": {

View file

@ -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",
},
},
}
}

View file

@ -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
}

View file

@ -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"
]
}
}`

View file

@ -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{
@ -695,7 +695,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,
@ -883,7 +883,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{
@ -906,7 +906,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,
},
@ -1205,7 +1205,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": {
@ -1299,7 +1299,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{
@ -1504,7 +1504,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{
@ -1596,7 +1596,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,
@ -1784,7 +1784,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{
@ -1807,7 +1807,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": {

2
go.mod
View file

@ -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

View file

@ -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.

View file

@ -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.
<a id="build-1"></a>

View file

@ -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.
<a id="grant-permissions-1"></a>
## 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)
```