mirror of
https://github.com/kreuzwerker/terraform-provider-docker.git
synced 2026-02-02 11:49:29 -05:00
feat: Implement docker cluster volume (#793)
* feat: Implement docker cluster volume * fix: linter findings * fix: Make cluster attribute updateable
This commit is contained in:
parent
b78f540ded
commit
e2c8d0b73a
6 changed files with 879 additions and 5 deletions
|
|
@ -23,6 +23,7 @@ resource "docker_volume" "shared_volume" {
|
|||
|
||||
### Optional
|
||||
|
||||
- `cluster` (Block List, Max: 1) Cluster-specific options for volume creation. Only works if the Docker daemon is running in swarm mode and is the swarm manager. (see [below for nested schema](#nestedblock--cluster))
|
||||
- `driver` (String) Driver type for the volume. Defaults to `local`.
|
||||
- `driver_opts` (Map of String) Options specific to the driver.
|
||||
- `labels` (Block Set) User-defined key/value metadata (see [below for nested schema](#nestedblock--labels))
|
||||
|
|
@ -33,6 +34,26 @@ resource "docker_volume" "shared_volume" {
|
|||
- `id` (String) The ID of this resource.
|
||||
- `mountpoint` (String) The mountpoint of the volume.
|
||||
|
||||
<a id="nestedblock--cluster"></a>
|
||||
### Nested Schema for `cluster`
|
||||
|
||||
Optional:
|
||||
|
||||
- `availability` (String) Availability of the volume. Can be `active` (default), `pause`, or `drain`.
|
||||
- `group` (String) Cluster Volume group
|
||||
- `limit_bytes` (String) Minimum size of the Cluster Volume in human readable memory bytes (like 128MiB, 2GiB, etc). Must be in format of KiB, MiB, Gib, Tib or PiB.
|
||||
- `required_bytes` (String) Maximum size of the Cluster Volume in human readable memory bytes (like 128MiB, 2GiB, etc). Must be in format of KiB, MiB, Gib, Tib or PiB.
|
||||
- `scope` (String) The scope of the volume. Can be `single` (default) or `multi`.
|
||||
- `sharing` (String) The sharing mode. Can be `none` (default), `readonly`, `onewriter` or `all`.
|
||||
- `topology_preferred` (String) A topology that the Cluster Volume would be preferred in
|
||||
- `topology_required` (String) A topology that the Cluster Volume must be accessible from
|
||||
- `type` (String) Cluster Volume access type. Can be `mount` or `block` (default).
|
||||
|
||||
Read-Only:
|
||||
|
||||
- `id` (String) The ID of the cluster volume.
|
||||
|
||||
|
||||
<a id="nestedblock--labels"></a>
|
||||
### Nested Schema for `labels`
|
||||
|
||||
|
|
|
|||
37
go.mod
37
go.mod
|
|
@ -33,6 +33,14 @@ require (
|
|||
require (
|
||||
4d63.com/gocheckcompilerdirectives v1.3.0 // indirect
|
||||
4d63.com/gochecknoglobals v0.2.2 // indirect
|
||||
cel.dev/expr v0.20.0 // indirect
|
||||
cloud.google.com/go v0.116.0 // indirect
|
||||
cloud.google.com/go/auth v0.15.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
||||
cloud.google.com/go/iam v1.2.2 // indirect
|
||||
cloud.google.com/go/monitoring v1.21.2 // indirect
|
||||
cloud.google.com/go/storage v1.49.0 // indirect
|
||||
github.com/4meepo/tagalign v1.4.2 // indirect
|
||||
github.com/Abirdcfly/dupword v0.1.3 // indirect
|
||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect
|
||||
|
|
@ -44,9 +52,14 @@ require (
|
|||
github.com/Crocmagnon/fatcontext v0.7.1 // indirect
|
||||
github.com/Djarvur/go-err113 v0.1.0 // indirect
|
||||
github.com/GaijinEntertainment/go-exhaustruct/v3 v3.3.1 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.26.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 // indirect
|
||||
github.com/Kunde21/markdownfmt/v3 v3.1.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Masterminds/semver/v3 v3.3.0 // indirect
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible // indirect
|
||||
github.com/Masterminds/sprig/v3 v3.2.3 // indirect
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/OpenPeeDeeP/depguard/v2 v2.2.1 // indirect
|
||||
|
|
@ -57,10 +70,12 @@ require (
|
|||
github.com/alexkohler/prealloc v1.0.0 // indirect
|
||||
github.com/alingse/asasalint v0.0.11 // indirect
|
||||
github.com/alingse/nilnesserr v0.1.2 // indirect
|
||||
github.com/apparentlymart/go-cidr v1.1.0 // indirect
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||
github.com/armon/go-radix v1.0.0 // indirect
|
||||
github.com/ashanbrown/forbidigo v1.6.0 // indirect
|
||||
github.com/ashanbrown/makezero v1.2.0 // indirect
|
||||
github.com/aws/aws-sdk-go v1.37.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.30.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.27.27 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.27 // indirect
|
||||
|
|
@ -75,6 +90,7 @@ require (
|
|||
github.com/aws/aws-sdk-go-v2/service/sts v1.30.3 // indirect
|
||||
github.com/aws/smithy-go v1.20.3 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
|
||||
github.com/bgentry/speakeasy v0.1.0 // indirect
|
||||
github.com/bkielbasa/cyclop v1.2.3 // indirect
|
||||
github.com/blizzy78/varnamelen v0.8.0 // indirect
|
||||
|
|
@ -92,6 +108,7 @@ require (
|
|||
github.com/chavacava/garif v0.1.0 // indirect
|
||||
github.com/ckaznocha/intrange v0.3.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect
|
||||
github.com/containerd/containerd/api v1.8.0 // indirect
|
||||
github.com/containerd/containerd/v2 v2.0.5 // indirect
|
||||
github.com/containerd/continuity v0.4.5 // indirect
|
||||
|
|
@ -108,6 +125,8 @@ require (
|
|||
github.com/docker/docker-credential-helpers v0.9.3 // indirect
|
||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
|
||||
github.com/ettle/strcase v0.2.0 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fatih/structtag v1.2.0 // indirect
|
||||
|
|
@ -119,6 +138,7 @@ require (
|
|||
github.com/fzipp/gocyclo v0.6.0 // indirect
|
||||
github.com/ghostiam/protogetter v0.3.9 // indirect
|
||||
github.com/go-critic/go-critic v0.12.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
|
|
@ -136,6 +156,7 @@ require (
|
|||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/gofrs/flock v0.12.1 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32 // indirect
|
||||
github.com/golangci/go-printf-func-name v0.1.0 // indirect
|
||||
|
|
@ -147,8 +168,11 @@ require (
|
|||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
|
||||
github.com/gookit/color v1.5.4 // indirect
|
||||
github.com/gordonklaus/ineffassign v0.1.0 // indirect
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
|
|
@ -161,11 +185,13 @@ require (
|
|||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-checkpoint v0.5.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-getter v1.5.3 // indirect
|
||||
github.com/hashicorp/go-hclog v1.6.3 // indirect
|
||||
github.com/hashicorp/go-immutable-radix/v2 v2.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-plugin v1.6.3 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
|
||||
github.com/hashicorp/go-safetemp v1.0.0 // indirect
|
||||
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
||||
github.com/hashicorp/go-version v1.7.0 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
|
|
@ -176,6 +202,7 @@ require (
|
|||
github.com/hashicorp/terraform-exec v0.23.0 // indirect
|
||||
github.com/hashicorp/terraform-json v0.25.0 // indirect
|
||||
github.com/hashicorp/terraform-plugin-go v0.27.0 // indirect
|
||||
github.com/hashicorp/terraform-plugin-sdk v1.17.2 // indirect
|
||||
github.com/hashicorp/terraform-registry-address v0.2.5 // indirect
|
||||
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
|
||||
github.com/hashicorp/yamux v0.1.1 // indirect
|
||||
|
|
@ -187,6 +214,7 @@ require (
|
|||
github.com/jgautheron/goconst v1.7.1 // indirect
|
||||
github.com/jingyugao/rowserrcheck v1.1.1 // indirect
|
||||
github.com/jjti/go-spancheck v0.6.4 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/julz/importas v0.2.0 // indirect
|
||||
|
|
@ -215,6 +243,7 @@ require (
|
|||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||
github.com/mgechev/revive v1.7.0 // indirect
|
||||
github.com/mitchellh/cli v1.1.2 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
|
|
@ -284,6 +313,7 @@ require (
|
|||
github.com/spf13/jwalterweatherman v1.1.0 // indirect
|
||||
github.com/spf13/pflag v1.0.6 // indirect
|
||||
github.com/spf13/viper v1.13.0 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
|
||||
github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect
|
||||
github.com/stbenjam/no-sprintf-host-port v0.2.0 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
|
|
@ -300,6 +330,7 @@ require (
|
|||
github.com/tonistiigi/go-csvvalue v0.0.0-20240710180619-ddb21b71c0b4 // indirect
|
||||
github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect
|
||||
github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab // indirect
|
||||
github.com/ulikunitz/xz v0.5.8 // indirect
|
||||
github.com/ultraware/funlen v0.2.0 // indirect
|
||||
github.com/ultraware/whitespace v0.2.0 // indirect
|
||||
github.com/uudashr/gocognit v1.2.0 // indirect
|
||||
|
|
@ -316,11 +347,15 @@ require (
|
|||
github.com/yuin/goldmark v1.7.7 // indirect
|
||||
github.com/yuin/goldmark-meta v1.1.0 // indirect
|
||||
github.com/zclconf/go-cty v1.16.3 // indirect
|
||||
github.com/zclconf/go-cty-yaml v1.0.2 // indirect
|
||||
github.com/zeebo/errs v1.4.0 // indirect
|
||||
gitlab.com/bosi/decorder v0.4.2 // indirect
|
||||
go-simpler.org/musttag v0.13.0 // indirect
|
||||
go-simpler.org/sloglint v0.9.0 // indirect
|
||||
go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.34.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.56.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect
|
||||
|
|
@ -348,7 +383,9 @@ require (
|
|||
golang.org/x/text v0.26.0 // indirect
|
||||
golang.org/x/time v0.11.0 // indirect
|
||||
golang.org/x/tools v0.33.0 // indirect
|
||||
google.golang.org/api v0.223.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250219182151-9fdb1cabc7b2 // indirect
|
||||
google.golang.org/grpc v1.72.1 // indirect
|
||||
|
|
|
|||
|
|
@ -3,14 +3,19 @@ package provider
|
|||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types/volume"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
||||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -26,6 +31,7 @@ func resourceDockerVolume() *schema.Resource {
|
|||
CreateContext: resourceDockerVolumeCreate,
|
||||
ReadContext: resourceDockerVolumeRead,
|
||||
DeleteContext: resourceDockerVolumeDelete,
|
||||
UpdateContext: resourceDockerVolumeUpdate,
|
||||
Importer: &schema.ResourceImporter{
|
||||
StateContext: schema.ImportStatePassthroughContext,
|
||||
},
|
||||
|
|
@ -63,6 +69,85 @@ func resourceDockerVolume() *schema.Resource {
|
|||
Description: "The mountpoint of the volume.",
|
||||
Computed: true,
|
||||
},
|
||||
"cluster": {
|
||||
Type: schema.TypeList,
|
||||
Description: "Cluster-specific options for volume creation. Only works if the Docker daemon is running in swarm mode and is the swarm manager.",
|
||||
Optional: true,
|
||||
MaxItems: 1,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"id": {
|
||||
Type: schema.TypeString,
|
||||
Description: "The ID of the cluster volume.",
|
||||
Computed: true,
|
||||
},
|
||||
"scope": {
|
||||
Type: schema.TypeString,
|
||||
Description: "The scope of the volume. Can be `single` (default) or `multi`.",
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
ValidateFunc: validation.StringInSlice([]string{"single", "multi"}, false),
|
||||
Default: "single",
|
||||
},
|
||||
"sharing": {
|
||||
Type: schema.TypeString,
|
||||
Description: "The sharing mode. Can be `none` (default), `readonly`, `onewriter` or `all`.",
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
ValidateFunc: validation.StringInSlice([]string{"none", "readonly", "onewriter", "all"}, false),
|
||||
Default: "none",
|
||||
},
|
||||
"type": {
|
||||
Type: schema.TypeString,
|
||||
Description: "Cluster Volume access type. Can be `mount` or `block` (default).",
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
ValidateFunc: validation.StringInSlice([]string{"mount", "block"}, false),
|
||||
Default: "block",
|
||||
},
|
||||
"topology_preferred": {
|
||||
Type: schema.TypeString,
|
||||
Description: "A topology that the Cluster Volume would be preferred in",
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
Default: "",
|
||||
},
|
||||
"topology_required": {
|
||||
Type: schema.TypeString,
|
||||
Description: "A topology that the Cluster Volume must be accessible from",
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
Default: "",
|
||||
},
|
||||
"required_bytes": {
|
||||
Type: schema.TypeString,
|
||||
Description: "Maximum size of the Cluster Volume in human readable memory bytes (like 128MiB, 2GiB, etc). Must be in format of KiB, MiB, Gib, Tib or PiB.",
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
"limit_bytes": {
|
||||
Type: schema.TypeString,
|
||||
Description: "Minimum size of the Cluster Volume in human readable memory bytes (like 128MiB, 2GiB, etc). Must be in format of KiB, MiB, Gib, Tib or PiB.",
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
"group": {
|
||||
Type: schema.TypeString,
|
||||
Description: "Cluster Volume group",
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
Default: "",
|
||||
},
|
||||
"availability": {
|
||||
Type: schema.TypeString,
|
||||
Description: "Availability of the volume. Can be `active` (default), `pause`, or `drain`.",
|
||||
Optional: true,
|
||||
ValidateFunc: validation.StringInSlice([]string{"active", "pause", "drain"}, false),
|
||||
Default: "active",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
SchemaVersion: 1,
|
||||
StateUpgraders: []schema.StateUpgrader{
|
||||
|
|
@ -95,6 +180,96 @@ func resourceDockerVolumeCreate(ctx context.Context, d *schema.ResourceData, met
|
|||
createOpts.DriverOpts = mapTypeMapValsToString(v.(map[string]interface{}))
|
||||
}
|
||||
|
||||
// Handle cluster volume options
|
||||
if v, ok := d.GetOk("cluster"); ok {
|
||||
if client.ClientVersion() < "1.42" {
|
||||
return diag.Errorf("You have supplied a 'cluster' block for your docker_volume resource. This is supported starting with client version '1.42', but you have %s", client.ClientVersion())
|
||||
}
|
||||
|
||||
clusterList := v.([]interface{})
|
||||
if len(clusterList) > 0 {
|
||||
clusterConfig := clusterList[0].(map[string]interface{})
|
||||
|
||||
createOpts.ClusterVolumeSpec = &volume.ClusterVolumeSpec{
|
||||
Group: clusterConfig["group"].(string),
|
||||
AccessMode: &volume.AccessMode{
|
||||
Scope: volume.Scope(clusterConfig["scope"].(string)),
|
||||
Sharing: volume.SharingMode(clusterConfig["sharing"].(string)),
|
||||
},
|
||||
Availability: volume.Availability(clusterConfig["availability"].(string)),
|
||||
}
|
||||
|
||||
switch clusterConfig["type"].(string) {
|
||||
case "mount":
|
||||
createOpts.ClusterVolumeSpec.AccessMode.MountVolume = &volume.TypeMount{}
|
||||
case "block":
|
||||
createOpts.ClusterVolumeSpec.AccessMode.BlockVolume = &volume.TypeBlock{}
|
||||
}
|
||||
|
||||
vcr := &volume.CapacityRange{}
|
||||
var memBytes opts.MemBytes
|
||||
if r := clusterConfig["required_bytes"].(string); len(r) > 0 {
|
||||
if err := memBytes.Set(r); err != nil {
|
||||
return diag.Errorf("Invalid value for required_bytes: %s", err)
|
||||
}
|
||||
vcr.RequiredBytes = memBytes.Value()
|
||||
fmt.Printf("[DEBUG] Required bytes set to %d\n", vcr.RequiredBytes)
|
||||
}
|
||||
|
||||
if l := clusterConfig["limit_bytes"].(string); len(l) > 0 {
|
||||
if err := memBytes.Set(l); err != nil {
|
||||
return diag.Errorf("Invalid value for limit_bytes: %s", err)
|
||||
}
|
||||
vcr.LimitBytes = memBytes.Value()
|
||||
fmt.Printf("[DEBUG] Limit bytes set to %d\n", vcr.LimitBytes)
|
||||
}
|
||||
createOpts.ClusterVolumeSpec.CapacityRange = vcr
|
||||
|
||||
if secretsList, ok := clusterConfig["secrets"].([]interface{}); ok && len(secretsList) > 0 {
|
||||
secrets := make([]volume.Secret, len(secretsList))
|
||||
for i, secretItem := range secretsList {
|
||||
secret := secretItem.(map[string]interface{})
|
||||
secrets[i] = volume.Secret{
|
||||
Key: secret["key"].(string),
|
||||
Secret: secret["secret"].(string),
|
||||
}
|
||||
}
|
||||
createOpts.ClusterVolumeSpec.Secrets = secrets
|
||||
sort.SliceStable(createOpts.ClusterVolumeSpec.Secrets, func(i, j int) bool {
|
||||
return createOpts.ClusterVolumeSpec.Secrets[i].Key < createOpts.ClusterVolumeSpec.Secrets[j].Key
|
||||
})
|
||||
}
|
||||
|
||||
topology := &volume.TopologyRequirement{}
|
||||
|
||||
if topology_required, ok := clusterConfig["topology_required"].(string); ok && len(topology_required) > 0 {
|
||||
segments := map[string]string{}
|
||||
for _, segment := range strings.Split(topology_required, ",") {
|
||||
k, v, _ := strings.Cut(segment, "=")
|
||||
segments[k] = v
|
||||
}
|
||||
|
||||
topology.Requisite = append(
|
||||
topology.Requisite,
|
||||
volume.Topology{Segments: segments},
|
||||
)
|
||||
}
|
||||
if topology_preferred, ok := clusterConfig["topology_preferred"].(string); ok && len(topology_preferred) > 0 {
|
||||
segments := map[string]string{}
|
||||
for _, segment := range strings.Split(topology_preferred, ",") {
|
||||
k, v, _ := strings.Cut(segment, "=")
|
||||
segments[k] = v
|
||||
}
|
||||
|
||||
topology.Preferred = append(
|
||||
topology.Preferred,
|
||||
volume.Topology{Segments: segments},
|
||||
)
|
||||
}
|
||||
|
||||
createOpts.ClusterVolumeSpec.AccessibilityRequirements = topology
|
||||
}
|
||||
}
|
||||
var err error
|
||||
var retVolume volume.Volume
|
||||
retVolume, err = client.VolumeCreate(ctx, createOpts)
|
||||
|
|
@ -114,7 +289,7 @@ func resourceDockerVolumeRead(ctx context.Context, d *schema.ResourceData, meta
|
|||
|
||||
if err != nil {
|
||||
if errdefs.IsNotFound(err) {
|
||||
log.Printf("[WARN] Volume with id '%s' not found, removing from state", d.Id())
|
||||
log.Printf("[WARN] Volume with id `%s` not found, removing from state", d.Id())
|
||||
d.SetId("")
|
||||
return nil
|
||||
}
|
||||
|
|
@ -130,11 +305,56 @@ func resourceDockerVolumeRead(ctx context.Context, d *schema.ResourceData, meta
|
|||
d.Set("driver_opts", volume.Options)
|
||||
d.Set("mountpoint", volume.Mountpoint)
|
||||
|
||||
// check if volume.ClusterVolume is set
|
||||
if volume.ClusterVolume != nil {
|
||||
|
||||
typeValue := "block"
|
||||
if volume.ClusterVolume.Spec.AccessMode.MountVolume != nil {
|
||||
typeValue = "mount"
|
||||
}
|
||||
|
||||
// loop over volume.ClusterVolume.Spec.AccessibilityRequirements.Preferred
|
||||
topologyPreferred := []string{}
|
||||
for _, segment := range volume.ClusterVolume.Spec.AccessibilityRequirements.Preferred {
|
||||
for key, value := range segment.Segments {
|
||||
topologyPreferred = append(topologyPreferred, fmt.Sprintf("%s=%s", key, value))
|
||||
}
|
||||
}
|
||||
topologyRequired := []string{}
|
||||
for _, segment := range volume.ClusterVolume.Spec.AccessibilityRequirements.Requisite {
|
||||
for key, value := range segment.Segments {
|
||||
topologyRequired = append(topologyRequired, fmt.Sprintf("%s=%s", key, value))
|
||||
}
|
||||
}
|
||||
|
||||
d.Set("cluster", []interface{}{
|
||||
map[string]interface{}{
|
||||
"id": volume.ClusterVolume.ID,
|
||||
"scope": volume.ClusterVolume.Spec.AccessMode.Scope,
|
||||
"sharing": volume.ClusterVolume.Spec.AccessMode.Sharing,
|
||||
"group": volume.ClusterVolume.Spec.Group,
|
||||
"availability": volume.ClusterVolume.Spec.Availability,
|
||||
"type": typeValue,
|
||||
"required_bytes": func() string {
|
||||
mb := opts.MemBytes(0)
|
||||
mb = opts.MemBytes(volume.ClusterVolume.Spec.CapacityRange.RequiredBytes)
|
||||
return mb.String()
|
||||
}(),
|
||||
"limit_bytes": func() string {
|
||||
mb := opts.MemBytes(0)
|
||||
mb = opts.MemBytes(volume.ClusterVolume.Spec.CapacityRange.LimitBytes)
|
||||
return mb.String()
|
||||
}(),
|
||||
"topology_preferred": strings.Join(topologyPreferred, ","),
|
||||
"topology_required": strings.Join(topologyRequired, ","),
|
||||
},
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceDockerVolumeDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
log.Printf("[INFO] Waiting for volume: '%s' to get removed: max '%v seconds'", d.Id(), volumeReadRefreshTimeout)
|
||||
log.Printf("[INFO] Waiting for volume: `%s` to get removed: max `%v seconds`", d.Id(), volumeReadRefreshTimeout)
|
||||
|
||||
stateConf := &retry.StateChangeConf{
|
||||
Pending: []string{"in_use"},
|
||||
|
|
@ -155,6 +375,44 @@ func resourceDockerVolumeDelete(ctx context.Context, d *schema.ResourceData, met
|
|||
return nil
|
||||
}
|
||||
|
||||
func resourceDockerVolumeUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
||||
attrs := []string{
|
||||
"cluster.availability",
|
||||
}
|
||||
for _, attr := range attrs {
|
||||
if d.HasChange(attr) {
|
||||
clusterList := d.Get("cluster").([]interface{})
|
||||
if len(clusterList) > 0 {
|
||||
clusterConfig := clusterList[0].(map[string]interface{})
|
||||
client := meta.(*ProviderConfig).DockerClient
|
||||
|
||||
vol, _, err := client.VolumeInspectWithRaw(ctx, clusterConfig["id"].(string))
|
||||
if err != nil {
|
||||
return diag.FromErr(err)
|
||||
}
|
||||
|
||||
if vol.ClusterVolume == nil || d.Get("cluster") == nil {
|
||||
return diag.Errorf("Can only update cluster volumes")
|
||||
}
|
||||
|
||||
vol.ClusterVolume.Spec.Availability = volume.Availability(clusterConfig["availability"].(string))
|
||||
|
||||
err = client.VolumeUpdate(
|
||||
ctx, vol.ClusterVolume.ID, vol.ClusterVolume.Version,
|
||||
volume.UpdateOptions{
|
||||
Spec: &vol.ClusterVolume.Spec,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return diag.Errorf("Unable to update the cluster volume: %v", err)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func resourceDockerVolumeRemoveRefreshFunc(
|
||||
volumeID string, meta interface{}) retry.StateRefreshFunc {
|
||||
return func() (interface{}, string, error) {
|
||||
|
|
@ -163,13 +421,13 @@ func resourceDockerVolumeRemoveRefreshFunc(
|
|||
|
||||
if err := client.VolumeRemove(context.Background(), volumeID, forceDelete); err != nil {
|
||||
if containsIgnorableErrorMessage(err.Error(), "volume is in use") {
|
||||
log.Printf("[INFO] Volume with id '%v' is still in use", volumeID)
|
||||
log.Printf("[INFO] Volume with id `%v` is still in use", volumeID)
|
||||
return volumeID, "in_use", nil
|
||||
}
|
||||
log.Printf("[INFO] Removing volume with id '%v' caused an error: %v", volumeID, err)
|
||||
log.Printf("[INFO] Removing volume with id `%v` caused an error: %v", volumeID, err)
|
||||
return nil, "", err
|
||||
}
|
||||
log.Printf("[INFO] Removing volume with id '%v' got removed", volumeID)
|
||||
log.Printf("[INFO] Removing volume with id `%v` got removed", volumeID)
|
||||
return volumeID, "removed", nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -193,3 +193,33 @@ func checkDockerVolumeCreated(n string, volumeToCheck *volume.Volume) resource.T
|
|||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccDockerVolume_cluster(t *testing.T) {
|
||||
var v volume.Volume
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
ProviderFactories: providerFactories,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: loadTestConfiguration(t, RESOURCE, "docker_volume", "testAccDockerVolumeCluster"),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestCheckResourceAttr("docker_volume.foo", "id", "testAccDockerVolume_cluster"),
|
||||
resource.TestCheckResourceAttr("docker_volume.foo", "name", "testAccDockerVolume_cluster"),
|
||||
resource.TestCheckResourceAttr("docker_volume.foo", "cluster.0.scope", "multi"),
|
||||
resource.TestCheckResourceAttr("docker_volume.foo", "cluster.0.required_bytes", "1MiB"),
|
||||
resource.TestCheckResourceAttr("docker_volume.foo", "cluster.0.limit_bytes", "2MiB"),
|
||||
resource.TestCheckResourceAttr("docker_volume.foo", "cluster.0.sharing", "all"),
|
||||
resource.TestCheckResourceAttr("docker_volume.foo", "cluster.0.group", "testgroup"),
|
||||
checkDockerVolumeCreated("docker_volume.foo", &v),
|
||||
// testCheckVolumeInspect,
|
||||
),
|
||||
},
|
||||
{
|
||||
ResourceName: "docker_volume.foo",
|
||||
ImportState: true,
|
||||
ImportStateVerify: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
|||
19
testdata/resources/docker_volume/testAccDockerVolumeCluster.tf
vendored
Normal file
19
testdata/resources/docker_volume/testAccDockerVolumeCluster.tf
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
resource "docker_volume" "foo" {
|
||||
name = "testAccDockerVolume_cluster"
|
||||
|
||||
driver = "local"
|
||||
driver_opts = {
|
||||
type = "btrfs"
|
||||
device = "/dev/sda2"
|
||||
}
|
||||
|
||||
cluster {
|
||||
scope = "multi"
|
||||
sharing = "all"
|
||||
group = "testgroup"
|
||||
|
||||
required_bytes = "1MiB"
|
||||
limit_bytes = "2MiB"
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Reference in a new issue