envirobly 0.10.0 → 1.1.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: 1502f5b98c25be6da48fbdd5ebf525723d8985d4e45eef5693611a0aea9db82a
4
- data.tar.gz: 5684ae39ecb8f3f7006979e849a9746d17bb33dd849f8173836d2b9afa1cc6cb
3
+ metadata.gz: 4069ab1f0da67a7bc06444c1d32cef454c23924f125925f1bc00bcd56419b94f
4
+ data.tar.gz: e5f5628338e7b9deda59a6c26f6e95b967f8ecbbfa0a1b41994aa3834f92a77a
5
5
  SHA512:
6
- metadata.gz: 8273cfe4dddd1f1da589add660066d5bd2b3c70f1ff0df92f0461f9b4c0b416f99276cb1a69ea1e1f13529ab6e18aca9f3720bb5e3896f8403251ee58e935bfd
7
- data.tar.gz: a9e26055d4d0de16d449aec2ddc23921d61496969eb2ebd5812dc0279a8d2a1848c5e9ea074bfe5ea7c1f35c89af69162510ecfc88c5b8f7fa137c0241120c76
6
+ metadata.gz: de515ea959bef33ba5a1e89ffa6862d3a8f116a8f4cab2249b97042e7a92e19020daeba4c5a93babbee793656f1f34b86850924cf97cb22b17f0a739333de4a1
7
+ data.tar.gz: af5c4e4b6d08d1c08a06be9089a9e0f0d790915efafbf0cf0694a0e0ff90c68fbfdebdcb78c2921a3f7dd571561132968839832c0c5c5cb8be7a5b13b59afba0
@@ -2,35 +2,86 @@ require "fileutils"
2
2
  require "pathname"
3
3
 
4
4
  class Envirobly::AccessToken
5
- def initialize(token = ENV.fetch("ENVIROBLY_ACCESS_TOKEN", nil))
6
- if token.nil? && File.exist?(access_token_path)
7
- @token = File.read(access_token_path)
5
+ include Envirobly::Colorize
6
+
7
+ attr_reader :shell
8
+
9
+ class << self
10
+ def destroy
11
+ if File.exist?(path)
12
+ FileUtils.rm path
13
+ end
14
+ end
15
+
16
+ def dir
17
+ if ENV["XDG_CONFIG_HOME"]
18
+ Pathname.new(ENV["XDG_CONFIG_HOME"]).join("envirobly")
19
+ else
20
+ Pathname.new(Dir.home).join(".envirobly")
21
+ end
22
+ end
23
+
24
+ def path
25
+ dir.join "access_token"
26
+ end
27
+ end
28
+
29
+ def initialize(token = ENV["ENVIROBLY_ACCESS_TOKEN"].presence, shell: nil)
30
+ @shell = shell
31
+
32
+ if token.blank? && File.exist?(self.class.path)
33
+ @token = File.read(self.class.path)
8
34
  else
9
35
  @token = token
10
36
  end
11
37
  end
12
38
 
13
39
  def save
14
- FileUtils.mkdir_p config_root
15
- File.write access_token_path, @token
16
- File.chmod 0600, access_token_path
17
- puts "Access token saved to #{access_token_path}"
40
+ FileUtils.mkdir_p self.class.dir
41
+ File.write self.class.path, @token
42
+ File.chmod 0600, self.class.path
18
43
  end
19
44
 
20
45
  def as_http_bearer
21
46
  "Bearer #{@token}"
22
47
  end
23
48
 
24
- private
25
- def config_root
26
- if ENV["XDG_CONFIG_HOME"]
27
- Pathname.new(ENV["XDG_CONFIG_HOME"]).join("envirobly")
28
- else
29
- Pathname.new(Dir.home).join(".envirobly")
49
+ def require!
50
+ return if @token.present?
51
+
52
+ shell.say "This action requires you to be signed in."
53
+ shell.say "Please visit https://on.envirobly.com/profile/access_tokens"
54
+ shell.say "to generate an access token and then paste it in here."
55
+ shell.say
56
+
57
+ set
58
+ end
59
+
60
+ def set
61
+ @token = nil
62
+
63
+ while @token.blank?
64
+ begin
65
+ @token = shell.ask("Access Token:", echo: false)
66
+ rescue Interrupt
67
+ shell.say
68
+ shell.say_error "Cancelled"
69
+ exit
30
70
  end
31
- end
32
71
 
33
- def access_token_path
34
- config_root.join "access_token"
72
+ api = Envirobly::Api.new(access_token: self)
73
+
74
+ # TODO: Eventually replace with custom `whoami` API that returns name, email...
75
+ if api.list_accounts.success?
76
+ save
77
+ shell.say
78
+ shell.say "Successfully signed in "
79
+ shell.say green_check
80
+ else
81
+ shell.say
82
+ shell.say_error "This token is invalid. Please try again"
83
+ @token = nil
84
+ end
35
85
  end
86
+ end
36
87
  end
data/lib/envirobly/api.rb CHANGED
@@ -4,17 +4,17 @@ require "socket"
4
4
  require "uri"
5
5
 
6
6
  class Envirobly::Api
7
- HOST = ENV["ENVIROBLY_API_HOST"] || "envirobly.com"
7
+ HOST = ENV["ENVIROBLY_API_HOST"].presence || "on.envirobly.com"
8
8
  USER_AGENT = "Envirobly CLI v#{Envirobly::VERSION}"
9
9
  CONTENT_TYPE = "application/json"
10
10
 
11
- def initialize
12
- @access_token = Envirobly::AccessToken.new
11
+ def initialize(access_token: Envirobly::AccessToken.new)
12
+ @access_token = access_token
13
13
  end
14
14
 
15
15
  def validate_shape(params)
16
16
  post_as_json(api_v1_shape_validations_url, params:, headers: authorization_headers).tap do |response|
17
- unless successful_response?(response)
17
+ unless response.success?
18
18
  $stderr.puts "Validation request responded with #{response.code}. Aborting."
19
19
  exit 1
20
20
  end
@@ -22,29 +22,40 @@ class Envirobly::Api
22
22
  end
23
23
 
24
24
  def create_deployment(params)
25
- post_as_json(api_v1_deployments_url, params:, headers: authorization_headers).tap do |response|
26
- unless successful_response?(response)
27
- $stderr.puts "Deployment creation request responded with #{response.code}. Aborting."
28
- # TODO: render 422 validation failed nicely
29
- $stderr.puts response.object
30
- exit 1
31
- end
32
- end
25
+ post_as_json(api_v1_deployments_url, params:, headers: authorization_headers)
26
+ end
27
+
28
+ def list_accounts
29
+ get_as_json api_v1_accounts_url, headers: authorization_headers
30
+ end
31
+
32
+ def list_regions
33
+ get_as_json api_v1_regions_url, headers: authorization_headers
34
+ end
35
+
36
+ def list_instance_types(region)
37
+ get_as_json api_v1_instance_types_url(region), headers: authorization_headers
33
38
  end
34
39
 
35
- MAX_RETRIES = 20
36
- RETRY_INTERVAL_SECONDS = 2
40
+ MAX_RETRIES = 30
41
+ SHORT_RETRY_INTERVAL = 2.seconds
42
+ LONG_RETRY_INTERVAL = 6.seconds
37
43
  def get_deployment_with_delay_and_retry(url, tries = 1)
38
- sleep RETRY_INTERVAL_SECONDS * tries
44
+ sleep SHORT_RETRY_INTERVAL * tries
39
45
  response = get_as_json URI(url)
40
46
 
41
- if successful_response?(response)
47
+ if response.success?
42
48
  response
43
49
  elsif MAX_RETRIES <= tries
44
50
  $stderr.puts "Max retries exhausted while waiting for deployment credentials. Aborting."
45
51
  exit 1
46
52
  else
47
- sleep RETRY_INTERVAL_SECONDS * tries
53
+ if tries > 3
54
+ sleep LONG_RETRY_INTERVAL
55
+ else
56
+ sleep SHORT_RETRY_INTERVAL
57
+ end
58
+
48
59
  get_deployment_with_delay_and_retry(url, tries + 1)
49
60
  end
50
61
  end
@@ -67,11 +78,27 @@ class Envirobly::Api
67
78
 
68
79
  private
69
80
  def api_v1_shape_validations_url
70
- URI::HTTPS.build(host: HOST, path: "/api/v1/shape_validations")
81
+ api_url_for "v1/shape_validations"
71
82
  end
72
83
 
73
84
  def api_v1_deployments_url
74
- URI::HTTPS.build(host: HOST, path: "/api/v1/deployments")
85
+ api_url_for "v1/deployments"
86
+ end
87
+
88
+ def api_v1_accounts_url
89
+ api_url_for "v1/accounts"
90
+ end
91
+
92
+ def api_v1_regions_url
93
+ api_url_for "v1/regions"
94
+ end
95
+
96
+ def api_v1_instance_types_url(region)
97
+ api_url_for "v1/instance_types", query: "region=#{region}"
98
+ end
99
+
100
+ def api_url_for(path, query: nil)
101
+ URI::HTTPS.build(host: HOST, path: "/api/#{path}", query:)
75
102
  end
76
103
 
77
104
  def request(url, type:, headers: {})
@@ -91,6 +118,10 @@ class Envirobly::Api
91
118
  def response.object
92
119
  @json_parsed_body ||= JSON.parse(body)
93
120
  end
121
+
122
+ def response.success?
123
+ (200..299).include?(code.to_i)
124
+ end
94
125
  end
95
126
  end
96
127
 
@@ -101,8 +132,4 @@ class Envirobly::Api
101
132
  def authorization_headers
102
133
  { "Authorization" => @access_token.as_http_bearer }
103
134
  end
104
-
105
- def successful_response?(response)
106
- (200..299).include?(response.code.to_i)
107
- end
108
135
  end
@@ -1,4 +1,6 @@
1
1
  class Envirobly::Cli::Main < Envirobly::Base
2
+ include Envirobly::Colorize
3
+
2
4
  desc "version", "Show Envirobly CLI version"
3
5
  method_option :pure, type: :boolean, default: false
4
6
  def version
@@ -9,104 +11,117 @@ class Envirobly::Cli::Main < Envirobly::Base
9
11
  end
10
12
  end
11
13
 
14
+ desc "signin", "Set access token generated at Envirobly"
15
+ def signin
16
+ access_token = Envirobly::AccessToken.new(shell:)
17
+ access_token.set
18
+ end
19
+
20
+ desc "signout", "Sign out"
21
+ def signout
22
+ Envirobly::AccessToken.destroy
23
+ say "You've signed out."
24
+ say "This didn't delete the access token itself."
25
+ say "You can sign in again with `envirobly signin`."
26
+ end
27
+
28
+ desc "set_default_account", "Choose default account to deploy the current project to"
29
+ def set_default_account
30
+ Envirobly::Defaults::Account.new(shell:).require_id
31
+ end
32
+
33
+ desc "set_default_region", "Set default region for the current project when deploying for the first time"
34
+ def set_default_region
35
+ Envirobly::Defaults::Region.new(shell:).require_id
36
+ end
37
+
12
38
  desc "validate", "Validates config"
13
39
  def validate
14
- configs = Envirobly::Configs.new
40
+ Envirobly::AccessToken.new(shell:).require!
41
+
42
+ configs = Envirobly::Config.new
15
43
  api = Envirobly::Api.new
16
44
 
17
45
  params = { validation: configs.to_params }
18
46
  response = api.validate_shape params
19
47
 
20
48
  if response.object.fetch("valid")
21
- puts "All checks pass."
49
+ puts "Config is valid #{green_check}"
22
50
  else
23
- response.object.fetch("errors").each do |config_path, messages|
24
- puts "#{config_path}:"
25
- puts
26
- messages.each_with_index do |message, index|
27
- puts " #{message}"
28
- puts
29
- end
30
- end
31
-
51
+ display_config_errors response.object.fetch("errors")
32
52
  exit 1
33
53
  end
34
54
  end
35
55
 
56
+ desc "instance_types [region]", "List instance types in a given region, including price and performance characteristics."
57
+ def instance_types(region = nil)
58
+ default_region = Envirobly::Defaults::Region.new(shell:)
59
+ region = region.presence || default_region.require_if_none
60
+
61
+ api = Envirobly::Api.new
62
+ table_data = api.list_instance_types(region).object.map do |item|
63
+ [
64
+ item["code"],
65
+ item["vcpu"],
66
+ Envirobly::Numeric.new(item["memory"], short: true),
67
+ Envirobly::Numeric.new(item["monthly_price"]),
68
+ item["group"]
69
+ ]
70
+ end
71
+
72
+ print_table [ [ "Name", "vCPU", "Memory (GB)", "Monthly price ($)", "Group" ] ] +
73
+ table_data, borders: true
74
+ end
75
+
36
76
  desc "deploy [ENVIRON_NAME]", <<~TXT
37
- Deploy to environment identified by name.
38
- When name is empty, current git branch name is used.
77
+ Deploy to environ identified by name.
78
+ Name can contain letters, numbers, dashes or underscores.
79
+ If environ name is left blank, current git branch name is used.
39
80
  TXT
81
+ method_option :account_id, type: :numeric
82
+ method_option :region, type: :string
83
+ method_option :project, type: :string
40
84
  method_option :commit, type: :string, default: "HEAD"
41
85
  method_option :dry_run, type: :boolean, default: false
42
- method_option :account_id, type: :numeric
43
- method_option :project_name, type: :string
44
- method_option :project_region, type: :string
45
- def deploy(environ_name = Envirobly::Git.new.current_branch)
46
- deployment = Envirobly::Deployment.new(
47
- environ_name:,
48
- commit_ref: options.commit,
49
- account_id: options.account_id,
50
- project_name: options.project_name,
51
- project_region: options.project_region
52
- )
53
- deployment.perform(dry_run: options.dry_run)
54
- end
55
-
56
- desc "set_access_token TOKEN", "Save and use an access token generated at Envirobly"
57
- def set_access_token
58
- token = ask("Access Token:", echo: false).strip
86
+ def deploy(environ_name = nil)
87
+ commit = Envirobly::Git::Commit.new options.commit
59
88
 
60
- if token.blank?
61
- $stderr.puts
62
- $stderr.puts "Token can't be empty."
89
+ unless commit.exists?
90
+ say_error "Commit '#{commit.ref}' doesn't exist in this repository. Aborting."
63
91
  exit 1
64
92
  end
65
93
 
66
- Envirobly::AccessToken.new(token).save
67
- end
94
+ Envirobly::AccessToken.new(shell:).require!
68
95
 
69
- desc "push", "Push commit manifest and blobs to S3"
70
- def push(region, bucket, ref = "HEAD")
71
- commit = Envirobly::Git::Commit.new ref
72
- s3 = Envirobly::Aws::S3.new(region:, bucket:)
73
- s3.push commit
96
+ environ_name = environ_name.presence || commit.current_branch
97
+ project_name = nil
98
+ project_id = nil
99
+
100
+ if options.project.present?
101
+ if options.project =~ Envirobly::Defaults::Project.regexp
102
+ project_id = $1.to_i
103
+ else
104
+ project_name = options.project
105
+ end
106
+ end
107
+
108
+ deployment = Envirobly::Deployment.new(
109
+ account_id: options.account_id,
110
+ region: options.region,
111
+ project_name:,
112
+ environ_name:,
113
+ project_id:,
114
+ commit:,
115
+ shell:
116
+ )
117
+ deployment.perform(dry_run: options.dry_run)
74
118
  end
75
119
 
76
- desc "pull", "Download working copy from S3"
120
+ desc "pull", "Download build context"
77
121
  def pull(region, bucket, ref, path)
78
122
  Envirobly::Duration.measure("Build context download took %s") do
79
123
  s3 = Envirobly::Aws::S3.new(region:, bucket:)
80
124
  s3.pull ref, path
81
125
  end
82
126
  end
83
-
84
- desc "object_tree", "Show object tree used for deployments"
85
- method_option :commit, type: :string, default: "HEAD"
86
- def object_tree
87
- commit = Envirobly::Git::Commit.new options.commit
88
- puts "Commit: #{commit.ref}"
89
- pp commit.object_tree
90
- puts "SHA256: #{commit.object_tree_checksum}"
91
- end
92
-
93
- desc "measure", "POC of Envirobly::Duration"
94
- def measure
95
- Envirobly::Duration.measure do
96
- print "Doing something for 2s"
97
- sleep 2
98
- end
99
-
100
- Envirobly::Duration.measure do
101
- print "Doing something else for 100ms"
102
- sleep 0.1
103
- end
104
-
105
- Envirobly::Duration.measure("Custom message, took %s") do
106
- puts "Sleeping 2.5s with custom message"
107
- sleep 2.5
108
- end
109
-
110
- puts "Done."
111
- end
112
127
  end
@@ -11,6 +11,10 @@ module Envirobly::Colorize
11
11
  [ FAINT, text, RESET ].join
12
12
  end
13
13
 
14
+ def bold(text)
15
+ [ BOLD, text, RESET ].join
16
+ end
17
+
14
18
  def green(text)
15
19
  [ GREEN, text, RESET ].join
16
20
  end
@@ -18,4 +22,33 @@ module Envirobly::Colorize
18
22
  def yellow(text)
19
23
  [ YELLOW, text, RESET ].join
20
24
  end
25
+
26
+ def red(text)
27
+ [ RED, text, RESET ].join
28
+ end
29
+
30
+ def green_check
31
+ green("✔")
32
+ end
33
+
34
+ def downwards_arrow_to_right
35
+ "↳"
36
+ end
37
+
38
+ def cross
39
+ "✖"
40
+ end
41
+
42
+ def display_config_errors(errors)
43
+ puts "#{red(cross)} Config contains the following issues:"
44
+
45
+ errors.each do |error|
46
+ puts
47
+ puts " #{error["message"]}"
48
+
49
+ if error["path"]
50
+ puts faint(" #{downwards_arrow_to_right} #{error["path"]}")
51
+ end
52
+ end
53
+ end
21
54
  end
@@ -0,0 +1,38 @@
1
+ require "dotenv"
2
+
3
+ class Envirobly::Config
4
+ DIR = ".envirobly"
5
+ BASE = "deploy.yml"
6
+ ENV_VARS = "env"
7
+ OVERRIDES_PATTERN = /deploy\.([a-z0-9\-_]+)\.yml/i
8
+
9
+ def initialize(dir = DIR)
10
+ @dir = Pathname.new dir
11
+ end
12
+
13
+ def to_params
14
+ {
15
+ configs:,
16
+ env_vars:
17
+ }
18
+ end
19
+
20
+ private
21
+ def configs
22
+ Dir.entries(@dir).map do |file|
23
+ path = File.join(@dir, file)
24
+
25
+ next unless File.file?(path) && config_file?(file)
26
+
27
+ [ "#{DIR}/#{file}", File.read(path) ]
28
+ end.compact.to_h
29
+ end
30
+
31
+ def env_vars
32
+ Dotenv.parse @dir.join(ENV_VARS), overwrite: true
33
+ end
34
+
35
+ def config_file?(file)
36
+ file == BASE || file.match?(OVERRIDES_PATTERN)
37
+ end
38
+ end
@@ -0,0 +1,47 @@
1
+ class Envirobly::Default
2
+ attr_accessor :shell
3
+
4
+ def self.key = "url"
5
+
6
+ def initialize(shell: nil)
7
+ @path = File.join Envirobly::Config::DIR, "defaults", self.class.file
8
+ @shell = shell
9
+ end
10
+
11
+ def id
12
+ if File.exist?(@path)
13
+ content = YAML.safe_load_file(@path)
14
+
15
+ if content[self.class.key] =~ self.class.regexp
16
+ return cast_id($1)
17
+ end
18
+ end
19
+
20
+ nil
21
+ end
22
+
23
+ def save(url)
24
+ unless url =~ self.class.regexp
25
+ raise ArgumentError, "'#{url}' must match #{self.class.regexp}"
26
+ end
27
+
28
+ FileUtils.mkdir_p(File.dirname(@path))
29
+ content = YAML.dump({ self.class.key => url })
30
+ File.write(@path, content)
31
+ end
32
+
33
+ def save_if_none(url)
34
+ return if id.present?
35
+
36
+ save(url)
37
+ end
38
+
39
+ def require_if_none
40
+ id || require_id
41
+ end
42
+
43
+ private
44
+ def cast_id(value)
45
+ value.to_i
46
+ end
47
+ end
@@ -0,0 +1,46 @@
1
+ class Envirobly::Defaults::Account < Envirobly::Default
2
+ include Envirobly::Colorize
3
+
4
+ def self.file = "account.yml"
5
+ def self.regexp = /accounts\/(\d+)/
6
+
7
+ def require_id
8
+ api = Envirobly::Api.new
9
+ accounts = api.list_accounts
10
+
11
+ if accounts.object.blank?
12
+ shell.say_error "Please connect an AWS account to your Envirobly account first."
13
+ exit 1
14
+ end
15
+
16
+ account = accounts.object.first
17
+ id = account["id"]
18
+
19
+ if accounts.object.size > 1
20
+ puts "Choose default account to deploy this project to:"
21
+
22
+ data = [ [ "ID", "Name", "AWS number", "URL" ] ] +
23
+ accounts.object.pluck("id", "name", "aws_id", "url")
24
+
25
+ shell.print_table data, borders: true
26
+
27
+ limited_to = accounts.object.pluck("id").map(&:to_s)
28
+
29
+ begin
30
+ id = shell.ask("Type in the account ID:", limited_to:).to_i
31
+ rescue Interrupt
32
+ shell.say_error "Cancelled"
33
+ exit
34
+ end
35
+
36
+ account = accounts.object.find { |a| a["id"] == id }
37
+ end
38
+
39
+ save account["url"]
40
+
41
+ shell.say "Account ##{id} set as project default "
42
+ shell.say green_check
43
+
44
+ id
45
+ end
46
+ end
@@ -0,0 +1,4 @@
1
+ class Envirobly::Defaults::Project < Envirobly::Default
2
+ def self.file = "project.yml"
3
+ def self.regexp = /projects\/(\d+)/
4
+ end
@@ -0,0 +1,45 @@
1
+ class Envirobly::Defaults::Region < Envirobly::Default
2
+ include Envirobly::Colorize
3
+
4
+ def self.file = "region.yml"
5
+ def self.regexp = /([a-z0-9\-)]+)/
6
+ def self.key = "code"
7
+
8
+ def require_id
9
+ api = Envirobly::Api.new
10
+ response = api.list_regions
11
+
12
+ shell.say "Choose default project region to deploy to:"
13
+ shell.print_table [ [ "Name", "Location", "Group" ] ] +
14
+ response.object.pluck("code", "title", "group_title"), borders: true
15
+
16
+ code = nil
17
+ limited_to = response.object.pluck("code")
18
+
19
+ while code.nil?
20
+ begin
21
+ code = shell.ask("Type in the region name:", default: "us-east-1")
22
+ rescue Interrupt
23
+ shell.say_error "Cancelled"
24
+ exit
25
+ end
26
+
27
+ unless code.in?(limited_to)
28
+ shell.say_error "'#{code}' is not a supported region, please try again"
29
+ code = nil
30
+ end
31
+ end
32
+
33
+ save code
34
+
35
+ shell.say "Region '#{id}' set as project default "
36
+ shell.say green_check
37
+
38
+ id
39
+ end
40
+
41
+ private
42
+ def cast_id(value)
43
+ value
44
+ end
45
+ end
@@ -0,0 +1,2 @@
1
+ module Envirobly::Defaults
2
+ end
@@ -3,24 +3,28 @@ require "yaml"
3
3
  class Envirobly::Deployment
4
4
  include Envirobly::Colorize
5
5
 
6
- def initialize(environ_name:, commit_ref:, account_id:, project_name:, project_region:)
6
+ def initialize(environ_name:, commit:, account_id:, project_name:, project_id:, region:, shell:)
7
7
  @environ_name = environ_name
8
- @commit = Envirobly::Git::Commit.new commit_ref
9
-
10
- unless @commit.exists?
11
- $stderr.puts "Commit #{commit_ref} doesn't exist in this repository. Aborting."
12
- exit 1
8
+ @commit = commit
9
+ @config = Envirobly::Config.new
10
+ @default_account = Envirobly::Defaults::Account.new(shell:)
11
+ @default_project = Envirobly::Defaults::Project.new(shell:)
12
+ @default_region = Envirobly::Defaults::Region.new(shell:)
13
+
14
+ if account_id.blank?
15
+ account_id = @default_account.require_if_none
13
16
  end
14
17
 
15
- @configs = Envirobly::Configs.new
18
+ if project_id.blank? && project_name.blank?
19
+ project_id = @default_project.id
16
20
 
17
- if account_id.nil?
18
- account_id = @configs.default_account_id
21
+ if project_id.nil?
22
+ project_name = File.basename(Dir.pwd)
23
+ end
19
24
  end
20
25
 
21
- project_id = nil
22
- if project_name.nil?
23
- project_id = @configs.default_project_id
26
+ if region.blank?
27
+ region = @default_region.require_if_none
24
28
  end
25
29
 
26
30
  @params = {
@@ -30,7 +34,7 @@ class Envirobly::Deployment
30
34
  project: {
31
35
  id: project_id,
32
36
  name: project_name,
33
- region: project_region
37
+ region:
34
38
  },
35
39
  deployment: {
36
40
  environ_name:,
@@ -38,7 +42,7 @@ class Envirobly::Deployment
38
42
  commit_time: @commit.time,
39
43
  commit_message: @commit.message,
40
44
  object_tree_checksum: @commit.object_tree_checksum,
41
- **@configs.to_params
45
+ **@config.to_params
42
46
  }
43
47
  }
44
48
  end
@@ -58,11 +62,18 @@ class Envirobly::Deployment
58
62
  api = Envirobly::Api.new
59
63
 
60
64
  Envirobly::Duration.measure do
61
- print "Preparing project..."
62
65
  response = api.create_deployment @params
63
66
 
64
- @configs.save_default_account(response.object.fetch("account_url"))
65
- @configs.save_default_project(response.object.fetch("project_url"))
67
+ unless response.success?
68
+ display_config_errors response.object.fetch("errors")
69
+ exit 1
70
+ end
71
+
72
+ print "Preparing project..."
73
+
74
+ @default_account.save_if_none response.object.fetch("account_url")
75
+ @default_project.save_if_none response.object.fetch("project_url")
76
+ @default_region.save_if_none response.object.fetch("region")
66
77
 
67
78
  # Fetch credentials for build context upload
68
79
  @deployment_url = response.object.fetch("url")
@@ -12,7 +12,7 @@ class Envirobly::Duration
12
12
  duration = format_duration(measurement)
13
13
 
14
14
  if message.nil?
15
- puts [ "", green("✔"), faint(duration) ].join(" ")
15
+ puts [ "", green_check, faint(duration) ].join(" ")
16
16
  else
17
17
  puts sprintf(message, duration)
18
18
  end
@@ -53,7 +53,7 @@ class Envirobly::Git::Commit < Envirobly::Git
53
53
  git(%(ls-tree -r #{ref}), chdir:).stdout.lines.each do |line|
54
54
  mode, type, object_hash, path = line.split(/\s+/)
55
55
 
56
- next if path.start_with?("#{Envirobly::Configs::DIR}/")
56
+ next if path.start_with?("#{Envirobly::Config::DIR}/")
57
57
 
58
58
  if type == "commit"
59
59
  objects.merge! object_tree(ref: object_hash, chdir: File.join(chdir, path))
@@ -74,6 +74,6 @@ class Envirobly::Git::Commit < Envirobly::Git
74
74
  # @deprecated
75
75
  def objects_with_checksum_at(path)
76
76
  git(%{ls-tree #{@ref} --format='%(objectname) %(path)' #{path}}).stdout.lines.map(&:chomp).
77
- reject { _1.split(" ").last == Envirobly::Configs::DIR }
77
+ reject { _1.split(" ").last == Envirobly::Config::DIR }
78
78
  end
79
79
  end
@@ -0,0 +1,14 @@
1
+ class Envirobly::Numeric < Numeric
2
+ def initialize(value, short: false)
3
+ @value = value
4
+ @short = short
5
+ end
6
+
7
+ def to_s
8
+ if @short
9
+ @value.to_s.delete_suffix(".0")
10
+ else
11
+ "%.2f" % @value
12
+ end
13
+ end
14
+ end
@@ -1,3 +1,3 @@
1
1
  module Envirobly
2
- VERSION = "0.10.0"
2
+ VERSION = "1.1.0"
3
3
  end
data/lib/envirobly.rb CHANGED
@@ -1,10 +1,10 @@
1
1
  module Envirobly
2
2
  end
3
3
 
4
+ require "active_support"
5
+ require "active_support/core_ext"
4
6
  require "zeitwerk"
5
- require "core_ext"
6
7
 
7
8
  loader = Zeitwerk::Loader.for_gem
8
- loader.ignore("#{__dir__}/core_ext.rb")
9
9
  loader.setup
10
10
  loader.eager_load
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: envirobly
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Starsi
@@ -37,20 +37,6 @@ dependencies:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
39
  version: '2.6'
40
- - !ruby/object:Gem::Dependency
41
- name: ostruct
42
- requirement: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - "~>"
45
- - !ruby/object:Gem::Version
46
- version: 0.1.0
47
- type: :runtime
48
- prerelease: false
49
- version_requirements: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - "~>"
52
- - !ruby/object:Gem::Version
53
- version: 0.1.0
54
40
  - !ruby/object:Gem::Dependency
55
41
  name: aws-sdk-s3
56
42
  requirement: !ruby/object:Gem::Requirement
@@ -94,75 +80,75 @@ dependencies:
94
80
  - !ruby/object:Gem::Version
95
81
  version: '3.1'
96
82
  - !ruby/object:Gem::Dependency
97
- name: debug
83
+ name: activesupport
98
84
  requirement: !ruby/object:Gem::Requirement
99
85
  requirements:
100
- - - ">="
86
+ - - "~>"
101
87
  - !ruby/object:Gem::Version
102
- version: '0'
103
- type: :development
88
+ version: '8.0'
89
+ type: :runtime
104
90
  prerelease: false
105
91
  version_requirements: !ruby/object:Gem::Requirement
106
92
  requirements:
107
- - - ">="
93
+ - - "~>"
108
94
  - !ruby/object:Gem::Version
109
- version: '0'
95
+ version: '8.0'
110
96
  - !ruby/object:Gem::Dependency
111
- name: minitest
97
+ name: debug
112
98
  requirement: !ruby/object:Gem::Requirement
113
99
  requirements:
114
- - - ">="
100
+ - - "~>"
115
101
  - !ruby/object:Gem::Version
116
- version: '0'
102
+ version: '1.10'
117
103
  type: :development
118
104
  prerelease: false
119
105
  version_requirements: !ruby/object:Gem::Requirement
120
106
  requirements:
121
- - - ">="
107
+ - - "~>"
122
108
  - !ruby/object:Gem::Version
123
- version: '0'
109
+ version: '1.10'
124
110
  - !ruby/object:Gem::Dependency
125
- name: mocha
111
+ name: minitest
126
112
  requirement: !ruby/object:Gem::Requirement
127
113
  requirements:
128
- - - ">="
114
+ - - "~>"
129
115
  - !ruby/object:Gem::Version
130
- version: '0'
116
+ version: '5.25'
131
117
  type: :development
132
118
  prerelease: false
133
119
  version_requirements: !ruby/object:Gem::Requirement
134
120
  requirements:
135
- - - ">="
121
+ - - "~>"
136
122
  - !ruby/object:Gem::Version
137
- version: '0'
123
+ version: '5.25'
138
124
  - !ruby/object:Gem::Dependency
139
- name: activesupport
125
+ name: mocha
140
126
  requirement: !ruby/object:Gem::Requirement
141
127
  requirements:
142
- - - ">="
128
+ - - "~>"
143
129
  - !ruby/object:Gem::Version
144
- version: '0'
130
+ version: '2.7'
145
131
  type: :development
146
132
  prerelease: false
147
133
  version_requirements: !ruby/object:Gem::Requirement
148
134
  requirements:
149
- - - ">="
135
+ - - "~>"
150
136
  - !ruby/object:Gem::Version
151
- version: '0'
137
+ version: '2.7'
152
138
  - !ruby/object:Gem::Dependency
153
139
  name: railties
154
140
  requirement: !ruby/object:Gem::Requirement
155
141
  requirements:
156
- - - ">="
142
+ - - "~>"
157
143
  - !ruby/object:Gem::Version
158
- version: '0'
144
+ version: '8.0'
159
145
  type: :development
160
146
  prerelease: false
161
147
  version_requirements: !ruby/object:Gem::Requirement
162
148
  requirements:
163
- - - ">="
149
+ - - "~>"
164
150
  - !ruby/object:Gem::Version
165
- version: '0'
151
+ version: '8.0'
166
152
  email: klevo@klevo.sk
167
153
  executables:
168
154
  - envirobly
@@ -171,7 +157,6 @@ extra_rdoc_files: []
171
157
  files:
172
158
  - LICENSE
173
159
  - bin/envirobly
174
- - lib/core_ext.rb
175
160
  - lib/envirobly.rb
176
161
  - lib/envirobly/access_token.rb
177
162
  - lib/envirobly/api.rb
@@ -182,12 +167,18 @@ files:
182
167
  - lib/envirobly/cli.rb
183
168
  - lib/envirobly/cli/main.rb
184
169
  - lib/envirobly/colorize.rb
185
- - lib/envirobly/configs.rb
170
+ - lib/envirobly/config.rb
171
+ - lib/envirobly/default.rb
172
+ - lib/envirobly/defaults.rb
173
+ - lib/envirobly/defaults/account.rb
174
+ - lib/envirobly/defaults/project.rb
175
+ - lib/envirobly/defaults/region.rb
186
176
  - lib/envirobly/deployment.rb
187
177
  - lib/envirobly/duration.rb
188
178
  - lib/envirobly/git.rb
189
179
  - lib/envirobly/git/commit.rb
190
180
  - lib/envirobly/git/unstaged.rb
181
+ - lib/envirobly/numeric.rb
191
182
  - lib/envirobly/version.rb
192
183
  homepage: https://github.com/envirobly/envirobly-cli
193
184
  licenses:
@@ -200,14 +191,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
200
191
  requirements:
201
192
  - - ">="
202
193
  - !ruby/object:Gem::Version
203
- version: '0'
194
+ version: '3.2'
204
195
  required_rubygems_version: !ruby/object:Gem::Requirement
205
196
  requirements:
206
197
  - - ">="
207
198
  - !ruby/object:Gem::Version
208
199
  version: '0'
209
200
  requirements: []
210
- rubygems_version: 3.6.7
201
+ rubygems_version: 3.6.9
211
202
  specification_version: 4
212
203
  summary: Envirobly command line interface
213
204
  test_files: []
data/lib/core_ext.rb DELETED
@@ -1,113 +0,0 @@
1
- class Array
2
- alias_method :blank?, :empty?
3
-
4
- def present?
5
- !empty?
6
- end
7
-
8
- def second
9
- self[1]
10
- end
11
-
12
- def third
13
- self[2]
14
- end
15
-
16
- def fourth
17
- self[3]
18
- end
19
-
20
- def fifth
21
- self[4]
22
- end
23
- end
24
-
25
- class Object
26
- def blank?
27
- respond_to?(:empty?) ? !!empty? : false
28
- end
29
-
30
- def present?
31
- !blank?
32
- end
33
-
34
- def presence
35
- self if present?
36
- end
37
- end
38
-
39
- class NilClass
40
- def blank?
41
- true
42
- end
43
-
44
- def present?
45
- false
46
- end
47
- end
48
-
49
- class FalseClass
50
- def blank?
51
- true
52
- end
53
-
54
- def present?
55
- false
56
- end
57
- end
58
-
59
- class TrueClass
60
- def blank?
61
- false
62
- end
63
-
64
- def present?
65
- true
66
- end
67
- end
68
-
69
- class Hash
70
- alias_method :blank?, :empty?
71
-
72
- def present?
73
- !empty?
74
- end
75
- end
76
-
77
- class Symbol
78
- alias_method :blank?, :empty?
79
-
80
- def present?
81
- !empty?
82
- end
83
- end
84
-
85
- class String
86
- def blank?
87
- strip.empty?
88
- end
89
-
90
- def present?
91
- !blank?
92
- end
93
- end
94
-
95
- class Numeric
96
- def blank?
97
- false
98
- end
99
-
100
- def present?
101
- true
102
- end
103
- end
104
-
105
- class Time
106
- def blank?
107
- false
108
- end
109
-
110
- def present?
111
- true
112
- end
113
- end
@@ -1,79 +0,0 @@
1
- require "dotenv"
2
-
3
- class Envirobly::Configs
4
- DIR = ".envirobly"
5
- ENV = "env"
6
- BASE = "deploy.yml"
7
- OVERRIDES_PATTERN = /deploy\.([a-z0-9\-_]+)\.yml/i
8
- DEFAULTS_DIR = File.join DIR, "defaults"
9
- DEFAULT_ACCOUNT_PATH = File.join(DEFAULTS_DIR, "account.yml")
10
- DEFAULT_PROJECT_PATH = File.join(DEFAULTS_DIR, "project.yml")
11
-
12
- def initialize(dir = DIR)
13
- @dir = Pathname.new dir
14
- end
15
-
16
- def to_params
17
- {
18
- configs:,
19
- env_vars:
20
- }
21
- end
22
-
23
- def default_project_id
24
- if File.exist?(DEFAULT_PROJECT_PATH)
25
- content = YAML.safe_load_file(DEFAULT_PROJECT_PATH)
26
- if content["url"] =~ /projects\/(\d+)/
27
- return $1.to_i
28
- end
29
- end
30
-
31
- nil
32
- end
33
-
34
- def default_account_id
35
- if File.exist?(DEFAULT_ACCOUNT_PATH)
36
- content = YAML.safe_load_file(DEFAULT_ACCOUNT_PATH)
37
- if content["url"] =~ /accounts\/(\d+)/
38
- return $1.to_i
39
- end
40
- end
41
-
42
- nil
43
- end
44
-
45
- def save_default_account(url)
46
- return if File.exist?(DEFAULT_ACCOUNT_PATH)
47
-
48
- FileUtils.mkdir_p(DEFAULTS_DIR)
49
- content = YAML.dump({ "url" => url })
50
- File.write(DEFAULT_ACCOUNT_PATH, content)
51
- end
52
-
53
- def save_default_project(url)
54
- return if File.exist?(DEFAULT_PROJECT_PATH)
55
-
56
- FileUtils.mkdir_p(DEFAULTS_DIR)
57
- content = YAML.dump({ "url" => url })
58
- File.write(DEFAULT_PROJECT_PATH, content)
59
- end
60
-
61
- private
62
- def configs
63
- Dir.entries(@dir).map do |file|
64
- path = File.join(@dir, file)
65
-
66
- next unless File.file?(path) && config_file?(file)
67
-
68
- [ "#{DIR}/#{file}", File.read(path) ]
69
- end.compact.to_h
70
- end
71
-
72
- def env_vars
73
- Dotenv.parse @dir.join(ENV)
74
- end
75
-
76
- def config_file?(file)
77
- file == BASE || file.match?(OVERRIDES_PATTERN)
78
- end
79
- end