devops tenant api

Signed-off-by: runzexia <runzexia@yunify.com>
This commit is contained in:
runzexia
2019-04-23 20:47:47 +08:00
committed by zryfish
parent 78f2dab18c
commit 5a6f51d775
143 changed files with 19533 additions and 341 deletions

View File

@@ -28,15 +28,15 @@ import (
"kubesphere.io/kubesphere/pkg/apiserver/logging"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/errors"
"kubesphere.io/kubesphere/pkg/models"
"kubesphere.io/kubesphere/pkg/models/devops"
"kubesphere.io/kubesphere/pkg/models/iam"
"kubesphere.io/kubesphere/pkg/models/metrics"
"kubesphere.io/kubesphere/pkg/models/resources"
"kubesphere.io/kubesphere/pkg/models/tenant"
"kubesphere.io/kubesphere/pkg/models/workspaces"
"kubesphere.io/kubesphere/pkg/params"
"kubesphere.io/kubesphere/pkg/simple/client/elasticsearch"
"kubesphere.io/kubesphere/pkg/simple/client/kubesphere"
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
"net/http"
"strings"
@@ -242,17 +242,10 @@ func DeleteDevopsProject(req *restful.Request, resp *restful.Response) {
return
}
err = kubesphere.Client().DeleteDevopsProject(username, devops)
err, code := tenant.DeleteDevOpsProject(devops, username)
if err != nil {
resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err))
return
}
err = workspaces.UnBindDevopsProject(workspaceName, devops)
if err != nil {
resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err))
resp.WriteHeaderAndEntity(code, errors.Wrap(err))
return
}
@@ -264,7 +257,7 @@ func CreateDevopsProject(req *restful.Request, resp *restful.Response) {
workspaceName := req.PathParameter("workspace")
username := req.HeaderParameter(constants.UserNameHeader)
var devops models.DevopsProject
var devops devops.DevOpsProject
err := req.ReadEntity(&devops)
@@ -274,10 +267,10 @@ func CreateDevopsProject(req *restful.Request, resp *restful.Response) {
}
glog.Infoln("create workspace", username, workspaceName, devops)
project, err := workspaces.CreateDevopsProject(username, workspaceName, &devops)
project, err, code := tenant.CreateDevopsProject(username, workspaceName, &devops)
if err != nil {
resp.WriteHeaderAndEntity(http.StatusInternalServerError, errors.Wrap(err))
resp.WriteHeaderAndEntity(code, errors.Wrap(err))
return
}

11
pkg/db/Dockerfile Normal file
View File

@@ -0,0 +1,11 @@
# Copyright 2017 The OpenPitrix Authors. All rights reserved.
# Use of this source code is governed by a Apache license
# that can be found in the LICENSE file.
FROM dhoer/flyway:5.1.4-mysql-8.0.11-alpine
RUN apk add --no-cache mysql-client
COPY ./schema /flyway/sql
COPY ./ddl /flyway/sql/ddl
COPY ./scripts /flyway/sql/ddl

109
pkg/db/condition.go Normal file
View File

@@ -0,0 +1,109 @@
/*
Copyright 2018 The KubeSphere Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package db
import (
"strings"
"github.com/gocraft/dbr"
)
const (
placeholder = "?"
)
type EqCondition struct {
dbr.Builder
Column string
Value interface{}
}
// Copy From vendor/github.com/gocraft/dbr/condition.go:36
func buildCmp(d dbr.Dialect, buf dbr.Buffer, pred string, column string, value interface{}) error {
buf.WriteString(d.QuoteIdent(column))
buf.WriteString(" ")
buf.WriteString(pred)
buf.WriteString(" ")
buf.WriteString(placeholder)
buf.WriteValue(value)
return nil
}
// And creates AND from a list of conditions
func And(cond ...dbr.Builder) dbr.Builder {
return dbr.And(cond...)
}
// Or creates OR from a list of conditions
func Or(cond ...dbr.Builder) dbr.Builder {
return dbr.Or(cond...)
}
func escape(str string) string {
return strings.Map(func(r rune) rune {
switch r {
case '%', '\'', '^', '[', ']', '!', '_':
return ' '
}
return r
}, str)
}
func Like(column string, value string) dbr.Builder {
value = "%" + strings.TrimSpace(escape(value)) + "%"
return dbr.BuildFunc(func(d dbr.Dialect, buf dbr.Buffer) error {
return buildCmp(d, buf, "LIKE", column, value)
})
}
// Eq is `=`.
// When value is nil, it will be translated to `IS NULL`.
// When value is a slice, it will be translated to `IN`.
// Otherwise it will be translated to `=`.
func Eq(column string, value interface{}) dbr.Builder {
return &EqCondition{
Builder: dbr.Eq(column, value),
Column: column,
Value: value,
}
}
// Neq is `!=`.
// When value is nil, it will be translated to `IS NOT NULL`.
// When value is a slice, it will be translated to `NOT IN`.
// Otherwise it will be translated to `!=`.
func Neq(column string, value interface{}) dbr.Builder {
return dbr.Neq(column, value)
}
// Gt is `>`.
func Gt(column string, value interface{}) dbr.Builder {
return dbr.Gt(column, value)
}
// Gte is '>='.
func Gte(column string, value interface{}) dbr.Builder {
return dbr.Gte(column, value)
}
// Lt is '<'.
func Lt(column string, value interface{}) dbr.Builder {
return dbr.Lt(column, value)
}
// Lte is `<=`.
func Lte(column string, value interface{}) dbr.Builder {
return dbr.Lte(column, value)
}

88
pkg/db/condition_test.go Normal file
View File

@@ -0,0 +1,88 @@
/*
Copyright 2018 The KubeSphere Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package db
import (
"testing"
"github.com/gocraft/dbr"
"github.com/gocraft/dbr/dialect"
"github.com/stretchr/testify/assert"
)
// Ref: https://github.com/gocraft/dbr/blob/5d59a8b3aa915660960329efb3af5513e7a0db07/condition_test.go
func TestCondition(t *testing.T) {
for _, test := range []struct {
cond dbr.Builder
query string
value []interface{}
}{
{
cond: Eq("col", 1),
query: "`col` = ?",
value: []interface{}{1},
},
{
cond: Eq("col", nil),
query: "`col` IS NULL",
value: nil,
},
{
cond: Eq("col", []int{}),
query: "0",
value: nil,
},
{
cond: Neq("col", 1),
query: "`col` != ?",
value: []interface{}{1},
},
{
cond: Neq("col", nil),
query: "`col` IS NOT NULL",
value: nil,
},
{
cond: Gt("col", 1),
query: "`col` > ?",
value: []interface{}{1},
},
{
cond: Gte("col", 1),
query: "`col` >= ?",
value: []interface{}{1},
},
{
cond: Lt("col", 1),
query: "`col` < ?",
value: []interface{}{1},
},
{
cond: Lte("col", 1),
query: "`col` <= ?",
value: []interface{}{1},
},
{
cond: And(Lt("a", 1), Or(Gt("b", 2), Neq("c", 3))),
query: "(`a` < ?) AND ((`b` > ?) OR (`c` != ?))",
value: []interface{}{1, 2, 3},
},
} {
buf := dbr.NewBuffer()
err := test.cond.Build(dialect.MySQL, buf)
assert.NoError(t, err)
assert.Equal(t, test.query, buf.String())
assert.Equal(t, test.value, buf.Value())
}
}

283
pkg/db/db.go Normal file
View File

@@ -0,0 +1,283 @@
/*
Copyright 2018 The KubeSphere Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package db
import (
"database/sql"
"fmt"
"strings"
_ "github.com/go-sql-driver/mysql"
"github.com/gocraft/dbr"
)
const (
DefaultSelectLimit = 200
)
func GetLimit(n uint64) uint64 {
if n < 0 {
n = 0
}
if n > DefaultSelectLimit {
n = DefaultSelectLimit
}
return n
}
func GetOffset(n uint64) uint64 {
if n < 0 {
n = 0
}
return n
}
type InsertHook func(query *InsertQuery)
type UpdateHook func(query *UpdateQuery)
type DeleteHook func(query *DeleteQuery)
type Database struct {
*dbr.Session
InsertHook InsertHook
UpdateHook UpdateHook
DeleteHook DeleteHook
}
type SelectQuery struct {
*dbr.SelectBuilder
JoinCount int // for join filter
}
type InsertQuery struct {
*dbr.InsertBuilder
Hook InsertHook
}
type DeleteQuery struct {
*dbr.DeleteBuilder
Hook DeleteHook
}
type UpdateQuery struct {
*dbr.UpdateBuilder
Hook UpdateHook
}
type UpsertQuery struct {
table string
*dbr.Session
whereConds map[string]string
upsertValues map[string]interface{}
}
// SelectQuery
// Example: Select().From().Where().Limit().Offset().OrderDir().Load()
// Select().From().Where().Limit().Offset().OrderDir().LoadOne()
// Select().From().Where().Count()
// SelectAll().From().Where().Limit().Offset().OrderDir().Load()
// SelectAll().From().Where().Limit().Offset().OrderDir().LoadOne()
// SelectAll().From().Where().Count()
func (db *Database) Select(columns ...string) *SelectQuery {
return &SelectQuery{db.Session.Select(columns...), 0}
}
func (db *Database) SelectBySql(query string, value ...interface{}) *SelectQuery {
return &SelectQuery{db.Session.SelectBySql(query, value...), 0}
}
func (db *Database) SelectAll(columns ...string) *SelectQuery {
return &SelectQuery{db.Session.Select("*"), 0}
}
func (b *SelectQuery) Join(table, on interface{}) *SelectQuery {
b.SelectBuilder.Join(table, on)
return b
}
func (b *SelectQuery) JoinAs(table string, alias string, on interface{}) *SelectQuery {
b.SelectBuilder.Join(dbr.I(table).As(alias), on)
return b
}
func (b *SelectQuery) From(table string) *SelectQuery {
b.SelectBuilder.From(table)
return b
}
func (b *SelectQuery) Where(query interface{}, value ...interface{}) *SelectQuery {
b.SelectBuilder.Where(query, value...)
return b
}
func (b *SelectQuery) GroupBy(col ...string) *SelectQuery {
b.SelectBuilder.GroupBy(col...)
return b
}
func (b *SelectQuery) Distinct() *SelectQuery {
b.SelectBuilder.Distinct()
return b
}
func (b *SelectQuery) Limit(n uint64) *SelectQuery {
n = GetLimit(n)
b.SelectBuilder.Limit(n)
return b
}
func (b *SelectQuery) Offset(n uint64) *SelectQuery {
n = GetLimit(n)
b.SelectBuilder.Offset(n)
return b
}
func (b *SelectQuery) OrderDir(col string, isAsc bool) *SelectQuery {
b.SelectBuilder.OrderDir(col, isAsc)
return b
}
func (b *SelectQuery) Load(value interface{}) (int, error) {
return b.SelectBuilder.Load(value)
}
func (b *SelectQuery) LoadOne(value interface{}) error {
return b.SelectBuilder.LoadOne(value)
}
func getColumns(dbrColumns []interface{}) string {
var columns []string
for _, column := range dbrColumns {
if c, ok := column.(string); ok {
columns = append(columns, c)
}
}
return strings.Join(columns, ", ")
}
func (b *SelectQuery) Count() (count uint32, err error) {
// cache SelectStmt
selectStmt := b.SelectStmt
limit := selectStmt.LimitCount
offset := selectStmt.OffsetCount
column := selectStmt.Column
isDistinct := selectStmt.IsDistinct
order := selectStmt.Order
b.SelectStmt.LimitCount = -1
b.SelectStmt.OffsetCount = -1
b.SelectStmt.Column = []interface{}{"COUNT(*)"}
b.SelectStmt.Order = []dbr.Builder{}
if isDistinct {
b.SelectStmt.Column = []interface{}{fmt.Sprintf("COUNT(DISTINCT %s)", getColumns(column))}
b.SelectStmt.IsDistinct = false
}
err = b.LoadOne(&count)
// fallback SelectStmt
selectStmt.LimitCount = limit
selectStmt.OffsetCount = offset
selectStmt.Column = column
selectStmt.IsDistinct = isDistinct
selectStmt.Order = order
b.SelectStmt = selectStmt
return
}
// InsertQuery
// Example: InsertInto().Columns().Record().Exec()
func (db *Database) InsertInto(table string) *InsertQuery {
return &InsertQuery{db.Session.InsertInto(table), db.InsertHook}
}
func (b *InsertQuery) Exec() (sql.Result, error) {
result, err := b.InsertBuilder.Exec()
if b.Hook != nil && err == nil {
defer b.Hook(b)
}
return result, err
}
func (b *InsertQuery) Columns(columns ...string) *InsertQuery {
b.InsertBuilder.Columns(columns...)
return b
}
func (b *InsertQuery) Record(structValue interface{}) *InsertQuery {
b.InsertBuilder.Record(structValue)
return b
}
// DeleteQuery
// Example: DeleteFrom().Where().Limit().Exec()
func (db *Database) DeleteFrom(table string) *DeleteQuery {
return &DeleteQuery{db.Session.DeleteFrom(table), db.DeleteHook}
}
func (b *DeleteQuery) Where(query interface{}, value ...interface{}) *DeleteQuery {
b.DeleteBuilder.Where(query, value...)
return b
}
func (b *DeleteQuery) Limit(n uint64) *DeleteQuery {
b.DeleteBuilder.Limit(n)
return b
}
func (b *DeleteQuery) Exec() (sql.Result, error) {
result, err := b.DeleteBuilder.Exec()
if b.Hook != nil && err == nil {
defer b.Hook(b)
}
return result, err
}
// UpdateQuery
// Example: Update().Set().Where().Exec()
func (db *Database) Update(table string) *UpdateQuery {
return &UpdateQuery{db.Session.Update(table), db.UpdateHook}
}
func (b *UpdateQuery) Exec() (sql.Result, error) {
result, err := b.UpdateBuilder.Exec()
if b.Hook != nil && err == nil {
defer b.Hook(b)
}
return result, err
}
func (b *UpdateQuery) Set(column string, value interface{}) *UpdateQuery {
b.UpdateBuilder.Set(column, value)
return b
}
func (b *UpdateQuery) SetMap(m map[string]interface{}) *UpdateQuery {
b.UpdateBuilder.SetMap(m)
return b
}
func (b *UpdateQuery) Where(query interface{}, value ...interface{}) *UpdateQuery {
b.UpdateBuilder.Where(query, value...)
return b
}
func (b *UpdateQuery) Limit(n uint64) *UpdateQuery {
b.UpdateBuilder.Limit(n)
return b
}

2
pkg/db/ddl/devops.sql Normal file
View File

@@ -0,0 +1,2 @@
CREATE DATABASE IF NOT EXISTS devops DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;
CREATE DATABASE IF NOT EXISTS kubesphere DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci;

31
pkg/db/error.go Normal file
View File

@@ -0,0 +1,31 @@
/*
Copyright 2018 The KubeSphere Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package db
import (
"github.com/gocraft/dbr"
)
// package errors
var (
ErrNotFound = dbr.ErrNotFound
ErrNotSupported = dbr.ErrNotSupported
ErrTableNotSpecified = dbr.ErrTableNotSpecified
ErrColumnNotSpecified = dbr.ErrColumnNotSpecified
ErrInvalidPointer = dbr.ErrInvalidPointer
ErrPlaceholderCount = dbr.ErrPlaceholderCount
ErrInvalidSliceLength = dbr.ErrInvalidSliceLength
ErrCantConvertToTime = dbr.ErrCantConvertToTime
ErrInvalidTimestring = dbr.ErrInvalidTimestring
)

55
pkg/db/event.go Normal file
View File

@@ -0,0 +1,55 @@
/*
Copyright 2018 The KubeSphere Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package db
import (
"github.com/golang/glog"
)
// EventReceiver is a sentinel EventReceiver; use it if the caller doesn't supply one
type EventReceiver struct{}
// Event receives a simple notification when various events occur
func (n *EventReceiver) Event(eventName string) {
}
// EventKv receives a notification when various events occur along with
// optional key/value data
func (n *EventReceiver) EventKv(eventName string, kvs map[string]string) {
}
// EventErr receives a notification of an error if one occurs
func (n *EventReceiver) EventErr(eventName string, err error) error {
return err
}
// EventErrKv receives a notification of an error if one occurs along with
// optional key/value data
func (n *EventReceiver) EventErrKv(eventName string, err error, kvs map[string]string) error {
glog.Errorf("%+v", err)
glog.Errorf("%s: %+v", eventName, kvs)
return err
}
// Timing receives the time an event took to happen
func (n *EventReceiver) Timing(eventName string, nanoseconds int64) {
}
// TimingKv receives the time an event took to happen along with optional key/value data
func (n *EventReceiver) TimingKv(eventName string, nanoseconds int64, kvs map[string]string) {
// TODO: Change logger level to debug
glog.Infof("%s spend %.2fms: %+v", eventName, float32(nanoseconds)/1000000, kvs)
}

View File

@@ -0,0 +1,23 @@
CREATE TABLE project (
`project_id` VARCHAR(50) NOT NULL,
`name` VARCHAR(50) NOT NULL,
`description` TEXT NOT NULL,
`creator` VARCHAR(50) NOT NULL,
`create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`status` VARCHAR(50) NOT NULL,
`visibility` VARCHAR(50) NOT NULL,
`extra` TEXT NOT NULL,
PRIMARY KEY (`project_id`)
);
CREATE TABLE `project_membership` (
`username` VARCHAR(50) NOT NULL,
`project_id` VARCHAR(50) NOT NULL,
`role` VARCHAR(50) NOT NULL,
`status` VARCHAR(50) NOT NULL,
`grant_by` VARCHAR(50) NOT NULL,
PRIMARY KEY (`username`, `project_id`)
);

View File

@@ -0,0 +1,8 @@
CREATE TABLE `project_credential` (
`project_id` VARCHAR(50) NOT NULL,
`credential_id` VARCHAR(255) NOT NULL,
`domain` VARCHAR(255) NOT NULL,
`creator` VARCHAR(50) NOT NULL,
`create_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`project_id`, `credential_id`, `domain`)
);

View File

@@ -0,0 +1,15 @@
CREATE TABLE IF NOT EXISTS `kubesphere.workspace_dp_bindings` (
`workspace` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
`dev_ops_project` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL,
PRIMARY KEY (`workspace`,`dev_ops_project`)
) ENGINE=InnoDB
ALTER TABLE kubesphere.workspace_dp_bindings
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE devops.project
ADD COLUMN workspace VARCHAR(255) NOT NULL DEFAULT '';
UPDATE devops.project t1
INNER JOIN kubesphere.workspace_dp_bindings t2 ON t1.project_id= t2.dev_ops_project
SET t1.workspace=t2.workspace;

17
pkg/db/scripts/ddl_init.sh Executable file
View File

@@ -0,0 +1,17 @@
#!/bin/sh
cd /flyway/sql/ddl
[ -n "$PASSWORD" ] && OPT="-p$(echo "$PASSWORD" | tr -d '\n')"
for F in $(ls *.sql)
do
echo "Start process $F"
mysql "$@" "$OPT" < "$F"
if [ $? -ne 0 ]; then
echo "Process $F failed"
return 1
else
echo "Process $F successful"
fi
done

24
pkg/gojenkins/README.md Normal file
View File

@@ -0,0 +1,24 @@
# Jenkins API Client for Go
## About
Jenkins is the most popular Open Source Continuous Integration system. This Library will help you interact with Jenkins in a more developer-friendly way.
Fork From https://github.com/bndr/gojenkins
These are some of the features that are currently implemented:
* Get information on test-results of completed/failed build
* Ability to query Nodes, and manipulate them. Start, Stop, set Offline.
* Ability to query Jobs, and manipulate them.
* Get Plugins, Builds, Artifacts, Fingerprints
* Validate Fingerprints of Artifacts
* Get Current Queue, Cancel Tasks
* etc. For all methods go to GoDoc Reference.
Add some features:
* Credentials Management
* Pipeline Model Converter
* RBAC control

View File

@@ -0,0 +1,4 @@
<table page-has-up="false" page-has-down="false" page-entry-newest="-9223372036854775805" page-entry-oldest="-9223372036854775807" class="pane hasPageData"><tr page-entry-id="-9223372036854775805" class="build-row single-line"><td class="build-row-cell"><div class="pane build-name"><div class="build-icon"><a href="/jenkins/job/j1/3/console" class="build-status-link"><img src="/jenkins/images/16x16/blue.png" alt="Success &gt; Console Output" tooltip="Success &gt; Console Output" style="width: 16px; height: 16px; " class="icon-blue icon-sm" /></a></div><a update-parent-class=".build-row" href="/jenkins/job/j1/3/" class="tip model-link inside build-link display-name">#3</a></div><div time="1484327939346" class="pane build-details"><a update-parent-class=".build-row" href="/jenkins/job/j1/3/" class="tip model-link inside build-link">Jan 13, 2017 9:18 AM</a></div><div class="pane build-controls"><div class="middle-align build-badge"></div></div><div class="left-bar"></div></td></tr><tr page-entry-id="-9223372036854775806" class="build-row single-line"><td class="build-row-cell"><div class="pane build-name"><div class="build-icon"><a href="/jenkins/job/j1/2/console" class="build-status-link"><img src="/jenkins/images/16x16/blue.png" alt="Success &gt; Console Output" tooltip="Success &gt; Console Output" style="width: 16px; height: 16px; " class="icon-blue icon-sm" /></a></div><a update-parent-class=".build-row" href="/jenkins/job/j1/2/" class="tip model-link inside build-link display-name">#2</a></div><div time="1484327935341" class="pane build-details"><a update-parent-class=".build-row" href="/jenkins/job/j1/2/" class="tip model-link inside build-link">Jan 13, 2017 9:18 AM</a></div><div class="pane build-controls"><div class="middle-align build-badge"></div></div><div class="left-bar"></div></td></tr><tr page-entry-id="-9223372036854775807" class="build-row single-line"><td class="build-row-cell"><div class="pane build-name"><div class="build-icon"><a href="/jenkins/job/j1/1/console" class="build-status-link"><img src="/jenkins/images/16x16/blue.png" alt="Success &gt; Console Output" tooltip="Success &gt; Console Output" style="width: 16px; height: 16px; " class="icon-blue icon-sm" /></a></div><a update-parent-class=".build-row" href="/jenkins/job/j1/1/" class="tip model-link inside build-link display-name">#1</a></div><div time="1484327924442" class="pane build-details"><a update-parent-class=".build-row" href="/jenkins/job/j1/1/" class="tip model-link inside build-link">Jan 13, 2017 9:18 AM</a></div><div class="pane build-controls"><div class="middle-align build-badge"></div></div><div class="left-bar"></div></td></tr></table>

View File

@@ -0,0 +1,27 @@
<?xml version='1.0' encoding='UTF-8'?>
<project>
<actions/>
<description>Some Job Description</description>
<keepDependencies>false</keepDependencies>
<properties>
<hudson.model.ParametersDefinitionProperty>
<parameterDefinitions>
<hudson.model.StringParameterDefinition>
<name>params1</name>
<description>description</description>
<defaultValue>defaultVal</defaultValue>
</hudson.model.StringParameterDefinition>
</parameterDefinitions>
</hudson.model.ParametersDefinitionProperty>
</properties>
<scm class="hudson.scm.NullSCM"/>
<canRoam>true</canRoam>
<disabled>false</disabled>
<blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
<blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
<triggers class="vector"/>
<concurrentBuild>false</concurrentBuild>
<builders/>
<publishers/>
<buildWrappers/>
</project>

118
pkg/gojenkins/artifact.go Normal file
View File

@@ -0,0 +1,118 @@
// Copyright 2015 Vadim Kravcenko
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package gojenkins
import (
"crypto/md5"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"path"
)
// Represents an Artifact
type Artifact struct {
Jenkins *Jenkins
Build *Build
FileName string
Path string
}
// Get raw byte data of Artifact
func (a Artifact) GetData() ([]byte, error) {
var data string
response, err := a.Jenkins.Requester.Get(a.Path, &data, nil)
if err != nil {
return nil, err
}
code := response.StatusCode
if code != 200 {
Error.Printf("Jenkins responded with StatusCode: %d", code)
return nil, errors.New("Could not get File Contents")
}
return []byte(data), nil
}
// Save artifact to a specific path, using your own filename.
func (a Artifact) Save(path string) (bool, error) {
data, err := a.GetData()
if err != nil {
return false, errors.New("No Data received, not saving file.")
}
if _, err = os.Stat(path); err == nil {
Warning.Println("Local Copy already exists, Overwriting...")
}
err = ioutil.WriteFile(path, data, 0644)
a.validateDownload(path)
if err != nil {
return false, err
}
return true, nil
}
// Save Artifact to directory using Artifact filename.
func (a Artifact) SaveToDir(dir string) (bool, error) {
if _, err := os.Stat(dir); err != nil {
Error.Printf("can't save artifact: directory %s does not exist", dir)
return false, fmt.Errorf("can't save artifact: directory %s does not exist", dir)
}
saved, err := a.Save(path.Join(dir, a.FileName))
if err != nil {
return saved, nil
}
return saved, nil
}
// Compare Remote and local MD5
func (a Artifact) validateDownload(path string) (bool, error) {
localHash := a.getMD5local(path)
fp := FingerPrint{Jenkins: a.Jenkins, Base: "/fingerprint/", Id: localHash, Raw: new(FingerPrintResponse)}
valid, err := fp.ValidateForBuild(a.FileName, a.Build)
if err != nil {
return false, err
}
if !valid {
return false, errors.New("FingerPrint of the downloaded artifact could not be verified")
}
return true, nil
}
// Get Local MD5
func (a Artifact) getMD5local(path string) string {
h := md5.New()
localFile, err := os.Open(path)
if err != nil {
return ""
}
buffer := make([]byte, 2^20)
n, err := localFile.Read(buffer)
defer localFile.Close()
for err == nil {
io.WriteString(h, string(buffer[0:n]))
n, err = localFile.Read(buffer)
}
return fmt.Sprintf("%x", h.Sum(nil))
}

480
pkg/gojenkins/build.go Normal file
View File

@@ -0,0 +1,480 @@
// Copyright 2015 Vadim Kravcenko
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package gojenkins
import (
"bytes"
"errors"
"net/http"
"net/url"
"regexp"
"strconv"
"time"
)
type Build struct {
Raw *BuildResponse
Job *Job
Jenkins *Jenkins
Base string
Depth int
}
type parameter struct {
Name string
Value string
}
type branch struct {
SHA1 string `json:",omitempty"`
Name string `json:",omitempty"`
}
type BuildRevision struct {
SHA1 string `json:"SHA1,omitempty"`
Branch []branch `json:"branch,omitempty"`
}
type Builds struct {
BuildNumber int64 `json:"buildNumber"`
BuildResult interface{} `json:"buildResult"`
Marked BuildRevision `json:"marked"`
Revision BuildRevision `json:"revision"`
}
type Culprit struct {
AbsoluteUrl string
FullName string
}
type GeneralObj struct {
Parameters []parameter `json:"parameters,omitempty"`
Causes []map[string]interface{} `json:"causes,omitempty"`
BuildsByBranchName map[string]Builds `json:"buildsByBranchName,omitempty"`
LastBuiltRevision *BuildRevision `json:"lastBuiltRevision,omitempty"`
RemoteUrls []string `json:"remoteUrls,omitempty"`
ScmName string `json:"scmName,omitempty"`
MercurialNodeName string `json:"mercurialNodeName,omitempty"`
MercurialRevisionNumber string `json:"mercurialRevisionNumber,omitempty"`
Subdir interface{} `json:"subdir,omitempty"`
ClassName string `json:"_class,omitempty"`
SonarTaskId string `json:"ceTaskId,omitempty"`
SonarServerUrl string `json:"serverUrl,omitempty"`
SonarDashboardUrl string `json:"sonarqubeDashboardUrl,omitempty"`
TotalCount int64 `json:",omitempty"`
UrlName string `json:",omitempty"`
}
type TestResult struct {
Duration int64 `json:"duration"`
Empty bool `json:"empty"`
FailCount int64 `json:"failCount"`
PassCount int64 `json:"passCount"`
SkipCount int64 `json:"skipCount"`
Suites []struct {
Cases []struct {
Age int64 `json:"age"`
ClassName string `json:"className"`
Duration int64 `json:"duration"`
ErrorDetails interface{} `json:"errorDetails"`
ErrorStackTrace interface{} `json:"errorStackTrace"`
FailedSince int64 `json:"failedSince"`
Name string `json:"name"`
Skipped bool `json:"skipped"`
SkippedMessage interface{} `json:"skippedMessage"`
Status string `json:"status"`
Stderr interface{} `json:"stderr"`
Stdout interface{} `json:"stdout"`
} `json:"cases"`
Duration int64 `json:"duration"`
ID interface{} `json:"id"`
Name string `json:"name"`
Stderr interface{} `json:"stderr"`
Stdout interface{} `json:"stdout"`
Timestamp interface{} `json:"timestamp"`
} `json:"suites"`
}
type BuildResponse struct {
Actions []GeneralObj
Artifacts []struct {
DisplayPath string `json:"displayPath"`
FileName string `json:"fileName"`
RelativePath string `json:"relativePath"`
} `json:"artifacts"`
Building bool `json:"building"`
BuiltOn string `json:"builtOn"`
ChangeSet struct {
Items []struct {
AffectedPaths []string `json:"affectedPaths"`
Author struct {
AbsoluteUrl string `json:"absoluteUrl"`
FullName string `json:"fullName"`
} `json:"author"`
Comment string `json:"comment"`
CommitID string `json:"commitId"`
Date string `json:"date"`
ID string `json:"id"`
Msg string `json:"msg"`
Paths []struct {
EditType string `json:"editType"`
File string `json:"file"`
} `json:"paths"`
Timestamp int64 `json:"timestamp"`
} `json:"items"`
Kind string `json:"kind"`
Revisions []struct {
Module string
Revision int
} `json:"revision"`
} `json:"changeSet"`
Culprits []Culprit `json:"culprits"`
Description interface{} `json:"description"`
Duration int64 `json:"duration"`
EstimatedDuration int64 `json:"estimatedDuration"`
Executor interface{} `json:"executor"`
FullDisplayName string `json:"fullDisplayName"`
ID string `json:"id"`
KeepLog bool `json:"keepLog"`
Number int64 `json:"number"`
QueueID int64 `json:"queueId"`
Result string `json:"result"`
Timestamp int64 `json:"timestamp"`
URL string `json:"url"`
MavenArtifacts interface{} `json:"mavenArtifacts"`
MavenVersionUsed string `json:"mavenVersionUsed"`
FingerPrint []FingerPrintResponse
Runs []struct {
Number int64
URL string
} `json:"runs"`
}
// Builds
func (b *Build) Info() *BuildResponse {
return b.Raw
}
func (b *Build) GetActions() []GeneralObj {
return b.Raw.Actions
}
func (b *Build) GetUrl() string {
return b.Raw.URL
}
func (b *Build) GetBuildNumber() int64 {
return b.Raw.Number
}
func (b *Build) GetResult() string {
return b.Raw.Result
}
func (b *Build) GetArtifacts() []Artifact {
artifacts := make([]Artifact, len(b.Raw.Artifacts))
for i, artifact := range b.Raw.Artifacts {
artifacts[i] = Artifact{
Jenkins: b.Jenkins,
Build: b,
FileName: artifact.FileName,
Path: b.Base + "/artifact/" + artifact.RelativePath,
}
}
return artifacts
}
func (b *Build) GetCulprits() []Culprit {
return b.Raw.Culprits
}
func (b *Build) Stop() (bool, error) {
if b.IsRunning() {
response, err := b.Jenkins.Requester.Post(b.Base+"/stop", nil, nil, nil)
if err != nil {
return false, err
}
if response.StatusCode != http.StatusOK {
return false, errors.New(strconv.Itoa(response.StatusCode))
}
}
return true, nil
}
func (b *Build) GetConsoleOutput() string {
url := b.Base + "/consoleText"
var content string
b.Jenkins.Requester.GetXML(url, &content, nil)
return content
}
func (b *Build) GetCauses() ([]map[string]interface{}, error) {
_, err := b.Poll()
if err != nil {
return nil, err
}
for _, a := range b.Raw.Actions {
if a.Causes != nil {
return a.Causes, nil
}
}
return nil, errors.New("No Causes")
}
func (b *Build) GetParameters() []parameter {
for _, a := range b.Raw.Actions {
if a.Parameters != nil {
return a.Parameters
}
}
return nil
}
func (b *Build) GetInjectedEnvVars() (map[string]string, error) {
var envVars struct {
EnvMap map[string]string `json:"envMap"`
}
endpoint := b.Base + "/injectedEnvVars"
_, err := b.Jenkins.Requester.GetJSON(endpoint, &envVars, nil)
if err != nil {
return envVars.EnvMap, err
}
return envVars.EnvMap, nil
}
func (b *Build) GetDownstreamBuilds() ([]*Build, error) {
result := make([]*Build, 0)
downstreamJobs, err := b.Job.GetDownstreamJobs()
if err != nil {
return nil, err
}
for _, job := range downstreamJobs {
allBuildIDs, err := job.GetAllBuildIds()
if err != nil {
return nil, err
}
for _, buildID := range allBuildIDs {
build, err := job.GetBuild(buildID.Number)
if err != nil {
return nil, err
}
upstreamBuild, _ := build.GetUpstreamBuild()
// cannot compare only id, it can be from different job
if b.GetUrl() == upstreamBuild.GetUrl() {
result = append(result, build)
break
}
}
}
return result, nil
}
func (b *Build) GetDownstreamJobNames() []string {
result := make([]string, 0)
downstreamJobs := b.Job.GetDownstreamJobsMetadata()
fingerprints := b.GetAllFingerPrints()
for _, fingerprint := range fingerprints {
for _, usage := range fingerprint.Raw.Usage {
for _, job := range downstreamJobs {
if job.Name == usage.Name {
result = append(result, job.Name)
}
}
}
}
return result
}
func (b *Build) GetAllFingerPrints() []*FingerPrint {
b.Poll(3)
result := make([]*FingerPrint, len(b.Raw.FingerPrint))
for i, f := range b.Raw.FingerPrint {
result[i] = &FingerPrint{Jenkins: b.Jenkins, Base: "/fingerprint/", Id: f.Hash, Raw: &f}
}
return result
}
func (b *Build) GetUpstreamJob() (*Job, error) {
causes, err := b.GetCauses()
if err != nil {
return nil, err
}
if len(causes) > 0 {
if job, ok := causes[0]["upstreamProject"]; ok {
return b.Jenkins.GetJob(job.(string))
}
}
return nil, errors.New("Unable to get Upstream Job")
}
func (b *Build) GetUpstreamBuildNumber() (int64, error) {
causes, err := b.GetCauses()
if err != nil {
return 0, err
}
if len(causes) > 0 {
if build, ok := causes[0]["upstreamBuild"]; ok {
switch t := build.(type) {
default:
return t.(int64), nil
case float64:
return int64(t), nil
}
}
}
return 0, nil
}
func (b *Build) GetUpstreamBuild() (*Build, error) {
job, err := b.GetUpstreamJob()
if err != nil {
return nil, err
}
if job != nil {
buildNumber, err := b.GetUpstreamBuildNumber()
if err == nil {
return job.GetBuild(buildNumber)
}
}
return nil, errors.New("Build not found")
}
func (b *Build) GetMatrixRuns() ([]*Build, error) {
_, err := b.Poll(0)
if err != nil {
return nil, err
}
runs := b.Raw.Runs
result := make([]*Build, len(b.Raw.Runs))
r, _ := regexp.Compile(`job/(.*?)/(.*?)/(\d+)/`)
for i, run := range runs {
result[i] = &Build{Jenkins: b.Jenkins, Job: b.Job, Raw: new(BuildResponse), Depth: 1, Base: "/" + r.FindString(run.URL)}
result[i].Poll()
}
return result, nil
}
func (b *Build) GetResultSet() (*TestResult, error) {
url := b.Base + "/testReport"
var report TestResult
_, err := b.Jenkins.Requester.GetJSON(url, &report, nil)
if err != nil {
return nil, err
}
return &report, nil
}
func (b *Build) GetTimestamp() time.Time {
msInt := int64(b.Raw.Timestamp)
return time.Unix(0, msInt*int64(time.Millisecond))
}
func (b *Build) GetDuration() int64 {
return b.Raw.Duration
}
func (b *Build) GetRevision() string {
vcs := b.Raw.ChangeSet.Kind
if vcs == "git" || vcs == "hg" {
for _, a := range b.Raw.Actions {
if a.LastBuiltRevision.SHA1 != "" {
return a.LastBuiltRevision.SHA1
}
if a.MercurialRevisionNumber != "" {
return a.MercurialRevisionNumber
}
}
} else if vcs == "svn" {
return strconv.Itoa(b.Raw.ChangeSet.Revisions[0].Revision)
}
return ""
}
func (b *Build) GetRevisionBranch() string {
vcs := b.Raw.ChangeSet.Kind
if vcs == "git" {
for _, a := range b.Raw.Actions {
if len(a.LastBuiltRevision.Branch) > 0 && a.LastBuiltRevision.Branch[0].SHA1 != "" {
return a.LastBuiltRevision.Branch[0].SHA1
}
}
} else {
panic("Not implemented")
}
return ""
}
func (b *Build) IsGood() bool {
return (!b.IsRunning() && b.Raw.Result == STATUS_SUCCESS)
}
func (b *Build) IsRunning() bool {
_, err := b.Poll()
if err != nil {
return false
}
return b.Raw.Building
}
func (b *Build) SetDescription(description string) error {
data := url.Values{}
data.Set("description", description)
_, err := b.Jenkins.Requester.Post(b.Base+"/submitDescription", bytes.NewBufferString(data.Encode()), nil, nil)
return err
}
func (b *Build) PauseToggle() error {
response, err := b.Jenkins.Requester.Post(b.Base+"/pause/toggle", nil, nil, nil)
if err != nil {
return err
}
if response.StatusCode != http.StatusOK {
return errors.New(strconv.Itoa(response.StatusCode))
}
return nil
}
// Poll for current data. Optional parameter - depth.
// More about depth here: https://wiki.jenkins-ci.org/display/JENKINS/Remote+access+API
func (b *Build) Poll(options ...interface{}) (int, error) {
depth := "-1"
for _, o := range options {
switch v := o.(type) {
case string:
depth = v
case int:
depth = strconv.Itoa(v)
case int64:
depth = strconv.FormatInt(v, 10)
}
}
if depth == "-1" {
depth = strconv.Itoa(b.Depth)
}
qr := map[string]string{
"depth": depth,
}
response, err := b.Jenkins.Requester.GetJSON(b.Base, b.Raw, qr)
if err != nil {
return 0, err
}
return response.StatusCode, nil
}

View File

@@ -0,0 +1,109 @@
package gojenkins
import (
"io"
"strconv"
"strings"
"golang.org/x/net/html"
)
// Parse jenkins ajax response in order find the current jenkins build history
func parseBuildHistory(d io.Reader) []*History {
z := html.NewTokenizer(d)
depth := 0
buildRowCellDepth := -1
builds := make([]*History, 0)
var curBuild *History
for {
tt := z.Next()
switch tt {
case html.ErrorToken:
if z.Err() == io.EOF {
return builds
}
case html.SelfClosingTagToken:
tn, hasAttr := z.TagName()
// fmt.Println("START__", string(tn), hasAttr)
if hasAttr {
a := attr(z)
// <img src="/static/f2881562/images/16x16/red.png" alt="Failed &gt; Console Output" tooltip="Failed &gt; Console Output" style="width: 16px; height: 16px; " class="icon-red icon-sm" />
if string(tn) == "img" {
if hasCSSClass(a, "icon-sm") && buildRowCellDepth > -1 {
if alt, found := a["alt"]; found {
curBuild.BuildStatus = strings.Fields(alt)[0]
}
}
}
}
case html.StartTagToken:
depth++
tn, hasAttr := z.TagName()
// fmt.Println("START__", string(tn), hasAttr)
if hasAttr {
a := attr(z)
// <td class="build-row-cell">
if string(tn) == "td" {
if hasCSSClass(a, "build-row-cell") {
buildRowCellDepth = depth
curBuild = &History{}
builds = append(builds, curBuild)
}
}
// <a update-parent-class=".build-row" href="/job/appscode/job/43/job/build-binary/227/" class="tip model-link inside build-link display-name">#227</a>
if string(tn) == "a" {
if hasCSSClass(a, "build-link") && buildRowCellDepth > -1 {
if href, found := a["href"]; found {
parts := strings.Split(href, "/")
if num, err := strconv.Atoi(parts[len(parts)-2]); err == nil {
curBuild.BuildNumber = num
}
}
}
}
// <div time="1469024602546" class="pane build-details"> ... </div>
if string(tn) == "div" {
if hasCSSClass(a, "build-details") && buildRowCellDepth > -1 {
if t, found := a["time"]; found {
if msec, err := strconv.ParseInt(t, 10, 0); err == nil {
curBuild.BuildTimestamp = msec / 1000
}
}
}
}
}
case html.EndTagToken:
tn, _ := z.TagName()
if string(tn) == "td" && depth == buildRowCellDepth {
buildRowCellDepth = -1
curBuild = nil
}
depth--
}
}
}
func attr(z *html.Tokenizer) map[string]string {
a := make(map[string]string)
for {
k, v, more := z.TagAttr()
if k != nil && v != nil {
a[string(k)] = string(v)
}
if !more {
break
}
}
return a
}
func hasCSSClass(a map[string]string, className string) bool {
if classes, found := a["class"]; found {
for _, class := range strings.Fields(classes) {
if class == className {
return true
}
}
}
return false
}

View File

@@ -0,0 +1,20 @@
package gojenkins
const (
STATUS_FAIL = "FAIL"
STATUS_ERROR = "ERROR"
STATUS_ABORTED = "ABORTED"
STATUS_REGRESSION = "REGRESSION"
STATUS_SUCCESS = "SUCCESS"
STATUS_FIXED = "FIXED"
STATUS_PASSED = "PASSED"
RESULT_STATUS_FAILURE = "FAILURE"
RESULT_STATUS_FAILED = "FAILED"
RESULT_STATUS_SKIPPED = "SKIPPED"
STR_RE_SPLIT_VIEW = "(.*)/view/([^/]*)/?"
)
const (
GLOBAL_ROLE = "globalRoles"
PROJECT_ROLE = "projectRoles"
)

225
pkg/gojenkins/credential.go Normal file
View File

@@ -0,0 +1,225 @@
/*
Copyright 2018 The KubeSphere Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package gojenkins
const SSHCrenditalStaplerClass = "com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey"
const DirectSSHCrenditalStaplerClass = "com.cloudbees.jenkins.plugins.sshcredentials.impl.BasicSSHUserPrivateKey$DirectEntryPrivateKeySource"
const UsernamePassswordCredentialStaplerClass = "com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl"
const SecretTextCredentialStaplerClass = "org.jenkinsci.plugins.plaincredentials.impl.StringCredentialsImpl"
const KubeconfigCredentialStaplerClass = "com.microsoft.jenkins.kubernetes.credentials.KubeconfigCredentials"
const DirectKubeconfigCredentialStaperClass = "com.microsoft.jenkins.kubernetes.credentials.KubeconfigCredentials$DirectEntryKubeconfigSource"
const GLOBALScope = "GLOBAL"
type CreateSshCredentialRequest struct {
Credentials SshCredential `json:"credentials"`
}
type CreateUsernamePasswordCredentialRequest struct {
Credentials UsernamePasswordCredential `json:"credentials"`
}
type CreateSecretTextCredentialRequest struct {
Credentials SecretTextCredential `json:"credentials"`
}
type CreateKubeconfigCredentialRequest struct {
Credentials KubeconfigCredential `json:"credentials"`
}
type UsernamePasswordCredential struct {
Scope string `json:"scope"`
Id string `json:"id"`
Username string `json:"username"`
Password string `json:"password"`
Description string `json:"description"`
StaplerClass string `json:"stapler-class"`
}
type SshCredential struct {
Scope string `json:"scope"`
Id string `json:"id"`
Username string `json:"username"`
Passphrase string `json:"passphrase"`
KeySource PrivateKeySource `json:"privateKeySource"`
Description string `json:"description"`
StaplerClass string `json:"stapler-class"`
}
type SecretTextCredential struct {
Scope string `json:"scope"`
Id string `json:"id"`
Secret string `json:"secret"`
Description string `json:"description"`
StaplerClass string `json:"stapler-class"`
}
type KubeconfigCredential struct {
Scope string `json:"scope"`
Id string `json:"id"`
Description string `json:"description"`
KubeconfigSource KubeconfigSource `json:"kubeconfigSource"`
StaplerClass string `json:"stapler-class"`
}
type PrivateKeySource struct {
StaplerClass string `json:"stapler-class"`
PrivateKey string `json:"privateKey"`
}
type KubeconfigSource struct {
StaplerClass string `json:"stapler-class"`
Content string `json:"content"`
}
type CredentialResponse struct {
Id string `json:"id"`
TypeName string `json:"typeName"`
DisplayName string `json:"displayName"`
Fingerprint *struct {
FileName string `json:"fileName,omitempty"`
Hash string `json:"hash,omitempty"`
Usage []*struct {
Name string `json:"name,omitempty"`
Ranges struct {
Ranges []*struct {
Start int `json:"start"`
End int `json:"end"`
} `json:"ranges"`
} `json:"ranges"`
} `json:"usage,omitempty"`
} `json:"fingerprint,omitempty"`
Description string `json:"description"`
Domain string `json:"domain"`
}
func NewCreateSshCredentialRequest(id, username, passphrase, privateKey, description string) *CreateSshCredentialRequest {
keySource := PrivateKeySource{
StaplerClass: DirectSSHCrenditalStaplerClass,
PrivateKey: privateKey,
}
sshCredential := SshCredential{
Scope: GLOBALScope,
Id: id,
Username: username,
Passphrase: passphrase,
KeySource: keySource,
Description: description,
StaplerClass: SSHCrenditalStaplerClass,
}
return &CreateSshCredentialRequest{
Credentials: sshCredential,
}
}
func NewCreateUsernamePasswordRequest(id, username, password, description string) *CreateUsernamePasswordCredentialRequest {
credential := UsernamePasswordCredential{
Scope: GLOBALScope,
Id: id,
Username: username,
Password: password,
Description: description,
StaplerClass: UsernamePassswordCredentialStaplerClass,
}
return &CreateUsernamePasswordCredentialRequest{
Credentials: credential,
}
}
func NewCreateSecretTextCredentialRequest(id, secret, description string) *CreateSecretTextCredentialRequest {
credential := SecretTextCredential{
Scope: GLOBALScope,
Id: id,
Secret: secret,
Description: description,
StaplerClass: SecretTextCredentialStaplerClass,
}
return &CreateSecretTextCredentialRequest{
Credentials: credential,
}
}
func NewCreateKubeconfigCredentialRequest(id, content, description string) *CreateKubeconfigCredentialRequest {
credentialSource := KubeconfigSource{
StaplerClass: DirectKubeconfigCredentialStaperClass,
Content: content,
}
credential := KubeconfigCredential{
Scope: GLOBALScope,
Id: id,
Description: description,
KubeconfigSource: credentialSource,
StaplerClass: KubeconfigCredentialStaplerClass,
}
return &CreateKubeconfigCredentialRequest{
credential,
}
}
func NewSshCredential(id, username, passphrase, privateKey, description string) *SshCredential {
keySource := PrivateKeySource{
StaplerClass: DirectSSHCrenditalStaplerClass,
PrivateKey: privateKey,
}
return &SshCredential{
Scope: GLOBALScope,
Id: id,
Username: username,
Passphrase: passphrase,
KeySource: keySource,
Description: description,
StaplerClass: SSHCrenditalStaplerClass,
}
}
func NewUsernamePasswordCredential(id, username, password, description string) *UsernamePasswordCredential {
return &UsernamePasswordCredential{
Scope: GLOBALScope,
Id: id,
Username: username,
Password: password,
Description: description,
StaplerClass: UsernamePassswordCredentialStaplerClass,
}
}
func NewSecretTextCredential(id, secret, description string) *SecretTextCredential {
return &SecretTextCredential{
Scope: GLOBALScope,
Id: id,
Secret: secret,
Description: description,
StaplerClass: SecretTextCredentialStaplerClass,
}
}
func NewKubeconfigCredential(id, content, description string) *KubeconfigCredential {
credentialSource := KubeconfigSource{
StaplerClass: DirectKubeconfigCredentialStaperClass,
Content: content,
}
return &KubeconfigCredential{
Scope: GLOBALScope,
Id: id,
Description: description,
KubeconfigSource: credentialSource,
StaplerClass: KubeconfigCredentialStaplerClass,
}
}

44
pkg/gojenkins/executor.go Normal file
View File

@@ -0,0 +1,44 @@
// Copyright 2015 Vadim Kravcenko
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package gojenkins
type Executor struct {
Raw *ExecutorResponse
Jenkins *Jenkins
}
type ViewData struct {
Name string `json:"name"`
URL string `json:"url"`
}
type ExecutorResponse struct {
AssignedLabels []struct{} `json:"assignedLabels"`
Description interface{} `json:"description"`
Jobs []InnerJob `json:"jobs"`
Mode string `json:"mode"`
NodeDescription string `json:"nodeDescription"`
NodeName string `json:"nodeName"`
NumExecutors int64 `json:"numExecutors"`
OverallLoad struct{} `json:"overallLoad"`
PrimaryView struct {
Name string `json:"name"`
URL string `json:"url"`
} `json:"primaryView"`
QuietingDown bool `json:"quietingDown"`
SlaveAgentPort int64 `json:"slaveAgentPort"`
UnlabeledLoad struct{} `json:"unlabeledLoad"`
UseCrumbs bool `json:"useCrumbs"`
UseSecurity bool `json:"useSecurity"`
Views []ViewData `json:"views"`
}

View File

@@ -0,0 +1,95 @@
// Copyright 2015 Vadim Kravcenko
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package gojenkins
import (
"errors"
"fmt"
)
type FingerPrint struct {
Jenkins *Jenkins
Base string
Id string
Raw *FingerPrintResponse
}
type FingerPrintResponse struct {
FileName string `json:"fileName"`
Hash string `json:"hash"`
Original struct {
Name string
Number int64
} `json:"original"`
Timestamp int64 `json:"timestamp"`
Usage []struct {
Name string `json:"name"`
Ranges struct {
Ranges []struct {
End int64 `json:"end"`
Start int64 `json:"start"`
} `json:"ranges"`
} `json:"ranges"`
} `json:"usage"`
}
func (f FingerPrint) Valid() (bool, error) {
status, err := f.Poll()
if err != nil {
return false, err
}
if status != 200 || f.Raw.Hash != f.Id {
return false, fmt.Errorf("Jenkins says %s is Invalid or the Status is unknown", f.Id)
}
return true, nil
}
func (f FingerPrint) ValidateForBuild(filename string, build *Build) (bool, error) {
valid, err := f.Valid()
if err != nil {
return false, err
}
if valid {
return true, nil
}
if f.Raw.FileName != filename {
return false, errors.New("Filename does not Match")
}
if build != nil && f.Raw.Original.Name == build.Job.GetName() &&
f.Raw.Original.Number == build.GetBuildNumber() {
return true, nil
}
return false, nil
}
func (f FingerPrint) GetInfo() (*FingerPrintResponse, error) {
_, err := f.Poll()
if err != nil {
return nil, err
}
return f.Raw, nil
}
func (f FingerPrint) Poll() (int, error) {
response, err := f.Jenkins.Requester.GetJSON(f.Base+f.Id, f.Raw, nil)
if err != nil {
return 0, err
}
return response.StatusCode, nil
}

77
pkg/gojenkins/folder.go Normal file
View File

@@ -0,0 +1,77 @@
// Copyright 2015 Vadim Kravcenko
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package gojenkins
import (
"errors"
"strconv"
"strings"
)
type Folder struct {
Raw *FolderResponse
Jenkins *Jenkins
Base string
}
type FolderResponse struct {
Actions []GeneralObj
Description string `json:"description"`
DisplayName string `json:"displayName"`
Name string `json:"name"`
URL string `json:"url"`
Jobs []InnerJob `json:"jobs"`
PrimaryView *ViewData `json:"primaryView"`
Views []ViewData `json:"views"`
}
func (f *Folder) parentBase() string {
return f.Base[:strings.LastIndex(f.Base, "/job")]
}
func (f *Folder) GetName() string {
return f.Raw.Name
}
func (f *Folder) Create(name, description string) (*Folder, error) {
mode := "com.cloudbees.hudson.plugins.folder.Folder"
data := map[string]string{
"name": name,
"mode": mode,
"Submit": "OK",
"json": makeJson(map[string]string{
"name": name,
"mode": mode,
"description": description,
}),
}
r, err := f.Jenkins.Requester.Post(f.parentBase()+"/createItem", nil, f.Raw, data)
if err != nil {
return nil, err
}
if r.StatusCode == 200 {
f.Poll()
return f, nil
}
return nil, errors.New(strconv.Itoa(r.StatusCode))
}
func (f *Folder) Poll() (int, error) {
response, err := f.Jenkins.Requester.GetJSON(f.Base, f.Raw, nil)
if err != nil {
return 0, err
}
return response.StatusCode, nil
}

1067
pkg/gojenkins/jenkins.go Normal file

File diff suppressed because it is too large Load Diff

529
pkg/gojenkins/job.go Normal file
View File

@@ -0,0 +1,529 @@
// Copyright 2015 Vadim Kravcenko
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package gojenkins
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/url"
"path"
"strconv"
"strings"
)
type Job struct {
Raw *JobResponse
Jenkins *Jenkins
Base string
}
type JobBuild struct {
Number int64
URL string
}
type JobBuildStatus struct {
Number int64
Building bool
Result string
}
type InnerJob struct {
Name string `json:"name"`
Url string `json:"url"`
Color string `json:"color"`
}
type ParameterDefinition struct {
DefaultParameterValue struct {
Name string `json:"name"`
Value interface{} `json:"value"`
} `json:"defaultParameterValue"`
Description string `json:"description"`
Name string `json:"name"`
Type string `json:"type"`
}
type JobResponse struct {
Class string `json:"_class"`
Actions []GeneralObj
Buildable bool `json:"buildable"`
Builds []JobBuild
Color string `json:"color"`
ConcurrentBuild bool `json:"concurrentBuild"`
Description string `json:"description"`
DisplayName string `json:"displayName"`
DisplayNameOrNull interface{} `json:"displayNameOrNull"`
DownstreamProjects []InnerJob `json:"downstreamProjects"`
FirstBuild JobBuild
HealthReport []struct {
Description string `json:"description"`
IconClassName string `json:"iconClassName"`
IconUrl string `json:"iconUrl"`
Score int64 `json:"score"`
} `json:"healthReport"`
InQueue bool `json:"inQueue"`
KeepDependencies bool `json:"keepDependencies"`
LastBuild JobBuild `json:"lastBuild"`
LastCompletedBuild JobBuild `json:"lastCompletedBuild"`
LastFailedBuild JobBuild `json:"lastFailedBuild"`
LastStableBuild JobBuild `json:"lastStableBuild"`
LastSuccessfulBuild JobBuild `json:"lastSuccessfulBuild"`
LastUnstableBuild JobBuild `json:"lastUnstableBuild"`
LastUnsuccessfulBuild JobBuild `json:"lastUnsuccessfulBuild"`
Name string `json:"name"`
SubJobs []InnerJob `json:"subJobs"`
NextBuildNumber int64 `json:"nextBuildNumber"`
Property []struct {
ParameterDefinitions []ParameterDefinition `json:"parameterDefinitions"`
} `json:"property"`
QueueItem interface{} `json:"queueItem"`
Scm struct{} `json:"scm"`
UpstreamProjects []InnerJob `json:"upstreamProjects"`
URL string `json:"url"`
Jobs []InnerJob `json:"jobs"`
PrimaryView *ViewData `json:"primaryView"`
Views []ViewData `json:"views"`
}
func (j *Job) parentBase() string {
return j.Base[:strings.LastIndex(j.Base, "/job/")]
}
type History struct {
BuildNumber int
BuildStatus string
BuildTimestamp int64
}
func (j *Job) GetName() string {
return j.Raw.Name
}
func (j *Job) GetDescription() string {
return j.Raw.Description
}
func (j *Job) GetDetails() *JobResponse {
return j.Raw
}
func (j *Job) GetBuild(id int64) (*Build, error) {
build := Build{Jenkins: j.Jenkins, Job: j, Raw: new(BuildResponse), Depth: 1, Base: "/job/" + j.GetName() + "/" + strconv.FormatInt(id, 10)}
status, err := build.Poll()
if err != nil {
return nil, err
}
if status == 200 {
return &build, nil
}
return nil, errors.New(strconv.Itoa(status))
}
func (j *Job) getBuildByType(buildType string) (*Build, error) {
allowed := map[string]JobBuild{
"lastStableBuild": j.Raw.LastStableBuild,
"lastSuccessfulBuild": j.Raw.LastSuccessfulBuild,
"lastBuild": j.Raw.LastBuild,
"lastCompletedBuild": j.Raw.LastCompletedBuild,
"firstBuild": j.Raw.FirstBuild,
"lastFailedBuild": j.Raw.LastFailedBuild,
}
number := ""
if val, ok := allowed[buildType]; ok {
number = strconv.FormatInt(val.Number, 10)
} else {
panic("No Such Build")
}
build := Build{
Jenkins: j.Jenkins,
Depth: 1,
Job: j,
Raw: new(BuildResponse),
Base: j.Base + "/" + number}
status, err := build.Poll()
if err != nil {
return nil, err
}
if status == 200 {
return &build, nil
}
return nil, errors.New(strconv.Itoa(status))
}
func (j *Job) GetLastSuccessfulBuild() (*Build, error) {
return j.getBuildByType("lastSuccessfulBuild")
}
func (j *Job) GetFirstBuild() (*Build, error) {
return j.getBuildByType("firstBuild")
}
func (j *Job) GetLastBuild() (*Build, error) {
return j.getBuildByType("lastBuild")
}
func (j *Job) GetLastStableBuild() (*Build, error) {
return j.getBuildByType("lastStableBuild")
}
func (j *Job) GetLastFailedBuild() (*Build, error) {
return j.getBuildByType("lastFailedBuild")
}
func (j *Job) GetLastCompletedBuild() (*Build, error) {
return j.getBuildByType("lastCompletedBuild")
}
// Returns All Builds with Number and URL
func (j *Job) GetAllBuildIds() ([]JobBuild, error) {
var buildsResp struct {
Builds []JobBuild `json:"allBuilds"`
}
_, err := j.Jenkins.Requester.GetJSON(j.Base, &buildsResp, map[string]string{"tree": "allBuilds[number,url]"})
if err != nil {
return nil, err
}
return buildsResp.Builds, nil
}
func (j *Job) GetAllBuildStatus() ([]JobBuildStatus, error) {
var buildsResp struct {
Builds []JobBuildStatus `json:"allBuilds"`
}
_, err := j.Jenkins.Requester.GetJSON(j.Base, &buildsResp, map[string]string{"tree": "allBuilds[number,building,result]"})
if err != nil {
return nil, err
}
return buildsResp.Builds, nil
}
func (j *Job) GetSubJobsMetadata() []InnerJob {
return j.Raw.SubJobs
}
func (j *Job) GetUpstreamJobsMetadata() []InnerJob {
return j.Raw.UpstreamProjects
}
func (j *Job) GetDownstreamJobsMetadata() []InnerJob {
return j.Raw.DownstreamProjects
}
func (j *Job) GetSubJobs() ([]*Job, error) {
jobs := make([]*Job, len(j.Raw.SubJobs))
for i, job := range j.Raw.SubJobs {
ji, err := j.Jenkins.GetSubJob(j.GetName(), job.Name)
if err != nil {
return nil, err
}
jobs[i] = ji
}
return jobs, nil
}
func (j *Job) GetInnerJobsMetadata() []InnerJob {
return j.Raw.Jobs
}
func (j *Job) GetUpstreamJobs() ([]*Job, error) {
jobs := make([]*Job, len(j.Raw.UpstreamProjects))
for i, job := range j.Raw.UpstreamProjects {
ji, err := j.Jenkins.GetJob(job.Name)
if err != nil {
return nil, err
}
jobs[i] = ji
}
return jobs, nil
}
func (j *Job) GetDownstreamJobs() ([]*Job, error) {
jobs := make([]*Job, len(j.Raw.DownstreamProjects))
for i, job := range j.Raw.DownstreamProjects {
ji, err := j.Jenkins.GetJob(job.Name)
if err != nil {
return nil, err
}
jobs[i] = ji
}
return jobs, nil
}
func (j *Job) GetInnerJob(id string) (*Job, error) {
job := Job{Jenkins: j.Jenkins, Raw: new(JobResponse), Base: j.Base + "/job/" + id}
status, err := job.Poll()
if err != nil {
return nil, err
}
if status == 200 {
return &job, nil
}
return nil, errors.New(strconv.Itoa(status))
}
func (j *Job) GetInnerJobs() ([]*Job, error) {
jobs := make([]*Job, len(j.Raw.Jobs))
for i, job := range j.Raw.Jobs {
ji, err := j.GetInnerJob(job.Name)
if err != nil {
return nil, err
}
jobs[i] = ji
}
return jobs, nil
}
func (j *Job) Enable() (bool, error) {
resp, err := j.Jenkins.Requester.Post(j.Base+"/enable", nil, nil, nil)
if err != nil {
return false, err
}
if resp.StatusCode != 200 {
return false, errors.New(strconv.Itoa(resp.StatusCode))
}
return true, nil
}
func (j *Job) Disable() (bool, error) {
resp, err := j.Jenkins.Requester.Post(j.Base+"/disable", nil, nil, nil)
if err != nil {
return false, err
}
if resp.StatusCode != 200 {
return false, errors.New(strconv.Itoa(resp.StatusCode))
}
return true, nil
}
func (j *Job) Delete() (bool, error) {
resp, err := j.Jenkins.Requester.Post(j.Base+"/doDelete", nil, nil, nil)
if err != nil {
return false, err
}
if resp.StatusCode != 200 {
return false, errors.New(strconv.Itoa(resp.StatusCode))
}
return true, nil
}
func (j *Job) Rename(name string) (bool, error) {
data := url.Values{}
data.Set("newName", name)
_, err := j.Jenkins.Requester.Post(j.Base+"/doRename", bytes.NewBufferString(data.Encode()), nil, nil)
if err != nil {
return false, err
}
j.Base = "/job/" + name
j.Poll()
return true, nil
}
func (j *Job) Create(config string, qr ...interface{}) (*Job, error) {
var querystring map[string]string
if len(qr) > 0 {
querystring = qr[0].(map[string]string)
}
resp, err := j.Jenkins.Requester.PostXML(j.parentBase()+"/createItem", config, j.Raw, querystring)
if err != nil {
return nil, err
}
if resp.StatusCode == 200 {
j.Poll()
return j, nil
}
return nil, errors.New(strconv.Itoa(resp.StatusCode))
}
func (j *Job) Copy(destinationName string) (*Job, error) {
qr := map[string]string{"name": destinationName, "from": j.GetName(), "mode": "copy"}
resp, err := j.Jenkins.Requester.Post(j.parentBase()+"/createItem", nil, nil, qr)
if err != nil {
return nil, err
}
if resp.StatusCode == 200 {
newJob := &Job{Jenkins: j.Jenkins, Raw: new(JobResponse), Base: "/job/" + destinationName}
_, err := newJob.Poll()
if err != nil {
return nil, err
}
return newJob, nil
}
return nil, errors.New(strconv.Itoa(resp.StatusCode))
}
func (j *Job) UpdateConfig(config string) error {
var querystring map[string]string
resp, err := j.Jenkins.Requester.PostXML(j.Base+"/config.xml", config, nil, querystring)
if err != nil {
return err
}
if resp.StatusCode == 200 {
j.Poll()
return nil
}
return errors.New(strconv.Itoa(resp.StatusCode))
}
func (j *Job) GetConfig() (string, error) {
var data string
_, err := j.Jenkins.Requester.GetXML(j.Base+"/config.xml", &data, nil)
if err != nil {
return "", err
}
return data, nil
}
func (j *Job) GetParameters() ([]ParameterDefinition, error) {
_, err := j.Poll()
if err != nil {
return nil, err
}
var parameters []ParameterDefinition
for _, property := range j.Raw.Property {
parameters = append(parameters, property.ParameterDefinitions...)
}
return parameters, nil
}
func (j *Job) IsQueued() (bool, error) {
if _, err := j.Poll(); err != nil {
return false, err
}
return j.Raw.InQueue, nil
}
func (j *Job) IsRunning() (bool, error) {
if _, err := j.Poll(); err != nil {
return false, err
}
lastBuild, err := j.GetLastBuild()
if err != nil {
return false, err
}
return lastBuild.IsRunning(), nil
}
func (j *Job) IsEnabled() (bool, error) {
if _, err := j.Poll(); err != nil {
return false, err
}
return j.Raw.Color != "disabled", nil
}
func (j *Job) HasQueuedBuild() {
panic("Not Implemented yet")
}
func (j *Job) InvokeSimple(params map[string]string) (int64, error) {
endpoint := "/build"
parameters, err := j.GetParameters()
if err != nil {
return 0, err
}
if len(parameters) > 0 {
endpoint = "/buildWithParameters"
}
data := url.Values{}
for k, v := range params {
data.Set(k, v)
}
resp, err := j.Jenkins.Requester.Post(j.Base+endpoint, bytes.NewBufferString(data.Encode()), nil, nil)
if err != nil {
return 0, err
}
if resp.StatusCode != 200 && resp.StatusCode != 201 {
return 0, errors.New("Could not invoke job " + j.GetName())
}
location := resp.Header.Get("Location")
if location == "" {
return 0, errors.New("Don't have key \"Location\" in response of header")
}
u, err := url.Parse(location)
if err != nil {
return 0, err
}
number, err := strconv.ParseInt(path.Base(u.Path), 10, 64)
if err != nil {
return 0, err
}
return number, nil
}
func (j *Job) Invoke(files []string, skipIfRunning bool, params map[string]string, cause string, securityToken string) (bool, error) {
isRunning, err := j.IsRunning()
if err != nil {
return false, err
}
if isRunning && skipIfRunning {
return false, fmt.Errorf("Will not request new build because %s is already running", j.GetName())
}
base := "/build"
// If parameters are specified - url is /builWithParameters
if params != nil {
base = "/buildWithParameters"
} else {
params = make(map[string]string)
}
// If files are specified - url is /build
if files != nil {
base = "/build"
}
reqParams := map[string]string{}
buildParams := map[string]string{}
if securityToken != "" {
reqParams["token"] = securityToken
}
buildParams["json"] = string(makeJson(params))
b, _ := json.Marshal(buildParams)
resp, err := j.Jenkins.Requester.PostFiles(j.Base+base, bytes.NewBuffer(b), nil, reqParams, files)
if err != nil {
return false, err
}
if resp.StatusCode == 200 || resp.StatusCode == 201 {
return true, nil
}
return false, errors.New(strconv.Itoa(resp.StatusCode))
}
func (j *Job) Poll() (int, error) {
response, err := j.Jenkins.Requester.GetJSON(j.Base, j.Raw, nil)
if err != nil {
return 0, err
}
return response.StatusCode, nil
}
func (j *Job) History() ([]*History, error) {
resp, err := j.Jenkins.Requester.Get(j.Base+"/buildHistory/ajax", nil, nil)
if err != nil {
return nil, err
}
return parseBuildHistory(resp.Body), nil
}

62
pkg/gojenkins/label.go Normal file
View File

@@ -0,0 +1,62 @@
// Copyright 2015 Vadim Kravcenko
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package gojenkins
type Label struct {
Raw *LabelResponse
Jenkins *Jenkins
Base string
}
type MODE string
const (
NORMAL MODE = "NORMAL"
EXCLUSIVE = "EXCLUSIVE"
)
type LabelNode struct {
NodeName string `json:"nodeName"`
NodeDescription string `json:"nodeDescription"`
NumExecutors int64 `json:"numExecutors"`
Mode string `json:"mode"`
Class string `json:"_class"`
}
type LabelResponse struct {
Name string `json:"name"`
Description string `json:"description"`
Nodes []LabelNode `json:"nodes"`
Offline bool `json:"offline"`
IdleExecutors int64 `json:"idleExecutors"`
BusyExecutors int64 `json:"busyExecutors"`
TotalExecutors int64 `json:"totalExecutors"`
}
func (l *Label) GetName() string {
return l.Raw.Name
}
func (l *Label) GetNodes() []LabelNode {
return l.Raw.Nodes
}
func (l *Label) Poll() (int, error) {
response, err := l.Jenkins.Requester.GetJSON(l.Base, l.Raw, nil)
if err != nil {
return 0, err
}
return response.StatusCode, nil
}

230
pkg/gojenkins/node.go Normal file
View File

@@ -0,0 +1,230 @@
// Copyright 2015 Vadim Kravcenko
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package gojenkins
import "errors"
// Nodes
type Computers struct {
BusyExecutors int `json:"busyExecutors"`
Computers []*NodeResponse `json:"computer"`
DisplayName string `json:"displayName"`
TotalExecutors int `json:"totalExecutors"`
}
type Node struct {
Raw *NodeResponse
Jenkins *Jenkins
Base string
}
type NodeResponse struct {
Actions []interface{} `json:"actions"`
DisplayName string `json:"displayName"`
Executors []struct {
CurrentExecutable struct {
Number int `json:"number"`
URL string `json:"url"`
SubBuilds []struct {
Abort bool `json:"abort"`
Build interface{} `json:"build"`
BuildNumber int `json:"buildNumber"`
Duration string `json:"duration"`
Icon string `json:"icon"`
JobName string `json:"jobName"`
ParentBuildNumber int `json:"parentBuildNumber"`
ParentJobName string `json:"parentJobName"`
PhaseName string `json:"phaseName"`
Result string `json:"result"`
Retry bool `json:"retry"`
URL string `json:"url"`
} `json:"subBuilds"`
} `json:"currentExecutable"`
} `json:"executors"`
Icon string `json:"icon"`
IconClassName string `json:"iconClassName"`
Idle bool `json:"idle"`
JnlpAgent bool `json:"jnlpAgent"`
LaunchSupported bool `json:"launchSupported"`
LoadStatistics struct{} `json:"loadStatistics"`
ManualLaunchAllowed bool `json:"manualLaunchAllowed"`
MonitorData struct {
Hudson_NodeMonitors_ArchitectureMonitor interface{} `json:"hudson.node_monitors.ArchitectureMonitor"`
Hudson_NodeMonitors_ClockMonitor interface{} `json:"hudson.node_monitors.ClockMonitor"`
Hudson_NodeMonitors_DiskSpaceMonitor interface{} `json:"hudson.node_monitors.DiskSpaceMonitor"`
Hudson_NodeMonitors_ResponseTimeMonitor struct {
Average int64 `json:"average"`
} `json:"hudson.node_monitors.ResponseTimeMonitor"`
Hudson_NodeMonitors_SwapSpaceMonitor interface{} `json:"hudson.node_monitors.SwapSpaceMonitor"`
Hudson_NodeMonitors_TemporarySpaceMonitor interface{} `json:"hudson.node_monitors.TemporarySpaceMonitor"`
} `json:"monitorData"`
NumExecutors int64 `json:"numExecutors"`
Offline bool `json:"offline"`
OfflineCause struct{} `json:"offlineCause"`
OfflineCauseReason string `json:"offlineCauseReason"`
OneOffExecutors []interface{} `json:"oneOffExecutors"`
TemporarilyOffline bool `json:"temporarilyOffline"`
}
func (n *Node) Info() (*NodeResponse, error) {
_, err := n.Poll()
if err != nil {
return nil, err
}
return n.Raw, nil
}
func (n *Node) GetName() string {
return n.Raw.DisplayName
}
func (n *Node) Delete() (bool, error) {
resp, err := n.Jenkins.Requester.Post(n.Base+"/doDelete", nil, nil, nil)
if err != nil {
return false, err
}
return resp.StatusCode == 200, nil
}
func (n *Node) IsOnline() (bool, error) {
_, err := n.Poll()
if err != nil {
return false, err
}
return !n.Raw.Offline, nil
}
func (n *Node) IsTemporarilyOffline() (bool, error) {
_, err := n.Poll()
if err != nil {
return false, err
}
return n.Raw.TemporarilyOffline, nil
}
func (n *Node) IsIdle() (bool, error) {
_, err := n.Poll()
if err != nil {
return false, err
}
return n.Raw.Idle, nil
}
func (n *Node) IsJnlpAgent() (bool, error) {
_, err := n.Poll()
if err != nil {
return false, err
}
return n.Raw.JnlpAgent, nil
}
func (n *Node) SetOnline() (bool, error) {
_, err := n.Poll()
if err != nil {
return false, err
}
if n.Raw.Offline && !n.Raw.TemporarilyOffline {
return false, errors.New("Node is Permanently offline, can't bring it up")
}
if n.Raw.Offline && n.Raw.TemporarilyOffline {
return n.ToggleTemporarilyOffline()
}
return true, nil
}
func (n *Node) SetOffline(options ...interface{}) (bool, error) {
if !n.Raw.Offline {
return n.ToggleTemporarilyOffline(options...)
}
return false, errors.New("Node already Offline")
}
func (n *Node) ToggleTemporarilyOffline(options ...interface{}) (bool, error) {
state_before, err := n.IsTemporarilyOffline()
if err != nil {
return false, err
}
qr := map[string]string{"offlineMessage": "requested from gojenkins"}
if len(options) > 0 {
qr["offlineMessage"] = options[0].(string)
}
_, err = n.Jenkins.Requester.Post(n.Base+"/toggleOffline", nil, nil, qr)
if err != nil {
return false, err
}
new_state, err := n.IsTemporarilyOffline()
if err != nil {
return false, err
}
if state_before == new_state {
return false, errors.New("Node state not changed")
}
return true, nil
}
func (n *Node) Poll() (int, error) {
response, err := n.Jenkins.Requester.GetJSON(n.Base, n.Raw, nil)
if err != nil {
return 0, err
}
return response.StatusCode, nil
}
func (n *Node) LaunchNodeBySSH() (int, error) {
qr := map[string]string{
"json": "",
"Submit": "Launch slave agent",
}
response, err := n.Jenkins.Requester.Post(n.Base+"/launchSlaveAgent", nil, nil, qr)
if err != nil {
return 0, err
}
return response.StatusCode, nil
}
func (n *Node) Disconnect() (int, error) {
qr := map[string]string{
"offlineMessage": "",
"json": makeJson(map[string]string{"offlineMessage": ""}),
"Submit": "Yes",
}
response, err := n.Jenkins.Requester.Post(n.Base+"/doDisconnect", nil, nil, qr)
if err != nil {
return 0, err
}
return response.StatusCode, nil
}
func (n *Node) GetLogText() (string, error) {
var log string
_, err := n.Jenkins.Requester.Post(n.Base+"/log", nil, nil, nil)
if err != nil {
return "", err
}
qr := map[string]string{"start": "0"}
_, err = n.Jenkins.Requester.GetJSON(n.Base+"/logText/progressiveHtml/", &log, qr)
if err != nil {
return "", nil
}
return log, nil
}

View File

@@ -0,0 +1,162 @@
/*
Copyright 2018 The KubeSphere Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package gojenkins
import (
"errors"
"net/http"
"strconv"
)
type ValidateJenkinsfileResponse struct {
Status string `json:"status"`
Data struct {
Result string `json:"result"`
Errors []map[string]interface{} `json:"errors"`
} `json:"data"`
}
type ValidatePipelineJsonResponse struct {
Status string `json:"status"`
Data struct {
Result string `json:"result"`
Errors []map[string]interface{} `json:"errors"`
}
}
type PipelineJsonToJenkinsfileResponse struct {
Status string `json:"status"`
Data struct {
Result string `json:"result"`
Errors []map[string]interface{} `json:"errors"`
Jenkinsfile string `json:"jenkinsfile"`
} `json:"data"`
}
type JenkinsfileToPipelineJsonResponse struct {
Status string `json:"status"`
Data struct {
Result string `json:"result"`
Errors []map[string]interface{} `json:"errors"`
Json map[string]interface{} `json:"json"`
} `json:"data"`
}
type StepJsonToJenkinsfileResponse struct {
Status string `json:"status"`
Data struct {
Result string `json:"result"`
Errors []map[string]interface{} `json:"errors"`
Jenkinsfile string `json:"jenkinsfile"`
} `json:"data"`
}
type StepsJenkinsfileToJsonResponse struct {
Status string `json:"status"`
Data struct {
Result string `json:"result"`
Errors []map[string]interface{} `json:"errors"`
Json []map[string]interface{} `json:"json"`
} `json:"data"`
}
func (j *Jenkins) ValidateJenkinsfile(jenkinsfile string) (*ValidateJenkinsfileResponse, error) {
responseStrut := &ValidateJenkinsfileResponse{}
query := map[string]string{
"jenkinsfile": jenkinsfile,
}
response, err := j.Requester.PostForm("/pipeline-model-converter/validateJenkinsfile", nil, responseStrut, query)
if err != nil {
return nil, err
}
if response.StatusCode != http.StatusOK {
return nil, errors.New(strconv.Itoa(response.StatusCode))
}
return responseStrut, nil
}
func (j *Jenkins) ValidatePipelineJson(json string) (*ValidatePipelineJsonResponse, error) {
responseStruct := &ValidatePipelineJsonResponse{}
query := map[string]string{
"json": json,
}
response, err := j.Requester.PostForm("/pipeline-model-converter/validateJson", nil, responseStruct, query)
if err != nil {
return nil, err
}
if response.StatusCode != http.StatusOK {
return nil, errors.New(strconv.Itoa(response.StatusCode))
}
return responseStruct, nil
}
func (j *Jenkins) PipelineJsonToJenkinsfile(json string) (*PipelineJsonToJenkinsfileResponse, error) {
responseStrut := &PipelineJsonToJenkinsfileResponse{}
query := map[string]string{
"json": json,
}
response, err := j.Requester.PostForm("/pipeline-model-converter/toJenkinsfile", nil, responseStrut, query)
if err != nil {
return nil, err
}
if response.StatusCode != http.StatusOK {
return nil, errors.New(strconv.Itoa(response.StatusCode))
}
return responseStrut, nil
}
func (j *Jenkins) JenkinsfileToPipelineJson(jenkinsfile string) (*JenkinsfileToPipelineJsonResponse, error) {
responseStrut := &JenkinsfileToPipelineJsonResponse{}
query := map[string]string{
"jenkinsfile": jenkinsfile,
}
response, err := j.Requester.PostForm("/pipeline-model-converter/toJson", nil, responseStrut, query)
if err != nil {
return nil, err
}
if response.StatusCode != http.StatusOK {
return nil, errors.New(strconv.Itoa(response.StatusCode))
}
return responseStrut, nil
}
func (j *Jenkins) StepsJsonToJenkinsfile(json string) (*StepJsonToJenkinsfileResponse, error) {
responseStrut := &StepJsonToJenkinsfileResponse{}
query := map[string]string{
"json": json,
}
response, err := j.Requester.PostForm("/pipeline-model-converter/stepsToJenkinsfile", nil, responseStrut, query)
if err != nil {
return nil, err
}
if response.StatusCode != http.StatusOK {
return nil, errors.New(strconv.Itoa(response.StatusCode))
}
return responseStrut, nil
}
func (j *Jenkins) StepsJenkinsfileToJson(jenkinsfile string) (*StepsJenkinsfileToJsonResponse, error) {
responseStrut := &StepsJenkinsfileToJsonResponse{}
query := map[string]string{
"jenkinsfile": jenkinsfile,
}
response, err := j.Requester.PostForm("/pipeline-model-converter/stepsToJson", nil, responseStrut, query)
if err != nil {
return nil, err
}
if response.StatusCode != http.StatusOK {
return nil, errors.New(strconv.Itoa(response.StatusCode))
}
return responseStrut, nil
}

75
pkg/gojenkins/plugin.go Normal file
View File

@@ -0,0 +1,75 @@
// Copyright 2015 Vadim Kravcenko
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package gojenkins
import (
"strconv"
)
type Plugins struct {
Jenkins *Jenkins
Raw *PluginResponse
Base string
Depth int
}
type PluginResponse struct {
Plugins []Plugin `json:"plugins"`
}
type Plugin struct {
Active bool `json:"active"`
BackupVersion interface{} `json:"backupVersion"`
Bundled bool `json:"bundled"`
Deleted bool `json:"deleted"`
Dependencies []struct {
Optional string `json:"optional"`
ShortName string `json:"shortname"`
Version string `json:"version"`
} `json:"dependencies"`
Downgradable bool `json:"downgradable"`
Enabled bool `json:"enabled"`
HasUpdate bool `json:"hasUpdate"`
LongName string `json:"longName"`
Pinned bool `json:"pinned"`
ShortName string `json:"shortName"`
SupportsDynamicLoad string `json:"supportsDynamicLoad"`
URL string `json:"url"`
Version string `json:"version"`
}
func (p *Plugins) Count() int {
return len(p.Raw.Plugins)
}
func (p *Plugins) Contains(name string) *Plugin {
for _, p := range p.Raw.Plugins {
if p.LongName == name || p.ShortName == name {
return &p
}
}
return nil
}
func (p *Plugins) Poll() (int, error) {
qr := map[string]string{
"depth": strconv.Itoa(p.Depth),
}
response, err := p.Jenkins.Requester.GetJSON(p.Base, p.Raw, qr)
if err != nil {
return 0, err
}
return response.StatusCode, nil
}

158
pkg/gojenkins/queue.go Normal file
View File

@@ -0,0 +1,158 @@
// Copyright 2015 Vadim Kravcenko
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package gojenkins
import (
"strconv"
)
type Queue struct {
Jenkins *Jenkins
Raw *queueResponse
Base string
}
type queueResponse struct {
Items []taskResponse
}
type Task struct {
Raw *taskResponse
Jenkins *Jenkins
Queue *Queue
}
type taskResponse struct {
Actions []generalAction `json:"actions"`
Blocked bool `json:"blocked"`
Buildable bool `json:"buildable"`
BuildableStartMilliseconds int64 `json:"buildableStartMilliseconds"`
ID int64 `json:"id"`
InQueueSince int64 `json:"inQueueSince"`
Params string `json:"params"`
Pending bool `json:"pending"`
Stuck bool `json:"stuck"`
Task struct {
Color string `json:"color"`
Name string `json:"name"`
URL string `json:"url"`
} `json:"task"`
URL string `json:"url"`
Why string `json:"why"`
}
type generalAction struct {
Causes []map[string]interface{}
Parameters []parameter
}
type QueueItemResponse struct {
Actions []generalAction `json:"actions"`
Blocked bool `json:"blocked"`
Buildable bool `json:"buildable"`
ID int64 `json:"id"`
InQueueSince int64 `json:"inQueueSince"`
Params string `json:"params"`
Stuck bool `json:"stuck"`
Task struct {
Color string `json:"color"`
Name string `json:"name"`
URL string `json:"url"`
} `json:"task"`
URL string `json:"url"`
Cancelled bool `json:"cancelled"`
Why string `json:"why"`
Executable struct {
Number int64 `json:"number"`
Url string `json:"url"`
} `json:"executable"`
}
func (q *Queue) Tasks() []*Task {
tasks := make([]*Task, len(q.Raw.Items))
for i, t := range q.Raw.Items {
tasks[i] = &Task{Jenkins: q.Jenkins, Queue: q, Raw: &t}
}
return tasks
}
func (q *Queue) GetTaskById(id int64) *Task {
for _, t := range q.Raw.Items {
if t.ID == id {
return &Task{Jenkins: q.Jenkins, Queue: q, Raw: &t}
}
}
return nil
}
func (q *Queue) GetTasksForJob(name string) []*Task {
tasks := make([]*Task, 0)
for _, t := range q.Raw.Items {
if t.Task.Name == name {
tasks = append(tasks, &Task{Jenkins: q.Jenkins, Queue: q, Raw: &t})
}
}
return tasks
}
func (q *Queue) CancelTask(id int64) (bool, error) {
task := q.GetTaskById(id)
return task.Cancel()
}
func (t *Task) Cancel() (bool, error) {
qr := map[string]string{
"id": strconv.FormatInt(t.Raw.ID, 10),
}
response, err := t.Jenkins.Requester.Post(t.Jenkins.GetQueueUrl()+"/cancelItem", nil, t.Raw, qr)
if err != nil {
return false, err
}
return response.StatusCode == 200, nil
}
func (t *Task) GetJob() (*Job, error) {
return t.Jenkins.GetJob(t.Raw.Task.Name)
}
func (t *Task) GetWhy() string {
return t.Raw.Why
}
func (t *Task) GetParameters() []parameter {
for _, a := range t.Raw.Actions {
if a.Parameters != nil {
return a.Parameters
}
}
return nil
}
func (t *Task) GetCauses() []map[string]interface{} {
for _, a := range t.Raw.Actions {
if a.Causes != nil {
return a.Causes
}
}
return nil
}
func (q *Queue) Poll() (int, error) {
response, err := q.Jenkins.Requester.GetJSON(q.Base, q.Raw, nil)
if err != nil {
return 0, err
}
return response.StatusCode, nil
}

468
pkg/gojenkins/request.go Normal file
View File

@@ -0,0 +1,468 @@
// Copyright 2015 Vadim Kravcenko
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package gojenkins
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
)
// Request Methods
type APIRequest struct {
Method string
Endpoint string
Payload io.Reader
Headers http.Header
Suffix string
}
func (ar *APIRequest) SetHeader(key string, value string) *APIRequest {
ar.Headers.Set(key, value)
return ar
}
func NewAPIRequest(method string, endpoint string, payload io.Reader) *APIRequest {
var headers = http.Header{}
var suffix string
ar := &APIRequest{method, endpoint, payload, headers, suffix}
return ar
}
type Requester struct {
Base string
BasicAuth *BasicAuth
Client *http.Client
CACert []byte
SslVerify bool
connControl chan struct{}
}
func (r *Requester) SetCrumb(ar *APIRequest) error {
crumbData := map[string]string{}
response, err := r.GetJSON("/crumbIssuer/api/json", &crumbData, nil)
if err != nil {
jenkinsError, ok := err.(*ErrorResponse)
if ok && jenkinsError.Response.StatusCode == http.StatusNotFound {
return nil
}
return err
}
if response.StatusCode == 200 && crumbData["crumbRequestField"] != "" {
ar.SetHeader(crumbData["crumbRequestField"], crumbData["crumb"])
}
return nil
}
func (r *Requester) PostJSON(endpoint string, payload io.Reader, responseStruct interface{}, querystring map[string]string) (*http.Response, error) {
ar := NewAPIRequest("POST", endpoint, payload)
if err := r.SetCrumb(ar); err != nil {
return nil, err
}
ar.SetHeader("Content-Type", "application/x-www-form-urlencoded")
ar.Suffix = "api/json"
return r.Do(ar, &responseStruct, querystring)
}
func (r *Requester) Post(endpoint string, payload io.Reader, responseStruct interface{}, querystring map[string]string) (*http.Response, error) {
ar := NewAPIRequest("POST", endpoint, payload)
if err := r.SetCrumb(ar); err != nil {
return nil, err
}
ar.SetHeader("Content-Type", "application/x-www-form-urlencoded")
ar.Suffix = ""
return r.Do(ar, &responseStruct, querystring)
}
func (r *Requester) PostForm(endpoint string, payload io.Reader, responseStruct interface{}, formString map[string]string) (*http.Response, error) {
ar := NewAPIRequest("POST", endpoint, payload)
if err := r.SetCrumb(ar); err != nil {
return nil, err
}
ar.SetHeader("Content-Type", "application/x-www-form-urlencoded")
ar.Suffix = ""
return r.DoPostForm(ar, &responseStruct, formString)
}
func (r *Requester) PostFiles(endpoint string, payload io.Reader, responseStruct interface{}, querystring map[string]string, files []string) (*http.Response, error) {
ar := NewAPIRequest("POST", endpoint, payload)
if err := r.SetCrumb(ar); err != nil {
return nil, err
}
return r.Do(ar, &responseStruct, querystring, files)
}
func (r *Requester) PostXML(endpoint string, xml string, responseStruct interface{}, querystring map[string]string) (*http.Response, error) {
payload := bytes.NewBuffer([]byte(xml))
ar := NewAPIRequest("POST", endpoint, payload)
if err := r.SetCrumb(ar); err != nil {
return nil, err
}
ar.SetHeader("Content-Type", "application/xml;charset=utf-8")
ar.Suffix = ""
return r.Do(ar, &responseStruct, querystring)
}
func (r *Requester) GetJSON(endpoint string, responseStruct interface{}, query map[string]string) (*http.Response, error) {
ar := NewAPIRequest("GET", endpoint, nil)
ar.SetHeader("Content-Type", "application/json")
ar.Suffix = "api/json"
return r.Do(ar, &responseStruct, query)
}
func (r *Requester) GetXML(endpoint string, responseStruct interface{}, query map[string]string) (*http.Response, error) {
ar := NewAPIRequest("GET", endpoint, nil)
ar.SetHeader("Content-Type", "application/xml")
ar.Suffix = ""
return r.Do(ar, responseStruct, query)
}
func (r *Requester) Get(endpoint string, responseStruct interface{}, querystring map[string]string) (*http.Response, error) {
ar := NewAPIRequest("GET", endpoint, nil)
ar.Suffix = ""
return r.Do(ar, responseStruct, querystring)
}
func (r *Requester) GetHtml(endpoint string, responseStruct interface{}, querystring map[string]string) (*http.Response, error) {
ar := NewAPIRequest("GET", endpoint, nil)
ar.Suffix = ""
return r.DoGet(ar, responseStruct, querystring)
}
func (r *Requester) SetClient(client *http.Client) *Requester {
r.Client = client
return r
}
//Add auth on redirect if required.
func (r *Requester) redirectPolicyFunc(req *http.Request, via []*http.Request) error {
if r.BasicAuth != nil {
req.SetBasicAuth(r.BasicAuth.Username, r.BasicAuth.Password)
}
return nil
}
func (r *Requester) DoGet(ar *APIRequest, responseStruct interface{}, options ...interface{}) (*http.Response, error) {
fileUpload := false
var files []string
URL, err := url.Parse(r.Base + ar.Endpoint + ar.Suffix)
if err != nil {
return nil, err
}
for _, o := range options {
switch v := o.(type) {
case map[string]string:
querystring := make(url.Values)
for key, val := range v {
querystring.Set(key, val)
}
URL.RawQuery = querystring.Encode()
case []string:
fileUpload = true
files = v
}
}
var req *http.Request
if fileUpload {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
for _, file := range files {
fileData, err := os.Open(file)
if err != nil {
Error.Println(err.Error())
return nil, err
}
part, err := writer.CreateFormFile("file", filepath.Base(file))
if err != nil {
Error.Println(err.Error())
return nil, err
}
if _, err = io.Copy(part, fileData); err != nil {
return nil, err
}
defer fileData.Close()
}
var params map[string]string
json.NewDecoder(ar.Payload).Decode(&params)
for key, val := range params {
if err = writer.WriteField(key, val); err != nil {
return nil, err
}
}
if err = writer.Close(); err != nil {
return nil, err
}
req, err = http.NewRequest(ar.Method, URL.String(), body)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", writer.FormDataContentType())
} else {
req, err = http.NewRequest(ar.Method, URL.String(), ar.Payload)
if err != nil {
return nil, err
}
}
if r.BasicAuth != nil {
req.SetBasicAuth(r.BasicAuth.Username, r.BasicAuth.Password)
}
req.Close = true
req.Header.Add("Accept", "*/*")
for k := range ar.Headers {
req.Header.Add(k, ar.Headers.Get(k))
}
r.connControl <- struct{}{}
if response, err := r.Client.Do(req); err != nil {
<-r.connControl
return nil, err
} else {
<-r.connControl
errorText := response.Header.Get("X-Error")
if errorText != "" {
return nil, errors.New(errorText)
}
err := CheckResponse(response)
if err != nil {
return nil, err
}
switch responseStruct.(type) {
case *string:
return r.ReadRawResponse(response, responseStruct)
default:
return r.ReadJSONResponse(response, responseStruct)
}
}
}
func (r *Requester) Do(ar *APIRequest, responseStruct interface{}, options ...interface{}) (*http.Response, error) {
if !strings.HasSuffix(ar.Endpoint, "/") && ar.Method != "POST" {
ar.Endpoint += "/"
}
fileUpload := false
var files []string
URL, err := url.Parse(r.Base + ar.Endpoint + ar.Suffix)
if err != nil {
return nil, err
}
for _, o := range options {
switch v := o.(type) {
case map[string]string:
querystring := make(url.Values)
for key, val := range v {
querystring.Set(key, val)
}
URL.RawQuery = querystring.Encode()
case []string:
fileUpload = true
files = v
}
}
var req *http.Request
if fileUpload {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
for _, file := range files {
fileData, err := os.Open(file)
if err != nil {
Error.Println(err.Error())
return nil, err
}
part, err := writer.CreateFormFile("file", filepath.Base(file))
if err != nil {
Error.Println(err.Error())
return nil, err
}
if _, err = io.Copy(part, fileData); err != nil {
return nil, err
}
defer fileData.Close()
}
var params map[string]string
json.NewDecoder(ar.Payload).Decode(&params)
for key, val := range params {
if err = writer.WriteField(key, val); err != nil {
return nil, err
}
}
if err = writer.Close(); err != nil {
return nil, err
}
req, err = http.NewRequest(ar.Method, URL.String(), body)
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", writer.FormDataContentType())
} else {
req, err = http.NewRequest(ar.Method, URL.String(), ar.Payload)
if err != nil {
return nil, err
}
}
if r.BasicAuth != nil {
req.SetBasicAuth(r.BasicAuth.Username, r.BasicAuth.Password)
}
req.Close = true
req.Header.Add("Accept", "*/*")
for k := range ar.Headers {
req.Header.Add(k, ar.Headers.Get(k))
}
r.connControl <- struct{}{}
if response, err := r.Client.Do(req); err != nil {
<-r.connControl
return nil, err
} else {
<-r.connControl
errorText := response.Header.Get("X-Error")
if errorText != "" {
return nil, errors.New(errorText)
}
err := CheckResponse(response)
if err != nil {
return nil, err
}
switch responseStruct.(type) {
case *string:
return r.ReadRawResponse(response, responseStruct)
default:
return r.ReadJSONResponse(response, responseStruct)
}
}
}
func (r *Requester) DoPostForm(ar *APIRequest, responseStruct interface{}, form map[string]string) (*http.Response, error) {
if !strings.HasSuffix(ar.Endpoint, "/") && ar.Method != "POST" {
ar.Endpoint += "/"
}
URL, err := url.Parse(r.Base + ar.Endpoint + ar.Suffix)
if err != nil {
return nil, err
}
formValue := make(url.Values)
for k, v := range form {
formValue.Set(k, v)
}
req, err := http.NewRequest("POST", URL.String(), strings.NewReader(formValue.Encode()))
if r.BasicAuth != nil {
req.SetBasicAuth(r.BasicAuth.Username, r.BasicAuth.Password)
}
req.Close = true
req.Header.Add("Accept", "*/*")
for k := range ar.Headers {
req.Header.Add(k, ar.Headers.Get(k))
}
r.connControl <- struct{}{}
if response, err := r.Client.Do(req); err != nil {
<-r.connControl
return nil, err
} else {
<-r.connControl
errorText := response.Header.Get("X-Error")
if errorText != "" {
return nil, errors.New(errorText)
}
err := CheckResponse(response)
if err != nil {
return nil, err
}
switch responseStruct.(type) {
case *string:
return r.ReadRawResponse(response, responseStruct)
default:
return r.ReadJSONResponse(response, responseStruct)
}
}
}
func (r *Requester) ReadRawResponse(response *http.Response, responseStruct interface{}) (*http.Response, error) {
defer response.Body.Close()
content, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
if str, ok := responseStruct.(*string); ok {
*str = string(content)
} else {
return nil, fmt.Errorf("Could not cast responseStruct to *string")
}
return response, nil
}
func (r *Requester) ReadJSONResponse(response *http.Response, responseStruct interface{}) (*http.Response, error) {
defer response.Body.Close()
err := json.NewDecoder(response.Body).Decode(responseStruct)
if err != nil && err.Error() == "EOF" {
return response, nil
}
return response, nil
}
type ErrorResponse struct {
Body []byte
Response *http.Response
Message string
}
func (e *ErrorResponse) Error() string {
u := fmt.Sprintf("%s://%s%s", e.Response.Request.URL.Scheme, e.Response.Request.URL.Host, e.Response.Request.URL.RequestURI())
return fmt.Sprintf("%s %s: %d %s", e.Response.Request.Method, u, e.Response.StatusCode, e.Message)
}
func CheckResponse(r *http.Response) error {
switch r.StatusCode {
case http.StatusOK, http.StatusCreated, http.StatusAccepted, http.StatusNoContent, http.StatusFound, http.StatusNotModified:
return nil
}
defer r.Body.Close()
errorResponse := &ErrorResponse{Response: r}
data, err := ioutil.ReadAll(r.Body)
if err == nil && data != nil {
errorResponse.Body = data
errorResponse.Message = string(data)
}
return errorResponse
}

204
pkg/gojenkins/role.go Normal file
View File

@@ -0,0 +1,204 @@
package gojenkins
import (
"errors"
"net/http"
"reflect"
"strconv"
"strings"
)
type GlobalRoleResponse struct {
RoleName string `json:"roleName"`
PermissionIds GlobalPermissionIds `json:"permissionIds"`
}
type GlobalRole struct {
Jenkins *Jenkins
Raw GlobalRoleResponse
}
type GlobalPermissionIds struct {
Administer bool `json:"hudson.model.Hudson.Administer"`
GlobalRead bool `json:"hudson.model.Hudson.Read"`
CredentialCreate bool `json:"com.cloudbees.plugins.credentials.CredentialsProvider.Create"`
CredentialUpdate bool `json:"com.cloudbees.plugins.credentials.CredentialsProvider.Update"`
CredentialView bool `json:"com.cloudbees.plugins.credentials.CredentialsProvider.View"`
CredentialDelete bool `json:"com.cloudbees.plugins.credentials.CredentialsProvider.Delete"`
CredentialManageDomains bool `json:"com.cloudbees.plugins.credentials.CredentialsProvider.ManageDomains"`
SlaveCreate bool `json:"hudson.model.Computer.Create"`
SlaveConfigure bool `json:"hudson.model.Computer.Configure"`
SlaveDelete bool `json:"hudson.model.Computer.Delete"`
SlaveBuild bool `json:"hudson.model.Computer.Build"`
SlaveConnect bool `json:"hudson.model.Computer.Connect"`
SlaveDisconnect bool `json:"hudson.model.Computer.Disconnect"`
ItemBuild bool `json:"hudson.model.Item.Build"`
ItemCreate bool `json:"hudson.model.Item.Create"`
ItemRead bool `json:"hudson.model.Item.Read"`
ItemConfigure bool `json:"hudson.model.Item.Configure"`
ItemCancel bool `json:"hudson.model.Item.Cancel"`
ItemMove bool `json:"hudson.model.Item.Move"`
ItemDiscover bool `json:"hudson.model.Item.Discover"`
ItemWorkspace bool `json:"hudson.model.Item.Workspace"`
ItemDelete bool `json:"hudson.model.Item.Delete"`
RunUpdate bool `json:"hudson.model.Run.Update"`
RunDelete bool `json:"hudson.model.Run.Delete"`
ViewCreate bool `json:"hudson.model.View.Create"`
ViewConfigure bool `json:"hudson.model.View.Configure"`
ViewRead bool `json:"hudson.model.View.Read"`
ViewDelete bool `json:"hudson.model.View.Delete"`
SCMTag bool `json:"hudson.scm.SCM.Tag"`
}
type ProjectRole struct {
Jenkins *Jenkins
Raw ProjectRoleResponse
}
type ProjectRoleResponse struct {
RoleName string `json:"roleName"`
PermissionIds ProjectPermissionIds `json:"permissionIds"`
Pattern string `json:"pattern"`
}
type ProjectPermissionIds struct {
CredentialCreate bool `json:"com.cloudbees.plugins.credentials.CredentialsProvider.Create"`
CredentialUpdate bool `json:"com.cloudbees.plugins.credentials.CredentialsProvider.Update"`
CredentialView bool `json:"com.cloudbees.plugins.credentials.CredentialsProvider.View"`
CredentialDelete bool `json:"com.cloudbees.plugins.credentials.CredentialsProvider.Delete"`
CredentialManageDomains bool `json:"com.cloudbees.plugins.credentials.CredentialsProvider.ManageDomains"`
ItemBuild bool `json:"hudson.model.Item.Build"`
ItemCreate bool `json:"hudson.model.Item.Create"`
ItemRead bool `json:"hudson.model.Item.Read"`
ItemConfigure bool `json:"hudson.model.Item.Configure"`
ItemCancel bool `json:"hudson.model.Item.Cancel"`
ItemMove bool `json:"hudson.model.Item.Move"`
ItemDiscover bool `json:"hudson.model.Item.Discover"`
ItemWorkspace bool `json:"hudson.model.Item.Workspace"`
ItemDelete bool `json:"hudson.model.Item.Delete"`
RunUpdate bool `json:"hudson.model.Run.Update"`
RunDelete bool `json:"hudson.model.Run.Delete"`
RunReplay bool `json:"hudson.model.Run.Replay"`
SCMTag bool `json:"hudson.scm.SCM.Tag"`
}
func (j *GlobalRole) Update(ids GlobalPermissionIds) error {
var idArray []string
values := reflect.ValueOf(ids)
for i := 0; i < values.NumField(); i++ {
field := values.Field(i)
if field.Bool() {
idArray = append(idArray, values.Type().Field(i).Tag.Get("json"))
}
}
param := map[string]string{
"roleName": j.Raw.RoleName,
"type": GLOBAL_ROLE,
"permissionIds": strings.Join(idArray, ","),
"overwrite": strconv.FormatBool(true),
}
responseString := ""
response, err := j.Jenkins.Requester.Post("/role-strategy/strategy/addRole", nil, &responseString, param)
if err != nil {
return err
}
if response.StatusCode != http.StatusOK {
return errors.New(strconv.Itoa(response.StatusCode))
}
return nil
}
func (j *GlobalRole) AssignRole(sid string) error {
param := map[string]string{
"type": GLOBAL_ROLE,
"roleName": j.Raw.RoleName,
"sid": sid,
}
responseString := ""
response, err := j.Jenkins.Requester.Post("/role-strategy/strategy/assignRole", nil, &responseString, param)
if err != nil {
return err
}
if response.StatusCode != http.StatusOK {
return errors.New(strconv.Itoa(response.StatusCode))
}
return nil
}
func (j *GlobalRole) UnAssignRole(sid string) error {
param := map[string]string{
"type": GLOBAL_ROLE,
"roleName": j.Raw.RoleName,
"sid": sid,
}
responseString := ""
response, err := j.Jenkins.Requester.Post("/role-strategy/strategy/unassignRole", nil, &responseString, param)
if err != nil {
return err
}
if response.StatusCode != http.StatusOK {
return errors.New(strconv.Itoa(response.StatusCode))
}
return nil
}
func (j *ProjectRole) Update(pattern string, ids ProjectPermissionIds) error {
var idArray []string
values := reflect.ValueOf(ids)
for i := 0; i < values.NumField(); i++ {
field := values.Field(i)
if field.Bool() {
idArray = append(idArray, values.Type().Field(i).Tag.Get("json"))
}
}
param := map[string]string{
"roleName": j.Raw.RoleName,
"type": PROJECT_ROLE,
"permissionIds": strings.Join(idArray, ","),
"overwrite": strconv.FormatBool(true),
"pattern": pattern,
}
responseString := ""
response, err := j.Jenkins.Requester.Post("/role-strategy/strategy/addRole", nil, &responseString, param)
if err != nil {
return err
}
if response.StatusCode != http.StatusOK {
return errors.New(strconv.Itoa(response.StatusCode))
}
return nil
}
func (j *ProjectRole) AssignRole(sid string) error {
param := map[string]string{
"type": PROJECT_ROLE,
"roleName": j.Raw.RoleName,
"sid": sid,
}
responseString := ""
response, err := j.Jenkins.Requester.Post("/role-strategy/strategy/assignRole", nil, &responseString, param)
if err != nil {
return err
}
if response.StatusCode != http.StatusOK {
return errors.New(strconv.Itoa(response.StatusCode))
}
return nil
}
func (j *ProjectRole) UnAssignRole(sid string) error {
param := map[string]string{
"type": PROJECT_ROLE,
"roleName": j.Raw.RoleName,
"sid": sid,
}
responseString := ""
response, err := j.Jenkins.Requester.Post("/role-strategy/strategy/unassignRole", nil, &responseString, param)
if err != nil {
return err
}
if response.StatusCode != http.StatusOK {
return errors.New(strconv.Itoa(response.StatusCode))
}
return nil
}

61
pkg/gojenkins/utils.go Normal file
View File

@@ -0,0 +1,61 @@
// Copyright 2015 Vadim Kravcenko
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package gojenkins
import (
"encoding/json"
"strings"
"time"
"unicode/utf8"
)
func makeJson(data interface{}) string {
str, err := json.Marshal(data)
if err != nil {
return ""
}
return string(json.RawMessage(str))
}
func Reverse(s string) string {
size := len(s)
buf := make([]byte, size)
for start := 0; start < size; {
r, n := utf8.DecodeRuneInString(s[start:])
start += n
utf8.EncodeRune(buf[size-start:], r)
}
return string(buf)
}
type JenkinsBlueTime time.Time
func (t *JenkinsBlueTime) UnmarshalJSON(b []byte) error {
if b == nil || strings.Trim(string(b), "\"") == "null" {
*t = JenkinsBlueTime(time.Time{})
return nil
}
j, err := time.Parse("2006-01-02T15:04:05.000-0700", strings.Trim(string(b), "\""))
if err != nil {
return err
}
*t = JenkinsBlueTime(j)
return nil
}
func (t JenkinsBlueTime) MarshalJSON() ([]byte, error) {
return json.Marshal(time.Time(t))
}

View File

@@ -0,0 +1,21 @@
package utils
import (
"github.com/asaskevich/govalidator"
"kubesphere.io/kubesphere/pkg/gojenkins"
"net/http"
"strconv"
)
func GetJenkinsStatusCode(jenkinsErr error) int {
if code, err := strconv.Atoi(jenkinsErr.Error()); err == nil {
message := http.StatusText(code)
if !govalidator.IsNull(message) {
return code
}
}
if jErr, ok := jenkinsErr.(*gojenkins.ErrorResponse); ok {
return jErr.Response.StatusCode
}
return http.StatusInternalServerError
}

94
pkg/gojenkins/views.go Normal file
View File

@@ -0,0 +1,94 @@
// Copyright 2015 Vadim Kravcenko
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package gojenkins
import (
"errors"
"strconv"
)
type View struct {
Raw *ViewResponse
Jenkins *Jenkins
Base string
}
type ViewResponse struct {
Description string `json:"description"`
Jobs []InnerJob `json:"jobs"`
Name string `json:"name"`
Property []interface{} `json:"property"`
URL string `json:"url"`
}
var (
LIST_VIEW = "hudson.model.ListView"
NESTED_VIEW = "hudson.plugins.nested_view.NestedView"
MY_VIEW = "hudson.model.MyView"
DASHBOARD_VIEW = "hudson.plugins.view.dashboard.Dashboard"
PIPELINE_VIEW = "au.com.centrumsystems.hudson.plugin.buildpipeline.BuildPipelineView"
)
// Returns True if successfully added Job, otherwise false
func (v *View) AddJob(name string) (bool, error) {
url := "/addJobToView"
qr := map[string]string{"name": name}
resp, err := v.Jenkins.Requester.Post(v.Base+url, nil, nil, qr)
if err != nil {
return false, err
}
if resp.StatusCode == 200 {
return true, nil
}
return false, errors.New(strconv.Itoa(resp.StatusCode))
}
// Returns True if successfully deleted Job, otherwise false
func (v *View) DeleteJob(name string) (bool, error) {
url := "/removeJobFromView"
qr := map[string]string{"name": name}
resp, err := v.Jenkins.Requester.Post(v.Base+url, nil, nil, qr)
if err != nil {
return false, err
}
if resp.StatusCode == 200 {
return true, nil
}
return false, errors.New(strconv.Itoa(resp.StatusCode))
}
func (v *View) GetDescription() string {
return v.Raw.Description
}
func (v *View) GetJobs() []InnerJob {
return v.Raw.Jobs
}
func (v *View) GetName() string {
return v.Raw.Name
}
func (v *View) GetUrl() string {
return v.Raw.URL
}
func (v *View) Poll() (int, error) {
response, err := v.Jenkins.Requester.GetJSON(v.Base, v.Raw, nil)
if err != nil {
return 0, err
}
return response.StatusCode, nil
}

View File

@@ -0,0 +1,61 @@
package devops
import (
"github.com/fatih/structs"
"kubesphere.io/kubesphere/pkg/utils/stringutils"
)
func GetColumnsFromStruct(s interface{}) []string {
names := structs.Names(s)
for i, name := range names {
names[i] = stringutils.CamelCaseToUnderscore(name)
}
return names
}
func GetColumnsFromStructWithPrefix(prefix string, s interface{}) []string {
names := structs.Names(s)
for i, name := range names {
names[i] = WithPrefix(prefix, stringutils.CamelCaseToUnderscore(name))
}
return names
}
func WithPrefix(prefix, str string) string {
return prefix + "." + str
}
const (
StatusActive = "active"
StatusDeleted = "deleted"
StatusDeleting = "deleting"
StatusFailed = "failed"
StatusPending = "pending"
StatusWorking = "working"
StatusSuccessful = "successful"
)
const (
StatusColumn = "status"
StatusTimeColumn = "status_time"
)
const (
VisibilityPrivate = "private"
VisibilityPublic = "public"
)
const (
KS_ADMIN = "admin"
)
const (
ProjectOwner = "owner"
ProjectMaintainer = "maintainer"
ProjectDeveloper = "developer"
ProjectReporter = "reporter"
)
const (
JenkinsAllUserRoleName = "kubesphere-user"
)

View File

@@ -0,0 +1,28 @@
package devops
const (
DevOpsProjectMembershipTableName = "project_membership"
DevOpsProjectMembershipUsernameColumn = "project_membership.username"
DevOpsProjectMembershipProjectIdColumn = "project_membership.project_id"
DevOpsProjectMembershipRoleColumn = "project_membership.role"
)
type DevOpsProjectMembership struct {
Username string `json:"username"`
ProjectId string `json:"project_id" db:"project_id"`
Role string `json:"role"`
Status string `json:"status"`
GrantBy string `json:"grand_by,omitempty"`
}
var DevOpsProjectMembershipColumns = GetColumnsFromStruct(&DevOpsProjectMembership{})
func NewDevOpsProjectMemberShip(username, projectId, role, grantBy string) *DevOpsProjectMembership {
return &DevOpsProjectMembership{
Username: username,
ProjectId: projectId,
Role: role,
Status: StatusActive,
GrantBy: grantBy,
}
}

View File

@@ -0,0 +1,45 @@
package devops
import (
"kubesphere.io/kubesphere/pkg/utils/idutils"
"time"
)
var DevOpsProjectColumns = GetColumnsFromStruct(&DevOpsProject{})
const (
DevOpsProjectTableName = "project"
DevOpsProjectPrefix = "project-"
DevOpsProjectDescriptionColumn = "description"
DevOpsProjectIdColumn = "project.project_id"
DevOpsProjectNameColumn = "project.name"
DevOpsProjectExtraColumn = "project.extra"
DevOpsProjectWorkSpaceColumn = "project.workspace"
DevOpsProjectCreateTimeColumn = "project.create_time"
)
type DevOpsProject struct {
ProjectId string `json:"project_id" db:"project_id"`
Name string `json:"name"`
Description string `json:"description"`
Creator string `json:"creator"`
CreateTime time.Time `json:"create_time"`
Status string `json:"status"`
Visibility string `json:"visibility"`
Extra string `json:"extra"`
Workspace string `json:"workspace"`
}
func NewDevOpsProject(name, description, creator, extra, workspace string) *DevOpsProject {
return &DevOpsProject{
ProjectId: idutils.GetUuid(DevOpsProjectPrefix),
Name: name,
Description: description,
Creator: creator,
CreateTime: time.Now(),
Status: StatusActive,
Visibility: VisibilityPrivate,
Extra: extra,
Workspace: workspace,
}
}

View File

@@ -18,73 +18,469 @@
package tenant
import (
"fmt"
"github.com/gocraft/dbr"
"github.com/golang/glog"
"kubesphere.io/kubesphere/pkg/db"
"kubesphere.io/kubesphere/pkg/gojenkins"
"kubesphere.io/kubesphere/pkg/gojenkins/utils"
"kubesphere.io/kubesphere/pkg/models"
"kubesphere.io/kubesphere/pkg/models/devops"
"kubesphere.io/kubesphere/pkg/params"
"kubesphere.io/kubesphere/pkg/simple/client/kubesphere"
"kubesphere.io/kubesphere/pkg/simple/client/mysql"
"sort"
"strings"
"kubesphere.io/kubesphere/pkg/simple/client/admin_jenkins"
"kubesphere.io/kubesphere/pkg/simple/client/devops_mysql"
"kubesphere.io/kubesphere/pkg/utils/reflectutils"
"net/http"
"sync"
)
const (
ProjectOwner = "owner"
ProjectMaintainer = "maintainer"
ProjectDeveloper = "developer"
ProjectReporter = "reporter"
)
var AllRoleSlice = []string{ProjectDeveloper, ProjectReporter, ProjectMaintainer, ProjectOwner}
var JenkinsOwnerProjectPermissionIds = &gojenkins.ProjectPermissionIds{
CredentialCreate: true,
CredentialDelete: true,
CredentialManageDomains: true,
CredentialUpdate: true,
CredentialView: true,
ItemBuild: true,
ItemCancel: true,
ItemConfigure: true,
ItemCreate: true,
ItemDelete: true,
ItemDiscover: true,
ItemMove: true,
ItemRead: true,
ItemWorkspace: true,
RunDelete: true,
RunReplay: true,
RunUpdate: true,
SCMTag: true,
}
var JenkinsProjectPermissionMap = map[string]gojenkins.ProjectPermissionIds{
ProjectOwner: gojenkins.ProjectPermissionIds{
CredentialCreate: true,
CredentialDelete: true,
CredentialManageDomains: true,
CredentialUpdate: true,
CredentialView: true,
ItemBuild: true,
ItemCancel: true,
ItemConfigure: true,
ItemCreate: true,
ItemDelete: true,
ItemDiscover: true,
ItemMove: true,
ItemRead: true,
ItemWorkspace: true,
RunDelete: true,
RunReplay: true,
RunUpdate: true,
SCMTag: true,
},
ProjectMaintainer: gojenkins.ProjectPermissionIds{
CredentialCreate: true,
CredentialDelete: true,
CredentialManageDomains: true,
CredentialUpdate: true,
CredentialView: true,
ItemBuild: true,
ItemCancel: true,
ItemConfigure: false,
ItemCreate: true,
ItemDelete: false,
ItemDiscover: true,
ItemMove: false,
ItemRead: true,
ItemWorkspace: true,
RunDelete: true,
RunReplay: true,
RunUpdate: true,
SCMTag: true,
},
ProjectDeveloper: gojenkins.ProjectPermissionIds{
CredentialCreate: false,
CredentialDelete: false,
CredentialManageDomains: false,
CredentialUpdate: false,
CredentialView: false,
ItemBuild: true,
ItemCancel: true,
ItemConfigure: false,
ItemCreate: false,
ItemDelete: false,
ItemDiscover: true,
ItemMove: false,
ItemRead: true,
ItemWorkspace: true,
RunDelete: true,
RunReplay: true,
RunUpdate: true,
SCMTag: false,
},
ProjectReporter: gojenkins.ProjectPermissionIds{
CredentialCreate: false,
CredentialDelete: false,
CredentialManageDomains: false,
CredentialUpdate: false,
CredentialView: false,
ItemBuild: false,
ItemCancel: false,
ItemConfigure: false,
ItemCreate: false,
ItemDelete: false,
ItemDiscover: true,
ItemMove: false,
ItemRead: true,
ItemWorkspace: false,
RunDelete: false,
RunReplay: false,
RunUpdate: false,
SCMTag: false,
},
}
var JenkinsPipelinePermissionMap = map[string]gojenkins.ProjectPermissionIds{
ProjectOwner: gojenkins.ProjectPermissionIds{
CredentialCreate: true,
CredentialDelete: true,
CredentialManageDomains: true,
CredentialUpdate: true,
CredentialView: true,
ItemBuild: true,
ItemCancel: true,
ItemConfigure: true,
ItemCreate: true,
ItemDelete: true,
ItemDiscover: true,
ItemMove: true,
ItemRead: true,
ItemWorkspace: true,
RunDelete: true,
RunReplay: true,
RunUpdate: true,
SCMTag: true,
},
ProjectMaintainer: gojenkins.ProjectPermissionIds{
CredentialCreate: true,
CredentialDelete: true,
CredentialManageDomains: true,
CredentialUpdate: true,
CredentialView: true,
ItemBuild: true,
ItemCancel: true,
ItemConfigure: true,
ItemCreate: true,
ItemDelete: true,
ItemDiscover: true,
ItemMove: true,
ItemRead: true,
ItemWorkspace: true,
RunDelete: true,
RunReplay: true,
RunUpdate: true,
SCMTag: true,
},
ProjectDeveloper: gojenkins.ProjectPermissionIds{
CredentialCreate: false,
CredentialDelete: false,
CredentialManageDomains: false,
CredentialUpdate: false,
CredentialView: false,
ItemBuild: true,
ItemCancel: true,
ItemConfigure: false,
ItemCreate: false,
ItemDelete: false,
ItemDiscover: true,
ItemMove: false,
ItemRead: true,
ItemWorkspace: true,
RunDelete: true,
RunReplay: true,
RunUpdate: true,
SCMTag: false,
},
ProjectReporter: gojenkins.ProjectPermissionIds{
CredentialCreate: false,
CredentialDelete: false,
CredentialManageDomains: false,
CredentialUpdate: false,
CredentialView: false,
ItemBuild: false,
ItemCancel: false,
ItemConfigure: false,
ItemCreate: false,
ItemDelete: false,
ItemDiscover: true,
ItemMove: false,
ItemRead: true,
ItemWorkspace: false,
RunDelete: false,
RunReplay: false,
RunUpdate: false,
SCMTag: false,
},
}
func GetProjectRoleName(projectId, role string) string {
return fmt.Sprintf("%s-%s-project", projectId, role)
}
func GetPipelineRoleName(projectId, role string) string {
return fmt.Sprintf("%s-%s-pipeline", projectId, role)
}
func GetProjectRolePattern(projectId string) string {
return fmt.Sprintf("^%s$", projectId)
}
func GetPipelineRolePattern(projectId string) string {
return fmt.Sprintf("^%s/.*", projectId)
}
type DevOpsProjectRoleResponse struct {
ProjectRole *gojenkins.ProjectRole
Err error
}
func CheckProjectUserInRole(username, projectId string, roles []string) error {
if username == devops.KS_ADMIN {
return nil
}
dbconn := devops_mysql.OpenDatabase()
membership := &devops.DevOpsProjectMembership{}
err := dbconn.Select(devops.DevOpsProjectMembershipColumns...).
From(devops.DevOpsProjectMembershipTableName).
Where(db.And(
db.Eq(devops.DevOpsProjectMembershipUsernameColumn, username),
db.Eq(devops.DevOpsProjectMembershipProjectIdColumn, projectId))).LoadOne(membership)
if err != nil {
return err
}
if !reflectutils.In(membership.Role, roles) {
return fmt.Errorf("user [%s] in project [%s] role is not in %s", username, projectId, roles)
}
return nil
}
func ListDevopsProjects(workspace, username string, conditions *params.Conditions, orderBy string, reverse bool, limit int, offset int) (*models.PageableResponse, error) {
db := mysql.Client()
dbconn := devops_mysql.OpenDatabase()
var workspaceDOPBindings []models.WorkspaceDPBinding
query := dbconn.Select(devops.GetColumnsFromStructWithPrefix(devops.DevOpsProjectTableName, devops.DevOpsProject{})...).
From(devops.DevOpsProjectTableName)
var sqconditions []dbr.Builder
if err := db.Where("workspace = ?", workspace).Find(&workspaceDOPBindings).Error; err != nil {
return nil, err
}
projects, err := kubesphere.Client().ListDevopsProjects(username)
if err != nil {
return nil, err
sqconditions = append(sqconditions, db.Eq(devops.DevOpsProjectWorkSpaceColumn, workspace))
switch username {
case devops.KS_ADMIN:
default:
onCondition := fmt.Sprintf("%s = %s", devops.DevOpsProjectMembershipProjectIdColumn, devops.DevOpsProjectIdColumn)
query.Join(devops.DevOpsProjectMembershipTableName, onCondition)
sqconditions = append(sqconditions, db.Eq(devops.DevOpsProjectMembershipUsernameColumn, username))
sqconditions = append(sqconditions, db.Eq(
devops.DevOpsProjectMembershipTableName+"."+devops.StatusColumn, devops.StatusActive))
}
sqconditions = append(sqconditions, db.Eq(
devops.DevOpsProjectTableName+"."+devops.StatusColumn, devops.StatusActive))
if keyword := conditions.Match["keyword"]; keyword != "" {
for i := 0; i < len(projects); i++ {
if !strings.Contains(projects[i].Name, keyword) {
projects = append(projects[:i], projects[i+1:]...)
i--
}
}
sqconditions = append(sqconditions, db.Like(devops.DevOpsProjectNameColumn, keyword))
}
projects := make([]*devops.DevOpsProject, 0)
sort.Slice(projects, func(i, j int) bool {
if len(sqconditions) > 0 {
query.Where(db.And(sqconditions...))
}
switch orderBy {
case "name":
if reverse {
tmp := i
i = j
j = tmp
query.OrderDesc(devops.DevOpsProjectNameColumn)
} else {
query.OrderAsc(devops.DevOpsProjectNameColumn)
}
switch orderBy {
case "name":
return projects[i].Name > projects[j].Name
default:
return projects[i].CreateTime.Before(*projects[j].CreateTime)
default:
if reverse {
query.OrderAsc(devops.DevOpsProjectCreateTimeColumn)
} else {
query.OrderDesc(devops.DevOpsProjectCreateTimeColumn)
}
})
for i := 0; i < len(projects); i++ {
inWorkspace := false
for _, binding := range workspaceDOPBindings {
if binding.DevOpsProject == projects[i].ProjectId {
inWorkspace = true
}
}
if !inWorkspace {
projects = append(projects[:i], projects[i+1:]...)
i--
}
}
query.Limit(uint64(limit))
query.Offset(uint64(offset))
_, err := query.Load(&projects)
if err != nil {
glog.Errorf("%+v", err)
return nil, err
}
count, err := query.Count()
if err != nil {
glog.Errorf("%+v", err)
return nil, err
}
// limit offset
result := make([]interface{}, 0)
for i, v := range projects {
if len(result) < limit && i >= offset {
result = append(result, v)
for _, v := range projects {
result = append(result, v)
}
return &models.PageableResponse{Items: result, TotalCount: int(count)}, nil
}
func DeleteDevOpsProject(projectId, username string) (error, int) {
err := CheckProjectUserInRole(username, projectId, []string{ProjectOwner})
if err != nil {
glog.Errorf("%+v", err)
return err, http.StatusForbidden
}
gojenkins := admin_jenkins.Client()
devopsdb := devops_mysql.OpenDatabase()
_, err = gojenkins.DeleteJob(projectId)
if err != nil && utils.GetJenkinsStatusCode(err) != http.StatusNotFound {
glog.Errorf("%+v", err)
return err, utils.GetJenkinsStatusCode(err)
}
roleNames := make([]string, 0)
for role := range JenkinsProjectPermissionMap {
roleNames = append(roleNames, GetProjectRoleName(projectId, role))
roleNames = append(roleNames, GetPipelineRoleName(projectId, role))
}
err = gojenkins.DeleteProjectRoles(roleNames...)
if err != nil {
glog.Errorf("%+v", err)
return err, utils.GetJenkinsStatusCode(err)
}
_, err = devopsdb.DeleteFrom(devops.DevOpsProjectMembershipTableName).
Where(db.Eq(devops.DevOpsProjectMembershipProjectIdColumn, projectId)).Exec()
if err != nil {
glog.Errorf("%+v", err)
return err, http.StatusInternalServerError
}
_, err = devopsdb.Update(devops.DevOpsProjectTableName).
Set(devops.StatusColumn, devops.StatusDeleted).
Where(db.Eq(devops.DevOpsProjectIdColumn, projectId)).Exec()
if err != nil {
glog.Errorf("%+v", err)
return err, http.StatusInternalServerError
}
project := &devops.DevOpsProject{}
err = devopsdb.Select(devops.DevOpsProjectColumns...).
From(devops.DevOpsProjectTableName).
Where(db.Eq(devops.DevOpsProjectIdColumn, projectId)).
LoadOne(project)
if err != nil {
glog.Errorf("%+v", err)
return err, http.StatusInternalServerError
}
return nil, http.StatusOK
}
func CreateDevopsProject(username string, workspace string, req *devops.DevOpsProject) (*devops.DevOpsProject, error, int) {
jenkinsClient := admin_jenkins.Client()
devopsdb := devops_mysql.OpenDatabase()
project := devops.NewDevOpsProject(req.Name, req.Description, username, req.Extra, workspace)
_, err := jenkinsClient.CreateFolder(project.ProjectId, project.Description)
if err != nil {
glog.Errorf("%+v", err)
return nil, err, utils.GetJenkinsStatusCode(err)
}
var addRoleCh = make(chan *DevOpsProjectRoleResponse, 8)
var addRoleWg sync.WaitGroup
for role, permission := range JenkinsProjectPermissionMap {
addRoleWg.Add(1)
go func(role string, permission gojenkins.ProjectPermissionIds) {
_, err := jenkinsClient.AddProjectRole(GetProjectRoleName(project.ProjectId, role),
GetProjectRolePattern(project.ProjectId), permission, true)
addRoleCh <- &DevOpsProjectRoleResponse{nil, err}
addRoleWg.Done()
}(role, permission)
}
for role, permission := range JenkinsPipelinePermissionMap {
addRoleWg.Add(1)
go func(role string, permission gojenkins.ProjectPermissionIds) {
_, err := jenkinsClient.AddProjectRole(GetPipelineRoleName(project.ProjectId, role),
GetPipelineRolePattern(project.ProjectId), permission, true)
addRoleCh <- &DevOpsProjectRoleResponse{nil, err}
addRoleWg.Done()
}(role, permission)
}
addRoleWg.Wait()
close(addRoleCh)
for addRoleResponse := range addRoleCh {
if addRoleResponse.Err != nil {
glog.Errorf("%+v", addRoleResponse.Err)
return nil, addRoleResponse.Err, utils.GetJenkinsStatusCode(addRoleResponse.Err)
}
}
return &models.PageableResponse{Items: result, TotalCount: len(projects)}, nil
globalRole, err := jenkinsClient.GetGlobalRole(devops.JenkinsAllUserRoleName)
if err != nil {
glog.Errorf("%+v", err)
return nil, err, utils.GetJenkinsStatusCode(err)
}
if globalRole == nil {
_, err := jenkinsClient.AddGlobalRole(devops.JenkinsAllUserRoleName, gojenkins.GlobalPermissionIds{
GlobalRead: true,
}, true)
if err != nil {
glog.Error("failed to create jenkins global role")
return nil, err, utils.GetJenkinsStatusCode(err)
}
}
err = globalRole.AssignRole(username)
if err != nil {
glog.Errorf("%+v", err)
return nil, err, utils.GetJenkinsStatusCode(err)
}
projectRole, err := jenkinsClient.GetProjectRole(GetProjectRoleName(project.ProjectId, ProjectOwner))
if err != nil {
glog.Errorf("%+v", err)
return nil, err, utils.GetJenkinsStatusCode(err)
}
err = projectRole.AssignRole(username)
if err != nil {
glog.Errorf("%+v", err)
return nil, err, utils.GetJenkinsStatusCode(err)
}
pipelineRole, err := jenkinsClient.GetProjectRole(GetPipelineRoleName(project.ProjectId, ProjectOwner))
if err != nil {
glog.Errorf("%+v", err)
return nil, err, utils.GetJenkinsStatusCode(err)
}
err = pipelineRole.AssignRole(username)
if err != nil {
glog.Errorf("%+v", err)
return nil, err, utils.GetJenkinsStatusCode(err)
}
_, err = devopsdb.InsertInto(devops.DevOpsProjectTableName).
Columns(devops.DevOpsProjectColumns...).Record(project).Exec()
if err != nil {
glog.Errorf("%+v", err)
return nil, err, http.StatusInternalServerError
}
projectMembership := devops.NewDevOpsProjectMemberShip(username, project.ProjectId, ProjectOwner, username)
_, err = devopsdb.InsertInto(devops.DevOpsProjectMembershipTableName).
Columns(devops.DevOpsProjectMembershipColumns...).Record(projectMembership).Exec()
if err != nil {
glog.Errorf("%+v", err)
return nil, err, http.StatusInternalServerError
}
return project, nil, http.StatusOK
}

View File

@@ -21,17 +21,17 @@ import (
"fmt"
"k8s.io/apimachinery/pkg/runtime/schema"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/db"
"kubesphere.io/kubesphere/pkg/informers"
"kubesphere.io/kubesphere/pkg/models"
"kubesphere.io/kubesphere/pkg/models/devops"
"kubesphere.io/kubesphere/pkg/models/iam"
"kubesphere.io/kubesphere/pkg/models/resources"
"kubesphere.io/kubesphere/pkg/params"
"kubesphere.io/kubesphere/pkg/simple/client/devops_mysql"
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
"kubesphere.io/kubesphere/pkg/simple/client/kubesphere"
"kubesphere.io/kubesphere/pkg/simple/client/mysql"
"kubesphere.io/kubesphere/pkg/utils/k8sutil"
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
"strings"
core "k8s.io/api/core/v1"
@@ -43,28 +43,6 @@ import (
"k8s.io/apimachinery/pkg/labels"
)
func UnBindDevopsProject(workspace string, devops string) error {
db := mysql.Client()
return db.Delete(&models.WorkspaceDPBinding{Workspace: workspace, DevOpsProject: devops}).Error
}
func CreateDevopsProject(username string, workspace string, devops *models.DevopsProject) (*models.DevopsProject, error) {
created, err := kubesphere.Client().CreateDevopsProject(username, devops)
if err != nil {
return nil, err
}
err = BindingDevopsProject(workspace, created.ProjectId)
if err != nil {
return nil, err
}
return created, nil
}
func Namespaces(workspaceName string) ([]*core.Namespace, error) {
namespaceLister := informers.SharedInformerFactory().Core().V1().Namespaces().Lister()
namespaces, err := namespaceLister.List(labels.SelectorFromSet(labels.Set{constants.WorkspaceLabelKey: workspaceName}))
@@ -86,11 +64,6 @@ func Namespaces(workspaceName string) ([]*core.Namespace, error) {
return out, nil
}
func BindingDevopsProject(workspace string, devops string) error {
db := mysql.Client()
return db.Create(&models.WorkspaceDPBinding{Workspace: workspace, DevOpsProject: devops}).Error
}
func DeleteNamespace(workspace string, namespaceName string) error {
namespace, err := k8s.Client().CoreV1().Namespaces().Get(namespaceName, meta_v1.GetOptions{})
if err != nil {
@@ -184,18 +157,17 @@ func DeleteWorkspaceRoleBinding(workspace, username string, role string) error {
func GetDevOpsProjects(workspaceName string) ([]string, error) {
db := mysql.Client()
dbconn := devops_mysql.OpenDatabase()
var workspaceDOPBindings []models.WorkspaceDPBinding
if err := db.Where("workspace = ?", workspaceName).Find(&workspaceDOPBindings).Error; err != nil {
return nil, err
}
query := dbconn.Select(devops.DevOpsProjectIdColumn).
From(devops.DevOpsProjectTableName).
Where(db.And(db.Eq(devops.DevOpsProjectWorkSpaceColumn, workspaceName),
db.Eq(devops.StatusColumn, devops.StatusActive)))
devOpsProjects := make([]string, 0)
for _, workspaceDOPBinding := range workspaceDOPBindings {
devOpsProjects = append(devOpsProjects, workspaceDOPBinding.DevOpsProject)
if _, err := query.Load(&devOpsProjects); err != nil {
return nil, err
}
return devOpsProjects, nil
}
@@ -249,12 +221,18 @@ func GetAllProjectNums() (int, error) {
}
func GetAllDevOpsProjectsNums() (int, error) {
db := mysql.Client()
var count int
if err := db.Model(&models.WorkspaceDPBinding{}).Count(&count).Error; err != nil {
dbconn := devops_mysql.OpenDatabase()
query := dbconn.Select(devops.DevOpsProjectIdColumn).
From(devops.DevOpsProjectTableName).
Where(db.Eq(devops.StatusColumn, devops.StatusActive))
devOpsProjects := make([]string, 0)
if _, err := query.Load(&devOpsProjects); err != nil {
return 0, err
}
return count, nil
return len(devOpsProjects), nil
}
func GetAllAccountNums() (int, error) {

View File

@@ -0,0 +1,63 @@
package admin_jenkins
import (
"flag"
"github.com/golang/glog"
"kubesphere.io/kubesphere/pkg/gojenkins"
"sync"
)
var (
jenkinsClientOnce sync.Once
jenkinsClient *gojenkins.Jenkins
jenkinsAdminAddress string
jenkinsAdminUsername string
jenkinsAdminPassword string
jenkinsMaxConn int
)
const (
JenkinsAllUserRoleName = "kubesphere-user"
)
func init() {
flag.StringVar(&jenkinsAdminAddress, "jenkins-address", "http://ks-jenkins.kubesphere-devops-system.svc/", "data source name")
flag.StringVar(&jenkinsAdminUsername, "jenkins-username", "admin", "username of jenkins")
flag.StringVar(&jenkinsAdminPassword, "jenkins-password", "passw0rd", "password of jenkins")
flag.IntVar(&jenkinsMaxConn, "jenkins-max-conn", 20, "max conn to jenkins")
}
func Client() *gojenkins.Jenkins {
jenkinsClientOnce.Do(func() {
jenkins := gojenkins.CreateJenkins(nil, jenkinsAdminAddress, jenkinsMaxConn, jenkinsAdminUsername, jenkinsAdminPassword)
jenkins, err := jenkins.Init()
if err != nil {
glog.Error("failed to connect jenkins")
return
}
jenkinsClient = jenkins
globalRole, err := jenkins.GetGlobalRole(JenkinsAllUserRoleName)
if err != nil {
glog.Error("failed to get jenkins role")
}
if globalRole == nil {
_, err := jenkins.AddGlobalRole(JenkinsAllUserRoleName, gojenkins.GlobalPermissionIds{
GlobalRead: true,
}, true)
if err != nil {
glog.Error("failed to create jenkins global role")
return
}
}
_, err = jenkins.AddProjectRole(JenkinsAllUserRoleName, "\\n\\s*\\r", gojenkins.ProjectPermissionIds{
SCMTag: true,
}, true)
if err != nil {
glog.Error("failed to create jenkins project role")
return
}
})
return jenkinsClient
}

View File

@@ -0,0 +1,56 @@
/*
Copyright 2018 The KubeSphere Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package devops_mysql
import (
"flag"
"github.com/gocraft/dbr"
"github.com/golang/glog"
"kubesphere.io/kubesphere/pkg/db"
"sync"
"time"
)
var (
dbClientOnce sync.Once
dsn string
dbClient *db.Database
)
func init() {
flag.StringVar(&dsn, "devops-database-connection", "root@tcp(127.0.0.1:3306)/devops", "data source name")
}
var defaultEventReceiver = db.EventReceiver{}
func OpenDatabase() *db.Database {
dbClientOnce.Do(func() {
conn, err := dbr.Open("mysql", dsn+"?parseTime=1&multiStatements=1&charset=utf8mb4&collation=utf8mb4_unicode_ci", &defaultEventReceiver)
if err != nil {
glog.Fatal(err)
}
conn.SetMaxIdleConns(100)
conn.SetMaxOpenConns(100)
conn.SetConnMaxLifetime(10 * time.Second)
dbClient = &db.Database{
Session: conn.NewSession(nil),
}
err = dbClient.Ping()
if err != nil {
glog.Error(err)
}
})
return dbClient
}

View File

@@ -1,60 +0,0 @@
/*
Copyright 2019 The KubeSphere Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package mysql
import (
"flag"
"log"
"os"
"os/signal"
"sync"
"syscall"
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
)
var (
dbClientOnce sync.Once
dbClient *gorm.DB
dsn string
)
func init() {
flag.StringVar(&dsn, "database-connection", "root@tcp(localhost:3306)/kubesphere?charset=utf8&parseTime=True", "data source name")
}
func Client() *gorm.DB {
dbClientOnce.Do(func() {
var err error
dbClient, err = gorm.Open("mysql", dsn)
if err != nil {
log.Fatalln(err)
}
c := make(chan os.Signal, 0)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
dbClient.Close()
}()
})
return dbClient
}

View File

@@ -0,0 +1,49 @@
package sonarqube
import (
"flag"
"github.com/golang/glog"
"github.com/kubesphere/sonargo/sonar"
"strings"
"sync"
)
var (
sonarAddress string
sonarToken string
sonarOnce sync.Once
sonarClient *sonargo.Client
)
func init() {
flag.StringVar(&sonarAddress, "sonar-address", "", "sonar server host")
flag.StringVar(&sonarToken, "sonar-token", "", "sonar token")
}
func Client() *sonargo.Client {
sonarOnce.Do(func() {
if sonarAddress == "" {
sonarClient = nil
glog.Info("skip sonar init")
return
}
if !strings.HasSuffix(sonarAddress, "/") {
sonarAddress += "/"
}
client, err := sonargo.NewClientWithToken(sonarAddress+"api/", sonarToken)
if err != nil {
glog.Error("failed to connect to sonar")
return
}
_, _, err = client.Projects.Search(nil)
if err != nil {
glog.Errorf("failed to search sonar projects [%+v]", err)
return
}
glog.Info("init sonar client success")
sonarClient = client
})
return sonarClient
}

View File

@@ -0,0 +1,94 @@
/*
Copyright 2018 The KubeSphere Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package idutils
import (
"errors"
"net"
"os"
"github.com/golang/example/stringutil"
"github.com/sony/sonyflake"
hashids "github.com/speps/go-hashids"
"kubesphere.io/kubesphere/pkg/utils/stringutils"
)
var sf *sonyflake.Sonyflake
func init() {
var st sonyflake.Settings
if len(os.Getenv("DEVOPSPHERE_IP")) != 0 {
st.MachineID = machineID
}
sf = sonyflake.NewSonyflake(st)
if sf == nil {
panic("failed to initialize sonyflake")
}
}
func GetIntId() uint64 {
id, err := sf.NextID()
if err != nil {
panic(err)
}
return id
}
// format likes: B6BZVN3mOPvx
func GetUuid(prefix string) string {
id := GetIntId()
hd := hashids.NewData()
h, err := hashids.NewWithData(hd)
if err != nil {
panic(err)
}
i, err := h.Encode([]int{int(id)})
if err != nil {
panic(err)
}
return prefix + stringutils.Reverse(i)
}
const Alphabet36 = "abcdefghijklmnopqrstuvwxyz1234567890"
// format likes: 300m50zn91nwz5
func GetUuid36(prefix string) string {
id := GetIntId()
hd := hashids.NewData()
hd.Alphabet = Alphabet36
h, err := hashids.NewWithData(hd)
if err != nil {
panic(err)
}
i, err := h.Encode([]int{int(id)})
if err != nil {
panic(err)
}
return prefix + stringutil.Reverse(i)
}
func machineID() (uint16, error) {
ipStr := os.Getenv("DEVOPSPHERE_IP")
if len(ipStr) == 0 {
return 0, errors.New("'DEVOPSPHERE_IP' environment variable not set")
}
ip := net.ParseIP(ipStr)
if len(ip) < 4 {
return 0, errors.New("invalid IP")
}
return uint16(ip[2])<<8 + uint16(ip[3]), nil
}

View File

@@ -0,0 +1,24 @@
package idutils
import (
"fmt"
"sort"
"testing"
)
func TestGetUuid(t *testing.T) {
fmt.Println(GetUuid(""))
}
func TestGetUuid36(t *testing.T) {
fmt.Println(GetUuid36(""))
}
func TestGetManyUuid(t *testing.T) {
var strSlice []string
for i := 0; i < 10000; i++ {
testId := GetUuid("")
strSlice = append(strSlice, testId)
}
sort.Strings(strSlice)
}

View File

@@ -0,0 +1,35 @@
/*
Copyright 2018 The KubeSphere Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package reflectutils
import "reflect"
func In(value interface{}, container interface{}) bool {
containerValue := reflect.ValueOf(container)
switch reflect.TypeOf(container).Kind() {
case reflect.Slice, reflect.Array:
for i := 0; i < containerValue.Len(); i++ {
if containerValue.Index(i).Interface() == value {
return true
}
}
case reflect.Map:
if containerValue.MapIndex(reflect.ValueOf(value)).IsValid() {
return true
}
default:
return false
}
return false
}

View File

@@ -0,0 +1,77 @@
/*
Copyright 2018 The KubeSphere Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package stringutils
import (
"unicode/utf8"
"github.com/asaskevich/govalidator"
)
// Creates an slice of slice values not included in the other given slice.
func Diff(base, exclude []string) (result []string) {
excludeMap := make(map[string]bool)
for _, s := range exclude {
excludeMap[s] = true
}
for _, s := range base {
if !excludeMap[s] {
result = append(result, s)
}
}
return result
}
func Unique(ss []string) (result []string) {
smap := make(map[string]bool)
for _, s := range ss {
smap[s] = true
}
for s := range smap {
result = append(result, s)
}
return result
}
func CamelCaseToUnderscore(str string) string {
return govalidator.CamelCaseToUnderscore(str)
}
func UnderscoreToCamelCase(str string) string {
return govalidator.UnderscoreToCamelCase(str)
}
func FindString(array []string, str string) int {
for index, s := range array {
if str == s {
return index
}
}
return -1
}
func StringIn(str string, array []string) bool {
return FindString(array, str) > -1
}
func Reverse(s string) string {
size := len(s)
buf := make([]byte, size)
for start := 0; start < size; {
r, n := utf8.DecodeRuneInString(s[start:])
start += n
utf8.EncodeRune(buf[size-start:], r)
}
return string(buf)
}