mirror of
https://github.com/semaphoreui/semaphore.git
synced 2025-01-21 07:49:34 +01:00
Merge pull request #524 from twhiston/api_tests
use dredd for api testing
This commit is contained in:
commit
9a20b17320
@ -7,7 +7,7 @@ aliases:
|
|||||||
image: circleci/golang:1.10
|
image: circleci/golang:1.10
|
||||||
|
|
||||||
- &working-dir
|
- &working-dir
|
||||||
/go/src/github.com/ansible-semaphore/semaphore
|
/go/src/github.com/ansible-semaphore/semaphore
|
||||||
|
|
||||||
- &store-bin-artifacts
|
- &store-bin-artifacts
|
||||||
store_artifacts:
|
store_artifacts:
|
||||||
@ -21,7 +21,7 @@ aliases:
|
|||||||
curl -L https://github.com/go-task/task/releases/download/v2.0.1/task_linux_amd64.tar.gz | tar xvz
|
curl -L https://github.com/go-task/task/releases/download/v2.0.1/task_linux_amd64.tar.gz | tar xvz
|
||||||
cd -
|
cd -
|
||||||
|
|
||||||
- &persist-bin
|
- &persist-from-build
|
||||||
persist_to_workspace:
|
persist_to_workspace:
|
||||||
root: .
|
root: .
|
||||||
paths:
|
paths:
|
||||||
@ -79,12 +79,12 @@ aliases:
|
|||||||
- v1-go-deps-
|
- v1-go-deps-
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
build:local:
|
build:local:
|
||||||
docker:
|
docker:
|
||||||
- *golang-image
|
- *golang-image
|
||||||
working_directory: *working-dir
|
working_directory: *working-dir
|
||||||
steps:
|
steps:
|
||||||
- run: export
|
|
||||||
- *install-node
|
- *install-node
|
||||||
- *install-task-binary
|
- *install-task-binary
|
||||||
- checkout
|
- checkout
|
||||||
@ -97,7 +97,7 @@ jobs:
|
|||||||
- *test-compile-changes
|
- *test-compile-changes
|
||||||
- run: task build:local
|
- run: task build:local
|
||||||
- *store-bin-artifacts
|
- *store-bin-artifacts
|
||||||
- *persist-bin
|
- *persist-from-build
|
||||||
|
|
||||||
build:
|
build:
|
||||||
docker:
|
docker:
|
||||||
@ -117,6 +117,22 @@ jobs:
|
|||||||
- run: task build
|
- run: task build
|
||||||
- *store-bin-artifacts
|
- *store-bin-artifacts
|
||||||
|
|
||||||
|
test:integration:hooks:
|
||||||
|
docker:
|
||||||
|
- *golang-image
|
||||||
|
working_directory: *working-dir
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- *install-node
|
||||||
|
- *install-task-binary
|
||||||
|
- run: task deps:integration
|
||||||
|
- run: task deps:tools
|
||||||
|
- run: task deps:be
|
||||||
|
- run: task compile:be
|
||||||
|
- run: task compile:api:hooks
|
||||||
|
- store_artifacts:
|
||||||
|
path: /go/src/github.com/ansible-semaphore/semaphore/.dredd/compiled_hooks
|
||||||
|
|
||||||
# Run goverage and post results
|
# Run goverage and post results
|
||||||
test:golang:
|
test:golang:
|
||||||
docker:
|
docker:
|
||||||
@ -140,11 +156,23 @@ jobs:
|
|||||||
path: /go/src/github.com/ansible-semaphore/semaphore/coverage.out
|
path: /go/src/github.com/ansible-semaphore/semaphore/coverage.out
|
||||||
|
|
||||||
test:integration:
|
test:integration:
|
||||||
|
machine: true
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- run: |
|
||||||
|
cd /home/circleci/bin
|
||||||
|
sudo curl -L https://github.com/go-task/task/releases/download/v2.0.1/task_linux_amd64.tar.gz | tar xvz
|
||||||
|
cd -
|
||||||
|
- run: context=ci task dc:up
|
||||||
|
|
||||||
|
test:db:migration:
|
||||||
docker:
|
docker:
|
||||||
- *golang-image
|
- *golang-image
|
||||||
- image: circleci/mysql
|
- image: circleci/mysql
|
||||||
working_directory: *working-dir
|
working_directory: *working-dir
|
||||||
steps:
|
steps:
|
||||||
|
- *install-task-binary
|
||||||
|
- *install-node
|
||||||
- attach_workspace:
|
- attach_workspace:
|
||||||
at: *working-dir
|
at: *working-dir
|
||||||
# This looks like utter filth in circleci v2 but we have no choice apart from this escaping madness
|
# This looks like utter filth in circleci v2 but we have no choice apart from this escaping madness
|
||||||
@ -155,7 +183,6 @@ jobs:
|
|||||||
name: Wait for db
|
name: Wait for db
|
||||||
command: dockerize -wait tcp://127.0.0.1:3306 -timeout 1m
|
command: dockerize -wait tcp://127.0.0.1:3306 -timeout 1m
|
||||||
- run: bin/semaphore --migrate -config config.json
|
- run: bin/semaphore --migrate -config config.json
|
||||||
# TODO - Here we could do some api/functional testing
|
|
||||||
|
|
||||||
test:docker:
|
test:docker:
|
||||||
docker:
|
docker:
|
||||||
@ -221,8 +248,10 @@ workflows:
|
|||||||
jobs:
|
jobs:
|
||||||
- test:docker
|
- test:docker
|
||||||
- test:golang
|
- test:golang
|
||||||
|
- test:integration:hooks
|
||||||
|
- test:integration
|
||||||
- build:local
|
- build:local
|
||||||
- test:integration:
|
- test:db:migration:
|
||||||
requires:
|
requires:
|
||||||
- build:local
|
- build:local
|
||||||
|
|
||||||
@ -230,6 +259,7 @@ workflows:
|
|||||||
- build:
|
- build:
|
||||||
requires:
|
requires:
|
||||||
- test:golang
|
- test:golang
|
||||||
|
- test:db:migration
|
||||||
- test:integration
|
- test:integration
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
@ -244,12 +274,11 @@ workflows:
|
|||||||
branches:
|
branches:
|
||||||
only: develop
|
only: develop
|
||||||
|
|
||||||
# Production releases only run on tags
|
# Production deploys only happen if everything passes
|
||||||
# use _ in tags and not - due to rpmbuild not allowing hyphens in version numbers
|
# and we have a tag starting with v
|
||||||
# https://github.com/semver/semver/issues/145
|
|
||||||
release:
|
|
||||||
jobs:
|
|
||||||
- release:
|
- release:
|
||||||
|
requires:
|
||||||
|
- build
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
ignore: /.*/
|
ignore: /.*/
|
||||||
@ -257,9 +286,11 @@ workflows:
|
|||||||
only: /^v.*/
|
only: /^v.*/
|
||||||
|
|
||||||
- deploy:prod:
|
- deploy:prod:
|
||||||
|
requires:
|
||||||
|
- build
|
||||||
|
- test:docker
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
ignore: /.*/
|
ignore: /.*/
|
||||||
tags:
|
tags:
|
||||||
only: /^v.*/
|
only: /^v.*/
|
||||||
|
|
34
.dredd/dredd.local.yml
Normal file
34
.dredd/dredd.local.yml
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
dry-run: null
|
||||||
|
hookfiles: ./.dredd/compiled_hooks
|
||||||
|
language: go
|
||||||
|
sandbox: false
|
||||||
|
#server: context=dev task dc:up
|
||||||
|
server-wait: 240
|
||||||
|
init: false
|
||||||
|
custom: {}
|
||||||
|
names: false
|
||||||
|
only: []
|
||||||
|
reporter: []
|
||||||
|
output: []
|
||||||
|
header: "Authorization: bearer h4a_i4qslpnxyyref71rk5nqbwxccrs7enwvggx0vfs="
|
||||||
|
sorted: false
|
||||||
|
user: null
|
||||||
|
inline-errors: false
|
||||||
|
details: false
|
||||||
|
method: []
|
||||||
|
color: true
|
||||||
|
level: info
|
||||||
|
timestamp: false
|
||||||
|
silent: false
|
||||||
|
path: []
|
||||||
|
hooks-worker-timeout: 5000
|
||||||
|
hooks-worker-connect-timeout: 1500
|
||||||
|
hooks-worker-connect-retry: 500
|
||||||
|
hooks-worker-after-connect-wait: 100
|
||||||
|
hooks-worker-term-timeout: 5000
|
||||||
|
hooks-worker-term-retry: 500
|
||||||
|
hooks-worker-handler-host: 0.0.0.0
|
||||||
|
hooks-worker-handler-port: 61321
|
||||||
|
config: ./.dredd/dredd.yml
|
||||||
|
blueprint: api-docs.yml
|
||||||
|
endpoint: 'http://0.0.0.0:3000'
|
34
.dredd/dredd.yml
Normal file
34
.dredd/dredd.yml
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
dry-run: null
|
||||||
|
hookfiles: ./.dredd/compiled_hooks
|
||||||
|
language: go
|
||||||
|
sandbox: false
|
||||||
|
#server: context=dev task dc:up
|
||||||
|
server-wait: 240
|
||||||
|
init: false
|
||||||
|
custom: {}
|
||||||
|
names: false
|
||||||
|
only: []
|
||||||
|
reporter: []
|
||||||
|
output: []
|
||||||
|
header: "Authorization: bearer h4a_i4qslpnxyyref71rk5nqbwxccrs7enwvggx0vfs="
|
||||||
|
sorted: false
|
||||||
|
user: null
|
||||||
|
inline-errors: false
|
||||||
|
details: false
|
||||||
|
method: []
|
||||||
|
color: true
|
||||||
|
level: info
|
||||||
|
timestamp: false
|
||||||
|
silent: false
|
||||||
|
path: []
|
||||||
|
hooks-worker-timeout: 5000
|
||||||
|
hooks-worker-connect-timeout: 1500
|
||||||
|
hooks-worker-connect-retry: 500
|
||||||
|
hooks-worker-after-connect-wait: 100
|
||||||
|
hooks-worker-term-timeout: 5000
|
||||||
|
hooks-worker-term-retry: 500
|
||||||
|
hooks-worker-handler-host: 0.0.0.0
|
||||||
|
hooks-worker-handler-port: 61321
|
||||||
|
config: ./.dredd/dredd.yml
|
||||||
|
blueprint: api-docs.yml
|
||||||
|
endpoint: 'http://semaphore_ci:3000'
|
159
.dredd/hooks/capabilities.go
Normal file
159
.dredd/hooks/capabilities.go
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/ansible-semaphore/semaphore/db"
|
||||||
|
trans "github.com/snikch/goodman/transaction"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// STATE
|
||||||
|
// Runtime created objects we needs to reference in test setups
|
||||||
|
var testRunnerUser *db.User
|
||||||
|
var userPathTestUser *db.User
|
||||||
|
var userProject *db.Project
|
||||||
|
var userKey *db.AccessKey
|
||||||
|
var task *db.Task
|
||||||
|
|
||||||
|
// Runtime created simple ID values for some items we need to reference in other objects
|
||||||
|
var repoID int64
|
||||||
|
var inventoryID int64
|
||||||
|
var environmentID int64
|
||||||
|
var templateID int64
|
||||||
|
|
||||||
|
var capabilities = map[string][]string{
|
||||||
|
"user": {},
|
||||||
|
"project": {"user"},
|
||||||
|
"access_key": {"project"},
|
||||||
|
"repository": {"access_key"},
|
||||||
|
"inventory": {"repository"},
|
||||||
|
"environment": {"repository"},
|
||||||
|
"template": {"repository", "inventory", "environment"},
|
||||||
|
"task": {"template"},
|
||||||
|
}
|
||||||
|
|
||||||
|
func capabilityWrapper(cap string) func(t *trans.Transaction) {
|
||||||
|
return func(t *trans.Transaction) {
|
||||||
|
addCapabilities([]string{cap})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addCapabilities(caps []string) {
|
||||||
|
dbConnect()
|
||||||
|
defer db.Mysql.Db.Close()
|
||||||
|
resolved := make([]string, 0)
|
||||||
|
uid := getUUID()
|
||||||
|
resolveCapability(caps, resolved, uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveCapability(caps []string, resolved []string, uid string) {
|
||||||
|
for _, v := range caps {
|
||||||
|
|
||||||
|
//if cap has deps resolve them
|
||||||
|
if val, ok := capabilities[v]; ok {
|
||||||
|
resolveCapability(val, resolved, uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
//skip if already resolved
|
||||||
|
if _, exists := stringInSlice(v, resolved); exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add dep specific stuff
|
||||||
|
switch v {
|
||||||
|
case "user":
|
||||||
|
userPathTestUser = addUser()
|
||||||
|
case "project":
|
||||||
|
userProject = addProject()
|
||||||
|
//allow the admin user (test executor) to manipulate the project
|
||||||
|
addUserProjectRelation(userProject.ID, testRunnerUser.ID)
|
||||||
|
addUserProjectRelation(userProject.ID, userPathTestUser.ID)
|
||||||
|
case "access_key":
|
||||||
|
userKey = addAccessKey(&userProject.ID)
|
||||||
|
case "repository":
|
||||||
|
pRepo, err := db.Mysql.Exec("insert into project__repository set project_id=?, git_url=?, ssh_key_id=?, name=?", userProject.ID, "git@github.com/ansible,semaphore/semaphore", userKey.ID, "ITR-"+uid)
|
||||||
|
printError(err)
|
||||||
|
repoID, _ = pRepo.LastInsertId()
|
||||||
|
case "inventory":
|
||||||
|
res, err := db.Mysql.Exec("insert into project__inventory set project_id=?, name=?, type=?, key_id=?, ssh_key_id=?, inventory=?", userProject.ID, "ITI-"+uid, "static", userKey.ID, userKey.ID, "Test Inventory")
|
||||||
|
printError(err)
|
||||||
|
inventoryID, _ = res.LastInsertId()
|
||||||
|
case "environment":
|
||||||
|
res, err := db.Mysql.Exec("insert into project__environment set project_id=?, name=?, json=?, password=?", userProject.ID, "ITI-"+uid, "{}", "test-pass")
|
||||||
|
printError(err)
|
||||||
|
environmentID, _ = res.LastInsertId()
|
||||||
|
case "template":
|
||||||
|
res, err := db.Mysql.Exec("insert into project__template set ssh_key_id=?, project_id=?, inventory_id=?, repository_id=?, environment_id=?, alias=?, playbook=?, arguments=?, override_args=?", userKey.ID, userProject.ID, inventoryID, repoID, environmentID, "Test-"+uid, "test-playbook.yml", "", false)
|
||||||
|
printError(err)
|
||||||
|
templateID, _ = res.LastInsertId()
|
||||||
|
case "task":
|
||||||
|
task = addTask()
|
||||||
|
}
|
||||||
|
resolved = append(resolved, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HOOKS
|
||||||
|
var skipTest = func(t *trans.Transaction) {
|
||||||
|
t.Skip = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contains all the substitutions for paths under test
|
||||||
|
// The parameter example value in the api-doc should respond to the index+1 of the function in this slice
|
||||||
|
// ie the project id, with example value 1, will be replaced by the return value of pathSubPatterns[0]
|
||||||
|
var pathSubPatterns = []func() string{
|
||||||
|
func() string { return strconv.Itoa(userProject.ID) },
|
||||||
|
func() string { return strconv.Itoa(userPathTestUser.ID) },
|
||||||
|
func() string { return strconv.Itoa(userKey.ID) },
|
||||||
|
func() string { return strconv.Itoa(int(repoID)) },
|
||||||
|
func() string { return strconv.Itoa(int(inventoryID)) },
|
||||||
|
func() string { return strconv.Itoa(int(environmentID)) },
|
||||||
|
func() string { return strconv.Itoa(int(templateID)) },
|
||||||
|
func() string { return strconv.Itoa(task.ID) },
|
||||||
|
}
|
||||||
|
|
||||||
|
// alterRequestPath with the above slice of functions
|
||||||
|
func alterRequestPath(t *trans.Transaction) {
|
||||||
|
pathArgs := strings.Split(t.FullPath, "/")
|
||||||
|
exploded := make([]string, len(pathArgs))
|
||||||
|
copy(exploded, pathArgs)
|
||||||
|
for k, v := range pathSubPatterns {
|
||||||
|
pos, exists := stringInSlice(strconv.Itoa(k+1), exploded)
|
||||||
|
if exists {
|
||||||
|
pathArgs[pos] = v()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.FullPath = strings.Join(pathArgs, "/")
|
||||||
|
t.Request.URI = t.FullPath
|
||||||
|
}
|
||||||
|
|
||||||
|
func alterRequestBody(t *trans.Transaction) {
|
||||||
|
var request map[string]interface{}
|
||||||
|
json.Unmarshal([]byte(t.Request.Body), &request)
|
||||||
|
|
||||||
|
if userProject != nil {
|
||||||
|
bodyFieldProcessor("project_id", userProject.ID, &request)
|
||||||
|
}
|
||||||
|
bodyFieldProcessor("json", "{}", &request)
|
||||||
|
if userKey != nil {
|
||||||
|
bodyFieldProcessor("ssh_key_id", userKey.ID, &request)
|
||||||
|
bodyFieldProcessor("key_id", userKey.ID, &request)
|
||||||
|
}
|
||||||
|
bodyFieldProcessor("environment_id", environmentID, &request)
|
||||||
|
bodyFieldProcessor("inventory_id", inventoryID, &request)
|
||||||
|
bodyFieldProcessor("repository_id", repoID, &request)
|
||||||
|
bodyFieldProcessor("template_id", templateID, &request)
|
||||||
|
if task != nil {
|
||||||
|
bodyFieldProcessor("task_id", task.ID, &request)
|
||||||
|
}
|
||||||
|
|
||||||
|
out, _ := json.Marshal(request)
|
||||||
|
t.Request.Body = string(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
func bodyFieldProcessor(id string, sub interface{}, request *map[string]interface{}) {
|
||||||
|
if _, ok := (*request)[id]; ok {
|
||||||
|
(*request)[id] = sub
|
||||||
|
}
|
||||||
|
}
|
192
.dredd/hooks/helpers.go
Normal file
192
.dredd/hooks/helpers.go
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"github.com/ansible-semaphore/semaphore/db"
|
||||||
|
"github.com/ansible-semaphore/semaphore/util"
|
||||||
|
"github.com/snikch/goodman/transaction"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Test Runner User
|
||||||
|
func addTestRunnerUser() {
|
||||||
|
uid := getUUID()
|
||||||
|
testRunnerUser = &db.User{
|
||||||
|
Username: "ITU-" + uid,
|
||||||
|
Name: "ITU-" + uid,
|
||||||
|
Email: uid + "@semaphore.test",
|
||||||
|
Created: db.GetParsedTime(time.Now()),
|
||||||
|
Admin: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
dbConnect()
|
||||||
|
defer db.Mysql.Db.Close()
|
||||||
|
if err := db.Mysql.Insert(testRunnerUser); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
addToken(adminToken, testRunnerUser.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeTestRunnerUser(transactions []*transaction.Transaction) {
|
||||||
|
dbConnect()
|
||||||
|
defer db.Mysql.Db.Close()
|
||||||
|
deleteToken(adminToken, testRunnerUser.ID)
|
||||||
|
deleteObject(testRunnerUser)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parameter Substitution
|
||||||
|
func setupObjectsAndPaths(t *transaction.Transaction) {
|
||||||
|
alterRequestBody(t)
|
||||||
|
alterRequestPath(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Object Lifecycle
|
||||||
|
func addUserProjectRelation(pid int, user int) {
|
||||||
|
_, err := db.Mysql.Exec("insert into project__user set project_id=?, user_id=?, `admin`=1", pid, user)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func deleteUserProjectRelation(pid int, user int) {
|
||||||
|
_, err := db.Mysql.Exec("delete from project__user where project_id=? and user_id=?", strconv.Itoa(pid), strconv.Itoa(user))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addAccessKey(pid *int) *db.AccessKey {
|
||||||
|
uid := getUUID()
|
||||||
|
secret := "5up3r53cr3t"
|
||||||
|
key := db.AccessKey{
|
||||||
|
Name: "ITK-" + uid,
|
||||||
|
Type: "ssh",
|
||||||
|
Secret: &secret,
|
||||||
|
ProjectID: pid,
|
||||||
|
}
|
||||||
|
if err := db.Mysql.Insert(&key); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
return &key
|
||||||
|
}
|
||||||
|
|
||||||
|
func addProject() *db.Project {
|
||||||
|
uid := getUUID()
|
||||||
|
project := db.Project{
|
||||||
|
Name: "ITP-" + uid,
|
||||||
|
Created: time.Now(),
|
||||||
|
}
|
||||||
|
if err := db.Mysql.Insert(&project); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
return &project
|
||||||
|
}
|
||||||
|
|
||||||
|
func addUser() *db.User {
|
||||||
|
uid := getUUID()
|
||||||
|
user := db.User{
|
||||||
|
Created: time.Now(),
|
||||||
|
Username: "ITU-" + uid,
|
||||||
|
Email: "test@semaphore." + uid,
|
||||||
|
}
|
||||||
|
if err := db.Mysql.Insert(&user); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
return &user
|
||||||
|
}
|
||||||
|
|
||||||
|
func addTask() *db.Task {
|
||||||
|
t := db.Task{
|
||||||
|
TemplateID: int(templateID),
|
||||||
|
Status: "testing",
|
||||||
|
UserID: &userPathTestUser.ID,
|
||||||
|
Created: db.GetParsedTime(time.Now()),
|
||||||
|
}
|
||||||
|
if err := db.Mysql.Insert(&t); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
return &t
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteObject(i interface{}) {
|
||||||
|
_, err := db.Mysql.Delete(i)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token Handling
|
||||||
|
func addToken(tok string, user int) {
|
||||||
|
token := db.APIToken{
|
||||||
|
ID: tok,
|
||||||
|
Created: time.Now(),
|
||||||
|
UserID: user,
|
||||||
|
Expired: false,
|
||||||
|
}
|
||||||
|
if err := db.Mysql.Insert(&token); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteToken(tok string, user int) {
|
||||||
|
token := db.APIToken{
|
||||||
|
ID: tok,
|
||||||
|
UserID: user,
|
||||||
|
}
|
||||||
|
deleteObject(&token)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HELPERS
|
||||||
|
var r *rand.Rand
|
||||||
|
var randSetup = false
|
||||||
|
|
||||||
|
func getUUID() string {
|
||||||
|
if !randSetup {
|
||||||
|
r = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
randSetup = true
|
||||||
|
}
|
||||||
|
return randomString(8)
|
||||||
|
}
|
||||||
|
func randomString(strlen int) string {
|
||||||
|
const chars = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||||
|
result := ""
|
||||||
|
for i := 0; i < strlen; i++ {
|
||||||
|
index := r.Intn(len(chars))
|
||||||
|
result += chars[index : index+1]
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadConfig() {
|
||||||
|
cwd, _ := os.Getwd()
|
||||||
|
file, _ := os.Open(cwd + "/.dredd/config.json")
|
||||||
|
if err := json.NewDecoder(file).Decode(&util.Config); err != nil {
|
||||||
|
fmt.Println("Could not decode configuration!")
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func dbConnect() {
|
||||||
|
if err := db.Connect(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
db.SetupDBLink()
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringInSlice(a string, list []string) (int, bool) {
|
||||||
|
for k, b := range list {
|
||||||
|
if b == a {
|
||||||
|
return k, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func printError(err error) {
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
}
|
120
.dredd/hooks/main.go
Normal file
120
.dredd/hooks/main.go
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ansible-semaphore/semaphore/db"
|
||||||
|
"github.com/snikch/goodman/hooks"
|
||||||
|
trans "github.com/snikch/goodman/transaction"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
adminToken = "h4a_i4qslpnxyyref71rk5nqbwxccrs7enwvggx0vfs="
|
||||||
|
expiredToken = "kwofd61g93-yuqvex8efmhjkgnbxlo8mp1tin6spyhu="
|
||||||
|
)
|
||||||
|
|
||||||
|
var skipTests = []string{
|
||||||
|
// TODO - dredd seems not to like the text response from this endpoint
|
||||||
|
"/api/ping > PING test > 200 > text/plain; charset=utf-8",
|
||||||
|
"/api/ws > Websocket handler > 200 > application/json",
|
||||||
|
"authentication > /api/auth/login > Performs Login > 204 > application/json",
|
||||||
|
"authentication > /api/auth/logout > Destroys current session > 204 > application/json",
|
||||||
|
"/api/upgrade > Upgrade the server > 200 > application/json",
|
||||||
|
// TODO - Skipping this while we work out how to get a 204 response from the api for testing
|
||||||
|
"/api/upgrade > Check if new updates available and fetch /info > 204 > application/json",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dredd expects that you have already set up the database and run all migrations before it begins.
|
||||||
|
// It will NOT initialize the database, only insert its test data.
|
||||||
|
// It does this in a way which ignores errors, which is fine on the ci, but might be an issue locally
|
||||||
|
// so look at the logs carefully if these tests fail and if in doubt re-init the db
|
||||||
|
// These hooks do NOT clean up after themselves and they produce a lot of database writes,
|
||||||
|
// so don't run this in production
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
h := hooks.NewHooks()
|
||||||
|
server := hooks.NewServer(hooks.NewHooksRunner(h))
|
||||||
|
|
||||||
|
//Get database connection info and create an admin who's token is used to execute the tests
|
||||||
|
h.BeforeAll(func(t []*trans.Transaction) {
|
||||||
|
loadConfig()
|
||||||
|
addTestRunnerUser()
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, v := range skipTests {
|
||||||
|
h.Before(v, skipTest)
|
||||||
|
}
|
||||||
|
|
||||||
|
h.BeforeEach(func(t *trans.Transaction) {
|
||||||
|
if strings.HasPrefix(t.Name, "user") {
|
||||||
|
addCapabilities([]string{"user"})
|
||||||
|
} else if strings.HasPrefix(t.Name, "project") || strings.HasPrefix(t.Name, "projects") {
|
||||||
|
addCapabilities([]string{"project"})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
h.Before("user > /api/user/tokens/{api_token_id} > Expires API token > 204 > application/json", func(transaction *trans.Transaction) {
|
||||||
|
dbConnect()
|
||||||
|
defer db.Mysql.Db.Close()
|
||||||
|
addToken(expiredToken, testRunnerUser.ID)
|
||||||
|
})
|
||||||
|
h.After("user > /api/user/tokens/{api_token_id} > Expires API token > 204 > application/json", func(transaction *trans.Transaction) {
|
||||||
|
dbConnect()
|
||||||
|
defer db.Mysql.Db.Close()
|
||||||
|
//tokens are expired and not deleted so we need to clean up
|
||||||
|
deleteToken(expiredToken, testRunnerUser.ID)
|
||||||
|
})
|
||||||
|
|
||||||
|
// This one seems to need some manual value setting in the body
|
||||||
|
h.Before("user > /api/users/{user_id}/password > Updates user password > 204 > application/json", func(transaction *trans.Transaction) {
|
||||||
|
transaction.Request.Body = "{\"password\":\"staub\"}"
|
||||||
|
})
|
||||||
|
|
||||||
|
// delete the auto generated association and insert the user id into the query
|
||||||
|
h.Before("project > /api/project/{project_id}/users > Link user to project > 204 > application/json", func(transaction *trans.Transaction) {
|
||||||
|
dbConnect()
|
||||||
|
defer db.Mysql.Db.Close()
|
||||||
|
deleteUserProjectRelation(userProject.ID, userPathTestUser.ID)
|
||||||
|
transaction.Request.Body = "{ \"user_id\": " + strconv.Itoa(userPathTestUser.ID) + ",\"admin\": true}"
|
||||||
|
})
|
||||||
|
|
||||||
|
h.Before("project > /api/project/{project_id}/keys/{key_id} > Updates access key > 204 > application/json", capabilityWrapper("access_key"))
|
||||||
|
h.Before("project > /api/project/{project_id}/keys/{key_id} > Removes access key > 204 > application/json", capabilityWrapper("access_key"))
|
||||||
|
|
||||||
|
h.Before("project > /api/project/{project_id}/repositories > Add repository > 204 > application/json", capabilityWrapper("access_key"))
|
||||||
|
h.Before("project > /api/project/{project_id}/repositories/{repository_id} > Removes repository > 204 > application/json", capabilityWrapper("repository"))
|
||||||
|
|
||||||
|
h.Before("project > /api/project/{project_id}/inventory > create inventory > 201 > application/json", capabilityWrapper("inventory"))
|
||||||
|
h.Before("project > /api/project/{project_id}/inventory/{inventory_id} > Updates inventory > 204 > application/json", capabilityWrapper("inventory"))
|
||||||
|
h.Before("project > /api/project/{project_id}/inventory/{inventory_id} > Removes inventory > 204 > application/json", capabilityWrapper("inventory"))
|
||||||
|
|
||||||
|
h.Before("project > /api/project/{project_id}/environment/{environment_id} > Update environment > 204 > application/json", capabilityWrapper("environment"))
|
||||||
|
h.Before("project > /api/project/{project_id}/environment/{environment_id} > Removes environment > 204 > application/json", capabilityWrapper("environment"))
|
||||||
|
|
||||||
|
h.Before("project > /api/project/{project_id}/templates > create template > 201 > application/json", func(t *trans.Transaction) {
|
||||||
|
addCapabilities([]string{"repository", "inventory", "environment"})
|
||||||
|
})
|
||||||
|
|
||||||
|
h.Before("project > /api/project/{project_id}/templates/{template_id} > Updates template > 204 > application/json", capabilityWrapper("template"))
|
||||||
|
h.Before("project > /api/project/{project_id}/templates/{template_id} > Removes template > 204 > application/json", capabilityWrapper("template"))
|
||||||
|
|
||||||
|
h.Before("project > /api/project/{project_id}/tasks > Starts a job > 201 > application/json", capabilityWrapper("template"))
|
||||||
|
h.Before("project > /api/project/{project_id}/tasks/last > Get last 200 Tasks related to current project > 200 > application/json", capabilityWrapper("template"))
|
||||||
|
|
||||||
|
h.Before("project > /api/project/{project_id}/tasks/{task_id} > Get a single task > 200 > application/json", capabilityWrapper("task"))
|
||||||
|
h.Before("project > /api/project/{project_id}/tasks/{task_id} > Deletes task (including output) > 204 > application/json", capabilityWrapper("task"))
|
||||||
|
h.Before("project > /api/project/{project_id}/tasks/{task_id}/output > Get task output > 200 > application/json", capabilityWrapper("task"))
|
||||||
|
|
||||||
|
//Add these last as they normalize the requests and path values after hook processing
|
||||||
|
h.BeforeAll(func(transactions []*trans.Transaction) {
|
||||||
|
for _, t := range transactions {
|
||||||
|
h.Before(t.Name, setupObjectsAndPaths)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Delete the test runner user so adding him next time does not result in errors
|
||||||
|
h.AfterAll(removeTestRunnerUser)
|
||||||
|
|
||||||
|
server.Serve()
|
||||||
|
defer server.Listener.Close()
|
||||||
|
}
|
@ -6,6 +6,7 @@ When creating a pull-request you should:
|
|||||||
- __gofmt and vet the code:__ Use `gofmt`, `golint`, `govet` and `goimports` to clean up your code.
|
- __gofmt and vet the code:__ Use `gofmt`, `golint`, `govet` and `goimports` to clean up your code.
|
||||||
- __vendor dependencies with dep:__ Use `dep ensure --update` if you have added to or updated dependencies, so that they get added to the dependency manifest.
|
- __vendor dependencies with dep:__ Use `dep ensure --update` if you have added to or updated dependencies, so that they get added to the dependency manifest.
|
||||||
- __Update api documentation:__ If your pull-request adding/modifying an API request, make sure you update the swagger documentation (`api-docs.yml`)
|
- __Update api documentation:__ If your pull-request adding/modifying an API request, make sure you update the swagger documentation (`api-docs.yml`)
|
||||||
|
- __Run Api Tests:__ If your pull request modifies the API make sure you run the integration tests using dredd.
|
||||||
|
|
||||||
# Installation in a development environment
|
# Installation in a development environment
|
||||||
|
|
||||||
@ -70,3 +71,35 @@ Now it's ready to start.. Run `task watch`
|
|||||||
|
|
||||||
Note: for Windows, you may need [Cygwin](https://www.cygwin.com/) to run certain commands because the [reflex](github.com/cespare/reflex) package probably doesn't work on Windows.
|
Note: for Windows, you may need [Cygwin](https://www.cygwin.com/) to run certain commands because the [reflex](github.com/cespare/reflex) package probably doesn't work on Windows.
|
||||||
You may encounter issues when running `task watch`, but running `task build` etc... will still be OK.
|
You may encounter issues when running `task watch`, but running `task build` etc... will still be OK.
|
||||||
|
|
||||||
|
## Integration Tests
|
||||||
|
|
||||||
|
Dredd is used for API integration tests, if you alter the API in any way you must make sure that the information in the api docs
|
||||||
|
matches the responses.
|
||||||
|
|
||||||
|
As Dredd and the application database config may differ it expects it's own config.json in the .dredd folder.
|
||||||
|
The most basic configuration for this using a local docker container to run the database would be
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mysql": {
|
||||||
|
"host": "0.0.0.0:3306",
|
||||||
|
"user": "semaphore",
|
||||||
|
"pass": "semaphore",
|
||||||
|
"name": "semaphore"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
It is strongly advised to run these tests inside docker containers, as dredd will write a lot of test information and will __NOT__ clear it up.
|
||||||
|
This means that you should never run these tests against your productive database!
|
||||||
|
The best practice to run these tests is to use docker and the task commands.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
task dc:build #First run only to build the images
|
||||||
|
context=dev task dc:up
|
||||||
|
task test:api
|
||||||
|
# alternatively if you want to run dredd in a container use the following command.
|
||||||
|
# You will need to use the host network so that it can reach the docker container on a 0.0.0.0 address
|
||||||
|
# docker run -it --rm -v $(pwd):/home/developer/src --network host tomwhiston/dredd --config .dredd/dredd.yml
|
||||||
|
```
|
142
Gopkg.lock
generated
142
Gopkg.lock
generated
@ -1,18 +1,96 @@
|
|||||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||||
|
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/PuerkitoBio/purell"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "0bcb03f4b4d0a9428594752bd2a3b9aa0a9d4bd4"
|
||||||
|
version = "v1.1.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/PuerkitoBio/urlesc"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "de5bf2ad457846296e2031421a34e2568e304e35"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/Sirupsen/logrus"
|
name = "github.com/Sirupsen/logrus"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "d682213848ed68c0a260ca37d6dd5ace8423f5ba"
|
revision = "d682213848ed68c0a260ca37d6dd5ace8423f5ba"
|
||||||
version = "v1.0.4"
|
version = "v1.0.4"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/asaskevich/govalidator"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "ccb8e960c48f04d6935e72476ae4a51028f9e22f"
|
||||||
|
version = "v9"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/castawaylabs/mulekick"
|
name = "github.com/castawaylabs/mulekick"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "7029fb389811e0f873c56cfbbda64d66af48b095"
|
revision = "7029fb389811e0f873c56cfbbda64d66af48b095"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/go-openapi/analysis"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "f59a71f0ece6f9dfb438be7f45148f006cbad88e"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/go-openapi/errors"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "7bcb96a367bac6b76e6e42fa84155bb5581dcff8"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/go-openapi/jsonpointer"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "3a0015ad55fa9873f41605d3e8f28cd279c32ab2"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/go-openapi/jsonreference"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "3fb327e6747da3043567ee86abd02bb6376b6be2"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/go-openapi/loads"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "2a2b323bab96e6b1fdee110e57d959322446e9c9"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/go-openapi/runtime"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "62281b694b396a17fe3e4313ee8b0ca2c3cca719"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/go-openapi/spec"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "370d9e047557906322be8396e77cb0376be6cb96"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/go-openapi/strfmt"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "481808443b00a14745fada967cb5eeff0f9b1df2"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/go-openapi/swag"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "811b1089cde9dad18d4d0c2d09fbdbf28dbd27a5"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/go-openapi/validate"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "180bba53b98899f743a112e568bed9e2ef31aa20"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/go-sql-driver/mysql"
|
name = "github.com/go-sql-driver/mysql"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
@ -79,12 +157,28 @@
|
|||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "62de8c46ede02a7675c4c79c84883eb164cb71e3"
|
revision = "62de8c46ede02a7675c4c79c84883eb164cb71e3"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/mailru/easyjson"
|
||||||
|
packages = [
|
||||||
|
"buffer",
|
||||||
|
"jlexer",
|
||||||
|
"jwriter"
|
||||||
|
]
|
||||||
|
revision = "8b799c424f57fa123fc63a99d6383bc6e4c02578"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/masterminds/squirrel"
|
name = "github.com/masterminds/squirrel"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "a6b93000bd219143c56c16e6cb1c4b91da3f224b"
|
revision = "a6b93000bd219143c56c16e6cb1c4b91da3f224b"
|
||||||
version = "v1.0"
|
version = "v1.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "github.com/mitchellh/mapstructure"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "00c29f56e2386353d58c599509e8dc3801b0d716"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/pkg/errors"
|
name = "github.com/pkg/errors"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
@ -107,6 +201,15 @@
|
|||||||
]
|
]
|
||||||
revision = "c7dcf104e3a7a1417abc0230cb0d5240d764159d"
|
revision = "c7dcf104e3a7a1417abc0230cb0d5240d764159d"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "master"
|
||||||
|
name = "golang.org/x/net"
|
||||||
|
packages = [
|
||||||
|
"context",
|
||||||
|
"idna"
|
||||||
|
]
|
||||||
|
revision = "61147c48b25b599e5b561d2e9c4f3e1ef489ca41"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "golang.org/x/sys"
|
name = "golang.org/x/sys"
|
||||||
@ -116,6 +219,28 @@
|
|||||||
]
|
]
|
||||||
revision = "7dca6fe1f43775aa6d1334576870ff63f978f539"
|
revision = "7dca6fe1f43775aa6d1334576870ff63f978f539"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "golang.org/x/text"
|
||||||
|
packages = [
|
||||||
|
"collate",
|
||||||
|
"collate/build",
|
||||||
|
"internal/colltab",
|
||||||
|
"internal/gen",
|
||||||
|
"internal/tag",
|
||||||
|
"internal/triegen",
|
||||||
|
"internal/ucd",
|
||||||
|
"language",
|
||||||
|
"secure/bidirule",
|
||||||
|
"transform",
|
||||||
|
"unicode/bidi",
|
||||||
|
"unicode/cldr",
|
||||||
|
"unicode/norm",
|
||||||
|
"unicode/rangetable",
|
||||||
|
"width"
|
||||||
|
]
|
||||||
|
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
|
||||||
|
version = "v0.3.0"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "gopkg.in/asn1-ber.v1"
|
name = "gopkg.in/asn1-ber.v1"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
@ -134,9 +259,24 @@
|
|||||||
revision = "bb7a9ca6e4fbc2129e3db588a34bc970ffe811a9"
|
revision = "bb7a9ca6e4fbc2129e3db588a34bc970ffe811a9"
|
||||||
version = "v2.5.1"
|
version = "v2.5.1"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
branch = "v2"
|
||||||
|
name = "gopkg.in/mgo.v2"
|
||||||
|
packages = [
|
||||||
|
"bson",
|
||||||
|
"internal/json"
|
||||||
|
]
|
||||||
|
revision = "3f83fa5005286a7fe593b055f0d7771a7dce4655"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "gopkg.in/yaml.v2"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
|
||||||
|
version = "v2.2.1"
|
||||||
|
|
||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "fd98c9632d4a76491d66eb42e4dd5e6440b36405c061136afe7eb92196055d0f"
|
inputs-digest = "c07a879b1ce764530cc0e0a4f80e207f8a6f7a8f1bd3715c35fa6cd366822180"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
27
Taskfile.yml
27
Taskfile.yml
@ -19,7 +19,7 @@ tasks:
|
|||||||
- task: build:local
|
- task: build:local
|
||||||
|
|
||||||
deps:
|
deps:
|
||||||
desc: Install all dependencies
|
desc: Install all dependencies (except dredd requirements)
|
||||||
cmds:
|
cmds:
|
||||||
- task: deps:tools
|
- task: deps:tools
|
||||||
- task: deps:be
|
- task: deps:be
|
||||||
@ -36,16 +36,23 @@ tasks:
|
|||||||
cmds:
|
cmds:
|
||||||
- npm install
|
- npm install
|
||||||
|
|
||||||
|
deps:integration:
|
||||||
|
desc: Installs requirements for integration testing with dredd
|
||||||
|
dir: web
|
||||||
|
cmds:
|
||||||
|
- npm install dredd@5.1.5
|
||||||
|
|
||||||
deps:tools:
|
deps:tools:
|
||||||
desc: Installs tools needed
|
desc: Installs tools needed
|
||||||
dir: web
|
dir: web
|
||||||
vars:
|
vars:
|
||||||
GORELEASER_VERSION: "0.66.1"
|
GORELEASER_VERSION: "0.67.0"
|
||||||
cmds:
|
cmds:
|
||||||
- go get -u github.com/golang/dep/cmd/dep
|
- go get -u github.com/golang/dep/cmd/dep
|
||||||
- go get github.com/cespare/reflex || true
|
- go get github.com/cespare/reflex || true
|
||||||
- go get -u github.com/gobuffalo/packr/...
|
- go get -u github.com/gobuffalo/packr/...
|
||||||
- go get -u github.com/haya14busa/goverage
|
- go get -u github.com/haya14busa/goverage
|
||||||
|
- go get github.com/snikch/goodman/cmd/goodman
|
||||||
- '{{ if ne OS "windows" }} curl -L https://github.com/goreleaser/goreleaser/releases/download/v{{ .GORELEASER_VERSION }}/goreleaser_$(uname -s)_$(uname -m).tar.gz | tar -xz -C ${GOPATH}/bin{{ else }} {{ end }}'
|
- '{{ if ne OS "windows" }} curl -L https://github.com/goreleaser/goreleaser/releases/download/v{{ .GORELEASER_VERSION }}/goreleaser_$(uname -s)_$(uname -m).tar.gz | tar -xz -C ${GOPATH}/bin{{ else }} {{ end }}'
|
||||||
- '{{ if ne OS "windows" }} chmod +x ${GOPATH}/bin/goreleaser{{ else }} {{ end }}'
|
- '{{ if ne OS "windows" }} chmod +x ${GOPATH}/bin/goreleaser{{ else }} {{ end }}'
|
||||||
- '{{ if eq OS "windows" }} echo "NOTICE: You must download goreleaser manually to build this application https://github.com/goreleaser/goreleaser/releases "{{ else }}:{{ end }}'
|
- '{{ if eq OS "windows" }} echo "NOTICE: You must download goreleaser manually to build this application https://github.com/goreleaser/goreleaser/releases "{{ else }}:{{ end }}'
|
||||||
@ -94,12 +101,17 @@ tasks:
|
|||||||
sh: git rev-parse --abbrev-ref HEAD
|
sh: git rev-parse --abbrev-ref HEAD
|
||||||
DIRTY:
|
DIRTY:
|
||||||
# We must exclude the package-lock file as npm install can change it!
|
# We must exclude the package-lock file as npm install can change it!
|
||||||
sh: git diff --exit-code --stat -- . ':(exclude)web/package-lock.json'
|
sh: git diff --exit-code --stat -- . ':(exclude)web/package-lock.json' ':(exclude)web/package.json'
|
||||||
SHA:
|
SHA:
|
||||||
sh: git log --pretty=format:'%h' -n 1
|
sh: git log --pretty=format:'%h' -n 1
|
||||||
TIMESTAMP:
|
TIMESTAMP:
|
||||||
sh: date +%s
|
sh: date +%s
|
||||||
|
|
||||||
|
compile:api:hooks:
|
||||||
|
dir: ./.dredd/hooks
|
||||||
|
cmds:
|
||||||
|
- go build -o ../compiled_hooks
|
||||||
|
|
||||||
watch:
|
watch:
|
||||||
desc: Watch fe and be file changes and rebuild
|
desc: Watch fe and be file changes and rebuild
|
||||||
dir: web/resources
|
dir: web/resources
|
||||||
@ -148,6 +160,10 @@ tasks:
|
|||||||
- gometalinter --exclude "\w*(-packr.go)" --vendor --disable goconst --deadline 240s ./...
|
- gometalinter --exclude "\w*(-packr.go)" --vendor --disable goconst --deadline 240s ./...
|
||||||
|
|
||||||
test:
|
test:
|
||||||
|
cmds:
|
||||||
|
- task: test:be
|
||||||
|
|
||||||
|
test:be:
|
||||||
desc: Run go code tests
|
desc: Run go code tests
|
||||||
cmds:
|
cmds:
|
||||||
- go vet ./...
|
- go vet ./...
|
||||||
@ -155,6 +171,11 @@ tasks:
|
|||||||
# if no tests exist but will still print failing test results
|
# if no tests exist but will still print failing test results
|
||||||
- goverage -v -coverprofile=coverage.out ./... 2> /dev/null
|
- goverage -v -coverprofile=coverage.out ./... 2> /dev/null
|
||||||
|
|
||||||
|
test:api:
|
||||||
|
desc: test the api with dredd
|
||||||
|
cmds:
|
||||||
|
- ./web/node_modules/.bin/dredd --config .dredd/dredd.yml
|
||||||
|
|
||||||
ci:artifacts:
|
ci:artifacts:
|
||||||
cmds:
|
cmds:
|
||||||
- rsync -a bin/ $CIRCLE_ARTIFACTS/
|
- rsync -a bin/ $CIRCLE_ARTIFACTS/
|
||||||
|
468
api-docs.yml
468
api-docs.yml
@ -6,6 +6,12 @@ info:
|
|||||||
|
|
||||||
host: localhost:3000
|
host: localhost:3000
|
||||||
|
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
- text/plain; charset=utf-8
|
||||||
|
|
||||||
tags:
|
tags:
|
||||||
- name: authentication
|
- name: authentication
|
||||||
description: Authentication, Logout & API Tokens
|
description: Authentication, Logout & API Tokens
|
||||||
@ -19,31 +25,47 @@ schemes:
|
|||||||
- https
|
- https
|
||||||
|
|
||||||
basePath: /api
|
basePath: /api
|
||||||
produces:
|
|
||||||
- application/json
|
|
||||||
|
|
||||||
definitions:
|
definitions:
|
||||||
|
|
||||||
|
Pong:
|
||||||
|
type: string
|
||||||
|
x-example: pong
|
||||||
|
|
||||||
Login:
|
Login:
|
||||||
type: object
|
type: object
|
||||||
required:
|
|
||||||
- auth
|
|
||||||
- password
|
|
||||||
properties:
|
properties:
|
||||||
auth:
|
auth:
|
||||||
type: string
|
type: string
|
||||||
description: Username/Email address
|
description: Username/Email address
|
||||||
|
x-example: user@semaphore.com
|
||||||
password:
|
password:
|
||||||
type: string
|
type: string
|
||||||
format: password
|
format: password
|
||||||
description: Password
|
description: Password
|
||||||
PONG:
|
|
||||||
type: string
|
UserRequest:
|
||||||
format: PONG
|
type: object
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
x-example: Integration Test User
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
x-example: test-user
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
x-example: test@ansiblesemaphore.test
|
||||||
|
alert:
|
||||||
|
type: boolean
|
||||||
|
admin:
|
||||||
|
type: boolean
|
||||||
User:
|
User:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
type: integer
|
type: integer
|
||||||
|
minimum: 1
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
username:
|
username:
|
||||||
@ -52,11 +74,12 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
created:
|
created:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
pattern: ^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}T\d{2}:\d{2}:\d{2}Z$
|
||||||
alert:
|
alert:
|
||||||
type: boolean
|
type: boolean
|
||||||
admin:
|
admin:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
|
||||||
APIToken:
|
APIToken:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@ -64,23 +87,50 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
created:
|
created:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
pattern: ^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}T\d{2}:\d{2}:\d{2}Z$
|
||||||
expired:
|
expired:
|
||||||
type: boolean
|
type: boolean
|
||||||
user_id:
|
user_id:
|
||||||
type: integer
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
|
||||||
|
ProjectRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
alert:
|
||||||
|
type: boolean
|
||||||
Project:
|
Project:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
type: integer
|
type: integer
|
||||||
|
minimum: 1
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
created:
|
created:
|
||||||
type: string
|
type: string
|
||||||
format: date-time
|
pattern: ^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}T\d{2}:\d{2}:\d{2}Z$
|
||||||
alert:
|
alert:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
|
||||||
|
AccessKeyRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
enum: [ssh, aws, gcloud, do]
|
||||||
|
project_id:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
x-example: 2
|
||||||
|
key:
|
||||||
|
type: string
|
||||||
|
secret:
|
||||||
|
type: string
|
||||||
AccessKey:
|
AccessKey:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@ -90,25 +140,61 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
type:
|
type:
|
||||||
type: string
|
type: string
|
||||||
|
enum: [ssh, aws, gcloud, do]
|
||||||
project_id:
|
project_id:
|
||||||
type: integer
|
type: integer
|
||||||
key:
|
key:
|
||||||
type: string
|
type: string
|
||||||
secret:
|
secret:
|
||||||
type: string
|
type: string
|
||||||
|
|
||||||
|
EnvironmentRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
project_id:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
json:
|
||||||
|
type: string
|
||||||
Environment:
|
Environment:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
type: integer
|
type: integer
|
||||||
|
minimum: 1
|
||||||
name:
|
name:
|
||||||
type: string
|
type: string
|
||||||
project_id:
|
project_id:
|
||||||
type: integer
|
type: integer
|
||||||
|
minimum: 1
|
||||||
password:
|
password:
|
||||||
type: string
|
type: string
|
||||||
json:
|
json:
|
||||||
type: string
|
type: string
|
||||||
|
|
||||||
|
InventoryRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
project_id:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
inventory:
|
||||||
|
type: string
|
||||||
|
key_id:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
ssh_key_id:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
enum: [static, file]
|
||||||
Inventory:
|
Inventory:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@ -126,6 +212,19 @@ definitions:
|
|||||||
type: integer
|
type: integer
|
||||||
type:
|
type:
|
||||||
type: string
|
type: string
|
||||||
|
enum: [static, file]
|
||||||
|
|
||||||
|
RepositoryRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
project_id:
|
||||||
|
type: integer
|
||||||
|
git_url:
|
||||||
|
type: string
|
||||||
|
ssh_key_id:
|
||||||
|
type: integer
|
||||||
Repository:
|
Repository:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@ -139,11 +238,13 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
ssh_key_id:
|
ssh_key_id:
|
||||||
type: integer
|
type: integer
|
||||||
|
|
||||||
Task:
|
Task:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
id:
|
id:
|
||||||
type: integer
|
type: integer
|
||||||
|
example: 23
|
||||||
template_id:
|
template_id:
|
||||||
type: integer
|
type: integer
|
||||||
status:
|
status:
|
||||||
@ -159,6 +260,7 @@ definitions:
|
|||||||
properties:
|
properties:
|
||||||
task_id:
|
task_id:
|
||||||
type: integer
|
type: integer
|
||||||
|
example: 23
|
||||||
task:
|
task:
|
||||||
type: string
|
type: string
|
||||||
time:
|
time:
|
||||||
@ -166,21 +268,25 @@ definitions:
|
|||||||
format: date-time
|
format: date-time
|
||||||
output:
|
output:
|
||||||
type: string
|
type: string
|
||||||
Template:
|
|
||||||
|
TemplateRequest:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
id:
|
|
||||||
type: integer
|
|
||||||
ssh_key_id:
|
ssh_key_id:
|
||||||
type: integer
|
type: integer
|
||||||
|
minimum: 1
|
||||||
project_id:
|
project_id:
|
||||||
type: integer
|
type: integer
|
||||||
|
minimum: 1
|
||||||
inventory_id:
|
inventory_id:
|
||||||
type: integer
|
type: integer
|
||||||
|
minimum: 1
|
||||||
repository_id:
|
repository_id:
|
||||||
type: integer
|
type: integer
|
||||||
|
minimum: 1
|
||||||
environment_id:
|
environment_id:
|
||||||
type: integer
|
type: integer
|
||||||
|
minimum: 1
|
||||||
alias:
|
alias:
|
||||||
type: string
|
type: string
|
||||||
playbook:
|
playbook:
|
||||||
@ -189,17 +295,51 @@ definitions:
|
|||||||
type: string
|
type: string
|
||||||
override_args:
|
override_args:
|
||||||
type: boolean
|
type: boolean
|
||||||
|
Template:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
ssh_key_id:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
project_id:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
inventory_id:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
repository_id:
|
||||||
|
type: integer
|
||||||
|
environment_id:
|
||||||
|
type: integer
|
||||||
|
minimum: 1
|
||||||
|
alias:
|
||||||
|
type: string
|
||||||
|
playbook:
|
||||||
|
type: string
|
||||||
|
arguments:
|
||||||
|
type: string
|
||||||
|
override_args:
|
||||||
|
type: boolean
|
||||||
|
|
||||||
Event:
|
Event:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
project_id:
|
project_id:
|
||||||
type: integer
|
type: integer
|
||||||
object_id:
|
object_id:
|
||||||
type: integer
|
type:
|
||||||
|
- integer
|
||||||
|
- 'null'
|
||||||
object_type:
|
object_type:
|
||||||
type: string
|
type:
|
||||||
|
- string
|
||||||
|
- 'null'
|
||||||
description:
|
description:
|
||||||
type: string
|
type: string
|
||||||
|
|
||||||
InfoType:
|
InfoType:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@ -213,15 +353,19 @@ definitions:
|
|||||||
tag_name:
|
tag_name:
|
||||||
type: string
|
type: string
|
||||||
|
|
||||||
# securityDefinitions:
|
securityDefinitions:
|
||||||
# cookie:
|
cookie:
|
||||||
# type: apiKey
|
type: apiKey
|
||||||
# name: Cookie
|
name: Cookie
|
||||||
# in: header
|
in: header
|
||||||
# bearer:
|
bearer:
|
||||||
# type: apiKey
|
type: apiKey
|
||||||
# name: Authorization
|
name: Authorization
|
||||||
# in: header
|
in: header
|
||||||
|
|
||||||
|
security:
|
||||||
|
- bearer: []
|
||||||
|
- cookie: []
|
||||||
|
|
||||||
parameters:
|
parameters:
|
||||||
project_id:
|
project_id:
|
||||||
@ -230,58 +374,73 @@ parameters:
|
|||||||
in: path
|
in: path
|
||||||
type: integer
|
type: integer
|
||||||
required: true
|
required: true
|
||||||
|
x-example: 1
|
||||||
user_id:
|
user_id:
|
||||||
name: user_id
|
name: user_id
|
||||||
description: User ID
|
description: User ID
|
||||||
in: path
|
in: path
|
||||||
type: integer
|
type: integer
|
||||||
required: true
|
required: true
|
||||||
|
x-example: 2
|
||||||
key_id:
|
key_id:
|
||||||
name: key_id
|
name: key_id
|
||||||
description: key ID
|
description: key ID
|
||||||
in: path
|
in: path
|
||||||
type: integer
|
type: integer
|
||||||
required: true
|
required: true
|
||||||
|
x-example: 3
|
||||||
repository_id:
|
repository_id:
|
||||||
name: repository_id
|
name: repository_id
|
||||||
description: repository ID
|
description: repository ID
|
||||||
in: path
|
in: path
|
||||||
type: integer
|
type: integer
|
||||||
required: true
|
required: true
|
||||||
|
x-example: 4
|
||||||
inventory_id:
|
inventory_id:
|
||||||
name: inventory_id
|
name: inventory_id
|
||||||
description: inventory ID
|
description: inventory ID
|
||||||
in: path
|
in: path
|
||||||
type: integer
|
type: integer
|
||||||
required: true
|
required: true
|
||||||
|
x-example: 5
|
||||||
environment_id:
|
environment_id:
|
||||||
name: environment_id
|
name: environment_id
|
||||||
description: environment ID
|
description: environment ID
|
||||||
in: path
|
in: path
|
||||||
type: integer
|
type: integer
|
||||||
required: true
|
required: true
|
||||||
|
x-example: 6
|
||||||
template_id:
|
template_id:
|
||||||
name: template_id
|
name: template_id
|
||||||
description: template ID
|
description: template ID
|
||||||
in: path
|
in: path
|
||||||
type: integer
|
type: integer
|
||||||
required: true
|
required: true
|
||||||
|
x-example: 7
|
||||||
task_id:
|
task_id:
|
||||||
name: task_id
|
name: task_id
|
||||||
description: task ID
|
description: task ID
|
||||||
in: path
|
in: path
|
||||||
type: integer
|
type: integer
|
||||||
required: true
|
required: true
|
||||||
|
x-example: 8
|
||||||
|
|
||||||
paths:
|
paths:
|
||||||
/ping:
|
/ping:
|
||||||
get:
|
get:
|
||||||
summary: PING test
|
summary: PING test
|
||||||
|
produces:
|
||||||
|
- text/plain
|
||||||
|
security: [] # No security
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: Successful "PONG" reply
|
description: Successful "PONG" reply
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/definitions/PONG"
|
$ref: "#/definitions/Pong"
|
||||||
|
headers:
|
||||||
|
content-type:
|
||||||
|
type: string
|
||||||
|
x-example: text/plain; charset=utf-8
|
||||||
|
|
||||||
/ws:
|
/ws:
|
||||||
get:
|
get:
|
||||||
@ -292,9 +451,9 @@ paths:
|
|||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: OK
|
description: OK
|
||||||
# security:
|
403:
|
||||||
# - cookie: []
|
description: not authenticated
|
||||||
# - bearer: []
|
|
||||||
/info:
|
/info:
|
||||||
get:
|
get:
|
||||||
summary: Fetches information about semaphore
|
summary: Fetches information about semaphore
|
||||||
@ -329,6 +488,7 @@ paths:
|
|||||||
summary: Performs Login
|
summary: Performs Login
|
||||||
description: |
|
description: |
|
||||||
Upon success you will be logged in
|
Upon success you will be logged in
|
||||||
|
security: [] # No security
|
||||||
parameters:
|
parameters:
|
||||||
- name: Login Body
|
- name: Login Body
|
||||||
in: body
|
in: body
|
||||||
@ -340,6 +500,7 @@ paths:
|
|||||||
description: You are logged in
|
description: You are logged in
|
||||||
400:
|
400:
|
||||||
description: something in body is missing / is invalid
|
description: something in body is missing / is invalid
|
||||||
|
|
||||||
/auth/logout:
|
/auth/logout:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
@ -349,7 +510,7 @@ paths:
|
|||||||
204:
|
204:
|
||||||
description: Your session was successfully nuked
|
description: Your session was successfully nuked
|
||||||
|
|
||||||
# User stuff
|
# User Tokens
|
||||||
/user:
|
/user:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@ -384,12 +545,14 @@ paths:
|
|||||||
description: API Token
|
description: API Token
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/definitions/APIToken"
|
$ref: "#/definitions/APIToken"
|
||||||
|
|
||||||
/user/tokens/{api_token_id}:
|
/user/tokens/{api_token_id}:
|
||||||
parameters:
|
parameters:
|
||||||
- name: api_token_id
|
- name: api_token_id
|
||||||
in: path
|
in: path
|
||||||
type: string
|
type: string
|
||||||
required: true
|
required: true
|
||||||
|
x-example: "kwofd61g93-yuqvex8efmhjkgnbxlo8mp1tin6spyhu="
|
||||||
delete:
|
delete:
|
||||||
tags:
|
tags:
|
||||||
- authentication
|
- authentication
|
||||||
@ -399,6 +562,98 @@ paths:
|
|||||||
204:
|
204:
|
||||||
description: Expired API Token
|
description: Expired API Token
|
||||||
|
|
||||||
|
# User Profiles
|
||||||
|
/users:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- user
|
||||||
|
summary: Fetches all users
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: Users
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/definitions/User"
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- user
|
||||||
|
summary: Creates a user
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- name: User
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/UserRequest"
|
||||||
|
responses:
|
||||||
|
400:
|
||||||
|
description: User creation failed
|
||||||
|
201:
|
||||||
|
description: User created
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/User"
|
||||||
|
|
||||||
|
/users/{user_id}:
|
||||||
|
parameters:
|
||||||
|
- $ref: "#/parameters/user_id"
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- user
|
||||||
|
summary: Fetches a user profile
|
||||||
|
responses:
|
||||||
|
200:
|
||||||
|
description: User profile
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/User"
|
||||||
|
put:
|
||||||
|
tags:
|
||||||
|
- user
|
||||||
|
summary: Updates user details
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- name: User
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
$ref: "#/definitions/UserRequest"
|
||||||
|
responses:
|
||||||
|
204:
|
||||||
|
description: User Updated
|
||||||
|
|
||||||
|
delete:
|
||||||
|
tags:
|
||||||
|
- user
|
||||||
|
summary: Deletes user
|
||||||
|
responses:
|
||||||
|
204:
|
||||||
|
description: User deleted
|
||||||
|
|
||||||
|
/users/{user_id}/password:
|
||||||
|
parameters:
|
||||||
|
- $ref: "#/parameters/user_id"
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- user
|
||||||
|
summary: Updates user password
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
|
parameters:
|
||||||
|
- name: Password
|
||||||
|
in: body
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
format: password
|
||||||
|
responses:
|
||||||
|
204:
|
||||||
|
description: Password updated
|
||||||
|
|
||||||
# Projects
|
# Projects
|
||||||
/projects:
|
/projects:
|
||||||
get:
|
get:
|
||||||
@ -416,15 +671,18 @@ paths:
|
|||||||
tags:
|
tags:
|
||||||
- projects
|
- projects
|
||||||
summary: Create a new project
|
summary: Create a new project
|
||||||
|
consumes:
|
||||||
|
- application/json
|
||||||
parameters:
|
parameters:
|
||||||
- name: Project
|
- name: Project
|
||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/definitions/Project'
|
$ref: '#/definitions/ProjectRequest'
|
||||||
responses:
|
responses:
|
||||||
201:
|
201:
|
||||||
description: Created project
|
description: Created project
|
||||||
|
|
||||||
/events:
|
/events:
|
||||||
get:
|
get:
|
||||||
summary: Get Events related to Semaphore and projects you are part of
|
summary: Get Events related to Semaphore and projects you are part of
|
||||||
@ -510,14 +768,16 @@ paths:
|
|||||||
in: query
|
in: query
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
format: name/username/email/admin
|
enum: [name, username, email, admin]
|
||||||
description: sorting name
|
description: sorting name
|
||||||
|
x-example: email
|
||||||
- name: order
|
- name: order
|
||||||
in: query
|
in: query
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
format: asc/desc
|
enum: [asc, desc]
|
||||||
description: ordering manner
|
description: ordering manner
|
||||||
|
x-example: desc
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: Users
|
description: Users
|
||||||
@ -538,7 +798,7 @@ paths:
|
|||||||
properties:
|
properties:
|
||||||
user_id:
|
user_id:
|
||||||
type: integer
|
type: integer
|
||||||
format: userID
|
minimum: 2
|
||||||
admin:
|
admin:
|
||||||
type: boolean
|
type: boolean
|
||||||
responses:
|
responses:
|
||||||
@ -583,24 +843,28 @@ paths:
|
|||||||
- project
|
- project
|
||||||
summary: Get access keys linked to project
|
summary: Get access keys linked to project
|
||||||
parameters:
|
parameters:
|
||||||
|
# TODO - the space in this parameter name results in a dredd warning
|
||||||
- name: Key type
|
- name: Key type
|
||||||
in: query
|
in: query
|
||||||
required: false
|
required: false
|
||||||
type: string
|
type: string
|
||||||
format: ssh/aws/gcloud/do
|
enum: [ssh, aws, gcloud, do]
|
||||||
description: Filter by key type
|
description: Filter by key type
|
||||||
|
x-example: ssh
|
||||||
- name: sort
|
- name: sort
|
||||||
in: query
|
in: query
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
format: name/type
|
enum: [name, type]
|
||||||
description: sorting name
|
description: sorting name
|
||||||
|
x-example: type
|
||||||
- name: order
|
- name: order
|
||||||
in: query
|
in: query
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
format: asc/desc
|
enum: [asc, desc]
|
||||||
description: ordering manner
|
description: ordering manner
|
||||||
|
x-example: asc
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: Access Keys
|
description: Access Keys
|
||||||
@ -617,7 +881,7 @@ paths:
|
|||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/definitions/AccessKey"
|
$ref: "#/definitions/AccessKeyRequest"
|
||||||
responses:
|
responses:
|
||||||
204:
|
204:
|
||||||
description: Access Key created
|
description: Access Key created
|
||||||
@ -636,7 +900,7 @@ paths:
|
|||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/definitions/AccessKey"
|
$ref: "#/definitions/AccessKeyRequest"
|
||||||
responses:
|
responses:
|
||||||
204:
|
204:
|
||||||
description: Key updated
|
description: Key updated
|
||||||
@ -663,13 +927,14 @@ paths:
|
|||||||
in: query
|
in: query
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
format: name/git_url/ssh_key
|
enum: [name, git_url, ssh_key]
|
||||||
description: sorting name
|
description: sorting name
|
||||||
- name: order
|
- name: order
|
||||||
in: query
|
in: query
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
format: asc/desc
|
format: asc/desc
|
||||||
|
enum: [asc, desc]
|
||||||
description: ordering manner
|
description: ordering manner
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
@ -687,7 +952,7 @@ paths:
|
|||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/definitions/Repository"
|
$ref: "#/definitions/RepositoryRequest"
|
||||||
responses:
|
responses:
|
||||||
204:
|
204:
|
||||||
description: Repository created
|
description: Repository created
|
||||||
@ -716,14 +981,14 @@ paths:
|
|||||||
in: query
|
in: query
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
format: name/type
|
|
||||||
description: sorting name
|
description: sorting name
|
||||||
|
enum: [name, type]
|
||||||
- name: order
|
- name: order
|
||||||
in: query
|
in: query
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
format: asc/desc
|
|
||||||
description: ordering manner
|
description: ordering manner
|
||||||
|
enum: [asc, desc]
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: inventory
|
description: inventory
|
||||||
@ -740,7 +1005,7 @@ paths:
|
|||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/definitions/Inventory"
|
$ref: "#/definitions/InventoryRequest"
|
||||||
responses:
|
responses:
|
||||||
201:
|
201:
|
||||||
description: inventory created
|
description: inventory created
|
||||||
@ -759,7 +1024,7 @@ paths:
|
|||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/definitions/Inventory"
|
$ref: "#/definitions/InventoryRequest"
|
||||||
responses:
|
responses:
|
||||||
204:
|
204:
|
||||||
description: Inventory updated
|
description: Inventory updated
|
||||||
@ -786,12 +1051,14 @@ paths:
|
|||||||
type: string
|
type: string
|
||||||
format: name
|
format: name
|
||||||
description: sorting name
|
description: sorting name
|
||||||
|
x-example: 'db-deploy'
|
||||||
- name: order
|
- name: order
|
||||||
in: query
|
in: query
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
format: asc/desc
|
format: asc/desc
|
||||||
description: ordering manner
|
description: ordering manner
|
||||||
|
x-example: desc
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: environment
|
description: environment
|
||||||
@ -808,7 +1075,7 @@ paths:
|
|||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/definitions/Environment"
|
$ref: "#/definitions/EnvironmentRequest"
|
||||||
responses:
|
responses:
|
||||||
204:
|
204:
|
||||||
description: Environment created
|
description: Environment created
|
||||||
@ -825,7 +1092,7 @@ paths:
|
|||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/definitions/Environment"
|
$ref: "#/definitions/EnvironmentRequest"
|
||||||
responses:
|
responses:
|
||||||
204:
|
204:
|
||||||
description: Environment Updated
|
description: Environment Updated
|
||||||
@ -850,14 +1117,14 @@ paths:
|
|||||||
in: query
|
in: query
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
format: alias/playbook/ssh_key/inventory/environment/repository
|
|
||||||
description: sorting name
|
description: sorting name
|
||||||
|
enum: [alias, playbook, ssh_key, inventory, environment, repository]
|
||||||
- name: order
|
- name: order
|
||||||
in: query
|
in: query
|
||||||
required: true
|
required: true
|
||||||
type: string
|
type: string
|
||||||
format: asc/desc
|
|
||||||
description: ordering manner
|
description: ordering manner
|
||||||
|
enum: [asc, desc]
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: template
|
description: template
|
||||||
@ -874,7 +1141,7 @@ paths:
|
|||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/definitions/Template"
|
$ref: "#/definitions/TemplateRequest"
|
||||||
responses:
|
responses:
|
||||||
201:
|
201:
|
||||||
description: template created
|
description: template created
|
||||||
@ -893,7 +1160,7 @@ paths:
|
|||||||
in: body
|
in: body
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/definitions/Template"
|
$ref: "#/definitions/TemplateRequest"
|
||||||
responses:
|
responses:
|
||||||
204:
|
204:
|
||||||
description: template updated
|
description: template updated
|
||||||
@ -973,6 +1240,13 @@ paths:
|
|||||||
description: Task
|
description: Task
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/definitions/Task"
|
$ref: "#/definitions/Task"
|
||||||
|
delete:
|
||||||
|
tags:
|
||||||
|
- project
|
||||||
|
summary: Deletes task (including output)
|
||||||
|
responses:
|
||||||
|
204:
|
||||||
|
description: task deleted
|
||||||
/project/{project_id}/tasks/{task_id}/output:
|
/project/{project_id}/tasks/{task_id}/output:
|
||||||
parameters:
|
parameters:
|
||||||
- $ref: '#/parameters/project_id'
|
- $ref: '#/parameters/project_id'
|
||||||
@ -988,95 +1262,3 @@ paths:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: "#/definitions/TaskOutput"
|
$ref: "#/definitions/TaskOutput"
|
||||||
/project/{project_id}/tasks/{task_id}:
|
|
||||||
parameters:
|
|
||||||
- $ref: '#/parameters/project_id'
|
|
||||||
- $ref: '#/parameters/task_id'
|
|
||||||
delete:
|
|
||||||
tags:
|
|
||||||
- project
|
|
||||||
summary: Deletes task (including output)
|
|
||||||
responses:
|
|
||||||
204:
|
|
||||||
description: task deleted
|
|
||||||
|
|
||||||
# users
|
|
||||||
/users:
|
|
||||||
get:
|
|
||||||
tags:
|
|
||||||
- user
|
|
||||||
summary: Fetches all users
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
description: Users
|
|
||||||
schema:
|
|
||||||
type: array
|
|
||||||
items:
|
|
||||||
$ref: "#/definitions/User"
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- user
|
|
||||||
summary: Creates a user
|
|
||||||
parameters:
|
|
||||||
- name: User
|
|
||||||
in: body
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: "#/definitions/User"
|
|
||||||
responses:
|
|
||||||
201:
|
|
||||||
description: User created
|
|
||||||
schema:
|
|
||||||
$ref: "#/definitions/User"
|
|
||||||
/users/{user_id}:
|
|
||||||
parameters:
|
|
||||||
- $ref: "#/parameters/user_id"
|
|
||||||
get:
|
|
||||||
tags:
|
|
||||||
- user
|
|
||||||
summary: Fetches a user profile
|
|
||||||
responses:
|
|
||||||
200:
|
|
||||||
description: User profile
|
|
||||||
schema:
|
|
||||||
$ref: "#/definitions/User"
|
|
||||||
put:
|
|
||||||
tags:
|
|
||||||
- user
|
|
||||||
summary: Updates user details
|
|
||||||
parameters:
|
|
||||||
- name: User
|
|
||||||
in: body
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
$ref: "#/definitions/User"
|
|
||||||
responses:
|
|
||||||
204:
|
|
||||||
description: User Updated
|
|
||||||
delete:
|
|
||||||
tags:
|
|
||||||
- user
|
|
||||||
summary: Deletes user
|
|
||||||
responses:
|
|
||||||
204:
|
|
||||||
description: User deleted
|
|
||||||
/users/{user_id}/password:
|
|
||||||
parameters:
|
|
||||||
- $ref: "#/parameters/user_id"
|
|
||||||
post:
|
|
||||||
tags:
|
|
||||||
- user
|
|
||||||
summary: Updates user password
|
|
||||||
parameters:
|
|
||||||
- name: Password
|
|
||||||
in: body
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
properties:
|
|
||||||
password:
|
|
||||||
type: string
|
|
||||||
format: password
|
|
||||||
responses:
|
|
||||||
204:
|
|
||||||
description: Password updated
|
|
||||||
|
33
api/api_test.go
Normal file
33
api/api_test.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"github.com/go-openapi/loads"
|
||||||
|
"github.com/go-openapi/spec"
|
||||||
|
"github.com/go-openapi/validate"
|
||||||
|
"github.com/go-openapi/strfmt"
|
||||||
|
"os"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestApi Validates the api description in the root meets the swagger/openapi spec
|
||||||
|
func TestApiSchemaValidation(t *testing.T) {
|
||||||
|
dir, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
fpath := dir+"/../api-docs.yml"
|
||||||
|
print(fpath)
|
||||||
|
document, err := loads.Spec(fpath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
spc := spec.ExpandOptions{RelativeBase: fpath}
|
||||||
|
document, err = document.Expanded(&spc)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := validate.Spec(document, strfmt.Default); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/castawaylabs/mulekick"
|
"github.com/castawaylabs/mulekick"
|
||||||
"github.com/gorilla/context"
|
"github.com/gorilla/context"
|
||||||
"github.com/masterminds/squirrel"
|
"github.com/masterminds/squirrel"
|
||||||
|
"time"
|
||||||
"github.com/ansible-semaphore/semaphore/util"
|
"github.com/ansible-semaphore/semaphore/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -49,9 +50,13 @@ func AddProject(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
desc := "Project Created"
|
desc := "Project Created"
|
||||||
|
oType := "Project"
|
||||||
if err := (db.Event{
|
if err := (db.Event{
|
||||||
ProjectID: &body.ID,
|
ProjectID: &body.ID,
|
||||||
Description: &desc,
|
Description: &desc,
|
||||||
|
ObjectType: &oType,
|
||||||
|
ObjectID: &body.ID,
|
||||||
|
Created: db.GetParsedTime(time.Now()),
|
||||||
}.Insert()); err != nil {
|
}.Insert()); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -83,6 +83,7 @@ func AddUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO - check if user already exists
|
||||||
if _, err := db.Mysql.Exec("insert into project__user set user_id=?, project_id=?, `admin`=?", user.UserID, project.ID, user.Admin); err != nil {
|
if _, err := db.Mysql.Exec("insert into project__user set user_id=?, project_id=?, `admin`=?", user.UserID, project.ID, user.Admin); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -16,12 +16,21 @@ import (
|
|||||||
|
|
||||||
var publicAssets = packr.NewBox("../web/public")
|
var publicAssets = packr.NewBox("../web/public")
|
||||||
|
|
||||||
|
//JSONMiddleware ensures that all the routes respond with Json, this is added by default to all routes
|
||||||
|
func JSONMiddleware(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("content-type", "application/json")
|
||||||
|
}
|
||||||
|
|
||||||
|
//PlainTextMiddleware resets headers to Plain Text if needed
|
||||||
|
func PlainTextMiddleware(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("content-type", "text/plain; charset=utf-8")
|
||||||
|
}
|
||||||
// Route declares all routes
|
// Route declares all routes
|
||||||
func Route() mulekick.Router {
|
func Route() mulekick.Router {
|
||||||
r := mulekick.New(mux.NewRouter(), mulekick.CorsMiddleware)
|
r := mulekick.New(mux.NewRouter(), mulekick.CorsMiddleware, JSONMiddleware)
|
||||||
r.NotFoundHandler = http.HandlerFunc(servePublic)
|
r.NotFoundHandler = http.HandlerFunc(servePublic)
|
||||||
|
|
||||||
r.Get("/api/ping", mulekick.PongHandler)
|
r.Get("/api/ping", PlainTextMiddleware, mulekick.PongHandler)
|
||||||
|
|
||||||
// set up the namespace
|
// set up the namespace
|
||||||
api := r.Group("/api")
|
api := r.Group("/api")
|
||||||
@ -45,7 +54,7 @@ func Route() mulekick.Router {
|
|||||||
|
|
||||||
api.Get("/tokens", getAPITokens)
|
api.Get("/tokens", getAPITokens)
|
||||||
api.Post("/tokens", createAPIToken)
|
api.Post("/tokens", createAPIToken)
|
||||||
api.Delete("/tokens/:token_id", expireAPIToken)
|
api.Delete("/tokens/{token_id}", expireAPIToken)
|
||||||
}(api.Group("/user"))
|
}(api.Group("/user"))
|
||||||
|
|
||||||
api.Get("/projects", projects.GetProjects)
|
api.Get("/projects", projects.GetProjects)
|
||||||
|
@ -43,7 +43,7 @@ func createAPIToken(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
token := db.APIToken{
|
token := db.APIToken{
|
||||||
ID: strings.ToLower(base64.URLEncoding.EncodeToString(tokenID)),
|
ID: strings.ToLower(base64.URLEncoding.EncodeToString(tokenID)),
|
||||||
Created: time.Now(),
|
Created: db.GetParsedTime(time.Now()),
|
||||||
UserID: user.ID,
|
UserID: user.ID,
|
||||||
Expired: false,
|
Expired: false,
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ func getUsers(w http.ResponseWriter, r *http.Request) {
|
|||||||
func addUser(w http.ResponseWriter, r *http.Request) {
|
func addUser(w http.ResponseWriter, r *http.Request) {
|
||||||
var user db.User
|
var user db.User
|
||||||
if err := mulekick.Bind(w, r, &user); err != nil {
|
if err := mulekick.Bind(w, r, &user); err != nil {
|
||||||
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,7 +36,7 @@ func addUser(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user.Created = time.Now()
|
user.Created = db.GetParsedTime(time.Now())
|
||||||
|
|
||||||
if err := db.Mysql.Insert(&user); err != nil {
|
if err := db.Mysql.Insert(&user); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
|
@ -10,7 +10,7 @@ import (
|
|||||||
type AccessKey struct {
|
type AccessKey struct {
|
||||||
ID int `db:"id" json:"id"`
|
ID int `db:"id" json:"id"`
|
||||||
Name string `db:"name" json:"name" binding:"required"`
|
Name string `db:"name" json:"name" binding:"required"`
|
||||||
// 'aws/do/gcloud/ssh',
|
// 'aws/do/gcloud/ssh'
|
||||||
Type string `db:"type" json:"type" binding:"required"`
|
Type string `db:"type" json:"type" binding:"required"`
|
||||||
|
|
||||||
ProjectID *int `db:"project_id" json:"project_id"`
|
ProjectID *int `db:"project_id" json:"project_id"`
|
||||||
|
18
db/mysql.go
18
db/mysql.go
@ -6,6 +6,7 @@ import (
|
|||||||
"github.com/ansible-semaphore/semaphore/util"
|
"github.com/ansible-semaphore/semaphore/util"
|
||||||
_ "github.com/go-sql-driver/mysql" // imports mysql driver
|
_ "github.com/go-sql-driver/mysql" // imports mysql driver
|
||||||
"gopkg.in/gorp.v1"
|
"gopkg.in/gorp.v1"
|
||||||
|
"time"
|
||||||
log "github.com/Sirupsen/logrus"
|
log "github.com/Sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -13,7 +14,22 @@ import (
|
|||||||
// db.Connect must be called to set this up correctly
|
// db.Connect must be called to set this up correctly
|
||||||
var Mysql *gorp.DbMap
|
var Mysql *gorp.DbMap
|
||||||
|
|
||||||
// Connect to MySQL and initialize the Mysql object
|
// DatabaseTimeFormat represents the format that dredd uses to validate the datetime.
|
||||||
|
// This is not the same as the raw value we pass to a new object so
|
||||||
|
// we need to use this to coerce raw values to meet the API standard
|
||||||
|
// /^\d{4}-(?:0[0-9]{1}|1[0-2]{1})-[0-9]{2}T\d{2}:\d{2}:\d{2}Z$/
|
||||||
|
const DatabaseTimeFormat = "2006-01-02T15:04:05:99Z"
|
||||||
|
|
||||||
|
// GetParsedTime returns the timestamp as it will retrieved from the database
|
||||||
|
// This allows us to create timestamp consistency on return values from create requests
|
||||||
|
func GetParsedTime(t time.Time) time.Time {
|
||||||
|
parsedTime, err := time.Parse(DatabaseTimeFormat,t.Format(DatabaseTimeFormat))
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
return parsedTime
|
||||||
|
}
|
||||||
|
// Connect ensures that the db is connected and mapped properly with gorp
|
||||||
func Connect() error {
|
func Connect() error {
|
||||||
db, err := connect()
|
db, err := connect()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -62,6 +62,13 @@ which contains the go toolchain and glibc in alpine.
|
|||||||
Because the test image links your local volume it expects that you have run `task deps` and `task compile` locally
|
Because the test image links your local volume it expects that you have run `task deps` and `task compile` locally
|
||||||
as necessary to make the application usable.
|
as necessary to make the application usable.
|
||||||
|
|
||||||
|
## CI
|
||||||
|
|
||||||
|
This context is a proxyless stack used to test the API in the ci. Essentially it just installs the app, adds a few bootstrapping files
|
||||||
|
and starts up so that dredd can be run against it. This should not be used in production as it does not remove the build toolchain,
|
||||||
|
or source code.
|
||||||
|
It is more advisable to use the dev context locally as it volume links the application directory and defaults to the watch task.
|
||||||
|
|
||||||
## Convenience Functions
|
## Convenience Functions
|
||||||
|
|
||||||
### dc:dev
|
### dc:dev
|
||||||
|
36
deployment/docker/ci/Dockerfile
Normal file
36
deployment/docker/ci/Dockerfile
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
# Golang testing image with some tools already installed
|
||||||
|
FROM tomwhiston/micro-golang:test-base
|
||||||
|
|
||||||
|
LABEL maintainer="Tom Whiston <tom.whiston@gmail.com>"
|
||||||
|
|
||||||
|
ENV SEMAPHORE_VERSION="development" SEMAPHORE_ARCH="linux_amd64" \
|
||||||
|
SEMAPHORE_CONFIG_PATH="${SEMAPHORE_CONFIG_PATH:-/etc/semaphore}" \
|
||||||
|
APP_ROOT="/go/src/github.com/ansible-semaphore/semaphore/"
|
||||||
|
|
||||||
|
# hadolint ignore=DL3013
|
||||||
|
RUN apk add --no-cache git mysql-client python py-pip py-openssl openssl ca-certificates curl curl-dev openssh-client tini nodejs nodejs-npm bash rsync && \
|
||||||
|
apk --update add --virtual build-dependencies python-dev libffi-dev openssl-dev build-base && \
|
||||||
|
pip install --upgrade pip cffi && \
|
||||||
|
pip install ansible && \
|
||||||
|
apk del build-dependencies && \
|
||||||
|
rm -rf /var/cache/apk/* && \
|
||||||
|
adduser -D -u 1002 -g 0 semaphore && \
|
||||||
|
mkdir -p /go/src/github.com/ansible-semaphore/semaphore && \
|
||||||
|
mkdir -p /tmp/semaphore && \
|
||||||
|
mkdir -p /etc/semaphore && \
|
||||||
|
chown -R semaphore:0 /go && \
|
||||||
|
chown -R semaphore:0 /tmp/semaphore && \
|
||||||
|
chown -R semaphore:0 /etc/semaphore && \
|
||||||
|
ssh-keygen -t rsa -q -f "/root/.ssh/id_rsa" -N "" && \
|
||||||
|
ssh-keyscan -H github.com > /root/.ssh/known_hosts && \
|
||||||
|
go get -u -v github.com/go-task/task/cmd/task
|
||||||
|
|
||||||
|
# Copy in app source
|
||||||
|
WORKDIR ${APP_ROOT}
|
||||||
|
COPY . ${APP_ROOT}
|
||||||
|
RUN deployment/docker/ci/bin/install
|
||||||
|
|
||||||
|
USER semaphore
|
||||||
|
EXPOSE 3000
|
||||||
|
ENTRYPOINT ["/usr/local/bin/semaphore-wrapper"]
|
||||||
|
CMD ["./bin/semaphore", "--config", "/etc/semaphore/config.json"]
|
15
deployment/docker/ci/bin/install
Executable file
15
deployment/docker/ci/bin/install
Executable file
@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
|
||||||
|
echo "--> Turn off StrictKeyChecking"
|
||||||
|
cat > /etc/ssh/ssh_config <<EOF
|
||||||
|
Host *
|
||||||
|
StrictHostKeyChecking no
|
||||||
|
UserKnownHostsFile=/dev/null
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "--> Install Semaphore entrypoint wrapper script"
|
||||||
|
cp ./deployment/docker/common/semaphore-wrapper /usr/local/bin/semaphore-wrapper
|
||||||
|
task deps
|
||||||
|
task compile
|
||||||
|
task build:local
|
45
deployment/docker/ci/docker-compose.yml
Normal file
45
deployment/docker/ci/docker-compose.yml
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
version: '2'
|
||||||
|
|
||||||
|
services:
|
||||||
|
mysql:
|
||||||
|
image: mysql
|
||||||
|
environment:
|
||||||
|
MYSQL_RANDOM_ROOT_PASSWORD: 'yes'
|
||||||
|
MYSQL_DATABASE: semaphore
|
||||||
|
MYSQL_USER: semaphore
|
||||||
|
MYSQL_PASSWORD: semaphore
|
||||||
|
ports:
|
||||||
|
- "3306:3306"
|
||||||
|
|
||||||
|
semaphore_ci:
|
||||||
|
image: ansiblesemaphore/semaphore:ci-compose
|
||||||
|
build:
|
||||||
|
context: ./../../../
|
||||||
|
dockerfile: ./deployment/docker/ci/Dockerfile
|
||||||
|
environment:
|
||||||
|
SEMAPHORE_DB_USER: semaphore
|
||||||
|
SEMAPHORE_DB_PASS: semaphore
|
||||||
|
SEMAPHORE_DB_HOST: mysql
|
||||||
|
SEMAPHORE_DB_PORT: 3306
|
||||||
|
SEMAPHORE_DB: semaphore
|
||||||
|
SEMAPHORE_PLAYBOOK_PATH: /etc/semaphore
|
||||||
|
SEMAPHORE_ADMIN_PASSWORD: password
|
||||||
|
SEMAPHORE_ADMIN_NAME: "Developer"
|
||||||
|
SEMAPHORE_ADMIN_EMAIL: admin@localhost
|
||||||
|
SEMAPHORE_ADMIN: admin
|
||||||
|
SEMAPHORE_WEB_ROOT: http://0.0.0.0:3000
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
depends_on:
|
||||||
|
- mysql
|
||||||
|
|
||||||
|
dredd:
|
||||||
|
image: ansiblesemaphore/dredd:ci
|
||||||
|
command: ["--config", ".dredd/dredd.yml"]
|
||||||
|
build:
|
||||||
|
context: ./../../../
|
||||||
|
dockerfile: ./deployment/docker/ci/dredd.Dockerfile
|
||||||
|
depends_on:
|
||||||
|
- semaphore_ci
|
||||||
|
- mysql
|
||||||
|
|
24
deployment/docker/ci/dredd.Dockerfile
Normal file
24
deployment/docker/ci/dredd.Dockerfile
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
# hadolint ignore=DL3006
|
||||||
|
FROM tomwhiston/dredd:latest
|
||||||
|
|
||||||
|
ENV TASK_VERSION=v2.0.1 \
|
||||||
|
GOPATH=/home/developer/go \
|
||||||
|
SEMAPHORE_SERVICE=semaphore_ci \
|
||||||
|
SEMAPHORE_PORT=3000 \
|
||||||
|
MYSQL_SERVICE=mysql \
|
||||||
|
MYSQL_PORT=3306
|
||||||
|
|
||||||
|
# We need the source and task to compile the hooks
|
||||||
|
USER 0
|
||||||
|
RUN dnf install -y nc
|
||||||
|
COPY deployment/docker/ci/dredd/entrypoint /usr/local/bin
|
||||||
|
COPY . /home/developer/go/src/github.com/ansible-semaphore/semaphore
|
||||||
|
WORKDIR /usr/local/bin
|
||||||
|
RUN curl -L "https://github.com/go-task/task/releases/download/${TASK_VERSION}/task_linux_amd64.tar.gz" | tar xvz && \
|
||||||
|
chown -R developer /home/developer/go
|
||||||
|
|
||||||
|
# Get tools and do compile
|
||||||
|
WORKDIR /home/developer/go/src/github.com/ansible-semaphore/semaphore
|
||||||
|
RUN task deps:tools && task deps:be && task compile:be && task compile:api:hooks
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/local/bin/entrypoint"]
|
25
deployment/docker/ci/dredd/entrypoint
Executable file
25
deployment/docker/ci/dredd/entrypoint
Executable file
@ -0,0 +1,25 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "---> Add Config"
|
||||||
|
cat > ./.dredd/config.json <<EOF
|
||||||
|
{
|
||||||
|
"mysql": {
|
||||||
|
"host": "${MYSQL_SERVICE}:${MYSQL_PORT}",
|
||||||
|
"user": "semaphore",
|
||||||
|
"pass": "semaphore",
|
||||||
|
"name": "semaphore"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "---> Waiting for semaphore"
|
||||||
|
while ! nc -z ${SEMAPHORE_SERVICE} ${SEMAPHORE_PORT}; do
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "---> Run Dredd"
|
||||||
|
# We do this because otherwise it can fail out
|
||||||
|
sleep 5
|
||||||
|
/home/developer/node_modules/.bin/dredd $@
|
@ -7,7 +7,7 @@ ENV SEMAPHORE_VERSION="development" SEMAPHORE_ARCH="linux_amd64" \
|
|||||||
SEMAPHORE_CONFIG_PATH="${SEMAPHORE_CONFIG_PATH:-/etc/semaphore}" \
|
SEMAPHORE_CONFIG_PATH="${SEMAPHORE_CONFIG_PATH:-/etc/semaphore}" \
|
||||||
APP_ROOT="/go/src/github.com/ansible-semaphore/semaphore/"
|
APP_ROOT="/go/src/github.com/ansible-semaphore/semaphore/"
|
||||||
|
|
||||||
RUN apk add --no-cache git mysql-client python py-pip py-openssl openssl ca-certificates curl openssh-client tini nodejs nodejs-npm bash rsync && \
|
RUN apk add --no-cache git mysql-client python py-pip py-openssl openssl ca-certificates curl curl-dev openssh-client tini nodejs nodejs-npm bash rsync && \
|
||||||
apk --update add --virtual build-dependencies python-dev libffi-dev openssl-dev build-base && \
|
apk --update add --virtual build-dependencies python-dev libffi-dev openssl-dev build-base && \
|
||||||
pip install --upgrade pip cffi && \
|
pip install --upgrade pip cffi && \
|
||||||
pip install ansible && \
|
pip install ansible && \
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
echo "--> Turn off StrictKeyChecking"
|
echo "--> Turn off StrictKeyChecking"
|
||||||
cat > /etc/ssh/ssh_config <<EOF
|
cat > /etc/ssh/ssh_config <<EOF
|
||||||
Host *
|
Host *
|
||||||
|
@ -8,6 +8,8 @@ services:
|
|||||||
MYSQL_DATABASE: semaphore
|
MYSQL_DATABASE: semaphore
|
||||||
MYSQL_USER: semaphore
|
MYSQL_USER: semaphore
|
||||||
MYSQL_PASSWORD: semaphore
|
MYSQL_PASSWORD: semaphore
|
||||||
|
ports:
|
||||||
|
- "3306:3306"
|
||||||
|
|
||||||
semaphore_dev:
|
semaphore_dev:
|
||||||
image: ansiblesemaphore/semaphore:dev-compose
|
image: ansiblesemaphore/semaphore:dev-compose
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
#!/usr/bin/env sh
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
# Go build environment
|
# Go build environment
|
||||||
export GOROOT=/usr/lib/go
|
export GOROOT=/usr/lib/go
|
||||||
export GOPATH=/go
|
export GOPATH=/go
|
||||||
@ -11,7 +13,9 @@ go get -u -v github.com/go-task/task/cmd/task
|
|||||||
|
|
||||||
# Compile and build
|
# Compile and build
|
||||||
task deps
|
task deps
|
||||||
|
set +e
|
||||||
task compile
|
task compile
|
||||||
|
set -e
|
||||||
task build:local
|
task build:local
|
||||||
|
|
||||||
mv ./bin/semaphore /usr/local/bin/
|
mv ./bin/semaphore /usr/local/bin/
|
||||||
|
Loading…
Reference in New Issue
Block a user