mirror of
https://github.com/kreuzwerker/terraform-provider-docker.git
synced 2026-01-28 17:37:34 -05:00
Merge branch 'edgarpoce-feature/resource-docker_registry_image' into master
This commit is contained in:
commit
3f7f3f9546
10 changed files with 1207 additions and 7 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -23,6 +23,7 @@ website/node_modules
|
|||
*.iml
|
||||
*.test
|
||||
*.iml
|
||||
.vscode
|
||||
|
||||
website/vendor
|
||||
|
||||
|
|
|
|||
|
|
@ -101,13 +101,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{
|
||||
|
|
|
|||
281
docker/resource_docker_registry_image.go
Normal file
281
docker/resource_docker_registry_image.go
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
"sha256_digest": {
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
519
docker/resource_docker_registry_image_funcs.go
Normal file
519
docker/resource_docker_registry_image_funcs.go
Normal file
|
|
@ -0,0 +1,519 @@
|
|||
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/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 {
|
||||
|
||||
type ErrorDetailMessage struct {
|
||||
Code int `json:"code,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
type BuildImageResponseMessage struct {
|
||||
Error string `json:"error,omitempty"`
|
||||
ErrorDetail *ErrorDetailMessage `json:"errorDetail,omitempty"`
|
||||
}
|
||||
|
||||
getError := func(body io.ReadCloser) error {
|
||||
dec := json.NewDecoder(body)
|
||||
for {
|
||||
message := BuildImageResponseMessage{}
|
||||
if err := dec.Decode(&message); err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return err
|
||||
}
|
||||
if message.ErrorDetail != nil {
|
||||
detail := message.ErrorDetail
|
||||
return fmt.Errorf("%v: %s", detail.Code, detail.Message)
|
||||
}
|
||||
if len(message.Error) > 0 {
|
||||
return fmt.Errorf("%s", message.Error)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to build context %v", err)
|
||||
}
|
||||
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()
|
||||
|
||||
err = getError(buildResponse.Body)
|
||||
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)
|
||||
}
|
||||
295
docker/resource_docker_registry_image_funcs_test.go
Normal file
295
docker/resource_docker_registry_image_funcs_test.go
Normal file
|
|
@ -0,0 +1,295 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"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"
|
||||
)
|
||||
|
||||
func TestAccDockerRegistryImageResource_mapping(t *testing.T) {
|
||||
|
||||
assert := func(condition bool, msg string) {
|
||||
if !condition {
|
||||
t.Errorf("assertion failed: wrong build parameter %s", msg)
|
||||
}
|
||||
}
|
||||
|
||||
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(options.SuppressOutput == true, "SuppressOutput")
|
||||
assert(options.RemoteContext == "fooRemoteContext", "RemoteContext")
|
||||
assert(options.NoCache == true, "NoCache")
|
||||
assert(options.Remove == true, "Remove")
|
||||
assert(options.ForceRemove == true, "ForceRemove")
|
||||
assert(options.PullParent == true, "PullParent")
|
||||
assert(options.Isolation == container.Isolation("hyperv"), "Isolation")
|
||||
assert(options.CPUSetCPUs == "fooCpuSetCpus", "CPUSetCPUs")
|
||||
assert(options.CPUSetMems == "fooCpuSetMems", "CPUSetMems")
|
||||
assert(options.CPUShares == int64(4), "CPUShares")
|
||||
assert(options.CPUQuota == int64(5), "CPUQuota")
|
||||
assert(options.CPUPeriod == int64(6), "CPUPeriod")
|
||||
assert(options.Memory == int64(1), "Memory")
|
||||
assert(options.MemorySwap == int64(2), "MemorySwap")
|
||||
assert(options.CgroupParent == "fooCgroupParent", "CgroupParent")
|
||||
assert(options.NetworkMode == "fooNetworkMode", "NetworkMode")
|
||||
assert(options.ShmSize == int64(3), "ShmSize")
|
||||
assert(options.Dockerfile == "fooDockerfile", "Dockerfile")
|
||||
assert(len(options.Ulimits) == 1, "Ulimits")
|
||||
assert(reflect.DeepEqual(*options.Ulimits[0], units.Ulimit{
|
||||
Name: "foo",
|
||||
Hard: int64(1),
|
||||
Soft: int64(2),
|
||||
}), "Ulimits")
|
||||
assert(len(options.BuildArgs) == 1, "BuildArgs")
|
||||
assert(*options.BuildArgs["HTTP_PROXY"] == "http://10.20.30.2:1234", "BuildArgs")
|
||||
assert(len(options.AuthConfigs) == 1, "AuthConfigs")
|
||||
assert(reflect.DeepEqual(options.AuthConfigs["foo.host"], types.AuthConfig{
|
||||
Username: "fooUserName",
|
||||
Password: "fooPassword",
|
||||
Auth: "fooAuth",
|
||||
Email: "fooEmail",
|
||||
ServerAddress: "fooServerAddress",
|
||||
IdentityToken: "fooIdentityToken",
|
||||
RegistryToken: "fooRegistryToken",
|
||||
}), "AuthConfigs")
|
||||
assert(reflect.DeepEqual(options.Labels, map[string]string{"foo": "bar"}), "Labels")
|
||||
assert(options.Squash == true, "Squash")
|
||||
assert(reflect.DeepEqual(options.CacheFrom, []string{"fooCacheFrom", "barCacheFrom"}), "CacheFrom")
|
||||
assert(reflect.DeepEqual(options.SecurityOpt, []string{"fooSecurityOpt", "barSecurityOpt"}), "SecurityOpt")
|
||||
assert(reflect.DeepEqual(options.ExtraHosts, []string{"fooExtraHost", "barExtraHost"}), "ExtraHosts")
|
||||
assert(options.Target == "fooTarget", "Target")
|
||||
assert(options.SessionID == "fooSessionId", "SessionID")
|
||||
assert(options.Platform == "fooPlatform", "Platform")
|
||||
assert(options.Version == types.BuilderVersion("1"), "Version")
|
||||
assert(options.BuildID == "fooBuildId", "BuildID")
|
||||
// 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"
|
||||
}
|
||||
`
|
||||
|
|
@ -27,6 +27,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.7.0
|
||||
# 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>
|
||||
|
|
|
|||
96
website/docs/r/registry_image.html.markdown
Normal file
96
website/docs/r/registry_image.html.markdown
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
---
|
||||
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, Map) See [Build](#build-1) below for details.
|
||||
|
||||
<a id="build-1"></a>
|
||||
#### Build Block
|
||||
|
||||
* `context` (Required, string) - The path to the context folder
|
||||
* `suppress_output` (Optional, bool) - Suppress the build output and print image ID on success
|
||||
* `remote_context` (Optional, string) - A Git repository URI or HTTP/HTTPS context URI
|
||||
* `no_cache` (Optional, bool) - Do not use the cache when building the image
|
||||
* `remove` (Optional, bool) - Remove intermediate containers after a successful build (default behavior)
|
||||
* `force_remove` (Optional, bool) - Always remove intermediate containers
|
||||
* `pull_parent` (Optional, bool) - Attempt to pull the image even if an older image exists locally
|
||||
* `isolation` (Optional, string) - Isolation represents the isolation technology of a container. The supported values are platform specific
|
||||
* `cpu_set_cpus` (Optional, string) - CPUs in which to allow execution (e.g., 0-3, 0,1)
|
||||
* `cpu_set_mems` (Optional, string) - MEMs in which to allow execution (0-3, 0,1)
|
||||
* `cpu_shares` (Optional, int) - CPU shares (relative weight)
|
||||
* `cpu_quota` (Optional, int) - Microseconds of CPU time that the container can get in a CPU period
|
||||
* `cpu_period` (Optional, int) - The length of a CPU period in microseconds
|
||||
* `memory` (Optional, int) - Set memory limit for build
|
||||
* `memory_swap` (Optional, int) - Total memory (memory + swap), -1 to enable unlimited swap
|
||||
* `cgroup_parent` (Optional, string) - Optional parent cgroup for the container
|
||||
* `network_mode` (Optional, string) - Set the networking mode for the RUN instructions during build
|
||||
* `shm_size` (Optional, int) - Size of /dev/shm in bytes. The size must be greater than 0
|
||||
* `` (Optional, string) - Set the networking mode for the RUN instructions during build
|
||||
* `dockerfile` (Optional, string) - Dockerfile file. Default is "Dockerfile"
|
||||
* `ulimit` (Optional, Map) - See [Ulimit](#ulimit-1) below for details
|
||||
* `build_args` (Optional, map of key/value pairs) string pairs for build-time variables
|
||||
* `auth_config` (Optional, Map) - See [AuthConfig](#authconfig-1) below for details
|
||||
* `labels` (Optional, map of key/value pairs) string pairs for labels
|
||||
* `squash` (Optional, bool) - squash the new layers into a new image with a single new layer
|
||||
* `cache_from` (Optional, []string) - Images to consider as cache sources
|
||||
* `security_opt` (Optional, []string) - Security options
|
||||
* `extra_hosts` (Optional, []string) - A list of hostnames/IP mappings to add to the container’s /etc/hosts file. Specified in the form ["hostname:IP"]
|
||||
* `target` (Optional, string) - Set the target build stage to build
|
||||
* `platform` (Optional, string) - Set platform if server is multi-platform capable
|
||||
* `version` (Optional, string) - Version of the unerlying builder to use
|
||||
* `build_id` (Optional, string) - BuildID is an optional identifier that can be passed together with the build request. The same identifier can be used to gracefully cancel the build with the cancel request
|
||||
|
||||
<a id="ulimit-1"></a>
|
||||
#### Ulimit Block
|
||||
|
||||
* `name` - (Required, string) type of ulimit, e.g. nofile
|
||||
* `soft` (Required, int) - soft limit
|
||||
* `hard` (Required, int) - hard limit
|
||||
|
||||
<a id="authconfig-1"></a>
|
||||
#### AuthConfig Block
|
||||
|
||||
* `host_name` - (Required, string) hostname of the registry
|
||||
* `user_name` - (Optional, string) the registry user name
|
||||
* `password` - (Optional, string) the registry password
|
||||
* `auth` - (Optional, string) the auth token
|
||||
* `email` - (Optional, string) the user emal
|
||||
* `server_address` - (Optional, string) the server address
|
||||
* `identity_token` - (Optional, string) the identity token
|
||||
* `registry_token` - (Optional, string) the registry token
|
||||
|
||||
## 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