envirobly 0.1.0 → 0.3.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 +4 -4
- data/lib/envirobly/access_token.rb +36 -0
- data/lib/envirobly/api.rb +82 -0
- data/lib/envirobly/aws/credentials.rb +17 -0
- data/lib/envirobly/aws.rb +2 -0
- data/lib/envirobly/cli/main.rb +13 -0
- data/lib/envirobly/cli.rb +2 -1
- data/lib/envirobly/config.rb +81 -0
- data/lib/envirobly/deployment.rb +65 -0
- data/lib/envirobly/git/commit.rb +23 -0
- data/lib/envirobly/git.rb +2 -0
- data/lib/envirobly/version.rb +1 -1
- metadata +11 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1dc280b944283bd549516dd80fab598dff0c4e093f8d16dada2e8f3663c650ab
|
4
|
+
data.tar.gz: b5aa5d9dbe07e35b00675830f900fa4a6ec7d29f894a9edb3ec780744bdf5123
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c5d4f46cc0904a036b5d7c8f16aa9eab588a5cd943bf650d98277a257138295183a6e5769944528044613f990ae9d18024066b07cbf69a6ba40619e43e88189d
|
7
|
+
data.tar.gz: bc2f471d2b2e5f372cf52557154fc18ef9147c47592a1f9369bb28fceec129313226cf37668b71c954d5d8a8c92747abb34403b0bed892544f958b4cd2794eab
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require "pathname"
|
3
|
+
|
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)
|
8
|
+
else
|
9
|
+
@token = token
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
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}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def as_http_bearer
|
21
|
+
"Bearer #{@token}"
|
22
|
+
end
|
23
|
+
|
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")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def access_token_path
|
34
|
+
config_root.join "access_token"
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require "json"
|
2
|
+
require "net/http"
|
3
|
+
require "socket"
|
4
|
+
require "uri"
|
5
|
+
|
6
|
+
class Envirobly::Api
|
7
|
+
HOST = ENV["ENVIROBLY_API_HOST"] || "envirobly.com"
|
8
|
+
USER_AGENT = "Envirobly CLI v#{Envirobly::VERSION}"
|
9
|
+
CONTENT_TYPE = "application/json"
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@access_token = Envirobly::AccessToken.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_deployment(params)
|
16
|
+
post_as_json(api_v1_deployments_url, params:, headers: authorization_headers).tap do |response|
|
17
|
+
unless response.code.to_i == 200
|
18
|
+
$stderr.puts "Deployment creation request responded with #{response.code}. Aborting."
|
19
|
+
exit 1
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
RETRY_INTERVAL_SECONDS = 3
|
25
|
+
MAX_RETRIES = 5
|
26
|
+
def get_deployment_with_delay_and_retry(url, tries = 1)
|
27
|
+
sleep RETRY_INTERVAL_SECONDS * tries
|
28
|
+
response = get_as_json URI(url)
|
29
|
+
|
30
|
+
if response.code.to_i == 200
|
31
|
+
return response
|
32
|
+
elsif MAX_RETRIES <= tries
|
33
|
+
$stderr.puts "Max retries exhausted while waiting for deployment credentials. Aborting."
|
34
|
+
exit 1
|
35
|
+
else
|
36
|
+
sleep RETRY_INTERVAL_SECONDS * tries
|
37
|
+
get_deployment_with_delay_and_retry(url, tries + 1)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def get_as_json(uri, headers: {})
|
43
|
+
request(uri, type: Net::HTTP::Get, headers:)
|
44
|
+
end
|
45
|
+
|
46
|
+
def post_as_json(uri, params: {}, headers: {})
|
47
|
+
request(uri, type: Net::HTTP::Post, headers:) do |request|
|
48
|
+
request.body = params.to_json
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def api_v1_deployments_url
|
53
|
+
URI::HTTPS.build(host: HOST, path: "/api/v1/deployments")
|
54
|
+
end
|
55
|
+
|
56
|
+
def request(uri, type:, headers: {})
|
57
|
+
http = Net::HTTP.new uri.host, uri.port
|
58
|
+
http.use_ssl = true
|
59
|
+
http.open_timeout = 10
|
60
|
+
http.read_timeout = 10
|
61
|
+
|
62
|
+
headers = default_headers.merge headers
|
63
|
+
request = type.new(uri, headers)
|
64
|
+
request.content_type = CONTENT_TYPE
|
65
|
+
|
66
|
+
yield request if block_given?
|
67
|
+
|
68
|
+
http.request(request).tap do |response|
|
69
|
+
def response.object
|
70
|
+
@json_parsed_body ||= JSON.parse body
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def default_headers
|
76
|
+
{ "User-Agent" => USER_AGENT, "X-Cli-Host" => Socket.gethostname }
|
77
|
+
end
|
78
|
+
|
79
|
+
def authorization_headers
|
80
|
+
{ "Authorization" => @access_token.as_http_bearer }
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Envirobly::Aws::Credentials
|
2
|
+
def initialize(params)
|
3
|
+
@params = params
|
4
|
+
end
|
5
|
+
|
6
|
+
def as_env_vars
|
7
|
+
[
|
8
|
+
%{AWS_ACCESS_KEY_ID="#{@params.fetch("access_key_id")}"},
|
9
|
+
%{AWS_SECRET_ACCESS_KEY="#{@params.fetch("secret_access_key")}"},
|
10
|
+
%{AWS_SESSION_TOKEN="#{@params.fetch("session_token")}"}
|
11
|
+
]
|
12
|
+
end
|
13
|
+
|
14
|
+
def as_inline_env_vars
|
15
|
+
as_env_vars.join " "
|
16
|
+
end
|
17
|
+
end
|
data/lib/envirobly/cli/main.rb
CHANGED
@@ -1,6 +1,19 @@
|
|
1
|
+
# require "debug"
|
2
|
+
|
1
3
|
class Envirobly::Cli::Main < Envirobly::Base
|
2
4
|
desc "version", "Show Envirobly CLI version"
|
3
5
|
def version
|
4
6
|
puts Envirobly::VERSION
|
5
7
|
end
|
8
|
+
|
9
|
+
desc "deploy ENVIRONMENT", "Deploy to environment identified by name or URL"
|
10
|
+
method_option :commit, type: :string, default: "HEAD"
|
11
|
+
def deploy(environment)
|
12
|
+
Envirobly::Deployment.new environment, options
|
13
|
+
end
|
14
|
+
|
15
|
+
desc "set_access_token TOKEN", "Save and use an access token generated at Envirobly"
|
16
|
+
def set_access_token(token)
|
17
|
+
Envirobly::AccessToken.new(token).save
|
18
|
+
end
|
6
19
|
end
|
data/lib/envirobly/cli.rb
CHANGED
@@ -1 +1,2 @@
|
|
1
|
-
module Envirobly::Cli
|
1
|
+
module Envirobly::Cli
|
2
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require "yaml"
|
2
|
+
require "json"
|
3
|
+
require "digest"
|
4
|
+
|
5
|
+
class Envirobly::Config
|
6
|
+
DIR = ".envirobly"
|
7
|
+
PATH = "#{DIR}/project.yml"
|
8
|
+
|
9
|
+
attr_reader :parsing_error
|
10
|
+
|
11
|
+
def initialize(commit)
|
12
|
+
@commit = commit
|
13
|
+
@parsing_error = nil
|
14
|
+
@project = parse_config_content_at_commit
|
15
|
+
|
16
|
+
if @project
|
17
|
+
transform_env_var_values!
|
18
|
+
append_image_tags!
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def dig(*args)
|
23
|
+
@project.dig *args
|
24
|
+
rescue NoMethodError
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_h
|
29
|
+
@project
|
30
|
+
end
|
31
|
+
|
32
|
+
def parsing_error?
|
33
|
+
!@parsing_error.nil?
|
34
|
+
end
|
35
|
+
|
36
|
+
def path
|
37
|
+
PATH
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
def parse_config_content_at_commit
|
42
|
+
YAML.load config_content_at_commit, aliases: true
|
43
|
+
rescue Psych::Exception => exception
|
44
|
+
@parsing_error = exception.message
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def config_content_at_commit
|
49
|
+
`git show #{@commit.ref}:#{path}`
|
50
|
+
end
|
51
|
+
|
52
|
+
def transform_env_var_values!
|
53
|
+
@project.fetch("services", {}).each do |logical_id, service|
|
54
|
+
service.fetch("env", {}).each do |key, value|
|
55
|
+
if value.is_a?(Hash) && value.has_key?("file")
|
56
|
+
@project["services"][logical_id]["env"][key] = File.read value.fetch("file")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
NON_BUILDABLE_TYPES = %w[ postgres mysql valkey ]
|
63
|
+
def append_image_tags!
|
64
|
+
@project.fetch("services", {}).each do |logical_id, service|
|
65
|
+
next if NON_BUILDABLE_TYPES.include?(service["type"]) || service["image_uri"]
|
66
|
+
|
67
|
+
dockerfile = service.fetch("dockerfile", "Dockerfile")
|
68
|
+
build_context = service.fetch("build_context", ".")
|
69
|
+
|
70
|
+
@project["services"][logical_id]["image_tag"] = Digest::SHA1.hexdigest [
|
71
|
+
git_path_checksums_at_commit(dockerfile),
|
72
|
+
git_path_checksums_at_commit(build_context)
|
73
|
+
].to_json
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def git_path_checksums_at_commit(path)
|
78
|
+
`git ls-tree #{@commit.ref} --format='%(objectname) %(path)' #{path}`.
|
79
|
+
lines.reject { _1.split(" ").last == DIR }
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
class Envirobly::Deployment
|
2
|
+
URL_MATCHER = /^https:\/\/envirobly\.(test|com)\/(\d+)\/environs\/(\d+)$/
|
3
|
+
|
4
|
+
def initialize(environment, options)
|
5
|
+
@commit = Envirobly::Git::Commit.new options.commit
|
6
|
+
|
7
|
+
unless @commit.exists?
|
8
|
+
$stderr.puts "Commit #{options.commit} doesn't exist in this repository. Aborting."
|
9
|
+
exit 1
|
10
|
+
end
|
11
|
+
|
12
|
+
config = Envirobly::Config.new(@commit)
|
13
|
+
if config.parsing_error?
|
14
|
+
$stderr.puts "Error while parsing #{config.path}"
|
15
|
+
$stderr.puts config.parsing_error
|
16
|
+
exit 1
|
17
|
+
end
|
18
|
+
|
19
|
+
params = {
|
20
|
+
environ: {
|
21
|
+
logical_id: environment
|
22
|
+
},
|
23
|
+
commit: {
|
24
|
+
ref: @commit.ref,
|
25
|
+
time: @commit.time,
|
26
|
+
message: @commit.message
|
27
|
+
},
|
28
|
+
config: config.to_h
|
29
|
+
}
|
30
|
+
|
31
|
+
puts params.to_json
|
32
|
+
|
33
|
+
unless environment =~ URL_MATCHER
|
34
|
+
if project_url = config.dig("remote", "origin")
|
35
|
+
params[:environ][:project_url] = project_url
|
36
|
+
else
|
37
|
+
$stderr.puts "{remote.origin} is required in .envirobly/project.yml"
|
38
|
+
exit 1
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
api = Envirobly::Api.new
|
43
|
+
response = api.create_deployment params
|
44
|
+
response = api.get_deployment_with_delay_and_retry response.object.fetch("url")
|
45
|
+
@credentials = Envirobly::Aws::Credentials.new response.object.fetch("credentials")
|
46
|
+
@bucket = response.object.fetch("bucket")
|
47
|
+
|
48
|
+
if archive_commit_and_upload
|
49
|
+
$stderr.puts "Build context exported into #{archive_uri}"
|
50
|
+
else
|
51
|
+
$stderr.puts "Error exporting build context. Aborting."
|
52
|
+
exit 1
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
def archive_uri
|
58
|
+
"s3://#{@bucket}/#{@commit.ref}.tar.gz"
|
59
|
+
end
|
60
|
+
|
61
|
+
def archive_commit_and_upload
|
62
|
+
`git archive --format=tar.gz #{@commit.ref} | #{@credentials.as_inline_env_vars} aws s3 cp - #{archive_uri}`
|
63
|
+
$?.success?
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "time"
|
2
|
+
|
3
|
+
class Envirobly::Git::Commit
|
4
|
+
def initialize(ref)
|
5
|
+
@ref = ref
|
6
|
+
end
|
7
|
+
|
8
|
+
def exists?
|
9
|
+
`git cat-file -t #{@ref}`.chomp("") == "commit"
|
10
|
+
end
|
11
|
+
|
12
|
+
def ref
|
13
|
+
@normalized_ref ||= `git rev-parse #{@ref}`.chomp("")
|
14
|
+
end
|
15
|
+
|
16
|
+
def message
|
17
|
+
`git log #{@ref} -n1 --pretty=%B`.chomp("")
|
18
|
+
end
|
19
|
+
|
20
|
+
def time
|
21
|
+
Time.parse `git log #{@ref} -n1 --date=iso --pretty=format:"%ad"`
|
22
|
+
end
|
23
|
+
end
|
data/lib/envirobly/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: envirobly
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Robert Starsi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-09-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -62,11 +62,19 @@ files:
|
|
62
62
|
- LICENSE
|
63
63
|
- bin/envirobly
|
64
64
|
- lib/envirobly.rb
|
65
|
+
- lib/envirobly/access_token.rb
|
66
|
+
- lib/envirobly/api.rb
|
67
|
+
- lib/envirobly/aws.rb
|
68
|
+
- lib/envirobly/aws/credentials.rb
|
65
69
|
- lib/envirobly/base.rb
|
66
70
|
- lib/envirobly/cli.rb
|
67
71
|
- lib/envirobly/cli/main.rb
|
72
|
+
- lib/envirobly/config.rb
|
73
|
+
- lib/envirobly/deployment.rb
|
74
|
+
- lib/envirobly/git.rb
|
75
|
+
- lib/envirobly/git/commit.rb
|
68
76
|
- lib/envirobly/version.rb
|
69
|
-
homepage: https://
|
77
|
+
homepage: https://github.com/envirobly/envirobly-cli
|
70
78
|
licenses:
|
71
79
|
- MIT
|
72
80
|
metadata: {}
|