summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xclover/cloverctl/build.sh1
-rw-r--r--clover/cloverctl/src/cloverctl/cmd/create_docker_registry.go57
-rw-r--r--clover/cloverctl/src/cloverctl/cmd/create_kubernetes.go87
-rw-r--r--clover/cloverctl/src/cloverctl/cmd/delete_docker_registry.go52
-rw-r--r--clover/cloverctl/src/cloverctl/cmd/delete_kubernetes.go53
-rw-r--r--clover/cloverctl/src/cloverctl/cmd/get_docker_registry.go59
-rw-r--r--clover/cloverctl/src/cloverctl/cmd/get_kubernetes.go59
-rw-r--r--clover/cloverctl/src/cloverctl/cmd/provider.go45
-rw-r--r--clover/cloverctl/src/cloverkube/main.go100
-rw-r--r--clover/controller/control/api/halyard.py167
-rw-r--r--clover/controller/control/control.py2
-rw-r--r--clover/controller/docker/Dockerfile3
-rwxr-xr-xclover/controller/process/gunicorn_process.sh5
-rw-r--r--clover/controller/process/nginx.conf1
-rw-r--r--clover/orchestration/kube_client.py34
-rw-r--r--clover/spinnaker/__init__.py0
-rwxr-xr-xclover/spinnaker/halyard.py120
-rw-r--r--clover/spinnaker/halyard_sample.py41
-rw-r--r--clover/spinnaker/install/quick-install-spinnaker.yml376
-rw-r--r--clover/spinnaker/lib/__init__.py0
-rwxr-xr-xclover/spinnaker/lib/halyard_base.py157
-rw-r--r--clover/tools/yaml/cassandra.yaml2
22 files changed, 1416 insertions, 5 deletions
diff --git a/clover/cloverctl/build.sh b/clover/cloverctl/build.sh
index 2f7be14..e67e4da 100755
--- a/clover/cloverctl/build.sh
+++ b/clover/cloverctl/build.sh
@@ -25,6 +25,7 @@ go get github.com/tools/godep
go get -u github.com/spf13/cobra/cobra
go get -u gopkg.in/resty.v1
+go get k8s.io/apimachinery/pkg/runtime
go get k8s.io/client-go/...
cd $GOPATH/src/k8s.io/client-go
git checkout $CLIENTGOVERSION
diff --git a/clover/cloverctl/src/cloverctl/cmd/create_docker_registry.go b/clover/cloverctl/src/cloverctl/cmd/create_docker_registry.go
new file mode 100644
index 0000000..77045f6
--- /dev/null
+++ b/clover/cloverctl/src/cloverctl/cmd/create_docker_registry.go
@@ -0,0 +1,57 @@
+// Copyright (c) Authors of Clover
+//
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Apache License, Version 2.0
+// which accompanies this distribution, and is available at
+// http://www.apache.org/licenses/LICENSE-2.0
+
+package cmd
+
+import (
+ "fmt"
+ "io/ioutil"
+
+ "gopkg.in/resty.v1"
+ "github.com/ghodss/yaml"
+ "github.com/spf13/cobra"
+)
+
+
+var dockerregistryCmd = &cobra.Command{
+ Use: "docker-registry",
+ Short: "Add one docker registry from yaml file to spinnaker",
+ Long: ``,
+ Run: func(cmd *cobra.Command, args []string) {
+ createDockerRegistry()
+ },
+}
+
+func init() {
+ providercreateCmd.AddCommand(dockerregistryCmd)
+ dockerregistryCmd.Flags().StringVarP(&cloverFile, "file", "f", "", "Input yaml file to add kubernetes provider")
+ dockerregistryCmd.MarkFlagRequired("file")
+
+}
+
+func createDockerRegistry() {
+ url := controllerIP + "/halyard/addregistry"
+ in, err := ioutil.ReadFile(cloverFile)
+ if err != nil {
+ fmt.Println("Please specify a valid rule definition yaml file")
+ return
+ }
+ out_json, err := yaml.YAMLToJSON(in)
+ if err != nil {
+ panic(err.Error())
+ }
+
+ resp, err := resty.R().
+ SetHeader("Content-Type", "application/json").
+ SetBody(out_json).
+ Post(url)
+ if err != nil {
+ panic(err.Error())
+ }
+ fmt.Printf("\n%v\n", resp)
+
+}
diff --git a/clover/cloverctl/src/cloverctl/cmd/create_kubernetes.go b/clover/cloverctl/src/cloverctl/cmd/create_kubernetes.go
new file mode 100644
index 0000000..7311090
--- /dev/null
+++ b/clover/cloverctl/src/cloverctl/cmd/create_kubernetes.go
@@ -0,0 +1,87 @@
+// Copyright (c) Authors of Clover
+//
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Apache License, Version 2.0
+// which accompanies this distribution, and is available at
+// http://www.apache.org/licenses/LICENSE-2.0
+
+package cmd
+
+import (
+ "fmt"
+ "time"
+ "io/ioutil"
+ "strings"
+
+ "gopkg.in/resty.v1"
+ "github.com/ghodss/yaml"
+ "github.com/spf13/cobra"
+ "cloverkube"
+)
+
+type Kubernetes struct {
+ Name string
+ ProviderVersion string
+ KubeconfigFile string
+ DockerRegistries []DockerRegistry
+}
+
+type DockerRegistry struct {
+ AccountName string
+}
+
+
+var kubeproviderCmd = &cobra.Command{
+ Use: "kubernetes",
+ Short: "Add one kubernete provider from yaml file to spinnaker",
+ Long: ``,
+ Run: func(cmd *cobra.Command, args []string) {
+ createProvider()
+ },
+}
+
+func init() {
+ providercreateCmd.AddCommand(kubeproviderCmd)
+ kubeproviderCmd.Flags().StringVarP(&cloverFile, "file", "f", "", "Input yaml file to add kubernetes provider")
+ kubeproviderCmd.MarkFlagRequired("file")
+
+}
+
+func createProvider() {
+ url := controllerIP + "/halyard/addkube"
+ in, err := ioutil.ReadFile(cloverFile)
+ if err != nil {
+ fmt.Println("Please specify a valid rule definition yaml file")
+ return
+ }
+
+ t := Kubernetes{}
+ yaml.Unmarshal(in, &t)
+ if(t.KubeconfigFile==""){
+ fmt.Println("error")
+ return;
+ }
+ filename := t.KubeconfigFile
+ hal_path := "/home/spinnaker/config"
+ timestamp := time.Now().Unix()
+ tm := time.Unix(timestamp, 0)
+ t.KubeconfigFile = hal_path + tm.Format("2006-01-02-15-04-05")
+ dest_container := "spinnaker/spin-halyard/halyard-daemon"
+ destPath := t.KubeconfigFile
+ dest := strings.Join([]string{dest_container, destPath}, ":")
+ cloverkube.CopyFileToPod(filename, dest)
+ newconfig, _ := yaml.Marshal(&t)
+ out_json, err := yaml.YAMLToJSON(newconfig)
+ if err != nil {
+ panic(err.Error())
+ }
+ resp, err := resty.R().
+ SetHeader("Content-Type", "application/json").
+ SetBody(out_json).
+ Post(url)
+ if err != nil {
+ panic(err.Error())
+ }
+ fmt.Printf("\n%v\n", resp)
+
+}
diff --git a/clover/cloverctl/src/cloverctl/cmd/delete_docker_registry.go b/clover/cloverctl/src/cloverctl/cmd/delete_docker_registry.go
new file mode 100644
index 0000000..d4403a5
--- /dev/null
+++ b/clover/cloverctl/src/cloverctl/cmd/delete_docker_registry.go
@@ -0,0 +1,52 @@
+// Copyright (c) Authors of Clover
+//
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Apache License, Version 2.0
+// which accompanies this distribution, and is available at
+// http://www.apache.org/licenses/LICENSE-2.0
+
+package cmd
+
+import (
+ "fmt"
+ "encoding/json"
+
+ "gopkg.in/resty.v1"
+ "github.com/spf13/cobra"
+)
+
+var deldockerproviderCmd = &cobra.Command{
+ Use: "docker-registry",
+ Short: "delete one docker registry provider by name from spinnaker",
+ Long: ``,
+ Run: func(cmd *cobra.Command, args []string) {
+ deldockerProvider()
+ },
+}
+
+func init() {
+ providerdelCmd.AddCommand(deldockerproviderCmd)
+ deldockerproviderCmd.Flags().StringVarP(&name, "name", "n", "", "Input docker-registry account name")
+ deldockerproviderCmd.MarkFlagRequired("name")
+
+}
+
+func deldockerProvider() {
+ url := controllerIP + "/halyard/delprovider"
+
+ var in = map[string]string{"name": name, "provider":"dockerRegistry"}
+ out_json, err := json.Marshal(in)
+ if err != nil {
+ fmt.Println("json.Marshal failed:", err)
+ return
+ }
+ resp, err := resty.R().
+ SetHeader("Content-Type", "application/json").
+ SetBody(out_json).
+ Post(url)
+ if err != nil {
+ panic(err.Error())
+ }
+ fmt.Printf("\n%v\n", resp)
+
+}
diff --git a/clover/cloverctl/src/cloverctl/cmd/delete_kubernetes.go b/clover/cloverctl/src/cloverctl/cmd/delete_kubernetes.go
new file mode 100644
index 0000000..77b466a
--- /dev/null
+++ b/clover/cloverctl/src/cloverctl/cmd/delete_kubernetes.go
@@ -0,0 +1,53 @@
+// Copyright (c) Authors of Clover
+//
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Apache License, Version 2.0
+// which accompanies this distribution, and is available at
+// http://www.apache.org/licenses/LICENSE-2.0
+
+package cmd
+
+import (
+ "fmt"
+ "encoding/json"
+
+ "gopkg.in/resty.v1"
+ "github.com/spf13/cobra"
+)
+
+var name string
+var delkubeproviderCmd = &cobra.Command{
+ Use: "kubernetes",
+ Short: "delete one kubernete provider by name from spinnaker",
+ Long: ``,
+ Run: func(cmd *cobra.Command, args []string) {
+ delProvider()
+ },
+}
+
+func init() {
+ providerdelCmd.AddCommand(delkubeproviderCmd)
+ delkubeproviderCmd.Flags().StringVarP(&name, "name", "n", "", "Input kubernetes account name")
+ delkubeproviderCmd.MarkFlagRequired("name")
+
+}
+
+func delProvider() {
+ url := controllerIP + "/halyard/delprovider"
+
+ var in = map[string]string{"name": name, "provider":"kubernetes"}
+ out_json, err := json.Marshal(in)
+ if err != nil {
+ fmt.Println("json.Marshal failed:", err)
+ return
+ }
+ resp, err := resty.R().
+ SetHeader("Content-Type", "application/json").
+ SetBody(out_json).
+ Post(url)
+ if err != nil {
+ panic(err.Error())
+ }
+ fmt.Printf("\n%v\n", resp)
+
+}
diff --git a/clover/cloverctl/src/cloverctl/cmd/get_docker_registry.go b/clover/cloverctl/src/cloverctl/cmd/get_docker_registry.go
new file mode 100644
index 0000000..93c1b3e
--- /dev/null
+++ b/clover/cloverctl/src/cloverctl/cmd/get_docker_registry.go
@@ -0,0 +1,59 @@
+// Copyright (c) Authors of Clover
+//
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Apache License, Version 2.0
+// which accompanies this distribution, and is available at
+// http://www.apache.org/licenses/LICENSE-2.0
+
+package cmd
+
+import (
+ "fmt"
+ "strings"
+ "encoding/json"
+
+ "gopkg.in/resty.v1"
+ "github.com/spf13/cobra"
+)
+
+
+var getdockerregistry = &cobra.Command{
+ Use: "docker-registry",
+ Short: "Get docker registry from spinnaker",
+ Long: ``,
+ Run: func(cmd *cobra.Command, args []string) {
+ getdocker()
+ },
+}
+
+func init() {
+ providergetCmd.AddCommand(getdockerregistry)
+}
+
+func getdocker() {
+ url := controllerIP + "/halyard/account"
+
+ var provider = map[string]string{"name": "dockerRegistry"}
+ out_json, err := json.Marshal(provider)
+ if err != nil {
+ fmt.Println("json.Marshal failed:", err)
+ return
+ }
+ resp, err := resty.SetAllowGetMethodPayload(true).R().
+ SetHeader("Content-Type", "application/json").
+ SetBody(out_json).
+ Get(url)
+ if err != nil {
+ panic(err.Error())
+ }
+ if resp.StatusCode() != 200 {
+ fmt.Printf("\n%v\n", resp)
+ return
+ }
+
+ accounts := strings.Split(resp.String(), ":")
+ fmt.Printf("\n")
+ for index, account := range accounts{
+ fmt.Printf("%d. %v\n",index + 1, account)
+ }
+}
diff --git a/clover/cloverctl/src/cloverctl/cmd/get_kubernetes.go b/clover/cloverctl/src/cloverctl/cmd/get_kubernetes.go
new file mode 100644
index 0000000..16dcca1
--- /dev/null
+++ b/clover/cloverctl/src/cloverctl/cmd/get_kubernetes.go
@@ -0,0 +1,59 @@
+// Copyright (c) Authors of Clover
+//
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Apache License, Version 2.0
+// which accompanies this distribution, and is available at
+// http://www.apache.org/licenses/LICENSE-2.0
+
+package cmd
+
+import (
+ "fmt"
+ "strings"
+ "encoding/json"
+
+ "gopkg.in/resty.v1"
+ "github.com/spf13/cobra"
+)
+
+
+var getkubeprovider = &cobra.Command{
+ Use: "kubernetes",
+ Short: "Get kubernetes provider from spinnaker",
+ Long: ``,
+ Run: func(cmd *cobra.Command, args []string) {
+ getkube()
+ },
+}
+
+func init() {
+ providergetCmd.AddCommand(getkubeprovider)
+}
+
+func getkube() {
+ url := controllerIP + "/halyard/account"
+
+ var provider = map[string]string{"name": "kubernetes"}
+ out_json, err := json.Marshal(provider)
+ if err != nil {
+ fmt.Println("json.Marshal failed:", err)
+ return
+ }
+ resp, err := resty.SetAllowGetMethodPayload(true).R().
+ SetHeader("Content-Type", "application/json").
+ SetBody(out_json).
+ Get(url)
+ if err != nil {
+ panic(err.Error())
+ }
+ if resp.StatusCode() != 200 {
+ fmt.Printf("\n%v\n", resp)
+ return
+ }
+
+ accounts := strings.Split(resp.String(), ":")
+ fmt.Printf("\n")
+ for index, account := range accounts{
+ fmt.Printf("%d. %v\n",index + 1, account)
+ }
+}
diff --git a/clover/cloverctl/src/cloverctl/cmd/provider.go b/clover/cloverctl/src/cloverctl/cmd/provider.go
new file mode 100644
index 0000000..fc8e888
--- /dev/null
+++ b/clover/cloverctl/src/cloverctl/cmd/provider.go
@@ -0,0 +1,45 @@
+// Copyright (c) Authors of Clover
+//
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Apache License, Version 2.0
+// which accompanies this distribution, and is available at
+// http://www.apache.org/licenses/LICENSE-2.0
+
+package cmd
+
+import (
+ "fmt"
+ "github.com/spf13/cobra"
+)
+
+var providercreateCmd = &cobra.Command{
+ Use: "provider",
+ Short: "Add spinnaker provider",
+ Long: ``,
+ Run: func(cmd *cobra.Command, args []string) {
+ fmt.Println("provider called")
+ },
+}
+
+var providerdelCmd = &cobra.Command{
+ Use: "provider",
+ Short: "Delete spinnaker provider",
+ Long: ``,
+ Run: func(cmd *cobra.Command, args []string) {
+ fmt.Println("provider called")
+ },
+}
+
+var providergetCmd = &cobra.Command{
+ Use: "provider",
+ Short: "Get spinnaker provider",
+ Long: ``,
+ Run: func(cmd *cobra.Command, args []string) {
+ fmt.Println("provider called")
+ },
+}
+func init() {
+ createCmd.AddCommand(providercreateCmd)
+ deleteCmd.AddCommand(providerdelCmd)
+ getCmd.AddCommand(providergetCmd)
+}
diff --git a/clover/cloverctl/src/cloverkube/main.go b/clover/cloverctl/src/cloverkube/main.go
index e2854b5..7710a13 100644
--- a/clover/cloverctl/src/cloverkube/main.go
+++ b/clover/cloverctl/src/cloverkube/main.go
@@ -10,14 +10,20 @@ package cloverkube
import (
"fmt"
"os"
- "path/filepath"
- "strings"
+ "path/filepath"
+ "strings"
+ "io/ioutil"
+ "io"
+ "bytes"
appsv1 "k8s.io/api/apps/v1"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
+ "k8s.io/apimachinery/pkg/runtime"
+ "k8s.io/client-go/tools/remotecommand"
+
)
func setClient() kubernetes.Interface {
@@ -398,3 +404,93 @@ func GetPodsIP(pod_name string, namespace string) []string {
return ips
}
+
+func CopyFileToPod(src, dest string) error {
+
+ // dest must be "namespace/podname/containername:<your path>"
+ pSplit := strings.Split(dest, ":")
+ pathPrefix := pSplit[0]
+ pathToCopy := pSplit[1]
+
+ buffer, err := ioutil.ReadFile(src)
+ if err != nil {
+ fmt.Print(err)
+ }
+
+ dir, _ := filepath.Split(pathToCopy)
+ command := "mkdir -p " + dir
+ _, stderr, err := Exec(pathPrefix, command, nil)
+
+ if err != nil {
+ fmt.Print(err)
+ fmt.Print(stderr)
+ return err
+ }
+
+ command = "cp /dev/stdin " + pathToCopy
+ stdin := bytes.NewReader(buffer)
+ _, stderr, err = Exec(pathPrefix, command, stdin)
+
+ if err != nil {
+ fmt.Print(err)
+ fmt.Print(stderr)
+ return err
+ }
+
+ return nil
+}
+
+
+func Exec(pathPrefix, command string, stdin io.Reader) ([]byte, []byte, error) {
+ clientset := setClient()
+ kubeconfig := filepath.Join(
+ os.Getenv("HOME"), ".kube", "config",
+ )
+ config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
+ if err != nil {
+ panic(err.Error())
+ }
+
+ prefixSplit := strings.Split(pathPrefix, "/")
+ namespace := prefixSplit[0]
+ podName := prefixSplit[1]
+ containerName := prefixSplit[2]
+
+ req := clientset.Core().RESTClient().Post().
+ Resource("pods").
+ Name(podName).
+ Namespace(namespace).
+ SubResource("exec")
+ scheme := runtime.NewScheme()
+ if err := apiv1.AddToScheme(scheme); err != nil {
+ return nil, nil, fmt.Errorf("error adding to scheme: %v", err)
+ }
+
+ parameterCodec := runtime.NewParameterCodec(scheme)
+ req.VersionedParams(&apiv1.PodExecOptions{
+ Command: strings.Fields(command),
+ Container: containerName,
+ Stdin: stdin != nil,
+ Stdout: true,
+ Stderr: true,
+ TTY: false,
+ }, parameterCodec)
+
+ exec, err := remotecommand.NewSPDYExecutor(config, "POST", req.URL())
+ if err != nil {
+ return nil, nil, fmt.Errorf("error while creating Executor: %v", err)
+ }
+
+ var stdout, stderr bytes.Buffer
+ err = exec.Stream(remotecommand.StreamOptions{
+ Stdin: stdin,
+ Stdout: &stdout,
+ Stderr: &stderr,
+ Tty: false,
+ })
+ if err != nil {
+ return nil, nil, fmt.Errorf("error in Stream: %v", err)
+ }
+
+ return stdout.Bytes(), stderr.Bytes(), nil
+}
diff --git a/clover/controller/control/api/halyard.py b/clover/controller/control/api/halyard.py
new file mode 100644
index 0000000..861de59
--- /dev/null
+++ b/clover/controller/control/api/halyard.py
@@ -0,0 +1,167 @@
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+
+from flask import Blueprint, request, Response
+import logging
+import lib.halyard_base as base
+
+halyard = Blueprint('halyard', __name__)
+
+@halyard.route("/halyard/addkube", methods=['GET', 'POST'])
+def addkubernetes():
+ try:
+ p = request.json
+ accountname = p['Name']
+ providerversion = p['ProviderVersion']
+ Registries = p['DockerRegistries']
+ kubeconfigFile = p['KubeconfigFile']
+
+ except (KeyError, ValueError) as e:
+ logging.debug(e)
+ return Response('Invalid value in kubernetes yaml', status=400)
+
+ try:
+ if base.is_account_exist("kubernetes",accountname):
+ return Response("account name has already exist", status=400)
+ if providerversion == None or providerversion == 'V1':
+ providerversion = None
+ if 0 == len(Registries) or isinstance(Registries, list) == False:
+ return Response("V1 provider require dockerRegistries", status=400)
+
+ dockerRegistries = []
+ for registry in Registries:
+ registryname = registry['AccountName']
+ if not base.is_account_exist("dockerRegistry",registryname):
+ return Response("docker registry: {0} don't exist".format(registryname),
+ status=400)
+ docker_dict = {"accountName":registryname, "namespaces":[]}
+ dockerRegistries.append(docker_dict)
+ data = {
+ "name": accountname,
+ "requiredGroupMembership": [],
+ "providerVersion": providerversion,
+ "permissions": {},
+ "dockerRegistries": dockerRegistries,
+ "context": None,
+ "cluster": None,
+ "user": None,
+ "configureImagePullSecrets": "true",
+ "serviceAccount": None,
+ "cacheThreads": 1,
+ "namespaces": [],
+ "omitNamespaces": [],
+ "kinds": [],
+ "omitKinds": [],
+ "customResources": [],
+ "cachingPolicies": [],
+ "kubeconfigFile": kubeconfigFile,
+ "kubeconfigContents": None,
+ "kubectlPath": None,
+ "namingStrategy": None,
+ "skin": None,
+ "debug": None,
+ "oauthScopes": [],
+ "oauthServiceAccount": None,
+ "oAuthServiceAccount": None,
+ "oAuthScopes": []
+ }
+ result = base.add_account("kubernetes",data)
+ except Exception as e:
+ logging.debug(e)
+ return Response('Failed add the kubernetes provider', status=400)
+ return result
+
+@halyard.route("/halyard/addregistry", methods=['GET', 'POST'])
+def add_docker_registry():
+ try:
+ p = request.json
+ accountname = p['name']
+ address = p['address']
+ repositories = p['repositories']
+ if p.has_key('username') and p.has_key('password'):
+ username = p['username']
+ password = p['password']
+ else:
+ username = None
+ password = None
+
+ except (KeyError, ValueError) as e:
+ logging.debug(e)
+ return Response('Invalid value in kubernetes yaml', status=400)
+
+ try:
+ if base.is_account_exist("dockerRegistry",accountname):
+ return Response("account name has already exist", status=400)
+
+ data = {
+ "name": accountname,
+ "requiredGroupMembership": [],
+ "providerVersion": None,
+ "permissions": {},
+ "address": address,
+ "username": username,
+ "password": password,
+ "email": "fake.email@spinnaker.io",
+ "cacheIntervalSeconds": 30,
+ "clientTimeoutMillis": 60000,
+ "cacheThreads": 1,
+ "paginateSize": 100,
+ "sortTagsByDate": False,
+ "trackDigests": False,
+ "insecureRegistry": False,
+ "repositories": repositories,
+ "passwordFile": None,
+ "dockerconfigFile": None
+ }
+ result = base.add_account("dockerRegistry",data)
+ if result != "SUCCEEDED":
+ return Response('Failed to add the docker registry', status=400)
+
+ except Exception as e:
+ logging.debug(e)
+ return Response('Failed to add the docker registry', status=400)
+ return result
+
+@halyard.route("/halyard/delprovider", methods=['GET', 'POST'])
+def delprovider():
+ try:
+ p = request.json
+ provider = p['provider']
+ name = p['name']
+ except (KeyError, ValueError) as e:
+ logging.debug(e)
+ return Response('Input invalid value', status=400)
+ try:
+ result = base.delete_account(provider, name)
+ if result != "SUCCEEDED":
+ print "Delete account failed"
+ return Response('Failed to delete the {0} provider'.format(provider), status=400)
+
+ apply_result = base.apply_deploy()
+ if apply_result != "SUCCEEDED":
+ print "Delete account failed"
+ return Response('Failed to delete the {0} provider'.format(provider), status=400)
+
+ except Exception as e:
+ logging.debug(e)
+ return Response('Failed to delete the kubernetes provider', status=400)
+
+ return apply_result
+
+
+@halyard.route("/halyard/account", methods=['GET', 'POST'])
+def getprovider():
+ try:
+ provider = ""
+ p = request.json
+ provider = p['name']
+ account_list = base.list_accounts(provider)
+ result = ':'.join(account_list)
+ except Exception as e:
+ logging.debug(e)
+ return Response('get {0} failed'.format(provider), status=400)
+ return result
diff --git a/clover/controller/control/control.py b/clover/controller/control/control.py
index 54f713a..70eeacd 100644
--- a/clover/controller/control/control.py
+++ b/clover/controller/control/control.py
@@ -9,6 +9,7 @@ from flask import Flask, request, jsonify
from views.dashboard import simple_page
from api.collector import collector
from api.snort import snort
+from api.halyard import halyard
from api.nginx import nginx
from api.jmeter import jmeter
from api.file_upload import file_upload
@@ -23,6 +24,7 @@ try:
application.register_blueprint(simple_page)
application.register_blueprint(collector)
application.register_blueprint(snort)
+ application.register_blueprint(halyard)
application.register_blueprint(nginx)
application.register_blueprint(jmeter)
application.register_blueprint(file_upload)
diff --git a/clover/controller/docker/Dockerfile b/clover/controller/docker/Dockerfile
index 52d4673..9b8241f 100644
--- a/clover/controller/docker/Dockerfile
+++ b/clover/controller/docker/Dockerfile
@@ -15,7 +15,7 @@ RUN apt-get update && apt-get install -y \
# Install required python packages
RUN python -m pip install gunicorn flask \
- grpcio protobuf jinja2 redis
+ grpcio protobuf jinja2 redis requests
COPY /control /control
COPY /process /process
@@ -35,6 +35,7 @@ RUN cp clover/clover/collector/grpc/collector_pb2_grpc.py /control/api
RUN cp clover/clover/collector/grpc/collector_pb2.py /control/api
RUN cp clover/clover/tools/jmeter/jmeter-master/grpc/jmeter_pb2_grpc.py /control/api
RUN cp clover/clover/tools/jmeter/jmeter-master/grpc/jmeter_pb2.py /control/api
+RUN cp -rf clover/clover/spinnaker/lib/ /control/api
RUN rm -rf /grpc_temp
WORKDIR /process
diff --git a/clover/controller/process/gunicorn_process.sh b/clover/controller/process/gunicorn_process.sh
index 033596f..d2fa4ef 100755
--- a/clover/controller/process/gunicorn_process.sh
+++ b/clover/controller/process/gunicorn_process.sh
@@ -8,4 +8,7 @@
# http://www.apache.org/licenses/LICENSE-2.0
#
-gunicorn --bind 0.0.0.0:8000 --chdir /control wsgi
+# it take a long time to add kubernetes. So I increse the timeout
+# and workers
+
+gunicorn --bind 0.0.0.0:8000 -t 1200 -w 5 --chdir /control wsgi
diff --git a/clover/controller/process/nginx.conf b/clover/controller/process/nginx.conf
index 5b26922..5d2dca9 100644
--- a/clover/controller/process/nginx.conf
+++ b/clover/controller/process/nginx.conf
@@ -11,6 +11,7 @@ http {
listen 80;
location / {
+ proxy_read_timeout 1200;
include proxy_params;
proxy_pass http://localhost:8000;
}
diff --git a/clover/orchestration/kube_client.py b/clover/orchestration/kube_client.py
index f7fa708..582447b 100644
--- a/clover/orchestration/kube_client.py
+++ b/clover/orchestration/kube_client.py
@@ -9,6 +9,7 @@ from os import path
import yaml
from kubernetes import client, config
+from kubernetes.stream import stream
class KubeClient(object):
@@ -120,3 +121,36 @@ class KubeClient(object):
svc_name = body.get('metadata').get('name')
return svc_name
+
+ def copy_file_to_pod(self, source, destination, podname, namespace='default'):
+ # Note: only can copy file to the pod, which only include one container
+ exec_command = ['/bin/sh']
+ resp = stream(self.core_v1.connect_get_namespaced_pod_exec, podname,
+ namespace,
+ command=exec_command,
+ stderr=True, stdin=True,
+ stdout=True, tty=False,
+ _preload_content=False)
+
+ buffer = ''
+ with open(source, "rb") as file:
+ buffer += file.read()
+
+ commands = []
+ commands.append(bytes("cat <<'EOF' >" + destination + "\n"))
+ commands.append(buffer)
+ commands.append(bytes("EOF\n"))
+
+ while resp.is_open():
+ resp.update(timeout=1)
+ if resp.peek_stdout():
+ print("STDOUT: %s" % resp.read_stdout())
+ if resp.peek_stderr():
+ print("STDERR: %s" % resp.read_stderr())
+ if commands:
+ c = commands.pop(0)
+ resp.write_stdin(c)
+ else:
+ break
+
+ resp.close()
diff --git a/clover/spinnaker/__init__.py b/clover/spinnaker/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/clover/spinnaker/__init__.py
diff --git a/clover/spinnaker/halyard.py b/clover/spinnaker/halyard.py
new file mode 100755
index 0000000..20c0f4b
--- /dev/null
+++ b/clover/spinnaker/halyard.py
@@ -0,0 +1,120 @@
+#!/bin/python
+
+import lib.halyard_base as base
+import time
+from clover.orchestration.kube_client import KubeClient
+
+namespace = 'spinnaker'
+client = KubeClient()
+
+def list_accounts(provider):
+ account_list = base.list_accounts(provider)
+ return account_list
+
+def delete_account(provider, accountname):
+ result = base.delete_account(provider, accountname)
+ if result != "SUCCEEDED":
+ print "Delete account failed"
+ return result
+
+ apply_result = base.apply_deploy()
+ if apply_result == "SUCCEEDED":
+ print "Delete account successfully"
+ else:
+ print "Delete account failed"
+
+ return apply_result
+
+def add_k8s_account(accountname, kubeconfigfile,
+ providerversion=None, registries=[]):
+ # Note: if providerversion is V1, must provider registries.
+ if base.is_account_exist("kubernetes",accountname):
+ return "FAILED"
+ if providerversion == None or providerversion == 'V1':
+ providerversion = None
+ if 0 == len(registries) or isinstance(registries, list) == False:
+ print "please provider docker registries or the type of registries is not list"
+ return "FAILED"
+ # Copy kubectl file to halyard pod
+ hal_kubeconfigfile = "/home/spinnaker/config" + \
+ time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime())
+ spinnaker_pods = client.find_pod_by_namespace(namespace)
+ for pod in spinnaker_pods:
+ if pod.find("spin-halyard") == 0:
+ client.copy_file_to_pod(kubeconfigfile,
+ hal_kubeconfigfile,
+ pod, namespace)
+ dockerRegistries = []
+ for registry in registries:
+ if not base.is_account_exist("dockerRegistry",registry):
+ print ("Please add docker registry: %s" %registry)
+ return "FAILED"
+ docker_dict = {"accountName":registry, "namespaces":[]}
+ dockerRegistries.append(docker_dict)
+
+ data = {
+ "name": accountname,
+ "requiredGroupMembership": [],
+ "providerVersion": providerversion,
+ "permissions": {},
+ "dockerRegistries": dockerRegistries,
+ "context": None,
+ "cluster": None,
+ "user": None,
+ "configureImagePullSecrets": "true",
+ "serviceAccount": None,
+ "cacheThreads": 1,
+ "namespaces": [],
+ "omitNamespaces": [],
+ "kinds": [],
+ "omitKinds": [],
+ "customResources": [],
+ "cachingPolicies": [],
+ "kubeconfigFile": hal_kubeconfigfile,
+ "kubeconfigContents": None,
+ "kubectlPath": None,
+ "namingStrategy": None,
+ "skin": None,
+ "debug": None,
+ "oauthScopes": [],
+ "oauthServiceAccount": None,
+ "oAuthServiceAccount": None,
+ "oAuthScopes": []
+ }
+# print data
+ result = base.add_account("kubernetes",data)
+ return result
+
+def add_docker_account(address, accountname, repositories=[],
+ username=None, password=None, validate='true'):
+
+ if base.is_account_exist("dockerRegistry",accountname):
+ return "FAILED"
+
+ data = {
+ "name": accountname,
+ "requiredGroupMembership": [],
+ "providerVersion": None,
+ "permissions": {},
+ "address": address,
+ "username": username,
+ "password": password,
+ "email": "fake.email@spinnaker.io",
+ "cacheIntervalSeconds": 30,
+ "clientTimeoutMillis": 60000,
+ "cacheThreads": 1,
+ "paginateSize": 100,
+ "sortTagsByDate": False,
+ "trackDigests": False,
+ "insecureRegistry": False,
+ "repositories": repositories,
+ "passwordFile": None,
+ "dockerconfigFile": None
+ }
+ result = base.add_account("dockerRegistry",data)
+ if result == "SUCCEEDED":
+ print "Add account successfully"
+ else:
+ print "Add account failed"
+ return result
+
diff --git a/clover/spinnaker/halyard_sample.py b/clover/spinnaker/halyard_sample.py
new file mode 100644
index 0000000..b78be9b
--- /dev/null
+++ b/clover/spinnaker/halyard_sample.py
@@ -0,0 +1,41 @@
+# Copyright (c) Authors of Clover
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+
+import halyard
+
+
+# please install the spinnaker useing the spinnaker/install/quick-install-spinnaker.yml first
+print "######## add docker provider ##########"
+address = "https://index.docker.io"
+repositories = ['wtwde/onap', 'wtwde/spinnaker', 'library/nginx']
+add_docker = halyard.add_docker_account(address, "docker-test", repositories)
+print "######## result ##########"
+print add_docker
+
+print "######## add kubernetes provider ##########"
+add_k8s = halyard.add_k8s_account('my-k8s-v1-t11561', "/root/config.115", "V1", ['dockerhub'])
+print "######## result ##########"
+print add_k8s
+
+print "######## k8s account list ##########"
+k8s_list = halyard.list_accounts('kubernetes')
+print "####### result ##########"
+print k8s_list
+
+print "######## docker account list ##########"
+docker_list = halyard.list_accounts('dockerRegistry')
+print "####### result ##########"
+print docker_list
+
+print "######## delete kubernetes provider ##########"
+del_k8s = halyard.delete_account('kubernetes','my-k8s-v1-t11561')
+print "######## result ##########"
+
+print "######## delete docker registry provider ##########"
+del_docker = halyard.delete_account('dockerRegistry','docker-test')
+print "######## result ##########"
+print del_docker
diff --git a/clover/spinnaker/install/quick-install-spinnaker.yml b/clover/spinnaker/install/quick-install-spinnaker.yml
new file mode 100644
index 0000000..c935453
--- /dev/null
+++ b/clover/spinnaker/install/quick-install-spinnaker.yml
@@ -0,0 +1,376 @@
+apiVersion: v1
+kind: Namespace
+metadata:
+ name: spinnaker
+
+---
+
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: spinnaker-admin
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: cluster-admin
+subjects:
+- kind: ServiceAccount
+ name: default
+ namespace: spinnaker
+
+---
+
+apiVersion: v1
+kind: Pod
+metadata:
+ name: spin-halyard
+ namespace: spinnaker
+ labels:
+ app: spin
+ stack: halyard
+spec:
+ containers:
+ - name: halyard-daemon
+ # todo - make :stable or digest of :stable
+ image: gcr.io/spinnaker-marketplace/halyard:stable
+ imagePullPolicy: Always
+ command:
+ - /bin/sh
+ args:
+ - -c
+ # when the configmap is mounted directly at /home/spinnaker/.hal the halyard daemon
+ # isn't able to replace the contents of the mount with user modifications.
+ # so instead we mount the configmap elsewhere and copy the files into
+ # place when the container starts.
+ - "cp -R /home/spinnaker/staging/.hal /home/spinnaker/.hal && /opt/halyard/bin/halyard"
+ readinessProbe:
+ exec:
+ command:
+ - wget
+ - -q
+ - --spider
+ - http://localhost:8064/health
+ ports:
+ - containerPort: 8064
+ volumeMounts:
+ - name: halconfig
+ mountPath: /home/spinnaker/staging/.hal/config
+ subPath: config
+ - name: halconfig
+ mountPath: /home/spinnaker/staging/.hal/default/service-settings/deck.yml
+ subPath: deck.yml
+ - name: halconfig
+ mountPath: /home/spinnaker/staging/.hal/default/service-settings/gate.yml
+ subPath: gate.yml
+ - name: halconfig
+ mountPath: /home/spinnaker/staging/.hal/default/service-settings/igor.yml
+ subPath: igor.yml
+ - name: halconfig
+ mountPath: /home/spinnaker/staging/.hal/default/service-settings/fiat.yml
+ subPath: fiat.yml
+ - name: halconfig
+ mountPath: /home/spinnaker/staging/.hal/default/profiles/front50-local.yml
+ subPath: front50-local.yml
+ volumes:
+ - name: halconfig
+ configMap:
+ name: halconfig
+---
+
+apiVersion: v1
+kind: Service
+metadata:
+ name: spin-halyard
+ namespace: spinnaker
+spec:
+ ports:
+ - port: 8064
+ targetPort: 8064
+ protocol: TCP
+ selector:
+ app: spin
+ stack: halyard
+
+---
+
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: halconfig
+ namespace: spinnaker
+data:
+ igor.yml: |
+ enabled: true
+ fiat.yml: |
+ enabled: false
+ skipLifeCycleManagement: true
+ front50-local.yml: |
+ spinnaker.s3.versioning: false
+ gate.yml: |
+ host: 0.0.0.0
+ deck.yml: |
+ host: 0.0.0.0
+ env:
+ API_HOST: http://spin-gate.spinnaker:8084/
+ config: |
+ currentDeployment: default
+ deploymentConfigurations:
+ - name: default
+ version: 1.7.0
+ providers:
+ appengine:
+ enabled: false
+ accounts: []
+ aws:
+ enabled: false
+ accounts: []
+ defaultKeyPairTemplate: '{{name}}-keypair'
+ defaultRegions:
+ - name: us-west-2
+ defaults:
+ iamRole: BaseIAMRole
+ azure:
+ enabled: false
+ accounts: []
+ bakeryDefaults:
+ templateFile: azure-linux.json
+ baseImages: []
+ dcos:
+ enabled: false
+ accounts: []
+ clusters: []
+ dockerRegistry:
+ enabled: true
+ accounts:
+ - name: dockerhub
+ address: https://index.docker.io
+ repositories:
+ - opnfv/clover
+ google:
+ enabled: false
+ accounts: []
+ bakeryDefaults:
+ templateFile: gce.json
+ baseImages: []
+ zone: us-central1-f
+ network: default
+ useInternalIp: false
+ kubernetes:
+ enabled: true
+ accounts:
+ - name: my-kubernetes-account
+ requiredGroupMembership: []
+ providerVersion: V2
+ dockerRegistries: []
+ configureImagePullSecrets: true
+ serviceAccount: true
+ namespaces: []
+ omitNamespaces: []
+ kinds: []
+ omitKinds: []
+ customResources: []
+ oauthScopes: []
+ oAuthScopes: []
+ primaryAccount: my-kubernetes-account
+ openstack:
+ enabled: false
+ accounts: []
+ bakeryDefaults:
+ baseImages: []
+ oraclebmcs:
+ enabled: false
+ accounts: []
+ deploymentEnvironment:
+ size: SMALL
+ type: Distributed
+ accountName: my-kubernetes-account
+ updateVersions: true
+ consul:
+ enabled: false
+ vault:
+ enabled: false
+ customSizing: {}
+ gitConfig:
+ upstreamUser: spinnaker
+ persistentStorage:
+ persistentStoreType: s3
+ azs: {}
+ gcs:
+ rootFolder: front50
+ redis: {}
+ s3:
+ bucket: spinnaker-artifacts
+ rootFolder: front50
+ endpoint: http://minio-service.spinnaker:9000
+ accessKeyId: dont-use-this
+ secretAccessKey: for-production
+ oraclebmcs: {}
+ features:
+ auth: false
+ fiat: false
+ chaos: false
+ entityTags: false
+ jobs: false
+ metricStores:
+ datadog:
+ enabled: false
+ prometheus:
+ enabled: false
+ add_source_metalabels: true
+ stackdriver:
+ enabled: false
+ period: 30
+ enabled: false
+ notifications:
+ slack:
+ enabled: false
+ timezone: America/Los_Angeles
+ ci:
+ jenkins:
+ enabled: true
+ masters: []
+ travis:
+ enabled: false
+ masters: []
+ security:
+ apiSecurity:
+ ssl:
+ enabled: false
+ overrideBaseUrl: /gate
+ uiSecurity:
+ ssl:
+ enabled: false
+ authn:
+ oauth2:
+ enabled: false
+ client: {}
+ resource: {}
+ userInfoMapping: {}
+ saml:
+ enabled: false
+ ldap:
+ enabled: false
+ x509:
+ enabled: false
+ enabled: false
+ authz:
+ groupMembership:
+ service: EXTERNAL
+ google:
+ roleProviderType: GOOGLE
+ github:
+ roleProviderType: GITHUB
+ file:
+ roleProviderType: FILE
+ enabled: false
+ artifacts:
+ gcs:
+ enabled: false
+ accounts: []
+ github:
+ enabled: false
+ accounts: []
+ http:
+ enabled: false
+ accounts: []
+ pubsub:
+ google:
+ enabled: false
+ subscriptions: []
+
+---
+
+apiVersion: v1
+kind: PersistentVolumeClaim
+metadata:
+ name: minio-pv-claim
+ namespace: spinnaker
+ labels:
+ app: minio-storage-claim
+spec:
+ accessModes:
+ - ReadWriteOnce
+ resources:
+ requests:
+ storage: 10Gi
+ storageClassName: standard
+
+---
+
+apiVersion: apps/v1beta1
+kind: Deployment
+metadata:
+ # This name uniquely identifies the Deployment
+ name: minio-deployment
+ namespace: spinnaker
+spec:
+ strategy:
+ type: Recreate
+ template:
+ metadata:
+ labels:
+ app: minio
+ spec:
+ volumes:
+ - name: storage
+ persistentVolumeClaim:
+ claimName: minio-pv-claim
+ containers:
+ - name: minio
+ image: minio/minio
+ args:
+ - server
+ - /storage
+ env:
+ - name: MINIO_ACCESS_KEY
+ value: "dont-use-this"
+ - name: MINIO_SECRET_KEY
+ value: "for-production"
+ ports:
+ - containerPort: 9000
+ volumeMounts:
+ - name: storage
+ mountPath: /storage
+
+---
+
+apiVersion: v1
+kind: Service
+metadata:
+ name: minio-service
+ namespace: spinnaker
+spec:
+ ports:
+ - port: 9000
+ targetPort: 9000
+ protocol: TCP
+ selector:
+ app: minio
+
+---
+
+apiVersion: batch/v1
+kind: Job
+metadata:
+ name: hal-deploy-apply
+ namespace: spinnaker
+ labels:
+ app: job
+ stack: hal-deploy
+spec:
+ template:
+ metadata:
+ labels:
+ app: job
+ stack: hal-deploy
+ spec:
+ restartPolicy: OnFailure
+ containers:
+ - name: hal-deploy-apply
+ # todo use a custom image
+ image: gcr.io/spinnaker-marketplace/halyard:stable
+ command:
+ - /bin/sh
+ args:
+ - -c
+ - "hal deploy apply --daemon-endpoint http://spin-halyard.spinnaker:8064"
diff --git a/clover/spinnaker/lib/__init__.py b/clover/spinnaker/lib/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/clover/spinnaker/lib/__init__.py
diff --git a/clover/spinnaker/lib/halyard_base.py b/clover/spinnaker/lib/halyard_base.py
new file mode 100755
index 0000000..7245d6a
--- /dev/null
+++ b/clover/spinnaker/lib/halyard_base.py
@@ -0,0 +1,157 @@
+#!/bin/python
+
+
+import requests
+import json
+import time
+
+base_url="http://spin-halyard.spinnaker:8064/v1"
+namespace= 'spinnaker'
+headers = {'content-type': 'application/json; charset=UTF-8'}
+debug = False
+
+def get(url):
+ result = requests.get(url)
+ return result.json()
+
+def post(url, data = None, headers = None):
+ result = requests.post(url, data=data, headers=headers)
+ return result.json()
+
+def put(url, data = None, headers = None):
+ result = requests.put(url, data=data, headers=headers)
+ return result.json()
+
+def delete(url):
+ result = requests.delete(url)
+ return result.json()
+
+def print_dict_info(dict_info, debug = False):
+ if dict_info == None:
+ return None
+
+ if debug == True:
+ for v,k in dict_info.items():
+ print('{v}:{k}'.format(v = v, k = k))
+ else:
+ print dict_info.get('name')
+ print dict_info.get('state')
+
+def is_account_exist(provider, accountname):
+ exist_accounts = list_accounts(provider)
+ if accountname in exist_accounts:
+ print "account exists"
+ return True
+ print "account doesn't exist"
+ return False
+
+def get_task_info(uuid):
+ if uuid == None:
+ return None
+ url = base_url + "/tasks/" + uuid + "/"
+ result = get(url)
+ return result
+
+def wait_task_successful(uuid):
+ flag = ""
+ while True:
+ resp = get_task_info(uuid)
+ state = resp.get('state')
+ if flag != state:
+ print_dict_info(resp, debug)
+ flag = state
+ if state == "SUCCEEDED":
+ return "SUCCEEDED", resp
+ if state == "FAILED":
+ return "FAILED", resp
+ if resp.get('timedOut'):
+ return "TimeOut", resp
+
+def get_current_deployment():
+ '''get the current deployment and check the state'''
+
+ url = base_url + "/config/currentDeployment"
+ result = get(url)
+ uuid = result.get('uuid')
+ task_info = get_task_info(uuid)
+ print_dict_info(task_info, debug)
+
+ return task_info
+
+def apply_deploy():
+ """
+ after using api to config halyard, it need ruan apply deploy.
+ """
+ prep_url = base_url + "/config/deployments/default/prep/?validate=true"
+ deploy_url = base_url + "/config/deployments/default/deploy/?validate=false"
+ data='""'
+ result = post(prep_url, data=data, headers=headers)
+ uuid = result.get('uuid')
+ result, task_info = wait_task_successful(uuid)
+ if result != "SUCCEEDED":
+ return result
+ result = post(deploy_url, data=data, headers=headers)
+ uuid = result.get('uuid')
+ result, task_info = wait_task_successful(uuid)
+ return result
+
+def list_accounts(provider):
+ """
+ According to the provider, list all accounts
+ """
+ url = base_url + "/config/deployments/default/providers/" + \
+ provider + "/?validate=true"
+ resp = get(url)
+ uuid = resp.get('uuid')
+ result, task_info = wait_task_successful(uuid)
+ if result != "SUCCEEDED":
+ print "Get account failed"
+ return None
+ accounts = task_info.get('response').get('responseBody').get('accounts')
+ account_list = []
+ for account in accounts:
+ account_name = account.get('name')
+ account_list.append(account_name)
+ return account_list
+
+def enable_provider(provider, data='true'):
+ """
+ if needs to add a provider, it is necessary to enable the provider
+ """
+ url = base_url + "/config/deployments/default/providers/" + \
+ provider + "/enabled/?validate=true"
+ resp = put(url,data=data,headers=headers)
+ uuid = resp.get('uuid')
+ result, task_info = wait_task_successful(uuid)
+ return result
+
+def add_account(provider, data):
+ url = base_url + "/config/deployments/default/providers/" + \
+ provider + "/accounts/?validate=true"
+
+ enable_provider(provider)
+
+ resp = post(url, data=json.dumps(data), headers=headers)
+ uuid = resp.get('uuid')
+ result, task_info = wait_task_successful(uuid)
+ if result != "SUCCEEDED":
+ print "Add account failed"
+ return result
+ apply_result = apply_deploy()
+ if apply_result == "SUCCEEDED":
+ print "Deployment successful"
+ else:
+ print "Deployment failed"
+ return apply_result
+
+def delete_account(provider, accountname):
+ if not is_account_exist(provider, accountname):
+ return "FAILED"
+ url = base_url + "/config/deployments/default/providers/" + \
+ provider + "/accounts/account/" + accountname + "/?validate=true"
+ resp = delete(url)
+ uuid = resp.get('uuid')
+ result, task_info = wait_task_successful(uuid)
+
+ return result
+
diff --git a/clover/tools/yaml/cassandra.yaml b/clover/tools/yaml/cassandra.yaml
index 83ed20f..0206d75 100644
--- a/clover/tools/yaml/cassandra.yaml
+++ b/clover/tools/yaml/cassandra.yaml
@@ -20,7 +20,7 @@
# note that this value rely on a functioning DNS on kubernetes to resolve the IP
#
# This static method can be made dynamic by using Helm
-# or bash script to replace the namespace on the fly.
+# or bash script to replace the namespace on the fly.
#
# - Cassandra launch only 1 replica in the cluster
# to dynamically scale up or down, you can use: