terraspace 2.0.3 → 2.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.cody/README.md +1 -1
- data/.cody/all/project.rb +4 -0
- data/.cody/aws/project.rb +4 -0
- data/.cody/azurerm/project.rb +4 -0
- data/.cody/google/project.rb +4 -0
- data/.cody/none/project.rb +4 -0
- data/.cody/shared/script/install/infracost.sh +6 -0
- data/.cody/shared/script/install/terraform.sh +2 -2
- data/.cody/shared/script/install.sh +1 -0
- data/.pipedream/README.md +1 -1
- data/.pipedream/pipeline.rb +15 -8
- data/CHANGELOG.md +14 -0
- data/README.md +1 -0
- data/lib/templates/base/project/Gemfile.tt +5 -0
- data/lib/templates/base/project/config/app.rb +6 -2
- data/lib/templates/plugin/ci/CHANGELOG.md.tt +1 -1
- data/lib/templates/plugin/ci/lib/%gem_name%/vars.rb.tt +1 -1
- data/lib/terraspace/app.rb +16 -0
- data/lib/terraspace/cli/base.rb +1 -0
- data/lib/terraspace/cli/commander.rb +2 -1
- data/lib/terraspace/cli/down.rb +43 -6
- data/lib/terraspace/cli/new/plugin/ci.rb +1 -4
- data/lib/terraspace/cli/plan.rb +66 -2
- data/lib/terraspace/cli/up.rb +65 -11
- data/lib/terraspace/cloud/api/cani.rb +15 -9
- data/lib/terraspace/cloud/api/concern.rb +0 -1
- data/lib/terraspace/cloud/api/http_methods.rb +7 -2
- data/lib/terraspace/cloud/api.rb +29 -5
- data/lib/terraspace/cloud/base.rb +15 -28
- data/lib/terraspace/cloud/ci.rb +1 -8
- data/lib/terraspace/cloud/comment.rb +28 -0
- data/lib/terraspace/cloud/context.rb +1 -0
- data/lib/terraspace/cloud/cost/infracost.rb +80 -0
- data/lib/terraspace/cloud/cost.rb +68 -0
- data/lib/terraspace/cloud/git.rb +0 -0
- data/lib/terraspace/cloud/plan.rb +33 -15
- data/lib/terraspace/cloud/stream.rb +113 -0
- data/lib/terraspace/cloud/streamer.rb +9 -0
- data/lib/terraspace/cloud/update.rb +19 -19
- data/lib/terraspace/cloud/{folder → upload}/base.rb +1 -1
- data/lib/terraspace/cloud/{folder → upload}/package.rb +1 -1
- data/lib/terraspace/cloud/{folder → upload}/tidy.rb +1 -1
- data/lib/terraspace/cloud/upload.rb +53 -0
- data/lib/terraspace/cloud/vcs/base.rb +6 -0
- data/lib/terraspace/cloud/vcs/ci_env.rb +15 -0
- data/lib/terraspace/cloud/vcs/commenter.rb +75 -0
- data/lib/terraspace/cloud/vcs/interface.rb +14 -0
- data/lib/terraspace/cloud/vcs/local_env.rb +25 -0
- data/lib/terraspace/cloud/{ci/vcs → vcs/local_git}/base.rb +6 -3
- data/lib/terraspace/cloud/vcs/local_git/bitbucket.rb +17 -0
- data/lib/terraspace/cloud/vcs/local_git/github.rb +17 -0
- data/lib/terraspace/cloud/vcs/local_git/gitlab.rb +17 -0
- data/lib/terraspace/cloud/{ci/manual.rb → vcs/local_git.rb} +18 -10
- data/lib/terraspace/cloud/vcs.rb +21 -0
- data/lib/terraspace/logger.rb +1 -1
- data/lib/terraspace/shell/error.rb +1 -1
- data/lib/terraspace/terraform/runner.rb +4 -22
- data/lib/terraspace/util/popen.rb +67 -0
- data/lib/terraspace/version.rb +1 -1
- metadata +25 -23
- data/lib/templates/plugin/ci/lib/%gem_name%/pr.rb.tt +0 -15
- data/lib/terraspace/cloud/api/concern/record.rb +0 -18
- data/lib/terraspace/cloud/ci/generic.rb +0 -25
- data/lib/terraspace/cloud/ci/vcs/bitbucket.rb +0 -11
- data/lib/terraspace/cloud/ci/vcs/github.rb +0 -11
- data/lib/terraspace/cloud/ci/vcs/gitlab.rb +0 -11
- data/lib/terraspace/cloud/ci/vcs.rb +0 -18
- data/lib/terraspace/cloud/folder/uploader.rb +0 -37
- data/lib/terraspace/cloud/folder.rb +0 -11
- data/lib/terraspace/terraform/ihooks/after/apply.rb +0 -8
- data/lib/terraspace/terraform/ihooks/after/destroy.rb +0 -8
- data/lib/terraspace/terraform/ihooks/after/plan.rb +0 -46
- data/lib/terraspace/terraform/ihooks/base.rb +0 -17
- data/lib/terraspace/terraform/ihooks/before/apply.rb +0 -8
- data/lib/terraspace/terraform/ihooks/before/destroy.rb +0 -8
- data/lib/terraspace/terraform/ihooks/before/plan.rb +0 -20
data/lib/terraspace/cloud/api.rb
CHANGED
@@ -16,18 +16,42 @@ module Terraspace::Cloud
|
|
16
16
|
"orgs/#{@org}/projects/#{@project}/stacks/#{@stack}"
|
17
17
|
end
|
18
18
|
|
19
|
-
|
20
|
-
|
19
|
+
# data: {stream_id:}
|
20
|
+
def create_upload(data)
|
21
|
+
post("#{stack_path}/uploads", @options.merge(data))
|
21
22
|
end
|
22
23
|
|
23
|
-
#
|
24
|
+
# data: {stream_id:}
|
25
|
+
def create_stream(data)
|
26
|
+
post("#{stack_path}/streams", @options.merge(data))
|
27
|
+
end
|
28
|
+
|
29
|
+
# data: {id:, success:}
|
30
|
+
def complete_stream(data={})
|
31
|
+
post("#{stack_path}/streams/#{data[:id]}/complete", @options.merge(data))
|
32
|
+
end
|
33
|
+
|
34
|
+
# data: {upload_id: "upload-nRPSpyWd65Ps6978", kind: "apply", stack_id: '...'}
|
24
35
|
def create_plan(data)
|
25
|
-
post("#{stack_path}/plans",
|
36
|
+
post("#{stack_path}/plans", @options.merge(data))
|
26
37
|
end
|
27
38
|
|
28
39
|
# data: {upload_id: "upload-nRPSpyWd65Ps6978", kind: "apply", stack_id: '...'}
|
29
40
|
def create_update(data)
|
30
|
-
post("#{stack_path}/updates",
|
41
|
+
post("#{stack_path}/updates", @options.merge(data))
|
42
|
+
end
|
43
|
+
|
44
|
+
# data: {upload_id: "upload-nRPSpyWd65Ps6978", stack_id: '...'}
|
45
|
+
def create_cost(data)
|
46
|
+
post("#{stack_path}/costs", @options.merge(data))
|
47
|
+
end
|
48
|
+
|
49
|
+
def get_previous_cost(data)
|
50
|
+
get("#{stack_path}/costs/previous", @options.merge(data))
|
51
|
+
end
|
52
|
+
|
53
|
+
def get_comment(data)
|
54
|
+
get("#{stack_path}/comment", @options.merge(data))
|
31
55
|
end
|
32
56
|
end
|
33
57
|
end
|
@@ -9,45 +9,39 @@ module Terraspace::Cloud
|
|
9
9
|
super
|
10
10
|
@cani = options[:cani]
|
11
11
|
@kind = options[:kind]
|
12
|
-
@
|
12
|
+
@vcs_vars = options[:vcs_vars]
|
13
13
|
setup_context(options)
|
14
14
|
end
|
15
15
|
|
16
|
-
def stage_attrs
|
17
|
-
status =
|
16
|
+
def stage_attrs(success)
|
17
|
+
status = success_status(success)
|
18
18
|
attrs = {
|
19
19
|
status: status,
|
20
20
|
kind: @kind,
|
21
21
|
terraspace_version: check.terraspace_version,
|
22
22
|
terraform_version: check.terraform_version,
|
23
23
|
}
|
24
|
-
attrs.merge!(
|
24
|
+
attrs.merge!(@vcs_vars)
|
25
25
|
attrs
|
26
26
|
end
|
27
27
|
|
28
|
-
def
|
29
|
-
|
30
|
-
end
|
31
|
-
memoize :check
|
32
|
-
|
33
|
-
def pr_comment(url)
|
34
|
-
ci.comment(url) if ci.respond_to?(:comment)
|
28
|
+
def cloud_upload
|
29
|
+
Upload.new(@options)
|
35
30
|
end
|
31
|
+
memoize :cloud_upload
|
36
32
|
|
37
|
-
def
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
else
|
43
|
-
ci.vars
|
33
|
+
def success_status(success)
|
34
|
+
case success
|
35
|
+
when true then "success"
|
36
|
+
when false then "fail"
|
37
|
+
when nil then "started"
|
44
38
|
end
|
45
39
|
end
|
46
40
|
|
47
|
-
def
|
48
|
-
Terraspace::
|
41
|
+
def check
|
42
|
+
Terraspace::CLI::Setup::Check.new
|
49
43
|
end
|
50
|
-
memoize :
|
44
|
+
memoize :check
|
51
45
|
|
52
46
|
def sh(command, exit_on_fail: true)
|
53
47
|
logger.debug "=> #{command}"
|
@@ -86,12 +80,5 @@ module Terraspace::Cloud
|
|
86
80
|
line.include?(' cancelled')
|
87
81
|
end
|
88
82
|
end
|
89
|
-
|
90
|
-
def terraspace_cloud_info(result)
|
91
|
-
data = result['data']
|
92
|
-
url = data['attributes']['url']
|
93
|
-
logger.info "Terraspace Cloud #{url}"
|
94
|
-
url
|
95
|
-
end
|
96
83
|
end
|
97
84
|
end
|
data/lib/terraspace/cloud/ci.rb
CHANGED
@@ -18,8 +18,6 @@ module Terraspace::Cloud
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def detect
|
21
|
-
return Generic if ENV['TS_CI_REPO']
|
22
|
-
|
23
21
|
detected = meta.find do |data|
|
24
22
|
env_key = data[:env_key] # IE: ENV['GITHUB_ACTIONS']
|
25
23
|
env_value = data[:env_value] # IE: "string" or /pattern/
|
@@ -30,12 +28,7 @@ module Terraspace::Cloud
|
|
30
28
|
ENV[env_key] # only env_key
|
31
29
|
end
|
32
30
|
end
|
33
|
-
|
34
|
-
interface_class(detected)
|
35
|
-
else
|
36
|
-
Manual
|
37
|
-
end
|
38
|
-
klass.new
|
31
|
+
interface_class(detected) if detected
|
39
32
|
end
|
40
33
|
|
41
34
|
# IE: TerraspaceCiGithub::Interface
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Terraspace::Cloud
|
2
|
+
class Comment < Base
|
3
|
+
def get(record, cost)
|
4
|
+
return unless Terraspace.cloud?
|
5
|
+
|
6
|
+
params = {}
|
7
|
+
params[:record_id] = record['data']['id']
|
8
|
+
params[:cost_id] = cost['data']['id'] if cost
|
9
|
+
|
10
|
+
sleep 1 # delay a second since job is queued
|
11
|
+
resp = nil
|
12
|
+
Timeout::timeout(20) do
|
13
|
+
loop do
|
14
|
+
resp = api.get_comment(params)
|
15
|
+
break if resp['data']['attributes']['status'] == 'completed'
|
16
|
+
sleep 2
|
17
|
+
end
|
18
|
+
end
|
19
|
+
resp
|
20
|
+
rescue Timeout::Error
|
21
|
+
# nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def cani?(exit_on_error: true)
|
25
|
+
api.create_cost(cani: 1, exit_on_error: exit_on_error)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
class Terraspace::Cloud::Cost
|
2
|
+
class Infracost
|
3
|
+
extend Memoist
|
4
|
+
include Terraspace::Util::Popen
|
5
|
+
|
6
|
+
def initialize(options={})
|
7
|
+
@cloud_stack_name = options[:cloud_stack_name]
|
8
|
+
end
|
9
|
+
|
10
|
+
def name
|
11
|
+
"infracost"
|
12
|
+
end
|
13
|
+
|
14
|
+
def run(output_dir=".terraspace-cache/.cache2/cost")
|
15
|
+
unless infracost_installed?
|
16
|
+
logger.info "WARN: infracost not installed. Please install infracost first: https://www.infracost.io/docs/".color(:yellow)
|
17
|
+
logger.info "Not running cost estimate"
|
18
|
+
return false
|
19
|
+
end
|
20
|
+
unless api_key?
|
21
|
+
logger.info "WARN: infracost api key not configured. Please set the environment variable INFRACOST_API_KEY".color(:yellow)
|
22
|
+
logger.info "Not running cost estimate"
|
23
|
+
return false
|
24
|
+
end
|
25
|
+
|
26
|
+
previous_cost_available = File.exist?("#{output_dir}/cost_prev.json")
|
27
|
+
commands = [
|
28
|
+
"infracost breakdown --path . --format json --out-file #{output_dir}/cost.json --project-name #{@cloud_stack_name}",
|
29
|
+
]
|
30
|
+
if previous_cost_available
|
31
|
+
commands << "infracost diff --path #{output_dir}/cost.json --compare-to=#{output_dir}/cost_prev.json --format json --out-file #{output_dir}/cost_diff.json"
|
32
|
+
end
|
33
|
+
|
34
|
+
cost_json_file = previous_cost_available ? "cost_diff.json" : "cost.json"
|
35
|
+
commands += [
|
36
|
+
"infracost output --path #{output_dir}/#{cost_json_file} --format html --out-file #{output_dir}/cost.html",
|
37
|
+
"infracost output --path #{output_dir}/#{cost_json_file} --format table --out-file #{output_dir}/cost.text",
|
38
|
+
]
|
39
|
+
|
40
|
+
if comment_format
|
41
|
+
path = previous_cost_available ? "cost_diff.json" : "cost.json"
|
42
|
+
commands << "infracost output --path #{output_dir}/#{path} --format #{comment_format} --out-file #{output_dir}/cost.comment"
|
43
|
+
end
|
44
|
+
|
45
|
+
commands.each do |command|
|
46
|
+
logger.debug "=> #{command}"
|
47
|
+
|
48
|
+
popen(command, filter: "Output saved to ")
|
49
|
+
if command.include?(".text")
|
50
|
+
logger.info IO.read("#{output_dir}/cost.text")
|
51
|
+
logger.info "\n"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def comment_format
|
57
|
+
name = Terraspace::Cloud::Vcs.detect_name # IE: github
|
58
|
+
format = "#{name}-comment"
|
59
|
+
# These are valid infracost comment format. Note: Not all have terraspace_vcs_* plugin support yet
|
60
|
+
valid = %w[github-comment gitlab-comment azure-repos-comment bitbucket-comment]
|
61
|
+
format if valid.include?(format)
|
62
|
+
end
|
63
|
+
|
64
|
+
def version
|
65
|
+
out = `infracost --version`.strip # Infracost v0.10.6
|
66
|
+
md = out.match(/ v(.*)/)
|
67
|
+
md ? md[1] : out
|
68
|
+
end
|
69
|
+
memoize :version
|
70
|
+
|
71
|
+
def infracost_installed?
|
72
|
+
system "type infracost > /dev/null 2>&1"
|
73
|
+
end
|
74
|
+
|
75
|
+
def api_key?
|
76
|
+
return true if ENV['INFRACOST_API_KEY']
|
77
|
+
File.exist?("#{ENV['HOME']}/.config/infracost/credentials.yml")
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module Terraspace::Cloud
|
2
|
+
class Cost < Base
|
3
|
+
# uid of plan or update
|
4
|
+
def create(uid:, stream:)
|
5
|
+
return unless Terraspace.cloud?
|
6
|
+
return unless Terraspace.config.cloud.cost.enabled
|
7
|
+
|
8
|
+
cani?
|
9
|
+
success = build(stream)
|
10
|
+
return unless success # in case infracost not installed
|
11
|
+
|
12
|
+
upload = cloud_upload.create(type: "cost", stream_id: stream['data']['id'])
|
13
|
+
api.create_cost(
|
14
|
+
upload_id: upload['data']['id'],
|
15
|
+
stack_uid: upload['data']['attributes']['stack_id'], # use stack_uid since stack_id is friendly url name
|
16
|
+
uid: uid,
|
17
|
+
cost: cost_attrs,
|
18
|
+
)
|
19
|
+
rescue Terraspace::NetworkError => e
|
20
|
+
logger.warn "WARN: #{e.class} #{e.message}"
|
21
|
+
logger.warn "WARN: Unable to save data to Terraspace cloud"
|
22
|
+
end
|
23
|
+
|
24
|
+
# different from stage_attrs
|
25
|
+
def cost_attrs
|
26
|
+
{
|
27
|
+
provider: provider.name, # IE: infracost
|
28
|
+
provider_version: provider.version,
|
29
|
+
terraspace_version: check.terraspace_version,
|
30
|
+
terraform_version: check.terraform_version,
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
def build(stream)
|
35
|
+
success = nil
|
36
|
+
clean_cache2_stage
|
37
|
+
download_previous_cost(stream)
|
38
|
+
# .terraspace-cache/dev/stacks/demo
|
39
|
+
Dir.chdir(@mod.cache_dir) do
|
40
|
+
logger.info "Running cost estimate..."
|
41
|
+
success = provider.run
|
42
|
+
end
|
43
|
+
success
|
44
|
+
end
|
45
|
+
|
46
|
+
def download_previous_cost(stream)
|
47
|
+
stack_id = stream['data']['attributes']['stack_id'] # stack-SHWx8fW5FbDKg3QK
|
48
|
+
cost = api.get_previous_cost(stack_uid: stack_id)
|
49
|
+
download_url = cost['data']['attributes']['download_url'] # could be nil
|
50
|
+
return unless download_url
|
51
|
+
uri = URI(download_url)
|
52
|
+
json = Net::HTTP.get(uri) # => JSON String
|
53
|
+
dest = "#{@mod.cache_dir}/.terraspace-cache/.cache2/cost/cost_prev.json"
|
54
|
+
FileUtils.mkdir_p(File.dirname(dest))
|
55
|
+
IO.write(dest, json)
|
56
|
+
end
|
57
|
+
|
58
|
+
def provider
|
59
|
+
Terraspace::Cloud::Cost::Infracost.new(cloud_stack_name: cloud_stack_name) # only provider currently supported
|
60
|
+
end
|
61
|
+
memoize :provider
|
62
|
+
|
63
|
+
def cani?(exit_on_error: true)
|
64
|
+
return true unless Terraspace.cloud?
|
65
|
+
api.create_cost(cani: 1, exit_on_error: exit_on_error)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
File without changes
|
@@ -2,29 +2,42 @@ module Terraspace::Cloud
|
|
2
2
|
class Plan < Base
|
3
3
|
include Terraspace::CLI::Concerns::PlanPath
|
4
4
|
|
5
|
-
def
|
6
|
-
return unless
|
5
|
+
def setup
|
6
|
+
return unless Terraspace.cloud?
|
7
|
+
cani?
|
8
|
+
|
9
|
+
return unless @mod.out_option
|
10
|
+
return if @mod.out_option =~ %r{^/} # not need to create parent dir for copy with absolute path
|
7
11
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
name = @mod.out_option.sub("#{Terraspace.root}/",'')
|
13
|
+
dest = "#{@mod.cache_dir}/#{name}"
|
14
|
+
FileUtils.mkdir_p(File.dirname(dest))
|
15
|
+
end
|
16
|
+
|
17
|
+
def create(success, stream)
|
18
|
+
return unless Terraspace.cloud?
|
19
|
+
return unless record?
|
20
|
+
build(success)
|
21
|
+
upload = cloud_upload.create(type: "plan", stream_id: stream['data']['id'])
|
22
|
+
api.create_plan(
|
23
|
+
upload_id: upload['data']['id'],
|
24
|
+
stack_uid: upload['data']['attributes']['stack_id'], # use stack_uid since stack_id is friendly url name
|
25
|
+
plan: stage_attrs(success),
|
15
26
|
)
|
16
|
-
url = terraspace_cloud_info(result)
|
17
|
-
pr_comment(url)
|
18
27
|
rescue Terraspace::NetworkError => e
|
19
28
|
logger.warn "WARN: #{e.class} #{e.message}"
|
20
29
|
logger.warn "WARN: Unable to save data to Terraspace cloud"
|
21
30
|
end
|
22
31
|
|
23
|
-
def
|
24
|
-
|
32
|
+
def create_stream
|
33
|
+
return unless Terraspace.cloud?
|
34
|
+
api.create_stream("plan")
|
35
|
+
rescue Terraspace::NetworkError => e
|
36
|
+
logger.warn "WARN: #{e.class} #{e.message}"
|
37
|
+
logger.warn "WARN: Unable to save data to Terraspace cloud"
|
25
38
|
end
|
26
39
|
|
27
|
-
def build
|
40
|
+
def build(success)
|
28
41
|
clean_cache2_stage
|
29
42
|
# .terraspace-cache/dev/stacks/demo
|
30
43
|
Dir.chdir(@mod.cache_dir) do
|
@@ -32,7 +45,7 @@ module Terraspace::Cloud
|
|
32
45
|
|
33
46
|
IO.write("#{plan_dir}/plan.log", Terraspace::Logger.logs)
|
34
47
|
|
35
|
-
return unless
|
48
|
+
return unless success
|
36
49
|
return if File.empty?(plan_path)
|
37
50
|
|
38
51
|
out_option_root_path = "#{Terraspace.root}/#{plan_path}"
|
@@ -43,5 +56,10 @@ module Terraspace::Cloud
|
|
43
56
|
sh "terraform show -json #{plan_path} > #{json}"
|
44
57
|
end
|
45
58
|
end
|
59
|
+
|
60
|
+
def cani?(exit_on_error: true)
|
61
|
+
return true unless Terraspace.cloud?
|
62
|
+
api.create_plan(cani: 1, exit_on_error: exit_on_error)
|
63
|
+
end
|
46
64
|
end
|
47
65
|
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module Terraspace::Cloud
|
2
|
+
class Stream < Base
|
3
|
+
def open(command)
|
4
|
+
return unless Terraspace.cloud?
|
5
|
+
@command = command # down, up, plan
|
6
|
+
@stream = create_stream
|
7
|
+
logger.info "Live Stream: #{@stream['data']['attributes']['url']}"
|
8
|
+
@stream_thread ||= Thread.new do
|
9
|
+
loop do
|
10
|
+
upload_stream
|
11
|
+
create_cloud_record
|
12
|
+
break if @end_streaming
|
13
|
+
sleep 1
|
14
|
+
end
|
15
|
+
end
|
16
|
+
@stream
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_cloud_record
|
20
|
+
return if @created
|
21
|
+
# Dont create records unless changes are detected or user configure to always create recrods
|
22
|
+
return unless record_all? || yes? || changes?
|
23
|
+
|
24
|
+
case @command
|
25
|
+
when "plan"
|
26
|
+
create_plan
|
27
|
+
when "up", "down"
|
28
|
+
create_update
|
29
|
+
end
|
30
|
+
@created = true
|
31
|
+
end
|
32
|
+
|
33
|
+
def record_all?
|
34
|
+
Terraspace.config.cloud.record == "all"
|
35
|
+
end
|
36
|
+
|
37
|
+
# Notes: https://gist.github.com/tongueroo/12ca3bec3043fce5e9b52b8580da6b6c
|
38
|
+
def changes?
|
39
|
+
buffer = Terraspace::Logger.buffer.map { |s| s.force_encoding('UTF-8') }
|
40
|
+
will_found = buffer.detect { |s| s.include?("Terraform will perform") }
|
41
|
+
saved_found = buffer.detect { |s| s.include?("Saved the plan to:") }
|
42
|
+
!!(will_found && saved_found)
|
43
|
+
end
|
44
|
+
|
45
|
+
def yes?
|
46
|
+
Terraspace::Logger.stdin_capture == 'yes'
|
47
|
+
end
|
48
|
+
|
49
|
+
def create_plan
|
50
|
+
api.create_plan(
|
51
|
+
stream_id: @stream['data']['id'],
|
52
|
+
plan: stage_attrs(nil),
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
def create_update
|
57
|
+
api.create_update(
|
58
|
+
stream_id: @stream['data']['id'],
|
59
|
+
update: stage_attrs(nil),
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
def close(success, exception)
|
64
|
+
return unless @stream
|
65
|
+
@end_streaming = true
|
66
|
+
@stream_thread.join
|
67
|
+
status = success ? "success" : "fail"
|
68
|
+
api.complete_stream(id: @stream['data']['id'], status: status, exception: !!exception)
|
69
|
+
end
|
70
|
+
|
71
|
+
def upload_stream(retries=0)
|
72
|
+
stream_url = @stream['data']['attributes']['upload_url']
|
73
|
+
url = stream_url
|
74
|
+
uri = URI.parse(url)
|
75
|
+
object_content = Terraspace::Logger.logs
|
76
|
+
resp = Net::HTTP.start(uri.host) do |http|
|
77
|
+
http.send_request(
|
78
|
+
'PUT',
|
79
|
+
uri.request_uri,
|
80
|
+
object_content,
|
81
|
+
'content-type' => ''
|
82
|
+
)
|
83
|
+
end
|
84
|
+
|
85
|
+
return if resp.code =~ /^20/
|
86
|
+
|
87
|
+
log_errors(resp.body, :debug)
|
88
|
+
if resp.body.include?('expired')
|
89
|
+
if retries >= 10 # 15m * 10 = 150m = 2.5h TODO: CHANGE
|
90
|
+
log_errors(resp.body, :info)
|
91
|
+
# raise "RetriesExceeded"
|
92
|
+
exit 1
|
93
|
+
end
|
94
|
+
logger.debug("Retrying upload")
|
95
|
+
@stream = create_stream(stream_id: @stream['data']['id'])
|
96
|
+
upload_stream(retries+1)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def create_stream(params={})
|
101
|
+
api.create_stream(params.merge(command: @command))
|
102
|
+
rescue Terraspace::NetworkError => e
|
103
|
+
logger.warn "WARN: Error calling Terraspace cloud"
|
104
|
+
logger.warn "WARN: #{e.class} #{e.message}"
|
105
|
+
end
|
106
|
+
|
107
|
+
def log_errors(body, level=:debug)
|
108
|
+
logger.send(level, "ERROR: Stream uploading logs")
|
109
|
+
logger.send(level, "resp body #{body}")
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
@@ -1,37 +1,37 @@
|
|
1
1
|
module Terraspace::Cloud
|
2
2
|
class Update < Base
|
3
|
-
def
|
3
|
+
def create(success, stream)
|
4
|
+
return unless Terraspace.cloud?
|
4
5
|
return unless record?
|
5
6
|
|
6
|
-
build
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
)
|
14
|
-
url = terraspace_cloud_info(result)
|
15
|
-
pr_comment(url)
|
7
|
+
build(success)
|
8
|
+
upload = cloud_upload.create(type: "update", stream_id: stream['data']['id'])
|
9
|
+
params = {
|
10
|
+
upload_id: upload['data']['id'],
|
11
|
+
stack_uid: upload['data']['attributes']['stack_id'], # use stack_uid since stack_id is friendly url name
|
12
|
+
update: stage_attrs(success),
|
13
|
+
}
|
14
|
+
api.create_update(params)
|
16
15
|
end
|
17
16
|
|
18
|
-
def
|
19
|
-
api.create_update(cani: 1)
|
20
|
-
end
|
21
|
-
|
22
|
-
def build
|
17
|
+
def build(success)
|
23
18
|
clean_cache2_stage
|
24
19
|
# .terraspace-cache/dev/stacks/demo
|
25
20
|
Dir.chdir(@mod.cache_dir) do
|
26
|
-
cache2_path = ".terraspace-cache/.cache2
|
21
|
+
cache2_path = ".terraspace-cache/.cache2/update"
|
27
22
|
FileUtils.mkdir_p(cache2_path)
|
28
23
|
|
29
|
-
IO.write("#{cache2_path}
|
30
|
-
return unless
|
24
|
+
IO.write("#{cache2_path}/update.log", Terraspace::Logger.logs)
|
25
|
+
return unless success
|
31
26
|
|
32
27
|
sh "terraform state pull > #{cache2_path}/state.json"
|
33
28
|
sh "terraform output -json > #{cache2_path}/output.json"
|
34
29
|
end
|
35
30
|
end
|
31
|
+
|
32
|
+
def cani?(exit_on_error: true)
|
33
|
+
return true unless Terraspace.cloud?
|
34
|
+
api.create_update(cani: 1, exit_on_error: exit_on_error)
|
35
|
+
end
|
36
36
|
end
|
37
37
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Terraspace::Cloud
|
2
|
+
class Upload < Base
|
3
|
+
def create(data) # data: {stream_id:, type: }
|
4
|
+
zip_path = Package.new(@options.merge(data)).build
|
5
|
+
upload = create_upload(data)
|
6
|
+
upload_project(upload, zip_path)
|
7
|
+
upload
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
def create_upload(params)
|
12
|
+
upload = api.create_upload(params)
|
13
|
+
if errors?(upload)
|
14
|
+
error_message(upload)
|
15
|
+
exit 1 # Consider: raise exception can rescue higher up
|
16
|
+
else
|
17
|
+
upload
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def upload_project(upload, path, retries=0)
|
22
|
+
url = upload['data']['attributes']['url']
|
23
|
+
uri = URI.parse(url)
|
24
|
+
object_content = IO.read(path)
|
25
|
+
sleep 2
|
26
|
+
resp = Net::HTTP.start(uri.host) do |http|
|
27
|
+
http.send_request(
|
28
|
+
'PUT',
|
29
|
+
uri.request_uri,
|
30
|
+
object_content,
|
31
|
+
'content-type' => ''
|
32
|
+
)
|
33
|
+
end
|
34
|
+
return if resp.code =~ /^20/
|
35
|
+
|
36
|
+
log_errors(resp.body, :debug)
|
37
|
+
if resp.body.include?('expired')
|
38
|
+
if retries >= 10 # 15m * 10 = 150m = 2.5h
|
39
|
+
log_errors(resp.body, :info)
|
40
|
+
exit 1
|
41
|
+
end
|
42
|
+
logger.debug("Retrying upload")
|
43
|
+
upload = create_upload(upload_id: upload['data']['id'])
|
44
|
+
upload_project(upload, path, retries+1)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def log_errors(body, level=:debug)
|
49
|
+
logger.send(level, "ERROR: Uploader uploading code")
|
50
|
+
logger.send(level, "resp body #{body}")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|