mirror of
https://github.com/ohwgiles/laminar.git
synced 2024-10-27 20:34:20 +00:00
resolves #67: laminarc list jobs
Implements the following laminarc commands: - show-jobs - show-running - show-queued - abort
This commit is contained in:
parent
21284731a3
commit
7cee824cee
@ -142,6 +142,14 @@ laminarc queue test-host test-target
|
|||||||
|
|
||||||
This is against the design principles of Laminar and was deliberately excluded. Laminar's web UI is strictly read-only, making it simple to deploy in mixed-permission or public environments without an authentication layer. Furthermore, Laminar tries to encourage ideal continuous integration, where manual triggering is an anti-pattern. Want to make a release? Push a git tag and implement a post-receive hook. Want to re-run a build due to sporadic failure/flaky tests? Fix the tests locally and push a patch. Experience shows that a manual trigger such as a "Build Now" button is often used as a crutch to avoid doing the correct thing, negatively impacting traceability and quality.
|
This is against the design principles of Laminar and was deliberately excluded. Laminar's web UI is strictly read-only, making it simple to deploy in mixed-permission or public environments without an authentication layer. Furthermore, Laminar tries to encourage ideal continuous integration, where manual triggering is an anti-pattern. Want to make a release? Push a git tag and implement a post-receive hook. Want to re-run a build due to sporadic failure/flaky tests? Fix the tests locally and push a patch. Experience shows that a manual trigger such as a "Build Now" button is often used as a crutch to avoid doing the correct thing, negatively impacting traceability and quality.
|
||||||
|
|
||||||
|
## Listing jobs from the command line
|
||||||
|
|
||||||
|
`laminarc` may be used to inspect the server state:
|
||||||
|
|
||||||
|
- `laminarc show-jobs`: Lists all files matching `/var/lib/laminar/cfg/jobs/*.run` on the server side.
|
||||||
|
- `laminarc show-running`: Lists all currently running jobs and their run numbers.
|
||||||
|
- `laminarc show-queued`: Lists all jobs waiting in the queue.
|
||||||
|
|
||||||
## Triggering a job at a certain time
|
## Triggering a job at a certain time
|
||||||
|
|
||||||
This is what `cron` is for. To trigger a build of `hello` every day at 0300, add
|
This is what `cron` is for. To trigger a build of `hello` every day at 0300, add
|
||||||
@ -433,7 +441,9 @@ make -C src
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Abort on timeout
|
# Aborting running jobs
|
||||||
|
|
||||||
|
## After a timeout
|
||||||
|
|
||||||
To configure a maximum execution time in seconds for a job, add a line to `/var/lib/laminar/cfg/jobs/JOBNAME.conf`:
|
To configure a maximum execution time in seconds for a job, add a line to `/var/lib/laminar/cfg/jobs/JOBNAME.conf`:
|
||||||
|
|
||||||
@ -441,6 +451,10 @@ To configure a maximum execution time in seconds for a job, add a line to `/var/
|
|||||||
TIMEOUT=120
|
TIMEOUT=120
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Manually
|
||||||
|
|
||||||
|
`laminarc abort $JOBNAME $NUMBER`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Nodes and Tags
|
# Nodes and Tags
|
||||||
@ -607,5 +621,9 @@ Finally, variables supplied on the command-line call to `laminarc queue`, `lamin
|
|||||||
- `start [JOB [PARAMS...]]...` starts one or more jobs with optional parameters, returning when the jobs begin execution.
|
- `start [JOB [PARAMS...]]...` starts one or more jobs with optional parameters, returning when the jobs begin execution.
|
||||||
- `run [JOB [PARAMS...]]...` triggers one or more jobs with optional parameters and waits for the completion of all jobs. Returns a non-zero error code if any job failed.
|
- `run [JOB [PARAMS...]]...` triggers one or more jobs with optional parameters and waits for the completion of all jobs. Returns a non-zero error code if any job failed.
|
||||||
- `set [VARIABLE=VALUE]...` sets one or more variables to be exported in subsequent scripts for the run identified by the `$JOB` and `$RUN` environment variables
|
- `set [VARIABLE=VALUE]...` sets one or more variables to be exported in subsequent scripts for the run identified by the `$JOB` and `$RUN` environment variables
|
||||||
|
- `show-jobs` shows the known jobs on the server (`$LAMINAR_HOME/cfg/jobs/*.run`).
|
||||||
|
- `show-running` shows the currently running jobs with their numbers.
|
||||||
|
- `show-queued` shows the names of the jobs waiting in the queue.
|
||||||
|
- `abort JOB NUMBER` manually aborts a currently running job by name and number.
|
||||||
|
|
||||||
`laminarc` connects to `laminard` using the address supplied by the `LAMINAR_HOST` environment variable. If it is not set, `laminarc` will first attempt to use `LAMINAR_BIND_RPC`, which will be available if `laminarc` is executed from a script within `laminard`. If neither `LAMINAR_HOST` nor `LAMINAR_BIND_RPC` is set, `laminarc` will assume a default host of `unix-abstract:laminar`.
|
`laminarc` connects to `laminard` using the address supplied by the `LAMINAR_HOST` environment variable. If it is not set, `laminarc` will first attempt to use `LAMINAR_BIND_RPC`, which will be available if `laminarc` is executed from a script within `laminard`. If neither `LAMINAR_HOST` nor `LAMINAR_BIND_RPC` is set, `laminarc` will assume a default host of `unix-abstract:laminar`.
|
||||||
|
@ -173,8 +173,8 @@ int main(int argc, char** argv) {
|
|||||||
char* name = argv[2];
|
char* name = argv[2];
|
||||||
*eq++ = '\0';
|
*eq++ = '\0';
|
||||||
char* val = eq;
|
char* val = eq;
|
||||||
req.setJobName(job);
|
req.getRun().setJob(job);
|
||||||
req.setBuildNum(atoi(num));
|
req.getRun().setBuildNum(atoi(num));
|
||||||
req.getParam().setName(name);
|
req.getParam().setName(name);
|
||||||
req.getParam().setValue(val);
|
req.getParam().setValue(val);
|
||||||
req.send().wait(waitScope);
|
req.send().wait(waitScope);
|
||||||
@ -182,6 +182,40 @@ int main(int argc, char** argv) {
|
|||||||
fprintf(stderr, "Missing $JOB or $RUN or param is not in the format key=value\n");
|
fprintf(stderr, "Missing $JOB or $RUN or param is not in the format key=value\n");
|
||||||
return EINVAL;
|
return EINVAL;
|
||||||
}
|
}
|
||||||
|
} else if(strcmp(argv[1], "abort") == 0) {
|
||||||
|
if(argc != 4) {
|
||||||
|
fprintf(stderr, "Usage %s abort <jobName> <jobNumber>\n", argv[0]);
|
||||||
|
return EINVAL;
|
||||||
|
}
|
||||||
|
auto req = laminar.abortRequest();
|
||||||
|
req.getRun().setJob(argv[2]);
|
||||||
|
req.getRun().setBuildNum(atoi(argv[3]));
|
||||||
|
if(req.send().wait(waitScope).getResult() != LaminarCi::MethodResult::SUCCESS)
|
||||||
|
ret = EFAILED;
|
||||||
|
} else if(strcmp(argv[1], "show-jobs") == 0) {
|
||||||
|
if(argc != 2) {
|
||||||
|
fprintf(stderr, "Usage: %s show-jobs\n", argv[0]);
|
||||||
|
return EINVAL;
|
||||||
|
}
|
||||||
|
for(auto it : laminar.listKnownRequest().send().wait(waitScope).getResult()) {
|
||||||
|
printf("%s\n", it.cStr());
|
||||||
|
}
|
||||||
|
} else if(strcmp(argv[1], "show-queued") == 0) {
|
||||||
|
if(argc != 2) {
|
||||||
|
fprintf(stderr, "Usage: %s show-queued\n", argv[0]);
|
||||||
|
return EINVAL;
|
||||||
|
}
|
||||||
|
for(auto it : laminar.listQueuedRequest().send().wait(waitScope).getResult()) {
|
||||||
|
printf("%s\n", it.cStr());
|
||||||
|
}
|
||||||
|
} else if(strcmp(argv[1], "show-running") == 0) {
|
||||||
|
if(argc != 2) {
|
||||||
|
fprintf(stderr, "Usage: %s show-running\n", argv[0]);
|
||||||
|
return EINVAL;
|
||||||
|
}
|
||||||
|
for(auto it : laminar.listRunningRequest().send().wait(waitScope).getResult()) {
|
||||||
|
printf("%s:%d\n", it.getJob().cStr(), it.getBuildNum());
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
fprintf(stderr, "Unknown command %s\n", argv[1]);
|
fprintf(stderr, "Unknown command %s\n", argv[1]);
|
||||||
return EINVAL;
|
return EINVAL;
|
||||||
|
@ -128,6 +128,15 @@ struct LaminarInterface {
|
|||||||
// the environment of subsequent scripts.
|
// the environment of subsequent scripts.
|
||||||
virtual bool setParam(std::string job, uint buildNum, std::string param, std::string value) = 0;
|
virtual bool setParam(std::string job, uint buildNum, std::string param, std::string value) = 0;
|
||||||
|
|
||||||
|
// Gets the list of jobs currently waiting in the execution queue
|
||||||
|
virtual const std::list<std::shared_ptr<Run>>& listQueuedJobs() = 0;
|
||||||
|
|
||||||
|
// Gets the list of currently executing jobs
|
||||||
|
virtual const RunSet& listRunningJobs() = 0;
|
||||||
|
|
||||||
|
// Gets the list of known jobs - scans cfg/jobs for *.run files
|
||||||
|
virtual std::list<std::string> listKnownJobs() = 0;
|
||||||
|
|
||||||
// Fetches the content of an artifact given its filename relative to
|
// Fetches the content of an artifact given its filename relative to
|
||||||
// $LAMINAR_HOME/archive. Ideally, this would instead be served by a
|
// $LAMINAR_HOME/archive. Ideally, this would instead be served by a
|
||||||
// proper web server which handles this url.
|
// proper web server which handles this url.
|
||||||
@ -143,6 +152,9 @@ struct LaminarInterface {
|
|||||||
// which handles this url.
|
// which handles this url.
|
||||||
virtual std::string getCustomCss() = 0;
|
virtual std::string getCustomCss() = 0;
|
||||||
|
|
||||||
|
// Aborts a single job
|
||||||
|
virtual bool abort(std::string job, uint buildNum) = 0;
|
||||||
|
|
||||||
// Abort all running jobs
|
// Abort all running jobs
|
||||||
virtual void abortAll() = 0;
|
virtual void abortAll() = 0;
|
||||||
|
|
||||||
|
@ -5,7 +5,16 @@ interface LaminarCi {
|
|||||||
queue @0 (jobName :Text, params :List(JobParam)) -> (result :MethodResult);
|
queue @0 (jobName :Text, params :List(JobParam)) -> (result :MethodResult);
|
||||||
start @1 (jobName :Text, params :List(JobParam)) -> (result :MethodResult, buildNum :UInt32);
|
start @1 (jobName :Text, params :List(JobParam)) -> (result :MethodResult, buildNum :UInt32);
|
||||||
run @2 (jobName :Text, params :List(JobParam)) -> (result :JobResult, buildNum :UInt32);
|
run @2 (jobName :Text, params :List(JobParam)) -> (result :JobResult, buildNum :UInt32);
|
||||||
set @3 (jobName :Text, buildNum :UInt32, param :JobParam) -> (result :MethodResult);
|
set @3 (run :Run, param :JobParam) -> (result :MethodResult);
|
||||||
|
listQueued @4 () -> (result :List(Text));
|
||||||
|
listRunning @5 () -> (result :List(Run));
|
||||||
|
listKnown @6 () -> (result :List(Text));
|
||||||
|
abort @7 (run :Run) -> (result :MethodResult);
|
||||||
|
|
||||||
|
struct Run {
|
||||||
|
job @0 :Text;
|
||||||
|
buildNum @1 :UInt32;
|
||||||
|
}
|
||||||
|
|
||||||
struct JobParam {
|
struct JobParam {
|
||||||
name @0 :Text;
|
name @0 :Text;
|
||||||
|
@ -130,6 +130,25 @@ bool Laminar::setParam(std::string job, uint buildNum, std::string param, std::s
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::list<std::shared_ptr<Run>>& Laminar::listQueuedJobs() {
|
||||||
|
return queuedJobs;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RunSet& Laminar::listRunningJobs() {
|
||||||
|
return activeJobs;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::list<std::string> Laminar::listKnownJobs() {
|
||||||
|
std::list<std::string> res;
|
||||||
|
KJ_IF_MAYBE(dir, fsHome->tryOpenSubdir(kj::Path{"cfg","jobs"})) {
|
||||||
|
for(kj::Directory::Entry& entry : (*dir)->listEntries()) {
|
||||||
|
if(entry.type == kj::FsNode::Type::FILE && entry.name.endsWith(".run")) {
|
||||||
|
res.emplace_back(entry.name.cStr(), entry.name.findLast('.').orDefault(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
void Laminar::populateArtifacts(Json &j, std::string job, uint num) const {
|
void Laminar::populateArtifacts(Json &j, std::string job, uint num) const {
|
||||||
kj::Path runArchive{job,std::to_string(num)};
|
kj::Path runArchive{job,std::to_string(num)};
|
||||||
@ -575,6 +594,14 @@ void Laminar::notifyConfigChanged()
|
|||||||
assignNewJobs();
|
assignNewJobs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Laminar::abort(std::string job, uint buildNum) {
|
||||||
|
if(Run* run = activeRun(job, buildNum)) {
|
||||||
|
run->abort(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void Laminar::abortAll() {
|
void Laminar::abortAll() {
|
||||||
for(std::shared_ptr<Run> run : activeJobs) {
|
for(std::shared_ptr<Run> run : activeJobs) {
|
||||||
run->abort(false);
|
run->abort(false);
|
||||||
|
@ -56,9 +56,13 @@ public:
|
|||||||
|
|
||||||
void sendStatus(LaminarClient* client) override;
|
void sendStatus(LaminarClient* client) override;
|
||||||
bool setParam(std::string job, uint buildNum, std::string param, std::string value) override;
|
bool setParam(std::string job, uint buildNum, std::string param, std::string value) override;
|
||||||
|
const std::list<std::shared_ptr<Run>>& listQueuedJobs() override;
|
||||||
|
const RunSet& listRunningJobs() override;
|
||||||
|
std::list<std::string> listKnownJobs() override;
|
||||||
kj::Maybe<kj::Own<const kj::ReadableFile>> getArtefact(std::string path) override;
|
kj::Maybe<kj::Own<const kj::ReadableFile>> getArtefact(std::string path) override;
|
||||||
bool handleBadgeRequest(std::string job, std::string& badge) override;
|
bool handleBadgeRequest(std::string job, std::string& badge) override;
|
||||||
std::string getCustomCss() override;
|
std::string getCustomCss() override;
|
||||||
|
bool abort(std::string job, uint buildNum) override;
|
||||||
void abortAll() override;
|
void abortAll() override;
|
||||||
void notifyConfigChanged() override;
|
void notifyConfigChanged() override;
|
||||||
|
|
||||||
|
@ -117,8 +117,8 @@ public:
|
|||||||
|
|
||||||
// Set a parameter on a running build
|
// Set a parameter on a running build
|
||||||
kj::Promise<void> set(SetContext context) override {
|
kj::Promise<void> set(SetContext context) override {
|
||||||
std::string jobName = context.getParams().getJobName();
|
std::string jobName = context.getParams().getRun().getJob();
|
||||||
uint buildNum = context.getParams().getBuildNum();
|
uint buildNum = context.getParams().getRun().getBuildNum();
|
||||||
LLOG(INFO, "RPC set", jobName, buildNum);
|
LLOG(INFO, "RPC set", jobName, buildNum);
|
||||||
|
|
||||||
LaminarCi::MethodResult result = laminar.setParam(jobName, buildNum,
|
LaminarCi::MethodResult result = laminar.setParam(jobName, buildNum,
|
||||||
@ -129,6 +129,52 @@ public:
|
|||||||
return kj::READY_NOW;
|
return kj::READY_NOW;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// List jobs in queue
|
||||||
|
kj::Promise<void> listQueued(ListQueuedContext context) override {
|
||||||
|
const std::list<std::shared_ptr<Run>>& queue = laminar.listQueuedJobs();
|
||||||
|
auto res = context.getResults().initResult(queue.size());
|
||||||
|
int i = 0;
|
||||||
|
for(auto it : queue) {
|
||||||
|
res.set(i++, it->name);
|
||||||
|
}
|
||||||
|
return kj::READY_NOW;
|
||||||
|
}
|
||||||
|
|
||||||
|
// List running jobs
|
||||||
|
kj::Promise<void> listRunning(ListRunningContext context) override {
|
||||||
|
const RunSet& active = laminar.listRunningJobs();
|
||||||
|
auto res = context.getResults().initResult(active.size());
|
||||||
|
int i = 0;
|
||||||
|
for(auto it : active) {
|
||||||
|
res[i].setJob(it->name);
|
||||||
|
res[i].setBuildNum(it->build);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
return kj::READY_NOW;
|
||||||
|
}
|
||||||
|
|
||||||
|
// List known jobs
|
||||||
|
kj::Promise<void> listKnown(ListKnownContext context) override {
|
||||||
|
std::list<std::string> known = laminar.listKnownJobs();
|
||||||
|
auto res = context.getResults().initResult(known.size());
|
||||||
|
int i = 0;
|
||||||
|
for(auto it : known) {
|
||||||
|
res.set(i++, it);
|
||||||
|
}
|
||||||
|
return kj::READY_NOW;
|
||||||
|
}
|
||||||
|
|
||||||
|
kj::Promise<void> abort(AbortContext context) override {
|
||||||
|
std::string jobName = context.getParams().getRun().getJob();
|
||||||
|
uint buildNum = context.getParams().getRun().getBuildNum();
|
||||||
|
LLOG(INFO, "RPC abort", jobName, buildNum);
|
||||||
|
LaminarCi::MethodResult result = laminar.abort(jobName, buildNum)
|
||||||
|
? LaminarCi::MethodResult::SUCCESS
|
||||||
|
: LaminarCi::MethodResult::FAILED;
|
||||||
|
context.getResults().setResult(result);
|
||||||
|
return kj::READY_NOW;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Helper to convert an RPC parameter list to a hash map
|
// Helper to convert an RPC parameter list to a hash map
|
||||||
ParamMap params(const capnp::List<LaminarCi::JobParam>::Reader& paramReader) {
|
ParamMap params(const capnp::List<LaminarCi::JobParam>::Reader& paramReader) {
|
||||||
|
@ -49,8 +49,13 @@ public:
|
|||||||
MOCK_METHOD1(deregisterWaiter, void(LaminarWaiter* waiter));
|
MOCK_METHOD1(deregisterWaiter, void(LaminarWaiter* waiter));
|
||||||
MOCK_METHOD1(sendStatus, void(LaminarClient* client));
|
MOCK_METHOD1(sendStatus, void(LaminarClient* client));
|
||||||
MOCK_METHOD4(setParam, bool(std::string job, uint buildNum, std::string param, std::string value));
|
MOCK_METHOD4(setParam, bool(std::string job, uint buildNum, std::string param, std::string value));
|
||||||
|
MOCK_METHOD0(listQueuedJobs, const std::list<std::shared_ptr<Run>>&());
|
||||||
|
MOCK_METHOD0(listRunningJobs, const RunSet&());
|
||||||
|
MOCK_METHOD0(listKnownJobs, std::list<std::string>());
|
||||||
|
|
||||||
MOCK_METHOD0(getCustomCss, std::string());
|
MOCK_METHOD0(getCustomCss, std::string());
|
||||||
MOCK_METHOD2(handleBadgeRequest, bool(std::string, std::string&));
|
MOCK_METHOD2(handleBadgeRequest, bool(std::string, std::string&));
|
||||||
|
MOCK_METHOD2(abort, bool(std::string, uint));
|
||||||
MOCK_METHOD0(abortAll, void());
|
MOCK_METHOD0(abortAll, void());
|
||||||
MOCK_METHOD0(notifyConfigChanged, void());
|
MOCK_METHOD0(notifyConfigChanged, void());
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user