Commit 8ec10d6e authored by Jack Greenfield's avatar Jack Greenfield

Merge pull request #443 from jackgr/fix-expansion

Some nice cleanups now that pkg/registry is no longer needed
parents 51bbfafc 2cb3bc76
......@@ -20,6 +20,7 @@ import (
"github.com/ghodss/yaml"
"github.com/kubernetes/helm/pkg/common"
"github.com/kubernetes/helm/pkg/repo"
"github.com/kubernetes/helm/pkg/util"
"archive/tar"
......@@ -90,15 +91,15 @@ var usage = func() {
os.Exit(0)
}
func getCredential() *common.RegistryCredential {
func getCredential() *repo.Credential {
*apitoken = strings.TrimSpace(*apitoken)
if *apitoken == "" {
*apitoken = strings.TrimSpace(os.Getenv("GITHUB_API_TOKEN"))
}
if *apitoken != "" {
return &common.RegistryCredential{
APIToken: common.APITokenCredential(*apitoken),
return &repo.Credential{
APIToken: repo.APITokenCredential(*apitoken),
}
}
......@@ -113,8 +114,8 @@ func getCredential() *common.RegistryCredential {
*password = strings.TrimSpace(os.Getenv("GITHUB_PASSWORD"))
}
return &common.RegistryCredential{
BasicAuth: common.BasicAuthCredential{
return &repo.Credential{
BasicAuth: repo.BasicAuthCredential{
Username: *username,
Password: *password,
},
......@@ -126,8 +127,8 @@ func getCredential() *common.RegistryCredential {
if err != nil {
log.Fatalf("Unable to read service account file: %v", err)
}
return &common.RegistryCredential{
ServiceAccount: common.JWTTokenCredential(string(b)),
return &repo.Credential{
ServiceAccount: repo.JWTTokenCredential(string(b)),
}
}
return nil
......
......@@ -427,20 +427,20 @@ func (c *Chart) loadMember(filename string) (*Member, error) {
return result, nil
}
// ChartContent is abstraction for the contents of a chart.
type ChartContent struct {
// Content is abstraction for the contents of a chart.
type Content struct {
Chartfile *Chartfile `json:"chartfile"`
Members []*Member `json:"members"`
}
// loadContent loads contents of a chart directory into ChartContent
func (c *Chart) loadContent() (*ChartContent, error) {
// LoadContent loads contents of a chart directory into Content
func (c *Chart) LoadContent() (*Content, error) {
ms, err := c.loadDirectory(c.Dir())
if err != nil {
return nil, err
}
cc := &ChartContent{
cc := &Content{
Chartfile: c.Chartfile(),
Members: ms,
}
......
......@@ -21,17 +21,6 @@ import (
"time"
)
// SchemaImport represents an import as declared in a schema file.
type SchemaImport struct {
Path string `json:"path"`
Name string `json:"name"`
}
// Schema is a partial DM schema. We only need access to the imports object at this level.
type Schema struct {
Imports []SchemaImport `json:"imports"`
}
// Deployment defines a deployment that describes
// the creation, modification and/or deletion of a set of resources.
type Deployment struct {
......@@ -76,17 +65,6 @@ func (s DeploymentStatus) String() string {
return string(s)
}
// LayoutResource defines the structure of resources in the manifest layout.
type LayoutResource struct {
Resource
Layout
}
// Layout defines the structure of a layout as returned from expansion.
type Layout struct {
Resources []*LayoutResource `json:"resources,omitempty"`
}
// Manifest contains the input configuration for a deployment, the fully
// expanded configuration, and the layout structure of the manifest.
//
......@@ -103,21 +81,16 @@ type CreateDeploymentRequest struct {
ChartInvocation *Resource `json:"chart_invocation"`
}
// ExpansionRequest defines the API to expander.
type ExpansionRequest struct {
ChartInvocation *Resource `json:"chart_invocation"`
Chart *chart.ChartContent `json:"chart"`
}
// ExpansionResponse defines the API to expander.
type ExpansionResponse struct {
Resources []interface{} `json:"resources"`
// ChartInstance defines the metadata for an instantiation of a chart.
type ChartInstance struct {
Name string `json:"name"` // instance name
Type string `json:"type"` // instance type
Deployment string `json:"deployment"` // deployment name
Manifest string `json:"manifest"` // manifest name
Path string `json:"path"` // JSON path within manifest
}
// Expander abstracts interactions with the expander and deployer services.
type Expander interface {
ExpandChart(request *ExpansionRequest) (*ExpansionResponse, error)
}
// TODO: Remove the following section when the refactoring of templates is complete.
// Template describes a set of resources to be deployed.
// Manager expands a Template into a Configuration, which
......@@ -135,6 +108,44 @@ type ImportFile struct {
Content string `json:"content"`
}
// SchemaImport represents an import as declared in a schema file.
type SchemaImport struct {
Path string `json:"path"`
Name string `json:"name"`
}
// Schema is a partial DM schema. We only need access to the imports object at this level.
type Schema struct {
Imports []SchemaImport `json:"imports"`
}
// LayoutResource defines the structure of resources in the manifest layout.
type LayoutResource struct {
Resource
Layout
}
// Layout defines the structure of a layout as returned from expansion.
type Layout struct {
Resources []*LayoutResource `json:"resources,omitempty"`
}
// ExpansionRequest defines the API to expander.
type ExpansionRequest struct {
ChartInvocation *Resource `json:"chart_invocation"`
Chart *chart.Content `json:"chart"`
}
// ExpansionResponse defines the API to expander.
type ExpansionResponse struct {
Resources []interface{} `json:"resources"`
}
// Expander abstracts interactions with the expander and deployer services.
type Expander interface {
ExpandChart(request *ExpansionRequest) (*ExpansionResponse, error)
}
// Configuration describes a set of resources in a form
// that can be instantiated.
type Configuration struct {
......@@ -169,102 +180,3 @@ type Resource struct {
Properties map[string]interface{} `json:"properties,omitempty"`
State *ResourceState `json:"state,omitempty"`
}
// ChartInstance defines the metadata for an instantiation of a template type
// in a deployment.
type ChartInstance struct {
Name string `json:"name"` // instance name
Type string `json:"type"` // instance type
Deployment string `json:"deployment"` // deployment name
Manifest string `json:"manifest"` // manifest name
Path string `json:"path"` // JSON path within manifest
}
// TODO: Remove the remainder of this file when the refactoring of pkg/registry is complete.
// BasicAuthCredential holds a username and password.
type BasicAuthCredential struct {
Username string `json:"username"`
Password string `json:"password"`
}
// APITokenCredential defines an API token.
type APITokenCredential string
// JWTTokenCredential defines a JWT token.
type JWTTokenCredential string
// RegistryCredential holds a credential used to access a registry.
type RegistryCredential struct {
APIToken APITokenCredential `json:"apitoken,omitempty"`
BasicAuth BasicAuthCredential `json:"basicauth,omitempty"`
ServiceAccount JWTTokenCredential `json:"serviceaccount,omitempty"`
}
// Registry describes a template registry
type Registry struct {
Name string `json:"name,omitempty"` // Friendly name for the registry
Type RegistryType `json:"type,omitempty"` // Technology implementing the registry
URL string `json:"url,omitempty"` // URL to the root of the registry
Format RegistryFormat `json:"format,omitempty"` // Format of the registry
CredentialName string `json:"credentialname,omitempty"` // Name of the credential to use
}
// RegistryType defines the technology that implements a registry.
type RegistryType string
// Constants that identify the supported registry types.
const (
GithubRegistryType RegistryType = "github"
GCSRegistryType RegistryType = "gcs"
)
// RegistryFormat is a semi-colon delimited string that describes the format
// of a registry.
type RegistryFormat string
const (
// Versioning.
// VersionedRegistry identifies a versioned registry, where types appear under versions.
VersionedRegistry RegistryFormat = "versioned"
// UnversionedRegistry identifies an unversioned registry, where types appear under their names.
UnversionedRegistry RegistryFormat = "unversioned"
// Organization.
// CollectionRegistry identfies a collection registry, where types are grouped into collections.
CollectionRegistry RegistryFormat = "collection"
// OneLevelRegistry identifies a one level registry, where all types appear at the top level.
OneLevelRegistry RegistryFormat = "onelevel"
)
// RegistryService maintains a set of registries that defines the scope of all
// registry based operations, such as search and type resolution.
type RegistryService interface {
// List all the registries
List() ([]*Registry, error)
// Create a new registry
Create(registry *Registry) error
// Get a registry
Get(name string) (*Registry, error)
// Get a registry with credential.
GetRegistry(name string) (*Registry, error)
// Delete a registry
Delete(name string) error
// Find a registry that backs the given URL
GetByURL(URL string) (*Registry, error)
// GetRegistryByURL returns a registry that handles the types for a given URL.
GetRegistryByURL(URL string) (*Registry, error)
}
// CredentialProvider provides credentials for registries.
type CredentialProvider interface {
// Set the credential for a registry.
// May not be supported by some registry services.
SetCredential(name string, credential *RegistryCredential) error
// GetCredential returns the specified credential or nil if there's no credential.
// Error is non-nil if fetching the credential failed.
GetCredential(name string) (*RegistryCredential, error)
}
/*
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 expander
import (
"github.com/kubernetes/helm/pkg/chart"
)
// SchemaImport represents an import as declared in a schema file.
type SchemaImport struct {
Path string `json:"path"`
Name string `json:"name"`
}
// Schema is a partial DM schema. We only need access to the imports object at this level.
type Schema struct {
Imports []SchemaImport `json:"imports"`
}
// LayoutResource defines the structure of resources in the manifest layout.
type LayoutResource struct {
Resource
Layout
}
// Layout defines the structure of a layout as returned from expansion.
type Layout struct {
Resources []*LayoutResource `json:"resources,omitempty"`
}
// ExpansionRequest defines the API to expander.
type ExpansionRequest struct {
ChartInvocation *Resource `json:"chart_invocation"`
Chart *chart.Content `json:"chart"`
}
// ExpansionResponse defines the API to expander.
type ExpansionResponse struct {
Resources []interface{} `json:"resources"`
}
// Expander abstracts interactions with the expander and deployer services.
type Expander interface {
ExpandChart(request *ExpansionRequest) (*ExpansionResponse, error)
}
// Configuration describes a set of resources in a form
// that can be instantiated.
type Configuration struct {
Resources []*Resource `json:"resources"`
}
// ResourceStatus is an enumeration type for the status of a resource.
type ResourceStatus string
// These constants implement the resourceStatus enumeration type.
const (
Created ResourceStatus = "Created"
Failed ResourceStatus = "Failed"
Aborted ResourceStatus = "Aborted"
)
// ResourceState describes the state of a resource.
// Status is set during resource creation and is a terminal state.
type ResourceState struct {
Status ResourceStatus `json:"status,omitempty"`
SelfLink string `json:"selflink,omitempty"`
Errors []string `json:"errors,omitempty"`
}
// Resource describes a resource in a configuration. A resource has
// a name, a type and a set of properties. The name and type are used
// to identify the resource in Kubernetes. The properties are passed
// to Kubernetes as the resource configuration.
type Resource struct {
Name string `json:"name"`
Type string `json:"type"`
Properties map[string]interface{} `json:"properties,omitempty"`
State *ResourceState `json:"state,omitempty"`
}
/*
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")
}
/*
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)
}
/*
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)
}
/*
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)
}
/*
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
}
/*
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)
}
/*
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
}
/*
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)
}
/*
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)
}
/*
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
}
/*
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)
}
}
}
/*
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 := &registryProvider{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
}
/*
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)
}
/*
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
}
/*
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
}
/*
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)
}
}
}
- name: test1
apitoken: token
- name: test2
basicauth:
username: user
password: password
/*
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))
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment