WIP: tests, REST API support (auth).

This commit is contained in:
Jonathan Bernard
2017-03-19 06:34:42 -05:00
parent 2551affd4b
commit b5a70f6de0
13 changed files with 211 additions and 20 deletions

View File

@ -1,9 +1,9 @@
import docopt, os, sequtils, tempfile
import strawboss/private/util
import strawboss/configuration
import strawboss/core
import strawboss/server
import strawbosspkg/private/util
import strawbosspkg/configuration
import strawbosspkg/core
import strawbosspkg/server
let SB_VER = "0.2.0"

View File

@ -12,7 +12,7 @@ type
artifacts*, cmdInput*, depends*, expectedEnv*: seq[string]
dontSkip*: bool
ProjectCfg* = object
ProjectConfig* = object
name*: string
versionCmd*: string
steps*: Table[string, Step]
@ -37,15 +37,31 @@ type
projects*: seq[ProjectDef]
users*: seq[UserRef]
# Equality on custom types
proc `==`*(a, b: UserRef): bool = result = a.name == b.name
proc `==`*(a, b: ProjectDef): bool =
if a.envVars.len != b.envVars.len: return false
for k, v in a.envVars:
if not b.envVars.hasKey(k) or a.envVars[k] != b.envVars[k]: return false
return
a.name == b.name and
a.cfgFilePath == b.cfgFilePath and
a.defaultBranch == b.defaultBranch and
a.repo == b.repo
# internal utils
let nullNode = newJNull()
proc getIfExists(n: JsonNode, key: string): JsonNode =
# convenience method to get a key from a JObject or return null
result = if n.hasKey(key): n[key]
else: nullNode
proc getOrFail(n: JsonNode, key: string, objName: string = ""): JsonNode =
# convenience method to get a key from a JObject or raise an exception
if not n.hasKey(key): raiseEx objName & " missing key " & key
return n[key]
@ -84,7 +100,7 @@ proc loadStrawBossConfig*(cfgFile: string): StrawBossConfig =
projects: projectDefs,
users: users)
proc loadProjectConfig*(cfgFile: string): ProjectCfg =
proc loadProjectConfig*(cfgFile: string): ProjectConfig =
if not existsFile(cfgFile):
raiseEx "project config file not found: " & cfgFile
@ -103,12 +119,12 @@ proc loadProjectConfig*(cfgFile: string): ProjectCfg =
artifacts: pJson.getIfExists("artifacts").getElems.mapIt(it.getStr),
cmdInput: pJson.getIfExists("cmdInput").getElems.mapIt(it.getStr),
expectedEnv: pJson.getIfExists("expectedEnv").getElems.mapIt(it.getStr),
dontSkip: pJson.getIfExists("dontSkip").getStr("false") != "false")
dontSkip: pJson.getIfExists("dontSkip").getBVal(false))
if steps[sName].stepCmd == "sh" and steps[sName].cmdInput.len == 0:
warn "Step " & sName & " uses 'sh' as its command but has no cmdInput."
result = ProjectCfg(
result = ProjectConfig(
name: jsonCfg.getOrFail("name", "project configuration").getStr,
versionCmd: jsonCfg.getIfExists("versionCmd").getStr("git describe --tags --always"),
steps: steps)
@ -121,6 +137,7 @@ proc loadBuildStatus*(statusFile: string): BuildStatus =
state: jsonObj.getOrFail("state", "build status").getStr,
details: jsonObj.getIfExists("details").getStr("") )
# TODO: unused and untested, add tests if we start using this
proc parseRunRequest*(reqStr: string): RunRequest =
let reqJson = parseJson(reqStr)

View File

@ -13,7 +13,7 @@ type
env*: StringTableRef ## environment variables for all build processes
openedFiles*: seq[File] ## all files that we have opened that need to be closed
outputHandler*: HandleProcMsgCB ## handler for process output
project*: ProjectCfg ## the project configuration
project*: ProjectConfig ## the project configuration
projectDef*: ProjectDef ## the StrawBoss project definition
status*: BuildStatus ## the current status of the build
statusFile*: string ## absolute path to the build status file
@ -188,7 +188,7 @@ proc runStep*(cfg: StrawBossConfig, req: RunRequest,
env: env,
openedFiles: @[stdoutFile, stderrFile],
outputHandler: combineProcMsgHandlers(outputHandler, logFilesOH),
project: ProjectCfg(),
project: ProjectConfig(),
projectDef: matching[0],
status: result,
statusFile: req.workspaceDir & "/" & "status.json",

View File

@ -1,4 +1,4 @@
import asyncdispatch, jester, json, jwt, osproc, sequtils, tempfile, times
import asyncdispatch, bcrypt, jester, json, jwt, osproc, sequtils, tempfile, times
import ./configuration, ./core, private/util
@ -15,11 +15,13 @@ type
issuedAt*, expires*: TimeInfo
const ISO_TIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"
const BCRYPT_ROUNDS = 16
proc makeJsonResp(status: HttpCode): string =
proc makeJsonResp(status: HttpCode, details: string = ""): string =
result = $(%* {
"statusCode": status.int,
"status": $status
"status": $status,
"details": details
})
proc newSession(user: UserRef): Session =
@ -61,7 +63,7 @@ proc extractSession(cfg: StrawBossConfig, request: Request): Session =
template requireAuth() =
var session {.inject.}: Session
try: session = extractSession(givenCfg, request)
except: resp Http401, makeJsonResp(Http401), "application/json"
except: resp(Http401, makeJsonResp(Http401), "application/json")
proc spawnWorker(req: RunRequest): Worker =
let dir = mkdtemp()
@ -77,15 +79,36 @@ proc start*(givenCfg: StrawBossConfig): void =
routes:
get "/api/ping":
resp $(%*"pong"), "application/json"
resp($(%*"pong"), "application/json")
get "/api/auth-token":
resp Http501, makeJsonResp(Http501), "application/json"
resp(Http501, makeJsonResp(Http501), "application/json")
get "/api/projects":
requireAuth()
#let projectDefs: seq[ProjectDef] = givenCfg.projects.mapIt(it)
resp $(%(givenCfg.projects)), "application/json"
resp($(%(givenCfg.projects)), "application/json")
get "/api/auth-token":
var username, pwd: string
try:
username = @"username"
pwd = @"password"
except: resp(Http401, makeJsonResp(Http401, "fields 'username' and 'password' required"))
let users = givenCfg.users.filterIt(it.name == username)
if users.len != 1:
resp(Http401, makeJsonResp(Http401, "invalid username or password"))
let user = users[0]
# generate salt
let salt = genSalt(BCRYPT_ROUNDS)
# bcrypt
let hashedPwd = hash(pwd, salt)
stdout.writeLine "Hashed pwd is " & $hashedPwd
resp(Http501, makeJsonResp(Http501))
post "/api/project/@projectName/@stepName/run/@buildRef?":
workers.add(spawnWorker(RunRequest(