semaph 0.3.0 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +39 -0
- data/.semaph +9 -0
- data/.semaphore/semaphore.yml +5 -1
- data/Gemfile.lock +21 -1
- data/README.md +12 -8
- data/Rakefile +4 -1
- data/lib/semaph.rb +43 -8
- data/lib/semaph/{api.rb → client.rb} +24 -3
- data/lib/semaph/commands.rb +63 -0
- data/lib/semaph/commands/rerun_workflow_command.rb +0 -2
- data/lib/semaph/commands/stop_workflow_command.rb +0 -2
- data/lib/semaph/formatting.rb +15 -1
- data/lib/semaph/model/job.rb +29 -2
- data/lib/semaph/model/job_collection.rb +21 -5
- data/lib/semaph/model/pipeline.rb +29 -3
- data/lib/semaph/model/promotion.rb +31 -0
- data/lib/semaph/model/promotion_collection.rb +21 -0
- data/lib/semaph/model/workflow.rb +17 -3
- data/lib/semaph/shells/organisation/organisation_shell.rb +4 -4
- data/lib/semaph/shells/organisation/projects_select_command.rb +6 -0
- data/lib/semaph/shells/organisations/organisations_list_command.rb +2 -2
- data/lib/semaph/shells/organisations/organisations_select_command.rb +11 -2
- data/lib/semaph/shells/organisations/organisations_shell.rb +2 -2
- data/lib/semaph/shells/pipeline/{jobs_logs_command.rb → job_debug_command.rb} +4 -5
- data/lib/semaph/shells/pipeline/job_log_command.rb +44 -0
- data/lib/semaph/shells/pipeline/job_log_grep_command.rb +28 -0
- data/lib/semaph/shells/pipeline/job_show_command.rb +28 -0
- data/lib/semaph/shells/pipeline/job_stop_command.rb +28 -0
- data/lib/semaph/shells/pipeline/jobs_list_command.rb +4 -2
- data/lib/semaph/shells/pipeline/jobs_poll_command.rb +57 -22
- data/lib/semaph/shells/pipeline/pipeline_shell.rb +29 -67
- data/lib/semaph/shells/pipeline/promote_command.rb +21 -0
- data/lib/semaph/shells/pipeline/promotions_list_command.rb +21 -0
- data/lib/semaph/shells/project/project_shell.rb +4 -2
- data/lib/semaph/shells/project/save_command.rb +26 -0
- data/lib/semaph/shells/project/workflows_list_command.rb +3 -16
- data/lib/semaph/shells/workflow/pipelines_list_command.rb +3 -1
- data/lib/semaph/shells/workflow/workflow_shell.rb +6 -66
- data/lib/semaph/version.rb +1 -1
- data/semaph.gemspec +1 -0
- metadata +32 -7
@@ -1,9 +1,10 @@
|
|
1
|
+
require "date"
|
1
2
|
require "semaph/model/job"
|
2
3
|
|
3
4
|
module Semaph
|
4
5
|
module Model
|
5
6
|
class JobCollection
|
6
|
-
attr_reader :all
|
7
|
+
attr_reader :all, :pipeline, :stopping_at, :running_at, :queuing_at, :pending_at, :done_at, :created_at
|
7
8
|
|
8
9
|
def initialize(pipeline)
|
9
10
|
@pipeline = pipeline
|
@@ -12,20 +13,21 @@ module Semaph
|
|
12
13
|
def reload
|
13
14
|
workflow = @pipeline.workflow
|
14
15
|
project = workflow.project
|
15
|
-
@all =
|
16
|
+
@all = parse_content(project.client.pipeline(@pipeline.id))
|
16
17
|
end
|
17
18
|
|
18
19
|
def incomplete
|
19
|
-
@all.reject
|
20
|
+
@all.reject(&:finished?)
|
20
21
|
end
|
21
22
|
|
22
23
|
def failed
|
23
|
-
@all.select
|
24
|
+
@all.select(&:failed?)
|
24
25
|
end
|
25
26
|
|
26
27
|
private
|
27
28
|
|
28
|
-
def
|
29
|
+
def parse_content(content)
|
30
|
+
parse_pipeline(content["pipeline"])
|
29
31
|
result = []
|
30
32
|
blocks = content.delete("blocks")
|
31
33
|
blocks.each do |block|
|
@@ -35,6 +37,15 @@ module Semaph
|
|
35
37
|
result
|
36
38
|
end
|
37
39
|
|
40
|
+
def parse_pipeline(pipeline_content)
|
41
|
+
with_time(pipeline_content["stopping_at"]) { |time| @stopping_at = time }
|
42
|
+
with_time(pipeline_content["running_at"]) { |time| @running_at = time }
|
43
|
+
with_time(pipeline_content["queuing_at"]) { |time| @queuing_at = time }
|
44
|
+
with_time(pipeline_content["pending_at"]) { |time| @pending_at = time }
|
45
|
+
with_time(pipeline_content["done_at"]) { |time| @done_at = time }
|
46
|
+
with_time(pipeline_content["created_at"]) { |time| @created_at = time }
|
47
|
+
end
|
48
|
+
|
38
49
|
def append_jobs(result, block, jobs)
|
39
50
|
if jobs.count.positive?
|
40
51
|
jobs.each { |job| result << Job.new(@pipeline, block, job) }
|
@@ -42,6 +53,11 @@ module Semaph
|
|
42
53
|
result << Job.new(@pipeline, block, {})
|
43
54
|
end
|
44
55
|
end
|
56
|
+
|
57
|
+
def with_time(string)
|
58
|
+
time = DateTime.parse(string).to_time
|
59
|
+
yield time unless time.to_i.zero?
|
60
|
+
end
|
45
61
|
end
|
46
62
|
end
|
47
63
|
end
|
@@ -1,15 +1,18 @@
|
|
1
|
+
require "semaph/formatting"
|
1
2
|
require "semaph/model/job_collection"
|
3
|
+
require "semaph/model/promotion_collection"
|
2
4
|
|
3
5
|
module Semaph
|
4
6
|
module Model
|
5
7
|
class Pipeline
|
6
|
-
attr_reader :workflow, :raw, :id, :yaml, :state, :result
|
8
|
+
attr_reader :workflow, :raw, :id, :name, :yaml, :state, :result
|
7
9
|
|
8
10
|
def initialize(workflow, raw)
|
9
11
|
@workflow = workflow
|
10
12
|
@raw = raw
|
11
13
|
@id = raw["ppl_id"]
|
12
14
|
@yaml = raw["yaml_file_name"]
|
15
|
+
@name = raw["name"]
|
13
16
|
@state = raw["state"]
|
14
17
|
@result = raw["result"]
|
15
18
|
%w[created done pending queuing running stopping].each do |name|
|
@@ -17,16 +20,33 @@ module Semaph
|
|
17
20
|
end
|
18
21
|
end
|
19
22
|
|
23
|
+
def promote(name)
|
24
|
+
workflow.project.client.promote(id, name)
|
25
|
+
end
|
26
|
+
|
20
27
|
def job_collection
|
21
28
|
@job_collection ||= JobCollection.new(self)
|
22
29
|
end
|
23
30
|
|
31
|
+
def promotion_collection
|
32
|
+
@promotion_collection ||= PromotionCollection.new(self)
|
33
|
+
end
|
34
|
+
|
24
35
|
def description
|
25
|
-
|
36
|
+
[
|
37
|
+
icon,
|
38
|
+
time,
|
39
|
+
name,
|
40
|
+
"(#{yaml})",
|
41
|
+
].compact.join(" ")
|
42
|
+
end
|
43
|
+
|
44
|
+
def done?
|
45
|
+
@state == "DONE"
|
26
46
|
end
|
27
47
|
|
28
48
|
def icon
|
29
|
-
return "🔵" unless
|
49
|
+
return "🔵" unless done?
|
30
50
|
|
31
51
|
return "⛔" if @result == "STOPPED"
|
32
52
|
|
@@ -37,6 +57,12 @@ module Semaph
|
|
37
57
|
|
38
58
|
private
|
39
59
|
|
60
|
+
def time
|
61
|
+
return ::Semaph::Formatting.hours_minutes_seconds(@done_at.to_i - @created_at.to_i) if done?
|
62
|
+
|
63
|
+
::Semaph::Formatting.hours_minutes_seconds(Time.now.to_i - @created_at.to_i)
|
64
|
+
end
|
65
|
+
|
40
66
|
def extract_time(name)
|
41
67
|
key = "#{name}_at"
|
42
68
|
return if raw[key]["seconds"].zero?
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "semaph/formatting"
|
2
|
+
|
3
|
+
module Semaph
|
4
|
+
module Model
|
5
|
+
class Promotion
|
6
|
+
attr_reader :pipeline, :raw
|
7
|
+
|
8
|
+
def initialize(pipeline, raw)
|
9
|
+
@pipeline = pipeline
|
10
|
+
@raw = raw
|
11
|
+
@name = raw["name"]
|
12
|
+
@status = raw["status"]
|
13
|
+
@triggered_at = Time.at(raw["triggered_at"]["seconds"])
|
14
|
+
end
|
15
|
+
|
16
|
+
def description
|
17
|
+
[
|
18
|
+
status_icon,
|
19
|
+
Semaph::Formatting.time(@triggered_at),
|
20
|
+
@name,
|
21
|
+
].join(" ")
|
22
|
+
end
|
23
|
+
|
24
|
+
def status_icon
|
25
|
+
return "🟢" if @status == "passed"
|
26
|
+
|
27
|
+
"🔴"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require "semaph/model/promotion"
|
2
|
+
|
3
|
+
module Semaph
|
4
|
+
module Model
|
5
|
+
class PromotionCollection
|
6
|
+
attr_reader :all, :pipeline
|
7
|
+
|
8
|
+
def initialize(pipeline)
|
9
|
+
@pipeline = pipeline
|
10
|
+
end
|
11
|
+
|
12
|
+
def reload
|
13
|
+
workflow = @pipeline.workflow
|
14
|
+
project = workflow.project
|
15
|
+
@all = project.client.promotions(@pipeline.id).map do |promotion_response|
|
16
|
+
Promotion.new(@pipeline, promotion_response)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -1,19 +1,25 @@
|
|
1
|
+
require "semaph/formatting"
|
1
2
|
require "semaph/model/pipeline_collection"
|
2
3
|
|
3
4
|
module Semaph
|
4
5
|
module Model
|
5
6
|
class Workflow
|
6
|
-
attr_reader :project, :raw, :id, :sha, :branch, :branch_id, :created_at
|
7
|
+
attr_reader :project, :raw, :id, :sha, :commit, :branch, :branch_id, :created_at
|
7
8
|
|
8
9
|
def initialize(project, raw)
|
9
10
|
@project = project
|
10
11
|
@raw = raw
|
11
12
|
@id = raw["wf_id"]
|
12
|
-
@sha = raw["commit_sha"]
|
13
13
|
@created_at = Time.at(raw["created_at"]["seconds"].to_i)
|
14
14
|
@branch = raw["branch_name"]
|
15
15
|
@branch_id = raw["branch_id"]
|
16
|
-
|
16
|
+
extract_git_details
|
17
|
+
end
|
18
|
+
|
19
|
+
def extract_git_details
|
20
|
+
@sha = raw["commit_sha"]
|
21
|
+
@commit = @sha.slice(0..10)
|
22
|
+
@commit = `git log -n 1 --format="%h %an %s" #{sha}`.chomp if `git cat-file -t #{sha} 2>&1`.chomp == "commit"
|
17
23
|
end
|
18
24
|
|
19
25
|
def pipeline_collection
|
@@ -26,6 +32,14 @@ module Semaph
|
|
26
32
|
Workflow.new(project, workflow_response["workflow"])
|
27
33
|
end
|
28
34
|
|
35
|
+
def description
|
36
|
+
[
|
37
|
+
Semaph::Formatting.time(created_at),
|
38
|
+
branch,
|
39
|
+
commit,
|
40
|
+
].join(" ")
|
41
|
+
end
|
42
|
+
|
29
43
|
def stop
|
30
44
|
project.client.stop_workflow(@id)
|
31
45
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require "semaph/
|
1
|
+
require "semaph/client"
|
2
2
|
require "semaph/commands/reload_command"
|
3
3
|
require "semaph/model/project_collection"
|
4
4
|
require "semaph/shells/organisation/projects_list_command"
|
@@ -12,7 +12,7 @@ module Semaph
|
|
12
12
|
include ShellShock::Context
|
13
13
|
|
14
14
|
def initialize(organisation)
|
15
|
-
@client = ::Semaph::
|
15
|
+
@client = ::Semaph::Client.new(organisation["auth"]["token"], organisation["host"])
|
16
16
|
@prompt = "🏗 #{@client.name} > "
|
17
17
|
add_commands
|
18
18
|
@project_list_command.execute("")
|
@@ -23,8 +23,8 @@ module Semaph
|
|
23
23
|
def add_commands
|
24
24
|
project_collection = ::Semaph::Model::ProjectCollection.new(@client)
|
25
25
|
@project_list_command = ProjectsListCommand.new(project_collection)
|
26
|
-
add_command @project_list_command, "list-projects"
|
27
|
-
add_command ProjectsSelectCommand.new(project_collection), "select-project"
|
26
|
+
add_command @project_list_command, "list-projects", "ls"
|
27
|
+
add_command ProjectsSelectCommand.new(project_collection), "select-project", "cd"
|
28
28
|
add_command ::Semaph::Commands::ReloadCommand.new, "reload" if ENV["SEMAPH_RELOAD"]
|
29
29
|
end
|
30
30
|
end
|
@@ -18,6 +18,12 @@ module Semaph
|
|
18
18
|
|
19
19
|
def execute(name)
|
20
20
|
selected_project = @project_collection.all.find { |project| project.name == name }
|
21
|
+
|
22
|
+
unless selected_project
|
23
|
+
puts "There is no project called #{name}"
|
24
|
+
return
|
25
|
+
end
|
26
|
+
|
21
27
|
::Semaph::Shells::Project::ProjectShell.new(selected_project).push
|
22
28
|
end
|
23
29
|
end
|
@@ -13,11 +13,20 @@ module Semaph
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def completion(text)
|
16
|
-
@organisations.
|
16
|
+
@organisations.map { |org| org["host"].split(".").first }.grep(/^#{text}/).sort
|
17
17
|
end
|
18
18
|
|
19
19
|
def execute(name)
|
20
|
-
|
20
|
+
organisation = @organisations.find do |org|
|
21
|
+
org["host"].split(".").first == name
|
22
|
+
end
|
23
|
+
|
24
|
+
unless organisation
|
25
|
+
puts "There is no organisation called #{name}"
|
26
|
+
return
|
27
|
+
end
|
28
|
+
|
29
|
+
::Semaph::Shells::Organisation::OrganisationShell.new(organisation).push
|
21
30
|
end
|
22
31
|
end
|
23
32
|
end
|
@@ -13,8 +13,8 @@ module Semaph
|
|
13
13
|
@organisations = organisations
|
14
14
|
@prompt = "🏗 > "
|
15
15
|
organisations_list_command = OrganisationsListCommand.new(organisations)
|
16
|
-
add_command organisations_list_command, "list-organisations"
|
17
|
-
add_command OrganisationsSelectCommand.new(organisations), "select-organisation"
|
16
|
+
add_command organisations_list_command, "list-organisations", "ls"
|
17
|
+
add_command OrganisationsSelectCommand.new(organisations), "select-organisation", "cd"
|
18
18
|
add_command ::Semaph::Commands::ReloadCommand.new, "reload" if ENV["SEMAPH_RELOAD"]
|
19
19
|
organisations_list_command.execute("")
|
20
20
|
end
|
@@ -1,13 +1,13 @@
|
|
1
1
|
module Semaph
|
2
2
|
module Shells
|
3
3
|
module Pipeline
|
4
|
-
class
|
4
|
+
class JobDebugCommand
|
5
5
|
attr_reader :usage, :help
|
6
6
|
|
7
7
|
def initialize(job_collection)
|
8
8
|
@job_collection = job_collection
|
9
9
|
@usage = "<job index>"
|
10
|
-
@help = "
|
10
|
+
@help = "debug job"
|
11
11
|
end
|
12
12
|
|
13
13
|
def execute(index_string)
|
@@ -20,9 +20,8 @@ module Semaph
|
|
20
20
|
return
|
21
21
|
end
|
22
22
|
|
23
|
-
|
24
|
-
|
25
|
-
system("less #{filename}")
|
23
|
+
puts "Debugging #{job.description}"
|
24
|
+
system("sem debug job #{job.id}")
|
26
25
|
end
|
27
26
|
end
|
28
27
|
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
|
3
|
+
module Semaph
|
4
|
+
module Shells
|
5
|
+
module Pipeline
|
6
|
+
class JobLogCommand
|
7
|
+
attr_reader :usage, :help, :job_collection
|
8
|
+
|
9
|
+
def initialize(job_collection)
|
10
|
+
@job_collection = job_collection
|
11
|
+
@usage = "<job index>"
|
12
|
+
@help = "retrieve log for job"
|
13
|
+
end
|
14
|
+
|
15
|
+
def execute(index_string)
|
16
|
+
with_job(index_string) { |job| system(command(job)) }
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def command(job)
|
22
|
+
base = "tmp/logs/pipeline/#{job_collection.pipeline.id}"
|
23
|
+
|
24
|
+
return "less #{job.write_log(base)}" if job.finished?
|
25
|
+
|
26
|
+
"open https://#{job_collection.pipeline.workflow.project.client.host}/jobs/#{job.id}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def with_job(index_string)
|
30
|
+
index = index_string.to_i - 1
|
31
|
+
|
32
|
+
job = job_collection.all[index]
|
33
|
+
|
34
|
+
unless job
|
35
|
+
puts "There is no job at position #{index}"
|
36
|
+
return
|
37
|
+
end
|
38
|
+
|
39
|
+
yield job
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Semaph
|
2
|
+
module Shells
|
3
|
+
module Pipeline
|
4
|
+
class JobLogGrepCommand
|
5
|
+
attr_reader :usage, :help, :job_collection
|
6
|
+
|
7
|
+
def initialize(job_collection, scope)
|
8
|
+
@job_collection = job_collection
|
9
|
+
@scope = scope
|
10
|
+
@usage = "<expression>"
|
11
|
+
@help = "retrieve logs for #{scope} jobs and grep for text"
|
12
|
+
end
|
13
|
+
|
14
|
+
def execute(expression)
|
15
|
+
base = "tmp/logs/pipeline/#{job_collection.pipeline.id}"
|
16
|
+
@job_collection.send(@scope).each do |job|
|
17
|
+
unless job.finished?
|
18
|
+
puts "skipping incomplete job #{job.id}"
|
19
|
+
next
|
20
|
+
end
|
21
|
+
job.write_log(base)
|
22
|
+
end
|
23
|
+
system("ag #{expression} #{base} | less")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Semaph
|
2
|
+
module Shells
|
3
|
+
module Pipeline
|
4
|
+
class JobShowCommand
|
5
|
+
attr_reader :usage, :help
|
6
|
+
|
7
|
+
def initialize(job_collection)
|
8
|
+
@job_collection = job_collection
|
9
|
+
@usage = "<job index>"
|
10
|
+
@help = "show job"
|
11
|
+
end
|
12
|
+
|
13
|
+
def execute(index_string)
|
14
|
+
index = index_string.to_i - 1
|
15
|
+
|
16
|
+
job = @job_collection.all[index]
|
17
|
+
|
18
|
+
unless job
|
19
|
+
puts "There is no job at position #{index}"
|
20
|
+
return
|
21
|
+
end
|
22
|
+
|
23
|
+
puts job.show
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|