mirror of https://github.com/helm/helm
parent
7fd7beba57
commit
94f78a5824
@ -1,79 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 registry
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/kubernetes/helm/pkg/common"
|
||||
)
|
||||
|
||||
// FilebasedCredentialProvider provides credentials for registries.
|
||||
type FilebasedCredentialProvider struct {
|
||||
// Actual backing store
|
||||
backingCredentialProvider common.CredentialProvider
|
||||
}
|
||||
|
||||
// NamedRegistryCredential associates a name with a RegistryCredential.
|
||||
type NamedRegistryCredential struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
common.RegistryCredential
|
||||
}
|
||||
|
||||
// NewFilebasedCredentialProvider creates a file based credential provider.
|
||||
func NewFilebasedCredentialProvider(filename string) (common.CredentialProvider, error) {
|
||||
icp := NewInmemCredentialProvider()
|
||||
c, err := readCredentialsFile(filename)
|
||||
if err != nil {
|
||||
return &FilebasedCredentialProvider{}, err
|
||||
}
|
||||
for _, nc := range c {
|
||||
log.Printf("Adding credential %s", nc.Name)
|
||||
icp.SetCredential(nc.Name, &nc.RegistryCredential)
|
||||
}
|
||||
|
||||
return &FilebasedCredentialProvider{icp}, nil
|
||||
}
|
||||
|
||||
func readCredentialsFile(filename string) ([]NamedRegistryCredential, error) {
|
||||
bytes, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return []NamedRegistryCredential{}, err
|
||||
}
|
||||
return parseCredentials(bytes)
|
||||
}
|
||||
|
||||
func parseCredentials(bytes []byte) ([]NamedRegistryCredential, error) {
|
||||
r := []NamedRegistryCredential{}
|
||||
if err := yaml.Unmarshal(bytes, &r); err != nil {
|
||||
return []NamedRegistryCredential{}, fmt.Errorf("cannot unmarshal credentials file (%#v)", err)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// GetCredential returns a credential by name.
|
||||
func (fcp *FilebasedCredentialProvider) GetCredential(name string) (*common.RegistryCredential, error) {
|
||||
return fcp.backingCredentialProvider.GetCredential(name)
|
||||
}
|
||||
|
||||
// SetCredential sets a credential by name.
|
||||
func (fcp *FilebasedCredentialProvider) SetCredential(name string, credential *common.RegistryCredential) error {
|
||||
return fmt.Errorf("SetCredential operation not supported with FilebasedCredentialProvider")
|
||||
}
|
@ -1,60 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 registry
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kubernetes/helm/pkg/common"
|
||||
)
|
||||
|
||||
var filename = "./test/test_credentials_file.yaml"
|
||||
|
||||
type filebasedTestCase struct {
|
||||
name string
|
||||
exp *common.RegistryCredential
|
||||
expErr error
|
||||
}
|
||||
|
||||
func TestNotExistFilebased(t *testing.T) {
|
||||
cp, err := NewFilebasedCredentialProvider(filename)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create a new FilebasedCredentialProvider %s : %v", filename, err)
|
||||
}
|
||||
tc := &testCase{"nonexistent", nil, createMissingError("nonexistent")}
|
||||
testGetCredential(t, cp, tc)
|
||||
}
|
||||
|
||||
func TestGetApiTokenFilebased(t *testing.T) {
|
||||
cp, err := NewFilebasedCredentialProvider(filename)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create a new FilebasedCredentialProvider %s : %v", filename, err)
|
||||
}
|
||||
tc := &testCase{"test1", &common.RegistryCredential{APIToken: "token"}, nil}
|
||||
testGetCredential(t, cp, tc)
|
||||
}
|
||||
|
||||
func TestSetAndGetBasicAuthFilebased(t *testing.T) {
|
||||
cp, err := NewFilebasedCredentialProvider(filename)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create a new FilebasedCredentialProvider %s : %v", filename, err)
|
||||
}
|
||||
tc := &testCase{"test2",
|
||||
&common.RegistryCredential{
|
||||
BasicAuth: common.BasicAuthCredential{Username: "user", Password: "password"}}, nil}
|
||||
testGetCredential(t, cp, tc)
|
||||
}
|
@ -1,167 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 registry
|
||||
|
||||
import (
|
||||
"github.com/kubernetes/helm/pkg/common"
|
||||
"github.com/kubernetes/helm/pkg/util"
|
||||
|
||||
// "golang.org/x/net/context"
|
||||
// "golang.org/x/oauth2/google"
|
||||
storage "google.golang.org/api/storage/v1"
|
||||
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
)
|
||||
|
||||
// GCSRegistry implements the ObbectStorageRegistry interface and implements a
|
||||
// Deployment Manager templates registry.
|
||||
//
|
||||
// A registry root must be a directory that contains all the available charts,
|
||||
// one or two files per template.
|
||||
// name-version.tgz
|
||||
// name-version.prov
|
||||
type GCSRegistry struct {
|
||||
name string
|
||||
shortURL string
|
||||
bucket string
|
||||
format common.RegistryFormat
|
||||
credentialName string
|
||||
|
||||
httpClient *http.Client
|
||||
service *storage.Service
|
||||
}
|
||||
|
||||
// RE for GCS storage
|
||||
|
||||
// ChartFormatMatcher matches the chart name format
|
||||
var ChartFormatMatcher = regexp.MustCompile("(.*)-(.*).tgz")
|
||||
|
||||
// URLFormatMatcher matches the GCS URL format (gs:).
|
||||
var URLFormatMatcher = regexp.MustCompile("gs://(.*)")
|
||||
|
||||
// NewGCSRegistry creates a GCS registry.
|
||||
func NewGCSRegistry(name, shortURL string, httpClient *http.Client, gcsService *storage.Service) (*GCSRegistry, error) {
|
||||
format := fmt.Sprintf("%s;%s", common.VersionedRegistry, common.OneLevelRegistry)
|
||||
trimmed := util.TrimURLScheme(shortURL)
|
||||
m := URLFormatMatcher.FindStringSubmatch(shortURL)
|
||||
if len(m) != 2 {
|
||||
return nil, fmt.Errorf("URL must be of the form gs://<bucket> was: %s", shortURL)
|
||||
}
|
||||
|
||||
return &GCSRegistry{
|
||||
name: name,
|
||||
shortURL: trimmed,
|
||||
format: common.RegistryFormat(format),
|
||||
httpClient: httpClient,
|
||||
service: gcsService,
|
||||
bucket: m[1],
|
||||
},
|
||||
nil
|
||||
}
|
||||
|
||||
// GetRegistryName returns the name of the registry.
|
||||
func (g GCSRegistry) GetRegistryName() string {
|
||||
return g.name
|
||||
}
|
||||
|
||||
// GetBucket returns the registry bucket.
|
||||
func (g GCSRegistry) GetBucket() string {
|
||||
return g.bucket
|
||||
}
|
||||
|
||||
// GetRegistryType returns the registry type.
|
||||
func (g GCSRegistry) GetRegistryType() common.RegistryType {
|
||||
return common.GCSRegistryType
|
||||
}
|
||||
|
||||
// ListTypes lists types in this registry whose string values conform to the
|
||||
// supplied regular expression, or all types, if the regular expression is nil.
|
||||
func (g GCSRegistry) ListTypes(regex *regexp.Regexp) ([]Type, error) {
|
||||
// List all files in the bucket/prefix that contain the
|
||||
types := []Type{}
|
||||
|
||||
// List all objects in a bucket using pagination
|
||||
pageToken := ""
|
||||
for {
|
||||
call := g.service.Objects.List(g.bucket)
|
||||
call.Delimiter("/")
|
||||
if pageToken != "" {
|
||||
call = call.PageToken(pageToken)
|
||||
}
|
||||
res, err := call.Do()
|
||||
if err != nil {
|
||||
return []Type{}, err
|
||||
}
|
||||
for _, object := range res.Items {
|
||||
// Charts should be named bucket/chart-X.Y.Z.tgz, so tease apart the version here
|
||||
m := ChartFormatMatcher.FindStringSubmatch(object.Name)
|
||||
if len(m) != 3 {
|
||||
continue
|
||||
}
|
||||
|
||||
t, err := NewType("", m[1], m[2])
|
||||
if err != nil {
|
||||
return []Type{}, fmt.Errorf("can't create a type type at path %#v", err)
|
||||
}
|
||||
types = append(types, t)
|
||||
}
|
||||
if pageToken = res.NextPageToken; pageToken == "" {
|
||||
break
|
||||
}
|
||||
}
|
||||
return types, nil
|
||||
}
|
||||
|
||||
// GetRegistryFormat returns the registry format.
|
||||
func (g GCSRegistry) GetRegistryFormat() common.RegistryFormat {
|
||||
return common.CollectionRegistry
|
||||
}
|
||||
|
||||
// GetRegistryShortURL returns the short URL for the registry.
|
||||
func (g GCSRegistry) GetRegistryShortURL() string {
|
||||
return g.shortURL
|
||||
}
|
||||
|
||||
// GetDownloadURLs fetches the download URLs for a given Chart
|
||||
func (g GCSRegistry) GetDownloadURLs(t Type) ([]*url.URL, error) {
|
||||
call := g.service.Objects.List(g.bucket)
|
||||
call.Delimiter("/")
|
||||
call.Prefix(t.String())
|
||||
res, err := call.Do()
|
||||
ret := []*url.URL{}
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
for _, object := range res.Items {
|
||||
log.Printf("Found: %s", object.Name)
|
||||
u, err := url.Parse(object.MediaLink)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse URL from %s: %s", object.MediaLink, err)
|
||||
}
|
||||
ret = append(ret, u)
|
||||
}
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// Do performs an HTTP operation on the receiver's httpClient.
|
||||
func (g GCSRegistry) Do(req *http.Request) (resp *http.Response, err error) {
|
||||
return g.httpClient.Do(req)
|
||||
}
|
@ -1,159 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 registry
|
||||
|
||||
import (
|
||||
"github.com/google/go-github/github"
|
||||
"github.com/kubernetes/helm/pkg/common"
|
||||
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GithubPackageRegistry implements the Registry interface that talks to github and
|
||||
// expects packages in helm format without versioning and no collection in the path.
|
||||
// Format of the directory for a type is like so:
|
||||
// package/
|
||||
// Chart.yaml
|
||||
// manifests/
|
||||
// foo.yaml
|
||||
// bar.yaml
|
||||
// ...
|
||||
type GithubPackageRegistry struct {
|
||||
githubRegistry
|
||||
}
|
||||
|
||||
// NewGithubPackageRegistry creates a GithubPackageRegistry.
|
||||
func NewGithubPackageRegistry(name, shortURL string, service GithubRepositoryService, httpClient *http.Client, client *github.Client) (*GithubPackageRegistry, error) {
|
||||
format := fmt.Sprintf("%s;%s", common.UnversionedRegistry, common.OneLevelRegistry)
|
||||
if service == nil {
|
||||
if client == nil {
|
||||
service = github.NewClient(nil).Repositories
|
||||
} else {
|
||||
service = client.Repositories
|
||||
}
|
||||
}
|
||||
|
||||
gr, err := newGithubRegistry(name, shortURL, common.RegistryFormat(format), httpClient, service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &GithubPackageRegistry{githubRegistry: *gr}, nil
|
||||
}
|
||||
|
||||
// ListTypes lists types in this registry whose string values conform to the
|
||||
// supplied regular expression, or all types, if the regular expression is nil.
|
||||
func (g GithubPackageRegistry) ListTypes(regex *regexp.Regexp) ([]Type, error) {
|
||||
// Just list all the types at the top level.
|
||||
types, err := g.getDirs("")
|
||||
if err != nil {
|
||||
log.Printf("Failed to list templates: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var retTypes []Type
|
||||
for _, t := range types {
|
||||
// Check to see if there's a Chart.yaml file in the directory
|
||||
_, dc, _, err := g.service.GetContents(g.owner, g.repository, t, nil)
|
||||
if err != nil {
|
||||
log.Printf("Failed to list package files at path: %s: %v", t, err)
|
||||
return nil, err
|
||||
}
|
||||
for _, f := range dc {
|
||||
if *f.Type == "file" && *f.Name == "Chart.yaml" {
|
||||
retTypes = append(retTypes, Type{Name: t})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if regex != nil {
|
||||
var matchTypes []Type
|
||||
for _, retType := range retTypes {
|
||||
if regex.MatchString(retType.String()) {
|
||||
matchTypes = append(matchTypes, retType)
|
||||
}
|
||||
}
|
||||
|
||||
return matchTypes, nil
|
||||
}
|
||||
|
||||
return retTypes, nil
|
||||
}
|
||||
|
||||
// GetDownloadURLs fetches the download URLs for a given Type.
|
||||
func (g GithubPackageRegistry) GetDownloadURLs(t Type) ([]*url.URL, error) {
|
||||
path, err := g.MakeRepositoryPath(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, dc, _, err := g.service.GetContents(g.owner, g.repository, path, nil)
|
||||
if err != nil {
|
||||
log.Printf("Failed to list package files at path: %s: %v", path, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
downloadURLs := []*url.URL{}
|
||||
for _, f := range dc {
|
||||
if *f.Type == "file" {
|
||||
if strings.HasSuffix(*f.Name, ".yaml") {
|
||||
u, err := url.Parse(*f.DownloadURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse URL from %s: %s", *f.DownloadURL, err)
|
||||
}
|
||||
|
||||
downloadURLs = append(downloadURLs, u)
|
||||
}
|
||||
}
|
||||
}
|
||||
return downloadURLs, nil
|
||||
}
|
||||
|
||||
func (g GithubPackageRegistry) getDirs(dir string) ([]string, error) {
|
||||
_, dc, _, err := g.service.GetContents(g.owner, g.repository, dir, nil)
|
||||
if err != nil {
|
||||
log.Printf("Failed to get contents at path: %s: %v", dir, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var dirs []string
|
||||
for _, entry := range dc {
|
||||
if *entry.Type == "dir" {
|
||||
dirs = append(dirs, *entry.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return dirs, nil
|
||||
}
|
||||
|
||||
// MakeRepositoryPath constructs a github path to a given type based on a repository, and type name.
|
||||
// The returned repository path will be of the form:
|
||||
// Type.Name/manifests
|
||||
func (g GithubPackageRegistry) MakeRepositoryPath(t Type) (string, error) {
|
||||
// Construct the return path
|
||||
return t.Name + "/manifests", nil
|
||||
}
|
||||
|
||||
// Do performs an HTTP operation on the receiver's httpClient.
|
||||
func (g GithubPackageRegistry) Do(req *http.Request) (resp *http.Response, err error) {
|
||||
return g.httpClient.Do(req)
|
||||
}
|
@ -1,132 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 registry
|
||||
|
||||
import (
|
||||
"github.com/google/go-github/github"
|
||||
"github.com/kubernetes/helm/pkg/common"
|
||||
"github.com/kubernetes/helm/pkg/util"
|
||||
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// githubRegistry is the base class for the Registry interface and talks to github. Actual implementations are
|
||||
// in GithubPackageRegistry and GithubTemplateRegistry.
|
||||
// The registry short URL and format determine how types are laid out in the
|
||||
// registry.
|
||||
type githubRegistry struct {
|
||||
name string
|
||||
shortURL string
|
||||
owner string
|
||||
repository string
|
||||
path string
|
||||
format common.RegistryFormat
|
||||
credentialName string
|
||||
service GithubRepositoryService
|
||||
httpClient *http.Client
|
||||
}
|
||||
|
||||
// GithubRepositoryService defines the interface that's defined in github.com/go-github/repos_contents.go GetContents method.
|
||||
type GithubRepositoryService interface {
|
||||
GetContents(
|
||||
owner, repo, path string,
|
||||
opt *github.RepositoryContentGetOptions,
|
||||
) (
|
||||
fileContent *github.RepositoryContent,
|
||||
directoryContent []*github.RepositoryContent,
|
||||
resp *github.Response,
|
||||
err error,
|
||||
)
|
||||
}
|
||||
|
||||
// newGithubRegistry creates a githubRegistry.
|
||||
func newGithubRegistry(name, shortURL string, format common.RegistryFormat, httpClient *http.Client, service GithubRepositoryService) (*githubRegistry, error) {
|
||||
trimmed := util.TrimURLScheme(shortURL)
|
||||
owner, repository, path, err := parseGithubShortURL(trimmed)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot create Github template registry %s: %s", name, err)
|
||||
}
|
||||
|
||||
return &githubRegistry{
|
||||
name: name,
|
||||
shortURL: trimmed,
|
||||
owner: owner,
|
||||
repository: repository,
|
||||
path: path,
|
||||
format: format,
|
||||
service: service,
|
||||
httpClient: httpClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseGithubShortURL(shortURL string) (string, string, string, error) {
|
||||
if !strings.HasPrefix(shortURL, "github.com/") {
|
||||
return "", "", "", fmt.Errorf("invalid Github short URL: %s", shortURL)
|
||||
}
|
||||
|
||||
tPath := strings.TrimPrefix(shortURL, "github.com/")
|
||||
parts := strings.Split(tPath, "/")
|
||||
|
||||
// Handle the case where there's no path after owner and repository.
|
||||
if len(parts) == 2 {
|
||||
return parts[0], parts[1], "", nil
|
||||
}
|
||||
|
||||
// Handle the case where there's a path after owner and repository.
|
||||
if len(parts) == 3 {
|
||||
return parts[0], parts[1], parts[2], nil
|
||||
}
|
||||
|
||||
return "", "", "", fmt.Errorf("invalid Github short URL: %s", shortURL)
|
||||
}
|
||||
|
||||
// GetRegistryName returns the name of this registry
|
||||
func (g githubRegistry) GetRegistryName() string {
|
||||
return g.name
|
||||
}
|
||||
|
||||
// GetRegistryType returns the type of this registry.
|
||||
func (g githubRegistry) GetRegistryType() common.RegistryType {
|
||||
return common.GithubRegistryType
|
||||
}
|
||||
|
||||
// GetRegistryShortURL returns the short URL for this registry.
|
||||
func (g githubRegistry) GetRegistryShortURL() string {
|
||||
return g.shortURL
|
||||
}
|
||||
|
||||
// GetRegistryFormat returns the format of this registry.
|
||||
func (g githubRegistry) GetRegistryFormat() common.RegistryFormat {
|
||||
return g.format
|
||||
}
|
||||
|
||||
// GetRegistryOwner returns the owner name for this registry
|
||||
func (g githubRegistry) GetRegistryOwner() string {
|
||||
return g.owner
|
||||
}
|
||||
|
||||
// GetRegistryRepository returns the repository name for this registry.
|
||||
func (g githubRegistry) GetRegistryRepository() string {
|
||||
return g.repository
|
||||
}
|
||||
|
||||
// GetRegistryName returns the name of this registry
|
||||
func (g githubRegistry) GetRegistryPath() string {
|
||||
return g.path
|
||||
}
|
@ -1,220 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 registry
|
||||
|
||||
import (
|
||||
"github.com/google/go-github/github"
|
||||
"github.com/kubernetes/helm/pkg/common"
|
||||
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GithubTemplateRegistry implements the Registry interface and implements a
|
||||
// Deployment Manager templates registry.
|
||||
// A registry root must be a directory that contains all the available templates,
|
||||
// one directory per template. Each template directory then contains version
|
||||
// directories, each of which in turn contains all the files necessary for that
|
||||
// version of the template.
|
||||
//
|
||||
// For example, a template registry containing two versions of redis
|
||||
// (implemented in jinja), and one version of replicatedservice (implemented
|
||||
// in python) would have a directory structure that looks something like this:
|
||||
// qualifier [optional] prefix to a virtual root within the repository.
|
||||
// /redis
|
||||
// /v1
|
||||
// redis.jinja
|
||||
// redis.jinja.schema
|
||||
// /v2
|
||||
// redis.jinja
|
||||
// redis.jinja.schema
|
||||
// /replicatedservice
|
||||
// /v1
|
||||
// replicatedservice.python
|
||||
// replicatedservice.python.schema
|
||||
type GithubTemplateRegistry struct {
|
||||
githubRegistry
|
||||
}
|
||||
|
||||
// NewGithubTemplateRegistry creates a GithubTemplateRegistry.
|
||||
func NewGithubTemplateRegistry(name, shortURL string, service GithubRepositoryService, httpClient *http.Client, client *github.Client) (*GithubTemplateRegistry, error) {
|
||||
format := fmt.Sprintf("%s;%s", common.VersionedRegistry, common.CollectionRegistry)
|
||||
if service == nil {
|
||||
service = client.Repositories
|
||||
}
|
||||
|
||||
gr, err := newGithubRegistry(name, shortURL, common.RegistryFormat(format), httpClient, service)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &GithubTemplateRegistry{githubRegistry: *gr}, nil
|
||||
}
|
||||
|
||||
// ListTypes lists types in this registry whose string values conform to the
|
||||
// supplied regular expression, or all types, if the regular expression is nil.
|
||||
func (g GithubTemplateRegistry) ListTypes(regex *regexp.Regexp) ([]Type, error) {
|
||||
// First list all the collections at the top level.
|
||||
collections, err := g.getDirs("")
|
||||
if err != nil {
|
||||
log.Printf("cannot list qualifiers: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var retTypes []Type
|
||||
for _, c := range collections {
|
||||
// Then we need to fetch the versions (directories for this type)
|
||||
types, err := g.getDirs(c)
|
||||
if err != nil {
|
||||
log.Printf("cannot fetch types for collection: %s", c)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, t := range types {
|
||||
path := c + "/" + t
|
||||
// Then we need to fetch the versions (directories for this type)
|
||||
versions, err := g.getDirs(path)
|
||||
if err != nil {
|
||||
log.Printf("cannot fetch versions at path %s", path)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, v := range versions {
|
||||
tt, err := NewType(c, t, v)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("malformed type at path %s", path)
|
||||
}
|
||||
|
||||
retTypes = append(retTypes, tt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if regex != nil {
|
||||
var matchTypes []Type
|
||||
for _, retType := range retTypes {
|
||||
if regex.MatchString(retType.String()) {
|
||||
matchTypes = append(matchTypes, retType)
|
||||
}
|
||||
}
|
||||
|
||||
return matchTypes, nil
|
||||
}
|
||||
|
||||
return retTypes, nil
|
||||
}
|
||||
|
||||
// GetDownloadURLs fetches the download URLs for a given Type and checks for existence of a schema file.
|
||||
func (g GithubTemplateRegistry) GetDownloadURLs(t Type) ([]*url.URL, error) {
|
||||
path, err := g.MakeRepositoryPath(t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, dc, _, err := g.service.GetContents(g.owner, g.repository, path, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot list versions at path %s: %v", path, err)
|
||||
}
|
||||
|
||||
var downloadURL, typeName, schemaName string
|
||||
for _, f := range dc {
|
||||
if *f.Type == "file" {
|
||||
if *f.Name == t.Name+".jinja" || *f.Name == t.Name+".py" {
|
||||
typeName = *f.Name
|
||||
downloadURL = *f.DownloadURL
|
||||
}
|
||||
if *f.Name == t.Name+".jinja.schema" || *f.Name == t.Name+".py.schema" {
|
||||
schemaName = *f.Name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if downloadURL == "" {
|
||||
return nil, fmt.Errorf("cannot find type %s", t.String())
|
||||
}
|
||||
|
||||
if schemaName != typeName+".schema" {
|
||||
return nil, fmt.Errorf("cannot find schema for %s, expected %s", t.String(), typeName+".schema")
|
||||
}
|
||||
|
||||
result, err := url.Parse(downloadURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot parse URL from %s: %s", downloadURL, err)
|
||||
}
|
||||
|
||||
return []*url.URL{result}, nil
|
||||
}
|
||||
|
||||
func (g GithubTemplateRegistry) getDirs(dir string) ([]string, error) {
|
||||
var path = g.path
|
||||
if dir != "" {
|
||||
path = g.path + "/" + dir
|
||||
}
|
||||
|
||||
_, dc, _, err := g.service.GetContents(g.owner, g.repository, path, nil)
|
||||
if err != nil {
|
||||
log.Printf("Failed to get contents at path: %s: %v", path, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var dirs []string
|
||||
for _, entry := range dc {
|
||||
if *entry.Type == "dir" {
|
||||
dirs = append(dirs, *entry.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return dirs, nil
|
||||
}
|
||||
|
||||
func (g GithubTemplateRegistry) mapCollection(collection string) (string, error) {
|
||||
if strings.ContainsAny(collection, "/") {
|
||||
return "", fmt.Errorf("collection must not contain slashes, got %s", collection)
|
||||
}
|
||||
// TODO(vaikas): Implement lookup from the root metadata file to map collection to a path
|
||||
return collection, nil
|
||||
}
|
||||
|
||||
// MakeRepositoryPath constructs a github path to a given type based on a repository, and type name and version.
|
||||
// The returned repository path will be of the form:
|
||||
// [GithubTemplateRegistry.path/][Type.Collection]/Type.Name/Type.Version
|
||||
// Type.Collection will be mapped using mapCollection in the future, for now it's a straight
|
||||
// 1:1 mapping (if given)
|
||||
func (g GithubTemplateRegistry) MakeRepositoryPath(t Type) (string, error) {
|
||||
// First map the collection
|
||||
collection, err := g.mapCollection(t.Collection)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Construct the return path
|
||||
p := ""
|
||||
if len(g.path) > 0 {
|
||||
p += g.path + "/"
|
||||
}
|
||||
if len(collection) > 0 {
|
||||
p += collection + "/"
|
||||
}
|
||||
return p + t.Name + "/" + t.GetVersion(), nil
|
||||
}
|
||||
|
||||
// Do performs an HTTP operation on the receiver's httpClient.
|
||||
func (g GithubTemplateRegistry) Do(req *http.Request) (resp *http.Response, err error) {
|
||||
return g.httpClient.Do(req)
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 registry
|
||||
|
||||
import (
|
||||
"github.com/kubernetes/helm/pkg/common"
|
||||
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// InmemCredentialProvider is a memory based credential provider.
|
||||
type InmemCredentialProvider struct {
|
||||
credentials map[string]*common.RegistryCredential
|
||||
}
|
||||
|
||||
// NewInmemCredentialProvider creates a new memory based credential provider.
|
||||
func NewInmemCredentialProvider() common.CredentialProvider {
|
||||
return &InmemCredentialProvider{credentials: make(map[string]*common.RegistryCredential)}
|
||||
}
|
||||
|
||||
// GetCredential returns a credential by name.
|
||||
func (fcp *InmemCredentialProvider) GetCredential(name string) (*common.RegistryCredential, error) {
|
||||
if val, ok := fcp.credentials[name]; ok {
|
||||
return val, nil
|
||||
}
|
||||
return nil, fmt.Errorf("no such credential : %s", name)
|
||||
}
|
||||
|
||||
// SetCredential sets a credential by name.
|
||||
func (fcp *InmemCredentialProvider) SetCredential(name string, credential *common.RegistryCredential) error {
|
||||
fcp.credentials[name] = &common.RegistryCredential{APIToken: credential.APIToken, BasicAuth: credential.BasicAuth, ServiceAccount: credential.ServiceAccount}
|
||||
return nil
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 registry
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/kubernetes/helm/pkg/common"
|
||||
)
|
||||
|
||||
type testCase struct {
|
||||
name string
|
||||
exp *common.RegistryCredential
|
||||
expErr error
|
||||
}
|
||||
|
||||
func createMissingError(name string) error {
|
||||
return fmt.Errorf("no such credential : %s", name)
|
||||
}
|
||||
|
||||
func testGetCredential(t *testing.T, cp common.CredentialProvider, tc *testCase) {
|
||||
actual, actualErr := cp.GetCredential(tc.name)
|
||||
if !reflect.DeepEqual(actual, tc.exp) {
|
||||
t.Fatalf("failed on: %s : expected %#v but got %#v", tc.name, tc.exp, actual)
|
||||
}
|
||||
if !reflect.DeepEqual(actualErr, tc.expErr) {
|
||||
t.Fatalf("failed on: %s : expected error %#v but got %#v", tc.name, tc.expErr, actualErr)
|
||||
}
|
||||
}
|
||||
|
||||
func verifySetAndGetCredential(t *testing.T, cp common.CredentialProvider, tc *testCase) {
|
||||
err := cp.SetCredential(tc.name, tc.exp)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to SetCredential on %s : %v", tc.name, err)
|
||||
}
|
||||
testGetCredential(t, cp, tc)
|
||||
}
|
||||
|
||||
func TestNotExist(t *testing.T) {
|
||||
cp := NewInmemCredentialProvider()
|
||||
tc := &testCase{"nonexistent", nil, createMissingError("nonexistent")}
|
||||
testGetCredential(t, cp, tc)
|
||||
}
|
||||
|
||||
func TestSetAndGetApiToken(t *testing.T) {
|
||||
cp := NewInmemCredentialProvider()
|
||||
tc := &testCase{"testcredential", &common.RegistryCredential{APIToken: "some token here"}, nil}
|
||||
verifySetAndGetCredential(t, cp, tc)
|
||||
}
|
||||
|
||||
func TestSetAndGetBasicAuth(t *testing.T) {
|
||||
cp := NewInmemCredentialProvider()
|
||||
tc := &testCase{"testcredential",
|
||||
&common.RegistryCredential{
|
||||
BasicAuth: common.BasicAuthCredential{Username: "user", Password: "pass"}}, nil}
|
||||
verifySetAndGetCredential(t, cp, tc)
|
||||
}
|
@ -1,127 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 registry
|
||||
|
||||
import (
|
||||
"github.com/kubernetes/helm/pkg/common"
|
||||
"github.com/kubernetes/helm/pkg/util"
|
||||
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type inmemRegistryService struct {
|
||||
registries map[string]*common.Registry
|
||||
}
|
||||
|
||||
// NewInmemRegistryService returns a new memory based registry service.
|
||||
func NewInmemRegistryService() common.RegistryService {
|
||||
rs := &inmemRegistryService{
|
||||
registries: make(map[string]*common.Registry),
|
||||
}
|
||||
|
||||
pFormat := fmt.Sprintf("%s;%s", common.UnversionedRegistry, common.OneLevelRegistry)
|
||||
rs.Create(&common.Registry{
|
||||
Name: "charts",
|
||||
Type: common.GithubRegistryType,
|
||||
URL: "github.com/helm/charts",
|
||||
Format: common.RegistryFormat(pFormat),
|
||||
CredentialName: "default",
|
||||
})
|
||||
|
||||
tFormat := fmt.Sprintf("%s;%s", common.VersionedRegistry, common.CollectionRegistry)
|
||||
rs.Create(&common.Registry{
|
||||
Name: "application-dm-templates",
|
||||
Type: common.GithubRegistryType,
|
||||
URL: "github.com/kubernetes/application-dm-templates",
|
||||
Format: common.RegistryFormat(tFormat),
|
||||
CredentialName: "default",
|
||||
})
|
||||
|
||||
return rs
|
||||
}
|
||||
|
||||
// List returns the list of known registries.
|
||||
func (rs *inmemRegistryService) List() ([]*common.Registry, error) {
|
||||
ret := []*common.Registry{}
|
||||
for _, r := range rs.registries {
|
||||
ret = append(ret, r)
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// Create creates a registry.
|
||||
func (rs *inmemRegistryService) Create(registry *common.Registry) error {
|
||||
rs.registries[registry.Name] = registry
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get returns a registry with a given name.
|
||||
func (rs *inmemRegistryService) Get(name string) (*common.Registry, error) {
|
||||
r, ok := rs.registries[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Failed to find registry named %s", name)
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// GetRegistry returns a registry with a given name.
|
||||
func (rs *inmemRegistryService) GetRegistry(name string) (*common.Registry, error) {
|
||||
r, ok := rs.registries[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Failed to find registry named %s", name)
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// Delete deletes the registry with a given name.
|
||||
func (rs *inmemRegistryService) Delete(name string) error {
|
||||
_, ok := rs.registries[name]
|
||||
if !ok {
|
||||
return fmt.Errorf("Failed to find registry named %s", name)
|
||||
}
|
||||
|
||||
delete(rs.registries, name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetByURL returns a registry that handles the types for a given URL.
|
||||
func (rs *inmemRegistryService) GetByURL(URL string) (*common.Registry, error) {
|
||||
trimmed := util.TrimURLScheme(URL)
|
||||
for _, r := range rs.registries {
|
||||
if strings.HasPrefix(trimmed, util.TrimURLScheme(r.URL)) {
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("Failed to find registry for url: %s", URL)
|
||||
}
|
||||
|
||||
// GetRegistryByURL returns a registry that handles the types for a given URL.
|
||||
func (rs *inmemRegistryService) GetRegistryByURL(URL string) (*common.Registry, error) {
|
||||
trimmed := util.TrimURLScheme(URL)
|
||||
for _, r := range rs.registries {
|
||||
if strings.HasPrefix(trimmed, util.TrimURLScheme(r.URL)) {
|
||||
return r, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("Failed to find registry for url: %s", URL)
|
||||
}
|
@ -1,154 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 registry
|
||||
|
||||
import (
|
||||
"github.com/kubernetes/helm/pkg/common"
|
||||
"github.com/kubernetes/helm/pkg/util"
|
||||
|
||||
"fmt"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Registry abstracts a registry that holds charts, which can be
|
||||
// used in a Deployment Manager configuration. There can be multiple
|
||||
// registry implementations.
|
||||
type Registry interface {
|
||||
// Also handles http.Client.Do method for authenticated File accesses
|
||||
util.HTTPDoer
|
||||
|
||||
// GetRegistryName returns the name of this registry
|
||||
GetRegistryName() string
|
||||
// GetRegistryType returns the type of this registry.
|
||||
GetRegistryType() common.RegistryType
|
||||
// GetRegistryShortURL returns the short URL for this registry.
|
||||
GetRegistryShortURL() string
|
||||
// GetRegistryFormat returns the format of this registry.
|
||||
GetRegistryFormat() common.RegistryFormat
|
||||
|
||||
// ListTypes lists types in this registry whose string values conform to the
|
||||
// supplied regular expression, or all types, if the regular expression is nil.
|
||||
ListTypes(regex *regexp.Regexp) ([]Type, error)
|
||||
// GetDownloadURLs returns the URLs required to download the type contents.
|
||||
GetDownloadURLs(t Type) ([]*url.URL, error)
|
||||
}
|
||||
|
||||
// GithubRegistry abstracts a registry that resides in a Github repository.
|
||||
type GithubRegistry interface {
|
||||
Registry // A GithubRegistry is a Registry.
|
||||
// GetRegistryOwner returns the owner name for this registry
|
||||
GetRegistryOwner() string
|
||||
// GetRegistryRepository returns the repository name for this registry.
|
||||
GetRegistryRepository() string
|
||||
// GetRegistryPath returns the path to the registry in the repository.
|
||||
GetRegistryPath() string
|
||||
}
|
||||
|
||||
// ObjectStorageRegistry abstracts a registry that resides in an Object Storage, for
|
||||
// example Google Cloud Storage or AWS S3, etc.
|
||||
type ObjectStorageRegistry interface {
|
||||
Registry // An ObjectStorageRegistry is a Registry.
|
||||
GetBucket() string
|
||||
}
|
||||
|
||||
// Type describes a type stored in a registry.
|
||||
type Type struct {
|
||||
Collection string
|
||||
Name string
|
||||
Version SemVer
|
||||
}
|
||||
|
||||
// NewType initializes a type
|
||||
func NewType(collection, name, version string) (Type, error) {
|
||||
result := Type{Collection: collection, Name: name}
|
||||
err := result.SetVersion(version)
|
||||
return result, err
|
||||
}
|
||||
|
||||
// NewTypeOrDie initializes a type and panics if initialization fails
|
||||
func NewTypeOrDie(collection, name, version string) Type {
|
||||
result, err := NewType(collection, name, version)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Type conforms to the Stringer interface.
|
||||
func (t Type) String() string {
|
||||
var result string
|
||||
if t.Collection != "" {
|
||||
result = t.Collection + "/"
|
||||
}
|
||||
|
||||
result = result + t.Name
|
||||
version := t.GetVersion()
|
||||
if version != "" && version != "v0" {
|
||||
result = result + ":" + version
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// GetVersion returns the type version with the letter "v" prepended.
|
||||
func (t Type) GetVersion() string {
|
||||
var result string
|
||||
version := t.Version.String()
|
||||
if version != "0" {
|
||||
result = "v" + version
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// SetVersion strips the letter "v" from version, if present,
|
||||
// and sets the the version of the type to the result.
|
||||
func (t *Type) SetVersion(version string) error {
|
||||
vstring := strings.TrimPrefix(version, "v")
|
||||
s, err := ParseSemVer(vstring)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.Version = s
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseType takes a registry type string and parses it into a *registry.Type.
|
||||
// TODO: needs better validation that this is actually a registry type.
|
||||
func ParseType(ts string) (Type, error) {
|
||||
tt := Type{}
|
||||
tList := strings.Split(ts, ":")
|
||||
if len(tList) == 2 {
|
||||
if err := tt.SetVersion(tList[1]); err != nil {
|
||||
return tt, fmt.Errorf("malformed type string: %s", ts)
|
||||
}
|
||||
}
|
||||
|
||||
cList := strings.Split(tList[0], "/")
|
||||
if len(cList) == 1 {
|
||||
tt.Name = tList[0]
|
||||
} else {
|
||||
tt.Collection = cList[0]
|
||||
tt.Name = cList[1]
|
||||
}
|
||||
|
||||
return tt, nil
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
/*
|
||||
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 registry
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTypeConversion(t *testing.T) {
|
||||
// TODO: Are there some real-world examples we want to validate here?
|
||||
tests := map[string]Type{
|
||||
"foo": NewTypeOrDie("", "foo", ""),
|
||||
"foo:v1": NewTypeOrDie("", "foo", "v1"),
|
||||
"github.com/foo": NewTypeOrDie("github.com", "foo", ""),
|
||||
"github.com/foo:v1.2.3": NewTypeOrDie("github.com", "foo", "v1.2.3"),
|
||||
}
|
||||
|
||||
for in, expect := range tests {
|
||||
out, err := ParseType(in)
|
||||
if err != nil {
|
||||
t.Errorf("Error parsing type string %s: %s", in, err)
|
||||
}
|
||||
|
||||
if out.Name != expect.Name {
|
||||
t.Errorf("Expected name to be %q, got %q", expect.Name, out.Name)
|
||||
}
|
||||
|
||||
if out.GetVersion() != expect.GetVersion() {
|
||||
t.Errorf("Expected version to be %q, got %q", expect.GetVersion(), out.GetVersion())
|
||||
}
|
||||
|
||||
if out.Collection != expect.Collection {
|
||||
t.Errorf("Expected collection to be %q, got %q", expect.Collection, out.Collection)
|
||||
}
|
||||
|
||||
svalue := out.String()
|
||||
if svalue != in {
|
||||
t.Errorf("Expected string value to be %q, got %q", in, svalue)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,421 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 registry
|
||||
|
||||
import (
|
||||
"github.com/google/go-github/github"
|
||||
"github.com/kubernetes/helm/pkg/common"
|
||||
"github.com/kubernetes/helm/pkg/util"
|
||||
"golang.org/x/oauth2"
|
||||
"golang.org/x/oauth2/google"
|
||||
storage "google.golang.org/api/storage/v1"
|
||||
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// RegistryProvider is a factory for Registry instances.
|
||||
type RegistryProvider interface {
|
||||
GetRegistryByShortURL(URL string) (Registry, error)
|
||||
GetRegistryByName(registryName string) (Registry, error)
|
||||
}
|
||||
|
||||
type registryProvider struct {
|
||||
sync.RWMutex
|
||||
rs common.RegistryService
|
||||
grp GithubRegistryProvider
|
||||
gcsrp GCSRegistryProvider
|
||||
cp common.CredentialProvider
|
||||
registries map[string]Registry
|
||||
}
|
||||
|
||||
// NewDefaultRegistryProvider creates a default registry provider with the supplied credential.
|
||||
func NewDefaultRegistryProvider(cp common.CredentialProvider, rs common.RegistryService) RegistryProvider {
|
||||
return NewRegistryProvider(rs, NewGithubRegistryProvider(cp), NewGCSRegistryProvider(cp), cp)
|
||||
}
|
||||
|
||||
// NewRegistryProvider creates a new registry provider using the supplied arguments.
|
||||
func NewRegistryProvider(rs common.RegistryService, grp GithubRegistryProvider, gcsrp GCSRegistryProvider, cp common.CredentialProvider) RegistryProvider {
|
||||
if rs == nil {
|
||||
rs = NewInmemRegistryService()
|
||||
}
|
||||
|
||||
if cp == nil {
|
||||
cp = NewInmemCredentialProvider()
|
||||
}
|
||||
|
||||
if grp == nil {
|
||||
grp = NewGithubRegistryProvider(cp)
|
||||
}
|
||||
|
||||
if gcsrp == nil {
|
||||
gcsrp = NewGCSRegistryProvider(cp)
|
||||
}
|
||||
|
||||
registries := make(map[string]Registry)
|
||||
rp := ®istryProvider{rs: rs, grp: grp, gcsrp: gcsrp, cp: cp, registries: registries}
|
||||
return rp
|
||||
}
|
||||
|
||||
func (rp *registryProvider) getRegistry(cr common.Registry) (Registry, error) {
|
||||
switch cr.Type {
|
||||
case common.GithubRegistryType:
|
||||
return rp.grp.GetGithubRegistry(cr)
|
||||
case common.GCSRegistryType:
|
||||
log.Printf("Creating a bigstore client using %#v", rp.gcsrp)
|
||||
return rp.gcsrp.GetGCSRegistry(cr)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown registry type: %s", cr.Type)
|
||||
}
|
||||
}
|
||||
|
||||
// GetRegistryByShortURL returns the registry identified by a short URL.
|
||||
func (rp *registryProvider) GetRegistryByShortURL(URL string) (Registry, error) {
|
||||
rp.RLock()
|
||||
defer rp.RUnlock()
|
||||
|
||||
result := rp.findRegistryByShortURL(URL)
|
||||
if result == nil {
|
||||
cr, err := rp.rs.GetByURL(URL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r, err := rp.getRegistry(*cr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rp.registries[r.GetRegistryName()] = r
|
||||
result = r
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// findRegistryByShortURL trims the scheme from both the supplied URL
|
||||
// and the short URL returned by GetRegistryShortURL.
|
||||
func (rp *registryProvider) findRegistryByShortURL(URL string) Registry {
|
||||
trimmed := util.TrimURLScheme(URL)
|
||||
for _, r := range rp.registries {
|
||||
if strings.HasPrefix(trimmed, util.TrimURLScheme(r.GetRegistryShortURL())) {
|
||||
return r
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRegistryByName returns a registry by name.
|
||||
func (rp *registryProvider) GetRegistryByName(registryName string) (Registry, error) {
|
||||
rp.RLock()
|
||||
defer rp.RUnlock()
|
||||
|
||||
cr, err := rp.rs.Get(registryName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r, err := rp.getRegistry(*cr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rp.registries[r.GetRegistryName()] = r
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// ParseRegistryFormat creates a map from a registry format string.
|
||||
func ParseRegistryFormat(rf common.RegistryFormat) map[common.RegistryFormat]bool {
|
||||
split := strings.Split(string(rf), ";")
|
||||
var result = map[common.RegistryFormat]bool{}
|
||||
for _, format := range split {
|
||||
result[common.RegistryFormat(format)] = true
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// GithubRegistryProvider is a factory for GithubRegistry instances.
|
||||
type GithubRegistryProvider interface {
|
||||
GetGithubRegistry(cr common.Registry) (GithubRegistry, error)
|
||||
}
|
||||
|
||||
type githubRegistryProvider struct {
|
||||
cp common.CredentialProvider
|
||||
}
|
||||
|
||||
// NewGithubRegistryProvider creates a GithubRegistryProvider.
|
||||
func NewGithubRegistryProvider(cp common.CredentialProvider) GithubRegistryProvider {
|
||||
if cp == nil {
|
||||
// TODO: replace this panic with an error return.
|
||||
panic(fmt.Errorf("no credential provider"))
|
||||
}
|
||||
return &githubRegistryProvider{cp: cp}
|
||||
}
|
||||
|
||||
func (grp githubRegistryProvider) createGithubClient(credentialName string) (*http.Client, *github.Client, error) {
|
||||
if credentialName == "" {
|
||||
return http.DefaultClient, github.NewClient(nil), nil
|
||||
}
|
||||
c, err := grp.cp.GetCredential(credentialName)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Failed to fetch credential %s: %v", credentialName, err)
|
||||
log.Print("Trying to use unauthenticated client")
|
||||
return http.DefaultClient, github.NewClient(nil), nil
|
||||
}
|
||||
|
||||
if c != nil {
|
||||
if c.APIToken != "" {
|
||||
ts := oauth2.StaticTokenSource(
|
||||
&oauth2.Token{AccessToken: string(c.APIToken)},
|
||||
)
|
||||
tc := oauth2.NewClient(oauth2.NoContext, ts)
|
||||
return tc, github.NewClient(tc), nil
|
||||
}
|
||||
if c.BasicAuth.Username != "" && c.BasicAuth.Password != "" {
|
||||
tp := github.BasicAuthTransport{
|
||||
Username: c.BasicAuth.Username,
|
||||
Password: c.BasicAuth.Password,
|
||||
}
|
||||
return tp.Client(), github.NewClient(tp.Client()), nil
|
||||
}
|
||||
|
||||
}
|
||||
return nil, nil, fmt.Errorf("No suitable credential found for %s", credentialName)
|
||||
|
||||
}
|
||||
|
||||
// GetGithubRegistry returns a new GithubRegistry. If there's a credential that is specified, will try
|
||||
// to fetch it and use it, and if there's no credential found, will fall back to unauthenticated client.
|
||||
func (grp githubRegistryProvider) GetGithubRegistry(cr common.Registry) (GithubRegistry, error) {
|
||||
// If there's a credential that we need to use, fetch it and create a client for it.
|
||||
httpClient, client, err := grp.createGithubClient(cr.CredentialName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fMap := ParseRegistryFormat(cr.Format)
|
||||
if fMap[common.UnversionedRegistry] && fMap[common.OneLevelRegistry] {
|
||||
return NewGithubPackageRegistry(cr.Name, cr.URL, nil, httpClient, client)
|
||||
}
|
||||
|
||||
if fMap[common.VersionedRegistry] && fMap[common.CollectionRegistry] {
|
||||
return NewGithubTemplateRegistry(cr.Name, cr.URL, nil, httpClient, client)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unknown registry format: %s", cr.Format)
|
||||
}
|
||||
|
||||
// GCSRegistryProvider is a factory for GCS Registry instances.
|
||||
type GCSRegistryProvider interface {
|
||||
GetGCSRegistry(cr common.Registry) (ObjectStorageRegistry, error)
|
||||
}
|
||||
|
||||
type gcsRegistryProvider struct {
|
||||
cp common.CredentialProvider
|
||||
}
|
||||
|
||||
// NewGCSRegistryProvider creates a GCSRegistryProvider.
|
||||
func NewGCSRegistryProvider(cp common.CredentialProvider) GCSRegistryProvider {
|
||||
if cp == nil {
|
||||
// TODO: replace this panic with an error return.
|
||||
panic(fmt.Errorf("no credential provider"))
|
||||
}
|
||||
return &gcsRegistryProvider{cp: cp}
|
||||
}
|
||||
|
||||
// GetGCSRegistry returns a new Google Cloud Storage . If there's a credential that is specified, will try
|
||||
// to fetch it and use it, and if there's no credential found, will fall back to unauthenticated client.
|
||||
func (gcsrp gcsRegistryProvider) GetGCSRegistry(cr common.Registry) (ObjectStorageRegistry, error) {
|
||||
client, err := gcsrp.createGCSClient(cr.CredentialName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
service, err := storage.New(client)
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to create storage service: %v", err)
|
||||
}
|
||||
return NewGCSRegistry(cr.Name, cr.URL, client, service)
|
||||
}
|
||||
|
||||
func (gcsrp gcsRegistryProvider) createGCSClient(credentialName string) (*http.Client, error) {
|
||||
if credentialName == "" {
|
||||
return http.DefaultClient, nil
|
||||
}
|
||||
|
||||
c, err := gcsrp.cp.GetCredential(credentialName)
|
||||
if err != nil {
|
||||
log.Printf("Failed to fetch credential %s: %v", credentialName, err)
|
||||
log.Print("Trying to use unauthenticated client")
|
||||
return http.DefaultClient, nil
|
||||
}
|
||||
|
||||
config, err := google.JWTConfigFromJSON([]byte(c.ServiceAccount), storage.DevstorageReadOnlyScope)
|
||||
|
||||
if err != nil {
|
||||
log.Fatalf("Unable to parse client secret file to config: %v", err)
|
||||
}
|
||||
return config.Client(oauth2.NoContext), nil
|
||||
}
|
||||
|
||||
// TemplateRegistryMatcher is an RE for a registry type that does support versions and has collections.
|
||||
var TemplateRegistryMatcher = regexp.MustCompile("github.com/(.*)/(.*)/(.*)/(.*):(.*)")
|
||||
|
||||
// PackageRegistryMatcher is an RE for a registry type that does not support versions and does not have collections.
|
||||
var PackageRegistryMatcher = regexp.MustCompile("github.com/(.*)/(.*)/(.*)")
|
||||
|
||||
// GCSRegistryMatcher is an RE for GCS storage
|
||||
var GCSRegistryMatcher = regexp.MustCompile("gs://(.*)/(.*)")
|
||||
|
||||
// IsGithubShortType returns whether a given type is a type description in a short format to a github repository type.
|
||||
// For now, this means using github types:
|
||||
// github.com/owner/repo/qualifier/type:version
|
||||
// for example:
|
||||
// github.com/kubernetes/application-dm-templates/storage/redis:v1
|
||||
func IsGithubShortType(t string) bool {
|
||||
return TemplateRegistryMatcher.MatchString(t)
|
||||
}
|
||||
|
||||
// IsGithubShortPackageType returns whether a given type is a type description in a short format to a github
|
||||
// package repository type.
|
||||
// For now, this means using github types:
|
||||
// github.com/owner/repo/type
|
||||
// for example:
|
||||
// github.com/helm/charts/cassandra
|
||||
func IsGithubShortPackageType(t string) bool {
|
||||
return PackageRegistryMatcher.MatchString(t)
|
||||
}
|
||||
|
||||
// IsGCSShortType returns whether a given type is a type description in a short format to GCS
|
||||
func IsGCSShortType(t string) bool {
|
||||
return strings.HasPrefix(t, "gs://")
|
||||
}
|
||||
|
||||
// GetDownloadURLs checks a type to see if it is either a short git hub url or a fully specified URL
|
||||
// and returns the URLs that should be used to fetch it. If the url is not fetchable (primitive type
|
||||
// for example), it returns an empty slice.
|
||||
func GetDownloadURLs(rp RegistryProvider, t string) ([]string, Registry, error) {
|
||||
if IsGithubShortType(t) {
|
||||
return ShortTypeToDownloadURLs(rp, t)
|
||||
} else if IsGithubShortPackageType(t) {
|
||||
return ShortTypeToPackageDownloadURLs(rp, t)
|
||||
} else if IsGCSShortType(t) {
|
||||
return ShortTypeToGCSDownloadUrls(rp, t)
|
||||
} else if util.IsHTTPURL(t) {
|
||||
result, err := url.Parse(t)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("cannot parse download URL %s: %s", t, err)
|
||||
}
|
||||
|
||||
return []string{result.String()}, nil, nil
|
||||
}
|
||||
|
||||
return []string{}, nil, nil
|
||||
}
|
||||
|
||||
// ShortTypeToDownloadURLs converts a github URL into downloadable URL from github.
|
||||
// Input must be of the type and is assumed to have been validated before this call:
|
||||
// github.com/owner/repo/qualifier/type:version
|
||||
// for example:
|
||||
// github.com/kubernetes/application-dm-templates/storage/redis:v1
|
||||
func ShortTypeToDownloadURLs(rp RegistryProvider, t string) ([]string, Registry, error) {
|
||||
m := TemplateRegistryMatcher.FindStringSubmatch(t)
|
||||
if len(m) != 6 {
|
||||
return nil, nil, fmt.Errorf("cannot parse short github url: %s", t)
|
||||
}
|
||||
|
||||
r, err := rp.GetRegistryByShortURL(t)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
if r == nil {
|
||||
return nil, nil, fmt.Errorf("cannot get github registry for %s", t)
|
||||
}
|
||||
|
||||
tt, err := NewType(m[3], m[4], m[5])
|
||||
if err != nil {
|
||||
return nil, r, err
|
||||
}
|
||||
|
||||
urls, err := r.GetDownloadURLs(tt)
|
||||
if err != nil {
|
||||
return nil, r, err
|
||||
}
|
||||
|
||||
return util.ConvertURLsToStrings(urls), r, err
|
||||
}
|
||||
|
||||
// ShortTypeToPackageDownloadURLs converts a github URL into downloadable URLs from github.
|
||||
// Input must be of the type and is assumed to have been validated before this call:
|
||||
// github.com/owner/repo/type
|
||||
// for example:
|
||||
// github.com/helm/charts/cassandra
|
||||
func ShortTypeToPackageDownloadURLs(rp RegistryProvider, t string) ([]string, Registry, error) {
|
||||
m := PackageRegistryMatcher.FindStringSubmatch(t)
|
||||
if len(m) != 4 {
|
||||
return nil, nil, fmt.Errorf("Failed to parse short github url: %s", t)
|
||||
}
|
||||
|
||||
r, err := rp.GetRegistryByShortURL(t)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
tt, err := NewType("", m[3], "")
|
||||
if err != nil {
|
||||
return nil, r, err
|
||||
}
|
||||
|
||||
urls, err := r.GetDownloadURLs(tt)
|
||||
if err != nil {
|
||||
return nil, r, err
|
||||
}
|
||||
|
||||
return util.ConvertURLsToStrings(urls), r, err
|
||||
}
|
||||
|
||||
// ShortTypeToGCSDownloadUrls returns the download URLs for a short type name.
|
||||
func ShortTypeToGCSDownloadUrls(rp RegistryProvider, t string) ([]string, Registry, error) {
|
||||
m := GCSRegistryMatcher.FindStringSubmatch(t)
|
||||
if len(m) != 3 {
|
||||
return nil, nil, fmt.Errorf("Failed to parse short gs url: %s", t)
|
||||
}
|
||||
|
||||
r, err := rp.GetRegistryByShortURL(t)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
tt, err := NewType(m[1], m[2], "")
|
||||
if err != nil {
|
||||
return nil, r, err
|
||||
}
|
||||
|
||||
urls, err := r.GetDownloadURLs(tt)
|
||||
if err != nil {
|
||||
return nil, r, err
|
||||
}
|
||||
return util.ConvertURLsToStrings(urls), r, err
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 registry
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testURLConversionDriver(rp RegistryProvider, tests map[string]TestURLAndError, t *testing.T) {
|
||||
for in, expected := range tests {
|
||||
// TODO(vaikas): Test to make sure it's the right registry.
|
||||
actual, _, err := GetDownloadURLs(rp, in)
|
||||
if err != expected.Err {
|
||||
t.Fatalf("failed on: %s : expected error %v but got %v", in, expected.Err, err)
|
||||
}
|
||||
|
||||
if actual[0] != expected.URL {
|
||||
t.Fatalf("failed on: %s : expected %s but got %v", in, expected.URL, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestShortGithubURLTemplateMapping(t *testing.T) {
|
||||
githubURLMaps := map[Type]TestURLAndError{
|
||||
NewTypeOrDie("common", "replicatedservice", "v1"): {"https://raw.githubusercontent.com/kubernetes/application-dm-templates/master/common/replicatedservice/v1/replicatedservice.py", nil},
|
||||
NewTypeOrDie("storage", "redis", "v1"): {"https://raw.githubusercontent.com/kubernetes/application-dm-templates/master/storage/redis/v1/redis.jinja", nil},
|
||||
}
|
||||
|
||||
tests := map[string]TestURLAndError{
|
||||
"github.com/kubernetes/application-dm-templates/common/replicatedservice:v1": {"https://raw.githubusercontent.com/kubernetes/application-dm-templates/master/common/replicatedservice/v1/replicatedservice.py", nil},
|
||||
"github.com/kubernetes/application-dm-templates/storage/redis:v1": {"https://raw.githubusercontent.com/kubernetes/application-dm-templates/master/storage/redis/v1/redis.jinja", nil},
|
||||
}
|
||||
|
||||
grp := NewTestGithubRegistryProvider("github.com/kubernetes/application-dm-templates", githubURLMaps)
|
||||
// TODO(vaikas): XXXX FIXME Add gcsrp
|
||||
testURLConversionDriver(NewRegistryProvider(nil, grp, nil, NewInmemCredentialProvider()), tests, t)
|
||||
}
|
||||
|
||||
func TestShortGithubURLPackageMapping(t *testing.T) {
|
||||
githubURLMaps := map[Type]TestURLAndError{
|
||||
NewTypeOrDie("", "mongodb", ""): {"https://raw.githubusercontent.com/helm/charts/master/mongodb/manifests/mongodb.yaml", nil},
|
||||
NewTypeOrDie("", "redis", ""): {"https://raw.githubusercontent.com/helm/charts/master/redis/manifests/redis.yaml", nil},
|
||||
}
|
||||
|
||||
tests := map[string]TestURLAndError{
|
||||
"github.com/helm/charts/mongodb": {"https://raw.githubusercontent.com/helm/charts/master/mongodb/manifests/mongodb.yaml", nil},
|
||||
"github.com/helm/charts/redis": {"https://raw.githubusercontent.com/helm/charts/master/redis/manifests/redis.yaml", nil},
|
||||
}
|
||||
|
||||
grp := NewTestGithubRegistryProvider("github.com/helm/charts", githubURLMaps)
|
||||
// TODO(vaikas): XXXX FIXME Add gcsrp
|
||||
testURLConversionDriver(NewRegistryProvider(nil, grp, nil, NewInmemCredentialProvider()), tests, t)
|
||||
}
|
@ -1,128 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 registry
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/kubernetes/helm/pkg/common"
|
||||
"github.com/kubernetes/helm/pkg/util"
|
||||
)
|
||||
|
||||
var (
|
||||
kubePath = flag.String("kubectl", "./kubectl", "The path to the kubectl binary.")
|
||||
kubeService = flag.String("service", "", "The DNS name of the kubernetes service.")
|
||||
kubeServer = flag.String("server", "", "The IP address and optional port of the kubernetes master.")
|
||||
kubeInsecure = flag.Bool("insecure-skip-tls-verify", false, "Do not check the server's certificate for validity.")
|
||||
kubeConfig = flag.String("config", "", "Path to a kubeconfig file.")
|
||||
kubeCertAuth = flag.String("certificate-authority", "", "Path to a file for the certificate authority.")
|
||||
kubeClientCert = flag.String("client-certificate", "", "Path to a client certificate file.")
|
||||
kubeClientKey = flag.String("client-key", "", "Path to a client key file.")
|
||||
kubeToken = flag.String("token", "", "A service account token.")
|
||||
kubeUsername = flag.String("username", "", "The username to use for basic auth.")
|
||||
kubePassword = flag.String("password", "", "The password to use for basic auth.")
|
||||
)
|
||||
|
||||
var kubernetesConfig *util.KubernetesConfig
|
||||
|
||||
const secretType = "Secret"
|
||||
|
||||
// SecretsCredentialProvider provides credentials for registries from Kubernertes secrets.
|
||||
type SecretsCredentialProvider struct {
|
||||
// Actual object that talks to secrets service.
|
||||
k util.Kubernetes
|
||||
}
|
||||
|
||||
// NewSecretsCredentialProvider creates a new secrets credential provider.
|
||||
func NewSecretsCredentialProvider() common.CredentialProvider {
|
||||
kubernetesConfig := &util.KubernetesConfig{
|
||||
KubePath: *kubePath,
|
||||
KubeService: *kubeService,
|
||||
KubeServer: *kubeServer,
|
||||
KubeInsecure: *kubeInsecure,
|
||||
KubeConfig: *kubeConfig,
|
||||
KubeCertAuth: *kubeCertAuth,
|
||||
KubeClientCert: *kubeClientCert,
|
||||
KubeClientKey: *kubeClientKey,
|
||||
KubeToken: *kubeToken,
|
||||
KubeUsername: *kubeUsername,
|
||||
KubePassword: *kubePassword,
|
||||
}
|
||||
return &SecretsCredentialProvider{util.NewKubernetesKubectl(kubernetesConfig)}
|
||||
}
|
||||
|
||||
func parseCredential(credential string) (*common.RegistryCredential, error) {
|
||||
var c util.KubernetesSecret
|
||||
if err := json.Unmarshal([]byte(credential), &c); err != nil {
|
||||
return nil, fmt.Errorf("cannot unmarshal credential '%s' (%#v)", credential, err)
|
||||
}
|
||||
|
||||
d, err := base64.StdEncoding.DecodeString(c.Data["credential"])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot unmarshal credential '%s' (%#v)", c, err)
|
||||
}
|
||||
// And then finally unmarshal it from yaml to common.RegistryCredential
|
||||
r := &common.RegistryCredential{}
|
||||
if err := yaml.Unmarshal(d, &r); err != nil {
|
||||
return nil, fmt.Errorf("cannot unmarshal credential %s (%#v)", c, err)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// GetCredential returns a credential by name.
|
||||
func (scp *SecretsCredentialProvider) GetCredential(name string) (*common.RegistryCredential, error) {
|
||||
o, err := scp.k.Get(name, secretType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return parseCredential(o)
|
||||
}
|
||||
|
||||
// SetCredential sets a credential by name.
|
||||
func (scp *SecretsCredentialProvider) SetCredential(name string, credential *common.RegistryCredential) error {
|
||||
// Marshal the credential & base64 encode it.
|
||||
b, err := yaml.Marshal(credential)
|
||||
if err != nil {
|
||||
log.Printf("yaml marshal failed for credential: %s: %v", name, err)
|
||||
return err
|
||||
}
|
||||
enc := base64.StdEncoding.EncodeToString(b)
|
||||
|
||||
// Then create a kubernetes object out of it
|
||||
metadata := make(map[string]string)
|
||||
metadata["name"] = name
|
||||
data := make(map[string]string)
|
||||
data["credential"] = enc
|
||||
obj := &util.KubernetesSecret{
|
||||
Kind: secretType,
|
||||
APIVersion: "v1",
|
||||
Metadata: metadata,
|
||||
Data: data,
|
||||
}
|
||||
ko, err := yaml.Marshal(obj)
|
||||
if err != nil {
|
||||
log.Printf("yaml marshal failed for kubernetes object: %s: %v", name, err)
|
||||
return err
|
||||
}
|
||||
_, err = scp.k.Create(string(ko))
|
||||
return err
|
||||
}
|
@ -1,92 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 registry
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// SemVer holds a semantic version as defined by semver.io.
|
||||
type SemVer struct {
|
||||
Major uint
|
||||
Minor uint
|
||||
Patch uint
|
||||
}
|
||||
|
||||
// ParseSemVer parses a semantic version string.
|
||||
func ParseSemVer(version string) (SemVer, error) {
|
||||
var err error
|
||||
major, minor, patch := uint64(0), uint64(0), uint64(0)
|
||||
if len(version) > 0 {
|
||||
parts := strings.SplitN(version, ".", 3)
|
||||
if len(parts) > 3 {
|
||||
return SemVer{}, fmt.Errorf("invalid semantic version: %s", version)
|
||||
}
|
||||
|
||||
if len(parts) < 1 {
|
||||
return SemVer{}, fmt.Errorf("invalid semantic version: %s", version)
|
||||
}
|
||||
|
||||
if parts[0] != "0" {
|
||||
major, err = strconv.ParseUint(parts[0], 10, 0)
|
||||
if err != nil {
|
||||
return SemVer{}, fmt.Errorf("invalid semantic version: %s", version)
|
||||
}
|
||||
}
|
||||
|
||||
if len(parts) > 1 {
|
||||
if parts[1] != "0" {
|
||||
minor, err = strconv.ParseUint(parts[1], 10, 0)
|
||||
if err != nil {
|
||||
return SemVer{}, fmt.Errorf("invalid semantic version: %s", version)
|
||||
}
|
||||
}
|
||||
|
||||
if len(parts) > 2 {
|
||||
if parts[2] != "0" {
|
||||
patch, err = strconv.ParseUint(parts[2], 10, 0)
|
||||
if err != nil {
|
||||
return SemVer{}, fmt.Errorf("invalid semantic version: %s", version)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SemVer{Major: uint(major), Minor: uint(minor), Patch: uint(patch)}, nil
|
||||
}
|
||||
|
||||
// IsZero returns true if the semantic version is zero.
|
||||
func (s SemVer) IsZero() bool {
|
||||
return s.Major == 0 && s.Minor == 0 && s.Patch == 0
|
||||
}
|
||||
|
||||
// SemVer conforms to the Stringer interface.
|
||||
func (s SemVer) String() string {
|
||||
result := strconv.Itoa(int(s.Major))
|
||||
if s.Minor != 0 || s.Patch != 0 {
|
||||
result = result + "." + strconv.Itoa(int(s.Minor))
|
||||
}
|
||||
|
||||
if s.Patch != 0 {
|
||||
result = result + "." + strconv.Itoa(int(s.Patch))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 registry
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParseInvalidVersionFails(t *testing.T) {
|
||||
for _, test := range []string{
|
||||
".",
|
||||
"..",
|
||||
"...",
|
||||
"1.2.3.4",
|
||||
"notAUnit",
|
||||
"1.notAUint",
|
||||
"1.1.notAUint",
|
||||
"-1",
|
||||
"1.-1",
|
||||
"1.1.-1",
|
||||
"1,1",
|
||||
"1.1,1",
|
||||
} {
|
||||
_, err := ParseSemVer(test)
|
||||
if err == nil {
|
||||
t.Errorf("Invalid version parsed successfully: %s\n", test)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseValidVersionSucceeds(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
String string
|
||||
Version SemVer
|
||||
}{
|
||||
{"", SemVer{0, 0, 0}},
|
||||
{"0", SemVer{0, 0, 0}},
|
||||
{"0.0", SemVer{0, 0, 0}},
|
||||
{"0.0.0", SemVer{0, 0, 0}},
|
||||
{"1", SemVer{1, 0, 0}},
|
||||
{"1.0", SemVer{1, 0, 0}},
|
||||
{"1.0.0", SemVer{1, 0, 0}},
|
||||
{"1.1", SemVer{1, 1, 0}},
|
||||
{"1.1.0", SemVer{1, 1, 0}},
|
||||
{"1.1.1", SemVer{1, 1, 1}},
|
||||
} {
|
||||
result, err := ParseSemVer(test.String)
|
||||
if err != nil {
|
||||
t.Errorf("Valid version %s did not parse successfully\n", test.String)
|
||||
}
|
||||
|
||||
if result.Major != test.Version.Major ||
|
||||
result.Minor != test.Version.Minor ||
|
||||
result.Patch != test.Version.Patch {
|
||||
t.Errorf("Valid version %s did not parse correctly: %s\n", test.String, test.Version)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertSemVerToStringSucceeds(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
String string
|
||||
Version SemVer
|
||||
}{
|
||||
{"0", SemVer{0, 0, 0}},
|
||||
{"0.1", SemVer{0, 1, 0}},
|
||||
{"0.0.1", SemVer{0, 0, 1}},
|
||||
{"1", SemVer{1, 0, 0}},
|
||||
{"1.1", SemVer{1, 1, 0}},
|
||||
{"1.1.1", SemVer{1, 1, 1}},
|
||||
} {
|
||||
result := test.Version.String()
|
||||
if result != test.String {
|
||||
t.Errorf("Valid version %s did not format correctly: %s\n", test.Version, test.String)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
- name: test1
|
||||
apitoken: token
|
||||
- name: test2
|
||||
basicauth:
|
||||
username: user
|
||||
password: password
|
@ -1,152 +0,0 @@
|
||||
/*
|
||||
Copyright 2015 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 registry
|
||||
|
||||
// TODO(jackgr): Mock github repository service to test package and template registry implementations.
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/kubernetes/helm/pkg/common"
|
||||
"github.com/kubernetes/helm/pkg/util"
|
||||
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TestURLAndError associates a URL with an error string for testing.
|
||||
type TestURLAndError struct {
|
||||
URL string
|
||||
Err error
|
||||
}
|
||||
|
||||
// DownloadResponse holds a mock http response for testing.
|
||||
type DownloadResponse struct {
|
||||
Err error
|
||||
Code int
|
||||
Body string
|
||||
}
|
||||
|
||||
type testGithubRegistryProvider struct {
|
||||
shortURL string
|
||||
responses map[Type]TestURLAndError
|
||||
downloadResponses map[string]DownloadResponse
|
||||
}
|
||||
|
||||
type testGithubRegistry struct {
|
||||
githubRegistry
|
||||
responses map[Type]TestURLAndError
|
||||
downloadResponses map[string]DownloadResponse
|
||||
}
|
||||
|
||||
// NewTestGithubRegistryProvider creates a test github registry provider.
|
||||
func NewTestGithubRegistryProvider(shortURL string, responses map[Type]TestURLAndError) GithubRegistryProvider {
|
||||
return testGithubRegistryProvider{
|
||||
shortURL: util.TrimURLScheme(shortURL),
|
||||
responses: responses,
|
||||
}
|
||||
}
|
||||
|
||||
// NewTestGithubRegistryProviderWithDownloads creates a test github registry provider with download responses.
|
||||
func NewTestGithubRegistryProviderWithDownloads(shortURL string, responses map[Type]TestURLAndError, downloadResponses map[string]DownloadResponse) GithubRegistryProvider {
|
||||
return testGithubRegistryProvider{
|
||||
shortURL: util.TrimURLScheme(shortURL),
|
||||
responses: responses,
|
||||
downloadResponses: downloadResponses,
|
||||
}
|
||||
}
|
||||
|
||||
// GetGithubRegistry is a mock implementation of the same method on GithubRegistryProvider.
|
||||
func (tgrp testGithubRegistryProvider) GetGithubRegistry(cr common.Registry) (GithubRegistry, error) {
|
||||
trimmed := util.TrimURLScheme(cr.URL)
|
||||
if strings.HasPrefix(trimmed, tgrp.shortURL) {
|
||||
ghr, err := newGithubRegistry(cr.Name, trimmed, cr.Format, http.DefaultClient, nil)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot create a github registry: %s", err))
|
||||
}
|
||||
|
||||
return &testGithubRegistry{
|
||||
githubRegistry: *ghr,
|
||||
responses: tgrp.responses,
|
||||
downloadResponses: tgrp.downloadResponses,
|
||||
}, nil
|
||||
}
|
||||
|
||||
panic(fmt.Errorf("unknown registry: %v", cr))
|
||||
}
|
||||
|
||||
// ListTypes is a mock implementation of the same method on GithubRegistryProvider.
|
||||
func (tgr testGithubRegistry) ListTypes(regex *regexp.Regexp) ([]Type, error) {
|
||||
panic(fmt.Errorf("ListTypes should not be called in the test"))
|
||||
}
|
||||
|
||||
// GetDownloadURLs is a mock implementation of the same method on GithubRegistryProvider.
|
||||
func (tgr testGithubRegistry) GetDownloadURLs(t Type) ([]*url.URL, error) {
|
||||
result := tgr.responses[t]
|
||||
URL, err := url.Parse(result.URL)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return []*url.URL{URL}, result.Err
|
||||
}
|
||||
|
||||
// Do is a mock implementation of the same method on GithubRegistryProvider.
|
||||
func (tgr testGithubRegistry) Do(req *http.Request) (resp *http.Response, err error) {
|
||||
response := tgr.downloadResponses[req.URL.String()]
|
||||
return &http.Response{StatusCode: response.Code, Body: ioutil.NopCloser(bytes.NewBufferString(response.Body))}, response.Err
|
||||
}
|
||||
|
||||
type testGCSRegistryProvider struct {
|
||||
shortURL string
|
||||
responses map[Type]TestURLAndError
|
||||
}
|
||||
|
||||
type testGCSRegistry struct {
|
||||
GCSRegistry
|
||||
responses map[Type]TestURLAndError
|
||||
}
|
||||
|
||||
// NewTestGCSRegistryProvider creates a test GCS registry provider.
|
||||
func NewTestGCSRegistryProvider(shortURL string, responses map[Type]TestURLAndError) GCSRegistryProvider {
|
||||
return testGCSRegistryProvider{
|
||||
shortURL: util.TrimURLScheme(shortURL),
|
||||
responses: responses,
|
||||
}
|
||||
}
|
||||
|
||||
// GetDownloadURLs is a mock implementation of the same method on GCSRegistryProvider.
|
||||
func (tgrp testGCSRegistryProvider) GetGCSRegistry(cr common.Registry) (ObjectStorageRegistry, error) {
|
||||
trimmed := util.TrimURLScheme(cr.URL)
|
||||
if strings.HasPrefix(trimmed, tgrp.shortURL) {
|
||||
gcsr, err := NewGCSRegistry(cr.Name, trimmed, http.DefaultClient, nil)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("cannot create gcs registry: %s", err))
|
||||
}
|
||||
|
||||
return &testGCSRegistry{
|
||||
GCSRegistry: *gcsr,
|
||||
responses: tgrp.responses,
|
||||
}, nil
|
||||
}
|
||||
|
||||
panic(fmt.Errorf("unknown registry: %v", cr))
|
||||
}
|
Loading…
Reference in new issue