Docker.vala 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. using GLib;
  2. namespace Spry.Cli {
  3. /**
  4. * `spry docker` — generates the multi-stage `Dockerfile` (and
  5. * `.dockerignore`) for a spry application and optionally assembles the
  6. * build context and runs podman/docker.
  7. *
  8. * The Web-Stack trees carry fixes not present on their remotes, so the
  9. * image build COPYs every stack library from the local Web-Stack
  10. * checkout. The context layout the Dockerfile expects is invercargill/,
  11. * invercargill-json/, astralis/, invercargill-sql/,
  12. * invercargill-sql-inversion/, statum/, spry/ and the application as
  13. * app/; inversion, the one dependency outside Web-Stack, is cloned
  14. * inside the image pinned to a reviewed commit.
  15. */
  16. public class Docker : Object {
  17. /**
  18. * The Web-Stack projects COPYed into the build context, in the
  19. * dependency order the Dockerfile builds them.
  20. */
  21. private const string[] STACK_PROJECTS = {
  22. "Invercargill", "Invercargill-Json", "Astralis", "Invercargill-Sql",
  23. "Invercargill-Sql-Inversion", "Statum", "Spry"
  24. };
  25. /** Writes `Dockerfile` and `.dockerignore` into `app_dir`. */
  26. public static void generate(string app_dir, string app_name) throws Error {
  27. var vars = new HashTable<string, string>(str_hash, str_equal);
  28. vars.insert("APP_NAME", app_name);
  29. Generator.write_file(Path.build_filename(app_dir, "Dockerfile"),
  30. Generator.fill(Templates.DOCKERFILE, vars));
  31. Generator.write_file(Path.build_filename(app_dir, ".dockerignore"),
  32. Templates.DOCKERIGNORE);
  33. stdout.printf("Generated %s\n", Path.build_filename(app_dir, "Dockerfile"));
  34. stdout.printf("Generated %s\n", Path.build_filename(app_dir, ".dockerignore"));
  35. }
  36. /**
  37. * Assembles the build context under `app_dir/.docker-build/context`
  38. * (every Web-Stack project from `stack_dir`, the app itself as
  39. * app/) and builds the image with podman or docker, whichever is
  40. * found.
  41. */
  42. public static void build(string app_dir, string app_name, string image_tag, string stack_dir) throws Error {
  43. var staging = Path.build_filename(app_dir, ".docker-build");
  44. Generator.remove_tree(staging);
  45. var context = Path.build_filename(staging, "context");
  46. foreach (var project in STACK_PROJECTS) {
  47. Generator.copy_tree(find_stack_project(stack_dir, project),
  48. Path.build_filename(context, project.down()));
  49. }
  50. Generator.copy_tree(app_dir, Path.build_filename(context, "app"));
  51. Generator.write_file(Path.build_filename(context, ".dockerignore"),
  52. "*/builddir/\n*/.git/\n*.sqlite*\napp/.docker-build/\napp/web-config.json\n");
  53. var builder = Environment.find_program_in_path("podman")
  54. ?? Environment.find_program_in_path("docker");
  55. if (builder == null) {
  56. throw Generator.cli_error("neither podman nor docker found on PATH");
  57. }
  58. var dockerfile = Path.build_filename(context, "app", "Dockerfile");
  59. stdout.printf("Running: %s build -t %s -f %s %s\n", (!)builder, image_tag, dockerfile, context);
  60. var launcher = new SubprocessLauncher(SubprocessFlags.NONE);
  61. var process = launcher.spawnv({ (!)builder, "build", "-t", image_tag, "-f", dockerfile, context });
  62. process.wait_check();
  63. stdout.printf("Built %s\n", image_tag);
  64. }
  65. private static string find_stack_project(string stack_dir, string name) throws Error {
  66. foreach (var candidate in new string[] { name, name.down() }) {
  67. var path = Path.build_filename(stack_dir, candidate);
  68. if (FileUtils.test(path, FileTest.IS_DIR) && has_meson_manifest(path)) {
  69. return path;
  70. }
  71. }
  72. throw Generator.cli_error("%s not found in %s — pass --stack-dir <path-to-Web-Stack>", name, stack_dir);
  73. }
  74. /**
  75. * True when `path` carries a meson manifest, either at its root or
  76. * in `src/` (Invercargill and Invercargill-Json build from src/).
  77. */
  78. private static bool has_meson_manifest(string path) {
  79. return FileUtils.test(Path.build_filename(path, "meson.build"), FileTest.EXISTS)
  80. || FileUtils.test(Path.build_filename(path, "src", "meson.build"), FileTest.EXISTS);
  81. }
  82. }
  83. }