mirror of
https://github.com/kreuzwerker/terraform-provider-docker.git
synced 2025-12-20 22:59:42 -05:00
585 lines
19 KiB
Go
585 lines
19 KiB
Go
package provider
|
|
|
|
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"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/docker/cli/cli/command/image/build"
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/api/types/container"
|
|
"github.com/docker/docker/client"
|
|
"github.com/docker/docker/pkg/fileutils"
|
|
"github.com/docker/go-units"
|
|
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
|
|
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
|
|
)
|
|
|
|
func resourceDockerRegistryImageCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
|
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 := buildDockerRegistryImage(ctx, client, buildOptionsMap, pushOpts.FqName)
|
|
if err != nil {
|
|
return diag.Errorf("Error building docker image: %s", err)
|
|
}
|
|
}
|
|
|
|
username, password := getDockerRegistryImageRegistryUserNameAndPassword(pushOpts, providerConfig)
|
|
if err := pushDockerRegistryImage(ctx, client, pushOpts, username, password); err != nil {
|
|
return diag.Errorf("Error pushing docker image: %s", err)
|
|
}
|
|
|
|
insecureSkipVerify := d.Get("insecure_skip_verify").(bool)
|
|
digest, err := getImageDigestWithFallback(pushOpts, username, password, insecureSkipVerify)
|
|
if err != nil {
|
|
return diag.Errorf("Unable to create image, image not found: %s", err)
|
|
}
|
|
d.SetId(digest)
|
|
d.Set("sha256_digest", digest)
|
|
return nil
|
|
}
|
|
|
|
func resourceDockerRegistryImageRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
|
providerConfig := meta.(*ProviderConfig)
|
|
name := d.Get("name").(string)
|
|
pushOpts := createPushImageOptions(name)
|
|
username, password := getDockerRegistryImageRegistryUserNameAndPassword(pushOpts, providerConfig)
|
|
|
|
insecureSkipVerify := d.Get("insecure_skip_verify").(bool)
|
|
digest, err := getImageDigestWithFallback(pushOpts, username, password, insecureSkipVerify)
|
|
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(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
|
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, true, false)
|
|
if err != nil {
|
|
err = deleteDockerRegistryImage(pushOpts, pushOpts.Tag, username, password, true, true)
|
|
if err != nil {
|
|
return diag.Errorf("Got error deleting registry image: %s", err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func resourceDockerRegistryImageUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
|
|
return resourceDockerRegistryImageRead(ctx, d, meta)
|
|
}
|
|
|
|
// Helpers
|
|
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 buildDockerRegistryImage(ctx context.Context, 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[lastIndex+1] != filepath.Separator) {
|
|
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)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer dockerBuildContext.Close()
|
|
|
|
buildResponse, err := client.ImageBuild(ctx, 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())
|
|
}
|
|
|
|
excludes, err := build.ReadDockerignore(buildContext)
|
|
if err != nil {
|
|
return "", fmt.Errorf("unable to read .dockerignore file - %v", err.Error())
|
|
}
|
|
|
|
pm, err := fileutils.NewPatternMatcher(excludes)
|
|
if err != nil {
|
|
return "", fmt.Errorf("unable to create pattern matcher from .dockerignore exlcudes - %v", err.Error())
|
|
}
|
|
|
|
tw := tar.NewWriter(tmpFile)
|
|
defer tw.Close()
|
|
|
|
if err := filepath.Walk(buildContext, func(file string, info os.FileInfo, err error) error {
|
|
// return on any error
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// if .dockerignore is present, ignore files from there
|
|
skip, err := pm.Matches(file)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// adapted from https://github.com/moby/moby/blob/v20.10.7/pkg/archive/archive.go#L851
|
|
if skip {
|
|
log.Printf("[DEBUG] Skipping file/dir from image build '%v'", file)
|
|
// If we want to skip this file and its a directory
|
|
// then we should first check to see if there's an
|
|
// excludes pattern (e.g. !dir/file) that starts with this
|
|
// dir. If so then we can't skip this dir.
|
|
|
|
// Its not a dir then so we can just return/skip.
|
|
if !info.IsDir() {
|
|
return nil
|
|
}
|
|
|
|
// No exceptions (!...) in patterns so just skip dir
|
|
if !pm.Exclusions() {
|
|
return filepath.SkipDir
|
|
}
|
|
|
|
dirSlash := file + string(filepath.Separator)
|
|
|
|
for _, pat := range pm.Patterns() {
|
|
if !pat.Exclusion() {
|
|
continue
|
|
}
|
|
if strings.HasPrefix(pat.String()+string(filepath.Separator), dirSlash) {
|
|
// found a match - so can't skip this dir
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// No matching exclusion dir so just skip dir
|
|
return filepath.SkipDir
|
|
}
|
|
|
|
// 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))
|
|
|
|
// set archive metadata non deterministic
|
|
header.Mode = 0
|
|
header.Uid = 0
|
|
header.Gid = 0
|
|
header.Uname = ""
|
|
header.Gname = ""
|
|
header.ModTime = time.Time{}
|
|
header.AccessTime = time.Time{}
|
|
header.ChangeTime = time.Time{}
|
|
|
|
// 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
|
|
}); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return tmpFile.Name(), nil
|
|
}
|
|
|
|
func getDockerImageContextTarHash(dockerContextTarPath string) (string, error) {
|
|
hasher := sha256.New()
|
|
s, err := ioutil.ReadFile(dockerContextTarPath)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if _, err := hasher.Write(s); err != nil {
|
|
return "", err
|
|
}
|
|
contextHash := hex.EncodeToString(hasher.Sum(nil))
|
|
return contextHash, nil
|
|
}
|
|
|
|
func pushDockerRegistryImage(ctx context.Context, 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(ctx, 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
|
|
}
|
|
if err := json.Unmarshal(streamBytes, &errorMessage); err != nil {
|
|
return err
|
|
}
|
|
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, insecureSkipVerify, fallback bool) error {
|
|
client := http.DefaultClient
|
|
|
|
// DevSkim: ignore DS440000
|
|
client.Transport = &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: insecureSkipVerify}}
|
|
|
|
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)
|
|
}
|
|
|
|
// We accept schema v2 manifests and manifest lists, and also OCI types
|
|
req.Header.Add("Accept", "application/vnd.docker.distribution.manifest.v2+json")
|
|
req.Header.Add("Accept", "application/vnd.docker.distribution.manifest.list.v2+json")
|
|
req.Header.Add("Accept", "application/vnd.oci.image.manifest.v1+json")
|
|
req.Header.Add("Accept", "application/vnd.oci.image.index.v1+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)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
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, insecureSkipVerify bool) (string, error) {
|
|
digest, err := getImageDigest(opts.Registry, opts.Repository, opts.Tag, username, password, insecureSkipVerify, false)
|
|
if err != nil {
|
|
digest, err = getImageDigest(opts.Registry, opts.Repository, opts.Tag, username, password, insecureSkipVerify, 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-1.docker.io"
|
|
} 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
|
|
}
|