Commit 8465cb1a authored by Ville Aikas's avatar Ville Aikas

Add GCS as a registry provider. Add a way to get a file using credentials from a…

Add GCS as a registry provider. Add a way to get a file using credentials from a registry to support private repos. Add ability to create / update a registry through manager
parent 3cbe6ea2
...@@ -182,11 +182,13 @@ type BasicAuthCredential struct { ...@@ -182,11 +182,13 @@ type BasicAuthCredential struct {
} }
type APITokenCredential string type APITokenCredential string
type JWTTokenCredential string
// Credential used to access the repository // Credential used to access the repository
type RegistryCredential struct { type RegistryCredential struct {
APIToken APITokenCredential `json:"apitoken,omitempty"` APIToken APITokenCredential `json:"apitoken,omitempty"`
BasicAuth BasicAuthCredential `json:"basicauth,omitempty"` BasicAuth BasicAuthCredential `json:"basicauth,omitempty"`
ServiceAccount JWTTokenCredential `json:"serviceaccount,omitempty"`
} }
// Registry describes a template registry // Registry describes a template registry
...@@ -204,6 +206,7 @@ type RegistryType string ...@@ -204,6 +206,7 @@ type RegistryType string
const ( const (
GithubRegistryType RegistryType = "github" GithubRegistryType RegistryType = "github"
GCSRegistryType RegistryType = "gcs"
) )
// RegistryFormat is a semi-colon delimited string that describes the format // RegistryFormat is a semi-colon delimited string that describes the format
......
...@@ -50,6 +50,8 @@ var ( ...@@ -50,6 +50,8 @@ var (
username = flag.String("username", "", "Github user name that overrides GITHUB_USERNAME environment variable") username = flag.String("username", "", "Github user name that overrides GITHUB_USERNAME environment variable")
password = flag.String("password", "", "Github password that overrides GITHUB_PASSWORD environment variable") password = flag.String("password", "", "Github password that overrides GITHUB_PASSWORD environment variable")
apitoken = flag.String("apitoken", "", "Github api token that overrides GITHUB_API_TOKEN environment variable") apitoken = flag.String("apitoken", "", "Github api token that overrides GITHUB_API_TOKEN environment variable")
serviceaccount = flag.String("serviceaccount", "", "Service account file containing JWT token")
registryfile = flag.String("registryfile", "", "File containing registry specification")
) )
var commands = []string{ var commands = []string{
...@@ -67,6 +69,7 @@ var commands = []string{ ...@@ -67,6 +69,7 @@ var commands = []string{
"describe \t\t Describes the named template in a given template registry", "describe \t\t Describes the named template in a given template registry",
"getcredential \t\t Gets the named credential used by a registry", "getcredential \t\t Gets the named credential used by a registry",
"setcredential \t\t Sets a credential used by a registry", "setcredential \t\t Sets a credential used by a registry",
"createregistry \t\t Creates a registry that holds charts",
} }
var usage = func() { var usage = func() {
...@@ -86,7 +89,7 @@ var usage = func() { ...@@ -86,7 +89,7 @@ var usage = func() {
os.Exit(0) os.Exit(0)
} }
func getGithubCredential() *common.RegistryCredential { func getCredential() *common.RegistryCredential {
*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"))
...@@ -117,6 +120,15 @@ func getGithubCredential() *common.RegistryCredential { ...@@ -117,6 +120,15 @@ func getGithubCredential() *common.RegistryCredential {
} }
} }
if *serviceaccount != "" {
b, err := ioutil.ReadFile(*serviceaccount)
if err != nil {
log.Fatalf("Unable to read service account file: %v", err)
}
return &common.RegistryCredential{
ServiceAccount: common.JWTTokenCredential(string(b)),
}
}
return nil return nil
} }
...@@ -124,6 +136,13 @@ func init() { ...@@ -124,6 +136,13 @@ func init() {
flag.Usage = usage flag.Usage = usage
} }
func getRegistry() ([]byte, error) {
if *registryfile == "" {
log.Fatalf("No registryfile specified (-registryfile)")
}
return ioutil.ReadFile(*registryfile)
}
func main() { func main() {
defer func() { defer func() {
result := recover() result := recover()
...@@ -145,6 +164,10 @@ func execute() { ...@@ -145,6 +164,10 @@ func execute() {
switch args[0] { switch args[0] {
case "templates": case "templates":
if len(args) < 2 {
fmt.Fprintln(os.Stderr, "No registry name supplied")
usage()
}
path := fmt.Sprintf("registries/%s/types", args[1]) path := fmt.Sprintf("registries/%s/types", args[1])
callService(path, "GET", "list templates", nil) callService(path, "GET", "list templates", nil)
case "describe": case "describe":
...@@ -162,7 +185,7 @@ func execute() { ...@@ -162,7 +185,7 @@ func execute() {
path := fmt.Sprintf("credentials/%s", args[1]) path := fmt.Sprintf("credentials/%s", args[1])
callService(path, "GET", "get credential", nil) callService(path, "GET", "get credential", nil)
case "setcredential": case "setcredential":
c := getGithubCredential() c := getCredential()
if c == nil { if c == nil {
panic(fmt.Errorf("Failed to create a credential from flags/arguments")) panic(fmt.Errorf("Failed to create a credential from flags/arguments"))
} }
...@@ -172,7 +195,14 @@ func execute() { ...@@ -172,7 +195,14 @@ func execute() {
} }
path := fmt.Sprintf("credentials/%s", args[1]) path := fmt.Sprintf("credentials/%s", args[1])
callService(path, "POST", "get credential", ioutil.NopCloser(bytes.NewReader(y))) callService(path, "POST", "set credential", ioutil.NopCloser(bytes.NewReader(y)))
case "createregistry":
reg, err := getRegistry()
if err != nil {
panic(fmt.Errorf("Failed to create a registry from arguments: %#v", err))
}
path := fmt.Sprintf("registries/%s", args[1])
callService(path, "POST", "set registry", ioutil.NopCloser(bytes.NewReader(reg)))
case "get": case "get":
if len(args) < 2 { if len(args) < 2 {
fmt.Fprintln(os.Stderr, "No deployment name supplied") fmt.Fprintln(os.Stderr, "No deployment name supplied")
...@@ -300,13 +330,20 @@ func describeType(args []string) { ...@@ -300,13 +330,20 @@ func describeType(args []string) {
os.Exit(1) os.Exit(1)
} }
tUrls := getDownloadURLs(args[1]) tUrls := getDownloadURLs(url.QueryEscape(args[1]))
if len(tUrls) == 0 { if len(tUrls) == 0 {
panic(fmt.Errorf("Invalid type name, must be a template URL or in the form \"<type-name>:<version>\": %s", args[1])) panic(fmt.Errorf("Invalid type name, must be a template URL or in the form \"<type-name>:<version>\": %s", args[1]))
} }
schemaUrl := tUrls[0] + ".schema" if !strings.Contains(tUrls[0], ".prov") {
fmt.Println(callHttp(schemaUrl, "GET", "get schema for type ("+tUrls[0]+")", nil)) // It's not a chart, so grab the schema
path := fmt.Sprintf("registries/%s/download?file=%s.schema", *template_registry, url.QueryEscape(tUrls[0]))
callService(path, "GET", "get schema for type ("+tUrls[0]+")", nil)
} else {
// It's a chart, so grab the provenance file
path := fmt.Sprintf("registries/%s/download?file=%s", *template_registry, url.QueryEscape(tUrls[0]))
callService(path, "GET", "get file", nil)
}
} }
// getDownloadURLs returns URLs for a type in the given registry // getDownloadURLs returns URLs for a type in the given registry
......
...@@ -54,8 +54,10 @@ var deployments = []Route{ ...@@ -54,8 +54,10 @@ var deployments = []Route{
{"ListTypeInstances", "/types/{type}/instances", "GET", listTypeInstancesHandlerFunc, ""}, {"ListTypeInstances", "/types/{type}/instances", "GET", listTypeInstancesHandlerFunc, ""},
{"ListRegistries", "/registries", "GET", listRegistriesHandlerFunc, ""}, {"ListRegistries", "/registries", "GET", listRegistriesHandlerFunc, ""},
{"GetRegistry", "/registries/{registry}", "GET", getRegistryHandlerFunc, ""}, {"GetRegistry", "/registries/{registry}", "GET", getRegistryHandlerFunc, ""},
{"CreateRegistry", "/registries/{registry}", "POST", createRegistryHandlerFunc, "JSON"},
{"ListRegistryTypes", "/registries/{registry}/types", "GET", listRegistryTypesHandlerFunc, ""}, {"ListRegistryTypes", "/registries/{registry}/types", "GET", listRegistryTypesHandlerFunc, ""},
{"GetDownloadURLs", "/registries/{registry}/types/{type}", "GET", getDownloadURLsHandlerFunc, ""}, {"GetDownloadURLs", "/registries/{registry}/types/{type}", "GET", getDownloadURLsHandlerFunc, ""},
{"GetFile", "/registries/{registry}/download", "GET", getFileHandlerFunc, ""},
{"CreateCredential", "/credentials/{credential}", "POST", createCredentialHandlerFunc, "JSON"}, {"CreateCredential", "/credentials/{credential}", "POST", createCredentialHandlerFunc, "JSON"},
{"GetCredential", "/credentials/{credential}", "GET", getCredentialHandlerFunc, ""}, {"GetCredential", "/credentials/{credential}", "GET", getCredentialHandlerFunc, ""},
} }
...@@ -97,12 +99,12 @@ func init() { ...@@ -97,12 +99,12 @@ func init() {
} }
func newManager(cp common.CredentialProvider) manager.Manager { func newManager(cp common.CredentialProvider) manager.Manager {
registryProvider := registry.NewDefaultRegistryProvider(cp) service := registry.NewInmemRegistryService()
resolver := manager.NewTypeResolver(registryProvider) registryProvider := registry.NewDefaultRegistryProvider(cp, service)
resolver := manager.NewTypeResolver(registryProvider, util.DefaultHTTPClient())
expander := manager.NewExpander(getServiceURL(*expanderURL, *expanderName), resolver) expander := manager.NewExpander(getServiceURL(*expanderURL, *expanderName), resolver)
deployer := manager.NewDeployer(getServiceURL(*deployerURL, *deployerName)) deployer := manager.NewDeployer(getServiceURL(*deployerURL, *deployerName))
r := repository.NewMapBasedRepository() r := repository.NewMapBasedRepository()
service := registry.NewInmemRegistryService()
credentialProvider := cp credentialProvider := cp
return manager.NewManager(expander, deployer, r, registryProvider, service, credentialProvider) return manager.NewManager(expander, deployer, r, registryProvider, service, credentialProvider)
} }
...@@ -378,6 +380,49 @@ func getRegistryHandlerFunc(w http.ResponseWriter, r *http.Request) { ...@@ -378,6 +380,49 @@ func getRegistryHandlerFunc(w http.ResponseWriter, r *http.Request) {
util.LogHandlerExitWithJSON(handler, w, cr, http.StatusOK) util.LogHandlerExitWithJSON(handler, w, cr, http.StatusOK)
} }
func getRegistry(w http.ResponseWriter, r *http.Request, handler string) *common.Registry {
util.LogHandlerEntry(handler, r)
j, err := getJsonFromRequest(w, r, handler)
if err != nil {
return nil
}
t := &common.Registry{}
if err := json.Unmarshal(j, t); err != nil {
e := fmt.Errorf("%v\n%v", err, string(j))
util.LogAndReturnError(handler, http.StatusBadRequest, e, w)
return nil
}
return t
}
func createRegistryHandlerFunc(w http.ResponseWriter, r *http.Request) {
handler := "manager: create registry"
util.LogHandlerEntry(handler, r)
defer r.Body.Close()
registryName, err := getPathVariable(w, r, "registry", handler)
if err != nil {
return
}
reg := getRegistry(w, r, handler)
if reg.Name != registryName {
e := fmt.Errorf("Registry name does not match %s != %s", reg.Name, registryName)
util.LogAndReturnError(handler, http.StatusBadRequest, e, w)
return
}
if reg != nil {
err = backend.CreateRegistry(reg)
if err != nil {
util.LogAndReturnError(handler, http.StatusBadRequest, err, w)
return
}
}
util.LogHandlerExitWithJSON(handler, w, reg, http.StatusOK)
}
func listRegistryTypesHandlerFunc(w http.ResponseWriter, r *http.Request) { func listRegistryTypesHandlerFunc(w http.ResponseWriter, r *http.Request) {
handler := "manager: list registry types" handler := "manager: list registry types"
util.LogHandlerEntry(handler, r) util.LogHandlerEntry(handler, r)
...@@ -437,6 +482,28 @@ func getDownloadURLsHandlerFunc(w http.ResponseWriter, r *http.Request) { ...@@ -437,6 +482,28 @@ func getDownloadURLsHandlerFunc(w http.ResponseWriter, r *http.Request) {
util.LogHandlerExitWithJSON(handler, w, urls, http.StatusOK) util.LogHandlerExitWithJSON(handler, w, urls, http.StatusOK)
} }
func getFileHandlerFunc(w http.ResponseWriter, r *http.Request) {
handler := "manager: get file"
util.LogHandlerEntry(handler, r)
registryName, err := getPathVariable(w, r, "registry", handler)
if err != nil {
return
}
file := r.FormValue("file")
if file == "" {
return
}
b, err := backend.GetFile(registryName, file)
if err != nil {
util.LogAndReturnError(handler, http.StatusBadRequest, err, w)
return
}
util.LogHandlerExitWithJSON(handler, w, b, http.StatusOK)
}
func getCredential(w http.ResponseWriter, r *http.Request, handler string) *common.RegistryCredential { func getCredential(w http.ResponseWriter, r *http.Request, handler string) *common.RegistryCredential {
util.LogHandlerEntry(handler, r) util.LogHandlerEntry(handler, r)
j, err := getJsonFromRequest(w, r, handler) j, err := getJsonFromRequest(w, r, handler)
......
...@@ -26,6 +26,7 @@ import ( ...@@ -26,6 +26,7 @@ import (
"github.com/kubernetes/deployment-manager/common" "github.com/kubernetes/deployment-manager/common"
"github.com/kubernetes/deployment-manager/manager/repository" "github.com/kubernetes/deployment-manager/manager/repository"
"github.com/kubernetes/deployment-manager/registry" "github.com/kubernetes/deployment-manager/registry"
"github.com/kubernetes/deployment-manager/util"
) )
// Manager manages a persistent set of Deployments. // Manager manages a persistent set of Deployments.
...@@ -55,6 +56,7 @@ type Manager interface { ...@@ -55,6 +56,7 @@ type Manager interface {
// Registry Types // Registry Types
ListRegistryTypes(registryName string, regex *regexp.Regexp) ([]registry.Type, error) ListRegistryTypes(registryName string, regex *regexp.Regexp) ([]registry.Type, error)
GetDownloadURLs(registryName string, t registry.Type) ([]*url.URL, error) GetDownloadURLs(registryName string, t registry.Type) ([]*url.URL, error)
GetFile(registryName string, url string) (string, error)
// Credentials // Credentials
CreateCredential(name string, c *common.RegistryCredential) error CreateCredential(name string, c *common.RegistryCredential) error
...@@ -394,6 +396,21 @@ func (m *manager) GetDownloadURLs(registryName string, t registry.Type) ([]*url. ...@@ -394,6 +396,21 @@ func (m *manager) GetDownloadURLs(registryName string, t registry.Type) ([]*url.
return r.GetDownloadURLs(t) return r.GetDownloadURLs(t)
} }
// GetFile returns a file from the backing registry
func (m *manager) GetFile(registryName string, url string) (string, error) {
r, err := m.registryProvider.GetRegistryByName(registryName)
if err != nil {
return "", err
}
getter := util.NewHTTPClient(3, r, util.NewSleeper())
body, _, err := getter.Get(url)
if err != nil {
return "", err
}
return body, nil
}
// CreateCredential creates a credential that can be used to authenticate to registry // CreateCredential creates a credential that can be used to authenticate to registry
func (m *manager) CreateCredential(name string, c *common.RegistryCredential) error { func (m *manager) CreateCredential(name string, c *common.RegistryCredential) error {
return m.credentialProvider.SetCredential(name, c) return m.credentialProvider.SetCredential(name, c)
......
...@@ -255,7 +255,7 @@ var testRepository = newRepositoryStub() ...@@ -255,7 +255,7 @@ var testRepository = newRepositoryStub()
var testDeployer = newDeployerStub() var testDeployer = newDeployerStub()
var testRegistryService = registry.NewInmemRegistryService() var testRegistryService = registry.NewInmemRegistryService()
var testCredentialProvider = registry.NewInmemCredentialProvider() var testCredentialProvider = registry.NewInmemCredentialProvider()
var testProvider = registry.NewRegistryProvider(nil, registry.NewTestGithubRegistryProvider("", nil), testCredentialProvider) var testProvider = registry.NewRegistryProvider(nil, registry.NewTestGithubRegistryProvider("", nil), registry.NewTestGCSRegistryProvider("", nil), testCredentialProvider)
var testManager = NewManager(testExpander, testDeployer, testRepository, testProvider, testRegistryService, testCredentialProvider) var testManager = NewManager(testExpander, testDeployer, testRepository, testProvider, testRegistryService, testCredentialProvider)
func TestListDeployments(t *testing.T) { func TestListDeployments(t *testing.T) {
......
...@@ -19,7 +19,6 @@ package manager ...@@ -19,7 +19,6 @@ package manager
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"time"
"github.com/kubernetes/deployment-manager/common" "github.com/kubernetes/deployment-manager/common"
"github.com/kubernetes/deployment-manager/registry" "github.com/kubernetes/deployment-manager/registry"
...@@ -40,26 +39,23 @@ type TypeResolver interface { ...@@ -40,26 +39,23 @@ type TypeResolver interface {
} }
type typeResolver struct { type typeResolver struct {
getter util.HTTPClient
maxUrls int maxUrls int
rp registry.RegistryProvider rp registry.RegistryProvider
c util.HTTPClient
}
type fetchableURL struct {
registry registry.Registry
url string
} }
type fetchUnit struct { type fetchUnit struct {
urls []string urls []fetchableURL
} }
// NewTypeResolver returns a new initialized TypeResolver. // NewTypeResolver returns a new initialized TypeResolver.
func NewTypeResolver(rp registry.RegistryProvider) TypeResolver { func NewTypeResolver(rp registry.RegistryProvider, c util.HTTPClient) TypeResolver {
ret := &typeResolver{} return &typeResolver{maxUrls: maxURLImports, rp: rp, c: c}
client := http.DefaultClient
//TODO (iantw): Make this a flag
timeout, _ := time.ParseDuration("10s")
client.Timeout = timeout
ret.getter = util.NewHTTPClient(3, client, util.NewSleeper())
ret.maxUrls = maxURLImports
ret.rp = rp
return ret
} }
func resolverError(c *common.Configuration, err error) error { func resolverError(c *common.Configuration, err error) error {
...@@ -67,7 +63,13 @@ func resolverError(c *common.Configuration, err error) error { ...@@ -67,7 +63,13 @@ func resolverError(c *common.Configuration, err error) error {
c, err) c, err)
} }
func performHTTPGet(g util.HTTPClient, u string, allowMissing bool) (content string, err error) { func (tr *typeResolver) performHTTPGet(d util.HTTPDoer, u string, allowMissing bool) (content string, err error) {
var g util.HTTPClient
if d == nil {
g = tr.c
} else {
g = util.NewHTTPClient(3, d, util.NewSleeper())
}
r, code, err := g.Get(u) r, code, err := g.Get(u)
if err != nil { if err != nil {
return "", err return "", err
...@@ -100,7 +102,7 @@ func (tr *typeResolver) ResolveTypes(config *common.Configuration, imports []*co ...@@ -100,7 +102,7 @@ func (tr *typeResolver) ResolveTypes(config *common.Configuration, imports []*co
toFetch := make([]*fetchUnit, 0, tr.maxUrls) toFetch := make([]*fetchUnit, 0, tr.maxUrls)
for _, r := range config.Resources { for _, r := range config.Resources {
// Map the type to a fetchable URL (if applicable) or skip it if it's a non-fetchable type (primitive for example). // Map the type to a fetchable URL (if applicable) or skip it if it's a non-fetchable type (primitive for example).
urls, err := registry.GetDownloadURLs(tr.rp, r.Type) urls, registry, err := registry.GetDownloadURLs(tr.rp, r.Type)
if err != nil { if err != nil {
return nil, resolverError(config, fmt.Errorf("Failed to understand download url for %s: %v", r.Type, err)) return nil, resolverError(config, fmt.Errorf("Failed to understand download url for %s: %v", r.Type, err))
} }
...@@ -108,14 +110,14 @@ func (tr *typeResolver) ResolveTypes(config *common.Configuration, imports []*co ...@@ -108,14 +110,14 @@ func (tr *typeResolver) ResolveTypes(config *common.Configuration, imports []*co
f := &fetchUnit{} f := &fetchUnit{}
for _, u := range urls { for _, u := range urls {
if len(u) > 0 { if len(u) > 0 {
f.urls = append(f.urls, u) f.urls = append(f.urls, fetchableURL{registry, u})
// Add to existing map so it is not fetched multiple times. // Add to existing map so it is not fetched multiple times.
existing[r.Type] = true existing[r.Type] = true
} }
} }
if len(f.urls) > 0 { if len(f.urls) > 0 {
toFetch = append(toFetch, f) toFetch = append(toFetch, f)
fetched[f.urls[0]] = append(fetched[f.urls[0]], &common.ImportFile{Name: r.Type, Path: f.urls[0]}) fetched[f.urls[0].url] = append(fetched[f.urls[0].url], &common.ImportFile{Name: r.Type, Path: f.urls[0].url})
} }
} }
} }
...@@ -138,14 +140,14 @@ func (tr *typeResolver) ResolveTypes(config *common.Configuration, imports []*co ...@@ -138,14 +140,14 @@ func (tr *typeResolver) ResolveTypes(config *common.Configuration, imports []*co
templates := []string{} templates := []string{}
url := toFetch[0].urls[0] url := toFetch[0].urls[0]
for _, u := range toFetch[0].urls { for _, u := range toFetch[0].urls {
template, err := performHTTPGet(tr.getter, u, false) template, err := tr.performHTTPGet(u.registry, u.url, false)
if err != nil { if err != nil {
return nil, resolverError(config, err) return nil, resolverError(config, err)
} }
templates = append(templates, template) templates = append(templates, template)
} }
for _, i := range fetched[url] { for _, i := range fetched[url.url] {
template, err := parseContent(templates) template, err := parseContent(templates)
if err != nil { if err != nil {
return nil, resolverError(config, err) return nil, resolverError(config, err)
...@@ -153,8 +155,8 @@ func (tr *typeResolver) ResolveTypes(config *common.Configuration, imports []*co ...@@ -153,8 +155,8 @@ func (tr *typeResolver) ResolveTypes(config *common.Configuration, imports []*co
i.Content = template i.Content = template
} }
schemaURL := url + schemaSuffix schemaURL := url.url + schemaSuffix
sch, err := performHTTPGet(tr.getter, schemaURL, true) sch, err := tr.performHTTPGet(url.registry, schemaURL, true)
if err != nil { if err != nil {
return nil, resolverError(config, err) return nil, resolverError(config, err)
} }
...@@ -168,7 +170,7 @@ func (tr *typeResolver) ResolveTypes(config *common.Configuration, imports []*co ...@@ -168,7 +170,7 @@ func (tr *typeResolver) ResolveTypes(config *common.Configuration, imports []*co
for _, v := range s.Imports { for _, v := range s.Imports {
i := &common.ImportFile{Name: v.Name} i := &common.ImportFile{Name: v.Name}
var existingSchema string var existingSchema string
urls, conversionErr := registry.GetDownloadURLs(tr.rp, v.Path) urls, registry, conversionErr := registry.GetDownloadURLs(tr.rp, v.Path)
if conversionErr != nil { if conversionErr != nil {
return nil, resolverError(config, fmt.Errorf("Failed to understand download url for %s: %v", v.Path, conversionErr)) return nil, resolverError(config, fmt.Errorf("Failed to understand download url for %s: %v", v.Path, conversionErr))
} }
...@@ -180,7 +182,7 @@ func (tr *typeResolver) ResolveTypes(config *common.Configuration, imports []*co ...@@ -180,7 +182,7 @@ func (tr *typeResolver) ResolveTypes(config *common.Configuration, imports []*co
for _, u := range urls { for _, u := range urls {
if len(fetched[u]) == 0 { if len(fetched[u]) == 0 {
// If this import URL is new to us, add it to the URLs to fetch. // If this import URL is new to us, add it to the URLs to fetch.
toFetch = append(toFetch, &fetchUnit{[]string{u}}) toFetch = append(toFetch, &fetchUnit{[]fetchableURL{fetchableURL{registry, u}}})
} else { } else {
// If this is not a new import URL and we've already fetched its contents, // If this is not a new import URL and we've already fetched its contents,
// reuse them. Also, check if we also found a schema for that import URL and // reuse them. Also, check if we also found a schema for that import URL and
...@@ -201,7 +203,7 @@ func (tr *typeResolver) ResolveTypes(config *common.Configuration, imports []*co ...@@ -201,7 +203,7 @@ func (tr *typeResolver) ResolveTypes(config *common.Configuration, imports []*co
} }
// Add the schema we've fetched as the schema for any templates which used this URL. // Add the schema we've fetched as the schema for any templates which used this URL.
for _, i := range fetched[url] { for _, i := range fetched[url.url] {
schemaImportName := i.Name + schemaSuffix schemaImportName := i.Name + schemaSuffix
fetched[schemaURL] = append(fetched[schemaURL], fetched[schemaURL] = append(fetched[schemaURL],
&common.ImportFile{Name: schemaImportName, Content: sch}) &common.ImportFile{Name: schemaImportName, Content: sch})
......
...@@ -18,6 +18,7 @@ package manager ...@@ -18,6 +18,7 @@ package manager
import ( import (
"errors" "errors"
"log"
"net/http" "net/http"
"reflect" "reflect"
"strings" "strings"
...@@ -50,19 +51,20 @@ type testGetter struct { ...@@ -50,19 +51,20 @@ type testGetter struct {
test *testing.T test *testing.T
} }
func (tg *testGetter) Get(url string) (body string, code int, err error) { func (tg testGetter) Get(url string) (body string, code int, err error) {
tg.count = tg.count + 1 tg.count = tg.count + 1
ret := tg.responses[url] ret := tg.responses[url]
log.Printf("GET RETURNING: '%s' '%d'", ret.resp, tg.count)
return ret.resp, ret.code, ret.err return ret.resp, ret.code, ret.err
} }
func testDriver(c resolverTestCase, t *testing.T) { func testDriver(c resolverTestCase, t *testing.T) {
g := &testGetter{test: t, responses: c.responses} g := &testGetter{test: t, responses: c.responses}
log.Printf("getter: %#v", g)
r := &typeResolver{ r := &typeResolver{
getter: g,
maxUrls: 5, maxUrls: 5,
rp: c.registryProvider, rp: c.registryProvider,
c: g,
} }
conf := &common.Configuration{} conf := &common.Configuration{}
...@@ -74,7 +76,7 @@ func testDriver(c resolverTestCase, t *testing.T) { ...@@ -74,7 +76,7 @@ func testDriver(c resolverTestCase, t *testing.T) {
result, err := r.ResolveTypes(conf, c.imports) result, err := r.ResolveTypes(conf, c.imports)
if g.count != c.urlcount { if g.count != c.urlcount {
t.Errorf("Expected %d url GETs but only %d found", c.urlcount, g.count) t.Errorf("Expected %d url GETs but only %d found %#v", c.urlcount, g.count, g)
} }
if (err != nil && c.expectedErr == nil) || (err == nil && c.expectedErr != nil) { if (err != nil && c.expectedErr == nil) || (err == nil && c.expectedErr != nil) {
...@@ -307,13 +309,19 @@ func TestShortGithubUrl(t *testing.T) { ...@@ -307,13 +309,19 @@ func TestShortGithubUrl(t *testing.T) {
registry.NewTypeOrDie("common", "replicatedservice", "v2"): registry.TestURLAndError{"https://raw.githubusercontent.com/kubernetes/application-dm-templates/master/common/replicatedservice/v2/replicatedservice.py", nil}, registry.NewTypeOrDie("common", "replicatedservice", "v2"): registry.TestURLAndError{"https://raw.githubusercontent.com/kubernetes/application-dm-templates/master/common/replicatedservice/v2/replicatedservice.py", nil},
} }
gcsUrlMaps := map[registry.Type]registry.TestURLAndError{
registry.NewTypeOrDie("common", "replicatedservice", "v1"): registry.TestURLAndError{"https://raw.githubusercontent.com/kubernetes/application-dm-templates/master/common/replicatedservice/v1/replicatedservice.py", nil},
registry.NewTypeOrDie("common", "replicatedservice", "v2"): registry.TestURLAndError{"https://raw.githubusercontent.com/kubernetes/application-dm-templates/master/common/replicatedservice/v2/replicatedservice.py", nil},
}
grp := registry.NewTestGithubRegistryProvider("github.com/kubernetes/application-dm-templates", githubUrlMaps) grp := registry.NewTestGithubRegistryProvider("github.com/kubernetes/application-dm-templates", githubUrlMaps)
gcsrp := registry.NewTestGCSRegistryProvider("gs://charts", gcsUrlMaps)
test := resolverTestCase{ test := resolverTestCase{
config: templateShortGithubTemplate, config: templateShortGithubTemplate,
importOut: finalImports, importOut: finalImports,
urlcount: 4, urlcount: 4,
responses: responses, responses: responses,
registryProvider: registry.NewRegistryProvider(nil, grp, registry.NewInmemCredentialProvider()), registryProvider: registry.NewRegistryProvider(nil, grp, gcsrp, registry.NewInmemCredentialProvider()),
} }
testDriver(test, t) testDriver(test, 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 (
"github.com/kubernetes/deployment-manager/common"
"github.com/kubernetes/deployment-manager/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
var ChartFormatMatcher = regexp.MustCompile("(.*)-(.*).tgz")
var URLFormatMatcher = regexp.MustCompile("gs://(.*)")
// NewGithubTemplateRegistry creates a GithubTemplateRegistry.
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
}
func (g GCSRegistry) GetRegistryName() string {
return g.name
}
func (g GCSRegistry) GetBucket() string {
return g.bucket
}
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
}
func (g GCSRegistry) GetRegistryFormat() common.RegistryFormat {
return common.CollectionRegistry
}
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
}
func (g GCSRegistry) Do(req *http.Request) (resp *http.Response, err error) {
return g.httpClient.Do(req)
}
...@@ -22,6 +22,7 @@ import ( ...@@ -22,6 +22,7 @@ import (
"fmt" "fmt"
"log" "log"
"net/http"
"net/url" "net/url"
"regexp" "regexp"
"strings" "strings"
...@@ -41,7 +42,7 @@ type GithubPackageRegistry struct { ...@@ -41,7 +42,7 @@ type GithubPackageRegistry struct {
} }
// NewGithubPackageRegistry creates a GithubPackageRegistry. // NewGithubPackageRegistry creates a GithubPackageRegistry.
func NewGithubPackageRegistry(name, shortURL string, service GithubRepositoryService, client *github.Client) (*GithubPackageRegistry, error) { func NewGithubPackageRegistry(name, shortURL string, service GithubRepositoryService, httpClient *http.Client, client *github.Client) (*GithubPackageRegistry, error) {
format := fmt.Sprintf("%s;%s", common.UnversionedRegistry, common.OneLevelRegistry) format := fmt.Sprintf("%s;%s", common.UnversionedRegistry, common.OneLevelRegistry)
if service == nil { if service == nil {
if client == nil { if client == nil {
...@@ -51,7 +52,7 @@ func NewGithubPackageRegistry(name, shortURL string, service GithubRepositorySer ...@@ -51,7 +52,7 @@ func NewGithubPackageRegistry(name, shortURL string, service GithubRepositorySer
} }
} }
gr, err := newGithubRegistry(name, shortURL, common.RegistryFormat(format), service) gr, err := newGithubRegistry(name, shortURL, common.RegistryFormat(format), httpClient, service)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -151,3 +152,7 @@ func (g GithubPackageRegistry) MakeRepositoryPath(t Type) (string, error) { ...@@ -151,3 +152,7 @@ func (g GithubPackageRegistry) MakeRepositoryPath(t Type) (string, error) {
// Construct the return path // Construct the return path
return t.Name + "/manifests", nil return t.Name + "/manifests", nil
} }
func (g GithubPackageRegistry) Do(req *http.Request) (resp *http.Response, err error) {
return g.httpClient.Do(req)
}
...@@ -22,6 +22,7 @@ import ( ...@@ -22,6 +22,7 @@ import (
"github.com/kubernetes/deployment-manager/util" "github.com/kubernetes/deployment-manager/util"
"fmt" "fmt"
"net/http"
"strings" "strings"
) )
...@@ -38,6 +39,7 @@ type githubRegistry struct { ...@@ -38,6 +39,7 @@ type githubRegistry struct {
format common.RegistryFormat format common.RegistryFormat
credentialName string credentialName string
service GithubRepositoryService service GithubRepositoryService
httpClient *http.Client
} }
// GithubRepositoryService defines the interface that's defined in github.com/go-github/repos_contents.go GetContents method. // GithubRepositoryService defines the interface that's defined in github.com/go-github/repos_contents.go GetContents method.
...@@ -54,7 +56,7 @@ type GithubRepositoryService interface { ...@@ -54,7 +56,7 @@ type GithubRepositoryService interface {
} }
// newGithubRegistry creates a githubRegistry. // newGithubRegistry creates a githubRegistry.
func newGithubRegistry(name, shortURL string, format common.RegistryFormat, service GithubRepositoryService) (*githubRegistry, error) { func newGithubRegistry(name, shortURL string, format common.RegistryFormat, httpClient *http.Client, service GithubRepositoryService) (*githubRegistry, error) {
trimmed := util.TrimURLScheme(shortURL) trimmed := util.TrimURLScheme(shortURL)
owner, repository, path, err := parseGithubShortURL(trimmed) owner, repository, path, err := parseGithubShortURL(trimmed)
if err != nil { if err != nil {
...@@ -69,6 +71,7 @@ func newGithubRegistry(name, shortURL string, format common.RegistryFormat, serv ...@@ -69,6 +71,7 @@ func newGithubRegistry(name, shortURL string, format common.RegistryFormat, serv
path: path, path: path,
format: format, format: format,
service: service, service: service,
httpClient: httpClient,
}, nil }, nil
} }
......
...@@ -22,6 +22,7 @@ import ( ...@@ -22,6 +22,7 @@ import (
"fmt" "fmt"
"log" "log"
"net/http"
"net/url" "net/url"
"regexp" "regexp"
"strings" "strings"
...@@ -54,13 +55,13 @@ type GithubTemplateRegistry struct { ...@@ -54,13 +55,13 @@ type GithubTemplateRegistry struct {
} }
// NewGithubTemplateRegistry creates a GithubTemplateRegistry. // NewGithubTemplateRegistry creates a GithubTemplateRegistry.
func NewGithubTemplateRegistry(name, shortURL string, service GithubRepositoryService, client *github.Client) (*GithubTemplateRegistry, error) { func NewGithubTemplateRegistry(name, shortURL string, service GithubRepositoryService, httpClient *http.Client, client *github.Client) (*GithubTemplateRegistry, error) {
format := fmt.Sprintf("%s;%s", common.VersionedRegistry, common.CollectionRegistry) format := fmt.Sprintf("%s;%s", common.VersionedRegistry, common.CollectionRegistry)
if service == nil { if service == nil {
service = client.Repositories service = client.Repositories
} }
gr, err := newGithubRegistry(name, shortURL, common.RegistryFormat(format), service) gr, err := newGithubRegistry(name, shortURL, common.RegistryFormat(format), httpClient, service)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -212,3 +213,7 @@ func (g GithubTemplateRegistry) MakeRepositoryPath(t Type) (string, error) { ...@@ -212,3 +213,7 @@ func (g GithubTemplateRegistry) MakeRepositoryPath(t Type) (string, error) {
} }
return p + t.Name + "/" + t.GetVersion(), nil return p + t.Name + "/" + t.GetVersion(), nil
} }
func (g GithubTemplateRegistry) Do(req *http.Request) (resp *http.Response, err error) {
return g.httpClient.Do(req)
}
...@@ -38,6 +38,6 @@ func (fcp *InmemCredentialProvider) GetCredential(name string) (*common.Registry ...@@ -38,6 +38,6 @@ func (fcp *InmemCredentialProvider) GetCredential(name string) (*common.Registry
} }
func (fcp *InmemCredentialProvider) SetCredential(name string, credential *common.RegistryCredential) error { func (fcp *InmemCredentialProvider) SetCredential(name string, credential *common.RegistryCredential) error {
fcp.credentials[name] = &common.RegistryCredential{credential.APIToken, credential.BasicAuth} fcp.credentials[name] = &common.RegistryCredential{credential.APIToken, credential.BasicAuth, credential.ServiceAccount}
return nil return nil
} }
...@@ -51,6 +51,14 @@ func NewInmemRegistryService() common.RegistryService { ...@@ -51,6 +51,14 @@ func NewInmemRegistryService() common.RegistryService {
CredentialName: "default", CredentialName: "default",
}) })
gFormat := fmt.Sprintf("%s", common.CollectionRegistry)
rs.Create(&common.Registry{
Name: "charts_gcs",
Type: common.GCSRegistryType,
URL: "gs://helm-charts-test",
Format: common.RegistryFormat(gFormat),
})
return rs return rs
} }
......
...@@ -18,6 +18,7 @@ package registry ...@@ -18,6 +18,7 @@ package registry
import ( import (
"github.com/kubernetes/deployment-manager/common" "github.com/kubernetes/deployment-manager/common"
"github.com/kubernetes/deployment-manager/util"
"fmt" "fmt"
"net/url" "net/url"
...@@ -29,6 +30,9 @@ import ( ...@@ -29,6 +30,9 @@ import (
// used in a Deployment Manager configuration. There can be multiple // used in a Deployment Manager configuration. There can be multiple
// registry implementations. // registry implementations.
type Registry interface { type Registry interface {
// Also handles http.Client.Do method for authenticated File accesses
util.HTTPDoer
// GetRegistryName returns the name of this registry // GetRegistryName returns the name of this registry
GetRegistryName() string GetRegistryName() string
// GetRegistryType returns the type of this registry. // GetRegistryType returns the type of this registry.
...@@ -56,6 +60,13 @@ type GithubRegistry interface { ...@@ -56,6 +60,13 @@ type GithubRegistry interface {
GetRegistryPath() string 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 Type struct { type Type struct {
Collection string Collection string
Name string Name string
......
...@@ -21,9 +21,12 @@ import ( ...@@ -21,9 +21,12 @@ import (
"github.com/kubernetes/deployment-manager/common" "github.com/kubernetes/deployment-manager/common"
"github.com/kubernetes/deployment-manager/util" "github.com/kubernetes/deployment-manager/util"
"golang.org/x/oauth2" "golang.org/x/oauth2"
"golang.org/x/oauth2/google"
storage "google.golang.org/api/storage/v1"
"fmt" "fmt"
"log" "log"
"net/http"
"net/url" "net/url"
"regexp" "regexp"
"strings" "strings"
...@@ -40,15 +43,16 @@ type registryProvider struct { ...@@ -40,15 +43,16 @@ type registryProvider struct {
sync.RWMutex sync.RWMutex
rs common.RegistryService rs common.RegistryService
grp GithubRegistryProvider grp GithubRegistryProvider
gcsrp GCSRegistryProvider
cp common.CredentialProvider cp common.CredentialProvider
registries map[string]Registry registries map[string]Registry
} }
func NewDefaultRegistryProvider(cp common.CredentialProvider) RegistryProvider { func NewDefaultRegistryProvider(cp common.CredentialProvider, rs common.RegistryService) RegistryProvider {
return NewRegistryProvider(nil, NewGithubRegistryProvider(cp), cp) return NewRegistryProvider(rs, NewGithubRegistryProvider(cp), NewGCSRegistryProvider(cp), cp)
} }
func NewRegistryProvider(rs common.RegistryService, grp GithubRegistryProvider, cp common.CredentialProvider) RegistryProvider { func NewRegistryProvider(rs common.RegistryService, grp GithubRegistryProvider, gcsrp GCSRegistryProvider, cp common.CredentialProvider) RegistryProvider {
if rs == nil { if rs == nil {
rs = NewInmemRegistryService() rs = NewInmemRegistryService()
} }
...@@ -61,11 +65,27 @@ func NewRegistryProvider(rs common.RegistryService, grp GithubRegistryProvider, ...@@ -61,11 +65,27 @@ func NewRegistryProvider(rs common.RegistryService, grp GithubRegistryProvider,
grp = NewGithubRegistryProvider(cp) grp = NewGithubRegistryProvider(cp)
} }
if gcsrp == nil {
gcsrp = NewGCSRegistryProvider(cp)
}
registries := make(map[string]Registry) registries := make(map[string]Registry)
rp := &registryProvider{rs: rs, grp: grp, cp: cp, registries: registries} rp := &registryProvider{rs: rs, grp: grp, gcsrp: gcsrp, cp: cp, registries: registries}
return rp 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)
}
}
func (rp registryProvider) GetRegistryByShortURL(URL string) (Registry, error) { func (rp registryProvider) GetRegistryByShortURL(URL string) (Registry, error) {
rp.RLock() rp.RLock()
defer rp.RUnlock() defer rp.RUnlock()
...@@ -77,7 +97,7 @@ func (rp registryProvider) GetRegistryByShortURL(URL string) (Registry, error) { ...@@ -77,7 +97,7 @@ func (rp registryProvider) GetRegistryByShortURL(URL string) (Registry, error) {
return nil, err return nil, err
} }
r, err := rp.grp.GetGithubRegistry(*cr) r, err := rp.getRegistry(*cr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -111,7 +131,7 @@ func (rp registryProvider) GetRegistryByName(registryName string) (Registry, err ...@@ -111,7 +131,7 @@ func (rp registryProvider) GetRegistryByName(registryName string) (Registry, err
return nil, err return nil, err
} }
r, err := rp.grp.GetGithubRegistry(*cr) r, err := rp.getRegistry(*cr)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -143,21 +163,21 @@ type githubRegistryProvider struct { ...@@ -143,21 +163,21 @@ type githubRegistryProvider struct {
// NewGithubRegistryProvider creates a GithubRegistryProvider. // NewGithubRegistryProvider creates a GithubRegistryProvider.
func NewGithubRegistryProvider(cp common.CredentialProvider) GithubRegistryProvider { func NewGithubRegistryProvider(cp common.CredentialProvider) GithubRegistryProvider {
if cp == nil { if cp == nil {
panic(fmt.Errorf("CP IS NIL: %v", cp)) panic(fmt.Errorf("cp is nil: %v", cp))
} }
return &githubRegistryProvider{cp: cp} return &githubRegistryProvider{cp: cp}
} }
func (grp githubRegistryProvider) createGithubClient(credentialName string) (*github.Client, error) { func (grp githubRegistryProvider) createGithubClient(credentialName string) (*http.Client, *github.Client, error) {
if credentialName == "" { if credentialName == "" {
return github.NewClient(nil), nil return http.DefaultClient, github.NewClient(nil), nil
} }
c, err := grp.cp.GetCredential(credentialName) c, err := grp.cp.GetCredential(credentialName)
if err != nil { if err != nil {
log.Printf("Failed to fetch credential %s: %v", credentialName, err) log.Printf("Failed to fetch credential %s: %v", credentialName, err)
log.Print("Trying to use unauthenticated client") log.Print("Trying to use unauthenticated client")
return github.NewClient(nil), nil return http.DefaultClient, github.NewClient(nil), nil
} }
if c != nil { if c != nil {
...@@ -166,44 +186,90 @@ func (grp githubRegistryProvider) createGithubClient(credentialName string) (*gi ...@@ -166,44 +186,90 @@ func (grp githubRegistryProvider) createGithubClient(credentialName string) (*gi
&oauth2.Token{AccessToken: string(c.APIToken)}, &oauth2.Token{AccessToken: string(c.APIToken)},
) )
tc := oauth2.NewClient(oauth2.NoContext, ts) tc := oauth2.NewClient(oauth2.NoContext, ts)
return github.NewClient(tc), nil return tc, github.NewClient(tc), nil
} }
if c.BasicAuth.Username != "" && c.BasicAuth.Password != "" { if c.BasicAuth.Username != "" && c.BasicAuth.Password != "" {
tp := github.BasicAuthTransport{ tp := github.BasicAuthTransport{
Username: c.BasicAuth.Username, Username: c.BasicAuth.Username,
Password: c.BasicAuth.Password, Password: c.BasicAuth.Password,
} }
return github.NewClient(tp.Client()), nil return tp.Client(), github.NewClient(tp.Client()), nil
} }
} }
return nil, fmt.Errorf("No suitable credential found for %s", credentialName) 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 // 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. // 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) { func (grp githubRegistryProvider) GetGithubRegistry(cr common.Registry) (GithubRegistry, error) {
if cr.Type == common.GithubRegistryType {
// If there's a credential that we need to use, fetch it and create a client for it. // If there's a credential that we need to use, fetch it and create a client for it.
client, err := grp.createGithubClient(cr.CredentialName) httpClient, client, err := grp.createGithubClient(cr.CredentialName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
fMap := ParseRegistryFormat(cr.Format) fMap := ParseRegistryFormat(cr.Format)
if fMap[common.UnversionedRegistry] && fMap[common.OneLevelRegistry] { if fMap[common.UnversionedRegistry] && fMap[common.OneLevelRegistry] {
return NewGithubPackageRegistry(cr.Name, cr.URL, nil, client) return NewGithubPackageRegistry(cr.Name, cr.URL, nil, httpClient, client)
} }
if fMap[common.VersionedRegistry] && fMap[common.CollectionRegistry] { if fMap[common.VersionedRegistry] && fMap[common.CollectionRegistry] {
return NewGithubTemplateRegistry(cr.Name, cr.URL, nil, client) return NewGithubTemplateRegistry(cr.Name, cr.URL, nil, httpClient, client)
} }
return nil, fmt.Errorf("unknown registry format: %s", cr.Format) 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 {
panic(fmt.Errorf("cp is nil: %v", cp))
} }
return &gcsRegistryProvider{cp: cp}
}
return nil, fmt.Errorf("unknown registry type: %s", cr.Type) // 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) {
// If there's a credential that we need to use, fetch it and create a client for it.
if cr.CredentialName == "" {
return nil, fmt.Errorf("No CredentialName specified for %s", cr.Name)
}
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) {
c, err := gcsrp.cp.GetCredential(credentialName)
if err != nil {
log.Printf("Failed to fetch credential %s: %v", credentialName, err)
return nil, fmt.Errorf("Failed to fetch Credential %s: %s", credentialName, err)
}
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
} }
// RE for a registry type that does support versions and has collections. // RE for a registry type that does support versions and has collections.
...@@ -212,6 +278,9 @@ var TemplateRegistryMatcher = regexp.MustCompile("github.com/(.*)/(.*)/(.*)/(.*) ...@@ -212,6 +278,9 @@ var TemplateRegistryMatcher = regexp.MustCompile("github.com/(.*)/(.*)/(.*)/(.*)
// RE for a registry type that does not support versions and does not have collections. // RE for a registry type that does not support versions and does not have collections.
var PackageRegistryMatcher = regexp.MustCompile("github.com/(.*)/(.*)/(.*)") var PackageRegistryMatcher = regexp.MustCompile("github.com/(.*)/(.*)/(.*)")
// 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. // 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: // For now, this means using github types:
// github.com/owner/repo/qualifier/type:version // github.com/owner/repo/qualifier/type:version
...@@ -231,24 +300,31 @@ func IsGithubShortPackageType(t string) bool { ...@@ -231,24 +300,31 @@ func IsGithubShortPackageType(t string) bool {
return PackageRegistryMatcher.MatchString(t) 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 // 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 // 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. // for example), it returns an empty slice.
func GetDownloadURLs(rp RegistryProvider, t string) ([]string, error) { func GetDownloadURLs(rp RegistryProvider, t string) ([]string, Registry, error) {
if IsGithubShortType(t) { if IsGithubShortType(t) {
return ShortTypeToDownloadURLs(rp, t) return ShortTypeToDownloadURLs(rp, t)
} else if IsGithubShortPackageType(t) { } else if IsGithubShortPackageType(t) {
return ShortTypeToPackageDownloadURLs(rp, t) return ShortTypeToPackageDownloadURLs(rp, t)
} else if IsGCSShortType(t) {
return ShortTypeToGCSDownloadUrls(rp, t)
} else if util.IsHttpUrl(t) { } else if util.IsHttpUrl(t) {
result, err := url.Parse(t) result, err := url.Parse(t)
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot parse download URL %s: %s", t, err) return nil, nil, fmt.Errorf("cannot parse download URL %s: %s", t, err)
} }
return []string{result.String()}, nil return []string{result.String()}, nil, nil
} }
return []string{}, nil return []string{}, nil, nil
} }
// ShortTypeToDownloadURLs converts a github URL into downloadable URL from github. // ShortTypeToDownloadURLs converts a github URL into downloadable URL from github.
...@@ -256,15 +332,15 @@ func GetDownloadURLs(rp RegistryProvider, t string) ([]string, error) { ...@@ -256,15 +332,15 @@ func GetDownloadURLs(rp RegistryProvider, t string) ([]string, error) {
// github.com/owner/repo/qualifier/type:version // github.com/owner/repo/qualifier/type:version
// for example: // for example:
// github.com/kubernetes/application-dm-templates/storage/redis:v1 // github.com/kubernetes/application-dm-templates/storage/redis:v1
func ShortTypeToDownloadURLs(rp RegistryProvider, t string) ([]string, error) { func ShortTypeToDownloadURLs(rp RegistryProvider, t string) ([]string, Registry, error) {
m := TemplateRegistryMatcher.FindStringSubmatch(t) m := TemplateRegistryMatcher.FindStringSubmatch(t)
if len(m) != 6 { if len(m) != 6 {
return nil, fmt.Errorf("cannot parse short github url: %s", t) return nil, nil, fmt.Errorf("cannot parse short github url: %s", t)
} }
r, err := rp.GetRegistryByShortURL(t) r, err := rp.GetRegistryByShortURL(t)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
if r == nil { if r == nil {
...@@ -273,15 +349,15 @@ func ShortTypeToDownloadURLs(rp RegistryProvider, t string) ([]string, error) { ...@@ -273,15 +349,15 @@ func ShortTypeToDownloadURLs(rp RegistryProvider, t string) ([]string, error) {
tt, err := NewType(m[3], m[4], m[5]) tt, err := NewType(m[3], m[4], m[5])
if err != nil { if err != nil {
return nil, err return nil, r, err
} }
urls, err := r.GetDownloadURLs(tt) urls, err := r.GetDownloadURLs(tt)
if err != nil { if err != nil {
return nil, err return nil, r, err
} }
return util.ConvertURLsToStrings(urls), err return util.ConvertURLsToStrings(urls), r, err
} }
// ShortTypeToPackageDownloadURLs converts a github URL into downloadable URLs from github. // ShortTypeToPackageDownloadURLs converts a github URL into downloadable URLs from github.
...@@ -289,26 +365,48 @@ func ShortTypeToDownloadURLs(rp RegistryProvider, t string) ([]string, error) { ...@@ -289,26 +365,48 @@ func ShortTypeToDownloadURLs(rp RegistryProvider, t string) ([]string, error) {
// github.com/owner/repo/type // github.com/owner/repo/type
// for example: // for example:
// github.com/helm/charts/cassandra // github.com/helm/charts/cassandra
func ShortTypeToPackageDownloadURLs(rp RegistryProvider, t string) ([]string, error) { func ShortTypeToPackageDownloadURLs(rp RegistryProvider, t string) ([]string, Registry, error) {
m := PackageRegistryMatcher.FindStringSubmatch(t) m := PackageRegistryMatcher.FindStringSubmatch(t)
if len(m) != 4 { if len(m) != 4 {
return nil, fmt.Errorf("Failed to parse short github url: %s", t) return nil, nil, fmt.Errorf("Failed to parse short github url: %s", t)
} }
r, err := rp.GetRegistryByShortURL(t) r, err := rp.GetRegistryByShortURL(t)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
tt, err := NewType("", m[3], "") tt, err := NewType("", m[3], "")
if err != nil { if err != nil {
return nil, err return nil, r, err
} }
urls, err := r.GetDownloadURLs(tt) urls, err := r.GetDownloadURLs(tt)
if err != nil { if err != nil {
return nil, err return nil, r, err
} }
return util.ConvertURLsToStrings(urls), err return util.ConvertURLsToStrings(urls), r, err
}
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
} }
...@@ -22,7 +22,8 @@ import ( ...@@ -22,7 +22,8 @@ import (
func testUrlConversionDriver(rp RegistryProvider, tests map[string]TestURLAndError, t *testing.T) { func testUrlConversionDriver(rp RegistryProvider, tests map[string]TestURLAndError, t *testing.T) {
for in, expected := range tests { for in, expected := range tests {
actual, err := GetDownloadURLs(rp, in) // TODO(vaikas): Test to make sure it's the right registry.
actual, _, err := GetDownloadURLs(rp, in)
if err != expected.Err { if err != expected.Err {
t.Fatalf("failed on: %s : expected error %v but got %v", in, expected.Err, err) t.Fatalf("failed on: %s : expected error %v but got %v", in, expected.Err, err)
} }
...@@ -45,7 +46,8 @@ func TestShortGithubUrlTemplateMapping(t *testing.T) { ...@@ -45,7 +46,8 @@ func TestShortGithubUrlTemplateMapping(t *testing.T) {
} }
grp := NewTestGithubRegistryProvider("github.com/kubernetes/application-dm-templates", githubUrlMaps) grp := NewTestGithubRegistryProvider("github.com/kubernetes/application-dm-templates", githubUrlMaps)
testUrlConversionDriver(NewRegistryProvider(nil, grp, NewInmemCredentialProvider()), tests, t) // TODO(vaikas): XXXX FIXME Add gcsrp
testUrlConversionDriver(NewRegistryProvider(nil, grp, nil, NewInmemCredentialProvider()), tests, t)
} }
func TestShortGithubUrlPackageMapping(t *testing.T) { func TestShortGithubUrlPackageMapping(t *testing.T) {
...@@ -60,5 +62,6 @@ func TestShortGithubUrlPackageMapping(t *testing.T) { ...@@ -60,5 +62,6 @@ func TestShortGithubUrlPackageMapping(t *testing.T) {
} }
grp := NewTestGithubRegistryProvider("github.com/helm/charts", githubUrlMaps) grp := NewTestGithubRegistryProvider("github.com/helm/charts", githubUrlMaps)
testUrlConversionDriver(NewRegistryProvider(nil, grp, NewInmemCredentialProvider()), tests, t) // TODO(vaikas): XXXX FIXME Add gcsrp
testUrlConversionDriver(NewRegistryProvider(nil, grp, nil, NewInmemCredentialProvider()), tests, t)
} }
...@@ -120,8 +120,6 @@ func (scp *SecretsCredentialProvider) SetCredential(name string, credential *com ...@@ -120,8 +120,6 @@ func (scp *SecretsCredentialProvider) SetCredential(name string, credential *com
log.Printf("yaml marshal failed for kubernetes object: %s: %v", name, err) log.Printf("yaml marshal failed for kubernetes object: %s: %v", name, err)
return err return err
} }
log.Printf("Calling with: %s", string(ko)) _, err = scp.k.Create(string(ko))
o, err := scp.k.Create(string(ko))
log.Printf("Create returned: %s", o)
return err return err
} }
...@@ -23,6 +23,7 @@ import ( ...@@ -23,6 +23,7 @@ import (
"github.com/kubernetes/deployment-manager/util" "github.com/kubernetes/deployment-manager/util"
"fmt" "fmt"
"net/http"
"net/url" "net/url"
"regexp" "regexp"
"strings" "strings"
...@@ -53,7 +54,7 @@ func NewTestGithubRegistryProvider(shortURL string, responses map[Type]TestURLAn ...@@ -53,7 +54,7 @@ func NewTestGithubRegistryProvider(shortURL string, responses map[Type]TestURLAn
func (tgrp testGithubRegistryProvider) GetGithubRegistry(cr common.Registry) (GithubRegistry, error) { func (tgrp testGithubRegistryProvider) GetGithubRegistry(cr common.Registry) (GithubRegistry, error) {
trimmed := util.TrimURLScheme(cr.URL) trimmed := util.TrimURLScheme(cr.URL)
if strings.HasPrefix(trimmed, tgrp.shortURL) { if strings.HasPrefix(trimmed, tgrp.shortURL) {
ghr, err := newGithubRegistry(cr.Name, trimmed, cr.Format, nil) ghr, err := newGithubRegistry(cr.Name, trimmed, cr.Format, http.DefaultClient, nil)
if err != nil { if err != nil {
panic(fmt.Errorf("cannot create a github registry: %s", err)) panic(fmt.Errorf("cannot create a github registry: %s", err))
} }
...@@ -80,3 +81,41 @@ func (tgr testGithubRegistry) GetDownloadURLs(t Type) ([]*url.URL, error) { ...@@ -80,3 +81,41 @@ func (tgr testGithubRegistry) GetDownloadURLs(t Type) ([]*url.URL, error) {
return []*url.URL{URL}, result.Err return []*url.URL{URL}, result.Err
} }
func (g testGithubRegistry) Do(req *http.Request) (resp *http.Response, err error) {
return nil, fmt.Errorf("Not implemented yet")
}
type testGCSRegistryProvider struct {
shortURL string
responses map[Type]TestURLAndError
}
type testGCSRegistry struct {
GCSRegistry
responses map[Type]TestURLAndError
}
func NewTestGCSRegistryProvider(shortURL string, responses map[Type]TestURLAndError) GCSRegistryProvider {
return testGCSRegistryProvider{
shortURL: util.TrimURLScheme(shortURL),
responses: responses,
}
}
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))
}
...@@ -72,6 +72,10 @@ type httpClient struct { ...@@ -72,6 +72,10 @@ type httpClient struct {
sleep Sleeper sleep Sleeper
} }
func DefaultHTTPClient() HTTPClient {
return NewHTTPClient(3, http.DefaultClient, NewSleeper())
}
// NewHTTPClient returns a new HTTPClient. // NewHTTPClient returns a new HTTPClient.
func NewHTTPClient(retries uint, c HTTPDoer, s Sleeper) HTTPClient { func NewHTTPClient(retries uint, c HTTPDoer, s Sleeper) HTTPClient {
ret := httpClient{} ret := httpClient{}
......
...@@ -126,15 +126,17 @@ func (k *KubernetesKubectl) execute(args []string, input string) (string, error) ...@@ -126,15 +126,17 @@ func (k *KubernetesKubectl) execute(args []string, input string) (string, error)
cmd.Stderr = combined cmd.Stderr = combined
if err := cmd.Start(); err != nil { if err := cmd.Start(); err != nil {
log.Printf("cannot start kubectl %s %#v", combined.String(), err) e := fmt.Errorf("cannot start kubectl %s %#v", combined.String(), err)
return combined.String(), err log.Printf("%s", e)
return combined.String(), e
} }
if err := cmd.Wait(); err != nil { if err := cmd.Wait(); err != nil {
log.Printf("kubectl failed: %s %#v", combined.String(), err) e := fmt.Errorf("kubectl failed %s", combined.String())
return combined.String(), err log.Printf("%s", e)
return combined.String(), e
} }
log.Printf("kubectl succeeded: SysTime: %v UserTime: %v\n%v", log.Printf("kubectl succeeded: SysTime: %v UserTime: %v",
cmd.ProcessState.SystemTime(), cmd.ProcessState.UserTime(), combined.String()) cmd.ProcessState.SystemTime(), cmd.ProcessState.UserTime())
return combined.String(), nil return combined.String(), nil
} }
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