Skip to content

Commit

Permalink
fix(containers): ensure containers are destroyed when wing console is…
Browse files Browse the repository at this point in the history
… interrupted
  • Loading branch information
skyrpex committed Mar 14, 2024
1 parent e237587 commit fa3a2b2
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 95 deletions.
4 changes: 2 additions & 2 deletions containers/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion containers/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@winglibs/containers",
"version": "0.0.22",
"version": "0.0.23",
"description": "Container support for Wing",
"repository": {
"type": "git",
Expand Down
25 changes: 19 additions & 6 deletions containers/utils.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
const child_process = require("child_process");
const fs = require('fs');
const crypto = require('crypto');
const glob = require('glob');
const path = require('path');
const fs = require("fs");
const crypto = require("crypto");
const glob = require("glob");
const path = require("path");

exports.spawn = async (options) => {
const child = child_process.spawn(options.command, options.arguments, {
cwd: options.cwd,
stdio: options.stdio,
});

return {
kill() {
child.kill("SIGINT");
},
};
};

exports.shell = async function (command, args, cwd) {
return new Promise((resolve, reject) => {
Expand All @@ -29,8 +42,8 @@ exports.dirname = function() {
return __dirname;
};

exports.contentHash = function(patterns, cwd) {
const hash = crypto.createHash('md5');
exports.contentHash = function (patterns, cwd) {
const hash = crypto.createHash("md5");
const files = glob.sync(patterns, { nodir: true, cwd });
for (const f of files) {
const data = fs.readFileSync(path.join(cwd, f));
Expand Down
15 changes: 13 additions & 2 deletions containers/utils.w
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
bring "./api.w" as api;
bring fs;

interface Process {
inflight kill(): void;
}

struct SpawnOptions {
command: str;
arguments: Array<str>;
stdio: str?;
}

pub class Util {
extern "./utils.js" pub static inflight spawn(options: SpawnOptions): Process;
extern "./utils.js" pub static inflight shell(command: str, args: Array<str>, cwd: str?): str;
extern "./utils.js" pub static contentHash(files: Array<str>, cwd: str): str;
extern "./utils.js" pub static dirname(): str;
Expand All @@ -22,9 +33,9 @@ pub class Util {
if !Util.isPath(props.image) {
return nil;
}

let sources = props.sources ?? ["**/*"];
let imageDir = props.image;
return props.sourceHash ?? Util.contentHash(sources, imageDir);
}
}
}
176 changes: 92 additions & 84 deletions containers/workload.sim.w
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub class Workload_sim {
imageTag: str;
public: bool;
state: sim.State;

new(props: api.WorkloadProps) {
this.appDir = utils.entrypointDir(this);
this.props = props;
Expand Down Expand Up @@ -51,108 +51,116 @@ pub class Workload_sim {
}

let s = new cloud.Service(inflight () => {
this.start();
return () => { this.stop(); };
});

std.Node.of(s).hidden = true;
std.Node.of(this.state).hidden = true;
}
log("starting workload...");

let opts = this.props;

// if this a reference to a local directory, build the image from a docker file
if utils.isPathInflight(opts.image) {
// check if the image is already built
try {
utils.shell("docker", ["inspect", this.imageTag]);
log("image {this.imageTag} already exists");
} catch {
log("building locally from {opts.image} and tagging {this.imageTag}...");
utils.shell("docker", ["build", "-t", this.imageTag, opts.image], this.appDir);
}
} else {
try {
utils.shell("docker", ["inspect", this.imageTag]);
log("image {this.imageTag} already exists");
} catch {
log("pulling {this.imageTag}");
utils.shell("docker", ["pull", this.imageTag]);
}
}

inflight start(): void {
log("starting workload...");
// start the new container
let dockerRun = MutArray<str>[];
dockerRun.push("run");
dockerRun.push("--rm");

let opts = this.props;
let name = util.uuidv4();
dockerRun.push("--name", name);

// if this a reference to a local directory, build the image from a docker file
if utils.isPathInflight(opts.image) {
// check if the image is already built
try {
utils.shell("docker", ["inspect", this.imageTag]);
log("image {this.imageTag} already exists");
} catch {
log("building locally from {opts.image} and tagging {this.imageTag}...");
utils.shell("docker", ["build", "-t", this.imageTag, opts.image], this.appDir);
if let port = opts.port {
dockerRun.push("-p");
dockerRun.push("{port}");
}
} else {
try {
utils.shell("docker", ["inspect", this.imageTag]);
log("image {this.imageTag} already exists");
} catch {
log("pulling {this.imageTag}");
utils.shell("docker", ["pull", this.imageTag]);
}
}

// start the new container
let dockerRun = MutArray<str>[];
dockerRun.push("run");
dockerRun.push("--detach");

if let port = opts.port {
dockerRun.push("-p");
dockerRun.push("{port}");
}

if let env = opts.env {
if env.size() > 0 {
dockerRun.push("-e");
for k in env.keys() {
dockerRun.push("{k}={env.get(k)!}");
if let env = opts.env {
if env.size() > 0 {
dockerRun.push("-e");
for k in env.keys() {
dockerRun.push("{k}={env.get(k)!}");
}
}
}
}

dockerRun.push(this.imageTag);
dockerRun.push(this.imageTag);

if let runArgs = this.props.args {
for a in runArgs {
dockerRun.push(a);
if let runArgs = this.props.args {
for a in runArgs {
dockerRun.push(a);
}
}
}

log("starting container from image {this.imageTag}");
log("docker {dockerRun.join(" ")}");
let containerId = utils.shell("docker", dockerRun.copy()).trim();
this.state.set(this.containerIdKey, containerId);
log("starting container from image {this.imageTag}");
log("docker {dockerRun.join(" ")}");
let container = utils.spawn(command: "docker", arguments: dockerRun.copy());
this.state.set(this.containerIdKey, name);

log("containerId={containerId}");
log("containerName={name}");

let out = Json.parse(utils.shell("docker", ["inspect", containerId]));
let var out: Json? = nil;
let inspected = util.waitUntil(inflight () => {
try {
out = Json.parse(utils.shell("docker", ["inspect", name]));
return true;
} catch {
return false;
}
}, interval: 250ms, timeout: 2s);

if let port = opts.port {
let hostPort = out.tryGetAt(0)?.tryGet("NetworkSettings")?.tryGet("Ports")?.tryGet("{port}/tcp")?.tryGetAt(0)?.tryGet("HostPort")?.tryAsStr();
if !hostPort? {
throw "Container does not listen to port {port}";
if inspected == false {
throw "container did not start in time";
}

let publicUrl = "http://localhost:{hostPort!}";
if let port = opts.port {
let hostPort = out?.tryGetAt(0)?.tryGet("NetworkSettings")?.tryGet("Ports")?.tryGet("{port}/tcp")?.tryGetAt(0)?.tryGet("HostPort")?.tryAsStr();
if !hostPort? {
throw "Container does not listen to port {port}";
}

if let k = this.publicUrlKey {
this.state.set(k, publicUrl);
}
let publicUrl = "http://localhost:{hostPort!}";

if let k = this.internalUrlKey {
this.state.set(k, "http://host.docker.internal:{hostPort!}");
}
if let k = this.publicUrlKey {
this.state.set(k, publicUrl);
}

if let readiness = opts.readiness {
let readinessUrl = "{publicUrl}{readiness}";
log("waiting for container to be ready: {readinessUrl}...");
util.waitUntil(inflight () => {
try {
return http.get(readinessUrl).ok;
} catch {
return false;
}
}, interval: 0.1s);
if let k = this.internalUrlKey {
this.state.set(k, "http://host.docker.internal:{hostPort!}");
}

if let readiness = opts.readiness {
let readinessUrl = "{publicUrl}{readiness}";
log("waiting for container to be ready: {readinessUrl}...");
util.waitUntil(inflight () => {
try {
return http.get(readinessUrl).ok;
} catch {
return false;
}
}, interval: 0.1s);
}
}
}
}

inflight stop() {
let containerId = this.state.get(this.containerIdKey).asStr();
log("stopping container {containerId}");
utils.shell("docker", ["rm", "-f", containerId]);
return () => {
container.kill();
};
});

std.Node.of(s).hidden = true;
std.Node.of(this.state).hidden = true;
}
}
}

0 comments on commit fa3a2b2

Please sign in to comment.