terraspace 2.0.1 → 2.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.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/.cody/README.md +1 -1
  3. data/.cody/all/project.rb +4 -0
  4. data/.cody/aws/project.rb +4 -0
  5. data/.cody/azurerm/project.rb +4 -0
  6. data/.cody/google/project.rb +4 -0
  7. data/.cody/none/project.rb +4 -0
  8. data/.cody/shared/script/install/infracost.sh +6 -0
  9. data/.cody/shared/script/install/terraform.sh +2 -2
  10. data/.cody/shared/script/install.sh +1 -0
  11. data/.github/FUNDING.yml +1 -0
  12. data/.pipedream/README.md +1 -1
  13. data/.pipedream/pipeline.rb +15 -8
  14. data/CHANGELOG.md +16 -0
  15. data/lib/templates/base/project/Gemfile.tt +5 -0
  16. data/lib/templates/base/project/config/app.rb +6 -2
  17. data/lib/templates/plugin/ci/CHANGELOG.md.tt +1 -1
  18. data/lib/templates/plugin/ci/lib/%gem_name%/vars.rb.tt +1 -1
  19. data/lib/terraspace/app.rb +16 -0
  20. data/lib/terraspace/cli/base.rb +1 -0
  21. data/lib/terraspace/cli/commander.rb +2 -1
  22. data/lib/terraspace/cli/concerns/plan_path.rb +1 -1
  23. data/lib/terraspace/cli/down.rb +42 -6
  24. data/lib/terraspace/cli/logs/concern.rb +1 -1
  25. data/lib/terraspace/cli/new/plugin/ci.rb +1 -4
  26. data/lib/terraspace/cli/plan.rb +66 -2
  27. data/lib/terraspace/cli/up.rb +65 -11
  28. data/lib/terraspace/cloud/api/cani.rb +15 -9
  29. data/lib/terraspace/cloud/api/concern.rb +0 -1
  30. data/lib/terraspace/cloud/api/http_methods.rb +7 -2
  31. data/lib/terraspace/cloud/api.rb +29 -5
  32. data/lib/terraspace/cloud/base.rb +16 -29
  33. data/lib/terraspace/cloud/ci.rb +1 -8
  34. data/lib/terraspace/cloud/comment.rb +28 -0
  35. data/lib/terraspace/cloud/context.rb +1 -0
  36. data/lib/terraspace/cloud/cost/infracost.rb +80 -0
  37. data/lib/terraspace/cloud/cost.rb +68 -0
  38. data/lib/terraspace/cloud/git.rb +0 -0
  39. data/lib/terraspace/cloud/plan.rb +33 -15
  40. data/lib/terraspace/cloud/stream.rb +113 -0
  41. data/lib/terraspace/cloud/streamer.rb +9 -0
  42. data/lib/terraspace/cloud/update.rb +19 -19
  43. data/lib/terraspace/cloud/{folder → upload}/base.rb +2 -2
  44. data/lib/terraspace/cloud/{folder → upload}/package.rb +2 -2
  45. data/lib/terraspace/cloud/{folder → upload}/tidy.rb +1 -1
  46. data/lib/terraspace/cloud/upload.rb +53 -0
  47. data/lib/terraspace/cloud/vcs/base.rb +6 -0
  48. data/lib/terraspace/cloud/vcs/ci_env.rb +15 -0
  49. data/lib/terraspace/cloud/vcs/commenter.rb +75 -0
  50. data/lib/terraspace/cloud/vcs/interface.rb +14 -0
  51. data/lib/terraspace/cloud/vcs/local_env.rb +25 -0
  52. data/lib/terraspace/cloud/{ci/vcs → vcs/local_git}/base.rb +6 -3
  53. data/lib/terraspace/cloud/vcs/local_git/bitbucket.rb +17 -0
  54. data/lib/terraspace/cloud/vcs/local_git/github.rb +17 -0
  55. data/lib/terraspace/cloud/vcs/local_git/gitlab.rb +17 -0
  56. data/lib/terraspace/cloud/{ci/manual.rb → vcs/local_git.rb} +18 -10
  57. data/lib/terraspace/cloud/vcs.rb +21 -0
  58. data/lib/terraspace/compiler/strategy/tfvar/layer.rb +1 -1
  59. data/lib/terraspace/core.rb +8 -0
  60. data/lib/terraspace/logger.rb +3 -2
  61. data/lib/terraspace/plugin/expander/interface.rb +5 -8
  62. data/lib/terraspace/shell/error.rb +1 -1
  63. data/lib/terraspace/terraform/runner.rb +4 -22
  64. data/lib/terraspace/util/popen.rb +67 -0
  65. data/lib/terraspace/version.rb +1 -1
  66. metadata +26 -23
  67. data/lib/templates/plugin/ci/lib/%gem_name%/pr.rb.tt +0 -15
  68. data/lib/terraspace/cloud/api/concern/record.rb +0 -18
  69. data/lib/terraspace/cloud/ci/generic.rb +0 -25
  70. data/lib/terraspace/cloud/ci/vcs/bitbucket.rb +0 -11
  71. data/lib/terraspace/cloud/ci/vcs/github.rb +0 -11
  72. data/lib/terraspace/cloud/ci/vcs/gitlab.rb +0 -11
  73. data/lib/terraspace/cloud/ci/vcs.rb +0 -18
  74. data/lib/terraspace/cloud/folder/uploader.rb +0 -37
  75. data/lib/terraspace/cloud/folder.rb +0 -11
  76. data/lib/terraspace/terraform/ihooks/after/apply.rb +0 -8
  77. data/lib/terraspace/terraform/ihooks/after/destroy.rb +0 -8
  78. data/lib/terraspace/terraform/ihooks/after/plan.rb +0 -46
  79. data/lib/terraspace/terraform/ihooks/base.rb +0 -17
  80. data/lib/terraspace/terraform/ihooks/before/apply.rb +0 -8
  81. data/lib/terraspace/terraform/ihooks/before/destroy.rb +0 -8
  82. data/lib/terraspace/terraform/ihooks/before/plan.rb +0 -20
@@ -16,18 +16,42 @@ module Terraspace::Cloud
16
16
  "orgs/#{@org}/projects/#{@project}/stacks/#{@stack}"
17
17
  end
18
18
 
19
- def create_upload
20
- post("#{stack_path}/uploads", @options)
19
+ # data: {stream_id:}
20
+ def create_upload(data)
21
+ post("#{stack_path}/uploads", @options.merge(data))
21
22
  end
22
23
 
23
- # record_attrs: {upload_id: "upload-nRPSpyWd65Ps6978", kind: "apply", stack_id: '...'}
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", data.merge(@options))
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", data.merge(@options))
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
- @success = options[:success]
12
+ @vcs_vars = options[:vcs_vars]
13
13
  setup_context(options)
14
14
  end
15
15
 
16
- def stage_attrs
17
- status = @success ? "success" : "fail"
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!(ci_vars) if ci_vars
24
+ attrs.merge!(@vcs_vars)
25
25
  attrs
26
26
  end
27
27
 
28
- def check
29
- Terraspace::CLI::Setup::Check.new
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 ci_vars
38
- return unless ci
39
- if ci.vars[:host]
40
- vcs = Ci::Vcs.new(ci.vars)
41
- vcs.merged_vars
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 ci
48
- Terraspace::Cloud::Ci.detect
41
+ def check
42
+ Terraspace::CLI::Setup::Check.new
49
43
  end
50
- memoize :ci
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/_cache2/#{kind}"
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
@@ -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
- klass = if detected
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
@@ -3,6 +3,7 @@ module Terraspace::Cloud
3
3
  include Terraspace::Cloud::Api::Validate
4
4
 
5
5
  def setup_context(options)
6
+ return unless Terraspace.cloud? # else validate of @org errors
6
7
  cloud = Terraspace.config.cloud
7
8
  @org = cloud.org
8
9
  @project = cloud.project
@@ -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 run
6
- return unless record?
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
- build
9
- folder = Folder.new(@options.merge(type: "plan"))
10
- upload = folder.upload_data # returns upload record
11
- result = api.create_plan(
12
- upload_id: upload['id'],
13
- stack_uid: upload['stack_id'], # use stack_uid since stack_id is friendly url name
14
- plan: stage_attrs,
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 cani?
24
- api.create_plan(cani: 1)
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 @success
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
@@ -0,0 +1,9 @@
1
+ module Terraspace::Cloud
2
+ module Streamer
3
+ extend Memoist
4
+ def cloud_stream
5
+ Terraspace::Cloud::Stream.new(@options.merge(stack: @mod.name, kind: kind, vcs_vars: vcs_vars))
6
+ end
7
+ memoize :cloud_stream
8
+ end
9
+ end
@@ -1,37 +1,37 @@
1
1
  module Terraspace::Cloud
2
2
  class Update < Base
3
- def run
3
+ def create(success, stream)
4
+ return unless Terraspace.cloud?
4
5
  return unless record?
5
6
 
6
- build
7
- folder = Folder.new(@options.merge(type: @kind))
8
- upload = folder.upload_data # returns upload record
9
- result = api.create_update(
10
- upload_id: upload['id'],
11
- stack_uid: upload['stack_id'], # use stack_uid since stack_id is friendly url name
12
- update: stage_attrs,
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 cani?
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/#{@kind}"
21
+ cache2_path = ".terraspace-cache/.cache2/update"
27
22
  FileUtils.mkdir_p(cache2_path)
28
23
 
29
- IO.write("#{cache2_path}/#{@kind}.log", Terraspace::Logger.logs)
30
- return unless @success
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::Folder
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/_cache2/artifacts/#{@type}"
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::Folder
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/_cache2/#{@type}/*"
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))
@@ -1,4 +1,4 @@
1
- class Terraspace::Cloud::Folder
1
+ class Terraspace::Cloud::Upload
2
2
  class Tidy < Base
3
3
  def cleanup
4
4
  removals.each do |removal|