cluster/tools/kartongips: init

This forks bitnami/kubecfg into kartongips. The rationale is that we
want to implement hscloud-specific functionality that wouldn't really be
upstreamable into kubecfg (like secret support, mulit-cluster support).

We forked off from github.com/q3k/kubecfg at commit b6817a94492c561ed61a44eeea2d92dcf2e6b8c0.

Change-Id: If5ba513905e0a86f971576fe7061a471c1d8b398
diff --git a/cluster/tools/kartongips/utils/resolver.go b/cluster/tools/kartongips/utils/resolver.go
new file mode 100644
index 0000000..9a9cc84
--- /dev/null
+++ b/cluster/tools/kartongips/utils/resolver.go
@@ -0,0 +1,165 @@
+// Copyright 2017 The kubecfg authors
+//
+//
+//    Licensed under the Apache License, Version 2.0 (the "License");
+//    you may not use this file except in compliance with the License.
+//    You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+//    Unless required by applicable law or agreed to in writing, software
+//    distributed under the License is distributed on an "AS IS" BASIS,
+//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//    See the License for the specific language governing permissions and
+//    limitations under the License.
+
+package utils
+
+import (
+	"bytes"
+	"context"
+	"fmt"
+
+	"github.com/genuinetools/reg/registry"
+	"github.com/genuinetools/reg/repoutils"
+)
+
+const defaultRegistry = "registry-1.docker.io"	
+
+// ImageName represents the parts of a docker image name
+type ImageName struct {
+	// eg: "myregistryhost:5000/fedora/httpd:version1.0"
+	Registry   string // "myregistryhost:5000"
+	Repository string // "fedora"
+	Name       string // "httpd"
+	Tag        string // "version1.0"
+	Digest     string
+}
+
+// String implements the Stringer interface
+func (n ImageName) String() string {
+	buf := bytes.Buffer{}
+	if n.Registry != "" {
+		buf.WriteString(n.Registry)
+		buf.WriteString("/")
+	}
+	if n.Repository != "" {
+		buf.WriteString(n.Repository)
+		buf.WriteString("/")
+	}
+	buf.WriteString(n.Name)
+	if n.Digest != "" {
+		buf.WriteString("@")
+		buf.WriteString(n.Digest)
+	} else {
+		buf.WriteString(":")
+		buf.WriteString(n.Tag)
+	}
+	return buf.String()
+}
+
+// RegistryRepoName returns the "repository" as used in the registry URL
+func (n ImageName) RegistryRepoName() string {
+	repo := n.Repository
+	if repo == "" {
+		repo = "library"
+	}
+	return fmt.Sprintf("%s/%s", repo, n.Name)
+}
+
+// RegistryURL returns the deduced base URL of the registry for this image
+func (n ImageName) RegistryURL() string {
+	reg := n.Registry
+	if reg == "" {
+		reg = defaultRegistry
+	}
+	return fmt.Sprintf("https://%s", reg)
+}
+
+// ParseImageName parses a docker image into an ImageName struct.
+func ParseImageName(image string) (ImageName, error) {
+	ret := ImageName{}
+
+	img, err := registry.ParseImage(image)
+	if err != nil {
+		return ret, err
+	}
+
+	ret.Registry = img.Domain
+	ret.Name = img.Path
+	ret.Digest = img.Digest.String()
+	ret.Tag = img.Tag
+
+	return ret, nil
+}
+
+// Resolver is able to resolve docker image names into more specific forms
+type Resolver interface {
+	Resolve(image *ImageName) error
+}
+
+// NewIdentityResolver returns a resolver that does only trivial
+// :latest canonicalisation
+func NewIdentityResolver() Resolver {
+	return identityResolver{}
+}
+
+type identityResolver struct{}
+
+func (r identityResolver) Resolve(image *ImageName) error {
+	return nil
+}
+
+// NewRegistryResolver returns a resolver that looks up a docker
+// registry to resolve digests
+func NewRegistryResolver(opt registry.Opt) Resolver {
+	return &registryResolver{
+		opt:   opt,
+		cache: make(map[string]string),
+	}
+}
+
+type registryResolver struct {
+	opt   registry.Opt
+	cache map[string]string
+}
+
+func (r *registryResolver) Resolve(n *ImageName) error {
+	// TODO: get context from caller.
+	ctx := context.Background()
+
+	if n.Digest != "" {
+		// Already has explicit digest
+		return nil
+	}
+
+	if digest, ok := r.cache[n.String()]; ok {
+		n.Digest = digest
+		return nil
+	}
+
+	img, err := registry.ParseImage(n.String())
+	if err != nil {
+		return fmt.Errorf("unable to parse image name: %v", err)
+	}
+
+	auth, err := repoutils.GetAuthConfig("", "", img.Domain)
+	if err != nil {
+		return fmt.Errorf("unable to get auth config for registry: %v", err)
+	}
+
+	c, err := registry.New(ctx, auth, r.opt)
+	if err != nil {
+		return fmt.Errorf("unable to create registry client: %v", err)
+	}
+
+	digest, err := c.Digest(ctx, img)
+	if err != nil {
+		return fmt.Errorf("unable to get digest from the registry: %v", err)
+	}
+
+	n.Digest = digest.String()
+	r.cache[n.String()] = n.Digest
+
+	return nil
+}