mirror of
https://github.com/kreuzwerker/terraform-provider-docker.git
synced 2026-02-10 14:23:43 -05:00
Feat/swarm 2 refactorings (#38)
* Removed id attribute of network resource. * Extracted common validators. Removed custom hash functions.
This commit is contained in:
parent
656c8a8c2f
commit
5a40076c95
5 changed files with 257 additions and 188 deletions
|
|
@ -1,12 +1,10 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"regexp"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/hashcode"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
)
|
||||
|
||||
|
|
@ -118,18 +116,11 @@ func resourceDockerContainer() *schema.Resource {
|
|||
},
|
||||
|
||||
"restart": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
Default: "no",
|
||||
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
|
||||
value := v.(string)
|
||||
if !regexp.MustCompile(`^(no|on-failure|always|unless-stopped)$`).MatchString(value) {
|
||||
es = append(es, fmt.Errorf(
|
||||
"%q must be one of \"no\", \"on-failure\", \"always\" or \"unless-stopped\"", k))
|
||||
}
|
||||
return
|
||||
},
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
Default: "no",
|
||||
ValidateFunc: validateStringMatchesPattern(`^(no|on-failure|always|unless-stopped)$`),
|
||||
},
|
||||
|
||||
"max_retry_count": &schema.Schema{
|
||||
|
|
@ -162,7 +153,6 @@ func resourceDockerContainer() *schema.Resource {
|
|||
},
|
||||
},
|
||||
},
|
||||
Set: resourceDockerCapabilitiesHash,
|
||||
},
|
||||
|
||||
"volumes": &schema.Schema{
|
||||
|
|
@ -203,7 +193,6 @@ func resourceDockerContainer() *schema.Resource {
|
|||
},
|
||||
},
|
||||
},
|
||||
Set: resourceDockerVolumesHash,
|
||||
},
|
||||
|
||||
"ports": &schema.Schema{
|
||||
|
|
@ -238,7 +227,6 @@ func resourceDockerContainer() *schema.Resource {
|
|||
},
|
||||
},
|
||||
},
|
||||
Set: resourceDockerPortsHash,
|
||||
},
|
||||
|
||||
"host": &schema.Schema{
|
||||
|
|
@ -260,7 +248,6 @@ func resourceDockerContainer() *schema.Resource {
|
|||
},
|
||||
},
|
||||
},
|
||||
Set: resourceDockerHostsHash,
|
||||
},
|
||||
|
||||
"env": &schema.Schema{
|
||||
|
|
@ -317,57 +304,32 @@ func resourceDockerContainer() *schema.Resource {
|
|||
},
|
||||
|
||||
"memory": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
|
||||
value := v.(int)
|
||||
if value < 0 {
|
||||
es = append(es, fmt.Errorf("%q must be greater than or equal to 0", k))
|
||||
}
|
||||
return
|
||||
},
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
ValidateFunc: validateIntegerGeqThan(0),
|
||||
},
|
||||
|
||||
"memory_swap": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
|
||||
value := v.(int)
|
||||
if value < -1 {
|
||||
es = append(es, fmt.Errorf("%q must be greater than or equal to -1", k))
|
||||
}
|
||||
return
|
||||
},
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
ValidateFunc: validateIntegerGeqThan(-1),
|
||||
},
|
||||
|
||||
"cpu_shares": &schema.Schema{
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
|
||||
value := v.(int)
|
||||
if value < 0 {
|
||||
es = append(es, fmt.Errorf("%q must be greater than or equal to 0", k))
|
||||
}
|
||||
return
|
||||
},
|
||||
Type: schema.TypeInt,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
ValidateFunc: validateIntegerGeqThan(0),
|
||||
},
|
||||
|
||||
"log_driver": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
Default: "json-file",
|
||||
ValidateFunc: func(v interface{}, k string) (ws []string, es []error) {
|
||||
value := v.(string)
|
||||
if !regexp.MustCompile(`^(json-file|syslog|journald|gelf|fluentd|awslogs)$`).MatchString(value) {
|
||||
es = append(es, fmt.Errorf(
|
||||
"%q must be one of \"json-file\", \"syslog\", \"journald\", \"gelf\", \"fluentd\", or \"awslogs\"", k))
|
||||
}
|
||||
return
|
||||
},
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
Default: "json-file",
|
||||
ValidateFunc: validateStringMatchesPattern(`^(json-file|syslog|journald|gelf|fluentd|awslogs)$`),
|
||||
},
|
||||
|
||||
"log_opts": &schema.Schema{
|
||||
|
|
@ -418,105 +380,11 @@ func resourceDockerContainer() *schema.Resource {
|
|||
},
|
||||
},
|
||||
},
|
||||
Set: resourceDockerUploadHash,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceDockerCapabilitiesHash(v interface{}) int {
|
||||
var buf bytes.Buffer
|
||||
m := v.(map[string]interface{})
|
||||
|
||||
if v, ok := m["add"]; ok {
|
||||
buf.WriteString(fmt.Sprintf("%v-", v))
|
||||
}
|
||||
|
||||
if v, ok := m["remove"]; ok {
|
||||
buf.WriteString(fmt.Sprintf("%v-", v))
|
||||
}
|
||||
|
||||
return hashcode.String(buf.String())
|
||||
}
|
||||
|
||||
func resourceDockerPortsHash(v interface{}) int {
|
||||
var buf bytes.Buffer
|
||||
m := v.(map[string]interface{})
|
||||
|
||||
buf.WriteString(fmt.Sprintf("%v-", m["internal"].(int)))
|
||||
|
||||
if v, ok := m["external"]; ok {
|
||||
buf.WriteString(fmt.Sprintf("%v-", v.(int)))
|
||||
}
|
||||
|
||||
if v, ok := m["ip"]; ok {
|
||||
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
|
||||
}
|
||||
|
||||
if v, ok := m["protocol"]; ok {
|
||||
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
|
||||
}
|
||||
|
||||
return hashcode.String(buf.String())
|
||||
}
|
||||
|
||||
func resourceDockerHostsHash(v interface{}) int {
|
||||
var buf bytes.Buffer
|
||||
m := v.(map[string]interface{})
|
||||
|
||||
if v, ok := m["ip"]; ok {
|
||||
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
|
||||
}
|
||||
|
||||
if v, ok := m["host"]; ok {
|
||||
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
|
||||
}
|
||||
|
||||
return hashcode.String(buf.String())
|
||||
}
|
||||
|
||||
func resourceDockerVolumesHash(v interface{}) int {
|
||||
var buf bytes.Buffer
|
||||
m := v.(map[string]interface{})
|
||||
|
||||
if v, ok := m["from_container"]; ok {
|
||||
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
|
||||
}
|
||||
|
||||
if v, ok := m["container_path"]; ok {
|
||||
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
|
||||
}
|
||||
|
||||
if v, ok := m["host_path"]; ok {
|
||||
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
|
||||
}
|
||||
|
||||
if v, ok := m["volume_name"]; ok {
|
||||
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
|
||||
}
|
||||
|
||||
if v, ok := m["read_only"]; ok {
|
||||
buf.WriteString(fmt.Sprintf("%v-", v.(bool)))
|
||||
}
|
||||
|
||||
return hashcode.String(buf.String())
|
||||
}
|
||||
|
||||
func resourceDockerUploadHash(v interface{}) int {
|
||||
var buf bytes.Buffer
|
||||
m := v.(map[string]interface{})
|
||||
|
||||
if v, ok := m["content"]; ok {
|
||||
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
|
||||
}
|
||||
|
||||
if v, ok := m["file"]; ok {
|
||||
buf.WriteString(fmt.Sprintf("%v-", v.(string)))
|
||||
}
|
||||
|
||||
return hashcode.String(buf.String())
|
||||
}
|
||||
|
||||
func validateDockerContainerPath(v interface{}, k string) (ws []string, errors []error) {
|
||||
|
||||
value := v.(string)
|
||||
|
|
|
|||
|
|
@ -59,8 +59,34 @@ func resourceDockerNetwork() *schema.Resource {
|
|||
Type: schema.TypeSet,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
Elem: getIpamConfigElem(),
|
||||
Set: resourceDockerIpamConfigHash,
|
||||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"subnet": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"ip_range": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"gateway": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"aux_address": &schema.Schema{
|
||||
Type: schema.TypeMap,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
Set: resourceDockerIpamConfigHash,
|
||||
},
|
||||
|
||||
"scope": &schema.Schema{
|
||||
|
|
@ -71,36 +97,6 @@ func resourceDockerNetwork() *schema.Resource {
|
|||
}
|
||||
}
|
||||
|
||||
func getIpamConfigElem() *schema.Resource {
|
||||
return &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"subnet": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"ip_range": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"gateway": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
|
||||
"aux_address": &schema.Schema{
|
||||
Type: schema.TypeMap,
|
||||
Optional: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func resourceDockerIpamConfigHash(v interface{}) int {
|
||||
var buf bytes.Buffer
|
||||
m := v.(map[string]interface{})
|
||||
|
|
@ -122,7 +118,7 @@ func resourceDockerIpamConfigHash(v interface{}) int {
|
|||
|
||||
keys := make([]string, len(auxAddress))
|
||||
i := 0
|
||||
for k, _ := range auxAddress {
|
||||
for k := range auxAddress {
|
||||
keys[i] = k
|
||||
i++
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ package docker
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
|
||||
dc "github.com/fsouza/go-dockerclient"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
|
|
@ -94,7 +96,24 @@ func resourceDockerVolumeDelete(d *schema.ResourceData, meta interface{}) error
|
|||
client := meta.(*ProviderConfig).DockerClient
|
||||
|
||||
if err := client.RemoveVolume(d.Id()); err != nil && err != dc.ErrNoSuchVolume {
|
||||
return fmt.Errorf("Error deleting volume %s: %s", d.Id(), err)
|
||||
if err == dc.ErrVolumeInUse {
|
||||
loops := 50
|
||||
sleepTime := 1000 * time.Millisecond
|
||||
for i := loops; i > 0; i-- {
|
||||
if err = client.RemoveVolume(d.Id()); err != nil {
|
||||
log.Printf("[INFO] Volume remove loop: %d of %d due to error: %s", loops-i+1, loops, err)
|
||||
if err == dc.ErrVolumeInUse {
|
||||
time.Sleep(sleepTime)
|
||||
continue
|
||||
}
|
||||
if err == dc.ErrNoSuchVolume {
|
||||
break // it's removed
|
||||
}
|
||||
// if it's not in use any more (so it's deleted successfully) and another error occurred
|
||||
return fmt.Errorf("Error deleting volume %s: %s", d.Id(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
d.SetId("")
|
||||
|
|
|
|||
95
docker/validators.go
Normal file
95
docker/validators.go
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
)
|
||||
|
||||
func validateIntegerInRange(min, max int) schema.SchemaValidateFunc {
|
||||
return func(v interface{}, k string) (ws []string, errors []error) {
|
||||
value := v.(int)
|
||||
if value < min {
|
||||
errors = append(errors, fmt.Errorf(
|
||||
"%q cannot be lower than %d: %d", k, min, value))
|
||||
}
|
||||
if value > max {
|
||||
errors = append(errors, fmt.Errorf(
|
||||
"%q cannot be higher than %d: %d", k, max, value))
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func validateIntegerGeqThan(threshold int) schema.SchemaValidateFunc {
|
||||
return func(v interface{}, k string) (ws []string, errors []error) {
|
||||
value := v.(int)
|
||||
if value < threshold {
|
||||
errors = append(errors, fmt.Errorf(
|
||||
"%q cannot be lower than %q", k, threshold))
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func validateFloatRatio() schema.SchemaValidateFunc {
|
||||
return func(v interface{}, k string) (ws []string, errors []error) {
|
||||
value := v.(float64)
|
||||
if value < 0.0 || value > 1.0 {
|
||||
errors = append(errors, fmt.Errorf(
|
||||
"%q has to be between 0.0 and 1.0", k))
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func validateDurationGeq0() schema.SchemaValidateFunc {
|
||||
return func(v interface{}, k string) (ws []string, errors []error) {
|
||||
value := v.(string)
|
||||
dur, err := time.ParseDuration(value)
|
||||
if err != nil {
|
||||
errors = append(errors, fmt.Errorf(
|
||||
"%q is not a valid duration", k))
|
||||
}
|
||||
if dur < 0 {
|
||||
errors = append(errors, fmt.Errorf(
|
||||
"duration must not be negative"))
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func validateStringMatchesPattern(pattern string) schema.SchemaValidateFunc {
|
||||
return func(v interface{}, k string) (ws []string, errors []error) {
|
||||
compiledRegex, err := regexp.Compile(pattern)
|
||||
if err != nil {
|
||||
errors = append(errors, fmt.Errorf(
|
||||
"%q regex does not compile", pattern))
|
||||
return
|
||||
}
|
||||
|
||||
value := v.(string)
|
||||
if !compiledRegex.MatchString(value) {
|
||||
errors = append(errors, fmt.Errorf(
|
||||
"%q doesn't match the pattern (%q): %q",
|
||||
k, pattern, value))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func validateStringIsBase64Encoded() schema.SchemaValidateFunc {
|
||||
return func(v interface{}, k string) (ws []string, errors []error) {
|
||||
value := v.(string)
|
||||
if _, err := base64.StdEncoding.DecodeString(value); err != nil {
|
||||
errors = append(errors, fmt.Errorf(
|
||||
"%q is not base64 decodeable", k))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
91
docker/validators_test.go
Normal file
91
docker/validators_test.go
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
package docker
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestValidateIntegerInRange(t *testing.T) {
|
||||
validIntegers := []int{-259, 0, 1, 5, 999}
|
||||
min := -259
|
||||
max := 999
|
||||
for _, v := range validIntegers {
|
||||
_, errors := validateIntegerInRange(min, max)(v, "name")
|
||||
if len(errors) != 0 {
|
||||
t.Fatalf("%q should be an integer in range (%d, %d): %q", v, min, max, errors)
|
||||
}
|
||||
}
|
||||
|
||||
invalidIntegers := []int{-260, -99999, 1000, 25678}
|
||||
for _, v := range invalidIntegers {
|
||||
_, errors := validateIntegerInRange(min, max)(v, "name")
|
||||
if len(errors) == 0 {
|
||||
t.Fatalf("%q should be an integer outside range (%d, %d)", v, min, max)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateIntegerGeqThan0(t *testing.T) {
|
||||
v := 1
|
||||
if _, error := validateIntegerGeqThan(0)(v, "name"); error != nil {
|
||||
t.Fatalf("%q should be an integer greater than 0", v)
|
||||
}
|
||||
|
||||
v = -4
|
||||
if _, error := validateIntegerGeqThan(0)(v, "name"); error == nil {
|
||||
t.Fatalf("%q should be an invalid integer smaller than 0", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateFloatRatio(t *testing.T) {
|
||||
v := 0.9
|
||||
if _, error := validateFloatRatio()(v, "name"); error != nil {
|
||||
t.Fatalf("%v should be a float between 0.0 and 1.0", v)
|
||||
}
|
||||
|
||||
v = -4.5
|
||||
if _, error := validateFloatRatio()(v, "name"); error == nil {
|
||||
t.Fatalf("%v should be an invalid float smaller than 0.0", v)
|
||||
}
|
||||
|
||||
v = 1.1
|
||||
if _, error := validateFloatRatio()(v, "name"); error == nil {
|
||||
t.Fatalf("%v should be an invalid float greater than 1.0", v)
|
||||
}
|
||||
}
|
||||
func TestValidateDurationGeq0(t *testing.T) {
|
||||
v := "1ms"
|
||||
if _, error := validateDurationGeq0()(v, "name"); error != nil {
|
||||
t.Fatalf("%v should be a valid durarion", v)
|
||||
}
|
||||
|
||||
v = "-2h"
|
||||
if _, error := validateDurationGeq0()(v, "name"); error == nil {
|
||||
t.Fatalf("%v should be an invalid duration smaller than 0", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateStringMatchesPattern(t *testing.T) {
|
||||
pattern := `^(pause|continue-mate|break)$`
|
||||
v := "pause"
|
||||
if _, error := validateStringMatchesPattern(pattern)(v, "name"); error != nil {
|
||||
t.Fatalf("%q should match the pattern", v)
|
||||
}
|
||||
v = "doesnotmatch"
|
||||
if _, error := validateStringMatchesPattern(pattern)(v, "name"); error == nil {
|
||||
t.Fatalf("%q should not match the pattern", v)
|
||||
}
|
||||
v = "continue-mate"
|
||||
if _, error := validateStringMatchesPattern(pattern)(v, "name"); error != nil {
|
||||
t.Fatalf("%q should match the pattern", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateStringShouldBeBase64Encoded(t *testing.T) {
|
||||
v := `YmtzbGRrc2xka3NkMjM4MQ==`
|
||||
if _, error := validateStringIsBase64Encoded()(v, "name"); error != nil {
|
||||
t.Fatalf("%q should be base64 decodeable", v)
|
||||
}
|
||||
|
||||
v = `%&df#3NkMjM4MQ==`
|
||||
if _, error := validateStringIsBase64Encoded()(v, "name"); error == nil {
|
||||
t.Fatalf("%q should NOT be base64 decodeable", v)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue