Skip to content

Commit

Permalink
Add support to create Pipeline in KubeSphere cluster (#61)
Browse files Browse the repository at this point in the history
* Add support to create Pipeline in KubeSphere cluster

* Fix the issues found by gosec

* Fix the html escape issues found by gosec
  • Loading branch information
LinuxSuRen committed Mar 2, 2021
1 parent 11eb591 commit 623dfeb
Show file tree
Hide file tree
Showing 10 changed files with 564 additions and 159 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ hd install -t 8 linuxsuren/ks/kubectl-ks
All features below work with [KubeSphere](https://github.com/kubsphere/kubesphere) instead of other concept.

* Pipeline management
* Create a Pipeline with java, go template
* Edit a Pipeline without give the fullname (namespace/name)
* User Management
* Component Management
Expand All @@ -45,6 +46,7 @@ Aliases:
pipeline, pip
Available Commands:
create Create a Pipeline in the KubeSphere cluster
delete Delete a specific Pipeline of KubeSphere DevOps
edit Edit the target pipeline
view Output the YAML format of a Pipeline
Expand Down
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,14 @@ go 1.15

require (
github.com/AlecAivazis/survey/v2 v2.2.7
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Masterminds/sprig v2.22.0+incompatible
github.com/huandu/xstrings v1.3.2 // indirect
github.com/imdario/mergo v0.3.9 // indirect
github.com/linuxsuren/cobra-extension v0.0.10
github.com/linuxsuren/go-cli-alias v0.0.4
github.com/mitchellh/copystructure v1.1.1 // indirect
github.com/spf13/cobra v1.1.1
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.0
Expand Down
13 changes: 13 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6L
github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60=
github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8 h1:xzYJEypr/85nBpB11F9br+3HUrpgb+fcm5iADzXXYEw=
github.com/Netflix/go-expect v0.0.0-20180615182759-c93bf25de8e8/go.mod h1:oX5x61PbNXchhh0oikYAH+4Pcfw5LKv21+Jnpr6r6Pc=
Expand Down Expand Up @@ -167,6 +173,7 @@ github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OI
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
Expand Down Expand Up @@ -208,6 +215,8 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174 h1:WlZsjVhE8Af9IcZDGgJGQpNflI3+MJSBhsgT5PCtzBQ=
github.com/hinshun/vt10x v0.0.0-20180616224451-1954e6464174/go.mod h1:DqJ97dSdRW1W22yXSB90986pcOyQ7r45iio1KN2ez1A=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imdario/mergo v0.3.5 h1:JboBksRwiiAJWvIYJVo46AfV+IAIKZpfrSzVKj42R4Q=
github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
Expand Down Expand Up @@ -283,6 +292,8 @@ github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1f
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/copystructure v1.1.1 h1:Bp6x9R1Wn16SIz3OfeDr0b7RnCG2OB66Y7PQyC/cvq4=
github.com/mitchellh/copystructure v1.1.1/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
Expand All @@ -291,6 +302,8 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE=
github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
Expand Down
318 changes: 318 additions & 0 deletions kubectl-plugin/pipeline/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,318 @@
package pipeline

import (
"bytes"
"context"
"fmt"
"github.com/Masterminds/sprig"
"github.com/linuxsuren/ks/kubectl-plugin/types"
"github.com/spf13/cobra"
"html"
"html/template"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/dynamic"
"strings"
)

type pipelineCreateOption struct {
Workspace string
Project string
Name string
Jenkinsfile string
Template string

// Inner fields
Client dynamic.Interface
WorkspaceUID string
}

func newPipelineCreateCmd(client dynamic.Interface) (cmd *cobra.Command) {
opt := &pipelineCreateOption{
Client: client,
}

cmd = &cobra.Command{
Use: "create",
Short: "Create a Pipeline in the KubeSphere cluster",
Long: `Create a Pipeline in the KubeSphere cluster
You can create a Pipeline with a java, go template. Before you do that, please make sure the workspace exists.
KubeSphere supports multiple types Pipeline. Currently, this CLI only support the simple one with Jenkinsfile inside.'`,
Example: "ks pip create --ws simple --template java --name java --project test",
PreRunE: opt.preRunE,
RunE: opt.runE,
}

flags := cmd.Flags()
flags.StringVarP(&opt.Workspace, "workspace", "", "",
"The workspace name of KubeSphere cluster")
flags.StringVarP(&opt.Workspace, "ws", "", "",
"The workspace name of KubeSphere cluster. This is an alias for --workspace")
flags.StringVarP(&opt.Project, "project", "", "",
"The DevOps project name of KubeSphere cluster")
flags.StringVarP(&opt.Name, "name", "", "",
"The name of the Pipeline")
flags.StringVarP(&opt.Jenkinsfile, "jenkinsfile", "", "",
"The Jenkinsfile of the Pipeline")
flags.StringVarP(&opt.Template, "template", "", "",
"Template of Jenkinsfile include: java, go. This option will override the option --jenkinsfile")
return
}

func (o *pipelineCreateOption) preRunE(cmd *cobra.Command, args []string) (err error) {
switch o.Template {
case "":
case "java":
o.Jenkinsfile = jenkinsfileTemplateForJava
case "go":
o.Jenkinsfile = jenkinsfileTemplateForGo
default:
err = fmt.Errorf("%s is not support", o.Template)
}
o.Jenkinsfile = strings.TrimSpace(o.Jenkinsfile)
return
}

func (o *pipelineCreateOption) runE(cmd *cobra.Command, args []string) (err error) {
ctx := context.TODO()

var ws *unstructured.Unstructured
if ws, err = o.checkWorkspace(); err != nil {
return
}
var project *unstructured.Unstructured
if project, err = o.checkDevOpsProject(ws); err != nil {
return
}
o.Project = project.GetName() // the previous name is the generate name

var rawPip *unstructured.Unstructured
if rawPip, err = o.createPipelineObj(); err == nil {
if rawPip, err = o.Client.Resource(types.GetPipelineSchema()).Namespace(o.Project).Create(ctx, rawPip, metav1.CreateOptions{}); err != nil {
err = fmt.Errorf("failed to create Pipeline, %v", err)
}
}
return
}

func (o *pipelineCreateOption) checkWorkspace() (ws *unstructured.Unstructured, err error) {
ctx := context.TODO()
ws, err = o.Client.Resource(types.GetWorkspaceSchema()).Get(ctx, o.Workspace, metav1.GetOptions{})
// TODO create the workspace if it's not exists
return
}

func (o *pipelineCreateOption) checkDevOpsProject(ws *unstructured.Unstructured) (project *unstructured.Unstructured, err error) {
ctx := context.TODO()

selector := labels.Set{"kubesphere.io/workspace": o.Workspace}
var list *unstructured.UnstructuredList
if list, err = o.Client.Resource(types.GetDevOpsProjectSchema()).List(ctx, metav1.ListOptions{
LabelSelector: labels.SelectorFromSet(selector).String(),
}); err != nil {
return
}

found := false
for i := range list.Items {
if list.Items[i].GetGenerateName() == o.Project {
found = true
project = &list.Items[i]
break
}
}

if !found {
var tpl *template.Template
o.WorkspaceUID = string(ws.GetUID())
if tpl, err = template.New("project").Parse(devopsProjectTemplate); err != nil {
return
}

var buf bytes.Buffer
if err = tpl.Execute(&buf, o); err != nil {
return
}

var projectObj *unstructured.Unstructured
if projectObj, err = types.GetObjectFromYaml(buf.String()); err != nil {
err = fmt.Errorf("failed to unmarshal yaml to DevOpsProject object, %v", err)
return
}

project, err = o.Client.Resource(types.GetDevOpsProjectSchema()).Create(ctx, projectObj, metav1.CreateOptions{})
}
return
}

func (o *pipelineCreateOption) createPipelineObj() (rawPip *unstructured.Unstructured, err error) {
var tpl *template.Template
funcMap := sprig.FuncMap()
funcMap["raw"] = html.EscapeString
if tpl, err = template.New("pipeline").Funcs(funcMap).Parse(pipelineTemplate); err != nil {
err = fmt.Errorf("failed to parse Pipeline template, %v", err)
return
}

var buf bytes.Buffer
if err = tpl.Execute(&buf, o); err != nil {
err = fmt.Errorf("failed to render Pipeline template, %v", err)
return
}

if rawPip, err = types.GetObjectFromYaml(buf.String()); err != nil {
err = fmt.Errorf("failed to unmarshal yaml to Pipeline object, %v", err)
}
return
}

var devopsProjectTemplate = `
apiVersion: devops.kubesphere.io/v1alpha3
kind: DevOpsProject
metadata:
annotations:
kubesphere.io/creator: admin
finalizers:
- devopsproject.finalizers.kubesphere.io
generateName: {{.Project}}
labels:
kubesphere.io/workspace: {{.Workspace}}
ownerReferences:
- apiVersion: tenant.kubesphere.io/v1alpha1
blockOwnerDeletion: true
controller: true
kind: Workspace
name: {{.Workspace}}
uid: {{.WorkspaceUID}}
`

var pipelineTemplate = `
apiVersion: devops.kubesphere.io/v1alpha3
kind: Pipeline
metadata:
annotations:
kubesphere.io/creator: admin
finalizers:
- pipeline.finalizers.kubesphere.io
name: "{{.Name}}"
namespace: {{.Project}}
spec:
pipeline:
disable_concurrent: true
discarder:
days_to_keep: "7"
num_to_keep: "10"
jenkinsfile: |
{{.Jenkinsfile | indent 6 | raw}}
name: "{{.Name}}"
type: pipeline
status: {}
`

var jenkinsfileTemplateForJava = `
pipeline {
agent {
node {
label 'maven'
}
}
stages {
stage('Clone') {
steps {
git(url: 'https://github.com/kubesphere-sigs/demo-java', changelog: true, poll: false)
}
}
stage('Build & Test') {
steps {
container('maven') {
sh 'mvn package test'
}
}
}
stage('Code Scan') {
steps {
withSonarQubeEnv('sonar') {
container('maven') {
sh '''mvn --version
mvn sonar:sonar \\
-Dsonar.projectKey=test \\
-Dsonar.host.url=http://139.198.9.130:30687/ \\
-Dsonar.login=b3e146cdb76ecef5ffb12743779cd78e69a4b5c5'''
}
}
waitForQualityGate 'false'
}
}
stage('Build Image') {
steps {
container('maven') {
withCredentials([usernamePassword(credentialsId : 'docker' ,passwordVariable : 'PASS' ,usernameVariable : 'USER' ,)]) {
sh '''docker login -u $USER -p $PASS
cat <<EOM >Dockerfile
FROM kubesphere/java-8-centos7:v2.1.0
COPY target/demo-java-1.0.0.jar demo.jar
COPY target/lib demo-lib
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "demo.jar"]
EOM
docker build . -t surenpi/java-demo
docker push surenpi/java-demo'''
}
}
}
}
}
}
`
var jenkinsfileTemplateForGo = `
pipeline {
agent {
node {
label 'go'
}
}
stages {
stage('Code Clone') {
steps {
git(url: 'https://github.com/kubesphere-sigs/demo-go-http', changelog: true, poll: false)
}
}
stage('Test & Code Scan') {
steps {
container('go') {
sh 'go test ./... -coverprofile=covprofile'
withCredentials([string(credentialsId : 'sonar-token' ,variable : 'TOKEN' ,)]) {
withSonarQubeEnv('sonar') {
sh 'sonar-scanner -Dsonar.login=$TOKEN'
}
}
}
waitForQualityGate 'false'
}
}
stage('Build Image & Push') {
steps {
container('go') {
sh ''' CGO_ENABLED=0 GOARCH=amd64 go build -o bin/go-server -ldflags "-w"
chmod u+x bin/go-server'''
withCredentials([usernamePassword(credentialsId : 'rick-docker-hub' ,passwordVariable : 'PASS' ,usernameVariable : 'USER' ,)]) {
sh 'echo "$PASS" | docker login -u "$USER" --password-stdin'
sh '''cat <<EOM >Dockerfile
FROM alpine
COPY bin/go-server go-server
EXPOSE 80
ENTRYPOINT ["go-server"]
EOM
docker build . -t surenpi/go-demo
docker push surenpi/go-demo'''
}
}
}
}
}
}
`
Loading

0 comments on commit 623dfeb

Please sign in to comment.