terraspace 2.0.3 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) 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/.pipedream/README.md +1 -1
  12. data/.pipedream/pipeline.rb +15 -8
  13. data/CHANGELOG.md +8 -0
  14. data/lib/templates/base/project/Gemfile.tt +5 -0
  15. data/lib/templates/base/project/config/app.rb +6 -2
  16. data/lib/templates/plugin/ci/CHANGELOG.md.tt +1 -1
  17. data/lib/templates/plugin/ci/lib/%gem_name%/vars.rb.tt +1 -1
  18. data/lib/terraspace/app.rb +16 -0
  19. data/lib/terraspace/cli/base.rb +1 -0
  20. data/lib/terraspace/cli/commander.rb +2 -1
  21. data/lib/terraspace/cli/down.rb +42 -6
  22. data/lib/terraspace/cli/new/plugin/ci.rb +1 -4
  23. data/lib/terraspace/cli/plan.rb +66 -2
  24. data/lib/terraspace/cli/up.rb +65 -11
  25. data/lib/terraspace/cloud/api/cani.rb +15 -9
  26. data/lib/terraspace/cloud/api/concern.rb +0 -1
  27. data/lib/terraspace/cloud/api/http_methods.rb +7 -2
  28. data/lib/terraspace/cloud/api.rb +29 -5
  29. data/lib/terraspace/cloud/base.rb +15 -28
  30. data/lib/terraspace/cloud/ci.rb +1 -8
  31. data/lib/terraspace/cloud/comment.rb +28 -0
  32. data/lib/terraspace/cloud/context.rb +1 -0
  33. data/lib/terraspace/cloud/cost/infracost.rb +80 -0
  34. data/lib/terraspace/cloud/cost.rb +68 -0
  35. data/lib/terraspace/cloud/git.rb +0 -0
  36. data/lib/terraspace/cloud/plan.rb +33 -15
  37. data/lib/terraspace/cloud/stream.rb +113 -0
  38. data/lib/terraspace/cloud/streamer.rb +9 -0
  39. data/lib/terraspace/cloud/update.rb +19 -19
  40. data/lib/terraspace/cloud/{folder → upload}/base.rb +1 -1
  41. data/lib/terraspace/cloud/{folder → upload}/package.rb +1 -1
  42. data/lib/terraspace/cloud/{folder → upload}/tidy.rb +1 -1
  43. data/lib/terraspace/cloud/upload.rb +53 -0
  44. data/lib/terraspace/cloud/vcs/base.rb +6 -0
  45. data/lib/terraspace/cloud/vcs/ci_env.rb +15 -0
  46. data/lib/terraspace/cloud/vcs/commenter.rb +75 -0
  47. data/lib/terraspace/cloud/vcs/interface.rb +14 -0
  48. data/lib/terraspace/cloud/vcs/local_env.rb +25 -0
  49. data/lib/terraspace/cloud/{ci/vcs → vcs/local_git}/base.rb +6 -3
  50. data/lib/terraspace/cloud/vcs/local_git/bitbucket.rb +17 -0
  51. data/lib/terraspace/cloud/vcs/local_git/github.rb +17 -0
  52. data/lib/terraspace/cloud/vcs/local_git/gitlab.rb +17 -0
  53. data/lib/terraspace/cloud/{ci/manual.rb → vcs/local_git.rb} +18 -10
  54. data/lib/terraspace/cloud/vcs.rb +21 -0
  55. data/lib/terraspace/logger.rb +1 -1
  56. data/lib/terraspace/shell/error.rb +1 -1
  57. data/lib/terraspace/terraform/runner.rb +4 -22
  58. data/lib/terraspace/util/popen.rb +67 -0
  59. data/lib/terraspace/version.rb +1 -1
  60. metadata +25 -23
  61. data/lib/templates/plugin/ci/lib/%gem_name%/pr.rb.tt +0 -15
  62. data/lib/terraspace/cloud/api/concern/record.rb +0 -18
  63. data/lib/terraspace/cloud/ci/generic.rb +0 -25
  64. data/lib/terraspace/cloud/ci/vcs/bitbucket.rb +0 -11
  65. data/lib/terraspace/cloud/ci/vcs/github.rb +0 -11
  66. data/lib/terraspace/cloud/ci/vcs/gitlab.rb +0 -11
  67. data/lib/terraspace/cloud/ci/vcs.rb +0 -18
  68. data/lib/terraspace/cloud/folder/uploader.rb +0 -37
  69. data/lib/terraspace/cloud/folder.rb +0 -11
  70. data/lib/terraspace/terraform/ihooks/after/apply.rb +0 -8
  71. data/lib/terraspace/terraform/ihooks/after/destroy.rb +0 -8
  72. data/lib/terraspace/terraform/ihooks/after/plan.rb +0 -46
  73. data/lib/terraspace/terraform/ihooks/base.rb +0 -17
  74. data/lib/terraspace/terraform/ihooks/before/apply.rb +0 -8
  75. data/lib/terraspace/terraform/ihooks/before/destroy.rb +0 -8
  76. data/lib/terraspace/terraform/ihooks/before/plan.rb +0 -20
@@ -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}"
@@ -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
@@ -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
@@ -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|
@@ -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
@@ -0,0 +1,6 @@
1
+ class Terraspace::Cloud::Vcs
2
+ class Base
3
+ extend Memoist
4
+ include Terraspace::Util::Logging
5
+ end
6
+ end
@@ -0,0 +1,15 @@
1
+ class Terraspace::Cloud::Vcs
2
+ class CiEnv
3
+ extend Memoist
4
+ include Terraspace::Util::Logging
5
+
6
+ def vars
7
+ klass = Terraspace::Cloud::Ci.detect
8
+ if klass
9
+ klass.new.vars.compact # remove items with nil values
10
+ else
11
+ {}
12
+ end
13
+ end
14
+ end
15
+ end