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
|
||||
*.test
|
||||
*.iml
|
||||
.vscode
|
||||
|
||||
website/vendor
|
||||
|
||||
|
|
|
|||
|
|
@ -105,13 +105,14 @@ func Provider() terraform.ResourceProvider {
|
|||
},
|
||||
|
||||
ResourcesMap: map[string]*schema.Resource{
|
||||
"docker_container": resourceDockerContainer(),
|
||||
"docker_image": resourceDockerImage(),
|
||||
"docker_network": resourceDockerNetwork(),
|
||||
"docker_volume": resourceDockerVolume(),
|
||||
"docker_config": resourceDockerConfig(),
|
||||
"docker_secret": resourceDockerSecret(),
|
||||
"docker_service": resourceDockerService(),
|
||||
"docker_container": resourceDockerContainer(),
|
||||
"docker_image": resourceDockerImage(),
|
||||
"docker_registry_image": resourceDockerRegistryImage(),
|
||||
"docker_network": resourceDockerNetwork(),
|
||||
"docker_volume": resourceDockerVolume(),
|
||||
"docker_config": resourceDockerConfig(),
|
||||
"docker_secret": resourceDockerSecret(),
|
||||
"docker_service": resourceDockerService(),
|
||||
},
|
||||
|
||||
DataSourcesMap: map[string]*schema.Resource{
|
||||
|
|
|
|||
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/pkg/errors v0.8.1 // 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
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ docker run -d -p 15000:5000 --rm --name private_registry \
|
|||
-v "$(pwd)"/scripts/testing/certs:/certs \
|
||||
-e "REGISTRY_HTTP_TLS_CERTIFICATE=/certs/registry_auth.crt" \
|
||||
-e "REGISTRY_HTTP_TLS_KEY=/certs/registry_auth.key" \
|
||||
-e "REGISTRY_STORAGE_DELETE_ENABLED=true" \
|
||||
registry:2
|
||||
# wait a bit for travis...
|
||||
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>
|
||||
</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") %>>
|
||||
<a href="/docs/providers/docker/r/network.html">docker_network</a>
|
||||
</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