Implemented GET on /projects/<proj-id> and started unit tests.
This commit is contained in:
parent
e547ecd607
commit
1e2af48892
13
api.rst
13
api.rst
@ -1,9 +1,16 @@
|
|||||||
✓ GET /api/ping
|
✓ GET /api/ping
|
||||||
- GET /api/auth-token
|
✓ GET /api/auth-token
|
||||||
|
✓ GET /api/verify-auth -- returns 200 or 401 depend on validity of the provided auth
|
||||||
✓ GET /api/projects -- return project summaries
|
✓ GET /api/projects -- return project summaries
|
||||||
- POST /api/projects -- create a new project
|
- POST /api/projects -- create a new project
|
||||||
- GET /api/project/<proj-id> -- return detailed project record (include steps)
|
* GET /api/project/<proj-id> -- return detailed project record (include steps)
|
||||||
- GET /api/project/<proj-id>/active -- return detailed information about all currently running runs
|
- GET /api/project/<proj-id>/active -- return detailed information about all currently running runs
|
||||||
- GET /api/project/<proj-id>/<step-id> -- return detailed step information (include runs)
|
- GET /api/project/<proj-id>/<step-id> -- return detailed step information (include runs)
|
||||||
- POST /api/project/<proj-id>/<step-id>/run/<ref> -- kick off a run
|
* POST /api/project/<proj-id>/<step-id>/run/<ref> -- kick off a run
|
||||||
- GET /api/project/<proj-id>/<step-id>/run/<ref> -- return detailed run information
|
- GET /api/project/<proj-id>/<step-id>/run/<ref> -- return detailed run information
|
||||||
|
|
||||||
|
|
||||||
|
Legend:
|
||||||
|
✓ implemented with passing tests
|
||||||
|
* implemented, needs testing
|
||||||
|
- not implemented
|
||||||
|
@ -79,7 +79,7 @@ proc getOrFail(n: JsonNode, key: string, objName: string = ""): JsonNode =
|
|||||||
return n[key]
|
return n[key]
|
||||||
|
|
||||||
# Configuration parsing code
|
# Configuration parsing code
|
||||||
|
|
||||||
proc parseProjectDef*(pJson: JsonNode): ProjectDef =
|
proc parseProjectDef*(pJson: JsonNode): ProjectDef =
|
||||||
var envVars = newStringTable(modeCaseSensitive)
|
var envVars = newStringTable(modeCaseSensitive)
|
||||||
for k, v in pJson.getIfExists("envVars").getFields: envVars[k] = v.getStr("")
|
for k, v in pJson.getIfExists("envVars").getFields: envVars[k] = v.getStr("")
|
||||||
@ -186,13 +186,28 @@ proc `%`*(req: RunRequest): JsonNode =
|
|||||||
"workspaceDir": req.workspaceDir,
|
"workspaceDir": req.workspaceDir,
|
||||||
"forceRebuild": req.forceRebuild }
|
"forceRebuild": req.forceRebuild }
|
||||||
|
|
||||||
|
proc `%`*(user: User): JsonNode =
|
||||||
|
result = %* {
|
||||||
|
"name": user.name,
|
||||||
|
"hashedPwd": user.hashedPwd }
|
||||||
|
|
||||||
|
proc `%`*(cfg: StrawBossConfig): JsonNode =
|
||||||
|
result = %* {
|
||||||
|
"artifactsRepo": cfg.artifactsRepo,
|
||||||
|
"authSecret": cfg.authSecret,
|
||||||
|
"debug": cfg.debug,
|
||||||
|
"projects": %cfg.projects,
|
||||||
|
"pwdCost": cfg.pwdCost,
|
||||||
|
"users": %cfg.users }
|
||||||
|
|
||||||
proc `$`*(s: BuildStatus): string = result = pretty(%s)
|
proc `$`*(s: BuildStatus): string = result = pretty(%s)
|
||||||
proc `$`*(req: RunRequest): string = result = pretty(%req)
|
proc `$`*(req: RunRequest): string = result = pretty(%req)
|
||||||
proc `$`*(pd: ProjectDef): string = result = pretty(%pd)
|
proc `$`*(pd: ProjectDef): string = result = pretty(%pd)
|
||||||
|
proc `$`*(cfg: StrawBossConfig): string = result = pretty(%cfg)
|
||||||
|
|
||||||
# TODO: maybe a macro for more general-purpose, shallow object comparison?
|
# TODO: maybe a macro for more general-purpose, shallow object comparison?
|
||||||
#proc `==`*(a, b: ProjectDef): bool =
|
#proc `==`*(a, b: ProjectDef): bool =
|
||||||
|
|
||||||
template shallowEquals(a, b: RootObj): bool =
|
template shallowEquals(a, b: RootObj): bool =
|
||||||
if type(a) != type(b): return false
|
if type(a) != type(b): return false
|
||||||
var anyB = toAny(b)
|
var anyB = toAny(b)
|
||||||
|
@ -140,7 +140,10 @@ proc runStep*(wksp: Workspace, step: Step) =
|
|||||||
let artifactPath = a.resolveEnvVars(wksp.env)
|
let artifactPath = a.resolveEnvVars(wksp.env)
|
||||||
let artifactName = artifactPath[(artifactPath.rfind("/")+1)..^1]
|
let artifactName = artifactPath[(artifactPath.rfind("/")+1)..^1]
|
||||||
try:
|
try:
|
||||||
wksp.outputHandler.sendMsg "copy " & wksp.dir & "/repo/" & step.workingDir & "/" & artifactPath & " -> " & wksp.artifactsDir & "/" & artifactName
|
wksp.outputHandler.sendMsg "copy " & wksp.dir & "/repo/" &
|
||||||
|
step.workingDir & "/" & artifactPath & " -> " &
|
||||||
|
wksp.artifactsDir & "/" & artifactName
|
||||||
|
|
||||||
copyFile(wksp.dir & "/repo/" & step.workingDir & "/" & artifactPath,
|
copyFile(wksp.dir & "/repo/" & step.workingDir & "/" & artifactPath,
|
||||||
wksp.artifactsDir & "/" & artifactName)
|
wksp.artifactsDir & "/" & artifactName)
|
||||||
except:
|
except:
|
||||||
@ -205,6 +208,13 @@ proc runStep*(cfg: StrawBossConfig, req: RunRequest,
|
|||||||
"cloning project repo and preparing to run '" & req.stepName & "'")
|
"cloning project repo and preparing to run '" & req.stepName & "'")
|
||||||
wksp.setupProject()
|
wksp.setupProject()
|
||||||
|
|
||||||
|
# Update our cache of project configurations by copying the configuration
|
||||||
|
# file to our artifacts directory.
|
||||||
|
copyFile(
|
||||||
|
wksp.dir & "/repo/" & wksp.projectDef.cfgFilePath,
|
||||||
|
cfg.artifactsRepo & "/" & wksp.project.name & "/configuration." &
|
||||||
|
wksp.version & ".json")
|
||||||
|
|
||||||
# Find the requested step
|
# Find the requested step
|
||||||
if not wksp.project.steps.hasKey(req.stepName):
|
if not wksp.project.steps.hasKey(req.stepName):
|
||||||
raiseEx "no step name '" & req.stepName & "' for " & req.projectName
|
raiseEx "no step name '" & req.stepName & "' for " & req.projectName
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import asyncdispatch, bcrypt, jester, json, jwt, os, osproc, sequtils,
|
import algorithm, asyncdispatch, bcrypt, jester, json, jwt, os, osproc,
|
||||||
strutils, tempfile, times, unittest
|
sequtils, strutils, tempfile, times, unittest
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import ./configuration, ./core, private/util
|
import ./configuration, ./core, private/util
|
||||||
@ -143,7 +143,60 @@ proc start*(cfg: StrawBossConfig): void =
|
|||||||
resp(Http200, $(%*{ "username": session.user.name }), JSON)
|
resp(Http200, $(%*{ "username": session.user.name }), JSON)
|
||||||
|
|
||||||
get "/projects": withSession:
|
get "/projects": withSession:
|
||||||
resp($(%(givenCfg.projects)), "application/json")
|
# List project summaries (ProjectDefs only)
|
||||||
|
resp($(%(cfg.projects)), JSON)
|
||||||
|
|
||||||
|
post "/projects": withSession:
|
||||||
|
# Create a new project definition
|
||||||
|
resp(Http501, makeJsonResp(Http501), JSON)
|
||||||
|
|
||||||
|
get "/project/@projectName/@version?": withSession:
|
||||||
|
## Get a detailed project record including step definitions (ProjectConfig).
|
||||||
|
|
||||||
|
# Make sure we know about that project
|
||||||
|
var project: ProjectDef
|
||||||
|
try: project = cfg.findProject(@"projectName")
|
||||||
|
except: resp(Http404, makeJsonResp(Http404, getCurrentExceptionMsg()), JSON)
|
||||||
|
|
||||||
|
# Given version
|
||||||
|
|
||||||
|
var cachedFilePath: string
|
||||||
|
if @"version" != "":
|
||||||
|
cachedFilePath = cfg.artifactsRepo & "/" & project.name &
|
||||||
|
"/configuration." & @"version" & ".json"
|
||||||
|
|
||||||
|
if not existsFile(cachedFilePath):
|
||||||
|
resp(Http404,
|
||||||
|
makeJsonResp(Http404, "I have never built version " & @"version"),
|
||||||
|
JSON)
|
||||||
|
|
||||||
|
# No version requested, use "latest"
|
||||||
|
else:
|
||||||
|
let confFilePaths = toSeq(walkFiles("configuration.*.json"))
|
||||||
|
if confFilePaths.len == 0:
|
||||||
|
resp(Http404, makeJsonResp(Http404, "I have not built any versions of " & project.name), JSON)
|
||||||
|
let modTimes = confFilePaths.mapIt(it.getLastModificationTime)
|
||||||
|
cachedFilePath = sorted(zip(confFilePaths, modTimes),
|
||||||
|
proc (a, b: tuple): int = cmp(a.b, b.b))[0].a
|
||||||
|
|
||||||
|
try: resp(readFile(cachedFilePath), JSON)
|
||||||
|
except:
|
||||||
|
debug "Could not serve cached project configuration at: " &
|
||||||
|
cachedFilePath & "\n\t Reason: " & getCurrentExceptionMsg()
|
||||||
|
resp(Http500, makeJsonResp(Http500, "could not read cached project configuration"), JSON)
|
||||||
|
|
||||||
|
get "/api/project/@projectName/active": withSession:
|
||||||
|
# List all currently active runs
|
||||||
|
resp(Http501, makeJsonResp(Http501), JSON)
|
||||||
|
|
||||||
|
get "/api/project/@projectName/@stepName": withSession:
|
||||||
|
|
||||||
|
# Get step details including runs.
|
||||||
|
resp(Http501, makeJsonResp(Http501), JSON)
|
||||||
|
|
||||||
|
get "/api/project/@projectName/@stepName/run/@buildRef": withSession:
|
||||||
|
# Get detailed information about a run
|
||||||
|
resp(Http501, makeJsonResp(Http501), JSON)
|
||||||
|
|
||||||
post "/project/@projectName/@stepName/run/@buildRef?":
|
post "/project/@projectName/@stepName/run/@buildRef?":
|
||||||
# Kick off a run
|
# Kick off a run
|
||||||
@ -159,4 +212,12 @@ proc start*(cfg: StrawBossConfig): void =
|
|||||||
callSoon(proc(): void = complete(stopFuture))
|
callSoon(proc(): void = complete(stopFuture))
|
||||||
resp($(%*"shutting down"), JSON)
|
resp($(%*"shutting down"), JSON)
|
||||||
|
|
||||||
|
#[
|
||||||
|
get re".*":
|
||||||
|
resp(Http404, makeJsonResp(Http404), JSON)
|
||||||
|
|
||||||
|
post re".*":
|
||||||
|
resp(Http404, makeJsonResp(Http404), JSON)
|
||||||
|
]#
|
||||||
|
|
||||||
waitFor(stopFuture)
|
waitFor(stopFuture)
|
||||||
|
@ -107,6 +107,10 @@ suite "load and save configuration objects":
|
|||||||
sameContents(pc.steps["test"].expectedEnv, @[])
|
sameContents(pc.steps["test"].expectedEnv, @[])
|
||||||
sameContents(pc.steps["test"].cmdInput, @[])
|
sameContents(pc.steps["test"].cmdInput, @[])
|
||||||
|
|
||||||
|
test "StrawBossConfig to string":
|
||||||
|
# TODO
|
||||||
|
check false
|
||||||
|
|
||||||
test "loadBuildStatus":
|
test "loadBuildStatus":
|
||||||
let st = loadBuildStatus("src/test/json/test-status.json")
|
let st = loadBuildStatus("src/test/json/test-status.json")
|
||||||
|
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import asyncdispatch, httpclient, json, os, osproc, sequtils, strutils, times, unittest
|
import asyncdispatch, httpclient, json, os, osproc, sequtils, strutils,
|
||||||
|
tempfile, times, unittest
|
||||||
|
|
||||||
|
import logging
|
||||||
import ./testutil
|
import ./testutil
|
||||||
import ../../main/nim/strawbosspkg/configuration
|
import ../../main/nim/strawbosspkg/configuration
|
||||||
import ../../main/nim/strawbosspkg/server
|
import ../../main/nim/strawbosspkg/server
|
||||||
import ../../main/nim/strawbosspkg/private/util
|
import ../../main/nim/strawbosspkg/private/util
|
||||||
|
|
||||||
import strtabs
|
|
||||||
|
|
||||||
# test helpers
|
# test helpers
|
||||||
proc newAuthenticatedHttpClient(apiBase, uname, pwd: string): HttpClient =
|
proc newAuthenticatedHttpClient(apiBase, uname, pwd: string): HttpClient =
|
||||||
result = newHttpClient()
|
result = newHttpClient()
|
||||||
@ -13,20 +14,20 @@ proc newAuthenticatedHttpClient(apiBase, uname, pwd: string): HttpClient =
|
|||||||
assert authResp.status.startsWith("200")
|
assert authResp.status.startsWith("200")
|
||||||
result.headers = newHttpHeaders({"Authorization": "Bearer " & parseJson(authResp.body).getStr})
|
result.headers = newHttpHeaders({"Authorization": "Bearer " & parseJson(authResp.body).getStr})
|
||||||
|
|
||||||
suite "strawboss server can...":
|
let apiBase = "http://localhost:8180/api"
|
||||||
|
let cfgFilePath = "src/test/json/strawboss.config.json"
|
||||||
|
let cfg = loadStrawBossConfig(cfgFilePath)
|
||||||
|
|
||||||
|
let testuser = UserRef( # note: needs to correspond to an actual user
|
||||||
|
name: "bob@builder.com",
|
||||||
|
hashedPwd: "$2a$11$lVZ9U4optQMhzPh0E9A7Yu6XndXblUF3gCa.zmEvJy4F.4C4718b.")
|
||||||
|
|
||||||
|
suite "strawboss server":
|
||||||
|
|
||||||
# suite setup code
|
# suite setup code
|
||||||
let cfgFilePath = "src/test/json/strawboss.config.json"
|
|
||||||
let cfg = loadStrawBossConfig(cfgFilePath)
|
|
||||||
|
|
||||||
discard startProcess("./strawboss", ".", @["serve", "-c", cfgFilePath], loadEnv(), {poUsePath})
|
discard startProcess("./strawboss", ".", @["serve", "-c", cfgFilePath], loadEnv(), {poUsePath})
|
||||||
|
|
||||||
let http = newHttpClient()
|
let http = newHttpClient()
|
||||||
let apiBase = "http://localhost:8180/api"
|
|
||||||
|
|
||||||
let testuser = UserRef( # note: needs to correspond to an actual user
|
|
||||||
name: "bob@builder.com",
|
|
||||||
hashedPwd: "$2a$11$lVZ9U4optQMhzPh0E9A7Yu6XndXblUF3gCa.zmEvJy4F.4C4718b.")
|
|
||||||
|
|
||||||
# give the server time to spin up
|
# give the server time to spin up
|
||||||
sleep(100)
|
sleep(100)
|
||||||
@ -88,5 +89,25 @@ suite "strawboss server can...":
|
|||||||
check sameContents(projects, cfg.projects)
|
check sameContents(projects, cfg.projects)
|
||||||
|
|
||||||
# suite tear-down
|
# suite tear-down
|
||||||
try: discard http.post(apiBase & "/service/debug/stop")
|
discard newAsyncHttpClient().post(apiBase & "/service/debug/stop")
|
||||||
except: discard ""
|
|
||||||
|
suite "strawboss server continued":
|
||||||
|
|
||||||
|
setup:
|
||||||
|
let tmpArtifactsDir = mkdtemp()
|
||||||
|
let (_, tmpCfgPath) = mkstemp()
|
||||||
|
var newCfg = cfg
|
||||||
|
newCfg.artifactsRepo = tmpArtifactsDir
|
||||||
|
writeFile(tmpCfgPath, $newCfg)
|
||||||
|
discard startProcess("./strawboss", ".", @["serve", "-c", tmpCfgPath], loadEnv(), {poUsePath})
|
||||||
|
|
||||||
|
# give the server time to spin up
|
||||||
|
sleep(100)
|
||||||
|
|
||||||
|
teardown:
|
||||||
|
discard newAsyncHttpClient().post(apiBase & "/service/debug/stop")
|
||||||
|
|
||||||
|
test "handle missing project configuration":
|
||||||
|
let http = newAuthenticatedHttpClient(apibase, "bob@builder.com", "password")
|
||||||
|
let resp = http.get(apiBase & "/projects/test-project-1")
|
||||||
|
check resp.status.startsWith("404")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user