mirror of
https://github.com/kreuzwerker/terraform-provider-docker.git
synced 2026-01-06 23:09:36 -05:00
Fixes for image pulling and local registry (#143)
Multiple fixes for handling private registries and login credential retrieval on OSX. - Fixes check for image locally before pulling it: #24 - Fixes image prefix workaround: #120 - Fixes passing of the `--with-registry-auth` flag: #77 - Fixes docker registry credentials in osxkeychain: #125
This commit is contained in:
parent
a8f3437b29
commit
a6fdf4c2a6
24 changed files with 1510 additions and 2992 deletions
|
|
@ -2,7 +2,6 @@ package docker
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"testing"
|
||||
|
||||
|
|
@ -42,8 +41,8 @@ func TestAccDockerRegistryImage_private(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestAccDockerRegistryImage_auth(t *testing.T) {
|
||||
registry := os.Getenv("DOCKER_REGISTRY_ADDRESS")
|
||||
image := os.Getenv("DOCKER_PRIVATE_IMAGE")
|
||||
registry := "127.0.0.1:15000"
|
||||
image := "127.0.0.1:15000/tftest-service:v1"
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
|
|
@ -55,6 +54,7 @@ func TestAccDockerRegistryImage_auth(t *testing.T) {
|
|||
),
|
||||
},
|
||||
},
|
||||
CheckDestroy: checkAndRemoveImages,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,10 +8,13 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/user"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
osx "github.com/docker/docker-credential-helpers/client"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
"github.com/hashicorp/terraform/terraform"
|
||||
|
|
@ -134,7 +137,7 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
|
|||
|
||||
authConfigs := &AuthConfigs{}
|
||||
|
||||
if v, ok := d.GetOk("registry_auth"); ok {
|
||||
if v, ok := d.GetOk("registry_auth"); ok { // TODO load them anyway
|
||||
authConfigs, err = providerSetToRegistryAuth(v.(*schema.Set))
|
||||
|
||||
if err != nil {
|
||||
|
|
@ -224,14 +227,17 @@ func providerSetToRegistryAuth(authSet *schema.Set) (*AuthConfigs, error) {
|
|||
}
|
||||
|
||||
// newAuthConfigurations returns AuthConfigs from a JSON encoded string in the
|
||||
// same format as the .dockercfg file.
|
||||
// same format as the .dockercfg/ ~/.docker/config.json file.
|
||||
func newAuthConfigurations(r io.Reader) (*AuthConfigs, error) {
|
||||
var auth *AuthConfigs
|
||||
log.Println("[DEBUG] Parsing Docker config file")
|
||||
confs, err := parseDockerConfig(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
auth, err = authConfigs(confs)
|
||||
|
||||
log.Printf("[DEBUG] Found Docker configs '%v'", confs)
|
||||
auth, err = convertDockerConfigToAuthConfigs(confs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -260,13 +266,18 @@ func parseDockerConfig(r io.Reader) (map[string]dockerConfig, error) {
|
|||
return confs, nil
|
||||
}
|
||||
|
||||
// authConfigs converts a dockerConfigs map to a AuthConfigs object.
|
||||
func authConfigs(confs map[string]dockerConfig) (*AuthConfigs, error) {
|
||||
// convertDockerConfigToAuthConfigs converts a dockerConfigs map to a AuthConfigs object.
|
||||
func convertDockerConfigToAuthConfigs(confs map[string]dockerConfig) (*AuthConfigs, error) {
|
||||
c := &AuthConfigs{
|
||||
Configs: make(map[string]types.AuthConfig),
|
||||
}
|
||||
for reg, conf := range confs {
|
||||
for registryAddress, conf := range confs {
|
||||
if conf.Auth == "" {
|
||||
authFromKeyChain, err := getCredentialsFromOSKeychain(registryAddress)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.Configs[registryAddress] = authFromKeyChain
|
||||
continue
|
||||
}
|
||||
data, err := base64.StdEncoding.DecodeString(conf.Auth)
|
||||
|
|
@ -277,13 +288,31 @@ func authConfigs(confs map[string]dockerConfig) (*AuthConfigs, error) {
|
|||
if len(userpass) != 2 {
|
||||
return nil, ErrCannotParseDockercfg
|
||||
}
|
||||
c.Configs[reg] = types.AuthConfig{
|
||||
c.Configs[registryAddress] = types.AuthConfig{
|
||||
Email: conf.Email,
|
||||
Username: userpass[0],
|
||||
Password: userpass[1],
|
||||
ServerAddress: reg,
|
||||
ServerAddress: registryAddress,
|
||||
Auth: conf.Auth,
|
||||
}
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// getCredentialsFromOSKeychain get config from system specific keychains
|
||||
func getCredentialsFromOSKeychain(registryAddress string) (types.AuthConfig, error) {
|
||||
authConfig := types.AuthConfig{}
|
||||
log.Printf("[DEBUG] Getting auth for registry '%s' on OS: '%s'", registryAddress, runtime.GOOS)
|
||||
if runtime.GOOS == "darwin" {
|
||||
p := osx.NewShellProgramFunc("docker-credential-osxkeychain")
|
||||
credentials, err := osx.Get(p, registryAddress)
|
||||
if err != nil {
|
||||
return authConfig, err
|
||||
}
|
||||
authConfig.Username = credentials.Username
|
||||
authConfig.Password = credentials.Secret
|
||||
authConfig.ServerAddress = registryAddress
|
||||
authConfig.Auth = base64.StdEncoding.EncodeToString([]byte(credentials.Username + ":" + credentials.Secret))
|
||||
}
|
||||
return authConfig, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"testing"
|
||||
|
||||
|
|
@ -43,19 +42,6 @@ func testAccPreCheck(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
if v := os.Getenv("DOCKER_REGISTRY_ADDRESS"); v == "" {
|
||||
t.Fatalf("DOCKER_REGISTRY_ADDRESS must be set for acceptance tests")
|
||||
}
|
||||
if v := os.Getenv("DOCKER_REGISTRY_USER"); v == "" {
|
||||
t.Fatalf("DOCKER_REGISTRY_USER must be set for acceptance tests")
|
||||
}
|
||||
if v := os.Getenv("DOCKER_REGISTRY_PASS"); v == "" {
|
||||
t.Fatalf("DOCKER_REGISTRY_PASS must be set for acceptance tests")
|
||||
}
|
||||
if v := os.Getenv("DOCKER_PRIVATE_IMAGE"); v == "" {
|
||||
t.Fatalf("DOCKER_PRIVATE_IMAGE must be set for acceptance tests")
|
||||
}
|
||||
|
||||
err := testAccProvider.Configure(terraform.NewResourceConfig(nil))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
|
|||
|
|
@ -84,6 +84,7 @@ func resourceDockerContainer() *schema.Resource {
|
|||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
// DiffSuppressFunc: suppressIfSHAwasAdded(), // TODO mvogel
|
||||
},
|
||||
|
||||
"hostname": {
|
||||
|
|
|
|||
|
|
@ -32,18 +32,11 @@ var (
|
|||
func resourceDockerContainerCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
var err error
|
||||
client := meta.(*ProviderConfig).DockerClient
|
||||
|
||||
var data Data
|
||||
if err := fetchLocalImages(&data, client); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
authConfigs := meta.(*ProviderConfig).AuthConfigs
|
||||
image := d.Get("image").(string)
|
||||
if _, ok := data.DockerImages[image]; !ok {
|
||||
if _, ok := data.DockerImages[image+":latest"]; !ok {
|
||||
return fmt.Errorf("Unable to find image %s", image)
|
||||
}
|
||||
image = image + ":latest"
|
||||
_, err = findImage(image, client, authConfigs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to create container with image %s: %s", image, err)
|
||||
}
|
||||
|
||||
config := &container.Config{
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"archive/tar"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -29,6 +30,28 @@ func TestMapTypeMapValsToStringSlice(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAccDockerContainer_private_image(t *testing.T) {
|
||||
registry := "127.0.0.1:15000"
|
||||
image := "127.0.0.1:15000/tftest-service:v1"
|
||||
wd, _ := os.Getwd()
|
||||
dockerConfig := wd + "/../scripts/testing/dockerconfig.json"
|
||||
|
||||
var c types.ContainerJSON
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: fmt.Sprintf(testAccDockerContainerPrivateImage, registry, dockerConfig, image),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccContainerRunning("docker_container.foo", &c),
|
||||
),
|
||||
},
|
||||
},
|
||||
CheckDestroy: checkAndRemoveImages,
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccDockerContainer_basic(t *testing.T) {
|
||||
var c types.ContainerJSON
|
||||
resource.Test(t, resource.TestCase{
|
||||
|
|
@ -1125,6 +1148,22 @@ func testValueHigherEqualThan(name, key string, value int) resource.TestCheckFun
|
|||
}
|
||||
}
|
||||
|
||||
const testAccDockerContainerPrivateImage = `
|
||||
provider "docker" {
|
||||
alias = "private"
|
||||
registry_auth {
|
||||
address = "%s"
|
||||
config_file = "%s"
|
||||
}
|
||||
}
|
||||
|
||||
resource "docker_container" "foo" {
|
||||
provider = "docker.private"
|
||||
name = "tf-test"
|
||||
image = "%s"
|
||||
}
|
||||
`
|
||||
|
||||
const testAccDockerContainerConfig = `
|
||||
resource "docker_image" "foo" {
|
||||
name = "nginx:latest"
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ import (
|
|||
|
||||
func resourceDockerImageCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
client := meta.(*ProviderConfig).DockerClient
|
||||
apiImage, err := findImage(d, client, meta.(*ProviderConfig).AuthConfigs)
|
||||
imageName := d.Get("name").(string)
|
||||
apiImage, err := findImage(imageName, client, meta.(*ProviderConfig).AuthConfigs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to read Docker image into resource: %s", err)
|
||||
}
|
||||
|
|
@ -49,7 +50,8 @@ func resourceDockerImageUpdate(d *schema.ResourceData, meta interface{}) error {
|
|||
// We need to re-read in case switching parameters affects
|
||||
// the value of "latest" or others
|
||||
client := meta.(*ProviderConfig).DockerClient
|
||||
apiImage, err := findImage(d, client, meta.(*ProviderConfig).AuthConfigs)
|
||||
imageName := d.Get("name").(string)
|
||||
apiImage, err := findImage(imageName, client, meta.(*ProviderConfig).AuthConfigs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to read Docker image into resource: %s", err)
|
||||
}
|
||||
|
|
@ -170,7 +172,7 @@ func pullImage(data *Data, client *client.Client, authConfig *AuthConfigs, image
|
|||
s := buf.String()
|
||||
log.Printf("[DEBUG] pulled image %v: %v", image, s)
|
||||
|
||||
return fetchLocalImages(data, client)
|
||||
return nil
|
||||
}
|
||||
|
||||
type internalPullImageOptions struct {
|
||||
|
|
@ -210,19 +212,15 @@ func parseImageOptions(image string) internalPullImageOptions {
|
|||
return pullOpts
|
||||
}
|
||||
|
||||
func findImage(d *schema.ResourceData, client *client.Client, authConfig *AuthConfigs) (*types.ImageSummary, error) {
|
||||
var data Data
|
||||
//if err := fetchLocalImages(&data, client); err != nil {
|
||||
// return nil, err
|
||||
//} Is done in pullImage
|
||||
|
||||
imageName := d.Get("name").(string)
|
||||
func findImage(imageName string, client *client.Client, authConfig *AuthConfigs) (*types.ImageSummary, error) {
|
||||
if imageName == "" {
|
||||
return nil, fmt.Errorf("Empty image name is not allowed")
|
||||
}
|
||||
|
||||
if err := pullImage(&data, client, authConfig, imageName); err != nil {
|
||||
return nil, fmt.Errorf("Unable to pull image %s: %s", imageName, err)
|
||||
var data Data
|
||||
// load local images into the data structure
|
||||
if err := fetchLocalImages(&data, client); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
foundImage := searchLocalImages(data, imageName)
|
||||
|
|
@ -230,5 +228,19 @@ func findImage(d *schema.ResourceData, client *client.Client, authConfig *AuthCo
|
|||
return foundImage, nil
|
||||
}
|
||||
|
||||
if err := pullImage(&data, client, authConfig, imageName); err != nil {
|
||||
return nil, fmt.Errorf("Unable to pull image %s: %s", imageName, err)
|
||||
}
|
||||
|
||||
// update the data structure of the images
|
||||
if err := fetchLocalImages(&data, client); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
foundImage = searchLocalImages(data, imageName)
|
||||
if foundImage != nil {
|
||||
return foundImage, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("Unable to find or pull image %s", imageName)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,8 +107,8 @@ func TestAccDockerImage_data_pull_trigger(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestAccDockerImage_data_private(t *testing.T) {
|
||||
registry := os.Getenv("DOCKER_REGISTRY_ADDRESS")
|
||||
image := os.Getenv("DOCKER_PRIVATE_IMAGE")
|
||||
registry := "127.0.0.1:15000"
|
||||
image := "127.0.0.1:15000/tftest-service:v1"
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
|
|
@ -122,6 +122,29 @@ func TestAccDockerImage_data_private(t *testing.T) {
|
|||
),
|
||||
},
|
||||
},
|
||||
CheckDestroy: checkAndRemoveImages,
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccDockerImage_data_private_config_file(t *testing.T) {
|
||||
registry := "127.0.0.1:15000"
|
||||
image := "127.0.0.1:15000/tftest-service:v1"
|
||||
wd, _ := os.Getwd()
|
||||
dockerConfig := wd + "/../scripts/testing/dockerconfig.json"
|
||||
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
PreventPostDestroyRefresh: true,
|
||||
Steps: []resource.TestStep{
|
||||
{
|
||||
Config: fmt.Sprintf(testAccDockerImageFromDataPrivateConfigFile, registry, dockerConfig, image),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
resource.TestMatchResourceAttr("docker_image.foo_private", "latest", contentDigestRegexp),
|
||||
),
|
||||
},
|
||||
},
|
||||
CheckDestroy: checkAndRemoveImages,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -214,6 +237,20 @@ resource "docker_image" "foo_private" {
|
|||
}
|
||||
`
|
||||
|
||||
const testAccDockerImageFromDataPrivateConfigFile = `
|
||||
provider "docker" {
|
||||
alias = "private"
|
||||
registry_auth {
|
||||
address = "%s"
|
||||
config_file = "%s"
|
||||
}
|
||||
}
|
||||
resource "docker_image" "foo_private" {
|
||||
provider = "docker.private"
|
||||
name = "%s"
|
||||
}
|
||||
`
|
||||
|
||||
const testAddDockerImageWithSHA256RepoDigest = `
|
||||
resource "docker_image" "foobar" {
|
||||
name = "stocard/gotthard@sha256:ed752380c07940c651b46c97ca2101034b3be112f4d86198900aa6141f37fe7b"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,10 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/terraform/helper/schema"
|
||||
)
|
||||
|
||||
|
|
@ -70,9 +74,10 @@ func resourceDockerService() *schema.Resource {
|
|||
Elem: &schema.Resource{
|
||||
Schema: map[string]*schema.Schema{
|
||||
"image": {
|
||||
Type: schema.TypeString,
|
||||
Description: "The image name to use for the containers of the service",
|
||||
Required: true,
|
||||
Type: schema.TypeString,
|
||||
Description: "The image name to use for the containers of the service",
|
||||
Required: true,
|
||||
DiffSuppressFunc: suppressIfSHAwasAdded(),
|
||||
},
|
||||
"labels": {
|
||||
Type: schema.TypeMap,
|
||||
|
|
@ -899,3 +904,96 @@ func resourceDockerService() *schema.Resource {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
func suppressIfSHAwasAdded() schema.SchemaDiffSuppressFunc {
|
||||
return func(k, old, new string, d *schema.ResourceData) bool {
|
||||
// the initial case when the service is created
|
||||
if old == "" && new != "" {
|
||||
return false
|
||||
}
|
||||
|
||||
oldURL, oldImage, oldTag, oldDigest, oldErr := splitImageName(old)
|
||||
if oldErr != nil {
|
||||
log.Printf("[DEBUG] invalid old image name: %s\n", oldErr.Error())
|
||||
return false
|
||||
}
|
||||
log.Printf("[DEBUG] old image parse: %s, %s, %s, %s\n", oldURL, oldImage, oldTag, oldDigest)
|
||||
|
||||
newURL, newImage, newTag, newDigest, newErr := splitImageName(new)
|
||||
if newErr != nil {
|
||||
log.Printf("[DEBUG] invalid new image name: %s\n", newErr.Error())
|
||||
return false
|
||||
}
|
||||
log.Printf("[DEBUG] new image parse: %s, %s, %s, %s\n", newURL, newImage, newTag, newDigest)
|
||||
|
||||
if oldURL != newURL || oldImage != newImage {
|
||||
return false
|
||||
}
|
||||
|
||||
// special case with latest
|
||||
if oldTag == "latest" && (newTag == "" || newTag == "latest") {
|
||||
if oldDigest != "" && newDigest == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// https://success.docker.com/article/images-tagging-vs-digests
|
||||
// we always pull if the tag changes, also in the empty and 'latest' case
|
||||
if (oldTag == "latest" || newTag == "") || (oldTag == "" && newTag == "latest") {
|
||||
return false
|
||||
}
|
||||
|
||||
if oldTag != newTag {
|
||||
return false
|
||||
}
|
||||
|
||||
// tags are the same and so should be its digests
|
||||
if oldDigest == newDigest || (oldDigest == "" && newDigest != "") || (oldDigest != "" && newDigest == "") {
|
||||
return true
|
||||
}
|
||||
|
||||
// we only update if the digests are given and different
|
||||
if oldDigest != newDigest {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// spitImageName splits an image with name 127.0.0.1:15000/tftest-service:v1@sha256:24..
|
||||
// into its parts. Handles edge cases like no tag and no digest
|
||||
func splitImageName(imageNameToSplit string) (url, image, tag, digest string, err error) {
|
||||
urlToRestSplit := strings.Split(imageNameToSplit, "/")
|
||||
if len(urlToRestSplit) != 2 {
|
||||
return "", "", "", "", fmt.Errorf("image name is not valid: %s", imageNameToSplit)
|
||||
}
|
||||
url = urlToRestSplit[0]
|
||||
imageNameToRestSplit := strings.Split(urlToRestSplit[1], ":")
|
||||
// we only have an image name without tag and sha256
|
||||
if len(imageNameToRestSplit) == 1 {
|
||||
image = imageNameToRestSplit[0]
|
||||
return url, image, "", "", nil
|
||||
}
|
||||
// has tag and sha256
|
||||
if len(imageNameToRestSplit) == 3 {
|
||||
image = imageNameToRestSplit[0]
|
||||
tag = strings.Replace(imageNameToRestSplit[1], "@sha256", "", 1)
|
||||
digest = imageNameToRestSplit[2]
|
||||
return url, image, tag, digest, nil
|
||||
}
|
||||
// can be either with tag or sha256, which implies 'latest' tag
|
||||
if len(imageNameToRestSplit) == 2 {
|
||||
image = imageNameToRestSplit[0]
|
||||
if strings.Contains(imageNameToRestSplit[1], "sha256") {
|
||||
digest = imageNameToRestSplit[1]
|
||||
return url, image, "", digest, nil
|
||||
}
|
||||
tag = strings.Replace(imageNameToRestSplit[1], "@sha256", "", 1)
|
||||
return url, image, tag, "", nil
|
||||
}
|
||||
|
||||
return "", "", "", "", fmt.Errorf("image name is not valid: %s", imageNameToSplit)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,13 +62,15 @@ func resourceDockerServiceCreate(d *schema.ResourceData, meta interface{}) error
|
|||
if v, ok := d.GetOk("auth"); ok {
|
||||
auth = authToServiceAuth(v.(map[string]interface{}))
|
||||
} else {
|
||||
auth = fromRegistryAuth(d.Get("task_spec.0.container_spec.0.image").(string), meta.(*ProviderConfig).AuthConfigs.Configs)
|
||||
authConfigs := meta.(*ProviderConfig).AuthConfigs.Configs
|
||||
log.Printf("[DEBUG] Getting configs from '%v'", authConfigs)
|
||||
auth = fromRegistryAuth(d.Get("task_spec.0.container_spec.0.image").(string), authConfigs)
|
||||
}
|
||||
encodedJSON, err := json.Marshal(auth)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error creating auth config: %s", err)
|
||||
}
|
||||
serviceOptions.EncodedRegistryAuth = base64.URLEncoding.EncodeToString(encodedJSON)
|
||||
|
||||
marshalledAuth, _ := json.Marshal(auth) // https://docs.docker.com/engine/api/v1.37/#section/Versioning
|
||||
serviceOptions.EncodedRegistryAuth = base64.URLEncoding.EncodeToString(marshalledAuth)
|
||||
serviceOptions.QueryRegistry = true
|
||||
log.Printf("[DEBUG] Passing registry auth '%s'", serviceOptions.EncodedRegistryAuth)
|
||||
|
||||
service, err := client.ServiceCreate(context.Background(), serviceSpec, serviceOptions)
|
||||
if err != nil {
|
||||
|
|
@ -1284,15 +1286,15 @@ func authToServiceAuth(auth map[string]interface{}) types.AuthConfig {
|
|||
}
|
||||
|
||||
// fromRegistryAuth extract the desired AuthConfiguration for the given image
|
||||
func fromRegistryAuth(image string, configs map[string]types.AuthConfig) types.AuthConfig {
|
||||
// Remove normalized prefixes to simlify substring
|
||||
func fromRegistryAuth(image string, authConfigs map[string]types.AuthConfig) types.AuthConfig {
|
||||
// Remove normalized prefixes to simplify substring
|
||||
image = strings.Replace(strings.Replace(image, "http://", "", 1), "https://", "", 1)
|
||||
// Get the registry with optional port
|
||||
lastBin := strings.Index(image, "/")
|
||||
// No auth given and image name has no slash like 'alpine:3.1'
|
||||
if lastBin != -1 {
|
||||
serverAddress := image[0:lastBin]
|
||||
if fromRegistryAuth, ok := configs[normalizeRegistryAddress(serverAddress)]; ok {
|
||||
if fromRegistryAuth, ok := authConfigs[normalizeRegistryAddress(serverAddress)]; ok {
|
||||
return fromRegistryAuth
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
1
go.mod
1
go.mod
|
|
@ -5,6 +5,7 @@ require (
|
|||
github.com/blang/semver v3.5.1+incompatible // indirect
|
||||
github.com/docker/distribution v0.0.0-20180522175653-f0cc92778478 // indirect
|
||||
github.com/docker/docker v0.0.0-20180530012807-65bd038fc5e4
|
||||
github.com/docker/docker-credential-helpers v0.6.2
|
||||
github.com/docker/go-connections v0.4.0
|
||||
github.com/docker/go-units v0.0.0-20171221200356-d59758554a3d
|
||||
github.com/gogo/protobuf v0.0.0-20180121160031-26de2f9a7d3b // indirect
|
||||
|
|
|
|||
2
go.sum
2
go.sum
|
|
@ -62,6 +62,8 @@ github.com/docker/distribution v0.0.0-20180522175653-f0cc92778478 h1:yCzgNaIN+6i
|
|||
github.com/docker/distribution v0.0.0-20180522175653-f0cc92778478/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v0.0.0-20180530012807-65bd038fc5e4 h1:l3X/U8oT8xvht8bB/kHUntALLPIYSzmUf4Wyr6UnGWU=
|
||||
github.com/docker/docker v0.0.0-20180530012807-65bd038fc5e4/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/docker-credential-helpers v0.6.2 h1:CrW9H1VMf3a4GrtyAi7IUJjkJVpwBBpX0+mvkvYJaus=
|
||||
github.com/docker/docker-credential-helpers v0.6.2/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
|
||||
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
|
||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||
github.com/docker/go-units v0.0.0-20171221200356-d59758554a3d h1:QEYTOUa4JROW5CSAHU8qc0rD8uhx1tsUkBsSDk8P5eM=
|
||||
|
|
|
|||
|
|
@ -2,32 +2,63 @@
|
|||
set -e
|
||||
|
||||
log() {
|
||||
echo ""
|
||||
echo "##################################"
|
||||
echo "-------> $1"
|
||||
echo "##################################"
|
||||
echo "####################"
|
||||
echo "## -> $1 "
|
||||
echo "####################"
|
||||
}
|
||||
|
||||
setup() {
|
||||
export DOCKER_REGISTRY_ADDRESS="127.0.0.1:15000"
|
||||
export DOCKER_REGISTRY_USER="testuser"
|
||||
export DOCKER_REGISTRY_PASS="testpwd"
|
||||
export DOCKER_PRIVATE_IMAGE="127.0.0.1:15000/tftest-service:v1"
|
||||
sh "$(pwd)"/scripts/testing/setup_private_registry.sh
|
||||
# Create self signed certs
|
||||
mkdir -p "$(pwd)"/scripts/testing/certs
|
||||
openssl req \
|
||||
-newkey rsa:2048 \
|
||||
-nodes \
|
||||
-x509 \
|
||||
-days 365 \
|
||||
-subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=127.0.0.1" \
|
||||
-keyout "$(pwd)"/scripts/testing/certs/registry_auth.key \
|
||||
-out "$(pwd)"/scripts/testing/certs/registry_auth.crt
|
||||
# Create auth
|
||||
mkdir -p "$(pwd)"/scripts/testing/auth
|
||||
# Start registry
|
||||
docker run --rm --entrypoint htpasswd registry:2 -Bbn testuser testpwd > "$(pwd)"/scripts/testing/auth/htpasswd
|
||||
docker run -d -p 15000:5000 --rm --name private_registry \
|
||||
-v "$(pwd)"/scripts/testing/auth:/auth \
|
||||
-e "REGISTRY_AUTH=htpasswd" \
|
||||
-e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
|
||||
-e "REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd" \
|
||||
-v "$(pwd)"/scripts/testing/certs:/certs \
|
||||
-e "REGISTRY_HTTP_TLS_CERTIFICATE=/certs/registry_auth.crt" \
|
||||
-e "REGISTRY_HTTP_TLS_KEY=/certs/registry_auth.key" \
|
||||
registry:2
|
||||
# wait a bit for travis...
|
||||
sleep 5
|
||||
# Login to private registry
|
||||
docker login -u testuser -p testpwd 127.0.0.1:15000
|
||||
# Build private images
|
||||
for i in $(seq 1 3); do
|
||||
docker build -t tftest-service --build-arg JS_FILE_PATH=server_v${i}.js "$(pwd)"/scripts/testing -f "$(pwd)"/scripts/testing/Dockerfile
|
||||
docker tag tftest-service 127.0.0.1:15000/tftest-service:v${i}
|
||||
docker push 127.0.0.1:15000/tftest-service:v${i}
|
||||
docker tag tftest-service 127.0.0.1:15000/tftest-service
|
||||
docker push 127.0.0.1:15000/tftest-service
|
||||
done
|
||||
# Remove images from host machine before starting the tests
|
||||
for i in $(docker images -aq 127.0.0.1:15000/tftest-service); do docker rmi -f "$i"; done
|
||||
}
|
||||
|
||||
run() {
|
||||
go clean -testcache
|
||||
TF_ACC=1 go test ./docker -v -timeout 120m
|
||||
|
||||
# for a single test comment the previous line and uncomment the next line
|
||||
#TF_LOG=INFO TF_ACC=1 go test -v github.com/terraform-providers/terraform-provider-docker/docker -run ^TestAccDockerContainer_port$ -timeout 360s
|
||||
#TF_LOG=INFO TF_ACC=1 go test -v ./docker -run ^TestAccDockerImage_data_private_config_file$ -timeout 360s
|
||||
|
||||
# keep the return value for the scripts to fail and clean properly
|
||||
return $?
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
unset DOCKER_REGISTRY_ADDRESS DOCKER_REGISTRY_USER DOCKER_REGISTRY_PASS DOCKER_PRIVATE_IMAGE
|
||||
echo "### unsetted env ###"
|
||||
for p in $(docker container ls -f 'name=private_registry' -q); do docker stop $p; done
|
||||
echo "### stopped private registry ###"
|
||||
|
|
|
|||
7
scripts/testing/dockerconfig.json
Normal file
7
scripts/testing/dockerconfig.json
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"auths": {
|
||||
"127.0.0.1:15000": {
|
||||
"auth": "dGVzdHVzZXI6dGVzdHB3ZA=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Create private registry
|
||||
## Create self signed certs
|
||||
mkdir -p "$(pwd)"/scripts/testing/certs
|
||||
openssl req \
|
||||
-newkey rsa:2048 \
|
||||
-nodes \
|
||||
-x509 \
|
||||
-days 365 \
|
||||
-subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=127.0.0.1" \
|
||||
-keyout "$(pwd)"/scripts/testing/certs/registry_auth.key \
|
||||
-out "$(pwd)"/scripts/testing/certs/registry_auth.crt
|
||||
## Create auth
|
||||
mkdir -p "$(pwd)"/scripts/testing/auth
|
||||
# Start registry
|
||||
docker run --rm --entrypoint htpasswd registry:2 -Bbn testuser testpwd > "$(pwd)"/scripts/testing/auth/htpasswd
|
||||
docker run -d -p 15000:5000 --rm --name private_registry \
|
||||
-v "$(pwd)"/scripts/testing/auth:/auth \
|
||||
-e "REGISTRY_AUTH=htpasswd" \
|
||||
-e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
|
||||
-e "REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd" \
|
||||
-v "$(pwd)"/scripts/testing/certs:/certs \
|
||||
-e "REGISTRY_HTTP_TLS_CERTIFICATE=/certs/registry_auth.crt" \
|
||||
-e "REGISTRY_HTTP_TLS_KEY=/certs/registry_auth.key" \
|
||||
registry:2
|
||||
# wait a bit for travis...
|
||||
sleep 5
|
||||
# Login to private registry
|
||||
docker login -u testuser -p testpwd 127.0.0.1:15000
|
||||
# Build private images
|
||||
for i in $(seq 1 3); do
|
||||
docker build -t tftest-service --build-arg JS_FILE_PATH=server_v${i}.js "$(pwd)"/scripts/testing -f "$(pwd)"/scripts/testing/Dockerfile
|
||||
docker tag tftest-service 127.0.0.1:15000/tftest-service:v${i}
|
||||
docker push 127.0.0.1:15000/tftest-service:v${i}
|
||||
done
|
||||
20
vendor/github.com/docker/docker-credential-helpers/LICENSE
generated
vendored
Normal file
20
vendor/github.com/docker/docker-credential-helpers/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
Copyright (c) 2016 David Calavera
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
121
vendor/github.com/docker/docker-credential-helpers/client/client.go
generated
vendored
Normal file
121
vendor/github.com/docker/docker-credential-helpers/client/client.go
generated
vendored
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker-credential-helpers/credentials"
|
||||
)
|
||||
|
||||
// isValidCredsMessage checks if 'msg' contains invalid credentials error message.
|
||||
// It returns whether the logs are free of invalid credentials errors and the error if it isn't.
|
||||
// error values can be errCredentialsMissingServerURL or errCredentialsMissingUsername.
|
||||
func isValidCredsMessage(msg string) error {
|
||||
if credentials.IsCredentialsMissingServerURLMessage(msg) {
|
||||
return credentials.NewErrCredentialsMissingServerURL()
|
||||
}
|
||||
|
||||
if credentials.IsCredentialsMissingUsernameMessage(msg) {
|
||||
return credentials.NewErrCredentialsMissingUsername()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Store uses an external program to save credentials.
|
||||
func Store(program ProgramFunc, creds *credentials.Credentials) error {
|
||||
cmd := program("store")
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
if err := json.NewEncoder(buffer).Encode(creds); err != nil {
|
||||
return err
|
||||
}
|
||||
cmd.Input(buffer)
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
t := strings.TrimSpace(string(out))
|
||||
|
||||
if isValidErr := isValidCredsMessage(t); isValidErr != nil {
|
||||
err = isValidErr
|
||||
}
|
||||
|
||||
return fmt.Errorf("error storing credentials - err: %v, out: `%s`", err, t)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get executes an external program to get the credentials from a native store.
|
||||
func Get(program ProgramFunc, serverURL string) (*credentials.Credentials, error) {
|
||||
cmd := program("get")
|
||||
cmd.Input(strings.NewReader(serverURL))
|
||||
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
t := strings.TrimSpace(string(out))
|
||||
|
||||
if credentials.IsErrCredentialsNotFoundMessage(t) {
|
||||
return nil, credentials.NewErrCredentialsNotFound()
|
||||
}
|
||||
|
||||
if isValidErr := isValidCredsMessage(t); isValidErr != nil {
|
||||
err = isValidErr
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("error getting credentials - err: %v, out: `%s`", err, t)
|
||||
}
|
||||
|
||||
resp := &credentials.Credentials{
|
||||
ServerURL: serverURL,
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(bytes.NewReader(out)).Decode(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Erase executes a program to remove the server credentials from the native store.
|
||||
func Erase(program ProgramFunc, serverURL string) error {
|
||||
cmd := program("erase")
|
||||
cmd.Input(strings.NewReader(serverURL))
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
t := strings.TrimSpace(string(out))
|
||||
|
||||
if isValidErr := isValidCredsMessage(t); isValidErr != nil {
|
||||
err = isValidErr
|
||||
}
|
||||
|
||||
return fmt.Errorf("error erasing credentials - err: %v, out: `%s`", err, t)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// List executes a program to list server credentials in the native store.
|
||||
func List(program ProgramFunc) (map[string]string, error) {
|
||||
cmd := program("list")
|
||||
cmd.Input(strings.NewReader("unused"))
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
t := strings.TrimSpace(string(out))
|
||||
|
||||
if isValidErr := isValidCredsMessage(t); isValidErr != nil {
|
||||
err = isValidErr
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("error listing credentials - err: %v, out: `%s`", err, t)
|
||||
}
|
||||
|
||||
var resp map[string]string
|
||||
if err = json.NewDecoder(bytes.NewReader(out)).Decode(&resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
56
vendor/github.com/docker/docker-credential-helpers/client/command.go
generated
vendored
Normal file
56
vendor/github.com/docker/docker-credential-helpers/client/command.go
generated
vendored
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// Program is an interface to execute external programs.
|
||||
type Program interface {
|
||||
Output() ([]byte, error)
|
||||
Input(in io.Reader)
|
||||
}
|
||||
|
||||
// ProgramFunc is a type of function that initializes programs based on arguments.
|
||||
type ProgramFunc func(args ...string) Program
|
||||
|
||||
// NewShellProgramFunc creates programs that are executed in a Shell.
|
||||
func NewShellProgramFunc(name string) ProgramFunc {
|
||||
return NewShellProgramFuncWithEnv(name, nil)
|
||||
}
|
||||
|
||||
// NewShellProgramFuncWithEnv creates programs that are executed in a Shell with environment variables
|
||||
func NewShellProgramFuncWithEnv(name string, env *map[string]string) ProgramFunc {
|
||||
return func(args ...string) Program {
|
||||
return &Shell{cmd: createProgramCmdRedirectErr(name, args, env)}
|
||||
}
|
||||
}
|
||||
|
||||
func createProgramCmdRedirectErr(commandName string, args []string, env *map[string]string) *exec.Cmd {
|
||||
programCmd := exec.Command(commandName, args...)
|
||||
programCmd.Env = os.Environ()
|
||||
if env != nil {
|
||||
for k, v := range *env {
|
||||
programCmd.Env = append(programCmd.Env, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
}
|
||||
programCmd.Stderr = os.Stderr
|
||||
return programCmd
|
||||
}
|
||||
|
||||
// Shell invokes shell commands to talk with a remote credentials helper.
|
||||
type Shell struct {
|
||||
cmd *exec.Cmd
|
||||
}
|
||||
|
||||
// Output returns responses from the remote credentials helper.
|
||||
func (s *Shell) Output() ([]byte, error) {
|
||||
return s.cmd.Output()
|
||||
}
|
||||
|
||||
// Input sets the input to send to a remote credentials helper.
|
||||
func (s *Shell) Input(in io.Reader) {
|
||||
s.cmd.Stdin = in
|
||||
}
|
||||
186
vendor/github.com/docker/docker-credential-helpers/credentials/credentials.go
generated
vendored
Normal file
186
vendor/github.com/docker/docker-credential-helpers/credentials/credentials.go
generated
vendored
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
package credentials
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Credentials holds the information shared between docker and the credentials store.
|
||||
type Credentials struct {
|
||||
ServerURL string
|
||||
Username string
|
||||
Secret string
|
||||
}
|
||||
|
||||
// isValid checks the integrity of Credentials object such that no credentials lack
|
||||
// a server URL or a username.
|
||||
// It returns whether the credentials are valid and the error if it isn't.
|
||||
// error values can be errCredentialsMissingServerURL or errCredentialsMissingUsername
|
||||
func (c *Credentials) isValid() (bool, error) {
|
||||
if len(c.ServerURL) == 0 {
|
||||
return false, NewErrCredentialsMissingServerURL()
|
||||
}
|
||||
|
||||
if len(c.Username) == 0 {
|
||||
return false, NewErrCredentialsMissingUsername()
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// CredsLabel holds the way Docker credentials should be labeled as such in credentials stores that allow labelling.
|
||||
// That label allows to filter out non-Docker credentials too at lookup/search in macOS keychain,
|
||||
// Windows credentials manager and Linux libsecret. Default value is "Docker Credentials"
|
||||
var CredsLabel = "Docker Credentials"
|
||||
|
||||
// SetCredsLabel is a simple setter for CredsLabel
|
||||
func SetCredsLabel(label string) {
|
||||
CredsLabel = label
|
||||
}
|
||||
|
||||
// Serve initializes the credentials helper and parses the action argument.
|
||||
// This function is designed to be called from a command line interface.
|
||||
// It uses os.Args[1] as the key for the action.
|
||||
// It uses os.Stdin as input and os.Stdout as output.
|
||||
// This function terminates the program with os.Exit(1) if there is an error.
|
||||
func Serve(helper Helper) {
|
||||
var err error
|
||||
if len(os.Args) != 2 {
|
||||
err = fmt.Errorf("Usage: %s <store|get|erase|list|version>", os.Args[0])
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
err = HandleCommand(helper, os.Args[1], os.Stdin, os.Stdout)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stdout, "%v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleCommand uses a helper and a key to run a credential action.
|
||||
func HandleCommand(helper Helper, key string, in io.Reader, out io.Writer) error {
|
||||
switch key {
|
||||
case "store":
|
||||
return Store(helper, in)
|
||||
case "get":
|
||||
return Get(helper, in, out)
|
||||
case "erase":
|
||||
return Erase(helper, in)
|
||||
case "list":
|
||||
return List(helper, out)
|
||||
case "version":
|
||||
return PrintVersion(out)
|
||||
}
|
||||
return fmt.Errorf("Unknown credential action `%s`", key)
|
||||
}
|
||||
|
||||
// Store uses a helper and an input reader to save credentials.
|
||||
// The reader must contain the JSON serialization of a Credentials struct.
|
||||
func Store(helper Helper, reader io.Reader) error {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
for scanner.Scan() {
|
||||
buffer.Write(scanner.Bytes())
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
|
||||
var creds Credentials
|
||||
if err := json.NewDecoder(buffer).Decode(&creds); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if ok, err := creds.isValid(); !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
return helper.Add(&creds)
|
||||
}
|
||||
|
||||
// Get retrieves the credentials for a given server url.
|
||||
// The reader must contain the server URL to search.
|
||||
// The writer is used to write the JSON serialization of the credentials.
|
||||
func Get(helper Helper, reader io.Reader, writer io.Writer) error {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
for scanner.Scan() {
|
||||
buffer.Write(scanner.Bytes())
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
|
||||
serverURL := strings.TrimSpace(buffer.String())
|
||||
if len(serverURL) == 0 {
|
||||
return NewErrCredentialsMissingServerURL()
|
||||
}
|
||||
|
||||
username, secret, err := helper.Get(serverURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resp := Credentials{
|
||||
ServerURL: serverURL,
|
||||
Username: username,
|
||||
Secret: secret,
|
||||
}
|
||||
|
||||
buffer.Reset()
|
||||
if err := json.NewEncoder(buffer).Encode(resp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprint(writer, buffer.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Erase removes credentials from the store.
|
||||
// The reader must contain the server URL to remove.
|
||||
func Erase(helper Helper, reader io.Reader) error {
|
||||
scanner := bufio.NewScanner(reader)
|
||||
|
||||
buffer := new(bytes.Buffer)
|
||||
for scanner.Scan() {
|
||||
buffer.Write(scanner.Bytes())
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil && err != io.EOF {
|
||||
return err
|
||||
}
|
||||
|
||||
serverURL := strings.TrimSpace(buffer.String())
|
||||
if len(serverURL) == 0 {
|
||||
return NewErrCredentialsMissingServerURL()
|
||||
}
|
||||
|
||||
return helper.Delete(serverURL)
|
||||
}
|
||||
|
||||
//List returns all the serverURLs of keys in
|
||||
//the OS store as a list of strings
|
||||
func List(helper Helper, writer io.Writer) error {
|
||||
accts, err := helper.List()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return json.NewEncoder(writer).Encode(accts)
|
||||
}
|
||||
|
||||
//PrintVersion outputs the current version.
|
||||
func PrintVersion(writer io.Writer) error {
|
||||
fmt.Fprintln(writer, Version)
|
||||
return nil
|
||||
}
|
||||
102
vendor/github.com/docker/docker-credential-helpers/credentials/error.go
generated
vendored
Normal file
102
vendor/github.com/docker/docker-credential-helpers/credentials/error.go
generated
vendored
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
package credentials
|
||||
|
||||
const (
|
||||
// ErrCredentialsNotFound standardizes the not found error, so every helper returns
|
||||
// the same message and docker can handle it properly.
|
||||
errCredentialsNotFoundMessage = "credentials not found in native keychain"
|
||||
|
||||
// ErrCredentialsMissingServerURL and ErrCredentialsMissingUsername standardize
|
||||
// invalid credentials or credentials management operations
|
||||
errCredentialsMissingServerURLMessage = "no credentials server URL"
|
||||
errCredentialsMissingUsernameMessage = "no credentials username"
|
||||
)
|
||||
|
||||
// errCredentialsNotFound represents an error
|
||||
// raised when credentials are not in the store.
|
||||
type errCredentialsNotFound struct{}
|
||||
|
||||
// Error returns the standard error message
|
||||
// for when the credentials are not in the store.
|
||||
func (errCredentialsNotFound) Error() string {
|
||||
return errCredentialsNotFoundMessage
|
||||
}
|
||||
|
||||
// NewErrCredentialsNotFound creates a new error
|
||||
// for when the credentials are not in the store.
|
||||
func NewErrCredentialsNotFound() error {
|
||||
return errCredentialsNotFound{}
|
||||
}
|
||||
|
||||
// IsErrCredentialsNotFound returns true if the error
|
||||
// was caused by not having a set of credentials in a store.
|
||||
func IsErrCredentialsNotFound(err error) bool {
|
||||
_, ok := err.(errCredentialsNotFound)
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsErrCredentialsNotFoundMessage returns true if the error
|
||||
// was caused by not having a set of credentials in a store.
|
||||
//
|
||||
// This function helps to check messages returned by an
|
||||
// external program via its standard output.
|
||||
func IsErrCredentialsNotFoundMessage(err string) bool {
|
||||
return err == errCredentialsNotFoundMessage
|
||||
}
|
||||
|
||||
// errCredentialsMissingServerURL represents an error raised
|
||||
// when the credentials object has no server URL or when no
|
||||
// server URL is provided to a credentials operation requiring
|
||||
// one.
|
||||
type errCredentialsMissingServerURL struct{}
|
||||
|
||||
func (errCredentialsMissingServerURL) Error() string {
|
||||
return errCredentialsMissingServerURLMessage
|
||||
}
|
||||
|
||||
// errCredentialsMissingUsername represents an error raised
|
||||
// when the credentials object has no username or when no
|
||||
// username is provided to a credentials operation requiring
|
||||
// one.
|
||||
type errCredentialsMissingUsername struct{}
|
||||
|
||||
func (errCredentialsMissingUsername) Error() string {
|
||||
return errCredentialsMissingUsernameMessage
|
||||
}
|
||||
|
||||
// NewErrCredentialsMissingServerURL creates a new error for
|
||||
// errCredentialsMissingServerURL.
|
||||
func NewErrCredentialsMissingServerURL() error {
|
||||
return errCredentialsMissingServerURL{}
|
||||
}
|
||||
|
||||
// NewErrCredentialsMissingUsername creates a new error for
|
||||
// errCredentialsMissingUsername.
|
||||
func NewErrCredentialsMissingUsername() error {
|
||||
return errCredentialsMissingUsername{}
|
||||
}
|
||||
|
||||
// IsCredentialsMissingServerURL returns true if the error
|
||||
// was an errCredentialsMissingServerURL.
|
||||
func IsCredentialsMissingServerURL(err error) bool {
|
||||
_, ok := err.(errCredentialsMissingServerURL)
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsCredentialsMissingServerURLMessage checks for an
|
||||
// errCredentialsMissingServerURL in the error message.
|
||||
func IsCredentialsMissingServerURLMessage(err string) bool {
|
||||
return err == errCredentialsMissingServerURLMessage
|
||||
}
|
||||
|
||||
// IsCredentialsMissingUsername returns true if the error
|
||||
// was an errCredentialsMissingUsername.
|
||||
func IsCredentialsMissingUsername(err error) bool {
|
||||
_, ok := err.(errCredentialsMissingUsername)
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsCredentialsMissingUsernameMessage checks for an
|
||||
// errCredentialsMissingUsername in the error message.
|
||||
func IsCredentialsMissingUsernameMessage(err string) bool {
|
||||
return err == errCredentialsMissingUsernameMessage
|
||||
}
|
||||
14
vendor/github.com/docker/docker-credential-helpers/credentials/helper.go
generated
vendored
Normal file
14
vendor/github.com/docker/docker-credential-helpers/credentials/helper.go
generated
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package credentials
|
||||
|
||||
// Helper is the interface a credentials store helper must implement.
|
||||
type Helper interface {
|
||||
// Add appends credentials to the store.
|
||||
Add(*Credentials) error
|
||||
// Delete removes credentials from the store.
|
||||
Delete(serverURL string) error
|
||||
// Get retrieves credentials from the store.
|
||||
// It returns username and secret as strings.
|
||||
Get(serverURL string) (string, string, error)
|
||||
// List returns the stored serverURLs and their associated usernames.
|
||||
List() (map[string]string, error)
|
||||
}
|
||||
4
vendor/github.com/docker/docker-credential-helpers/credentials/version.go
generated
vendored
Normal file
4
vendor/github.com/docker/docker-credential-helpers/credentials/version.go
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
package credentials
|
||||
|
||||
// Version holds a string describing the current version
|
||||
const Version = "0.6.2"
|
||||
3
vendor/modules.txt
vendored
3
vendor/modules.txt
vendored
|
|
@ -69,6 +69,9 @@ github.com/docker/docker/api
|
|||
github.com/docker/docker/api/types/events
|
||||
github.com/docker/docker/api/types/image
|
||||
github.com/docker/docker/api/types/time
|
||||
# github.com/docker/docker-credential-helpers v0.6.2
|
||||
github.com/docker/docker-credential-helpers/client
|
||||
github.com/docker/docker-credential-helpers/credentials
|
||||
# github.com/docker/go-connections v0.4.0
|
||||
github.com/docker/go-connections/nat
|
||||
github.com/docker/go-connections/sockets
|
||||
|
|
|
|||
Loading…
Reference in a new issue