mirror of https://github.com/helm/helm.git
Construct http.Client for repositories from config, add TLS support
This commit is contained in:
parent
b928088a8a
commit
b0e7a43b5b
|
@ -103,7 +103,7 @@ func TestDependencyBuildCmd(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
i, err := repo.LoadIndexFile(dbc.helmhome.CacheIndex("test"))
|
||||
i, err := repo.NewChartRepositoryIndexFromFile(dbc.helmhome.CacheIndex("test"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -88,7 +88,7 @@ func TestDependencyUpdateCmd(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
i, err := repo.LoadIndexFile(duc.helmhome.CacheIndex("test"))
|
||||
i, err := repo.NewChartRepositoryIndexFromFile(duc.helmhome.CacheIndex("test"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -166,7 +166,7 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, er
|
|||
}
|
||||
|
||||
// Next, we need to load the index, and actually look up the chart.
|
||||
i, err := repo.LoadIndexFile(c.HelmHome.CacheIndex(repoName))
|
||||
i, err := repo.NewChartRepositoryIndexFromFile(c.HelmHome.CacheIndex(repoName))
|
||||
if err != nil {
|
||||
return u, fmt.Errorf("no cached repo found. (try 'helm repo update'). %s", err)
|
||||
}
|
||||
|
@ -182,7 +182,7 @@ func (c *ChartDownloader) ResolveChartVersion(ref, version string) (*url.URL, er
|
|||
return url.Parse(cv.URLs[0])
|
||||
}
|
||||
|
||||
func findRepoEntry(name string, repos []*repo.Entry) (*repo.Entry, error) {
|
||||
func findRepoEntry(name string, repos []*repo.ChartRepositoryConfig) (*repo.ChartRepositoryConfig, error) {
|
||||
for _, re := range repos {
|
||||
if re.Name == name {
|
||||
return re, nil
|
||||
|
|
|
@ -35,6 +35,7 @@ import (
|
|||
"k8s.io/helm/pkg/chartutil"
|
||||
"k8s.io/helm/pkg/proto/hapi/chart"
|
||||
"k8s.io/helm/pkg/repo"
|
||||
"k8s.io/helm/pkg/urlutil"
|
||||
)
|
||||
|
||||
// Manager handles the lifecycle of fetching, resolving, and storing dependencies.
|
||||
|
@ -226,7 +227,7 @@ func (m *Manager) hasAllRepos(deps []*chartutil.Dependency) error {
|
|||
found = true
|
||||
} else {
|
||||
for _, repo := range repos {
|
||||
if urlsAreEqual(repo.URL, strings.TrimSuffix(dd.Repository, "/")) {
|
||||
if urlutil.URLAreEqual(repo.URL, strings.TrimSuffix(dd.Repository, "/")) {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
|
@ -258,7 +259,7 @@ func (m *Manager) getRepoNames(deps []*chartutil.Dependency) (map[string]string,
|
|||
found := false
|
||||
|
||||
for _, repo := range repos {
|
||||
if urlsAreEqual(repo.URL, dd.Repository) {
|
||||
if urlutil.URLAreEqual(repo.URL, dd.Repository) {
|
||||
found = true
|
||||
reposMap[dd.Name] = repo.Name
|
||||
break
|
||||
|
@ -283,53 +284,35 @@ func (m *Manager) UpdateRepositories() error {
|
|||
repos := rf.Repositories
|
||||
if len(repos) > 0 {
|
||||
// This prints warnings straight to out.
|
||||
m.parallelRepoUpdate(repos)
|
||||
if err := m.parallelRepoUpdate(repos); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Manager) parallelRepoUpdate(repos []*repo.Entry) {
|
||||
func (m *Manager) parallelRepoUpdate(repos []*repo.ChartRepositoryConfig) error {
|
||||
out := m.Out
|
||||
fmt.Fprintln(out, "Hang tight while we grab the latest from your chart repositories...")
|
||||
var wg sync.WaitGroup
|
||||
for _, re := range repos {
|
||||
for _, c := range repos {
|
||||
r, err := repo.NewChartRepository(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wg.Add(1)
|
||||
go func(n, u string) {
|
||||
if err := repo.DownloadIndexFile(n, u, m.HelmHome.CacheIndex(n)); err != nil {
|
||||
fmt.Fprintf(out, "...Unable to get an update from the %q chart repository (%s):\n\t%s\n", n, u, err)
|
||||
go func(r *repo.ChartRepository) {
|
||||
if err := r.DownloadIndexFile(); err != nil {
|
||||
fmt.Fprintf(out, "...Unable to get an update from the %q chart repository (%s):\n\t%s\n", r.Config.Name, r.Config.URL, err)
|
||||
} else {
|
||||
fmt.Fprintf(out, "...Successfully got an update from the %q chart repository\n", n)
|
||||
fmt.Fprintf(out, "...Successfully got an update from the %q chart repository\n", r.Config.Name)
|
||||
}
|
||||
wg.Done()
|
||||
}(re.Name, re.URL)
|
||||
}(r)
|
||||
}
|
||||
wg.Wait()
|
||||
fmt.Fprintln(out, "Update Complete. ⎈Happy Helming!⎈")
|
||||
}
|
||||
|
||||
// urlsAreEqual normalizes two URLs and then compares for equality.
|
||||
//
|
||||
// TODO: This and the urlJoin functions should really be moved to a 'urlutil' package.
|
||||
func urlsAreEqual(a, b string) bool {
|
||||
au, err := url.Parse(a)
|
||||
if err != nil {
|
||||
a = filepath.Clean(a)
|
||||
b = filepath.Clean(b)
|
||||
// If urls are paths, return true only if they are an exact match
|
||||
return a == b
|
||||
}
|
||||
bu, err := url.Parse(b)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, u := range []*url.URL{au, bu} {
|
||||
if u.Path == "" {
|
||||
u.Path = "/"
|
||||
}
|
||||
u.Path = filepath.Clean(u.Path)
|
||||
}
|
||||
return au.String() == bu.String()
|
||||
return nil
|
||||
}
|
||||
|
||||
// findChartURL searches the cache of repo data for a chart that has the name and the repoURL specified.
|
||||
|
@ -342,7 +325,7 @@ func urlsAreEqual(a, b string) bool {
|
|||
// If it finds a URL that is "relative", it will prepend the repoURL.
|
||||
func findChartURL(name, version, repoURL string, repos map[string]*repo.ChartRepository) (string, error) {
|
||||
for _, cr := range repos {
|
||||
if urlsAreEqual(repoURL, cr.URL) {
|
||||
if urlutil.URLAreEqual(repoURL, cr.Config.URL) {
|
||||
entry, err := findEntryByName(name, cr)
|
||||
if err != nil {
|
||||
return "", err
|
||||
|
@ -434,13 +417,14 @@ func (m *Manager) loadChartRepositories() (map[string]*repo.ChartRepository, err
|
|||
for _, re := range rf.Repositories {
|
||||
lname := re.Name
|
||||
cacheindex := m.HelmHome.CacheIndex(lname)
|
||||
index, err := repo.LoadIndexFile(cacheindex)
|
||||
index, err := repo.NewChartRepositoryIndexFromFile(cacheindex)
|
||||
if err != nil {
|
||||
return indices, err
|
||||
}
|
||||
|
||||
// TODO: use constructor
|
||||
cr := &repo.ChartRepository{
|
||||
URL: re.URL,
|
||||
Config: re,
|
||||
IndexFile: index,
|
||||
}
|
||||
indices[lname] = cr
|
||||
|
|
|
@ -135,27 +135,3 @@ func TestGetRepoNames(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUrlsAreEqual(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
a, b string
|
||||
match bool
|
||||
}{
|
||||
{"http://example.com", "http://example.com", true},
|
||||
{"http://example.com", "http://another.example.com", false},
|
||||
{"https://example.com", "https://example.com", true},
|
||||
{"http://example.com/", "http://example.com", true},
|
||||
{"https://example.com", "http://example.com", false},
|
||||
{"http://example.com/foo", "http://example.com/foo/", true},
|
||||
{"http://example.com/foo//", "http://example.com/foo/", true},
|
||||
{"http://example.com/./foo/", "http://example.com/foo/", true},
|
||||
{"http://example.com/bar/../foo/", "http://example.com/foo/", true},
|
||||
{"/foo", "/foo", true},
|
||||
{"/foo", "/foo/", true},
|
||||
{"/foo/.", "/foo/", true},
|
||||
} {
|
||||
if tt.match != urlsAreEqual(tt.a, tt.b) {
|
||||
t.Errorf("Expected %q==%q to be %t", tt.a, tt.b, tt.match)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -92,7 +92,6 @@ func newFetchCmd(out io.Writer) *cobra.Command {
|
|||
}
|
||||
|
||||
func (f *fetchCmd) run() error {
|
||||
pname := f.chartRef
|
||||
c := downloader.ChartDownloader{
|
||||
HelmHome: helmpath.Home(homePath()),
|
||||
Out: f.out,
|
||||
|
@ -118,7 +117,7 @@ func (f *fetchCmd) run() error {
|
|||
defer os.RemoveAll(dest)
|
||||
}
|
||||
|
||||
saved, v, err := c.DownloadTo(pname, f.version, dest)
|
||||
saved, v, err := c.DownloadTo(f.chartRef, f.version, dest)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -254,12 +254,12 @@ func ensureTestHome(home helmpath.Home, t *testing.T) error {
|
|||
|
||||
repoFile := home.RepositoryFile()
|
||||
if fi, err := os.Stat(repoFile); err != nil {
|
||||
rf := repo.NewRepoFile()
|
||||
rf.Add(&repo.Entry{
|
||||
rf := repo.NewRepositoryFile()
|
||||
rf.Add(&repo.ChartRepositoryConfig{
|
||||
Name: "charts",
|
||||
URL: "http://example.com/foo",
|
||||
Cache: "charts-index.yaml",
|
||||
}, &repo.Entry{
|
||||
}, &repo.ChartRepositoryConfig{
|
||||
Name: "local",
|
||||
URL: "http://localhost.com:7743/foo",
|
||||
Cache: "local-index.yaml",
|
||||
|
@ -279,7 +279,7 @@ func ensureTestHome(home helmpath.Home, t *testing.T) error {
|
|||
|
||||
localRepoIndexFile := home.LocalRepository(localRepoIndexFilePath)
|
||||
if fi, err := os.Stat(localRepoIndexFile); err != nil {
|
||||
i := repo.NewIndexFile()
|
||||
i := repo.NewChartRepositoryIndex()
|
||||
if err := i.WriteFile(localRepoIndexFile, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
108
cmd/helm/init.go
108
cmd/helm/init.go
|
@ -102,7 +102,6 @@ func newInitCmd(out io.Writer) *cobra.Command {
|
|||
|
||||
// runInit initializes local config and installs tiller to Kubernetes Cluster
|
||||
func (i *initCmd) run() error {
|
||||
|
||||
if flagDebug {
|
||||
m, err := installer.DeploymentManifest(i.namespace, i.image, i.canary)
|
||||
if err != nil {
|
||||
|
@ -110,13 +109,21 @@ func (i *initCmd) run() error {
|
|||
}
|
||||
fmt.Fprintln(i.out, m)
|
||||
}
|
||||
|
||||
if i.dryRun {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := ensureHome(i.home, i.out); err != nil {
|
||||
if err := ensureDirectories(i.home, i.out); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ensureDefaultRepos(i.home, i.out); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ensureRepoFileFormat(i.home.RepositoryFile(), i.out); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(i.out, "$HELM_HOME has been configured at %s.\n", helmHome)
|
||||
|
||||
if !i.clientOnly {
|
||||
if i.kubeClient == nil {
|
||||
|
@ -137,15 +144,23 @@ func (i *initCmd) run() error {
|
|||
} else {
|
||||
fmt.Fprintln(i.out, "Not installing tiller due to 'client-only' flag having been set")
|
||||
}
|
||||
|
||||
fmt.Fprintln(i.out, "Happy Helming!")
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensureHome checks to see if $HELM_HOME exists
|
||||
// ensureDirectories checks to see if $HELM_HOME exists
|
||||
//
|
||||
// If $HELM_HOME does not exist, this function will create it.
|
||||
func ensureHome(home helmpath.Home, out io.Writer) error {
|
||||
configDirectories := []string{home.String(), home.Repository(), home.Cache(), home.LocalRepository(), home.Plugins(), home.Starters()}
|
||||
func ensureDirectories(home helmpath.Home, out io.Writer) error {
|
||||
configDirectories := []string{
|
||||
home.String(),
|
||||
home.Repository(),
|
||||
home.Cache(),
|
||||
home.LocalRepository(),
|
||||
home.Plugins(),
|
||||
home.Starters(),
|
||||
}
|
||||
for _, p := range configDirectories {
|
||||
if fi, err := os.Stat(p); err != nil {
|
||||
fmt.Fprintf(out, "Creating %s \n", p)
|
||||
|
@ -157,50 +172,77 @@ func ensureHome(home helmpath.Home, out io.Writer) error {
|
|||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ensureDefaultRepos(home helmpath.Home, out io.Writer) error {
|
||||
repoFile := home.RepositoryFile()
|
||||
if fi, err := os.Stat(repoFile); err != nil {
|
||||
fmt.Fprintf(out, "Creating %s \n", repoFile)
|
||||
r := repo.NewRepoFile()
|
||||
r.Add(&repo.Entry{
|
||||
Name: stableRepository,
|
||||
URL: stableRepositoryURL,
|
||||
Cache: "stable-index.yaml",
|
||||
}, &repo.Entry{
|
||||
Name: localRepository,
|
||||
URL: localRepositoryURL,
|
||||
Cache: "local-index.yaml",
|
||||
})
|
||||
if err := r.WriteFile(repoFile, 0644); err != nil {
|
||||
f := repo.NewRepositoryFile()
|
||||
sr, err := initStableRepo(home.CacheIndex(stableRepository))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cif := home.CacheIndex(stableRepository)
|
||||
if err := repo.DownloadIndexFile(stableRepository, stableRepositoryURL, cif); err != nil {
|
||||
fmt.Fprintf(out, "WARNING: Failed to download %s: %s (run 'helm repo update')\n", stableRepository, err)
|
||||
lr, err := initLocalRepo(home.LocalRepository(localRepoIndexFilePath), home.CacheIndex("local"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.Add(sr)
|
||||
f.Add(lr)
|
||||
f.WriteFile(repoFile, 0644)
|
||||
} else if fi.IsDir() {
|
||||
return fmt.Errorf("%s must be a file, not a directory", repoFile)
|
||||
}
|
||||
if r, err := repo.LoadRepositoriesFile(repoFile); err == repo.ErrRepoOutOfDate {
|
||||
fmt.Fprintln(out, "Updating repository file format...")
|
||||
if err := r.WriteFile(repoFile, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func initStableRepo(cacheFile string) (*repo.ChartRepositoryConfig, error) {
|
||||
c := repo.ChartRepositoryConfig{
|
||||
Name: stableRepository,
|
||||
URL: stableRepositoryURL,
|
||||
Cache: cacheFile,
|
||||
}
|
||||
r, err := repo.NewChartRepository(&c)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
localRepoIndexFile := home.LocalRepository(localRepoIndexFilePath)
|
||||
if fi, err := os.Stat(localRepoIndexFile); err != nil {
|
||||
fmt.Fprintf(out, "Creating %s \n", localRepoIndexFile)
|
||||
i := repo.NewIndexFile()
|
||||
if err := i.WriteFile(localRepoIndexFile, 0644); err != nil {
|
||||
return err
|
||||
if err := r.DownloadIndexFile(); err != nil {
|
||||
return nil, fmt.Errorf("Looks like %q is not a valid chart repository or cannot be reached: %s", stableRepositoryURL, err.Error())
|
||||
}
|
||||
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
func initLocalRepo(indexFile, cacheFile string) (*repo.ChartRepositoryConfig, error) {
|
||||
if fi, err := os.Stat(indexFile); err != nil {
|
||||
i := repo.NewChartRepositoryIndex()
|
||||
if err := i.WriteFile(indexFile, 0644); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//TODO: take this out and replace with helm update functionality
|
||||
os.Symlink(localRepoIndexFile, home.CacheIndex("local"))
|
||||
os.Symlink(indexFile, cacheFile)
|
||||
} else if fi.IsDir() {
|
||||
return fmt.Errorf("%s must be a file, not a directory", localRepoIndexFile)
|
||||
return nil, fmt.Errorf("%s must be a file, not a directory", indexFile)
|
||||
}
|
||||
|
||||
return &repo.ChartRepositoryConfig{
|
||||
Name: localRepository,
|
||||
URL: localRepositoryURL,
|
||||
Cache: cacheFile,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ensureRepoFileFormat(file string, out io.Writer) error {
|
||||
r, err := repo.LoadRepositoriesFile(file)
|
||||
if err == repo.ErrRepoOutOfDate {
|
||||
fmt.Fprintln(out, "Updating repository file format...")
|
||||
if err := r.WriteFile(file, 0644); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(out, "$HELM_HOME has been configured at %s.\n", helmHome)
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -43,20 +43,22 @@ func TestInitCmd(t *testing.T) {
|
|||
defer os.Remove(home)
|
||||
|
||||
var buf bytes.Buffer
|
||||
fc := fake.NewSimpleClientset()
|
||||
|
||||
fake := testclient.Fake{}
|
||||
cmd := &initCmd{
|
||||
out: &buf,
|
||||
home: helmpath.Home(home),
|
||||
kubeClient: fc.Extensions(),
|
||||
namespace: api.NamespaceDefault,
|
||||
kubeClient: fake.Extensions(),
|
||||
}
|
||||
if err := cmd.run(); err != nil {
|
||||
t.Errorf("expected error: %v", err)
|
||||
}
|
||||
action := fc.Actions()[0]
|
||||
if !action.Matches("create", "deployments") {
|
||||
t.Errorf("unexpected action: %v, expected create deployment", action)
|
||||
|
||||
actions := fake.Actions()
|
||||
if action, ok := actions[0].(testclient.CreateAction); !ok || action.GetResource() != "deployments" {
|
||||
t.Errorf("unexpected action: %v, expected create deployment", actions[0])
|
||||
}
|
||||
|
||||
expected := "Tiller (the helm server side component) has been installed into your Kubernetes Cluster."
|
||||
if !strings.Contains(buf.String(), expected) {
|
||||
t.Errorf("expected %q, got %q", expected, buf.String())
|
||||
|
@ -169,7 +171,13 @@ func TestEnsureHome(t *testing.T) {
|
|||
b := bytes.NewBuffer(nil)
|
||||
hh := helmpath.Home(home)
|
||||
helmHome = home
|
||||
if err := ensureHome(hh, b); err != nil {
|
||||
if err := ensureDirectories(hh, b); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := ensureDefaultRepos(hh, b); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if err := ensureRepoFileFormat(hh.RepositoryFile(), b); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
|
|
|
@ -19,8 +19,6 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
|
@ -32,8 +30,13 @@ type repoAddCmd struct {
|
|||
name string
|
||||
url string
|
||||
home helmpath.Home
|
||||
out io.Writer
|
||||
noupdate bool
|
||||
|
||||
certFile string
|
||||
keyFile string
|
||||
caFile string
|
||||
|
||||
out io.Writer
|
||||
}
|
||||
|
||||
func newRepoAddCmd(out io.Writer) *cobra.Command {
|
||||
|
@ -56,73 +59,54 @@ func newRepoAddCmd(out io.Writer) *cobra.Command {
|
|||
return add.run()
|
||||
},
|
||||
}
|
||||
|
||||
f := cmd.Flags()
|
||||
f.BoolVar(&add.noupdate, "no-update", false, "raise error if repo is already registered")
|
||||
f.StringVar(&add.certFile, "cert-file", "", "identify HTTPS client using this SSL certificate file")
|
||||
f.StringVar(&add.keyFile, "key-file", "", "identify HTTPS client using this SSL key file")
|
||||
f.StringVar(&add.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func (a *repoAddCmd) run() error {
|
||||
var err error
|
||||
if a.noupdate {
|
||||
err = addRepository(a.name, a.url, a.home)
|
||||
} else {
|
||||
err = updateRepository(a.name, a.url, a.home)
|
||||
}
|
||||
if err != nil {
|
||||
if err := addRepository(a.name, a.url, a.home, a.certFile, a.keyFile, a.caFile, a.noupdate); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintf(a.out, "%q has been added to your repositories\n", a.name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func addRepository(name, url string, home helmpath.Home) error {
|
||||
func addRepository(name, url string, home helmpath.Home, certFile, keyFile, caFile string, noUpdate bool) error {
|
||||
f, err := repo.LoadRepositoriesFile(home.RepositoryFile())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if noUpdate && f.Has(name) {
|
||||
return fmt.Errorf("The repository name you provided (%s) already exists. Please specify a different name.", name)
|
||||
}
|
||||
|
||||
cif := home.CacheIndex(name)
|
||||
if err := repo.DownloadIndexFile(name, url, cif); err != nil {
|
||||
c := repo.ChartRepositoryConfig{
|
||||
Name: name,
|
||||
Cache: cif,
|
||||
URL: url,
|
||||
CertFile: certFile,
|
||||
KeyFile: keyFile,
|
||||
CAFile: caFile,
|
||||
}
|
||||
|
||||
r, err := repo.NewChartRepository(&c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.DownloadIndexFile(); err != nil {
|
||||
return fmt.Errorf("Looks like %q is not a valid chart repository or cannot be reached: %s", url, err.Error())
|
||||
}
|
||||
|
||||
return insertRepoLine(name, url, home)
|
||||
}
|
||||
f.Update(&c)
|
||||
|
||||
func insertRepoLine(name, url string, home helmpath.Home) error {
|
||||
cif := home.CacheIndex(name)
|
||||
f, err := repo.LoadRepositoriesFile(home.RepositoryFile())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if f.Has(name) {
|
||||
return fmt.Errorf("The repository name you provided (%s) already exists. Please specify a different name.", name)
|
||||
}
|
||||
f.Add(&repo.Entry{
|
||||
Name: name,
|
||||
URL: strings.TrimSuffix(url, "/"),
|
||||
Cache: filepath.Base(cif),
|
||||
})
|
||||
return f.WriteFile(home.RepositoryFile(), 0644)
|
||||
}
|
||||
|
||||
func updateRepository(name, url string, home helmpath.Home) error {
|
||||
cif := home.CacheIndex(name)
|
||||
if err := repo.DownloadIndexFile(name, url, cif); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return updateRepoLine(name, url, home)
|
||||
}
|
||||
|
||||
func updateRepoLine(name, url string, home helmpath.Home) error {
|
||||
cif := home.CacheIndex(name)
|
||||
f, err := repo.LoadRepositoriesFile(home.RepositoryFile())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.Update(&repo.Entry{
|
||||
Name: name,
|
||||
URL: url,
|
||||
Cache: filepath.Base(cif),
|
||||
})
|
||||
|
||||
return f.WriteFile(home.RepositoryFile(), 0666)
|
||||
}
|
||||
|
|
|
@ -80,7 +80,7 @@ func TestRepoAdd(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := addRepository(testName, ts.URL(), hh); err != nil {
|
||||
if err := addRepository(testName, ts.URL(), hh, "", "", "", true); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
|
@ -93,11 +93,11 @@ func TestRepoAdd(t *testing.T) {
|
|||
t.Errorf("%s was not successfully inserted into %s", testName, hh.RepositoryFile())
|
||||
}
|
||||
|
||||
if err := updateRepository(testName, ts.URL(), hh); err != nil {
|
||||
if err := addRepository(testName, ts.URL(), hh, "", "", "", false); err != nil {
|
||||
t.Errorf("Repository was not updated: %s", err)
|
||||
}
|
||||
|
||||
if err := addRepository(testName, ts.URL(), hh); err == nil {
|
||||
if err := addRepository(testName, ts.URL(), hh, "", "", "", false); err != nil {
|
||||
t.Errorf("Duplicate repository name was added")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -83,12 +83,12 @@ func (i *repoIndexCmd) run() error {
|
|||
func index(dir, url, mergeTo string) error {
|
||||
out := filepath.Join(dir, "index.yaml")
|
||||
|
||||
i, err := repo.IndexDirectory(dir, url)
|
||||
i, err := repo.NewChartRepositoryIndexFromDirectory(dir, url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if mergeTo != "" {
|
||||
i2, err := repo.LoadIndexFile(mergeTo)
|
||||
i2, err := repo.NewChartRepositoryIndexFromFile(mergeTo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Merge failed: %s", err)
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ func TestRepoIndexCmd(t *testing.T) {
|
|||
|
||||
destIndex := filepath.Join(dir, "index.yaml")
|
||||
|
||||
index, err := repo.LoadIndexFile(destIndex)
|
||||
index, err := repo.NewChartRepositoryIndexFromFile(destIndex)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -94,7 +94,7 @@ func TestRepoIndexCmd(t *testing.T) {
|
|||
t.Error(err)
|
||||
}
|
||||
|
||||
index, err = repo.LoadIndexFile(destIndex)
|
||||
index, err = repo.NewChartRepositoryIndexFromFile(destIndex)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
@ -24,24 +24,33 @@ import (
|
|||
|
||||
"k8s.io/helm/cmd/helm/helmpath"
|
||||
"k8s.io/helm/pkg/repo"
|
||||
"k8s.io/helm/pkg/repo/repotest"
|
||||
)
|
||||
|
||||
func TestRepoRemove(t *testing.T) {
|
||||
testURL := "https://test-url.com"
|
||||
|
||||
b := bytes.NewBuffer(nil)
|
||||
|
||||
home, err := tempHelmHome(t)
|
||||
ts, thome, err := repotest.NewTempServer("testdata/testserver/*.*")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(home)
|
||||
hh := helmpath.Home(home)
|
||||
|
||||
oldhome := homePath()
|
||||
helmHome = thome
|
||||
hh := helmpath.Home(thome)
|
||||
defer func() {
|
||||
ts.Stop()
|
||||
helmHome = oldhome
|
||||
os.Remove(thome)
|
||||
}()
|
||||
if err := ensureTestHome(hh, t); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
b := bytes.NewBuffer(nil)
|
||||
|
||||
if err := removeRepoLine(b, testName, hh); err == nil {
|
||||
t.Errorf("Expected error removing %s, but did not get one.", testName)
|
||||
}
|
||||
if err := insertRepoLine(testName, testURL, hh); err != nil {
|
||||
if err := addRepository(testName, ts.URL(), hh, "", "", "", true); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
|
|
|
@ -36,10 +36,14 @@ Information is cached locally, where it is used by commands like 'helm search'.
|
|||
future releases.
|
||||
`
|
||||
|
||||
var (
|
||||
errNoRepositories = errors.New("no repositories found. You must add one before updating")
|
||||
)
|
||||
|
||||
type repoUpdateCmd struct {
|
||||
update func([]*repo.Entry, bool, io.Writer, helmpath.Home)
|
||||
out io.Writer
|
||||
update func([]*repo.ChartRepository, io.Writer)
|
||||
home helmpath.Home
|
||||
out io.Writer
|
||||
}
|
||||
|
||||
func newRepoUpdateCmd(out io.Writer) *cobra.Command {
|
||||
|
@ -67,31 +71,39 @@ func (u *repoUpdateCmd) run() error {
|
|||
}
|
||||
|
||||
if len(f.Repositories) == 0 {
|
||||
return errors.New("no repositories found. You must add one before updating")
|
||||
return errNoRepositories
|
||||
}
|
||||
var repos []*repo.ChartRepository
|
||||
for _, cfg := range f.Repositories {
|
||||
r, err := repo.NewChartRepository(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
repos = append(repos, r)
|
||||
}
|
||||
|
||||
u.update(f.Repositories, flagDebug, u.out, u.home)
|
||||
u.update(repos, u.out)
|
||||
return nil
|
||||
}
|
||||
|
||||
func updateCharts(repos []*repo.Entry, verbose bool, out io.Writer, home helmpath.Home) {
|
||||
func updateCharts(repos []*repo.ChartRepository, out io.Writer) {
|
||||
fmt.Fprintln(out, "Hang tight while we grab the latest from your chart repositories...")
|
||||
var wg sync.WaitGroup
|
||||
for _, re := range repos {
|
||||
wg.Add(1)
|
||||
go func(n, u string) {
|
||||
go func(re *repo.ChartRepository) {
|
||||
defer wg.Done()
|
||||
if n == localRepository {
|
||||
// We skip local because the indices are symlinked.
|
||||
if re.Config.Name == localRepository {
|
||||
fmt.Fprintf(out, "...Skip %s chart repository", re.Config.Name)
|
||||
return
|
||||
}
|
||||
err := repo.DownloadIndexFile(n, u, home.CacheIndex(n))
|
||||
err := re.DownloadIndexFile()
|
||||
if err != nil {
|
||||
fmt.Fprintf(out, "...Unable to get an update from the %q chart repository (%s):\n\t%s\n", n, u, err)
|
||||
fmt.Fprintf(out, "...Unable to get an update from the %q chart repository (%s):\n\t%s\n", re.Config.Name, re.Config.URL, err)
|
||||
} else {
|
||||
fmt.Fprintf(out, "...Successfully got an update from the %q chart repository\n", n)
|
||||
fmt.Fprintf(out, "...Successfully got an update from the %q chart repository\n", re.Config.Name)
|
||||
}
|
||||
}(re.Name, re.URL)
|
||||
}(re)
|
||||
}
|
||||
wg.Wait()
|
||||
fmt.Fprintln(out, "Update Complete. ⎈ Happy Helming!⎈ ")
|
||||
|
|
|
@ -43,15 +43,15 @@ func TestUpdateCmd(t *testing.T) {
|
|||
out := bytes.NewBuffer(nil)
|
||||
// Instead of using the HTTP updater, we provide our own for this test.
|
||||
// The TestUpdateCharts test verifies the HTTP behavior independently.
|
||||
updater := func(repos []*repo.Entry, verbose bool, out io.Writer, home helmpath.Home) {
|
||||
updater := func(repos []*repo.ChartRepository, out io.Writer) {
|
||||
for _, re := range repos {
|
||||
fmt.Fprintln(out, re.Name)
|
||||
fmt.Fprintln(out, re.Config.Name)
|
||||
}
|
||||
}
|
||||
uc := &repoUpdateCmd{
|
||||
out: out,
|
||||
update: updater,
|
||||
home: helmpath.Home(thome),
|
||||
out: out,
|
||||
}
|
||||
if err := uc.run(); err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -63,33 +63,40 @@ func TestUpdateCmd(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestUpdateCharts(t *testing.T) {
|
||||
srv, thome, err := repotest.NewTempServer("testdata/testserver/*.*")
|
||||
ts, thome, err := repotest.NewTempServer("testdata/testserver/*.*")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
oldhome := homePath()
|
||||
helmHome = thome
|
||||
hh := helmpath.Home(thome)
|
||||
defer func() {
|
||||
srv.Stop()
|
||||
ts.Stop()
|
||||
helmHome = oldhome
|
||||
os.Remove(thome)
|
||||
}()
|
||||
if err := ensureTestHome(helmpath.Home(thome), t); err != nil {
|
||||
if err := ensureTestHome(hh, t); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
repos := []*repo.Entry{
|
||||
{Name: "charts", URL: srv.URL()},
|
||||
r, err := repo.NewChartRepository(&repo.ChartRepositoryConfig{
|
||||
Name: "charts",
|
||||
URL: ts.URL(),
|
||||
Cache: hh.CacheIndex("charts"),
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
updateCharts(repos, false, buf, helmpath.Home(thome))
|
||||
|
||||
got := buf.String()
|
||||
b := bytes.NewBuffer(nil)
|
||||
updateCharts([]*repo.ChartRepository{r}, b)
|
||||
|
||||
got := b.String()
|
||||
if strings.Contains(got, "Unable to get an update") {
|
||||
t.Errorf("Failed to get a repo: %q", got)
|
||||
}
|
||||
if !strings.Contains(got, "Update Complete.") {
|
||||
t.Errorf("Update was not successful")
|
||||
t.Error("Update was not successful")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ func (r *Resolver) Resolve(reqs *chartutil.Requirements, repoNames map[string]st
|
|||
return nil, fmt.Errorf("dependency %q has an invalid version/constraint format: %s", d.Name, err)
|
||||
}
|
||||
|
||||
repoIndex, err := repo.LoadIndexFile(r.helmhome.CacheIndex(repoNames[d.Name]))
|
||||
repoIndex, err := repo.NewChartRepositoryIndexFromFile(r.helmhome.CacheIndex(repoNames[d.Name]))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("no cached repo found. (try 'helm repo update'). %s", err)
|
||||
}
|
||||
|
|
|
@ -119,7 +119,7 @@ func (s *searchCmd) buildIndex() (*search.Index, error) {
|
|||
for _, re := range rf.Repositories {
|
||||
n := re.Name
|
||||
f := s.helmhome.CacheIndex(n)
|
||||
ind, err := repo.LoadIndexFile(f)
|
||||
ind, err := repo.NewChartRepositoryIndexFromFile(f)
|
||||
if err != nil {
|
||||
fmt.Fprintf(s.out, "WARNING: Repo %q is corrupt or missing. Try 'helm repo update'.", n)
|
||||
continue
|
||||
|
|
|
@ -61,7 +61,7 @@ func NewIndex() *Index {
|
|||
const verSep = "$$"
|
||||
|
||||
// AddRepo adds a repository index to the search index.
|
||||
func (i *Index) AddRepo(rname string, ind *repo.IndexFile, all bool) {
|
||||
func (i *Index) AddRepo(rname string, ind *repo.ChartRepositoryIndex, all bool) {
|
||||
for name, ref := range ind.Entries {
|
||||
if len(ref) == 0 {
|
||||
// Skip chart names that have zero releases.
|
||||
|
|
|
@ -95,8 +95,8 @@ var indexfileEntries = map[string]repo.ChartVersions{
|
|||
|
||||
func loadTestIndex(t *testing.T, all bool) *Index {
|
||||
i := NewIndex()
|
||||
i.AddRepo("testing", &repo.IndexFile{Entries: indexfileEntries}, all)
|
||||
i.AddRepo("ztesting", &repo.IndexFile{Entries: map[string]repo.ChartVersions{
|
||||
i.AddRepo("testing", &repo.ChartRepositoryIndex{Entries: indexfileEntries}, all)
|
||||
i.AddRepo("ztesting", &repo.ChartRepositoryIndex{Entries: map[string]repo.ChartVersions{
|
||||
"pinta": {
|
||||
{
|
||||
URLs: []string{"http://example.com/charts/pinta-2.0.0.tgz"},
|
||||
|
|
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 repo // import "k8s.io/helm/pkg/repo"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
|
||||
"k8s.io/helm/pkg/chartutil"
|
||||
"k8s.io/helm/pkg/provenance"
|
||||
"k8s.io/helm/pkg/tlsutil"
|
||||
)
|
||||
|
||||
// ChartRepositoryConfig represents a collection of parameters for chart repository
|
||||
type ChartRepositoryConfig struct {
|
||||
Name string `json:"name"`
|
||||
Cache string `json:"cache"`
|
||||
URL string `json:"url"`
|
||||
CertFile string `json:"certFile"`
|
||||
KeyFile string `json:"keyFile"`
|
||||
CAFile string `json:"caFile"`
|
||||
}
|
||||
|
||||
// ChartRepository represents a chart repository
|
||||
type ChartRepository struct {
|
||||
Config *ChartRepositoryConfig
|
||||
ChartPaths []string
|
||||
IndexFile *ChartRepositoryIndex
|
||||
Client *http.Client
|
||||
}
|
||||
|
||||
// NewChartRepository constructs ChartRepository
|
||||
func NewChartRepository(cfg *ChartRepositoryConfig) (*ChartRepository, error) {
|
||||
var client *http.Client
|
||||
if cfg.CertFile != "" && cfg.KeyFile != "" && cfg.CAFile != "" {
|
||||
tlsConf, err := tlsutil.NewClientTLS(cfg.CertFile, cfg.KeyFile, cfg.CAFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't create TLS config for client: %s", err.Error())
|
||||
}
|
||||
tlsConf.BuildNameToCertificate()
|
||||
client = &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: tlsConf,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
client = http.DefaultClient
|
||||
}
|
||||
|
||||
return &ChartRepository{
|
||||
Config: cfg,
|
||||
IndexFile: NewChartRepositoryIndex(),
|
||||
Client: client,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Load loads a directory of charts as if it were a repository.
|
||||
//
|
||||
// It requires the presence of an index.yaml file in the directory.
|
||||
func (r *ChartRepository) Load() error {
|
||||
dirInfo, err := os.Stat(r.Config.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !dirInfo.IsDir() {
|
||||
return fmt.Errorf("%q is not a directory", r.Config.Name)
|
||||
}
|
||||
|
||||
// FIXME: Why are we recursively walking directories?
|
||||
// FIXME: Why are we not reading the repositories.yaml to figure out
|
||||
// what repos to use?
|
||||
filepath.Walk(r.Config.Name, func(path string, f os.FileInfo, err error) error {
|
||||
if !f.IsDir() {
|
||||
if strings.Contains(f.Name(), "-index.yaml") {
|
||||
i, err := NewChartRepositoryIndexFromFile(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
r.IndexFile = i
|
||||
} else if strings.HasSuffix(f.Name(), ".tgz") {
|
||||
r.ChartPaths = append(r.ChartPaths, path)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// DownloadIndexFile fetches the index from a repository.
|
||||
func (r *ChartRepository) DownloadIndexFile() error {
|
||||
var indexURL string
|
||||
|
||||
indexURL = strings.TrimSuffix(r.Config.URL, "/") + "/index.yaml"
|
||||
resp, err := r.Client.Get(indexURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
index, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := loadIndex(index); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(r.Config.Cache, index, 0644)
|
||||
}
|
||||
|
||||
// Index generates an index for the chart repository and writes an index.yaml file.
|
||||
func (r *ChartRepository) Index() error {
|
||||
err := r.generateIndex()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r.saveIndexFile()
|
||||
}
|
||||
|
||||
func (r *ChartRepository) saveIndexFile() error {
|
||||
index, err := yaml.Marshal(r.IndexFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(filepath.Join(r.Config.Name, indexPath), index, 0644)
|
||||
}
|
||||
|
||||
func (r *ChartRepository) generateIndex() error {
|
||||
for _, path := range r.ChartPaths {
|
||||
ch, err := chartutil.Load(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
digest, err := provenance.DigestFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !r.IndexFile.Has(ch.Metadata.Name, ch.Metadata.Version) {
|
||||
r.IndexFile.Add(ch.Metadata, path, r.Config.URL, digest)
|
||||
}
|
||||
// TODO: If a chart exists, but has a different Digest, should we error?
|
||||
}
|
||||
r.IndexFile.SortEntries()
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,185 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 repo
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/helm/pkg/proto/hapi/chart"
|
||||
)
|
||||
|
||||
const (
|
||||
testRepository = "testdata/repository"
|
||||
testURL = "http://example-charts.com"
|
||||
)
|
||||
|
||||
func TestLoadChartRepository(t *testing.T) {
|
||||
r, err := NewChartRepository(&ChartRepositoryConfig{
|
||||
Name: testRepository,
|
||||
URL: testURL,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Problem creating chart repository from %s: %v", testRepository, err)
|
||||
}
|
||||
|
||||
if err := r.Load(); err != nil {
|
||||
t.Errorf("Problem loading chart repository from %s: %v", testRepository, err)
|
||||
}
|
||||
|
||||
paths := []string{
|
||||
filepath.Join(testRepository, "frobnitz-1.2.3.tgz"),
|
||||
filepath.Join(testRepository, "sprocket-1.1.0.tgz"),
|
||||
filepath.Join(testRepository, "sprocket-1.2.0.tgz"),
|
||||
}
|
||||
|
||||
if r.Config.Name != testRepository {
|
||||
t.Errorf("Expected %s as Name but got %s", testRepository, r.Config.Name)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(r.ChartPaths, paths) {
|
||||
t.Errorf("Expected %#v but got %#v\n", paths, r.ChartPaths)
|
||||
}
|
||||
|
||||
if r.Config.URL != testURL {
|
||||
t.Errorf("Expected url for chart repository to be %s but got %s", testURL, r.Config.URL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIndex(t *testing.T) {
|
||||
r, err := NewChartRepository(&ChartRepositoryConfig{
|
||||
Name: testRepository,
|
||||
URL: testURL,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Problem creating chart repository from %s: %v", testRepository, err)
|
||||
}
|
||||
|
||||
if err := r.Load(); err != nil {
|
||||
t.Errorf("Problem loading chart repository from %s: %v", testRepository, err)
|
||||
}
|
||||
|
||||
err = r.Index()
|
||||
if err != nil {
|
||||
t.Errorf("Error performing index: %v\n", err)
|
||||
}
|
||||
|
||||
tempIndexPath := filepath.Join(testRepository, indexPath)
|
||||
actual, err := NewChartRepositoryIndexFromFile(tempIndexPath)
|
||||
defer os.Remove(tempIndexPath) // clean up
|
||||
if err != nil {
|
||||
t.Errorf("Error loading index file %v", err)
|
||||
}
|
||||
verifyIndex(t, actual)
|
||||
|
||||
// Re-index and test again.
|
||||
err = r.Index()
|
||||
if err != nil {
|
||||
t.Errorf("Error performing re-index: %s\n", err)
|
||||
}
|
||||
second, err := NewChartRepositoryIndexFromFile(tempIndexPath)
|
||||
if err != nil {
|
||||
t.Errorf("Error re-loading index file %v", err)
|
||||
}
|
||||
verifyIndex(t, second)
|
||||
}
|
||||
|
||||
func verifyIndex(t *testing.T, actual *ChartRepositoryIndex) {
|
||||
var empty time.Time
|
||||
if actual.Generated == empty {
|
||||
t.Errorf("Generated should be greater than 0: %s", actual.Generated)
|
||||
}
|
||||
|
||||
if actual.APIVersion != APIVersionV1 {
|
||||
t.Error("Expected v1 API")
|
||||
}
|
||||
|
||||
entries := actual.Entries
|
||||
if numEntries := len(entries); numEntries != 2 {
|
||||
t.Errorf("Expected 2 charts to be listed in index file but got %v", numEntries)
|
||||
}
|
||||
|
||||
expects := map[string]ChartVersions{
|
||||
"frobnitz": {
|
||||
{
|
||||
Metadata: &chart.Metadata{
|
||||
Name: "frobnitz",
|
||||
Version: "1.2.3",
|
||||
},
|
||||
},
|
||||
},
|
||||
"sprocket": {
|
||||
{
|
||||
Metadata: &chart.Metadata{
|
||||
Name: "sprocket",
|
||||
Version: "1.2.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
Metadata: &chart.Metadata{
|
||||
Name: "sprocket",
|
||||
Version: "1.1.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, versions := range expects {
|
||||
got, ok := entries[name]
|
||||
if !ok {
|
||||
t.Errorf("Could not find %q entry", name)
|
||||
continue
|
||||
}
|
||||
if len(versions) != len(got) {
|
||||
t.Errorf("Expected %d versions, got %d", len(versions), len(got))
|
||||
continue
|
||||
}
|
||||
for i, e := range versions {
|
||||
g := got[i]
|
||||
if e.Name != g.Name {
|
||||
t.Errorf("Expected %q, got %q", e.Name, g.Name)
|
||||
}
|
||||
if e.Version != g.Version {
|
||||
t.Errorf("Expected %q, got %q", e.Version, g.Version)
|
||||
}
|
||||
if len(g.Keywords) != 3 {
|
||||
t.Error("Expected 3 keyrwords.")
|
||||
}
|
||||
if len(g.Maintainers) != 2 {
|
||||
t.Error("Expected 2 maintainers.")
|
||||
}
|
||||
if g.Created == empty {
|
||||
t.Error("Expected created to be non-empty")
|
||||
}
|
||||
if g.Description == "" {
|
||||
t.Error("Expected description to be non-empty")
|
||||
}
|
||||
if g.Home == "" {
|
||||
t.Error("Expected home to be non-empty")
|
||||
}
|
||||
if g.Digest == "" {
|
||||
t.Error("Expected digest to be non-empty")
|
||||
}
|
||||
if len(g.URLs) != 1 {
|
||||
t.Error("Expected exactly 1 URL")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -21,10 +21,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
@ -36,6 +33,7 @@ import (
|
|||
"k8s.io/helm/pkg/chartutil"
|
||||
"k8s.io/helm/pkg/proto/hapi/chart"
|
||||
"k8s.io/helm/pkg/provenance"
|
||||
"k8s.io/helm/pkg/urlutil"
|
||||
)
|
||||
|
||||
var indexPath = "index.yaml"
|
||||
|
@ -76,17 +74,35 @@ func (c ChartVersions) Less(a, b int) bool {
|
|||
return i.LessThan(j)
|
||||
}
|
||||
|
||||
// IndexFile represents the index file in a chart repository
|
||||
type IndexFile struct {
|
||||
// ChartRepositoryIndex represents the index file in a chart repository
|
||||
type ChartRepositoryIndex struct {
|
||||
APIVersion string `json:"apiVersion"`
|
||||
Generated time.Time `json:"generated"`
|
||||
Entries map[string]ChartVersions `json:"entries"`
|
||||
PublicKeys []string `json:"publicKeys,omitempty"`
|
||||
}
|
||||
|
||||
// NewIndexFile initializes an index.
|
||||
func NewIndexFile() *IndexFile {
|
||||
return &IndexFile{
|
||||
// ChartVersion represents a chart entry in the ChartRepositoryIndex
|
||||
type ChartVersion struct {
|
||||
*chart.Metadata
|
||||
URLs []string `json:"urls"`
|
||||
Created time.Time `json:"created,omitempty"`
|
||||
Removed bool `json:"removed,omitempty"`
|
||||
Digest string `json:"digest,omitempty"`
|
||||
}
|
||||
|
||||
// unversionedEntry represents a deprecated pre-Alpha.5 format.
|
||||
//
|
||||
// This will be removed prior to v2.0.0
|
||||
type unversionedEntry struct {
|
||||
Checksum string `json:"checksum"`
|
||||
URL string `json:"url"`
|
||||
Chartfile *chart.Metadata `json:"chartfile"`
|
||||
}
|
||||
|
||||
// NewChartRepositoryIndex initializes an index.
|
||||
func NewChartRepositoryIndex() *ChartRepositoryIndex {
|
||||
return &ChartRepositoryIndex{
|
||||
APIVersion: APIVersionV1,
|
||||
Generated: time.Now(),
|
||||
Entries: map[string]ChartVersions{},
|
||||
|
@ -94,14 +110,103 @@ func NewIndexFile() *IndexFile {
|
|||
}
|
||||
}
|
||||
|
||||
// NewChartRepositoryIndexFromFile takes a file at the given path and returns an ChartRepositoryIndex object
|
||||
func NewChartRepositoryIndexFromFile(path string) (*ChartRepositoryIndex, error) {
|
||||
b, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return loadIndex(b)
|
||||
}
|
||||
|
||||
// NewChartRepositoryIndexFromDirectory reads a (flat) directory and generates an index.
|
||||
//
|
||||
// It indexes only charts that have been packaged (*.tgz).
|
||||
//
|
||||
// The index returned will be in an unsorted state
|
||||
func NewChartRepositoryIndexFromDirectory(dir, baseURL string) (*ChartRepositoryIndex, error) {
|
||||
archives, err := filepath.Glob(filepath.Join(dir, "*.tgz"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
index := NewChartRepositoryIndex()
|
||||
for _, arch := range archives {
|
||||
fname := filepath.Base(arch)
|
||||
c, err := chartutil.Load(arch)
|
||||
if err != nil {
|
||||
// Assume this is not a chart.
|
||||
continue
|
||||
}
|
||||
hash, err := provenance.DigestFile(arch)
|
||||
if err != nil {
|
||||
return index, err
|
||||
}
|
||||
index.Add(c.Metadata, fname, baseURL, hash)
|
||||
}
|
||||
return index, nil
|
||||
}
|
||||
|
||||
// loadIndex loads an index file and does minimal validity checking.
|
||||
//
|
||||
// This will fail if API Version is not set (ErrNoAPIVersion) or if the unmarshal fails.
|
||||
func loadIndex(data []byte) (*ChartRepositoryIndex, error) {
|
||||
i := &ChartRepositoryIndex{}
|
||||
if err := yaml.Unmarshal(data, i); err != nil {
|
||||
return i, err
|
||||
}
|
||||
if i.APIVersion == "" {
|
||||
// When we leave Beta, we should remove legacy support and just
|
||||
// return this error:
|
||||
//return i, ErrNoAPIVersion
|
||||
return loadUnversionedIndex(data)
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// loadUnversionedIndex loads a pre-Alpha.5 index.yaml file.
|
||||
//
|
||||
// This format is deprecated. This function will be removed prior to v2.0.0.
|
||||
func loadUnversionedIndex(data []byte) (*ChartRepositoryIndex, error) {
|
||||
fmt.Fprintln(os.Stderr, "WARNING: Deprecated index file format. Try 'helm repo update'")
|
||||
i := map[string]unversionedEntry{}
|
||||
|
||||
// This gets around an error in the YAML parser. Instead of parsing as YAML,
|
||||
// we convert to JSON, and then decode again.
|
||||
var err error
|
||||
data, err = yaml.YAMLToJSON(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal(data, &i); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(i) == 0 {
|
||||
return nil, ErrNoAPIVersion
|
||||
}
|
||||
ni := NewChartRepositoryIndex()
|
||||
for n, item := range i {
|
||||
if item.Chartfile == nil || item.Chartfile.Name == "" {
|
||||
parts := strings.Split(n, "-")
|
||||
ver := ""
|
||||
if len(parts) > 1 {
|
||||
ver = strings.TrimSuffix(parts[1], ".tgz")
|
||||
}
|
||||
item.Chartfile = &chart.Metadata{Name: parts[0], Version: ver}
|
||||
}
|
||||
ni.Add(item.Chartfile, item.URL, "", item.Checksum)
|
||||
}
|
||||
return ni, nil
|
||||
}
|
||||
|
||||
// Add adds a file to the index
|
||||
// This can leave the index in an unsorted state
|
||||
func (i IndexFile) Add(md *chart.Metadata, filename, baseURL, digest string) {
|
||||
func (i ChartRepositoryIndex) Add(md *chart.Metadata, filename, baseURL, digest string) {
|
||||
u := filename
|
||||
if baseURL != "" {
|
||||
var err error
|
||||
_, file := filepath.Split(filename)
|
||||
u, err = urlJoin(baseURL, file)
|
||||
u, err = urlutil.URLJoin(baseURL, file)
|
||||
if err != nil {
|
||||
u = filepath.Join(baseURL, file)
|
||||
}
|
||||
|
@ -120,7 +225,7 @@ func (i IndexFile) Add(md *chart.Metadata, filename, baseURL, digest string) {
|
|||
}
|
||||
|
||||
// Has returns true if the index has an entry for a chart with the given name and exact version.
|
||||
func (i IndexFile) Has(name, version string) bool {
|
||||
func (i ChartRepositoryIndex) Has(name, version string) bool {
|
||||
_, err := i.Get(name, version)
|
||||
return err == nil
|
||||
}
|
||||
|
@ -131,7 +236,7 @@ func (i IndexFile) Has(name, version string) bool {
|
|||
// the most recent release for every version is in the 0th slot in the
|
||||
// Entries.ChartVersions array. That way, tooling can predict the newest
|
||||
// version without needing to parse SemVers.
|
||||
func (i IndexFile) SortEntries() {
|
||||
func (i ChartRepositoryIndex) SortEntries() {
|
||||
for _, versions := range i.Entries {
|
||||
sort.Sort(sort.Reverse(versions))
|
||||
}
|
||||
|
@ -140,7 +245,7 @@ func (i IndexFile) SortEntries() {
|
|||
// Get returns the ChartVersion for the given name.
|
||||
//
|
||||
// If version is empty, this will return the chart with the highest version.
|
||||
func (i IndexFile) Get(name, version string) (*ChartVersion, error) {
|
||||
func (i ChartRepositoryIndex) Get(name, version string) (*ChartVersion, error) {
|
||||
vs, ok := i.Entries[name]
|
||||
if !ok {
|
||||
return nil, ErrNoChartName
|
||||
|
@ -163,7 +268,7 @@ func (i IndexFile) Get(name, version string) (*ChartVersion, error) {
|
|||
// WriteFile writes an index file to the given destination path.
|
||||
//
|
||||
// The mode on the file is set to 'mode'.
|
||||
func (i IndexFile) WriteFile(dest string, mode os.FileMode) error {
|
||||
func (i ChartRepositoryIndex) WriteFile(dest string, mode os.FileMode) error {
|
||||
b, err := yaml.Marshal(i)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -179,7 +284,7 @@ func (i IndexFile) WriteFile(dest string, mode os.FileMode) error {
|
|||
// In all other cases, the existing record is preserved.
|
||||
//
|
||||
// This can leave the index in an unsorted state
|
||||
func (i *IndexFile) Merge(f *IndexFile) {
|
||||
func (i *ChartRepositoryIndex) Merge(f *ChartRepositoryIndex) {
|
||||
for _, cvs := range f.Entries {
|
||||
for _, cv := range cvs {
|
||||
if !i.Has(cv.Name, cv.Version) {
|
||||
|
@ -189,153 +294,3 @@ func (i *IndexFile) Merge(f *IndexFile) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Need both JSON and YAML annotations until we get rid of gopkg.in/yaml.v2
|
||||
|
||||
// ChartVersion represents a chart entry in the IndexFile
|
||||
type ChartVersion struct {
|
||||
*chart.Metadata
|
||||
URLs []string `json:"urls"`
|
||||
Created time.Time `json:"created,omitempty"`
|
||||
Removed bool `json:"removed,omitempty"`
|
||||
Digest string `json:"digest,omitempty"`
|
||||
}
|
||||
|
||||
// IndexDirectory reads a (flat) directory and generates an index.
|
||||
//
|
||||
// It indexes only charts that have been packaged (*.tgz).
|
||||
//
|
||||
// The index returned will be in an unsorted state
|
||||
func IndexDirectory(dir, baseURL string) (*IndexFile, error) {
|
||||
archives, err := filepath.Glob(filepath.Join(dir, "*.tgz"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
index := NewIndexFile()
|
||||
for _, arch := range archives {
|
||||
fname := filepath.Base(arch)
|
||||
c, err := chartutil.Load(arch)
|
||||
if err != nil {
|
||||
// Assume this is not a chart.
|
||||
continue
|
||||
}
|
||||
hash, err := provenance.DigestFile(arch)
|
||||
if err != nil {
|
||||
return index, err
|
||||
}
|
||||
index.Add(c.Metadata, fname, baseURL, hash)
|
||||
}
|
||||
return index, nil
|
||||
}
|
||||
|
||||
// DownloadIndexFile fetches the index from a repository.
|
||||
func DownloadIndexFile(repoName, url, indexFilePath string) error {
|
||||
var indexURL string
|
||||
|
||||
indexURL = strings.TrimSuffix(url, "/") + "/index.yaml"
|
||||
resp, err := http.Get(indexURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := LoadIndex(b); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ioutil.WriteFile(indexFilePath, b, 0644)
|
||||
}
|
||||
|
||||
// LoadIndex loads an index file and does minimal validity checking.
|
||||
//
|
||||
// This will fail if API Version is not set (ErrNoAPIVersion) or if the unmarshal fails.
|
||||
func LoadIndex(data []byte) (*IndexFile, error) {
|
||||
i := &IndexFile{}
|
||||
if err := yaml.Unmarshal(data, i); err != nil {
|
||||
return i, err
|
||||
}
|
||||
if i.APIVersion == "" {
|
||||
// When we leave Beta, we should remove legacy support and just
|
||||
// return this error:
|
||||
//return i, ErrNoAPIVersion
|
||||
return loadUnversionedIndex(data)
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
// unversionedEntry represents a deprecated pre-Alpha.5 format.
|
||||
//
|
||||
// This will be removed prior to v2.0.0
|
||||
type unversionedEntry struct {
|
||||
Checksum string `json:"checksum"`
|
||||
URL string `json:"url"`
|
||||
Chartfile *chart.Metadata `json:"chartfile"`
|
||||
}
|
||||
|
||||
// loadUnversionedIndex loads a pre-Alpha.5 index.yaml file.
|
||||
//
|
||||
// This format is deprecated. This function will be removed prior to v2.0.0.
|
||||
func loadUnversionedIndex(data []byte) (*IndexFile, error) {
|
||||
fmt.Fprintln(os.Stderr, "WARNING: Deprecated index file format. Try 'helm repo update'")
|
||||
i := map[string]unversionedEntry{}
|
||||
|
||||
// This gets around an error in the YAML parser. Instead of parsing as YAML,
|
||||
// we convert to JSON, and then decode again.
|
||||
var err error
|
||||
data, err = yaml.YAMLToJSON(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal(data, &i); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(i) == 0 {
|
||||
return nil, ErrNoAPIVersion
|
||||
}
|
||||
ni := NewIndexFile()
|
||||
for n, item := range i {
|
||||
if item.Chartfile == nil || item.Chartfile.Name == "" {
|
||||
parts := strings.Split(n, "-")
|
||||
ver := ""
|
||||
if len(parts) > 1 {
|
||||
ver = strings.TrimSuffix(parts[1], ".tgz")
|
||||
}
|
||||
item.Chartfile = &chart.Metadata{Name: parts[0], Version: ver}
|
||||
}
|
||||
ni.Add(item.Chartfile, item.URL, "", item.Checksum)
|
||||
}
|
||||
return ni, nil
|
||||
}
|
||||
|
||||
// LoadIndexFile takes a file at the given path and returns an IndexFile object
|
||||
func LoadIndexFile(path string) (*IndexFile, error) {
|
||||
b, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return LoadIndex(b)
|
||||
}
|
||||
|
||||
// urlJoin joins a base URL to one or more path components.
|
||||
//
|
||||
// It's like filepath.Join for URLs. If the baseURL is pathish, this will still
|
||||
// perform a join.
|
||||
//
|
||||
// If the URL is unparsable, this returns an error.
|
||||
func urlJoin(baseURL string, paths ...string) (string, error) {
|
||||
u, err := url.Parse(baseURL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// We want path instead of filepath because path always uses /.
|
||||
all := []string{u.Path}
|
||||
all = append(all, paths...)
|
||||
u.Path = path.Join(all...)
|
||||
return u.String(), nil
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ const (
|
|||
)
|
||||
|
||||
func TestIndexFile(t *testing.T) {
|
||||
i := NewIndexFile()
|
||||
i := NewChartRepositoryIndex()
|
||||
i.Add(&chart.Metadata{Name: "clipper", Version: "0.1.0"}, "clipper-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890")
|
||||
i.Add(&chart.Metadata{Name: "cutter", Version: "0.1.1"}, "cutter-0.1.1.tgz", "http://example.com/charts", "sha256:1234567890abc")
|
||||
i.Add(&chart.Metadata{Name: "cutter", Version: "0.1.0"}, "cutter-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890abc")
|
||||
|
@ -67,7 +67,7 @@ func TestLoadIndex(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
i, err := LoadIndex(b)
|
||||
i, err := loadIndex(b)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -75,7 +75,7 @@ func TestLoadIndex(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestLoadIndexFile(t *testing.T) {
|
||||
i, err := LoadIndexFile(testfile)
|
||||
i, err := NewChartRepositoryIndexFromFile(testfile)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -83,13 +83,13 @@ func TestLoadIndexFile(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMerge(t *testing.T) {
|
||||
ind1 := NewIndexFile()
|
||||
ind1 := NewChartRepositoryIndex()
|
||||
ind1.Add(&chart.Metadata{
|
||||
Name: "dreadnought",
|
||||
Version: "0.1.0",
|
||||
}, "dreadnought-0.1.0.tgz", "http://example.com", "aaaa")
|
||||
|
||||
ind2 := NewIndexFile()
|
||||
ind2 := NewChartRepositoryIndex()
|
||||
ind2.Add(&chart.Metadata{
|
||||
Name: "dreadnought",
|
||||
Version: "0.2.0",
|
||||
|
@ -132,21 +132,30 @@ func TestDownloadIndexFile(t *testing.T) {
|
|||
}
|
||||
defer os.RemoveAll(dirName)
|
||||
|
||||
path := filepath.Join(dirName, testRepo+"-index.yaml")
|
||||
if err := DownloadIndexFile(testRepo, srv.URL, path); err != nil {
|
||||
indexFilePath := filepath.Join(dirName, testRepo+"-index.yaml")
|
||||
r, err := NewChartRepository(&ChartRepositoryConfig{
|
||||
Name: testRepo,
|
||||
URL: srv.URL,
|
||||
Cache: indexFilePath,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Problem creating chart repository from %s: %v", testRepo, err)
|
||||
}
|
||||
|
||||
if err := r.DownloadIndexFile(); err != nil {
|
||||
t.Errorf("%#v", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
if _, err := os.Stat(indexFilePath); err != nil {
|
||||
t.Errorf("error finding created index file: %#v", err)
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadFile(path)
|
||||
b, err := ioutil.ReadFile(indexFilePath)
|
||||
if err != nil {
|
||||
t.Errorf("error reading index file: %#v", err)
|
||||
}
|
||||
|
||||
i, err := LoadIndex(b)
|
||||
i, err := loadIndex(b)
|
||||
if err != nil {
|
||||
t.Errorf("Index %q failed to parse: %s", testfile, err)
|
||||
return
|
||||
|
@ -155,7 +164,7 @@ func TestDownloadIndexFile(t *testing.T) {
|
|||
verifyLocalIndex(t, i)
|
||||
}
|
||||
|
||||
func verifyLocalIndex(t *testing.T, i *IndexFile) {
|
||||
func verifyLocalIndex(t *testing.T, i *ChartRepositoryIndex) {
|
||||
numEntries := len(i.Entries)
|
||||
if numEntries != 2 {
|
||||
t.Errorf("Expected 2 entries in index file but got %d", numEntries)
|
||||
|
@ -255,7 +264,7 @@ func verifyLocalIndex(t *testing.T, i *IndexFile) {
|
|||
|
||||
func TestIndexDirectory(t *testing.T) {
|
||||
dir := "testdata/repository"
|
||||
index, err := IndexDirectory(dir, "http://localhost:8080")
|
||||
index, err := NewChartRepositoryIndexFromDirectory(dir, "http://localhost:8080")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
@ -305,8 +314,7 @@ func TestLoadUnversionedIndex(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestIndexAdd(t *testing.T) {
|
||||
|
||||
i := NewIndexFile()
|
||||
i := NewChartRepositoryIndex()
|
||||
i.Add(&chart.Metadata{Name: "clipper", Version: "0.1.0"}, "clipper-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890")
|
||||
|
||||
if i.Entries["clipper"][0].URLs[0] != "http://example.com/charts/clipper-0.1.0.tgz" {
|
||||
|
@ -325,24 +333,3 @@ func TestIndexAdd(t *testing.T) {
|
|||
t.Errorf("Expected http://example.com/charts/deis-0.1.0.tgz, got %s", i.Entries["deis"][0].URLs[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestUrlJoin(t *testing.T) {
|
||||
tests := []struct {
|
||||
name, url, expect string
|
||||
paths []string
|
||||
}{
|
||||
{name: "URL, one path", url: "http://example.com", paths: []string{"hello"}, expect: "http://example.com/hello"},
|
||||
{name: "Long URL, one path", url: "http://example.com/but/first", paths: []string{"slurm"}, expect: "http://example.com/but/first/slurm"},
|
||||
{name: "URL, two paths", url: "http://example.com", paths: []string{"hello", "world"}, expect: "http://example.com/hello/world"},
|
||||
{name: "URL, no paths", url: "http://example.com", paths: []string{}, expect: "http://example.com"},
|
||||
{name: "basepath, two paths", url: "../example.com", paths: []string{"hello", "world"}, expect: "../example.com/hello/world"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
if got, err := urlJoin(tt.url, tt.paths...); err != nil {
|
||||
t.Errorf("%s: error %q", tt.name, err)
|
||||
} else if got != tt.expect {
|
||||
t.Errorf("%s: expected %q, got %q", tt.name, tt.expect, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,7 +82,7 @@ func (s *RepositoryServer) htmlIndex(w http.ResponseWriter, r *http.Request) {
|
|||
t := htemplate.Must(htemplate.New("index.html").Parse(indexHTMLTemplate))
|
||||
// load index
|
||||
lrp := filepath.Join(s.RepoPath, "index.yaml")
|
||||
i, err := LoadIndexFile(lrp)
|
||||
i, err := NewChartRepositoryIndexFromFile(lrp)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 500)
|
||||
return
|
||||
|
@ -107,7 +107,7 @@ func AddChartToLocalRepo(ch *chart.Chart, path string) error {
|
|||
// Reindex adds an entry to the index file at the given path
|
||||
func Reindex(ch *chart.Chart, path string) error {
|
||||
name := ch.Metadata.Name + "-" + ch.Metadata.Version
|
||||
y, err := LoadIndexFile(path)
|
||||
y, err := NewChartRepositoryIndexFromFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
142
pkg/repo/repo.go
142
pkg/repo/repo.go
|
@ -21,64 +21,44 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
|
||||
"k8s.io/helm/pkg/chartutil"
|
||||
"k8s.io/helm/pkg/provenance"
|
||||
)
|
||||
|
||||
// ErrRepoOutOfDate indicates that the repository file is out of date, but
|
||||
// is fixable.
|
||||
var ErrRepoOutOfDate = errors.New("repository file is out of date")
|
||||
|
||||
// ChartRepository represents a chart repository
|
||||
type ChartRepository struct {
|
||||
RootPath string
|
||||
URL string // URL of repository
|
||||
ChartPaths []string
|
||||
IndexFile *IndexFile
|
||||
// RepositoryFile represents the repositories.yaml file in $HELM_HOME
|
||||
type RepositoryFile struct {
|
||||
APIVersion string `json:"apiVersion"`
|
||||
Generated time.Time `json:"generated"`
|
||||
Repositories []*ChartRepositoryConfig `json:"repositories"`
|
||||
}
|
||||
|
||||
// Entry represents one repo entry in a repositories listing.
|
||||
type Entry struct {
|
||||
Name string `json:"name"`
|
||||
Cache string `json:"cache"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
// RepoFile represents the repositories.yaml file in $HELM_HOME
|
||||
type RepoFile struct {
|
||||
APIVersion string `json:"apiVersion"`
|
||||
Generated time.Time `json:"generated"`
|
||||
Repositories []*Entry `json:"repositories"`
|
||||
}
|
||||
|
||||
// NewRepoFile generates an empty repositories file.
|
||||
// NewRepositoryFile generates an empty repositories file.
|
||||
//
|
||||
// Generated and APIVersion are automatically set.
|
||||
func NewRepoFile() *RepoFile {
|
||||
return &RepoFile{
|
||||
func NewRepositoryFile() *RepositoryFile {
|
||||
return &RepositoryFile{
|
||||
APIVersion: APIVersionV1,
|
||||
Generated: time.Now(),
|
||||
Repositories: []*Entry{},
|
||||
Repositories: []*ChartRepositoryConfig{},
|
||||
}
|
||||
}
|
||||
|
||||
// LoadRepositoriesFile takes a file at the given path and returns a RepoFile object
|
||||
// LoadRepositoriesFile takes a file at the given path and returns a RepositoryFile object
|
||||
//
|
||||
// If this returns ErrRepoOutOfDate, it also returns a recovered RepoFile that
|
||||
// If this returns ErrRepoOutOfDate, it also returns a recovered RepositoryFile that
|
||||
// can be saved as a replacement to the out of date file.
|
||||
func LoadRepositoriesFile(path string) (*RepoFile, error) {
|
||||
func LoadRepositoriesFile(path string) (*RepositoryFile, error) {
|
||||
b, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := &RepoFile{}
|
||||
r := &RepositoryFile{}
|
||||
err = yaml.Unmarshal(b, r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -90,9 +70,9 @@ func LoadRepositoriesFile(path string) (*RepoFile, error) {
|
|||
if err = yaml.Unmarshal(b, &m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r := NewRepoFile()
|
||||
r := NewRepositoryFile()
|
||||
for k, v := range m {
|
||||
r.Add(&Entry{
|
||||
r.Add(&ChartRepositoryConfig{
|
||||
Name: k,
|
||||
URL: v,
|
||||
Cache: fmt.Sprintf("%s-index.yaml", k),
|
||||
|
@ -105,13 +85,13 @@ func LoadRepositoriesFile(path string) (*RepoFile, error) {
|
|||
}
|
||||
|
||||
// Add adds one or more repo entries to a repo file.
|
||||
func (r *RepoFile) Add(re ...*Entry) {
|
||||
func (r *RepositoryFile) Add(re ...*ChartRepositoryConfig) {
|
||||
r.Repositories = append(r.Repositories, re...)
|
||||
}
|
||||
|
||||
// Update attempts to replace one or more repo entries in a repo file. If an
|
||||
// entry with the same name doesn't exist in the repo file it will add it.
|
||||
func (r *RepoFile) Update(re ...*Entry) {
|
||||
func (r *RepositoryFile) Update(re ...*ChartRepositoryConfig) {
|
||||
for _, target := range re {
|
||||
found := false
|
||||
for j, repo := range r.Repositories {
|
||||
|
@ -128,7 +108,7 @@ func (r *RepoFile) Update(re ...*Entry) {
|
|||
}
|
||||
|
||||
// Has returns true if the given name is already a repository name.
|
||||
func (r *RepoFile) Has(name string) bool {
|
||||
func (r *RepositoryFile) Has(name string) bool {
|
||||
for _, rf := range r.Repositories {
|
||||
if rf.Name == name {
|
||||
return true
|
||||
|
@ -138,8 +118,8 @@ func (r *RepoFile) Has(name string) bool {
|
|||
}
|
||||
|
||||
// Remove removes the entry from the list of repositories.
|
||||
func (r *RepoFile) Remove(name string) bool {
|
||||
cp := []*Entry{}
|
||||
func (r *RepositoryFile) Remove(name string) bool {
|
||||
cp := []*ChartRepositoryConfig{}
|
||||
found := false
|
||||
for _, rf := range r.Repositories {
|
||||
if rf.Name == name {
|
||||
|
@ -153,90 +133,10 @@ func (r *RepoFile) Remove(name string) bool {
|
|||
}
|
||||
|
||||
// WriteFile writes a repositories file to the given path.
|
||||
func (r *RepoFile) WriteFile(path string, perm os.FileMode) error {
|
||||
func (r *RepositoryFile) WriteFile(path string, perm os.FileMode) error {
|
||||
data, err := yaml.Marshal(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(path, data, perm)
|
||||
}
|
||||
|
||||
// LoadChartRepository loads a directory of charts as if it were a repository.
|
||||
//
|
||||
// It requires the presence of an index.yaml file in the directory.
|
||||
//
|
||||
// This function evaluates the contents of the directory and
|
||||
// returns a ChartRepository
|
||||
func LoadChartRepository(dir, url string) (*ChartRepository, error) {
|
||||
dirInfo, err := os.Stat(dir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !dirInfo.IsDir() {
|
||||
return nil, fmt.Errorf("%q is not a directory", dir)
|
||||
}
|
||||
|
||||
r := &ChartRepository{RootPath: dir, URL: url}
|
||||
|
||||
// FIXME: Why are we recursively walking directories?
|
||||
// FIXME: Why are we not reading the repositories.yaml to figure out
|
||||
// what repos to use?
|
||||
filepath.Walk(dir, func(path string, f os.FileInfo, err error) error {
|
||||
if !f.IsDir() {
|
||||
if strings.Contains(f.Name(), "-index.yaml") {
|
||||
i, err := LoadIndexFile(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
r.IndexFile = i
|
||||
} else if strings.HasSuffix(f.Name(), ".tgz") {
|
||||
r.ChartPaths = append(r.ChartPaths, path)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *ChartRepository) saveIndexFile() error {
|
||||
index, err := yaml.Marshal(r.IndexFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ioutil.WriteFile(filepath.Join(r.RootPath, indexPath), index, 0644)
|
||||
}
|
||||
|
||||
// Index generates an index for the chart repository and writes an index.yaml file.
|
||||
func (r *ChartRepository) Index() error {
|
||||
err := r.generateIndex()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r.saveIndexFile()
|
||||
}
|
||||
|
||||
func (r *ChartRepository) generateIndex() error {
|
||||
if r.IndexFile == nil {
|
||||
r.IndexFile = NewIndexFile()
|
||||
}
|
||||
|
||||
for _, path := range r.ChartPaths {
|
||||
ch, err := chartutil.Load(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
digest, err := provenance.DigestFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !r.IndexFile.Has(ch.Metadata.Name, ch.Metadata.Version) {
|
||||
r.IndexFile.Add(ch.Metadata, path, r.URL, digest)
|
||||
}
|
||||
// TODO: If a chart exists, but has a different Digest, should we error?
|
||||
}
|
||||
r.IndexFile.SortEntries()
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -16,29 +16,19 @@ limitations under the License.
|
|||
|
||||
package repo
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/helm/pkg/proto/hapi/chart"
|
||||
)
|
||||
import "testing"
|
||||
|
||||
const testRepositoriesFile = "testdata/repositories.yaml"
|
||||
const testRepository = "testdata/repository"
|
||||
const testURL = "http://example-charts.com"
|
||||
|
||||
func TestRepoFile(t *testing.T) {
|
||||
rf := NewRepoFile()
|
||||
rf := NewRepositoryFile()
|
||||
rf.Add(
|
||||
&Entry{
|
||||
&ChartRepositoryConfig{
|
||||
Name: "stable",
|
||||
URL: "https://example.com/stable/charts",
|
||||
Cache: "stable-index.yaml",
|
||||
},
|
||||
&Entry{
|
||||
&ChartRepositoryConfig{
|
||||
Name: "incubator",
|
||||
URL: "https://example.com/incubator",
|
||||
Cache: "incubator-index.yaml",
|
||||
|
@ -68,15 +58,15 @@ func TestRepoFile(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestLoadRepositoriesFile(t *testing.T) {
|
||||
expects := NewRepoFile()
|
||||
func TestNewRepositoriesFile(t *testing.T) {
|
||||
expects := NewRepositoryFile()
|
||||
expects.Add(
|
||||
&Entry{
|
||||
&ChartRepositoryConfig{
|
||||
Name: "stable",
|
||||
URL: "https://example.com/stable/charts",
|
||||
Cache: "stable-index.yaml",
|
||||
},
|
||||
&Entry{
|
||||
&ChartRepositoryConfig{
|
||||
Name: "incubator",
|
||||
URL: "https://example.com/incubator",
|
||||
Cache: "incubator-index.yaml",
|
||||
|
@ -106,7 +96,7 @@ func TestLoadRepositoriesFile(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestLoadPreV1RepositoriesFile(t *testing.T) {
|
||||
func TestNewPreV1RepositoriesFile(t *testing.T) {
|
||||
r, err := LoadRepositoriesFile("testdata/old-repositories.yaml")
|
||||
if err != nil && err != ErrRepoOutOfDate {
|
||||
t.Fatal(err)
|
||||
|
@ -126,139 +116,3 @@ func TestLoadPreV1RepositoriesFile(t *testing.T) {
|
|||
t.Errorf("expected the best charts ever. Got %#v", r.Repositories)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadChartRepository(t *testing.T) {
|
||||
cr, err := LoadChartRepository(testRepository, testURL)
|
||||
if err != nil {
|
||||
t.Errorf("Problem loading chart repository from %s: %v", testRepository, err)
|
||||
}
|
||||
|
||||
paths := []string{filepath.Join(testRepository, "frobnitz-1.2.3.tgz"), filepath.Join(testRepository, "sprocket-1.1.0.tgz"), filepath.Join(testRepository, "sprocket-1.2.0.tgz")}
|
||||
|
||||
if cr.RootPath != testRepository {
|
||||
t.Errorf("Expected %s as RootPath but got %s", testRepository, cr.RootPath)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(cr.ChartPaths, paths) {
|
||||
t.Errorf("Expected %#v but got %#v\n", paths, cr.ChartPaths)
|
||||
}
|
||||
|
||||
if cr.URL != testURL {
|
||||
t.Errorf("Expected url for chart repository to be %s but got %s", testURL, cr.URL)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIndex(t *testing.T) {
|
||||
cr, err := LoadChartRepository(testRepository, testURL)
|
||||
if err != nil {
|
||||
t.Errorf("Problem loading chart repository from %s: %v", testRepository, err)
|
||||
}
|
||||
|
||||
err = cr.Index()
|
||||
if err != nil {
|
||||
t.Errorf("Error performing index: %v\n", err)
|
||||
}
|
||||
|
||||
tempIndexPath := filepath.Join(testRepository, indexPath)
|
||||
actual, err := LoadIndexFile(tempIndexPath)
|
||||
defer os.Remove(tempIndexPath) // clean up
|
||||
if err != nil {
|
||||
t.Errorf("Error loading index file %v", err)
|
||||
}
|
||||
verifyIndex(t, actual)
|
||||
|
||||
// Re-index and test again.
|
||||
err = cr.Index()
|
||||
if err != nil {
|
||||
t.Errorf("Error performing re-index: %s\n", err)
|
||||
}
|
||||
second, err := LoadIndexFile(tempIndexPath)
|
||||
if err != nil {
|
||||
t.Errorf("Error re-loading index file %v", err)
|
||||
}
|
||||
verifyIndex(t, second)
|
||||
}
|
||||
|
||||
func verifyIndex(t *testing.T, actual *IndexFile) {
|
||||
|
||||
var empty time.Time
|
||||
if actual.Generated == empty {
|
||||
t.Errorf("Generated should be greater than 0: %s", actual.Generated)
|
||||
}
|
||||
|
||||
if actual.APIVersion != APIVersionV1 {
|
||||
t.Error("Expected v1 API")
|
||||
}
|
||||
|
||||
entries := actual.Entries
|
||||
if numEntries := len(entries); numEntries != 2 {
|
||||
t.Errorf("Expected 2 charts to be listed in index file but got %v", numEntries)
|
||||
}
|
||||
|
||||
expects := map[string]ChartVersions{
|
||||
"frobnitz": {
|
||||
{
|
||||
Metadata: &chart.Metadata{
|
||||
Name: "frobnitz",
|
||||
Version: "1.2.3",
|
||||
},
|
||||
},
|
||||
},
|
||||
"sprocket": {
|
||||
{
|
||||
Metadata: &chart.Metadata{
|
||||
Name: "sprocket",
|
||||
Version: "1.2.0",
|
||||
},
|
||||
},
|
||||
{
|
||||
Metadata: &chart.Metadata{
|
||||
Name: "sprocket",
|
||||
Version: "1.1.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for name, versions := range expects {
|
||||
got, ok := entries[name]
|
||||
if !ok {
|
||||
t.Errorf("Could not find %q entry", name)
|
||||
continue
|
||||
}
|
||||
if len(versions) != len(got) {
|
||||
t.Errorf("Expected %d versions, got %d", len(versions), len(got))
|
||||
continue
|
||||
}
|
||||
for i, e := range versions {
|
||||
g := got[i]
|
||||
if e.Name != g.Name {
|
||||
t.Errorf("Expected %q, got %q", e.Name, g.Name)
|
||||
}
|
||||
if e.Version != g.Version {
|
||||
t.Errorf("Expected %q, got %q", e.Version, g.Version)
|
||||
}
|
||||
if len(g.Keywords) != 3 {
|
||||
t.Error("Expected 3 keyrwords.")
|
||||
}
|
||||
if len(g.Maintainers) != 2 {
|
||||
t.Error("Expected 2 maintainers.")
|
||||
}
|
||||
if g.Created == empty {
|
||||
t.Error("Expected created to be non-empty")
|
||||
}
|
||||
if g.Description == "" {
|
||||
t.Error("Expected description to be non-empty")
|
||||
}
|
||||
if g.Home == "" {
|
||||
t.Error("Expected home to be non-empty")
|
||||
}
|
||||
if g.Digest == "" {
|
||||
t.Error("Expected digest to be non-empty")
|
||||
}
|
||||
if len(g.URLs) != 1 {
|
||||
t.Error("Expected exactly 1 URL")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
|
||||
"github.com/ghodss/yaml"
|
||||
|
||||
"k8s.io/helm/cmd/helm/helmpath"
|
||||
"k8s.io/helm/pkg/repo"
|
||||
)
|
||||
|
||||
|
@ -69,7 +70,7 @@ func NewServer(docroot string) *Server {
|
|||
}
|
||||
srv.start()
|
||||
// Add the testing repository as the only repo.
|
||||
if err := setTestingRepository(docroot, "test", srv.URL()); err != nil {
|
||||
if err := setTestingRepository(helmpath.Home(docroot), "test", srv.URL()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return srv
|
||||
|
@ -113,7 +114,7 @@ func (s *Server) CopyCharts(origin string) ([]string, error) {
|
|||
// CreateIndex will read docroot and generate an index.yaml file.
|
||||
func (s *Server) CreateIndex() error {
|
||||
// generate the index
|
||||
index, err := repo.IndexDirectory(s.docroot, s.URL())
|
||||
index, err := repo.NewChartRepositoryIndexFromDirectory(s.docroot, s.URL())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -158,11 +159,13 @@ func (s *Server) LinkIndices() error {
|
|||
}
|
||||
|
||||
// setTestingRepository sets up a testing repository.yaml with only the given name/URL.
|
||||
func setTestingRepository(helmhome, name, url string) error {
|
||||
rf := repo.NewRepoFile()
|
||||
rf.Add(&repo.Entry{Name: name, URL: url})
|
||||
os.MkdirAll(filepath.Join(helmhome, "repository", name), 0755)
|
||||
dest := filepath.Join(helmhome, "repository/repositories.yaml")
|
||||
|
||||
return rf.WriteFile(dest, 0644)
|
||||
func setTestingRepository(home helmpath.Home, name, url string) error {
|
||||
r := repo.NewRepositoryFile()
|
||||
r.Add(&repo.ChartRepositoryConfig{
|
||||
Name: name,
|
||||
URL: url,
|
||||
Cache: home.CacheIndex(name),
|
||||
})
|
||||
os.MkdirAll(filepath.Join(home.Repository(), name), 0755)
|
||||
return r.WriteFile(home.RepositoryFile(), 0644)
|
||||
}
|
||||
|
|
|
@ -77,7 +77,7 @@ func TestServer(t *testing.T) {
|
|||
return
|
||||
}
|
||||
|
||||
m := repo.NewIndexFile()
|
||||
m := repo.NewChartRepositoryIndex()
|
||||
if err := yaml.Unmarshal(data, m); err != nil {
|
||||
t.Error(err)
|
||||
return
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 tlsutil
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// NewClientTLS returns tls.Config appropriate for client auth.
|
||||
func NewClientTLS(certFile, keyFile, caFile string) (*tls.Config, error) {
|
||||
cert, err := CertFromFilePair(certFile, keyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cp, err := CertPoolFromFile(caFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &tls.Config{
|
||||
Certificates: []tls.Certificate{*cert},
|
||||
RootCAs: cp,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CertPoolFromFile returns an x509.CertPool containing the certificates
|
||||
// in the given PEM-encoded file.
|
||||
// Returns an error if the file could not be read, a certificate could not
|
||||
// be parsed, or if the file does not contain any certificates
|
||||
func CertPoolFromFile(filename string) (*x509.CertPool, error) {
|
||||
b, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read CA file: %v", filename)
|
||||
}
|
||||
cp := x509.NewCertPool()
|
||||
if !cp.AppendCertsFromPEM(b) {
|
||||
return nil, fmt.Errorf("failed to append certificates from file: %s", filename)
|
||||
}
|
||||
return cp, nil
|
||||
}
|
||||
|
||||
// CertFromFilePair returns an tls.Certificate containing the
|
||||
// certificates public/private key pair from a pair of given PEM-encoded files.
|
||||
// Returns an error if the file could not be read, a certificate could not
|
||||
// be parsed, or if the file does not contain any certificates
|
||||
func CertFromFilePair(certFile, keyFile string) (*tls.Certificate, error) {
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't load key pair from cert %s and key %s", certFile, keyFile)
|
||||
}
|
||||
return &cert, err
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 urlutil
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// URLJoin joins a base URL to one or more path components.
|
||||
//
|
||||
// It's like filepath.Join for URLs. If the baseURL is pathish, this will still
|
||||
// perform a join.
|
||||
//
|
||||
// If the URL is unparsable, this returns an error.
|
||||
func URLJoin(baseURL string, paths ...string) (string, error) {
|
||||
u, err := url.Parse(baseURL)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// We want path instead of filepath because path always uses /.
|
||||
all := []string{u.Path}
|
||||
all = append(all, paths...)
|
||||
u.Path = path.Join(all...)
|
||||
return u.String(), nil
|
||||
}
|
||||
|
||||
// URLAreEqual normalizes two URLs and then compares for equality.
|
||||
//
|
||||
// TODO: This and the urlJoin functions should really be moved to a 'urlutil' package.
|
||||
func URLAreEqual(a, b string) bool {
|
||||
au, err := url.Parse(a)
|
||||
if err != nil {
|
||||
a = filepath.Clean(a)
|
||||
b = filepath.Clean(b)
|
||||
// If urls are paths, return true only if they are an exact match
|
||||
return a == b
|
||||
}
|
||||
bu, err := url.Parse(b)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, u := range []*url.URL{au, bu} {
|
||||
if u.Path == "" {
|
||||
u.Path = "/"
|
||||
}
|
||||
u.Path = filepath.Clean(u.Path)
|
||||
}
|
||||
return au.String() == bu.String()
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 urlutil
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestUrlJoin(t *testing.T) {
|
||||
tests := []struct {
|
||||
name, url, expect string
|
||||
paths []string
|
||||
}{
|
||||
{name: "URL, one path", url: "http://example.com", paths: []string{"hello"}, expect: "http://example.com/hello"},
|
||||
{name: "Long URL, one path", url: "http://example.com/but/first", paths: []string{"slurm"}, expect: "http://example.com/but/first/slurm"},
|
||||
{name: "URL, two paths", url: "http://example.com", paths: []string{"hello", "world"}, expect: "http://example.com/hello/world"},
|
||||
{name: "URL, no paths", url: "http://example.com", paths: []string{}, expect: "http://example.com"},
|
||||
{name: "basepath, two paths", url: "../example.com", paths: []string{"hello", "world"}, expect: "../example.com/hello/world"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
if got, err := URLJoin(tt.url, tt.paths...); err != nil {
|
||||
t.Errorf("%s: error %q", tt.name, err)
|
||||
} else if got != tt.expect {
|
||||
t.Errorf("%s: expected %q, got %q", tt.name, tt.expect, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestUrlAreEqual(t *testing.T) {
|
||||
for _, tt := range []struct {
|
||||
a, b string
|
||||
match bool
|
||||
}{
|
||||
{"http://example.com", "http://example.com", true},
|
||||
{"http://example.com", "http://another.example.com", false},
|
||||
{"https://example.com", "https://example.com", true},
|
||||
{"http://example.com/", "http://example.com", true},
|
||||
{"https://example.com", "http://example.com", false},
|
||||
{"http://example.com/foo", "http://example.com/foo/", true},
|
||||
{"http://example.com/foo//", "http://example.com/foo/", true},
|
||||
{"http://example.com/./foo/", "http://example.com/foo/", true},
|
||||
{"http://example.com/bar/../foo/", "http://example.com/foo/", true},
|
||||
{"/foo", "/foo", true},
|
||||
{"/foo", "/foo/", true},
|
||||
{"/foo/.", "/foo/", true},
|
||||
} {
|
||||
if tt.match != URLAreEqual(tt.a, tt.b) {
|
||||
t.Errorf("Expected %q==%q to be %t", tt.a, tt.b, tt.match)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue