Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support Docker plugin #35

Merged
merged 36 commits into from
Jan 8, 2021
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
6c0434e
feat: support Docker plugin
suzuki-shunsuke Dec 22, 2020
2238594
feat: add destroy_option
suzuki-shunsuke Dec 29, 2020
c0fc9db
feat: enhance docker plugin
suzuki-shunsuke Dec 29, 2020
ceae7b1
fix: make docker_plugin.alias force new
suzuki-shunsuke Dec 29, 2020
1c1ba51
fix: make docker_plugin.env computed
suzuki-shunsuke Dec 29, 2020
963699b
fix: remove the plugin from state when it failed to inspect the plugin
suzuki-shunsuke Dec 29, 2020
bbd50ed
test: add test of docker_plugin
suzuki-shunsuke Dec 29, 2020
260a2ef
docs: fix a lint error
suzuki-shunsuke Dec 29, 2020
1f7e286
test: add tests of docker_plugin
suzuki-shunsuke Dec 29, 2020
731342a
docs: format document
suzuki-shunsuke Dec 29, 2020
593269b
style: remove TODO comment
suzuki-shunsuke Dec 29, 2020
84dd122
fix: rename env to args
suzuki-shunsuke Dec 29, 2020
3b7ab72
feat: add debug logs
suzuki-shunsuke Dec 29, 2020
a9662ad
docs: update plugin document
suzuki-shunsuke Dec 29, 2020
6522a35
fix: enable plugin after disable it temporarily with defer
suzuki-shunsuke Dec 29, 2020
7a79c28
docs: fix forcely to forcibly
suzuki-shunsuke Dec 29, 2020
908fd7c
feat: add a data source docker_plugin
suzuki-shunsuke Dec 29, 2020
9df6394
docs: add document of data.docker_plugin
suzuki-shunsuke Dec 29, 2020
e52b264
fix: raise an error if both id and alias are specified.
suzuki-shunsuke Dec 29, 2020
5f925d6
fix: replace docker_plugin.disabled to enabled
suzuki-shunsuke Dec 29, 2020
54629cc
test: add test of data.docker_plugin
suzuki-shunsuke Dec 30, 2020
afcb76c
test: test docker_plugin.grant_all_permissions
suzuki-shunsuke Dec 30, 2020
3ec71c5
refactor: remove skipArgs to make code clear
suzuki-shunsuke Dec 30, 2020
c8b9c7f
fix: make attributes `Computed: true`
suzuki-shunsuke Dec 30, 2020
967f2ba
fix: replace args to env
suzuki-shunsuke Dec 30, 2020
03b2187
feat: add an attribute "docker_plugin.grant_permissions"
suzuki-shunsuke Dec 30, 2020
735df8a
docs: add document of plugin.grant_permissions
suzuki-shunsuke Dec 31, 2020
0d31498
test: test docker_plugin.grant_permissions
suzuki-shunsuke Dec 31, 2020
bc2a157
test: test getDockerPluginEnv
suzuki-shunsuke Dec 31, 2020
3a8e2b0
test: test getDockerPluginGrantPermissions
suzuki-shunsuke Dec 31, 2020
abec1d5
fix: remove docker_plugin.disable_when_set
suzuki-shunsuke Jan 4, 2021
17d8fc6
refactor: refactor resourceDockerPluginUpdate
suzuki-shunsuke Jan 5, 2021
e3384e9
style: remove "." in the description
suzuki-shunsuke Jan 8, 2021
7a7aee3
fix: implement DiffSuppressFunc of docker_plugin.alias
suzuki-shunsuke Jan 8, 2021
b82956b
feat: add a field docker_plugin.name
suzuki-shunsuke Jan 8, 2021
e0ce386
test: test normalizePluginName and complementTag
suzuki-shunsuke Jan 8, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions docker/data_source_docker_plugin.go
Original file line number Diff line number Diff line change
@@ -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.",
suzuki-shunsuke marked this conversation as resolved.
Show resolved Hide resolved
},

"plugin_reference": {
suzuki-shunsuke marked this conversation as resolved.
Show resolved Hide resolved
Type: schema.TypeString,
Description: "Docker Plugin Reference.",
Optional: true,
},
"disabled": {
suzuki-shunsuke marked this conversation as resolved.
Show resolved Hide resolved
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
}
2 changes: 2 additions & 0 deletions docker/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
67 changes: 67 additions & 0 deletions docker/resource_docker_plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
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{
"plugin_reference": {
suzuki-shunsuke marked this conversation as resolved.
Show resolved Hide resolved
Type: schema.TypeString,
Description: "Docker Plugin Reference.",
Required: true,
ForceNew: true,
},
"alias": {
Type: schema.TypeString,
Computed: true,
Optional: true,
ForceNew: true,
Description: "Docker Plugin alias.",
},
suzuki-shunsuke marked this conversation as resolved.
Show resolved Hide resolved
"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,
Computed: true,
Elem: &schema.Schema{Type: schema.TypeString},
},

"disable_when_set": {
mavogel marked this conversation as resolved.
Show resolved Hide resolved
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,
},
"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.",
},
},
mavogel marked this conversation as resolved.
Show resolved Hide resolved
}
}
165 changes: 165 additions & 0 deletions docker/resource_docker_plugin_funcs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package docker

import (
"context"
"fmt"
"io/ioutil"
"log"

"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
)

func getDockerPluginArgs(src interface{}) []string {
if src == nil {
return nil
}
b := src.(*schema.Set)
args := make([]string, b.Len())
for i, a := range b.List() {
args[i] = a.(string)
}
return args
}

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{
RemoteRef: pluginRef,
AcceptAllPermissions: d.Get("grant_all_permissions").(bool),
Disabled: d.Get("disabled").(bool),
Args: getDockerPluginArgs(d.Get("args")),
})
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should implement AcceptPermissionsFunc that matches against permissions listed in a resource attribute that ConfictsWith "grant_all_permissions"

https://pkg.go.dev/github.com/docker/docker/api/types#PluginInstallOptions

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know why this comment is being hidden but it is vital to the initial implementation. For production use grant_all_permissions must be false and the permissions need to be explicitly provided.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@suzuki-shunsuke can you take a look, please

Copy link
Collaborator Author

@suzuki-shunsuke suzuki-shunsuke Dec 30, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have added an attribute grant_permissions.

03b2187

TODO

if err != nil {
return fmt.Errorf("install a Docker plugin "+pluginRef+": %w", err)
}
_, _ = ioutil.ReadAll(body)
key := pluginRef
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("disabled", !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
suzuki-shunsuke marked this conversation as resolved.
Show resolved Hide resolved
d.Set("args", plugin.Settings.Env)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Env is not the correct value for Args.
You need to implement all of https://pkg.go.dev/github.com/docker/docker/api/types#PluginSettings

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I misunderstood the purpose of this value. You might want to change the attribute to env to make clear what it is doing.

}

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 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)
}
return nil
}

func resourceDockerPluginUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*ProviderConfig).DockerClient
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 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),
}); err != nil {
return fmt.Errorf("enable the Docker plugin "+pluginID+": %w", err)
}
}
}
if !skipArgs {
suzuki-shunsuke marked this conversation as resolved.
Show resolved Hide resolved
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 && d.Get("disable_when_set").(bool)); err != nil {
return err
}
}
suzuki-shunsuke marked this conversation as resolved.
Show resolved Hide resolved
// 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
}
Loading