neetodeploy 1.1.23 → 1.1.25

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: c7c04dddccb926fbd8c26f28d31e9aa6ed098b653b29da7e8711c4452d5ede10
4
- data.tar.gz: 0d44f122ba5a598006ac7c6d0a002233e82407a4dc371c5b808cc84f2dc28bfc
3
+ metadata.gz: a0396bbc473e87998286bdc95905c601721c24787da18a64e7ab67a9cd42ddfa
4
+ data.tar.gz: 8d002597268fcca2205aa42a5fcdeb371f2a9edcbdfeb14ff80bff449407b7e0
5
5
  SHA512:
6
- metadata.gz: 1efe8207bae407d607719fcb4774421dc0186dbc086325833fd069de62c45085c696c8db208467ad24a5178323fe0835ba76721d5c101bd8c6b55085d2999ca3
7
- data.tar.gz: 8368430e6da97dca5969caacb139cdc89dfd19b6ca850e07633d9c35f3471975ce503d023e185ecbb051eae3e131eb6e58e9a0838456ec6c50f03ef301f85b84
6
+ metadata.gz: 0f07f591022176db4bd74ab0b6a5c3839634c728f5db28cef1cd504d2ff728fcec857fe84d85a9c02ba9b01a99a56d1086d41c023a318d789b5f3b26ddcd32b0
7
+ data.tar.gz: d7bc9e789cf2174cca1beb3fc37b816ceeb7d554fab45be4d50e46d24d0f4cec7d4f1d094b78f5b22847f46c59bf65c125317fa7180c307cfb202a07d8e5286b
data/Gemfile.lock CHANGED
@@ -1,18 +1,21 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- neetodeploy (1.1.22)
5
- aws-sdk-cloudwatchlogs (~> 1.0)
6
- aws-sdk-cognitoidentity (~> 1.0)
7
- colorize (~> 1.1)
4
+ neetodeploy (1.1.25)
5
+ base64
6
+ bigdecimal
7
+ colorize
8
+ csv
8
9
  dotenv (~> 2.8.1)
9
10
  httparty (~> 0.21.0)
10
11
  launchy (~> 2.5.0)
11
- rexml (~> 3.2)
12
+ logger
13
+ ostruct
14
+ rexml
12
15
  terminal-table (~> 3.0.2)
13
- thor (~> 1.3.0)
14
- tty-spinner (~> 0.9)
15
- websocket-eventmachine-client (~> 1.2)
16
+ thor (~> 1.5.0)
17
+ tty-spinner
18
+ websocket-eventmachine-client
16
19
 
17
20
  GEM
18
21
  remote: https://rubygems.org/
@@ -34,8 +37,11 @@ GEM
34
37
  jmespath (~> 1, >= 1.6.1)
35
38
  aws-sigv4 (1.5.2)
36
39
  aws-eventstream (~> 1, >= 1.0.2)
40
+ base64 (0.3.0)
41
+ bigdecimal (4.0.1)
37
42
  byebug (11.1.3)
38
43
  colorize (1.1.0)
44
+ csv (3.3.5)
39
45
  dotenv (2.8.1)
40
46
  eventmachine (1.2.7)
41
47
  httparty (0.21.0)
@@ -44,13 +50,15 @@ GEM
44
50
  jmespath (1.6.2)
45
51
  launchy (2.5.2)
46
52
  addressable (~> 2.8)
53
+ logger (1.7.0)
47
54
  mini_mime (1.1.2)
48
55
  multi_xml (0.6.0)
56
+ ostruct (0.6.3)
49
57
  public_suffix (5.0.1)
50
58
  rexml (3.4.1)
51
59
  terminal-table (3.0.2)
52
60
  unicode-display_width (>= 1.1.1, < 3)
53
- thor (1.3.0)
61
+ thor (1.5.0)
54
62
  tty-cursor (0.7.1)
55
63
  tty-spinner (0.9.3)
56
64
  tty-cursor (~> 0.7)
@@ -68,6 +76,8 @@ PLATFORMS
68
76
  arm64-darwin-20
69
77
  arm64-darwin-21
70
78
  arm64-darwin-23
79
+ arm64-darwin-24
80
+ arm64-darwin-25
71
81
  x86_64-darwin-21
72
82
  x86_64-darwin-23
73
83
  x86_64-linux
@@ -9,6 +9,10 @@ module NeetoDeploy
9
9
  def addon_url(addon_name)
10
10
  "#{NEETO_DEPLOY_CLI_API_BASE_URL}/addons/#{addon_name}"
11
11
  end
12
+
13
+ def scheduled_exports_url(app_slug)
14
+ "#{NEETO_DEPLOY_CLI_API_BASE_URL}/scheduled_exports/#{app_slug}"
15
+ end
12
16
  end
13
17
  end
14
18
  end
@@ -42,7 +42,7 @@ module NeetoDeploy
42
42
  end
43
43
 
44
44
  def print_output
45
- ui.error(@response["error"]) and return unless @response.success?
45
+ ui.error(@response["error"] || @response.message) and return unless @response.success?
46
46
 
47
47
  flatten_hash_from(JSON[@response.body]).each do |k, v|
48
48
  ui.info("#{k}: #{v}")
@@ -12,34 +12,32 @@ module NeetoDeploy
12
12
  include Constants
13
13
  include Session
14
14
 
15
- attr_reader :app_name
15
+ attr_reader :app_slug
16
16
 
17
17
  def initialize(options)
18
18
  super()
19
- @app_name = options[:app]
19
+ @app_slug = options[:app]
20
20
  end
21
21
 
22
22
  def run
23
23
  ui.execute_with_loading("Fetching info...") do
24
- send_request
24
+ @response = send_get_request(scheduled_exports_url(app_slug), { app_slug: })
25
25
  end
26
26
  print_output
27
27
  end
28
28
 
29
29
  private
30
30
 
31
- def send_request
32
- @response = send_get_request("#{NEETO_DEPLOY_CLI_API_BASE_URL}/scheduled_exports/#{app_name}", {app_slug: app_name})
33
- end
34
-
35
31
  def print_output
36
- ui.error(@response["error"]) and return unless @response.success?
32
+ ui.error(@response["error"] || @response.message) and return unless @response.success?
37
33
 
38
34
  scheduled_exports = JSON.parse(@response.body)["scheduled_exports_enabled"]
39
35
  if scheduled_exports.nil?
40
36
  ui.error("App doesn't seem to have a primary database addon")
37
+ elsif scheduled_exports
38
+ ui.success("Scheduled exports is turned on for #{app_slug}'s primary database")
41
39
  else
42
- ui.info("Scheduled exports is turned #{scheduled_exports ? "\u001b[32mon\u001b[0m" : "\u001b[31moff\u001b[0m"} for #{app_name}'s primary database")
40
+ ui.info("Scheduled exports is turned off for #{app_slug}'s primary database")
43
41
  end
44
42
  end
45
43
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "thor"
4
4
 
5
+ require_relative "./constants"
5
6
  require_relative "./list"
6
7
 
7
8
  module NeetoDeploy
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NeetoDeploy
4
+ class CLI
5
+ module AutoscalingConfig
6
+ module Constants
7
+ def autoscaling_configs_url(app_slug)
8
+ "#{NEETO_DEPLOY_CLI_API_BASE_URL}/autoscaling_configs/#{app_slug}"
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,12 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "thor"
4
+ require "terminal-table"
5
+
6
+ require_relative "../session"
7
+ require_relative "./constants"
4
8
 
5
9
  module NeetoDeploy
6
10
  class CLI
7
11
  module AutoscalingConfig
8
12
  class List < CLI::Base
9
13
  include Session
14
+ include Constants
10
15
 
11
16
  attr_reader :app_slug
12
17
 
@@ -16,15 +21,22 @@ module NeetoDeploy
16
21
  end
17
22
 
18
23
  def run
19
- response = send_get_request(
20
- "#{NEETO_DEPLOY_CLI_API_BASE_URL}/autoscaling_configs/#{app_slug}", {
21
- app_slug:
22
- }
23
- )
24
+ ui.execute_with_loading("Fetching autoscaling configs...") do
25
+ @response = send_get_request(autoscaling_configs_url(app_slug), { app_slug: })
26
+ end
27
+
28
+ ui.error(@response["error"] || @response.message) and return unless @response.success?
29
+
30
+ status = JSON.parse(@response.body)["autoscaling_status"]
24
31
 
25
- ui.error(response) and return unless response.success?
32
+ if status.nil? || status.empty?
33
+ ui.info("No autoscaling configs found for #{app_slug}.")
34
+ return
35
+ end
26
36
 
27
- ui.success(JSON.parse(response.body)["autoscaling_status"])
37
+ rows = status.map { |process_type, config| [process_type, config&.[]("enabled") ? "Yes" : "No", config&.[]("type") || "-"] }
38
+ table = Terminal::Table.new(headings: ["Process Type", "Enabled", "Type"], rows: rows)
39
+ ui.info(table)
28
40
  end
29
41
  end
30
42
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "thor"
4
4
 
5
+ require_relative "./constants"
5
6
  require_relative "./list"
6
7
 
7
8
  module NeetoDeploy
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NeetoDeploy
4
+ class CLI
5
+ module Certificates
6
+ module Constants
7
+ def certificates_url
8
+ "#{NEETO_DEPLOY_CLI_API_BASE_URL}/certificates"
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,12 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "thor"
4
+ require "terminal-table"
5
+
6
+ require_relative "../session"
7
+ require_relative "./constants"
4
8
 
5
9
  module NeetoDeploy
6
10
  class CLI
7
11
  module Certificates
8
12
  class List < CLI::Base
9
13
  include Session
14
+ include Constants
10
15
 
11
16
  attr_reader :app_slug
12
17
 
@@ -16,15 +21,27 @@ module NeetoDeploy
16
21
  end
17
22
 
18
23
  def run
19
- response = send_get_request(
20
- "#{NEETO_DEPLOY_CLI_API_BASE_URL}/certificates", {
21
- app_slug:
22
- }
23
- )
24
+ ui.execute_with_loading("Fetching certificates...") do
25
+ @response = send_get_request(certificates_url, { app_slug: })
26
+ end
27
+
28
+ ui.error(@response["error"] || @response.message) and return unless @response.success?
29
+
30
+ certificates = JSON.parse(@response.body)["certificates"]
31
+
32
+ if certificates.nil? || certificates.empty?
33
+ ui.info("No certificates found for #{app_slug}.")
34
+ return
35
+ end
24
36
 
25
- ui.error(response) and return unless response.success?
37
+ rows = certificates.map do |cert|
38
+ domains = Array(cert["domains"]).map { |d| d["hostname"] }.join(", ")
39
+ expiring_soon = cert["expires_before_30_days"] ? " (expiring soon)" : ""
40
+ [cert["name"], cert["expiration"], domains + expiring_soon]
41
+ end
26
42
 
27
- ui.success(JSON.parse(response.body)["certificates"])
43
+ table = Terminal::Table.new(headings: ["Name", "Expiration", "Domains"], rows: rows)
44
+ ui.info(table)
28
45
  end
29
46
  end
30
47
  end
@@ -15,21 +15,39 @@ module NeetoDeploy
15
15
  def process!
16
16
  start_spinner
17
17
  send_console_session_request
18
- ui.error("\n#{@response.body}") and return unless @response.success?
18
+ ui.error(@response["error"] || @response.message) and return unless @response.success?
19
19
 
20
+ print_connection_banner
20
21
  start_console
22
+ print_session_end
21
23
  connection_cleanup_callback
22
24
  end
23
25
 
24
26
  private
25
27
 
26
28
  def console_executable_path
27
- gem_spec = Gem::Specification.find_by_name("neetodeploy")
28
- gem_dir = gem_spec.gem_dir
29
- executable_path = File.join(gem_dir, "exe", console_executable_name)
29
+ if ENV["DEV_ENVIRONMENT"]
30
+ File.expand_path("../../../exe/#{console_executable_name}", __dir__)
31
+ else
32
+ gem_spec = Gem::Specification.find_by_name("neetodeploy")
33
+ File.join(gem_spec.gem_dir, "exe", console_executable_name)
34
+ end
35
+ end
36
+
37
+ def print_connection_banner
38
+ separator = "─" * 60
39
+ ui.success("Connected to #{addon_name}. Type \"exit\" to end the session.")
40
+ ui.info(separator)
41
+ end
42
+
43
+ def print_session_end
44
+ separator = "─" * 60
45
+ ui.info(separator)
46
+ ui.info("Session ended.")
30
47
  end
31
48
 
32
49
  def start_console
50
+ @pubsub_token = @response.parsed_response["console_pubsub_token"]
33
51
  console_access_token = @response.parsed_response["console_access_token"]
34
52
  pod_name = @response.parsed_response["pod_name"]
35
53
  database_url = @response.parsed_response["database_url"]
@@ -39,7 +57,7 @@ module NeetoDeploy
39
57
  end
40
58
 
41
59
  def execute_console(pod_name, console_access_token, container_name, kind, database_url)
42
- system("#{console_executable_path} -podname #{pod_name} -token #{console_access_token} -kind #{kind} -container #{container_name} -url #{database_url}")
60
+ system(console_executable_path, "-podname", pod_name, "-token", console_access_token, "-kind", kind, "-container", container_name, "-url", database_url, err: File::NULL)
43
61
  end
44
62
 
45
63
  def send_console_session_request
@@ -49,11 +67,12 @@ module NeetoDeploy
49
67
 
50
68
  def connection_cleanup_callback
51
69
  url = "#{console_session_base_url}/#{addon_name}"
52
- send_delete_request(url, { pubsub_token: @pubsub_token })
70
+ response = send_delete_request(url, { pubsub_token: @pubsub_token })
71
+ ui.error("Warning: failed to clean up session — #{response["error"] || response.message}") unless response.success?
53
72
  end
54
73
 
55
74
  def start_spinner
56
- @spinner = TTY::Spinner.new("Setting up dyno [:spinner]", format: :classic)
75
+ @spinner = TTY::Spinner.new("Setting up console for #{addon_name} [:spinner]", format: :classic)
57
76
  @spinner.auto_spin
58
77
  end
59
78
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "terminal-table"
4
4
  require "thor"
5
+ require "io/console"
5
6
 
6
7
  require_relative "../session"
7
8
  require_relative "./constants"
@@ -22,16 +23,14 @@ module NeetoDeploy
22
23
  end
23
24
 
24
25
  def run
25
- response = send_get_request(
26
- environment_variables_url, {
27
- app_slug:
28
- }
29
- )
26
+ ui.execute_with_loading("Fetching environment variables...") do
27
+ @response = send_get_request(environment_variables_url, { app_slug: })
28
+ end
30
29
 
31
- ui.error(response) and return unless response.success?
30
+ ui.error(@response["error"] || @response.message) and return unless @response.success?
32
31
 
33
- data = is_json_format ? json_data(response["environment_variables"]) : table_data(response["environment_variables"])
34
- ui.success(data)
32
+ data = is_json_format ? json_data(@response["environment_variables"]) : table_data(@response["environment_variables"])
33
+ ui.info(data)
35
34
  end
36
35
 
37
36
  private
@@ -47,7 +46,20 @@ module NeetoDeploy
47
46
  end
48
47
 
49
48
  def table_data(envs)
50
- Terminal::Table.new(headings: table_columns, rows: table_rows(envs))
49
+ rows = table_rows(envs)
50
+ key_width = rows.map { |r| r[0].length }.max || 10
51
+ val_width = [terminal_width - key_width - 7, 20].max
52
+ wrapped_rows = rows.map { |key, value| [key, wrap_value(value, val_width)] }
53
+ Terminal::Table.new(headings: table_columns, rows: wrapped_rows, style: { all_separators: false })
54
+ end
55
+
56
+ def terminal_width
57
+ IO.console&.winsize&.last || 120
58
+ end
59
+
60
+ def wrap_value(text, width)
61
+ return text if text.length <= width
62
+ text.chars.each_slice(width).map(&:join).join("\n")
51
63
  end
52
64
 
53
65
  def json_data(envs)
@@ -25,24 +25,11 @@ module NeetoDeploy
25
25
  end
26
26
 
27
27
  def run
28
- table = Terminal::Table.new(
29
- headings: table_columns,
30
- rows: environment_variables.map { |environment_variable|
31
- [environment_variable[:key], environment_variable[:value]]
32
- }
33
- )
34
- ui.info(table)
35
-
36
- ui.info("Setting environment variables and restarting app...")
37
-
38
- response = send_post_request(
39
- environment_variables_url, {
40
- app_slug:,
41
- environment_variables:
42
- }
43
- )
44
-
45
- ui.error(response) and return unless response.success?
28
+ ui.execute_with_loading("Setting environment variables and restarting app...") do
29
+ @response = send_post_request(environment_variables_url, { app_slug:, environment_variables: })
30
+ end
31
+
32
+ ui.error(@response["error"] || @response.message) and return unless @response.success?
46
33
 
47
34
  ui.success("Done")
48
35
  end
@@ -25,16 +25,11 @@ module NeetoDeploy
25
25
  end
26
26
 
27
27
  def run
28
- ui.info("Unsetting environment variable keys #{environment_variables_string_array} and restarting app...")
29
-
30
- response = send_delete_request(
31
- environment_variables_url, {
32
- app_slug:,
33
- environment_variables:
34
- }
35
- )
28
+ ui.execute_with_loading("Unsetting #{environment_variables_string_array.join(", ")} and restarting app...") do
29
+ @response = send_delete_request(environment_variables_url, { app_slug:, environment_variables: })
30
+ end
36
31
 
37
- ui.error(response) and return unless response.success?
32
+ ui.error(@response["error"] || @response.message) and return unless @response.success?
38
33
 
39
34
  ui.success("Done")
40
35
  end
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "thor"
4
- require "readline"
5
4
  require "rbconfig"
6
5
 
7
6
  require_relative "../session"
@@ -27,18 +26,35 @@ module NeetoDeploy
27
26
  def process!
28
27
  start_spinner
29
28
  send_console_session_request
30
- ui.error("\n#{@response.body}") and return unless @response.success?
29
+ ui.error(@response["error"] || @response.message) and return unless @response.success?
31
30
 
31
+ print_connection_banner
32
32
  start_console
33
+ print_session_end
33
34
  connection_cleanup_callback
34
35
  end
35
36
 
36
37
  private
37
38
 
38
39
  def console_executable_path
39
- gem_spec = Gem::Specification.find_by_name("neetodeploy")
40
- gem_dir = gem_spec.gem_dir
41
- executable_path = File.join(gem_dir, "exe", console_executable_name)
40
+ if ENV["DEV_ENVIRONMENT"]
41
+ File.expand_path("../../../../exe/#{console_executable_name}", __dir__)
42
+ else
43
+ gem_spec = Gem::Specification.find_by_name("neetodeploy")
44
+ File.join(gem_spec.gem_dir, "exe", console_executable_name)
45
+ end
46
+ end
47
+
48
+ def print_connection_banner
49
+ separator = "─" * 60
50
+ ui.success("Connected to #{app_name}. Type \"exit\" to end the session.")
51
+ ui.info(separator)
52
+ end
53
+
54
+ def print_session_end
55
+ separator = "─" * 60
56
+ ui.info(separator)
57
+ ui.info("Session ended.")
42
58
  end
43
59
 
44
60
  def start_console
@@ -46,7 +62,7 @@ module NeetoDeploy
46
62
  console_access_token = @response.parsed_response["console_access_token"]
47
63
  pod_name = "#{app_name}-#{@pubsub_token}-console-deployment"
48
64
  container_name = "#{app_name}-#{@pubsub_token}-console"
49
- system("#{console_executable_path} -podname #{pod_name} -token #{console_access_token} -container #{container_name}")
65
+ system(console_executable_path, "-podname", pod_name, "-token", console_access_token, "-container", container_name, err: File::NULL)
50
66
  end
51
67
 
52
68
  def send_console_session_request
@@ -56,11 +72,12 @@ module NeetoDeploy
56
72
 
57
73
  def connection_cleanup_callback
58
74
  url = "#{console_session_base_url}/#{app_name}"
59
- send_delete_request(url, { pubsub_token: @pubsub_token })
75
+ response = send_delete_request(url, { pubsub_token: @pubsub_token })
76
+ ui.error("Warning: failed to clean up session — #{response["error"] || response.message}") unless response.success?
60
77
  end
61
78
 
62
79
  def start_spinner
63
- @spinner = TTY::Spinner.new("Setting up dyno [:spinner]", format: :classic)
80
+ @spinner = TTY::Spinner.new("Setting up console for #{app_name} [:spinner]", format: :classic)
64
81
  @spinner.auto_spin
65
82
  end
66
83
 
@@ -53,13 +53,15 @@ module NeetoDeploy
53
53
 
54
54
  def wait_until_user_authenticates!
55
55
  two_minutes_later = Time.now.utc + 120
56
- loop_running = true
57
- while loop_running
58
- loop_running = false if check_if_user_authenticated
59
- loop_running = false if Time.now.utc >= two_minutes_later
56
+ authenticated = false
57
+ while Time.now.utc < two_minutes_later
58
+ if check_if_user_authenticated
59
+ authenticated = true
60
+ break
61
+ end
60
62
  sleep LOGIN_STATUS_CHECK_INTERVAL_SECONDS
61
63
  end
62
- ui.say("Logged in successfully")
64
+ authenticated ? ui.success("Logged in successfully") : ui.error("Login timed out. Please try again.")
63
65
  end
64
66
 
65
67
  def check_if_user_authenticated
@@ -3,8 +3,6 @@
3
3
  require "thor"
4
4
  require "colorize"
5
5
 
6
- require "aws-sdk-cloudwatchlogs"
7
- require "aws-sdk-cognitoidentity"
8
6
  require_relative "../session"
9
7
  require_relative "./constants"
10
8
 
@@ -15,86 +13,97 @@ module NeetoDeploy
15
13
  include Constants
16
14
  include Session
17
15
 
16
+ POLL_INTERVAL_SECONDS = 2
17
+ INITIAL_LIMIT = 100
18
+ POLL_LIMIT = 1000
19
+
18
20
  attr_reader :app_slug, :process_type
19
21
 
20
22
  def initialize(app_slug, process_type = nil)
21
23
  super()
22
24
  @app_slug = app_slug
23
25
  @process_type = process_type
24
- @log_client = nil
26
+ @last_timestamp_ms = nil
25
27
  end
26
28
 
27
29
  def process!
28
30
  start_spinner
29
- response = authenticate_user
30
- return unless response.success?
31
+ response = fetch_logs
32
+ @spinner.stop
33
+
34
+ unless response.success?
35
+ ui.error(response["error"] || response.message)
36
+ return
37
+ end
38
+
39
+ data = JSON.parse(response.body)
40
+
41
+ unless data["configured"]
42
+ ui.error("Logs are not configured for this app.")
43
+ return
44
+ end
31
45
 
32
- log_params = JSON[response.body]
33
- @log_client = cloud_watch_client(log_params)
34
- start_log_streaming(log_params)
46
+ # Initial batch returned DESC — reverse for chronological display
47
+ logs = data["logs"].reverse
48
+ logs.each { |log| print_log(log) }
49
+ # Subtract 10s buffer to account for potential client/server clock skew
50
+ @last_timestamp_ms = logs.any? ? logs.last["timestamp"] : ((Time.now.to_f - 10) * 1000).to_i
51
+
52
+ poll_for_logs
35
53
  end
36
54
 
37
55
  private
38
56
 
39
- def authenticate_user
40
- response = send_post_request(authenticate_live_stream_url, { app_slug:, process_type: })
41
- @spinner.stop && ui.error(response) unless response.success?
42
- response
43
- end
57
+ def fetch_logs(after_timestamp_ms: nil)
58
+ params = { app_slug:, process_type:, limit: INITIAL_LIMIT }
44
59
 
45
- def cloud_watch_client(log_params)
46
- cognito = Aws::CognitoIdentity::Client.new(region: log_params["region"])
47
- identity_id = cognito.get_id(identity_pool_id: log_params["identity_pool_id"]).identity_id
48
- credentials = cognito.get_credentials_for_identity(identity_id:).credentials
49
-
50
- Aws::CloudWatchLogs::Client.new(
51
- region: log_params["region"],
52
- access_key_id: credentials.access_key_id,
53
- secret_access_key: credentials.secret_key,
54
- session_token: credentials.session_token
55
- )
56
- end
60
+ if after_timestamp_ms
61
+ params[:after_timestamp] = ms_to_iso8601(after_timestamp_ms)
62
+ params[:limit] = POLL_LIMIT
63
+ end
57
64
 
58
- def start_spinner
59
- @spinner = TTY::Spinner.new("Starting live stream session [:spinner]", format: :classic)
60
- @spinner.auto_spin
65
+ send_get_request(logs_v2_url, params)
61
66
  end
62
67
 
63
- def start_log_streaming(log_params)
64
- next_token = nil
65
- @spinner.stop
68
+ def poll_for_logs
66
69
  loop do
67
- begin
68
- params = {
69
- log_group_name: log_params["log_group_name"],
70
- log_stream_name: log_params["log_stream_name"],
71
- start_from_head: !next_token.nil?,
72
- limit: next_token.nil? ? 100 : 1000
73
- }
74
-
75
- params[:next_token] = next_token unless next_token.nil?
76
- response = @log_client.get_log_events(params)
77
-
78
- response.events.each do |event|
79
- puts "\e[35m#{Time.at(event.timestamp / 1000)}\e[0m #{event.message}"
80
- end
81
-
82
- if next_token == response.next_forward_token
83
- sleep 3
84
- else
85
- next_token = response.next_forward_token
86
- end
87
- rescue Aws::CloudWatchLogs::Errors::ExpiredTokenException,
88
- Aws::CloudWatchLogs::Errors::UnrecognizedClientException => e
89
- warn "Stream time limit has been reached"
90
- exit 1
91
- rescue => e
70
+ response = fetch_logs(after_timestamp_ms: @last_timestamp_ms)
71
+
72
+ if response.success?
73
+ data = JSON.parse(response.body)
74
+ logs = data["logs"]
75
+ logs.each { |log| print_log(log) }
76
+ @last_timestamp_ms = logs.last["timestamp"] if logs.any?
77
+ next if data["has_more_forward"]
78
+ elsif response.code.between?(400, 499)
79
+ ui.error("Error #{response.code}: #{response["error"] || response.message}")
80
+ break
81
+ else
92
82
  warn "Connection lost. Reconnecting..."
93
- sleep 5
94
83
  end
95
- sleep 2
84
+
85
+ sleep POLL_INTERVAL_SECONDS
86
+ rescue Interrupt
87
+ raise
88
+ rescue => e
89
+ warn "Error: #{e.message}. Reconnecting..."
90
+ sleep 5
96
91
  end
97
92
  end
93
+
94
+ def print_log(log)
95
+ timestamp = Time.at(log["timestamp"] / 1000.0)
96
+ puts "#{timestamp.to_s.colorize(:magenta)} #{log["message"]}"
97
+ end
98
+
99
+ def ms_to_iso8601(ms)
100
+ Time.at(ms / 1000.0).utc.iso8601(3)
101
+ end
102
+
103
+ def start_spinner
104
+ @spinner = TTY::Spinner.new("Starting live stream session [:spinner]", format: :classic)
105
+ @spinner.auto_spin
106
+ end
98
107
  end
99
108
  end
100
109
  end
@@ -4,10 +4,11 @@ module NeetoDeploy
4
4
  class CLI
5
5
  module Logs
6
6
  module Constants
7
- NEETO_DEPLOY_CLI_API_ENVIRONMENT_VARIABLES_URL = "#{NEETO_DEPLOY_CLI_API_BASE_URL}/logs".freeze
7
+ NEETO_DEPLOY_CLI_V2_API_BASE_URL = "#{NEETO_DEPLOY_HOST}/api/cli/v2".freeze
8
+ NEETO_DEPLOY_CLI_API_V2_LOGS_URL = "#{NEETO_DEPLOY_CLI_V2_API_BASE_URL}/logs".freeze
8
9
 
9
- def authenticate_live_stream_url
10
- NEETO_DEPLOY_CLI_API_ENVIRONMENT_VARIABLES_URL
10
+ def logs_v2_url
11
+ NEETO_DEPLOY_CLI_API_V2_LOGS_URL
11
12
  end
12
13
  end
13
14
  end
@@ -38,7 +38,7 @@ module NeetoDeploy
38
38
  end
39
39
 
40
40
  def print_output
41
- ui.error(@response["error"]) and return unless @response.success?
41
+ ui.error(@response["error"] || @response.message) and return unless @response.success?
42
42
 
43
43
  ui.success(@response["message"])
44
44
  end
@@ -26,11 +26,11 @@ module NeetoDeploy
26
26
  command: "CONFIG RESETSTAT"
27
27
  }
28
28
  )
29
- ui.error(response["error"]) and return unless response.success?
29
+ ui.error(response["error"] || response.message) and return unless response.success?
30
30
 
31
31
  ui.success("Stats reset successful.")
32
32
  else
33
- puts "Reset cancelled."
33
+ ui.info("Reset cancelled.")
34
34
  end
35
35
  end
36
36
  end
@@ -23,7 +23,7 @@ module NeetoDeploy
23
23
 
24
24
  def run
25
25
  unless valid_config?
26
- ui.error("Could not set cofig \"#{key}\". Please refer manageable redis configs: #{AVAILABLE_REDIS_CONFIGS_TO_EDIT}")
26
+ ui.error("Could not set config \"#{key}\". Manageable Redis configs: #{AVAILABLE_REDIS_CONFIGS_TO_EDIT.join(", ")}")
27
27
  return
28
28
  end
29
29
 
@@ -49,7 +49,7 @@ module NeetoDeploy
49
49
  end
50
50
 
51
51
  def print_output
52
- ui.error(@response["error"]) and return unless @response.success?
52
+ ui.error(@response["error"] || @response.message) and return unless @response.success?
53
53
 
54
54
  ui.success("#{key} config for #{addon_name} set to #{value}")
55
55
  end
@@ -19,19 +19,19 @@ module NeetoDeploy
19
19
  end
20
20
 
21
21
  def send_get_request(url, body)
22
- HTTParty.get(url, headers:, body:, format: :json)
22
+ HTTParty.get(url, { body: common_body.merge(body), format: :json })
23
23
  end
24
24
 
25
25
  def send_post_request(url, body)
26
- HTTParty.post(url, headers:, body:, format: :json)
26
+ HTTParty.post(url, { body: common_body.merge(body), format: :json })
27
27
  end
28
28
 
29
29
  def send_patch_request(url, body)
30
- HTTParty.patch(url, headers:, body:, format: :json)
30
+ HTTParty.patch(url, { body: common_body.merge(body), format: :json })
31
31
  end
32
32
 
33
33
  def send_delete_request(url, body)
34
- HTTParty.delete(url, headers:, body:, format: :json)
34
+ HTTParty.delete(url, { body: common_body.merge(body), format: :json })
35
35
  end
36
36
 
37
37
  def common_body
@@ -41,16 +41,6 @@ module NeetoDeploy
41
41
  raise Error.new("Unable to retrieve session info. Try logging in again.")
42
42
  end
43
43
 
44
- def session_token
45
- JSON.parse(File.read(CLI_SESSION_STORE_FILE_PATH))["session_token"]
46
- rescue
47
- raise Error.new("Unable to retrieve session token. Try logging in again.")
48
- end
49
-
50
- def headers
51
- { "Session-Token" => session_token }
52
- end
53
-
54
44
  def os
55
45
  @os ||= (
56
46
  host_os = RbConfig::CONFIG["host_os"]
@@ -60,7 +50,7 @@ module NeetoDeploy
60
50
  when /linux/
61
51
  :linux
62
52
  else
63
- raise Error::WebDriverError, "unsupported os: #{host_os.inspect}"
53
+ raise Error, "unsupported os: #{host_os.inspect}"
64
54
  end
65
55
  )
66
56
  end
@@ -18,6 +18,13 @@ module NeetoDeploy
18
18
  require_relative "cli/autoscaling_config/commands"
19
19
  require_relative "cli/certificates/commands"
20
20
 
21
+ # Define exit behavior for Thor 1.5.0+
22
+ # Set to false to maintain current behavior (exit with status 0)
23
+ # Set to true if you want to exit with status 1 on failures
24
+ def self.exit_on_failure?
25
+ false
26
+ end
27
+
21
28
  def self.start(*)
22
29
  super
23
30
  end
@@ -38,7 +45,7 @@ module NeetoDeploy
38
45
 
39
46
  desc "logs", "Show logs"
40
47
  option :app, type: :string, aliases: "-a", required: true, desc: "App slug"
41
- option :process_type, type: :string, aliases: "-p", required: true, desc: "Process type"
48
+ option :process_type, type: :string, aliases: "-p", desc: "Process type (optional)"
42
49
  def logs
43
50
  CLI::Logs::Base.new(options[:app], options[:process_type]).process!
44
51
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module NeetoDeploy
4
- VERSION = "1.1.23"
4
+ VERSION = "1.1.25"
5
5
  CLI_API_VERSION = "v1"
6
6
  end
data/neetodeploy.gemspec CHANGED
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
26
26
  end
27
27
  end
28
28
  spec.bindir = "exe"
29
- spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }.reject { |f| f.match?(/console-linux-(amd64|arm64)$/) }
29
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
30
  spec.require_paths = ["lib"]
31
31
 
32
32
  # Must have deps
@@ -34,13 +34,17 @@ Gem::Specification.new do |spec|
34
34
  spec.add_dependency "httparty", "~> 0.21.0" # for http requests
35
35
  spec.add_dependency "launchy", "~> 2.5.0" # for opening in browser
36
36
  spec.add_dependency "terminal-table", "~> 3.0.2" # for building cli table
37
- spec.add_dependency "thor", "~> 1.3.0" # for cli
38
- spec.add_dependency "websocket-eventmachine-client", "~> 1.2"
39
- spec.add_dependency "colorize", "~> 1.1"
40
- spec.add_dependency "tty-spinner", "~> 0.9"
41
- spec.add_dependency "aws-sdk-cloudwatchlogs", "~> 1.0"
42
- spec.add_dependency "aws-sdk-cognitoidentity", "~> 1.0"
43
- spec.add_dependency "rexml", "~> 3.2"
37
+ spec.add_dependency "thor", "~> 1.5.0" # for cli
38
+ spec.add_dependency "websocket-eventmachine-client"
39
+ spec.add_dependency "colorize"
40
+ spec.add_dependency "tty-spinner"
41
+ spec.add_dependency "rexml"
42
+ # Gems removed from Ruby stdlib in 3.4/4.0, required by our dependencies
43
+ spec.add_dependency "base64"
44
+ spec.add_dependency "bigdecimal"
45
+ spec.add_dependency "csv"
46
+ spec.add_dependency "logger"
47
+ spec.add_dependency "ostruct"
44
48
 
45
49
  # To add the files from submodules
46
50
  `git submodule --quiet foreach pwd`.split($\).each do |submodule_path|
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: neetodeploy
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.23
4
+ version: 1.1.25
5
5
  platform: ruby
6
6
  authors:
7
7
  - Subin Siby
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-11-12 00:00:00.000000000 Z
11
+ date: 2026-03-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dotenv
@@ -72,103 +72,146 @@ dependencies:
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: 1.3.0
75
+ version: 1.5.0
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: 1.3.0
82
+ version: 1.5.0
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: websocket-eventmachine-client
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - "~>"
87
+ - - ">="
88
88
  - !ruby/object:Gem::Version
89
- version: '1.2'
89
+ version: '0'
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - "~>"
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
- version: '1.2'
96
+ version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: colorize
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - "~>"
101
+ - - ">="
102
102
  - !ruby/object:Gem::Version
103
- version: '1.1'
103
+ version: '0'
104
104
  type: :runtime
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - "~>"
108
+ - - ">="
109
109
  - !ruby/object:Gem::Version
110
- version: '1.1'
110
+ version: '0'
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: tty-spinner
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
- - - "~>"
115
+ - - ">="
116
116
  - !ruby/object:Gem::Version
117
- version: '0.9'
117
+ version: '0'
118
118
  type: :runtime
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
- - - "~>"
122
+ - - ">="
123
123
  - !ruby/object:Gem::Version
124
- version: '0.9'
124
+ version: '0'
125
125
  - !ruby/object:Gem::Dependency
126
- name: aws-sdk-cloudwatchlogs
126
+ name: rexml
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
- - - "~>"
129
+ - - ">="
130
130
  - !ruby/object:Gem::Version
131
- version: '1.0'
131
+ version: '0'
132
132
  type: :runtime
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
- - - "~>"
136
+ - - ">="
137
137
  - !ruby/object:Gem::Version
138
- version: '1.0'
138
+ version: '0'
139
139
  - !ruby/object:Gem::Dependency
140
- name: aws-sdk-cognitoidentity
140
+ name: base64
141
141
  requirement: !ruby/object:Gem::Requirement
142
142
  requirements:
143
- - - "~>"
143
+ - - ">="
144
144
  - !ruby/object:Gem::Version
145
- version: '1.0'
145
+ version: '0'
146
146
  type: :runtime
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
- - - "~>"
150
+ - - ">="
151
151
  - !ruby/object:Gem::Version
152
- version: '1.0'
152
+ version: '0'
153
153
  - !ruby/object:Gem::Dependency
154
- name: rexml
154
+ name: bigdecimal
155
155
  requirement: !ruby/object:Gem::Requirement
156
156
  requirements:
157
- - - "~>"
157
+ - - ">="
158
158
  - !ruby/object:Gem::Version
159
- version: '3.2'
159
+ version: '0'
160
160
  type: :runtime
161
161
  prerelease: false
162
162
  version_requirements: !ruby/object:Gem::Requirement
163
163
  requirements:
164
- - - "~>"
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: csv
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :runtime
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: logger
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ type: :runtime
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ - !ruby/object:Gem::Dependency
196
+ name: ostruct
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ type: :runtime
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ">="
165
207
  - !ruby/object:Gem::Version
166
- version: '3.2'
208
+ version: '0'
167
209
  description: Manage neetoDeploy apps with CLI
168
210
  email:
169
211
  - subin.siby@bigbinary.com
170
212
  executables:
171
- - console
213
+ - console-linux-amd64
214
+ - console-linux-arm64
172
215
  - neetodeploy
173
216
  extensions: []
174
217
  extra_rdoc_files: []
@@ -176,7 +219,6 @@ files:
176
219
  - ".ruby-version"
177
220
  - Gemfile
178
221
  - Gemfile.lock
179
- - exe/console
180
222
  - exe/console-linux-amd64
181
223
  - exe/console-linux-arm64
182
224
  - exe/neetodeploy
@@ -187,9 +229,11 @@ files:
187
229
  - lib/neeto_deploy/cli/addon/info.rb
188
230
  - lib/neeto_deploy/cli/addon/scheduled_exports_settings.rb
189
231
  - lib/neeto_deploy/cli/autoscaling_config/commands.rb
232
+ - lib/neeto_deploy/cli/autoscaling_config/constants.rb
190
233
  - lib/neeto_deploy/cli/autoscaling_config/list.rb
191
234
  - lib/neeto_deploy/cli/base.rb
192
235
  - lib/neeto_deploy/cli/certificates/commands.rb
236
+ - lib/neeto_deploy/cli/certificates/constants.rb
193
237
  - lib/neeto_deploy/cli/certificates/list.rb
194
238
  - lib/neeto_deploy/cli/dyno_console_manager.rb
195
239
  - lib/neeto_deploy/cli/env/commands.rb
@@ -239,7 +283,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
239
283
  - !ruby/object:Gem::Version
240
284
  version: '0'
241
285
  requirements: []
242
- rubygems_version: 3.5.10
286
+ rubygems_version: 3.4.19
243
287
  signing_key:
244
288
  specification_version: 4
245
289
  summary: CLI for neetoDeploy
data/exe/console DELETED
Binary file