Commit 7344fed4 authored by Jack Greenfield's avatar Jack Greenfield

Merge pull request #256 from jackgr/url-escapes

Preparation for api cleanup.
parents 6d020901 2cdb34fd
...@@ -166,9 +166,19 @@ func execute() { ...@@ -166,9 +166,19 @@ func execute() {
switch args[0] { switch args[0] {
case "templates": case "templates":
path := fmt.Sprintf("registries/%s/types", *templateRegistry) path := fmt.Sprintf("registries/%s/types", *templateRegistry)
if *regexString != "" {
path += fmt.Sprintf("?%s", url.QueryEscape(*regexString))
}
callService(path, "GET", "list templates", nil) callService(path, "GET", "list templates", nil)
case "describe": case "describe":
describeType(args) if len(args) != 2 {
fmt.Fprintln(os.Stderr, "No type name or URL supplied")
os.Exit(1)
}
path := fmt.Sprintf("types/%s/metadata", url.QueryEscape(args[1]))
callService(path, "GET", "get metadata for type", nil)
case "expand": case "expand":
template := loadTemplate(args) template := loadTemplate(args)
callService("expand", "POST", "expand configuration", marshalTemplate(template)) callService("expand", "POST", "expand configuration", marshalTemplate(template))
...@@ -206,26 +216,19 @@ func execute() { ...@@ -206,26 +216,19 @@ func execute() {
os.Exit(1) os.Exit(1)
} }
path := fmt.Sprintf("deployments/%s", args[1]) path := fmt.Sprintf("deployments/%s", url.QueryEscape(args[1]))
action := fmt.Sprintf("get deployment named %s", args[1]) action := fmt.Sprintf("get deployment named %s", args[1])
callService(path, "GET", action, nil) callService(path, "GET", action, nil)
case "manifest": case "manifest":
msg := "Must specify manifest in the form <deployment>/<manifest> or <deployment> to list." msg := "Must specify manifest in the form <deployment> <manifest> or just <deployment> to list."
if len(args) < 2 { if len(args) < 2 || len(args) > 3 {
fmt.Fprintln(os.Stderr, msg) fmt.Fprintln(os.Stderr, msg)
os.Exit(1) os.Exit(1)
} }
s := strings.Split(args[1], "/") path := fmt.Sprintf("deployments/%s/manifests", url.QueryEscape(args[1]))
ls := len(s) if len(args) > 2 {
if ls < 1 || ls > 2 { path = path + fmt.Sprintf("/%s", url.QueryEscape(args[2]))
fmt.Fprintln(os.Stderr, fmt.Sprintf("Invalid manifest (%s), %s", args[1], msg))
os.Exit(1)
}
path := fmt.Sprintf("deployments/%s/manifests", s[0])
if ls == 2 {
path = path + fmt.Sprintf("/%s", s[1])
} }
action := fmt.Sprintf("get manifest %s", args[1]) action := fmt.Sprintf("get manifest %s", args[1])
...@@ -236,12 +239,12 @@ func execute() { ...@@ -236,12 +239,12 @@ func execute() {
os.Exit(1) os.Exit(1)
} }
path := fmt.Sprintf("deployments/%s", args[1]) path := fmt.Sprintf("deployments/%s", url.QueryEscape(args[1]))
action := fmt.Sprintf("delete deployment named %s", args[1]) action := fmt.Sprintf("delete deployment named %s", args[1])
callService(path, "DELETE", action, nil) callService(path, "DELETE", action, nil)
case "update": case "update":
template := loadTemplate(args) template := loadTemplate(args)
path := fmt.Sprintf("deployments/%s", template.Name) path := fmt.Sprintf("deployments/%s", url.QueryEscape(template.Name))
action := fmt.Sprintf("delete deployment named %s", template.Name) action := fmt.Sprintf("delete deployment named %s", template.Name)
callService(path, "PUT", action, marshalTemplate(template)) callService(path, "PUT", action, marshalTemplate(template))
case "deployed-types": case "deployed-types":
...@@ -252,15 +255,7 @@ func execute() { ...@@ -252,15 +255,7 @@ func execute() {
os.Exit(1) os.Exit(1)
} }
tUrls := getDownloadURLs(args[1]) tURL := args[1]
var tURL = ""
if len(tUrls) == 0 {
// Type is most likely a primitive.
tURL = args[1]
} else {
// TODO(vaikas): Support packages properly.
tURL = tUrls[0]
}
path := fmt.Sprintf("types/%s/instances", url.QueryEscape(tURL)) path := fmt.Sprintf("types/%s/instances", url.QueryEscape(tURL))
action := fmt.Sprintf("list deployed instances of type %s", tURL) action := fmt.Sprintf("list deployed instances of type %s", tURL)
callService(path, "GET", action, nil) callService(path, "GET", action, nil)
...@@ -274,9 +269,14 @@ func execute() { ...@@ -274,9 +269,14 @@ func execute() {
} }
func callService(path, method, action string, reader io.ReadCloser) { func callService(path, method, action string, reader io.ReadCloser) {
u := fmt.Sprintf("%s/%s", *service, path) var URL *url.URL
URL, err := url.Parse(*service)
if err != nil {
panic(fmt.Errorf("cannot parse url (%s): %s\n", path, err))
}
resp := callHTTP(u, method, action, reader) URL.Path += path
resp := callHTTP(URL.String(), method, action, reader)
var j interface{} var j interface{}
if err := json.Unmarshal([]byte(resp), &j); err != nil { if err := json.Unmarshal([]byte(resp), &j); err != nil {
panic(fmt.Errorf("Failed to parse JSON response from service: %s", resp)) panic(fmt.Errorf("Failed to parse JSON response from service: %s", resp))
...@@ -318,42 +318,6 @@ func callHTTP(path, method, action string, reader io.ReadCloser) string { ...@@ -318,42 +318,6 @@ func callHTTP(path, method, action string, reader io.ReadCloser) string {
return string(body) return string(body)
} }
// describeType prints the schema for a type specified by either a
// template URL or a fully qualified registry type name (e.g.,
// <type-name>:<version>)
func describeType(args []string) {
if len(args) != 2 {
fmt.Fprintln(os.Stderr, "No type name or URL supplied")
os.Exit(1)
}
tUrls := getDownloadURLs(url.QueryEscape(args[1]))
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]))
}
if !strings.Contains(tUrls[0], ".prov") {
// It's not a chart, so grab the schema
path := fmt.Sprintf("registries/%s/download?file=%s.schema", *templateRegistry, 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", *templateRegistry, url.QueryEscape(tUrls[0]))
callService(path, "GET", "get file", nil)
}
}
// getDownloadURLs returns URLs for a type in the given registry
func getDownloadURLs(tName string) []string {
path := fmt.Sprintf("%s/registries/%s/types/%s", *service, *templateRegistry, url.QueryEscape(tName))
resp := callHTTP(path, "GET", "get download urls", nil)
u := []string{}
if err := json.Unmarshal([]byte(resp), &u); err != nil {
panic(fmt.Errorf("Failed to parse JSON response from service: %s", resp))
}
return u
}
func loadTemplate(args []string) *common.Template { func loadTemplate(args []string) *common.Template {
var template *common.Template var template *common.Template
var err error var err error
...@@ -433,24 +397,12 @@ func buildTemplateFromType(t string) *common.Template { ...@@ -433,24 +397,12 @@ func buildTemplateFromType(t string) *common.Template {
} }
// Name the deployment after the type name. // Name the deployment after the type name.
name := t template, err := expander.NewTemplateFromType(t, t, props)
config := common.Configuration{Resources: []*common.Resource{&common.Resource{
Name: name,
Type: getDownloadURLs(t)[0],
Properties: props,
}}}
y, err := yaml.Marshal(config)
if err != nil { if err != nil {
panic(fmt.Errorf("error: %s\ncannot create configuration for deployment: %v\n", err, config)) panic(fmt.Errorf("cannot create configuration from type (%s): %s\n", t, err))
} }
return &common.Template{ return template
Name: name,
Content: string(y),
// No imports, as this is a single type from repository.
}
} }
func marshalTemplate(template *common.Template) io.ReadCloser { func marshalTemplate(template *common.Template) io.ReadCloser {
......
...@@ -6,7 +6,7 @@ you may not use this file except in compliance with the License. ...@@ -6,7 +6,7 @@ you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
...@@ -46,6 +46,30 @@ func NewExpander(binary string) Expander { ...@@ -46,6 +46,30 @@ func NewExpander(binary string) Expander {
return &expander{binary} return &expander{binary}
} }
// NewTemplateFromType creates and returns a new template whose content
// is a YAML marshaled resource assembled from the supplied arguments.
func NewTemplateFromType(name, typeName string, properties map[string]interface{}) (*common.Template, error) {
resource := &common.Resource{
Name: name,
Type: typeName,
Properties: properties,
}
config := common.Configuration{Resources: []*common.Resource{resource}}
content, err := yaml.Marshal(config)
if err != nil {
return nil, fmt.Errorf("error: %s\ncannot marshal configuration: %v\n", err, config)
}
template := &common.Template{
Name: name,
Content: string(content),
Imports: []*common.ImportFile{},
}
return template, nil
}
// NewTemplateFromArchive creates and returns a new template whose content // NewTemplateFromArchive creates and returns a new template whose content
// and imported files are read from the supplied archive. // and imported files are read from the supplied archive.
func NewTemplateFromArchive(name string, r io.Reader, importFileNames []string) (*common.Template, error) { func NewTemplateFromArchive(name string, r io.Reader, importFileNames []string) (*common.Template, error) {
......
...@@ -6,7 +6,7 @@ you may not use this file except in compliance with the License. ...@@ -6,7 +6,7 @@ you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
...@@ -28,6 +28,7 @@ import ( ...@@ -28,6 +28,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/ghodss/yaml"
"github.com/kubernetes/deployment-manager/common" "github.com/kubernetes/deployment-manager/common"
) )
...@@ -107,6 +108,33 @@ func testExpandTemplateFromFile(t *testing.T, fileName, baseName string, importF ...@@ -107,6 +108,33 @@ func testExpandTemplateFromFile(t *testing.T, fileName, baseName string, importF
expandAndVerifyOutput(t, actualOutput, description) expandAndVerifyOutput(t, actualOutput, description)
} }
var (
testTemplateName = "expandybird"
testTemplateType = "replicatedservice.py"
testTemplateProperties = `
service_port: 8080
target_port: 8080
container_port: 8080
external_service: true
replicas: 3
image: gcr.io/dm-k8s-testing/expandybird
labels:
app: expandybird
`
)
func TestNewTemplateFromType(t *testing.T) {
var properties map[string]interface{}
if err := yaml.Unmarshal([]byte(testTemplateProperties), &properties); err != nil {
t.Fatalf("cannot unmarshal test data: %s", err)
}
_, err := NewTemplateFromType(testTemplateName, testTemplateType, properties)
if err != nil {
t.Fatalf("cannot create template from type %s: %s", testTemplateType, err)
}
}
func TestNewTemplateFromReader(t *testing.T) { func TestNewTemplateFromReader(t *testing.T) {
r := bytes.NewReader([]byte{}) r := bytes.NewReader([]byte{})
if _, err := NewTemplateFromReader("test", r, nil); err == nil { if _, err := NewTemplateFromReader("test", r, nil); err == nil {
......
...@@ -53,6 +53,8 @@ var deployments = []Route{ ...@@ -53,6 +53,8 @@ var deployments = []Route{
{"Expand", "/expand", "POST", expandHandlerFunc, ""}, {"Expand", "/expand", "POST", expandHandlerFunc, ""},
{"ListTypes", "/types", "GET", listTypesHandlerFunc, ""}, {"ListTypes", "/types", "GET", listTypesHandlerFunc, ""},
{"ListTypeInstances", "/types/{type}/instances", "GET", listTypeInstancesHandlerFunc, ""}, {"ListTypeInstances", "/types/{type}/instances", "GET", listTypeInstancesHandlerFunc, ""},
{"GetRegistryForType", "/types/{type}/registry", "GET", getRegistryForTypeHandlerFunc, ""},
{"GetMetadataForType", "/types/{type}/metadata", "GET", getMetadataForTypeHandlerFunc, ""},
{"ListRegistries", "/registries", "GET", listRegistriesHandlerFunc, ""}, {"ListRegistries", "/registries", "GET", listRegistriesHandlerFunc, ""},
{"GetRegistry", "/registries/{registry}", "GET", getRegistryHandlerFunc, ""}, {"GetRegistry", "/registries/{registry}", "GET", getRegistryHandlerFunc, ""},
{"CreateRegistry", "/registries/{registry}", "POST", createRegistryHandlerFunc, "JSON"}, {"CreateRegistry", "/registries/{registry}", "POST", createRegistryHandlerFunc, "JSON"},
...@@ -378,6 +380,40 @@ func listTypeInstancesHandlerFunc(w http.ResponseWriter, r *http.Request) { ...@@ -378,6 +380,40 @@ func listTypeInstancesHandlerFunc(w http.ResponseWriter, r *http.Request) {
util.LogHandlerExitWithJSON(handler, w, instances, http.StatusOK) util.LogHandlerExitWithJSON(handler, w, instances, http.StatusOK)
} }
func getRegistryForTypeHandlerFunc(w http.ResponseWriter, r *http.Request) {
handler := "manager: get type registry"
util.LogHandlerEntry(handler, r)
typeName, err := getPathVariable(w, r, "type", handler)
if err != nil {
return
}
registry, err := backend.GetRegistryForType(typeName)
if err != nil {
util.LogAndReturnError(handler, http.StatusBadRequest, err, w)
return
}
util.LogHandlerExitWithJSON(handler, w, registry, http.StatusOK)
}
func getMetadataForTypeHandlerFunc(w http.ResponseWriter, r *http.Request) {
handler := "manager: get type metadata"
util.LogHandlerEntry(handler, r)
typeName, err := getPathVariable(w, r, "type", handler)
if err != nil {
return
}
metadata, err := backend.GetMetadataForType(typeName)
if err != nil {
util.LogAndReturnError(handler, http.StatusBadRequest, err, w)
return
}
util.LogHandlerExitWithJSON(handler, w, metadata, http.StatusOK)
}
// Putting Registry handlers here for now because deployments.go // Putting Registry handlers here for now because deployments.go
// currently owns its own Manager backend and doesn't like to share. // currently owns its own Manager backend and doesn't like to share.
func listRegistriesHandlerFunc(w http.ResponseWriter, r *http.Request) { func listRegistriesHandlerFunc(w http.ResponseWriter, r *http.Request) {
...@@ -459,12 +495,18 @@ func listRegistryTypesHandlerFunc(w http.ResponseWriter, r *http.Request) { ...@@ -459,12 +495,18 @@ func listRegistryTypesHandlerFunc(w http.ResponseWriter, r *http.Request) {
return return
} }
values, err := url.ParseQuery(r.URL.RawQuery)
if err != nil {
util.LogAndReturnError(handler, http.StatusBadRequest, err, w)
return
}
var regex *regexp.Regexp var regex *regexp.Regexp
regexString, err := getPathVariable(w, r, "regex", handler) regexString := values.Get("regex")
if err == nil { if regexString != "" {
regex, err = regexp.Compile(regexString) regex, err = regexp.Compile(regexString)
if err != nil { if err != nil {
util.LogAndReturnError(handler, http.StatusInternalServerError, err, w) util.LogAndReturnError(handler, http.StatusBadRequest, err, w)
return return
} }
} }
......
...@@ -21,6 +21,7 @@ import ( ...@@ -21,6 +21,7 @@ import (
"log" "log"
"net/url" "net/url"
"regexp" "regexp"
"strings"
"time" "time"
"github.com/kubernetes/deployment-manager/common" "github.com/kubernetes/deployment-manager/common"
...@@ -46,6 +47,8 @@ type Manager interface { ...@@ -46,6 +47,8 @@ type Manager interface {
// Types // Types
ListTypes() ([]string, error) ListTypes() ([]string, error)
ListInstances(typeName string) ([]*common.TypeInstance, error) ListInstances(typeName string) ([]*common.TypeInstance, error)
GetRegistryForType(typeName string) (string, error)
GetMetadataForType(typeName string) (string, error)
// Registries // Registries
ListRegistries() ([]*common.Registry, error) ListRegistries() ([]*common.Registry, error)
...@@ -334,6 +337,42 @@ func (m *manager) ListInstances(typeName string) ([]*common.TypeInstance, error) ...@@ -334,6 +337,42 @@ func (m *manager) ListInstances(typeName string) ([]*common.TypeInstance, error)
return m.repository.GetTypeInstances(typeName) return m.repository.GetTypeInstances(typeName)
} }
// GetRegistryForType returns the registry where a type resides.
func (m *manager) GetRegistryForType(typeName string) (string, error) {
_, r, err := registry.GetDownloadURLs(m.registryProvider, typeName)
if err != nil {
return "", err
}
return r.GetRegistryName(), nil
}
// GetMetadataForType returns the metadata for type.
func (m *manager) GetMetadataForType(typeName string) (string, error) {
URLs, r, err := registry.GetDownloadURLs(m.registryProvider, typeName)
if err != nil {
return "", err
}
if len(URLs) < 1 {
return "", nil
}
// If it's a chart, we want the provenance file
fPath := URLs[0]
if !strings.Contains(fPath, ".prov") {
// It's not a chart, so we want the schema
fPath += ".schema"
}
metadata, err := getFileFromRegistry(fPath, r)
if err != nil {
return "", fmt.Errorf("cannot get metadata for type (%s): %s", typeName, err)
}
return metadata, nil
}
// ListRegistries returns the list of registries // ListRegistries returns the list of registries
func (m *manager) ListRegistries() ([]*common.Registry, error) { func (m *manager) ListRegistries() ([]*common.Registry, error) {
return m.service.List() return m.service.List()
...@@ -403,11 +442,16 @@ func (m *manager) GetFile(registryName string, url string) (string, error) { ...@@ -403,11 +442,16 @@ func (m *manager) GetFile(registryName string, url string) (string, error) {
return "", err return "", err
} }
return getFileFromRegistry(url, r)
}
func getFileFromRegistry(url string, r registry.Registry) (string, error) {
getter := util.NewHTTPClient(3, r, util.NewSleeper()) getter := util.NewHTTPClient(3, r, util.NewSleeper())
body, _, err := getter.Get(url) body, _, err := getter.Get(url)
if err != nil { if err != nil {
return "", err return "", err
} }
return body, nil return body, 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