mirror of
https://github.com/kreuzwerker/terraform-provider-docker.git
synced 2025-12-20 22:59:42 -05:00
added docker_registry_image
This commit is contained in:
parent
5d0f0fb806
commit
6a3c615a30
11 changed files with 1125 additions and 8 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -23,6 +23,7 @@ website/node_modules
|
||||||
*.iml
|
*.iml
|
||||||
*.test
|
*.test
|
||||||
*.iml
|
*.iml
|
||||||
|
.vscode
|
||||||
|
|
||||||
website/vendor
|
website/vendor
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,7 @@ func Provider() terraform.ResourceProvider {
|
||||||
ResourcesMap: map[string]*schema.Resource{
|
ResourcesMap: map[string]*schema.Resource{
|
||||||
"docker_container": resourceDockerContainer(),
|
"docker_container": resourceDockerContainer(),
|
||||||
"docker_image": resourceDockerImage(),
|
"docker_image": resourceDockerImage(),
|
||||||
|
"docker_registry_image": resourceDockerRegistryImage(),
|
||||||
"docker_network": resourceDockerNetwork(),
|
"docker_network": resourceDockerNetwork(),
|
||||||
"docker_volume": resourceDockerVolume(),
|
"docker_volume": resourceDockerVolume(),
|
||||||
"docker_config": resourceDockerConfig(),
|
"docker_config": resourceDockerConfig(),
|
||||||
|
|
|
||||||
285
docker/resource_docker_registry_image.go
Normal file
285
docker/resource_docker_registry_image.go
Normal file
|
|
@ -0,0 +1,285 @@
|
||||||
|
package docker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourceDockerRegistryImage() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourceDockerRegistryImageCreate,
|
||||||
|
Read: resourceDockerRegistryImageRead,
|
||||||
|
Delete: resourceDockerRegistryImageDelete,
|
||||||
|
Update: resourceDockerRegistryImageUpdate,
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"name": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"keep_remotely": {
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
Default: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
"build": &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Optional: true,
|
||||||
|
MaxItems: 1,
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"suppress_output": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"remote_context": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"no_cache": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"remove": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"force_remove": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"pull_parent": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"isolation": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"cpu_set_cpus": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"cpu_set_mems": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"cpu_shares": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"cpu_quota": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"cpu_period": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"memory": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"memory_swap": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"cgroup_parent": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"network_mode": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"shm_size": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"dockerfile": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Default: "Dockerfile",
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"ulimit": &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Optional: true,
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"hard": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"soft": &schema.Schema{
|
||||||
|
Type: schema.TypeInt,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"build_args": &schema.Schema{
|
||||||
|
Type: schema.TypeMap,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
Elem: &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"auth_config": &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Optional: true,
|
||||||
|
Elem: &schema.Resource{
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
"host_name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
"user_name": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"password": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"auth": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"email": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"server_address": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"identity_token": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"registry_token": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"context": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
ForceNew: true,
|
||||||
|
StateFunc: func(val interface{}) string {
|
||||||
|
// the context hash is stored to identify changes in the context files
|
||||||
|
dockerContextTarPath, _ := buildDockerImageContextTar(val.(string))
|
||||||
|
defer os.Remove(dockerContextTarPath)
|
||||||
|
contextTarHash, _ := getDockerImageContextTarHash(dockerContextTarPath)
|
||||||
|
return val.(string) + ":" + contextTarHash
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"labels": &schema.Schema{
|
||||||
|
Type: schema.TypeMap,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
Elem: &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"squash": &schema.Schema{
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"cache_from": &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
Elem: &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"security_opt": &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
Elem: &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"extra_hosts": &schema.Schema{
|
||||||
|
Type: schema.TypeList,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
Elem: &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"target": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"session_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"platform": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"version": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
"build_id": &schema.Schema{
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
ForceNew: true,
|
||||||
|
},
|
||||||
|
// "output": &schema.Schema{
|
||||||
|
// Type: schema.TypeString,
|
||||||
|
// Optional: true,
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
"sha256_digest": {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
488
docker/resource_docker_registry_image_funcs.go
Normal file
488
docker/resource_docker_registry_image_funcs.go
Normal file
|
|
@ -0,0 +1,488 @@
|
||||||
|
package docker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/tls"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/docker/docker/client"
|
||||||
|
"github.com/docker/docker/pkg/jsonmessage"
|
||||||
|
"github.com/docker/docker/pkg/term"
|
||||||
|
"github.com/docker/go-units"
|
||||||
|
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
|
||||||
|
)
|
||||||
|
|
||||||
|
type internalPushImageOptions struct {
|
||||||
|
Name string
|
||||||
|
FqName string
|
||||||
|
Registry string
|
||||||
|
NormalizedRegistry string
|
||||||
|
Repository string
|
||||||
|
Tag string
|
||||||
|
}
|
||||||
|
|
||||||
|
func createImageBuildOptions(buildOptions map[string]interface{}) types.ImageBuildOptions {
|
||||||
|
|
||||||
|
mapOfInterfacesToMapOfStrings := func(mapOfInterfaces map[string]interface{}) map[string]string {
|
||||||
|
mapOfStrings := make(map[string]string, len(mapOfInterfaces))
|
||||||
|
for k, v := range mapOfInterfaces {
|
||||||
|
mapOfStrings[k] = fmt.Sprintf("%v", v)
|
||||||
|
}
|
||||||
|
return mapOfStrings
|
||||||
|
}
|
||||||
|
|
||||||
|
interfaceArrayToStringArray := func(interfaceArray []interface{}) []string {
|
||||||
|
stringArray := make([]string, len(interfaceArray))
|
||||||
|
for i, v := range interfaceArray {
|
||||||
|
stringArray[i] = fmt.Sprintf("%v", v)
|
||||||
|
}
|
||||||
|
return stringArray
|
||||||
|
}
|
||||||
|
|
||||||
|
mapToBuildArgs := func(buildArgsOptions map[string]interface{}) map[string]*string {
|
||||||
|
buildArgs := make(map[string]*string, len(buildArgsOptions))
|
||||||
|
for k, v := range buildArgsOptions {
|
||||||
|
value := v.(string)
|
||||||
|
buildArgs[k] = &value
|
||||||
|
}
|
||||||
|
return buildArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
readULimits := func(options []interface{}) []*units.Ulimit {
|
||||||
|
ulimits := make([]*units.Ulimit, len(options))
|
||||||
|
for i, v := range options {
|
||||||
|
ulimitOption := v.(map[string]interface{})
|
||||||
|
ulimit := units.Ulimit{
|
||||||
|
Name: ulimitOption["name"].(string),
|
||||||
|
Hard: int64(ulimitOption["hard"].(int)),
|
||||||
|
Soft: int64(ulimitOption["soft"].(int)),
|
||||||
|
}
|
||||||
|
ulimits[i] = &ulimit
|
||||||
|
}
|
||||||
|
return ulimits
|
||||||
|
}
|
||||||
|
|
||||||
|
readAuthConfigs := func(options []interface{}) map[string]types.AuthConfig {
|
||||||
|
authConfigs := make(map[string]types.AuthConfig, len(options))
|
||||||
|
for _, v := range options {
|
||||||
|
authOptions := v.(map[string]interface{})
|
||||||
|
auth := types.AuthConfig{
|
||||||
|
Username: authOptions["user_name"].(string),
|
||||||
|
Password: authOptions["password"].(string),
|
||||||
|
Auth: authOptions["auth"].(string),
|
||||||
|
Email: authOptions["email"].(string),
|
||||||
|
ServerAddress: authOptions["server_address"].(string),
|
||||||
|
IdentityToken: authOptions["identity_token"].(string),
|
||||||
|
RegistryToken: authOptions["registry_token"].(string),
|
||||||
|
}
|
||||||
|
authConfigs[authOptions["host_name"].(string)] = auth
|
||||||
|
}
|
||||||
|
return authConfigs
|
||||||
|
}
|
||||||
|
|
||||||
|
buildImageOptions := types.ImageBuildOptions{}
|
||||||
|
buildImageOptions.SuppressOutput = buildOptions["suppress_output"].(bool)
|
||||||
|
buildImageOptions.RemoteContext = buildOptions["remote_context"].(string)
|
||||||
|
buildImageOptions.NoCache = buildOptions["no_cache"].(bool)
|
||||||
|
buildImageOptions.Remove = buildOptions["remove"].(bool)
|
||||||
|
buildImageOptions.ForceRemove = buildOptions["force_remove"].(bool)
|
||||||
|
buildImageOptions.PullParent = buildOptions["pull_parent"].(bool)
|
||||||
|
buildImageOptions.Isolation = container.Isolation(buildOptions["isolation"].(string))
|
||||||
|
buildImageOptions.CPUSetCPUs = buildOptions["cpu_set_cpus"].(string)
|
||||||
|
buildImageOptions.CPUSetMems = buildOptions["cpu_set_mems"].(string)
|
||||||
|
buildImageOptions.CPUShares = int64(buildOptions["cpu_shares"].(int))
|
||||||
|
buildImageOptions.CPUQuota = int64(buildOptions["cpu_quota"].(int))
|
||||||
|
buildImageOptions.CPUPeriod = int64(buildOptions["cpu_period"].(int))
|
||||||
|
buildImageOptions.Memory = int64(buildOptions["memory"].(int))
|
||||||
|
buildImageOptions.MemorySwap = int64(buildOptions["memory_swap"].(int))
|
||||||
|
buildImageOptions.CgroupParent = buildOptions["cgroup_parent"].(string)
|
||||||
|
buildImageOptions.NetworkMode = buildOptions["network_mode"].(string)
|
||||||
|
buildImageOptions.ShmSize = int64(buildOptions["shm_size"].(int))
|
||||||
|
buildImageOptions.Dockerfile = buildOptions["dockerfile"].(string)
|
||||||
|
buildImageOptions.Ulimits = readULimits(buildOptions["ulimit"].([]interface{}))
|
||||||
|
buildImageOptions.BuildArgs = mapToBuildArgs(buildOptions["build_args"].(map[string]interface{}))
|
||||||
|
buildImageOptions.AuthConfigs = readAuthConfigs(buildOptions["auth_config"].([]interface{}))
|
||||||
|
buildImageOptions.Labels = mapOfInterfacesToMapOfStrings(buildOptions["labels"].(map[string]interface{}))
|
||||||
|
buildImageOptions.Squash = buildOptions["squash"].(bool)
|
||||||
|
buildImageOptions.CacheFrom = interfaceArrayToStringArray(buildOptions["cache_from"].([]interface{}))
|
||||||
|
buildImageOptions.SecurityOpt = interfaceArrayToStringArray(buildOptions["security_opt"].([]interface{}))
|
||||||
|
buildImageOptions.ExtraHosts = interfaceArrayToStringArray(buildOptions["extra_hosts"].([]interface{}))
|
||||||
|
buildImageOptions.Target = buildOptions["target"].(string)
|
||||||
|
buildImageOptions.SessionID = buildOptions["session_id"].(string)
|
||||||
|
buildImageOptions.Platform = buildOptions["platform"].(string)
|
||||||
|
buildImageOptions.Version = types.BuilderVersion(buildOptions["version"].(string))
|
||||||
|
buildImageOptions.BuildID = buildOptions["build_id"].(string)
|
||||||
|
// outputs
|
||||||
|
|
||||||
|
return buildImageOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildDockerImage(client *client.Client, buildOptions map[string]interface{}, fqName string) error {
|
||||||
|
|
||||||
|
log.Printf("[DEBUG] Building docker image")
|
||||||
|
imageBuildOptions := createImageBuildOptions(buildOptions)
|
||||||
|
imageBuildOptions.Tags = []string{fqName}
|
||||||
|
|
||||||
|
// the tar hash is passed only after the initial creation
|
||||||
|
buildContext := buildOptions["context"].(string)
|
||||||
|
if lastIndex := strings.LastIndexByte(buildContext, ':'); lastIndex > -1 {
|
||||||
|
buildContext = buildContext[:lastIndex]
|
||||||
|
}
|
||||||
|
dockerContextTarPath, err := buildDockerImageContextTar(buildContext)
|
||||||
|
defer os.Remove(dockerContextTarPath)
|
||||||
|
dockerBuildContext, err := os.Open(dockerContextTarPath)
|
||||||
|
defer dockerBuildContext.Close()
|
||||||
|
|
||||||
|
buildResponse, err := client.ImageBuild(context.Background(), dockerBuildContext, imageBuildOptions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer buildResponse.Body.Close()
|
||||||
|
|
||||||
|
termFd, isTerm := term.GetFdInfo(os.Stderr)
|
||||||
|
err = jsonmessage.DisplayJSONMessagesStream(buildResponse.Body, os.Stderr, termFd, isTerm, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildDockerImageContextTar(buildContext string) (string, error) {
|
||||||
|
// Create our Temp File: This will create a filename like /tmp/terraform-provider-docker-123456.tar
|
||||||
|
tmpFile, err := ioutil.TempFile(os.TempDir(), "terraform-provider-docker-*.tar")
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Cannot create temporary file - %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
defer tmpFile.Close()
|
||||||
|
|
||||||
|
if _, err = os.Stat(buildContext); err != nil {
|
||||||
|
return "", fmt.Errorf("Unable to read build context - %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
tw := tar.NewWriter(tmpFile)
|
||||||
|
defer tw.Close()
|
||||||
|
|
||||||
|
err = filepath.Walk(buildContext, func(file string, info os.FileInfo, err error) error {
|
||||||
|
|
||||||
|
// return on any error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// create a new dir/file header
|
||||||
|
header, err := tar.FileInfoHeader(info, info.Name())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the name to correctly reflect the desired destination when untaring
|
||||||
|
header.Name = strings.TrimPrefix(strings.Replace(file, buildContext, "", -1), string(filepath.Separator))
|
||||||
|
|
||||||
|
// write the header
|
||||||
|
if err := tw.WriteHeader(header); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// return on non-regular files (thanks to [kumo](https://medium.com/@komuw/just-like-you-did-fbdd7df829d3) for this suggested update)
|
||||||
|
if !info.Mode().IsRegular() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// open files for taring
|
||||||
|
f, err := os.Open(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// copy file data into tar writer
|
||||||
|
if _, err := io.Copy(tw, f); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// manually close here after each file operation; defering would cause each file close
|
||||||
|
// to wait until all operations have completed.
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
return tmpFile.Name(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDockerImageContextTarHash(dockerContextTarPath string) (string, error) {
|
||||||
|
hasher := sha256.New()
|
||||||
|
s, err := ioutil.ReadFile(dockerContextTarPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
hasher.Write(s)
|
||||||
|
contextHash := hex.EncodeToString(hasher.Sum(nil))
|
||||||
|
return contextHash, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func pushDockerRegistryImage(client *client.Client, pushOpts internalPushImageOptions, username string, password string) error {
|
||||||
|
pushOptions := types.ImagePushOptions{}
|
||||||
|
if username != "" {
|
||||||
|
auth := types.AuthConfig{Username: username, Password: password}
|
||||||
|
authBytes, err := json.Marshal(auth)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating push options: %s", err)
|
||||||
|
}
|
||||||
|
authBase64 := base64.URLEncoding.EncodeToString(authBytes)
|
||||||
|
pushOptions.RegistryAuth = authBase64
|
||||||
|
}
|
||||||
|
|
||||||
|
out, err := client.ImagePush(context.Background(), pushOpts.FqName, pushOptions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
|
||||||
|
type ErrorMessage struct {
|
||||||
|
Error string
|
||||||
|
}
|
||||||
|
var errorMessage ErrorMessage
|
||||||
|
buffIOReader := bufio.NewReader(out)
|
||||||
|
for {
|
||||||
|
streamBytes, err := buffIOReader.ReadBytes('\n')
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
json.Unmarshal(streamBytes, &errorMessage)
|
||||||
|
if errorMessage.Error != "" {
|
||||||
|
return fmt.Errorf("Error pushing image: %s", errorMessage.Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Printf("[DEBUG] Pushed image: %s", pushOpts.FqName)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDockerRegistryImageRegistryUserNameAndPassword(
|
||||||
|
pushOpts internalPushImageOptions,
|
||||||
|
providerConfig *ProviderConfig) (string, string) {
|
||||||
|
registry := pushOpts.NormalizedRegistry
|
||||||
|
username := ""
|
||||||
|
password := ""
|
||||||
|
if authConfig, ok := providerConfig.AuthConfigs.Configs[registry]; ok {
|
||||||
|
username = authConfig.Username
|
||||||
|
password = authConfig.Password
|
||||||
|
}
|
||||||
|
return username, password
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteDockerRegistryImage(pushOpts internalPushImageOptions, sha256Digest, username, password string, fallback bool) error {
|
||||||
|
client := http.DefaultClient
|
||||||
|
|
||||||
|
// Allow insecure registries only for ACC tests
|
||||||
|
// cuz we don't have a valid certs for this case
|
||||||
|
if env, okEnv := os.LookupEnv("TF_ACC"); okEnv {
|
||||||
|
if i, errConv := strconv.Atoi(env); errConv == nil && i >= 1 {
|
||||||
|
cfg := &tls.Config{
|
||||||
|
InsecureSkipVerify: true,
|
||||||
|
}
|
||||||
|
client.Transport = &http.Transport{
|
||||||
|
TLSClientConfig: cfg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("DELETE", pushOpts.NormalizedRegistry+"/v2/"+pushOpts.Repository+"/manifests/"+sha256Digest, nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error deleting registry image: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if username != "" {
|
||||||
|
req.SetBasicAuth(username, password)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set this header so that we get the v2 manifest back from the registry.
|
||||||
|
req.Header.Set("Accept", "application/vnd.docker.distribution.manifest.v2+json")
|
||||||
|
if fallback {
|
||||||
|
// Fallback to this header if the registry does not support the v2 manifest like gcr.io
|
||||||
|
req.Header.Set("Accept", "application/vnd.docker.distribution.manifest.v1+prettyjws")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error during registry request: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch resp.StatusCode {
|
||||||
|
// Basic auth was valid or not needed
|
||||||
|
case http.StatusOK, http.StatusAccepted, http.StatusNotFound:
|
||||||
|
return nil
|
||||||
|
|
||||||
|
// Either OAuth is required or the basic auth creds were invalid
|
||||||
|
case http.StatusUnauthorized:
|
||||||
|
if strings.HasPrefix(resp.Header.Get("www-authenticate"), "Bearer") {
|
||||||
|
auth := parseAuthHeader(resp.Header.Get("www-authenticate"))
|
||||||
|
params := url.Values{}
|
||||||
|
params.Set("service", auth["service"])
|
||||||
|
params.Set("scope", auth["scope"])
|
||||||
|
tokenRequest, err := http.NewRequest("GET", auth["realm"]+"?"+params.Encode(), nil)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error creating registry request: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if username != "" {
|
||||||
|
tokenRequest.SetBasicAuth(username, password)
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenResponse, err := client.Do(tokenRequest)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error during registry request: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tokenResponse.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("Got bad response from registry: " + tokenResponse.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
body, err := ioutil.ReadAll(tokenResponse.Body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error reading response body: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
token := &TokenResponse{}
|
||||||
|
err = json.Unmarshal(body, token)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error parsing OAuth token response: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Authorization", "Bearer "+token.Token)
|
||||||
|
oauthResp, err := client.Do(req)
|
||||||
|
switch oauthResp.StatusCode {
|
||||||
|
case http.StatusOK, http.StatusAccepted, http.StatusNotFound:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Got bad response from registry: " + resp.Status)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("Bad credentials: " + resp.Status)
|
||||||
|
|
||||||
|
// Some unexpected status was given, return an error
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Got bad response from registry: " + resp.Status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getImageDigestWithFallback(opts internalPushImageOptions, username, password string) (string, error) {
|
||||||
|
digest, err := getImageDigest(opts.Registry, opts.Repository, opts.Tag, username, password, false)
|
||||||
|
if err != nil {
|
||||||
|
digest, err = getImageDigest(opts.Registry, opts.Repository, opts.Tag, username, password, true)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("Unable to get digest: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return digest, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createPushImageOptions(image string) internalPushImageOptions {
|
||||||
|
pullOpts := parseImageOptions(image)
|
||||||
|
if pullOpts.Registry == "" {
|
||||||
|
pullOpts.Registry = "registry.hub.docker.com"
|
||||||
|
} else {
|
||||||
|
pullOpts.Repository = strings.Replace(pullOpts.Repository, pullOpts.Registry+"/", "", 1)
|
||||||
|
}
|
||||||
|
pushOpts := internalPushImageOptions{
|
||||||
|
Name: image,
|
||||||
|
Registry: pullOpts.Registry,
|
||||||
|
NormalizedRegistry: normalizeRegistryAddress(pullOpts.Registry),
|
||||||
|
Repository: pullOpts.Repository,
|
||||||
|
Tag: pullOpts.Tag,
|
||||||
|
FqName: fmt.Sprintf("%s/%s:%s", pullOpts.Registry, pullOpts.Repository, pullOpts.Tag),
|
||||||
|
}
|
||||||
|
return pushOpts
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceDockerRegistryImageCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
client := meta.(*ProviderConfig).DockerClient
|
||||||
|
providerConfig := meta.(*ProviderConfig)
|
||||||
|
name := d.Get("name").(string)
|
||||||
|
log.Printf("[DEBUG] Creating docker image %s", name)
|
||||||
|
|
||||||
|
pushOpts := createPushImageOptions(name)
|
||||||
|
|
||||||
|
if buildOptions, ok := d.GetOk("build"); ok {
|
||||||
|
buildOptionsMap := buildOptions.([]interface{})[0].(map[string]interface{})
|
||||||
|
err := buildDockerImage(client, buildOptionsMap, pushOpts.FqName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error building docker image: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
username, password := getDockerRegistryImageRegistryUserNameAndPassword(pushOpts, providerConfig)
|
||||||
|
if err := pushDockerRegistryImage(client, pushOpts, username, password); err != nil {
|
||||||
|
return fmt.Errorf("Error pushing docker image: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
digest, err := getImageDigestWithFallback(pushOpts, username, password)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Unable to create image, image not found: %s", err)
|
||||||
|
}
|
||||||
|
d.SetId(digest)
|
||||||
|
d.Set("sha256_digest", digest)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceDockerRegistryImageRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
providerConfig := meta.(*ProviderConfig)
|
||||||
|
name := d.Get("name").(string)
|
||||||
|
pushOpts := createPushImageOptions(name)
|
||||||
|
username, password := getDockerRegistryImageRegistryUserNameAndPassword(pushOpts, providerConfig)
|
||||||
|
digest, err := getImageDigestWithFallback(pushOpts, username, password)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Got error getting registry image digest: %s", err)
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
d.Set("sha256_digest", digest)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceDockerRegistryImageDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
if d.Get("keep_remotely").(bool) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
providerConfig := meta.(*ProviderConfig)
|
||||||
|
name := d.Get("name").(string)
|
||||||
|
pushOpts := createPushImageOptions(name)
|
||||||
|
username, password := getDockerRegistryImageRegistryUserNameAndPassword(pushOpts, providerConfig)
|
||||||
|
digest := d.Get("sha256_digest").(string)
|
||||||
|
err := deleteDockerRegistryImage(pushOpts, digest, username, password, false)
|
||||||
|
if err != nil {
|
||||||
|
err = deleteDockerRegistryImage(pushOpts, pushOpts.Tag, username, password, true)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Got error getting registry image digest: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourceDockerRegistryImageUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
return resourceDockerRegistryImageRead(d, meta)
|
||||||
|
}
|
||||||
289
docker/resource_docker_registry_image_funcs_test.go
Normal file
289
docker/resource_docker_registry_image_funcs_test.go
Normal file
|
|
@ -0,0 +1,289 @@
|
||||||
|
package docker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/docker/go-units"
|
||||||
|
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
|
||||||
|
"github.com/hashicorp/terraform-plugin-sdk/terraform"
|
||||||
|
"gotest.tools/assert"
|
||||||
|
"gotest.tools/assert/cmp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccDockerRegistryImageResource_mapping(t *testing.T) {
|
||||||
|
dummyProvider := Provider().(*schema.Provider)
|
||||||
|
dummyResource := dummyProvider.ResourcesMap["docker_registry_image"]
|
||||||
|
dummyResource.Create = func(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
build := d.Get("build").([]interface{})[0].(map[string]interface{})
|
||||||
|
options := createImageBuildOptions(build)
|
||||||
|
|
||||||
|
assert.Check(t, cmp.Equal(options.SuppressOutput, true))
|
||||||
|
assert.Check(t, cmp.Equal(options.RemoteContext, "fooRemoteContext"))
|
||||||
|
assert.Check(t, cmp.Equal(options.NoCache, true))
|
||||||
|
assert.Check(t, cmp.Equal(options.Remove, true))
|
||||||
|
assert.Check(t, cmp.Equal(options.ForceRemove, true))
|
||||||
|
assert.Check(t, cmp.Equal(options.PullParent, true))
|
||||||
|
assert.Check(t, cmp.Equal(options.Isolation, container.Isolation("hyperv")))
|
||||||
|
assert.Check(t, cmp.Equal(options.CPUSetCPUs, "fooCpuSetCpus"))
|
||||||
|
assert.Check(t, cmp.Equal(options.CPUSetMems, "fooCpuSetMems"))
|
||||||
|
assert.Check(t, cmp.Equal(options.CPUShares, int64(4)))
|
||||||
|
assert.Check(t, cmp.Equal(options.CPUQuota, int64(5)))
|
||||||
|
assert.Check(t, cmp.Equal(options.CPUPeriod, int64(6)))
|
||||||
|
assert.Check(t, cmp.Equal(options.Memory, int64(1)))
|
||||||
|
assert.Check(t, cmp.Equal(options.MemorySwap, int64(2)))
|
||||||
|
assert.Check(t, cmp.Equal(options.CgroupParent, "fooCgroupParent"))
|
||||||
|
assert.Check(t, cmp.Equal(options.NetworkMode, "fooNetworkMode"))
|
||||||
|
assert.Check(t, cmp.Equal(options.ShmSize, int64(3)))
|
||||||
|
assert.Check(t, cmp.Equal(options.Dockerfile, "fooDockerfile"))
|
||||||
|
assert.Check(t, cmp.Equal(len(options.Ulimits), 1))
|
||||||
|
assert.Check(t, cmp.DeepEqual(*options.Ulimits[0], units.Ulimit{
|
||||||
|
Name: "foo",
|
||||||
|
Hard: int64(1),
|
||||||
|
Soft: int64(2),
|
||||||
|
}))
|
||||||
|
assert.Check(t, cmp.Equal(len(options.BuildArgs), 1))
|
||||||
|
assert.Check(t, cmp.Equal(*options.BuildArgs["HTTP_PROXY"], "http://10.20.30.2:1234"))
|
||||||
|
assert.Check(t, cmp.Equal(len(options.AuthConfigs), 1))
|
||||||
|
assert.Check(t, cmp.DeepEqual(options.AuthConfigs["foo.host"], types.AuthConfig{
|
||||||
|
Username: "fooUserName",
|
||||||
|
Password: "fooPassword",
|
||||||
|
Auth: "fooAuth",
|
||||||
|
Email: "fooEmail",
|
||||||
|
ServerAddress: "fooServerAddress",
|
||||||
|
IdentityToken: "fooIdentityToken",
|
||||||
|
RegistryToken: "fooRegistryToken",
|
||||||
|
}))
|
||||||
|
assert.Check(t, cmp.DeepEqual(options.Labels, map[string]string{"foo": "bar"}))
|
||||||
|
assert.Check(t, cmp.Equal(options.Squash, true))
|
||||||
|
assert.Check(t, cmp.DeepEqual(options.CacheFrom, []string{"fooCacheFrom", "barCacheFrom"}))
|
||||||
|
assert.Check(t, cmp.DeepEqual(options.SecurityOpt, []string{"fooSecurityOpt", "barSecurityOpt"}))
|
||||||
|
assert.Check(t, cmp.DeepEqual(options.ExtraHosts, []string{"fooExtraHost", "barExtraHost"}))
|
||||||
|
assert.Check(t, cmp.Equal(options.Target, "fooTarget"))
|
||||||
|
assert.Check(t, cmp.Equal(options.SessionID, "fooSessionId"))
|
||||||
|
assert.Check(t, cmp.Equal(options.Platform, "fooPlatform"))
|
||||||
|
assert.Check(t, cmp.Equal(options.Version, types.BuilderVersion("1")))
|
||||||
|
assert.Check(t, cmp.Equal(options.BuildID, "fooBuildId"))
|
||||||
|
// output
|
||||||
|
d.SetId("foo")
|
||||||
|
d.Set("sha256_digest", "bar")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
dummyResource.Update = func(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
dummyResource.Delete = func(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
dummyResource.Read = func(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
d.Set("sha256_digest", "bar")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: map[string]terraform.ResourceProvider{"docker": dummyProvider},
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: testBuildDockerRegistryImageMappingConfig,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttrSet("docker_registry_image.foo", "sha256_digest"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccDockerRegistryImageResource_build(t *testing.T) {
|
||||||
|
pushOptions := createPushImageOptions("127.0.0.1:15000/tftest-dockerregistryimage:1.0")
|
||||||
|
context := "../scripts/testing/docker_registry_image_context"
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testDockerRegistryImageNotInRegistry(pushOptions),
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: fmt.Sprintf(testBuildDockerRegistryImageNoKeepConfig, pushOptions.Registry, pushOptions.Name, context),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttrSet("docker_registry_image.foo", "sha256_digest"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccDockerRegistryImageResource_buildAndKeep(t *testing.T) {
|
||||||
|
pushOptions := createPushImageOptions("127.0.0.1:15000/tftest-dockerregistryimage:1.0")
|
||||||
|
context := "../scripts/testing/docker_registry_image_context"
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testDockerRegistryImageInRegistry(pushOptions, true),
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: fmt.Sprintf(testBuildDockerRegistryImageKeepConfig, pushOptions.Registry, pushOptions.Name, context),
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
resource.TestCheckResourceAttrSet("docker_registry_image.foo", "sha256_digest"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAccDockerRegistryImageResource_pushMissingImage(t *testing.T) {
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: testDockerRegistryImagePushMissingConfig,
|
||||||
|
ExpectError: regexp.MustCompile("An image does not exist locally"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDockerRegistryImageNotInRegistry(pushOpts internalPushImageOptions) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
providerConfig := testAccProvider.Meta().(*ProviderConfig)
|
||||||
|
username, password := getDockerRegistryImageRegistryUserNameAndPassword(pushOpts, providerConfig)
|
||||||
|
digest, _ := getImageDigestWithFallback(pushOpts, username, password)
|
||||||
|
if digest != "" {
|
||||||
|
return fmt.Errorf("image found")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testDockerRegistryImageInRegistry(pushOpts internalPushImageOptions, cleanup bool) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
providerConfig := testAccProvider.Meta().(*ProviderConfig)
|
||||||
|
username, password := getDockerRegistryImageRegistryUserNameAndPassword(pushOpts, providerConfig)
|
||||||
|
digest, err := getImageDigestWithFallback(pushOpts, username, password)
|
||||||
|
if err != nil || len(digest) < 1 {
|
||||||
|
return fmt.Errorf("image not found")
|
||||||
|
}
|
||||||
|
if cleanup {
|
||||||
|
err := deleteDockerRegistryImage(pushOpts, digest, username, password, false)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Unable to remove test image. %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const testBuildDockerRegistryImageMappingConfig = `
|
||||||
|
resource "docker_registry_image" "foo" {
|
||||||
|
name = "localhost:15000/foo:1.0"
|
||||||
|
build {
|
||||||
|
suppress_output = true
|
||||||
|
remote_context = "fooRemoteContext"
|
||||||
|
no_cache = true
|
||||||
|
remove = true
|
||||||
|
force_remove = true
|
||||||
|
pull_parent = true
|
||||||
|
isolation = "hyperv"
|
||||||
|
cpu_set_cpus = "fooCpuSetCpus"
|
||||||
|
cpu_set_mems = "fooCpuSetMems"
|
||||||
|
cpu_shares = 4
|
||||||
|
cpu_quota = 5
|
||||||
|
cpu_period = 6
|
||||||
|
memory = 1
|
||||||
|
memory_swap = 2
|
||||||
|
cgroup_parent = "fooCgroupParent"
|
||||||
|
network_mode = "fooNetworkMode"
|
||||||
|
shm_size = 3
|
||||||
|
dockerfile = "fooDockerfile"
|
||||||
|
ulimit {
|
||||||
|
name = "foo"
|
||||||
|
hard = 1
|
||||||
|
soft = 2
|
||||||
|
}
|
||||||
|
auth_config {
|
||||||
|
host_name = "foo.host"
|
||||||
|
user_name = "fooUserName"
|
||||||
|
password = "fooPassword"
|
||||||
|
auth = "fooAuth"
|
||||||
|
email = "fooEmail"
|
||||||
|
server_address = "fooServerAddress"
|
||||||
|
identity_token = "fooIdentityToken"
|
||||||
|
registry_token = "fooRegistryToken"
|
||||||
|
|
||||||
|
}
|
||||||
|
build_args = {
|
||||||
|
"HTTP_PROXY" = "http://10.20.30.2:1234"
|
||||||
|
}
|
||||||
|
context = "context"
|
||||||
|
labels = {
|
||||||
|
foo = "bar"
|
||||||
|
}
|
||||||
|
squash = true
|
||||||
|
cache_from = ["fooCacheFrom", "barCacheFrom"]
|
||||||
|
security_opt = ["fooSecurityOpt", "barSecurityOpt"]
|
||||||
|
extra_hosts = ["fooExtraHost", "barExtraHost"]
|
||||||
|
target = "fooTarget"
|
||||||
|
session_id = "fooSessionId"
|
||||||
|
platform = "fooPlatform"
|
||||||
|
version = "1"
|
||||||
|
build_id = "fooBuildId"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const testBuildDockerRegistryImageNoKeepConfig = `
|
||||||
|
provider "docker" {
|
||||||
|
alias = "private"
|
||||||
|
registry_auth {
|
||||||
|
address = "%s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resource "docker_registry_image" "foo" {
|
||||||
|
provider = "docker.private"
|
||||||
|
name = "%s"
|
||||||
|
build {
|
||||||
|
context = "%s"
|
||||||
|
remove = true
|
||||||
|
force_remove = true
|
||||||
|
no_cache = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const testBuildDockerRegistryImageKeepConfig = `
|
||||||
|
provider "docker" {
|
||||||
|
alias = "private"
|
||||||
|
registry_auth {
|
||||||
|
address = "%s"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resource "docker_registry_image" "foo" {
|
||||||
|
provider = "docker.private"
|
||||||
|
name = "%s"
|
||||||
|
keep_remotely = true
|
||||||
|
build {
|
||||||
|
context = "%s"
|
||||||
|
remove = true
|
||||||
|
force_remove = true
|
||||||
|
no_cache = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const testDockerRegistryImagePushMissingConfig = `
|
||||||
|
provider "docker" {
|
||||||
|
alias = "private"
|
||||||
|
registry_auth {
|
||||||
|
address = "127.0.0.1:15000"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resource "docker_registry_image" "foo" {
|
||||||
|
provider = "docker.private"
|
||||||
|
name = "127.0.0.1:15000/nonexistent:1.0"
|
||||||
|
}
|
||||||
|
`
|
||||||
2
go.mod
2
go.mod
|
|
@ -17,7 +17,7 @@ require (
|
||||||
github.com/opencontainers/image-spec v0.0.0-20171125024018-577479e4dc27 // indirect
|
github.com/opencontainers/image-spec v0.0.0-20171125024018-577479e4dc27 // indirect
|
||||||
github.com/pkg/errors v0.8.1 // indirect
|
github.com/pkg/errors v0.8.1 // indirect
|
||||||
github.com/sirupsen/logrus v1.4.2 // indirect
|
github.com/sirupsen/logrus v1.4.2 // indirect
|
||||||
gotest.tools v2.2.0+incompatible // indirect
|
gotest.tools v2.2.0+incompatible
|
||||||
)
|
)
|
||||||
|
|
||||||
go 1.13
|
go 1.13
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ docker run -d -p 15000:5000 --rm --name private_registry \
|
||||||
-v "$(pwd)"/scripts/testing/certs:/certs \
|
-v "$(pwd)"/scripts/testing/certs:/certs \
|
||||||
-e "REGISTRY_HTTP_TLS_CERTIFICATE=/certs/registry_auth.crt" \
|
-e "REGISTRY_HTTP_TLS_CERTIFICATE=/certs/registry_auth.crt" \
|
||||||
-e "REGISTRY_HTTP_TLS_KEY=/certs/registry_auth.key" \
|
-e "REGISTRY_HTTP_TLS_KEY=/certs/registry_auth.key" \
|
||||||
|
-e "REGISTRY_STORAGE_DELETE_ENABLED=true" \
|
||||||
registry:2
|
registry:2
|
||||||
# wait a bit for travis...
|
# wait a bit for travis...
|
||||||
sleep 5
|
sleep 5
|
||||||
|
|
|
||||||
2
scripts/testing/docker_registry_image_context/Dockerfile
Normal file
2
scripts/testing/docker_registry_image_context/Dockerfile
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
FROM scratch
|
||||||
|
COPY empty /empty
|
||||||
0
scripts/testing/docker_registry_image_context/empty
Normal file
0
scripts/testing/docker_registry_image_context/empty
Normal file
|
|
@ -30,6 +30,10 @@
|
||||||
<a href="/docs/providers/docker/r/image.html">docker_image</a>
|
<a href="/docs/providers/docker/r/image.html">docker_image</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li<%= sidebar_current("docs-docker-resource-registry-image") %>>
|
||||||
|
<a href="/docs/providers/docker/r/registry_image.html">docker_registry_image</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li<%= sidebar_current("docs-docker-resource-network") %>>
|
<li<%= sidebar_current("docs-docker-resource-network") %>>
|
||||||
<a href="/docs/providers/docker/r/network.html">docker_network</a>
|
<a href="/docs/providers/docker/r/network.html">docker_network</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
|
||||||
46
website/docs/r/registry_image.html.markdown
Normal file
46
website/docs/r/registry_image.html.markdown
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
---
|
||||||
|
layout: "docker"
|
||||||
|
page_title: "Docker: docker_registry_image"
|
||||||
|
sidebar_current: "docs-docker-resource-registry-image"
|
||||||
|
description: |-
|
||||||
|
Manages the lifecycle of docker image/tag in a registry.
|
||||||
|
---
|
||||||
|
|
||||||
|
# docker\_registry\_image
|
||||||
|
|
||||||
|
Provides an image/tag in a Docker registry.
|
||||||
|
|
||||||
|
## Example Usage
|
||||||
|
|
||||||
|
```hcl
|
||||||
|
resource "docker_registry_image" "helloworld" {
|
||||||
|
|
||||||
|
name = "helloworld:1.0"
|
||||||
|
|
||||||
|
build {
|
||||||
|
context = "pathToContextFolder"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
* `name` - (Required, string) The name of the Docker image.
|
||||||
|
* `keep_remotely` - (Optional, boolean) If true, then the Docker image won't be
|
||||||
|
deleted on destroy operation. If this is false, it will delete the image from
|
||||||
|
the docker registry on destroy operation.
|
||||||
|
|
||||||
|
* `build` - (Optional, Customize docker build arguments) See [Build](#build-1) below for details.
|
||||||
|
|
||||||
|
<a id="build-1"></a>
|
||||||
|
#### Build Block
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Attributes Reference
|
||||||
|
|
||||||
|
The following attributes are exported in addition to the above configuration:
|
||||||
|
|
||||||
|
* `sha256_digest` (string) - The sha256 digest of the image.
|
||||||
Loading…
Reference in a new issue