WIP: tests, REST API support (auth).
This commit is contained in:
@ -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"
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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",
|
@ -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(
|
Reference in New Issue
Block a user