diff --git a/common/types.go b/common/types.go index 35a44b0be..0e673a0dd 100644 --- a/common/types.go +++ b/common/types.go @@ -167,6 +167,14 @@ type KubernetesObject struct { Spec map[string]interface{} `json:"spec"` } +// KubernetesSecret represents a Kubernetes secret +type KubernetesSecret struct { + Kind string `json:"kind"` + ApiVersion string `json:"apiVersion"` + Metadata map[string]string `json:"metadata"` + Data map[string]string `json:"data,omitempty"` +} + // Repository related types type BasicAuthCredential struct { Username string `json:"username"` @@ -186,7 +194,7 @@ type RegistryCredential struct { 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:"name,omitempty"` // URL to the root of 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 } diff --git a/dm/dm.go b/dm/dm.go index 8e461d316..9b77a55d4 100644 --- a/dm/dm.go +++ b/dm/dm.go @@ -21,8 +21,6 @@ import ( "github.com/kubernetes/deployment-manager/common" "github.com/kubernetes/deployment-manager/expandybird/expander" - "github.com/kubernetes/deployment-manager/registry" - "github.com/kubernetes/deployment-manager/util" "archive/tar" "bytes" @@ -35,8 +33,6 @@ import ( "net/http" "net/url" "os" - "path" - "regexp" "strconv" "strings" "time" @@ -46,10 +42,10 @@ var ( deployment_name = flag.String("name", "", "Name of deployment, used for deploy and update commands (defaults to template name)") stdin = flag.Bool("stdin", false, "Reads a configuration from the standard input") properties = flag.String("properties", "", "Properties to use when deploying a template (e.g., --properties k1=v1,k2=v2)") - template_registry = flag.String("registry", "github.com/kubernetes/application-dm-templates", "Registry (github.com/owner/repo)") + template_registry = flag.String("registry", "application-dm-templates", "Registry name") service = flag.String("service", "http://localhost:8001/api/v1/proxy/namespaces/dm/services/manager-service:manager", "URL for deployment manager") binary = flag.String("binary", "../expandybird/expansion/expansion.py", "Path to template expansion binary") - timeout = flag.Int("timeout", 10, "Time in seconds to wait for response") + timeout = flag.Int("timeout", 20, "Time in seconds to wait for response") regex_string = flag.String("regex", "", "Regular expression to filter the templates listed in a template registry") 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") @@ -67,6 +63,7 @@ var commands = []string{ "deployed-types \t\t Lists the types deployed in the cluster", "deployed-instances \t Lists the instances of the named type deployed in the cluster", "templates \t\t Lists the templates in a given template registry", + "registries \t\t Lists the registries available", "describe \t\t Describes the named template in a given template registry", } @@ -87,45 +84,6 @@ var usage = func() { panic("\n") } -// TODO(jackgr): Move all registry related operations to the server side. -var registryProvider registry.RegistryProvider - -func getRegistryProvider() registry.RegistryProvider { - if registryProvider == nil { - rs := registry.NewInmemRegistryService() - _, err := rs.GetByURL(*template_registry) - if err != nil { - r := newRegistry(*template_registry) - if err := rs.Create(r); err != nil { - panic(fmt.Errorf("cannot configure registry at %s: %s", r.URL, err)) - } - } - - cp := registry.NewInmemCredentialProvider() - credential := getGithubCredential() - if credential != nil { - if err := cp.SetCredential("default", credential); err != nil { - panic(fmt.Errorf("cannot set credential at %s: %s", "default", err)) - } - } - - registryProvider = registry.NewRegistryProvider(rs, nil, cp) - } - - return registryProvider -} - -func newRegistry(URL string) *common.Registry { - tFormat := fmt.Sprintf("%s;%s", common.VersionedRegistry, common.CollectionRegistry) - return &common.Registry{ - Name: util.TrimURLScheme(URL), - Type: common.GithubRegistryType, - URL: URL, - Format: common.RegistryFormat(tFormat), - CredentialName: "default", - } -} - func getGithubCredential() *common.RegistryCredential { *apitoken = strings.TrimSpace(*apitoken) if *apitoken == "" { @@ -160,16 +118,6 @@ func getGithubCredential() *common.RegistryCredential { return nil } -func getGithubRegistry() registry.Registry { - provider := getRegistryProvider() - git, err := provider.GetRegistryByShortURL(*template_registry) - if err != nil { - panic(fmt.Errorf("cannot open registry %s: %s", *template_registry, err)) - } - - return git -} - func main() { defer func() { result := recover() @@ -192,32 +140,8 @@ func execute() { switch args[0] { case "templates": - var regex *regexp.Regexp - if *regex_string != "" { - var err error - regex, err = regexp.Compile(*regex_string) - if err != nil { - panic(fmt.Errorf("cannot compile regular expression %s: %s", *regex_string, err)) - } - } - - git := getGithubRegistry() - types, err := git.ListTypes(regex) - if err != nil { - panic(fmt.Errorf("cannot list templates in registry %s: %s", *template_registry, err)) - } - - for _, t := range types { - fmt.Printf("%s\n", t.String()) - urls, err := git.GetDownloadURLs(t) - if err != nil { - panic(fmt.Errorf("cannot get download urls for %s: %s", t, err)) - } - - for _, downloadURL := range urls { - fmt.Printf("\t%s\n", downloadURL) - } - } + path := fmt.Sprintf("registries/%s/types", args[1]) + callService(path, "GET", "list templates", nil) case "describe": describeType(args) case "expand": @@ -293,6 +217,8 @@ func execute() { path := fmt.Sprintf("types/%s/instances", url.QueryEscape(tUrl)) action := fmt.Sprintf("list deployed instances of type %s", tUrl) callService(path, "GET", action, nil) + case "registries": + callService("registries", "GET", "list registries", nil) default: usage() } @@ -361,16 +287,19 @@ func describeType(args []string) { fmt.Println(callHttp(schemaUrl, "GET", "get schema for type ("+tUrls[0]+")", nil)) } -// getDownloadURLs returns URLs or empty list if a primitive type. +// getDownloadURLs returns URLs for a type in the given registry func getDownloadURLs(tName string) []string { - qName := path.Join(*template_registry, tName) - provider := getRegistryProvider() - result, err := registry.GetDownloadURLs(provider, qName) - if err != nil { - panic(fmt.Errorf("cannot get URLs for %s: %s\n", tName, err)) + path := fmt.Sprintf("%s/registries/%s/types/%s", *service, *template_registry, 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 result + urls := []string{} + for _, url := range u { + urls = append(urls, url) + } + return urls } func loadTemplate(args []string) *common.Template { @@ -408,13 +337,13 @@ func loadTemplate(args []string) *common.Template { // See if the first argument is a local file. It could either be a type, or it could be a configuration. If // it's a local file, it's configuration. if _, err := os.Stat(args[1]); err == nil { - template, err = expander.NewTemplateFromFileNames(args[1], args[2:]) - } else { - if t, err := registry.ParseType(args[1]); err == nil { - template = buildTemplateFromType(t) + if len(args) > 2 { + template, err = expander.NewTemplateFromFileNames(args[1], args[2:]) } else { template, err = expander.NewTemplateFromRootTemplate(args[1]) } + } else { + template = buildTemplateFromType(args[1]) } if err != nil { @@ -430,7 +359,7 @@ func loadTemplate(args []string) *common.Template { return template } -func buildTemplateFromType(t registry.Type) *common.Template { +func buildTemplateFromType(t string) *common.Template { props := make(map[string]interface{}) if *properties != "" { plist := strings.Split(*properties, ",") @@ -452,11 +381,11 @@ func buildTemplateFromType(t registry.Type) *common.Template { } // Name the deployment after the type name. - name := fmt.Sprintf("%s:%s", t.Name, t.GetVersion()) + name := t config := common.Configuration{Resources: []*common.Resource{&common.Resource{ Name: name, - Type: getDownloadURLs(t.String())[0], + Type: getDownloadURLs(t)[0], Properties: props, }}} diff --git a/examples/bootstrap/bootstrap.sh b/examples/bootstrap/bootstrap.sh index 264bc88d0..ad6222a3a 100755 --- a/examples/bootstrap/bootstrap.sh +++ b/examples/bootstrap/bootstrap.sh @@ -38,7 +38,7 @@ if [[ -z $MANAGER ]] ; then exit 1 fi pkill -f $MANAGER -nohup $MANAGER > $LOGDIR/manager.log 2>&1 --port=8080 --expanderURL=http://localhost:8081 --deployerURL=http://localhost:8082 & +nohup $MANAGER > $LOGDIR/manager.log 2>&1 --port=8080 --kubectl=$KUBECTL --expanderURL=http://localhost:8081 --deployerURL=http://localhost:8082 & echo echo "Creating dm namespace..." diff --git a/manager/Dockerfile b/manager/Dockerfile index 093a4de83..ca13c5b3a 100644 --- a/manager/Dockerfile +++ b/manager/Dockerfile @@ -15,6 +15,17 @@ FROM golang:1.4 MAINTAINER Jack Greenfield +RUN apt-get update \ + && apt-get autoremove -y \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* + +WORKDIR /usr/local/bin +ENV KUBE_VERSION v1.0.5 + +RUN curl -fsSL -o kubectl https://storage.googleapis.com/kubernetes-release/release/$KUBE_VERSION/bin/linux/amd64/kubectl \ + && chmod +x kubectl + RUN mkdir -p "$GOPATH/src/github.com" && chmod 777 "$GOPATH/src/github.com" WORKDIR "$GOPATH/src/github.com" @@ -37,4 +48,4 @@ RUN go-wrapper install github.com/kubernetes/deployment-manager/manager/... EXPOSE 8080 -ENTRYPOINT ["bin/manager"] +ENTRYPOINT ["bin/manager", "--kubectl=/usr/local/bin/kubectl"] diff --git a/manager/deployments.go b/manager/deployments.go index c2440fca3..42ee00a22 100644 --- a/manager/deployments.go +++ b/manager/deployments.go @@ -61,12 +61,13 @@ var deployments = []Route{ } var ( - maxLength = flag.Int64("maxLength", 1024, "The maximum length (KB) of a template.") - expanderName = flag.String("expander", "expandybird-service", "The DNS name of the expander service.") - expanderURL = flag.String("expanderURL", "", "The URL for the expander service.") - deployerName = flag.String("deployer", "resourcifier-service", "The DNS name of the deployer service.") - deployerURL = flag.String("deployerURL", "", "The URL for the deployer service.") - credentialFile = flag.String("credentialFile", "", "Local file to use for credentials.") + maxLength = flag.Int64("maxLength", 1024, "The maximum length (KB) of a template.") + expanderName = flag.String("expander", "expandybird-service", "The DNS name of the expander service.") + expanderURL = flag.String("expanderURL", "", "The URL for the expander service.") + deployerName = flag.String("deployer", "resourcifier-service", "The DNS name of the deployer service.") + deployerURL = flag.String("deployerURL", "", "The URL for the deployer service.") + credentialFile = flag.String("credentialFile", "", "Local file to use for credentials.") + credentialSecrets = flag.Bool("credentialSecrets", true, "Use secrets for credentials.") ) var backend manager.Manager @@ -84,6 +85,8 @@ func init() { if err != nil { panic(fmt.Errorf("cannot create credential provider %s: %s", *credentialFile, err)) } + } else if *credentialSecrets { + credentialProvider = registry.NewSecretsCredentialProvider() } else { credentialProvider = registry.NewInmemCredentialProvider() } @@ -424,7 +427,11 @@ func getDownloadURLsHandlerFunc(w http.ResponseWriter, r *http.Request) { return } - util.LogHandlerExitWithJSON(handler, w, c, http.StatusOK) + var urls = []string{} + for _, u := range c { + urls = append(urls, u.String()) + } + util.LogHandlerExitWithJSON(handler, w, urls, http.StatusOK) } func getCredential(w http.ResponseWriter, r *http.Request, handler string) *common.RegistryCredential { diff --git a/manager/main.go b/manager/main.go index e455cc413..5296c58fa 100644 --- a/manager/main.go +++ b/manager/main.go @@ -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 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. @@ -43,6 +43,12 @@ var routes = []Route{} // port to listen on var port = flag.Int("port", 8080, "The port to listen on") +func init() { + if !flag.Parsed() { + flag.Parse() + } +} + func main() { if !flag.Parsed() { flag.Parse() diff --git a/registry/filebased_credential_provider.go b/registry/filebased_credential_provider.go index bad310e1f..a14931c33 100644 --- a/registry/filebased_credential_provider.go +++ b/registry/filebased_credential_provider.go @@ -23,13 +23,7 @@ import ( "github.com/ghodss/yaml" "github.com/kubernetes/deployment-manager/common" - - /* - "net/url" - "regexp" - "strings" - "sync" - */) +) // CredentialProvider provides credentials for registries. type FilebasedCredentialProvider struct { diff --git a/registry/github_package_registry.go b/registry/github_package_registry.go index 00be7054b..5d91147ef 100644 --- a/registry/github_package_registry.go +++ b/registry/github_package_registry.go @@ -125,6 +125,9 @@ func (g GithubPackageRegistry) GetDownloadURLs(t Type) ([]*url.URL, error) { } } + for _, ll := range downloadURLs { + log.Printf("Found DOWNLOAD URL AS: %#v", ll.String()) + } return downloadURLs, nil } diff --git a/registry/registry.go b/registry/registry.go index 0f703f5e4..fa690d019 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -59,7 +59,7 @@ type GithubRegistry interface { type Type struct { Collection string Name string - version SemVer + Version SemVer } // NewType initializes a type @@ -98,7 +98,7 @@ func (t Type) String() string { // GetVersion returns the type version with the letter "v" prepended. func (t Type) GetVersion() string { var result string - version := t.version.String() + version := t.Version.String() if version != "0" { result = "v" + version } @@ -115,7 +115,7 @@ func (t *Type) SetVersion(version string) error { return err } - t.version = s + t.Version = s return nil } diff --git a/registry/secrets_credential_provider.go b/registry/secrets_credential_provider.go new file mode 100644 index 000000000..93c76c68b --- /dev/null +++ b/registry/secrets_credential_provider.go @@ -0,0 +1,127 @@ +/* +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/deployment-manager/common" + "github.com/kubernetes/deployment-manager/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" + +// CredentialProvider provides credentials for registries. +type SecretsCredentialProvider struct { + // Actual object that talks to secrets service. + k util.Kubernetes +} + +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 common.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 +} + +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) +} + +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 := &common.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 + } + log.Printf("Calling with: %s", string(ko)) + o, err := scp.k.Create(string(ko)) + log.Printf("Create returned: %s", o) + return err +} diff --git a/util/kubernetes.go b/util/kubernetes.go new file mode 100644 index 000000000..797f0cd1c --- /dev/null +++ b/util/kubernetes.go @@ -0,0 +1,41 @@ +/* +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 util + +// KubernetesConfiguration defines the configuration options for talking to Kubernetes master +type KubernetesConfig struct { + KubePath string // The path to kubectl binary + KubeService string // DNS name of the kubernetes service + KubeServer string // The IP address and optional port of the kubernetes master + KubeInsecure bool // Do not check the server's certificate for validity + KubeConfig string // Path to a kubeconfig file + KubeCertAuth string // Path to a file for the certificate authority + KubeClientCert string // Path to a client certificate file + KubeClientKey string // Path to a client key file + KubeToken string // A service account token + KubeUsername string // The username to use for basic auth + KubePassword string // The password to use for basic auth +} + +// Kubernetes defines the interface for talking to Kubernetes. Currently the +// only implementation is through kubectl, but eventually this could be done +// via direct API calls. +type Kubernetes interface { + Get(name string, resourceType string) (string, error) + Create(resource string) (string, error) + Delete(name string, resourceType string) (string, error) +} diff --git a/util/kubernetes_kubectl.go b/util/kubernetes_kubectl.go new file mode 100644 index 000000000..92e79dcf0 --- /dev/null +++ b/util/kubernetes_kubectl.go @@ -0,0 +1,136 @@ +/* +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 util + +import ( + "bytes" + "fmt" + "log" + "net" + "os" + "os/exec" +) + +// KubernetesKubectl implements the interface for talking to Kubernetes by wrapping calls +// via kubectl. +type KubernetesKubectl struct { + KubePath string + // Base level arguments to kubectl. User commands/arguments get appended to this. + Arguments []string +} + +func NewKubernetesKubectl(config *KubernetesConfig) Kubernetes { + if config.KubePath == "" { + log.Fatalf("kubectl path cannot be empty") + } + // If a configuration file is specified, then it will provide the server + // address and credentials. If not, then we check for the server address + // and credentials as individual flags. + var args []string + if config.KubeConfig != "" { + config.KubeConfig = os.ExpandEnv(config.KubeConfig) + args = append(args, fmt.Sprintf("--kubeconfig=%s", config.KubeConfig)) + } else { + if config.KubeServer != "" { + args = append(args, fmt.Sprintf("--server=https://%s", config.KubeServer)) + } else if config.KubeService != "" { + addrs, err := net.LookupHost(config.KubeService) + if err != nil || len(addrs) < 1 { + log.Fatalf("cannot resolve DNS name: %v", config.KubeService) + } + + args = append(args, fmt.Sprintf("--server=https://%s", addrs[0])) + } + + if config.KubeInsecure { + args = append(args, fmt.Sprintf("--insecure-skip-tls-verify=%s", config.KubeInsecure)) + } else { + if config.KubeCertAuth != "" { + args = append(args, fmt.Sprintf("--certificate-authority=%s", config.KubeCertAuth)) + if config.KubeClientCert == "" { + args = append(args, fmt.Sprintf("--client-certificate=%s", config.KubeClientCert)) + } + + if config.KubeClientKey == "" { + args = append(args, fmt.Sprintf("--client-key=%s", config.KubeClientKey)) + } + } + } + if config.KubeToken != "" { + args = append(args, fmt.Sprintf("--token=%s", config.KubeToken)) + } else { + if config.KubeUsername != "" { + args = append(args, fmt.Sprintf("--username=%s", config.KubeUsername)) + } + + if config.KubePassword != "" { + args = append(args, fmt.Sprintf("--password=%s", config.KubePassword)) + } + } + } + return &KubernetesKubectl{config.KubePath, args} +} + +func (k *KubernetesKubectl) Get(name string, resourceType string) (string, error) { + args := []string{"get"} + // Specify output as json rather than human readable for easier machine parsing + args = append(args, "-o", "json") + args = append(args, resourceType) + args = append(args, name) + return k.execute(args, "") +} + +func (k *KubernetesKubectl) Create(resource string) (string, error) { + args := []string{"create"} + // args = append(args, resourceType) + // args = append(args, name) + return k.execute(args, resource) +} + +func (k *KubernetesKubectl) Delete(name string, resourceType string) (string, error) { + args := []string{"delete"} + args = append(args, resourceType) + args = append(args, name) + return k.execute(args, "") +} + +func (k *KubernetesKubectl) execute(args []string, input string) (string, error) { + if len(input) > 0 { + args = append(args, "-f", "-") + } + + // Tack on the common arguments to the end of the command line + args = append(args, k.Arguments...) + cmd := exec.Command(k.KubePath, args...) + cmd.Stdin = bytes.NewBuffer([]byte(input)) + + // Combine stdout and stderr into a single dynamically resized buffer + combined := &bytes.Buffer{} + cmd.Stdout = combined + cmd.Stderr = combined + + if err := cmd.Start(); err != nil { + log.Printf("cannot start kubectl %#v", err) + return combined.String(), err + } + + if err := cmd.Wait(); err != nil { + log.Printf("kubectl failed: %#v", err) + return combined.String(), err + } + return combined.String(), nil +}