renuo-cli 4.24.0 → 4.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: df7e7a6d263ccafd4c253e16e68a8ce4dbb489e1f726d6f8e55988fa82a3c9f3
4
- data.tar.gz: be67e8df4fedb4a8e08913e2758a83ab43a4ee5e7117277db93a4fac64e6326e
3
+ metadata.gz: 2db0ed70e43dfb8e6c5d05d3d08ed4313e7bb15a418cf05920561d55a4973c3e
4
+ data.tar.gz: a68fd2324f7d09dac3e1bed32f2d3b8ba3dccaf8ccc6e847b9d9585adc3d5c2a
5
5
  SHA512:
6
- metadata.gz: bf10d74c836b636236cedf3acceea7cf01ee682ef5f79d153f5d4c08a39a0d17ff8f574e3c49e6505e289ed4b178dedf2eca1ec42af1018085c3c22ab4911a34
7
- data.tar.gz: 19320a9d06dd5d2d4cde92bd86c68f3d6efd5509ccfb4d18cdcebc26653f9109c0cd6fbedb9755ed68ba7744ed9d4c9a40ecf86842ea637572440fd27918c2b8
6
+ metadata.gz: 25f6a3ae51271d7c93227af3f147ea47ce7d041bb2d1194632eea875e58c161b4ed12794064192614d10ff8407d66179874be15571e90e9db9c0efac3d31bc00
7
+ data.tar.gz: db3b684155e2447af9a94fd48e2d9ef2e62e3ff9af11d3f47565cf87ec6a61d53ae841809d3a74e2b15408df9e096f7bc74670850170f4d6c333ce63228e3d61
@@ -2,14 +2,14 @@
2
2
 
3
3
  # simplecov:disable
4
4
  module Renuo::Cli::Commands::Ci
5
- class CheckDeploioStatus
5
+ class CheckDeploioStatus # rubocop:disable Metrics/ClassLength
6
6
  TIMEOUT_IN_SECONDS = 600
7
7
  INTERVAL_IN_SECONDS = 5
8
8
  LOG_DRAIN_SECONDS = 2
9
9
 
10
10
  command "ci check-deploio-status" do |c|
11
11
  c.syntax = "renuo ci check-deploio-status [options] <app-name>"
12
- c.summary = "Checks the build status of the deployment."
12
+ c.summary = "Checks the build and release status of the deployment."
13
13
  c.description = c.summary
14
14
  c.option "-p", "--project <name>", String, "The name of the project"
15
15
  c.option "-g", "--git-revision <hash>", String, "The Git revision"
@@ -17,9 +17,12 @@ module Renuo::Cli::Commands::Ci
17
17
  end
18
18
 
19
19
  def run
20
- puts "Checking build status for revision #{@revision}..."
20
+ puts "[RENUO-CLI] (1/2) Checking build status for revision #{@revision}..."
21
21
  succeeded = with_streamed_build_logs { poll_build }
22
- abort "build check timed out after #{TIMEOUT_IN_SECONDS} seconds" unless succeeded
22
+ abort "[RENUO-CLI] build check timed out after #{TIMEOUT_IN_SECONDS} seconds" unless succeeded
23
+
24
+ puts "[RENUO-CLI] (2/2) Checking release status for build #{build_name}..."
25
+ abort "[RENUO-CLI] release check timed out after #{TIMEOUT_IN_SECONDS} seconds" unless poll_release
23
26
  end
24
27
 
25
28
  private
@@ -29,20 +32,20 @@ module Renuo::Cli::Commands::Ci
29
32
  @project = options.project
30
33
  @revision = options.git_revision || ENV.fetch("SEMAPHORE_GIT_SHA", nil)
31
34
 
32
- abort "missing app name" unless @app
33
- abort "missing project name" unless @project
34
- abort "this command must be run in Semaphore" unless @revision
35
+ abort "[RENUO-CLI] missing app name" unless @app
36
+ abort "[RENUO-CLI] missing project name" unless @project
37
+ abort "[RENUO-CLI] this command must be run in Semaphore" unless @revision
35
38
  end
36
39
 
37
40
  def system!(cmd)
38
41
  abort unless system cmd
39
42
  end
40
43
 
41
- def fetch_builds
42
- command = "nctl get builds -a #{@app} -p #{@project} -o yaml"
44
+ def fetch(type)
45
+ command = "nctl get #{type} -a #{@app} -p #{@project} -o yaml"
43
46
  stdout, stderr, status = Open3.capture3 command
44
47
 
45
- abort "error fetching build information: #{stderr}" unless status.success?
48
+ abort "[RENUO-CLI] error fetching #{type} information: #{stderr}" unless status.success?
46
49
  Psych.load_stream stdout
47
50
  end
48
51
 
@@ -67,7 +70,7 @@ module Renuo::Cli::Commands::Ci
67
70
  end
68
71
 
69
72
  def build
70
- @build ||= fetch_builds.find do |build|
73
+ @build ||= fetch("builds").find do |build|
71
74
  build.dig("spec", "forProvider", "sourceConfig", "git", "revision") == @revision
72
75
  end
73
76
  end
@@ -80,34 +83,63 @@ module Renuo::Cli::Commands::Ci
80
83
  build.dig("status", "atProvider", "buildStatus")
81
84
  end
82
85
 
83
- def build_succeeded? # rubocop:disable Metrics/MethodLength
86
+ def release
87
+ @release ||= fetch("releases").find do |release|
88
+ release.dig("spec", "forProvider", "build", "name") == build_name
89
+ end
90
+ end
91
+
92
+ def release_name
93
+ release.dig("metadata", "name")
94
+ end
95
+
96
+ def release_status
97
+ release.dig("status", "atProvider", "releaseStatus")
98
+ end
99
+
100
+ def succeeded?(type, name, status) # rubocop:disable Metrics/MethodLength
84
101
  time = Time.current.iso8601.to_s
85
102
 
86
- case build_status
103
+ case status
87
104
  when "available", "success"
88
- puts "[RENUO-CLI] #{time} build #{build_name} succeeded"
105
+ puts "[RENUO-CLI] #{time} #{type} #{name} succeeded"
106
+ true
107
+ when "superseded"
108
+ puts "[RENUO-CLI] #{time} #{type} #{name} was superseded"
89
109
  true
90
110
  when "paused"
91
111
  abort "[RENUO-CLI] #{time} app is paused"
92
112
  when "error", "failed", "failure"
93
- abort "[RENUO-CLI] #{time} build #{build_name} failed"
113
+ abort "[RENUO-CLI] #{time} #{type} #{name} failed"
94
114
  else
95
- puts "[RENUO-CLI] #{time} build #{build_name} status is #{build_status}, waiting..."
115
+ puts "[RENUO-CLI] #{time} #{type} #{name} status is #{status}, waiting..."
96
116
  false
97
117
  end
98
118
  end
99
119
 
100
- def poll_build # rubocop:disable Metrics/MethodLength
120
+ def poll_build
121
+ poll do
122
+ next puts("[RENUO-CLI] no matching build found, waiting...") unless build
123
+ next true if succeeded?("build", build_name, build_status)
124
+
125
+ @build = nil
126
+ end
127
+ end
128
+
129
+ def poll_release
130
+ poll do
131
+ next puts("[RENUO-CLI] no matching release found, waiting...") unless release
132
+ next true if succeeded?("release", release_name, release_status)
133
+
134
+ @release = nil
135
+ end
136
+ end
137
+
138
+ def poll
101
139
  elapsed = 0
102
140
 
103
141
  while elapsed < TIMEOUT_IN_SECONDS
104
- if build
105
- return true if build_succeeded?
106
-
107
- @build = nil
108
- else
109
- puts "no matching build found, waiting..."
110
- end
142
+ return true if yield
111
143
 
112
144
  sleep INTERVAL_IN_SECONDS
113
145
  elapsed += INTERVAL_IN_SECONDS
@@ -96,14 +96,7 @@ class Renuo::Cli::Commands::CreateNineCloudvm
96
96
 
97
97
  def set_vault_name
98
98
  @vault_name = ask("An SSH key to access the cloud VM will be stored on 1Password. " \
99
- "Enter the 1Password vault name: ") do |q|
100
- q.default = "Deploio"
101
- end
102
- end
103
-
104
- def setup_ssh_key
105
- say "\n# Generate the SSH key to access the cloud VM\n".bold
106
- generate_ssh_key(SSH_KEY_FILE_NAME)
99
+ "Enter the 1Password vault name: ") { |q| q.default = "Deploio" }
107
100
  end
108
101
 
109
102
  def setup_environments
@@ -133,17 +126,25 @@ class Renuo::Cli::Commands::CreateNineCloudvm
133
126
  --wait \\
134
127
  --location="#{@location}" \\
135
128
  --machine-type="#{@machine_type}" \\
136
- --hostname="#{hostname(environment)}" \\
129
+ --hostname="#{@project_name_with_org_prefix}-#{environment}" \\
137
130
  --os="#{@os}" \\
138
131
  --ssh-keys-from-files=#{SSH_KEY_FILE_NAME}.pub
132
+
133
+ # Add an A record for #{domain(environment)} pointing to the IP address of the #{environment} cloud VM
134
+ # (see: nctl get cloudvm #{environment} --project #{@project_name_with_org_prefix})
139
135
  OUTPUT
140
136
  end
141
137
 
142
- def hostname(environment)
143
- "#{@project_name_with_org_prefix}-#{environment}"
138
+ def domain(environment)
139
+ "#{@project_name}-#{environment}.renuoapp.ch"
140
+ end
141
+
142
+ def ssh_url_fields
143
+ ENVIRONMENTS.map { |environment| "'#{environment}[url]=ssh://root@#{domain(environment)}'" }.join(" \\\n ")
144
144
  end
145
145
 
146
- def generate_ssh_key(file_name)
146
+ def setup_ssh_key
147
+ say "\n# Generate the SSH key to access the cloud VM\n".bold
147
148
  say <<~OUTPUT
148
149
  # Create SSH key item in 1Password directly
149
150
  op item create \\
@@ -152,15 +153,16 @@ class Renuo::Cli::Commands::CreateNineCloudvm
152
153
  --ssh-generate-key #{SSH_ALGORITHM.downcase} \\
153
154
  --title "#{@project_name_with_org_prefix}-cloudvm-key" \\
154
155
  --vault #{@vault_name} \\
155
- --tags #{@project_name_with_org_prefix}
156
+ --tags #{@project_name_with_org_prefix} \\
157
+ #{ssh_url_fields}
156
158
 
157
159
  # Extract keys to temp files for cloud VM usage
158
160
  op item get "#{@project_name_with_org_prefix}-cloudvm-key" --vault #{@vault_name} \\
159
161
  --account renuo.1password.com \\
160
- --reveal --fields "public key" > #{file_name}.pub
162
+ --reveal --fields "public key" > #{SSH_KEY_FILE_NAME}.pub
161
163
  op item get "#{@project_name_with_org_prefix}-cloudvm-key" --vault #{@vault_name} --reveal \\
162
164
  --account renuo.1password.com \\
163
- --fields "private key" --format json | jq -r '.ssh_formats.openssh.value' > #{file_name}
165
+ --fields "private key" --format json | jq -r '.ssh_formats.openssh.value' > #{SSH_KEY_FILE_NAME}
164
166
  OUTPUT
165
167
  end
166
168
  end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../debug"
4
+
5
+ class Renuo::Cli::Commands::Debug
6
+ # Lists all known apps grouped by service. Invoked via `renuo debug --all`.
7
+ # Prints every app and server found in the caches, grouped by category (Hetzner, Deploio, Heroku).
8
+ class All
9
+ CATEGORIES = [
10
+ { title: "Hetzner", cache: "hetzner_vms", row: :hetzner_vm_row },
11
+ { title: "Deploio", cache: "deploio_apps", row: :deploio_app_row },
12
+ { title: "Deploio VMs", cache: "deploio_vms", row: :deploio_vm_row },
13
+ { title: "Heroku", cache: "heroku_apps", row: :heroku_app_row }
14
+ ].freeze
15
+
16
+ def run
17
+ CATEGORIES.each { |category| print_category(category) }
18
+ end
19
+
20
+ private
21
+
22
+ def print_category(category)
23
+ rows = rows_for(category).sort_by(&:first)
24
+
25
+ say "\n#{header_for(category, rows.size)}"
26
+ return say " no entries, update the cache with `renuo debug <app-name or domain>`".light_black if rows.empty?
27
+
28
+ print_rows(rows)
29
+ end
30
+
31
+ def rows_for(category)
32
+ entries = Cache.restore(category[:cache]) || []
33
+ entries.map { |entry| send(category[:row], entry) }
34
+ end
35
+
36
+ def header_for(category, count)
37
+ title = "#{category[:title]} (#{count})".bold
38
+ "#{title} #{"updated #{Cache.stored_at(category[:cache]) || "never"}".light_black}"
39
+ end
40
+
41
+ def print_rows(rows)
42
+ widths = column_widths(rows)
43
+ rows.each do |row|
44
+ columns = row.each_with_index.map { |column, index| column.ljust(widths[index]) }
45
+ say " #{columns.join(" ").rstrip}"
46
+ end
47
+ end
48
+
49
+ def column_widths(rows)
50
+ rows.first.each_index.map { |index| rows.map { |row| row[index].length }.max }
51
+ end
52
+
53
+ def hetzner_vm_row(server)
54
+ [server[:name].to_s, server[:public_ip].to_s, server[:fqdn].to_s]
55
+ end
56
+
57
+ def deploio_app_row(app)
58
+ [app[:name].to_s, [app[:organization], app[:namespace]].compact_blank.uniq.join("/"), app[:hosts].to_a.join(", ")]
59
+ end
60
+
61
+ def deploio_vm_row(server)
62
+ [server[:name].to_s, server[:namespace].to_s, [server[:ip], server[:fqdn]].compact_blank.join(" ")]
63
+ end
64
+
65
+ def heroku_app_row(app)
66
+ [app[:name].to_s, app[:domains].to_a.join(", ")]
67
+ end
68
+ end
69
+ end
@@ -4,6 +4,7 @@ require_relative "../services/cache"
4
4
  require_relative "../services/deploio"
5
5
  require_relative "../services/heroku"
6
6
  require_relative "../services/hetzner"
7
+ require_relative "debug/all"
7
8
  require "shellwords"
8
9
 
9
10
  class Renuo::Cli::Commands::Debug # rubocop:disable Metrics/ClassLength
@@ -23,6 +24,8 @@ class Renuo::Cli::Commands::Debug # rubocop:disable Metrics/ClassLength
23
24
  c.option "-H", "--heroku", "Show only Heroku apps"
24
25
  c.option "-d", "--deploio", "Show only Deploio apps"
25
26
  c.option "-z", "--hetzner", "Show only Hetzner vms"
27
+ c.option "-a", "--all", "List all known apps grouped by category"
28
+ c.example "renuo debug --all", "Lists all cached apps and servers"
26
29
  c.action do |args, options|
27
30
  new.run(args, options)
28
31
  rescue Interrupt
@@ -34,6 +37,8 @@ class Renuo::Cli::Commands::Debug # rubocop:disable Metrics/ClassLength
34
37
  # rubocop:todo Metrics/MethodLength
35
38
  # rubocop:todo Metrics/AbcSize
36
39
  def run(args, options) # rubocop:todo Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
40
+ return All.new.run if options.all
41
+
37
42
  query = args[0]
38
43
  abort(">> Please provide an app name or domain.") if query.blank?
39
44
 
@@ -3,7 +3,7 @@
3
3
  # simplecov:disable
4
4
  module Renuo
5
5
  class Cli
6
- VERSION = "4.24.0"
6
+ VERSION = "4.25.0"
7
7
  NAME = "renuo-cli"
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: renuo-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.24.0
4
+ version: 4.25.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Renuo AG
@@ -80,6 +80,7 @@ files:
80
80
  - lib/renuo/cli/commands/create_pr.rb
81
81
  - lib/renuo/cli/commands/create_slidev_presentation.rb
82
82
  - lib/renuo/cli/commands/debug.rb
83
+ - lib/renuo/cli/commands/debug/all.rb
83
84
  - lib/renuo/cli/commands/display_name.rb
84
85
  - lib/renuo/cli/commands/fetch_emails.rb
85
86
  - lib/renuo/cli/commands/fetch_secrets.rb
@@ -132,7 +133,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
132
133
  - !ruby/object:Gem::Version
133
134
  version: '0'
134
135
  requirements: []
135
- rubygems_version: 4.0.15
136
+ rubygems_version: 4.0.21
136
137
  specification_version: 4
137
138
  summary: The Renuo CLI automates some common workflows.
138
139
  test_files: []