@@ -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
11
pkg/db/Dockerfile
Normal 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
109
pkg/db/condition.go
Normal 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
88
pkg/db/condition_test.go
Normal 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
283
pkg/db/db.go
Normal 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
2
pkg/db/ddl/devops.sql
Normal 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
31
pkg/db/error.go
Normal 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
55
pkg/db/event.go
Normal 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)
|
||||
}
|
||||
23
pkg/db/schema/devops/V0_1__init.sql
Normal file
23
pkg/db/schema/devops/V0_1__init.sql
Normal 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`)
|
||||
);
|
||||
|
||||
8
pkg/db/schema/devops/V0_2__credential.sql
Normal file
8
pkg/db/schema/devops/V0_2__credential.sql
Normal 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`)
|
||||
);
|
||||
15
pkg/db/schema/devops/V0_3__workspace.sql
Normal file
15
pkg/db/schema/devops/V0_3__workspace.sql
Normal 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
17
pkg/db/scripts/ddl_init.sh
Executable 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
24
pkg/gojenkins/README.md
Normal 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
|
||||
4
pkg/gojenkins/_tests/build_history.txt
Normal file
4
pkg/gojenkins/_tests/build_history.txt
Normal 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 > Console Output" tooltip="Success > 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 > Console Output" tooltip="Success > 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 > Console Output" tooltip="Success > 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>
|
||||
27
pkg/gojenkins/_tests/job.xml
Normal file
27
pkg/gojenkins/_tests/job.xml
Normal 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
118
pkg/gojenkins/artifact.go
Normal 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
480
pkg/gojenkins/build.go
Normal 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
|
||||
}
|
||||
109
pkg/gojenkins/build_history.go
Normal file
109
pkg/gojenkins/build_history.go
Normal 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 > Console Output" tooltip="Failed > 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
|
||||
}
|
||||
20
pkg/gojenkins/constants.go
Normal file
20
pkg/gojenkins/constants.go
Normal 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
225
pkg/gojenkins/credential.go
Normal 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
44
pkg/gojenkins/executor.go
Normal 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"`
|
||||
}
|
||||
95
pkg/gojenkins/fingerprint.go
Normal file
95
pkg/gojenkins/fingerprint.go
Normal 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
77
pkg/gojenkins/folder.go
Normal 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
1067
pkg/gojenkins/jenkins.go
Normal file
File diff suppressed because it is too large
Load Diff
529
pkg/gojenkins/job.go
Normal file
529
pkg/gojenkins/job.go
Normal 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
62
pkg/gojenkins/label.go
Normal 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
230
pkg/gojenkins/node.go
Normal 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
|
||||
}
|
||||
162
pkg/gojenkins/pipeline_model_converter.go
Normal file
162
pkg/gojenkins/pipeline_model_converter.go
Normal 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
75
pkg/gojenkins/plugin.go
Normal 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
158
pkg/gojenkins/queue.go
Normal 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
468
pkg/gojenkins/request.go
Normal 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(¶ms)
|
||||
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(¶ms)
|
||||
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
204
pkg/gojenkins/role.go
Normal 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
61
pkg/gojenkins/utils.go
Normal 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))
|
||||
}
|
||||
21
pkg/gojenkins/utils/utils.go
Normal file
21
pkg/gojenkins/utils/utils.go
Normal 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
94
pkg/gojenkins/views.go
Normal 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
|
||||
}
|
||||
61
pkg/models/devops/common.go
Normal file
61
pkg/models/devops/common.go
Normal 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"
|
||||
)
|
||||
28
pkg/models/devops/membership.go
Normal file
28
pkg/models/devops/membership.go
Normal 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,
|
||||
}
|
||||
}
|
||||
45
pkg/models/devops/project.go
Normal file
45
pkg/models/devops/project.go
Normal 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,
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
63
pkg/simple/client/admin_jenkins/jenkins.go
Normal file
63
pkg/simple/client/admin_jenkins/jenkins.go
Normal 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
|
||||
|
||||
}
|
||||
56
pkg/simple/client/devops_mysql/mysql.go
Normal file
56
pkg/simple/client/devops_mysql/mysql.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
}
|
||||
49
pkg/simple/client/sonarqube/sonar.go
Normal file
49
pkg/simple/client/sonarqube/sonar.go
Normal 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
|
||||
}
|
||||
94
pkg/utils/idutils/id_utils.go
Normal file
94
pkg/utils/idutils/id_utils.go
Normal 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
|
||||
}
|
||||
24
pkg/utils/idutils/id_utils_test.go
Normal file
24
pkg/utils/idutils/id_utils_test.go
Normal 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)
|
||||
}
|
||||
35
pkg/utils/reflectutils/reflect.go
Normal file
35
pkg/utils/reflectutils/reflect.go
Normal 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
|
||||
}
|
||||
77
pkg/utils/stringutils/string.go
Normal file
77
pkg/utils/stringutils/string.go
Normal 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)
|
||||
}
|
||||
Reference in New Issue
Block a user