Skip to content

Commit

Permalink
Merge jobs and executor (#20)
Browse files Browse the repository at this point in the history
* Merge jobs and executor
* Renamed executors to jobs
* Renamed commands directory
* Moved application to separate file
* No more models
* Capturing multiple signals in one block
* Adjusting config tests
* Adjusting coding style
  • Loading branch information
marghidanu authored Dec 27, 2021
1 parent cab2099 commit 253cc6b
Show file tree
Hide file tree
Showing 14 changed files with 285 additions and 284 deletions.
56 changes: 35 additions & 21 deletions spec/config_spec.cr
Original file line number Diff line number Diff line change
@@ -1,37 +1,51 @@
require "./spec_helper"

describe "Config" do
it "should load the config file" do
config = Werk::Model::Config.load_file("werk.yml")

config.is_a?(Werk::Model::Config).should eq true
it "empty" do
expect_raises(Exception, "Empty configuration!") do
config = Werk::Config.load_string("")
end
end

it "should fail for non existing file" do
expect_raises(Exception, "Configuration file missing!") do
Werk::Model::Config.load_file("werk.yaml")
it "invalid" do
expect_raises(Exception, /^Parse error/) do
config = Werk::Config.load_string(%(
version: 1
jobs:
main:
executor: shell
))
end
end

it "should fail for empty content" do
temp = File.tempfile
File.write(temp.path, "")
it "valid" do
config = Werk::Config.load_string(%(
version: 1.0
expect_raises(Exception, "Configuration file is empty!") do
Werk::Model::Config.load_file(temp.path)
end
jobs:
shell:
executor: local
container:
executor: docker
))

temp.delete
config.should be_a Werk::Config
config.version.should eq "1.0"
config.jobs.keys.should eq ["shell", "container"]

config.jobs["shell"].should be_a Werk::Job::Local
config.jobs["container"].should be_a Werk::Job::Docker
end

it "should fail with parsing error" do
temp = File.tempfile
File.write(temp.path, "jobs: []")
it "should load the config file" do
config = Werk::Config.load_file("werk.yml")
config.should be_a Werk::Config
end

expect_raises(Exception, "Parse error at line 1, column 7") do
Werk::Model::Config.load_file(temp.path)
it "should fail for non existing file" do
expect_raises(Exception, "Configuration file missing!") do
Werk::Config.load_file("werk.yaml")
end

temp.delete
end
end
6 changes: 4 additions & 2 deletions spec/spec_helper.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
require "spec"
require "../src/model/*"
require "../src/executor/*"

require "../src/config"
require "../src/report"
require "../src/jobs/*"
require "../src/utils/*"
21 changes: 21 additions & 0 deletions src/application.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
require "admiral"

require "./version"
require "./commands/*"

module Werk
class Application < Admiral::Command
define_version Werk::VERSION
define_help description: "Werk"

register_sub_command plan : Werk::Command::Plan,
description: "Display execution plan"

register_sub_command run : Werk::Command::Run,
description: "Run a job by name"

def run
puts help
end
end
end
5 changes: 3 additions & 2 deletions src/command/plan.cr → src/commands/plan.cr
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
require "admiral"
require "tallboy"
require "colorize"

require "../model/*"
require "../config"
require "../scheduler"

module Werk
Expand All @@ -22,7 +23,7 @@ module Werk
long: "stdin"

def run
config = (flags.stdin) ? Werk::Model::Config.load_string(STDIN.gets_to_end) : Werk::Model::Config.load_file(flags.config)
config = (flags.stdin) ? Werk::Config.load_string(STDIN.gets_to_end) : Werk::Config.load_file(flags.config)

target = arguments.target || "main"
plan = Werk::Scheduler.new(config).get_plan(target)
Expand Down
30 changes: 16 additions & 14 deletions src/command/run.cr → src/commands/run.cr
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
require "admiral"
require "log"
require "tallboy"
require "colorize"
require "docr"

require "../model/*"
require "../config"
require "../scheduler"

module Werk
Expand Down Expand Up @@ -49,7 +50,7 @@ module Werk
short: "e"

def run
config = (flags.stdin) ? Werk::Model::Config.load_string(STDIN.gets_to_end) : Werk::Model::Config.load_file(flags.config)
config = (flags.stdin) ? Werk::Config.load_string(STDIN.gets_to_end) : Werk::Config.load_file(flags.config)

# Parsing additional variables
variables = Hash(String, String).new
Expand All @@ -59,20 +60,19 @@ module Werk
end

# Override max_jobs if a different value is specified ar an flag
config.max_jobs = flags.max_jobs if flags.max_jobs > 0
if flags.max_jobs > 0
config.max_jobs = flags.max_jobs
end

# Creating the scheduler ...
scheduler = Werk::Scheduler.new(config)

Signal::INT.trap {
Log.debug { "Captured SIGINT!" }
cleanup(scheduler.session_id)
}

Signal::TERM.trap {
Log.debug { "Captured SIGTERM!" }
cleanup(scheduler.session_id)
}
[Signal::INT, Signal::TERM].each do |signal|
signal.trap {
Log.debug { "Captured #{signal}!" }
cleanup(scheduler.session_id)
}
end

# ... and running the job
report = scheduler.run(
Expand All @@ -81,7 +81,9 @@ module Werk
variables: variables,
)

display_report(report) if flags.report
if flags.report
display_report(report)
end
end

def display_report(report)
Expand Down Expand Up @@ -139,7 +141,7 @@ module Werk
rescue ex
Log.debug { ex.message }
ensure
exit(1)
exit 1
end
end
end
109 changes: 109 additions & 0 deletions src/config.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
require "yaml"

module Werk
class Config
include YAML::Serializable

# Configuration file version.
@[YAML::Field(key: "version")]
getter version = "1"

# Description for the configuration file
@[YAML::Field(key: "description")]
getter description = ""

@[YAML::Field(key: "dotenv")]
getter dotenv = Set(String).new

# List of global variables
@[YAML::Field(key: "variables")]
property variables = Hash(String, String).new

@[YAML::Field(key: "max_jobs")]
property max_jobs : UInt32 = 32_u32

# Jobs available in the current configuration
@[YAML::Field(key: "jobs")]
getter jobs = Hash(String, Config::Job).new

# Load configuration from file
def self.load_file(path : String)
unless File.exists?(path)
raise "Configuration file missing!"
end

content = File.read(path)
self.load_string(content)
end

def self.load_string(content : String)
if content.empty?
raise "Empty configuration!"
end

self.from_yaml(content)
rescue yaml_ex : YAML::ParseException
raise "Parse error at line #{yaml_ex.line_number}, column #{yaml_ex.column_number}"
end

abstract class Job
include YAML::Serializable

# The description for the job
@[YAML::Field(key: "description")]
getter description = ""

# List of dotenv files to be loaded
@[YAML::Field(key: "dotenv")]
getter dotenv = Set(String).new

# A list of variables to be passed to the job
@[YAML::Field(key: "variables")]
property variables = Hash(String, String).new

# List commands
@[YAML::Field(key: "commands")]
getter commands = Array(String).new

# Dependencies list
@[YAML::Field(key: "needs")]
getter needs = Array(String).new

# Signals if the job is allowed to fail or not.
@[YAML::Field(key: "can_fail")]
getter can_fail = false

# Suppress job output to STDOUT
@[YAML::Field(key: "silent")]
getter silent = false

@[YAML::Field(key: "executor")]
getter executor : String

@[YAML::Field(key: "interpreter")]
getter interpreter = "/bin/sh"

use_yaml_discriminator "executor", {
local: Werk::Job::Local,
docker: Werk::Job::Docker,
}

abstract def run(session_id : UUID, name : String, context : String) : {Int32, String}

def get_script_content
[
"#!#{@interpreter}",
].concat(@commands).join("\n")
end

def get_script_file
script = File.tempfile
content = get_script_content
File.write(script.path, content)
File.chmod(script.path, 0o755)

script
end
end
end
end
7 changes: 0 additions & 7 deletions src/executor.cr

This file was deleted.

40 changes: 21 additions & 19 deletions src/executor/docker.cr → src/jobs/docker.cr
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,39 @@ require "digest/md5"
require "docr"
require "log"

require "../executor"

module Werk
class Executor::Docker < Werk::Executor
Log = ::Log.for(self)
class Job::Docker < Config::Job
@[YAML::Field(key: "image")]
getter image = "alpine:latest"

@[YAML::Field(key: "volumes")]
getter volumes = Array(String).new

def run(job : Werk::Model::Job, session_id : UUID, name : String, context : String) : {Int32, String}
job = job.as(Werk::Model::Job::Docker)
@[YAML::Field(key: "entrypoint")]
getter entrypoint = ["/bin/sh"]

Log = ::Log.for(self)

script = File.tempfile
content = job.get_script_content
File.write(script.path, content)
File.chmod(script.path, 0o755)
def run(session_id : UUID, name : String, context : String) : {Int32, String}
script = get_script_file
Log.debug { "Created temporary script file #{script.path}" }

buffer_io = IO::Memory.new
writers = Array(IO).new
writers << buffer_io
writers << Werk::Utils::PrefixIO.new(STDOUT, name) unless job.silent
writers << Werk::Utils::PrefixIO.new(STDOUT, name) unless @silent
output_io = IO::MultiWriter.new(writers)

client = Docr::Client.new
api = Docr::API.new(client)

begin
# Checking if the image exists locally
api.images.inspect(job.image)
Log.debug { "Image #{job.image} was found locally" }
api.images.inspect(@image)
Log.debug { "Image #{@image} was found locally" }
rescue
Log.debug { "Fetching image #{job.image}" }
repository, tag = Docr::Utils.parse_repository_tag(job.image)
Log.debug { "Fetching image #{@image}" }
repository, tag = Docr::Utils.parse_repository_tag(@image)
api.images.create(repository, tag)
end

Expand All @@ -42,16 +44,16 @@ module Werk
container = api.containers.create(
container_name,
Docr::Types::CreateContainerConfig.new(
image: job.image,
entrypoint: job.entrypoint,
image: @image,
entrypoint: @entrypoint,
cmd: ["/opt/start.sh"],
working_dir: "/opt/workspace",
env: job.variables.map { |k, v| "#{k}=#{v}" },
env: @variables.map { |k, v| "#{k}=#{v}" },
host_config: Docr::Types::HostConfig.new(
binds: [
"#{script.path}:/opt/start.sh",
"#{Path[context].expand}:/opt/workspace",
].concat(job.volumes)
].concat(@volumes)
),
labels: {
"com.stuffo.werk.name" => name,
Expand Down
Loading

0 comments on commit 253cc6b

Please sign in to comment.