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/importer.go b/cluster/tools/kartongips/utils/importer.go
new file mode 100644
index 0000000..b456f0d
--- /dev/null
+++ b/cluster/tools/kartongips/utils/importer.go
@@ -0,0 +1,170 @@
+package utils
+
+import (
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "net"
+ "net/http"
+ "net/url"
+ "os"
+ "regexp"
+ "strings"
+ "time"
+
+ assetfs "github.com/elazarl/go-bindata-assetfs"
+ jsonnet "github.com/google/go-jsonnet"
+ log "github.com/sirupsen/logrus"
+)
+
+var errNotFound = errors.New("Not found")
+
+var extVarKindRE = regexp.MustCompile("^<(?:extvar|top-level-arg):.+>$")
+
+//go:generate go-bindata -nometadata -ignore .*_test\.|~$DOLLAR -pkg $GOPACKAGE -o bindata.go -prefix ../ ../lib/...
+func newInternalFS(prefix string) http.FileSystem {
+ // Asset/AssetDir returns `fmt.Errorf("Asset %s not found")`,
+ // which does _not_ get mapped to 404 by `http.FileSystem`.
+ // Need to convert to `os.ErrNotExist` explicitly ourselves.
+ mapNotFound := func(err error) error {
+ if err != nil && strings.Contains(err.Error(), "not found") {
+ err = os.ErrNotExist
+ }
+ return err
+ }
+ return &assetfs.AssetFS{
+ Asset: func(path string) ([]byte, error) {
+ ret, err := Asset(path)
+ return ret, mapNotFound(err)
+ },
+ AssetDir: func(path string) ([]string, error) {
+ ret, err := AssetDir(path)
+ return ret, mapNotFound(err)
+ },
+ Prefix: prefix,
+ }
+}
+
+/*
+MakeUniversalImporter creates an importer that handles resolving imports from the filesystem and HTTP/S.
+
+In addition to the standard importer, supports:
+ - URLs in import statements
+ - URLs in library search paths
+
+A real-world example:
+ - You have https://raw.githubusercontent.com/ksonnet/ksonnet-lib/master in your search URLs.
+ - You evaluate a local file which calls `import "ksonnet.beta.2/k.libsonnet"`.
+ - If the `ksonnet.beta.2/k.libsonnet`` is not located in the current working directory, an attempt
+ will be made to follow the search path, i.e. to download
+ https://raw.githubusercontent.com/ksonnet/ksonnet-lib/master/ksonnet.beta.2/k.libsonnet.
+ - Since the downloaded `k.libsonnet`` file turn in contains `import "k8s.libsonnet"`, the import
+ will be resolved as https://raw.githubusercontent.com/ksonnet/ksonnet-lib/master/ksonnet.beta.2/k8s.libsonnet
+ and downloaded from that location.
+*/
+func MakeUniversalImporter(searchURLs []*url.URL) jsonnet.Importer {
+ // Reconstructed copy of http.DefaultTransport (to avoid
+ // modifying the default)
+ t := &http.Transport{
+ Proxy: http.ProxyFromEnvironment,
+ DialContext: (&net.Dialer{
+ Timeout: 30 * time.Second,
+ KeepAlive: 30 * time.Second,
+ DualStack: true,
+ }).DialContext,
+ MaxIdleConns: 100,
+ IdleConnTimeout: 90 * time.Second,
+ TLSHandshakeTimeout: 10 * time.Second,
+ ExpectContinueTimeout: 1 * time.Second,
+ }
+
+ t.RegisterProtocol("file", http.NewFileTransport(http.Dir("/")))
+ t.RegisterProtocol("internal", http.NewFileTransport(newInternalFS("lib")))
+
+ return &universalImporter{
+ BaseSearchURLs: searchURLs,
+ HTTPClient: &http.Client{Transport: t},
+ cache: map[string]jsonnet.Contents{},
+ }
+}
+
+type universalImporter struct {
+ BaseSearchURLs []*url.URL
+ HTTPClient *http.Client
+ cache map[string]jsonnet.Contents
+}
+
+func (importer *universalImporter) Import(importedFrom, importedPath string) (jsonnet.Contents, string, error) {
+ log.Debugf("Importing %q from %q", importedPath, importedFrom)
+
+ candidateURLs, err := importer.expandImportToCandidateURLs(importedFrom, importedPath)
+ if err != nil {
+ return jsonnet.Contents{}, "", fmt.Errorf("Could not get candidate URLs for when importing %s (imported from %s): %v", importedPath, importedFrom, err)
+ }
+
+ var tried []string
+ for _, u := range candidateURLs {
+ foundAt := u.String()
+ if c, ok := importer.cache[foundAt]; ok {
+ return c, foundAt, nil
+ }
+
+ tried = append(tried, foundAt)
+ importedData, err := importer.tryImport(foundAt)
+ if err == nil {
+ importer.cache[foundAt] = importedData
+ return importedData, foundAt, nil
+ } else if err != errNotFound {
+ return jsonnet.Contents{}, "", err
+ }
+ }
+
+ return jsonnet.Contents{}, "", fmt.Errorf("Couldn't open import %q, no match locally or in library search paths. Tried: %s",
+ importedPath,
+ strings.Join(tried, ";"),
+ )
+}
+
+func (importer *universalImporter) tryImport(url string) (jsonnet.Contents, error) {
+ res, err := importer.HTTPClient.Get(url)
+ if err != nil {
+ return jsonnet.Contents{}, err
+ }
+ defer res.Body.Close()
+ log.Debugf("GET %q -> %s", url, res.Status)
+ if res.StatusCode == http.StatusNotFound {
+ return jsonnet.Contents{}, errNotFound
+ } else if res.StatusCode != http.StatusOK {
+ return jsonnet.Contents{}, fmt.Errorf("error reading content: %s", res.Status)
+ }
+
+ bodyBytes, err := ioutil.ReadAll(res.Body)
+ if err != nil {
+ return jsonnet.Contents{}, err
+ }
+ return jsonnet.MakeContents(string(bodyBytes)), nil
+}
+
+func (importer *universalImporter) expandImportToCandidateURLs(importedFrom, importedPath string) ([]*url.URL, error) {
+ importedPathURL, err := url.Parse(importedPath)
+ if err != nil {
+ return nil, fmt.Errorf("Import path %q is not valid", importedPath)
+ }
+ if importedPathURL.IsAbs() {
+ return []*url.URL{importedPathURL}, nil
+ }
+
+ importDirURL, err := url.Parse(importedFrom)
+ if err != nil {
+ return nil, fmt.Errorf("Invalid import dir %q: %v", importedFrom, err)
+ }
+
+ candidateURLs := make([]*url.URL, 1, len(importer.BaseSearchURLs)+1)
+ candidateURLs[0] = importDirURL.ResolveReference(importedPathURL)
+
+ for _, u := range importer.BaseSearchURLs {
+ candidateURLs = append(candidateURLs, u.ResolveReference(importedPathURL))
+ }
+
+ return candidateURLs, nil
+}