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 ( ...@@ -20,6 +20,7 @@ import (
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
"github.com/kubernetes/helm/pkg/common" "github.com/kubernetes/helm/pkg/common"
"github.com/kubernetes/helm/pkg/repo"
"github.com/kubernetes/helm/pkg/util" "github.com/kubernetes/helm/pkg/util"
"archive/tar" "archive/tar"
...@@ -90,15 +91,15 @@ var usage = func() { ...@@ -90,15 +91,15 @@ var usage = func() {
os.Exit(0) os.Exit(0)
} }
func getCredential() *common.RegistryCredential { func getCredential() *repo.Credential {
*apitoken = strings.TrimSpace(*apitoken) *apitoken = strings.TrimSpace(*apitoken)
if *apitoken == "" { if *apitoken == "" {
*apitoken = strings.TrimSpace(os.Getenv("GITHUB_API_TOKEN")) *apitoken = strings.TrimSpace(os.Getenv("GITHUB_API_TOKEN"))
} }
if *apitoken != "" { if *apitoken != "" {
return &common.RegistryCredential{ return &repo.Credential{
APIToken: common.APITokenCredential(*apitoken), APIToken: repo.APITokenCredential(*apitoken),
} }
} }
...@@ -113,8 +114,8 @@ func getCredential() *common.RegistryCredential { ...@@ -113,8 +114,8 @@ func getCredential() *common.RegistryCredential {
*password = strings.TrimSpace(os.Getenv("GITHUB_PASSWORD")) *password = strings.TrimSpace(os.Getenv("GITHUB_PASSWORD"))
} }
return &common.RegistryCredential{ return &repo.Credential{
BasicAuth: common.BasicAuthCredential{ BasicAuth: repo.BasicAuthCredential{
Username: *username, Username: *username,
Password: *password, Password: *password,
}, },
...@@ -126,8 +127,8 @@ func getCredential() *common.RegistryCredential { ...@@ -126,8 +127,8 @@ func getCredential() *common.RegistryCredential {
if err != nil { if err != nil {
log.Fatalf("Unable to read service account file: %v", err) log.Fatalf("Unable to read service account file: %v", err)
} }
return &common.RegistryCredential{ return &repo.Credential{
ServiceAccount: common.JWTTokenCredential(string(b)), ServiceAccount: repo.JWTTokenCredential(string(b)),
} }
} }
return nil return nil
......
...@@ -427,20 +427,20 @@ func (c *Chart) loadMember(filename string) (*Member, error) { ...@@ -427,20 +427,20 @@ func (c *Chart) loadMember(filename string) (*Member, error) {
return result, nil return result, nil
} }
// ChartContent is abstraction for the contents of a chart. // Content is abstraction for the contents of a chart.
type ChartContent struct { type Content struct {
Chartfile *Chartfile `json:"chartfile"` Chartfile *Chartfile `json:"chartfile"`
Members []*Member `json:"members"` Members []*Member `json:"members"`
} }
// loadContent loads contents of a chart directory into ChartContent // LoadContent loads contents of a chart directory into Content
func (c *Chart) loadContent() (*ChartContent, error) { func (c *Chart) LoadContent() (*Content, error) {
ms, err := c.loadDirectory(c.Dir()) ms, err := c.loadDirectory(c.Dir())
if err != nil { if err != nil {
return nil, err return nil, err
} }
cc := &ChartContent{ cc := &Content{
Chartfile: c.Chartfile(), Chartfile: c.Chartfile(),
Members: ms, Members: ms,
} }
......
...@@ -21,17 +21,6 @@ import ( ...@@ -21,17 +21,6 @@ import (
"time" "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 // Deployment defines a deployment that describes
// the creation, modification and/or deletion of a set of resources. // the creation, modification and/or deletion of a set of resources.
type Deployment struct { type Deployment struct {
...@@ -76,17 +65,6 @@ func (s DeploymentStatus) String() string { ...@@ -76,17 +65,6 @@ func (s DeploymentStatus) String() string {
return string(s) 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 // Manifest contains the input configuration for a deployment, the fully
// expanded configuration, and the layout structure of the manifest. // expanded configuration, and the layout structure of the manifest.
// //
...@@ -103,21 +81,16 @@ type CreateDeploymentRequest struct { ...@@ -103,21 +81,16 @@ type CreateDeploymentRequest struct {
ChartInvocation *Resource `json:"chart_invocation"` ChartInvocation *Resource `json:"chart_invocation"`
} }
// ExpansionRequest defines the API to expander. // ChartInstance defines the metadata for an instantiation of a chart.
type ExpansionRequest struct { type ChartInstance struct {
ChartInvocation *Resource `json:"chart_invocation"` Name string `json:"name"` // instance name
Chart *chart.ChartContent `json:"chart"` Type string `json:"type"` // instance type
} Deployment string `json:"deployment"` // deployment name
Manifest string `json:"manifest"` // manifest name
// ExpansionResponse defines the API to expander. Path string `json:"path"` // JSON path within manifest
type ExpansionResponse struct {
Resources []interface{} `json:"resources"`
} }
// Expander abstracts interactions with the expander and deployer services. // TODO: Remove the following section when the refactoring of templates is complete.
type Expander interface {
ExpandChart(request *ExpansionRequest) (*ExpansionResponse, error)
}
// Template describes a set of resources to be deployed. // Template describes a set of resources to be deployed.
// Manager expands a Template into a Configuration, which // Manager expands a Template into a Configuration, which
...@@ -135,6 +108,44 @@ type ImportFile struct { ...@@ -135,6 +108,44 @@ type ImportFile struct {
Content string `json:"content"` 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 // Configuration describes a set of resources in a form
// that can be instantiated. // that can be instantiated.
type Configuration struct { type Configuration struct {
...@@ -169,102 +180,3 @@ type Resource struct { ...@@ -169,102 +180,3 @@ type Resource struct {
Properties map[string]interface{} `json:"properties,omitempty"` Properties map[string]interface{} `json:"properties,omitempty"`
State *ResourceState `json:"state,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)
}
}
}
This diff is collapsed.
/*
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