terraspace 2.0.2 → 2.1.1
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/.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/.github/FUNDING.yml +1 -0
- data/.pipedream/README.md +1 -1
- data/.pipedream/pipeline.rb +15 -8
- data/CHANGELOG.md +16 -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/concerns/plan_path.rb +1 -1
- data/lib/terraspace/cli/down.rb +43 -6
- data/lib/terraspace/cli/logs/concern.rb +1 -1
- 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 +16 -29
- 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 +2 -2
- data/lib/terraspace/cloud/{folder → upload}/package.rb +2 -2
- 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/compiler/strategy/tfvar/layer.rb +1 -1
- data/lib/terraspace/logger.rb +3 -2
- 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 +26 -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}"
|
@@ -62,7 +56,7 @@ module Terraspace::Cloud
|
|
62
56
|
# terraform plan can be a kind of apply or destroy
|
63
57
|
# terraform apply can be a kind of apply or destroy
|
64
58
|
kind = self.class.name.to_s.split('::').last.underscore # IE: apply or destroy
|
65
|
-
dir = "#{@mod.cache_dir}/.terraspace-cache
|
59
|
+
dir = "#{@mod.cache_dir}/.terraspace-cache/.cache2/#{kind}"
|
66
60
|
FileUtils.rm_rf(dir)
|
67
61
|
FileUtils.mkdir_p(dir)
|
68
62
|
end
|
@@ -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/
|
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
|
@@ -1,4 +1,4 @@
|
|
1
|
-
class Terraspace::Cloud::
|
1
|
+
class Terraspace::Cloud::Upload
|
2
2
|
class Base < Terraspace::Cloud::Base
|
3
3
|
def initialize(options={})
|
4
4
|
super
|
@@ -11,7 +11,7 @@ class Terraspace::Cloud::Folder
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def artifacts_path
|
14
|
-
"#{@mod.cache_dir}/.terraspace-cache/
|
14
|
+
"#{@mod.cache_dir}/.terraspace-cache/.cache2/artifacts/#{@type}"
|
15
15
|
end
|
16
16
|
end
|
17
17
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'zip_folder'
|
2
2
|
|
3
|
-
class Terraspace::Cloud::
|
3
|
+
class Terraspace::Cloud::Upload
|
4
4
|
class Package < Base
|
5
5
|
def build
|
6
6
|
copy
|
@@ -12,7 +12,7 @@ class Terraspace::Cloud::Folder
|
|
12
12
|
FileUtils.rm_rf(artifacts_path)
|
13
13
|
FileUtils.mkdir_p(File.dirname(artifacts_path))
|
14
14
|
|
15
|
-
expr = "#{@mod.cache_dir}/.terraspace-cache
|
15
|
+
expr = "#{@mod.cache_dir}/.terraspace-cache/.cache2/#{@type}/*"
|
16
16
|
Dir.glob(expr).each do |src|
|
17
17
|
dest = "#{artifacts_path}/#{File.basename(src)}"
|
18
18
|
FileUtils.mkdir_p(File.dirname(dest))
|