diff options
author | Stephen Wong <stephen.kf.wong@gmail.com> | 2018-09-19 20:51:18 +0000 |
---|---|---|
committer | Gerrit Code Review <gerrit@opnfv.org> | 2018-09-19 20:51:18 +0000 |
commit | edc329733d16a2df409ddfc34f1fae52e875ffd6 (patch) | |
tree | e273335db6e358e815518d7dbf8048c313303cfc | |
parent | bf2f1c7aa3491f683b1660de68876cf2731f64a4 (diff) | |
parent | 8371ccd29229c418dd8bb534fda3d28184c4e986 (diff) |
Merge "Spinnaker as a Service"
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: |