terraspace 2.0.3 → 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.
- 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 +8 -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 +42 -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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 98afb84e95b715872759e1e8c1ded1304cf7fd53688ed990ba2875079933a4e7
|
4
|
+
data.tar.gz: 94716c0e911013aa51d433164ead45535b424a7e7b3847681f9b5ff11aeb2f38
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fd23a9a876b03a71f0ec1689db46f342c166e4ad817d9bc671a3fadc8be493f1f5c31a199133876c6574884271672773d2efcdcc779c5fdb2187edc8ce56e366
|
7
|
+
data.tar.gz: e6e0ba42d6e2091da24925ca7dabd88bd67ef704b606b3e1642cecb3f6b73d3db76cef2edbd04e6da71d693d550c9f3f12e14244bdb55ddf361eacb53efb29ab
|
data/.cody/README.md
CHANGED
data/.cody/all/project.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
github("boltops-tools/terraspace")
|
2
2
|
image("aws/codebuild/amazonlinux2-x86_64-standard:3.0")
|
3
3
|
env_vars(
|
4
|
+
INFRACOST_API_KEY: "ssm:/#{Cody.env}/INFRACOST_API_KEY",
|
5
|
+
TS_API: "ssm:/#{Cody.env}/TS_API",
|
6
|
+
TS_COST: true,
|
7
|
+
TS_LOG_LEVEL: "info",
|
4
8
|
TS_ORG: "qa",
|
5
9
|
TS_TOKEN: "ssm:/#{Cody.env}/TS_TOKEN",
|
6
10
|
)
|
data/.cody/aws/project.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
github("boltops-tools/terraspace")
|
2
2
|
image("aws/codebuild/amazonlinux2-x86_64-standard:3.0")
|
3
3
|
env_vars(
|
4
|
+
INFRACOST_API_KEY: "ssm:/#{Cody.env}/INFRACOST_API_KEY",
|
5
|
+
TS_API: "ssm:/#{Cody.env}/TS_API",
|
6
|
+
TS_COST: true, # not ssm so can see in the codebuild logs
|
7
|
+
TS_LOG_LEVEL: "info", # not ssm so can see in the codebuild logs
|
4
8
|
TS_ORG: "qa",
|
5
9
|
TS_TOKEN: "ssm:/#{Cody.env}/TS_TOKEN",
|
6
10
|
)
|
data/.cody/azurerm/project.rb
CHANGED
@@ -3,6 +3,10 @@ image("aws/codebuild/amazonlinux2-x86_64-standard:3.0")
|
|
3
3
|
env_vars(
|
4
4
|
# Used by .cody/azurerm/bin/az/configure.sh
|
5
5
|
AZURE_APP_CLIENT_JSON: ssm("/terraspace/#{Cody.env}/azure_app_client_json"),
|
6
|
+
INFRACOST_API_KEY: "ssm:/#{Cody.env}/INFRACOST_API_KEY",
|
7
|
+
TS_API: "ssm:/#{Cody.env}/TS_API",
|
8
|
+
TS_COST: true,
|
9
|
+
TS_LOG_LEVEL: "info",
|
6
10
|
TS_ORG: "qa",
|
7
11
|
TS_TOKEN: "ssm:/#{Cody.env}/TS_TOKEN",
|
8
12
|
)
|
data/.cody/google/project.rb
CHANGED
@@ -3,6 +3,10 @@ image("aws/codebuild/amazonlinux2-x86_64-standard:3.0")
|
|
3
3
|
env_vars(
|
4
4
|
# Used by .cody/google/bin/gcloud/configure.sh
|
5
5
|
GOOGLE_CREDS_JSON: ssm("/terraspace/#{Cody.env}/google_creds_json"),
|
6
|
+
INFRACOST_API_KEY: "ssm:/#{Cody.env}/INFRACOST_API_KEY",
|
7
|
+
TS_API: "ssm:/#{Cody.env}/TS_API",
|
8
|
+
TS_COST: true,
|
9
|
+
TS_LOG_LEVEL: "info",
|
6
10
|
TS_ORG: "qa",
|
7
11
|
TS_TOKEN: "ssm:/#{Cody.env}/TS_TOKEN",
|
8
12
|
)
|
data/.cody/none/project.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
github("boltops-tools/terraspace")
|
2
2
|
image("aws/codebuild/amazonlinux2-x86_64-standard:3.0")
|
3
3
|
env_vars(
|
4
|
+
INFRACOST_API_KEY: "ssm:/#{Cody.env}/INFRACOST_API_KEY",
|
5
|
+
TS_API: "ssm:/#{Cody.env}/TS_API",
|
6
|
+
TS_LOG_LEVEL: "info",
|
7
|
+
TS_COST: true,
|
4
8
|
TS_ORG: "qa",
|
5
9
|
TS_TOKEN: "ssm:/#{Cody.env}/TS_TOKEN",
|
6
10
|
)
|
data/.pipedream/README.md
CHANGED
data/.pipedream/pipeline.rb
CHANGED
@@ -7,13 +7,20 @@ stage "Source" do
|
|
7
7
|
end
|
8
8
|
|
9
9
|
stage "Build" do
|
10
|
-
|
11
|
-
|
12
|
-
"
|
13
|
-
"
|
14
|
-
|
15
|
-
"
|
16
|
-
"
|
17
|
-
"terraspace-unit",
|
10
|
+
vars = env_vars(
|
11
|
+
INFRACOST_API_KEY: "ssm:/#{Pipedream.env}/INFRACOST_API_KEY",
|
12
|
+
TS_API: "ssm:/#{Pipedream.env}/TS_API",
|
13
|
+
TS_LOG_LEVEL: "info", # not ssm so can see in the codebuild logs
|
14
|
+
TS_COST: true,
|
15
|
+
TS_ORG: "qa",
|
16
|
+
TS_TOKEN: "ssm:/#{Pipedream.env}/TS_TOKEN",
|
18
17
|
)
|
18
|
+
in_parallel do
|
19
|
+
codebuild(Name: "terraspace-all", EnvironmentVariables: vars)
|
20
|
+
codebuild(Name: "terraspace-aws", EnvironmentVariables: vars)
|
21
|
+
codebuild(Name: "terraspace-azurerm", EnvironmentVariables: vars)
|
22
|
+
codebuild(Name: "terraspace-google", EnvironmentVariables: vars)
|
23
|
+
codebuild(Name: "terraspace-none", EnvironmentVariables: vars)
|
24
|
+
codebuild(Name: "terraspace-unit") # does not have EnvironmentVariables
|
25
|
+
end
|
19
26
|
end
|
data/CHANGELOG.md
CHANGED
@@ -3,6 +3,14 @@
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
4
4
|
This project *loosely tries* to adhere to [Semantic Versioning](http://semver.org/), even before v1.0.
|
5
5
|
|
6
|
+
## [2.1.0] - 2022-07-11
|
7
|
+
- [#247](https://github.com/boltops-tools/terraspace/pull/247) cost estimates and real-time stream logging
|
8
|
+
- Cloud Cost Estimation support
|
9
|
+
- Live Stream Logging support
|
10
|
+
- CI/CD plugin updates. Decoupled CI from PR plugins
|
11
|
+
- Improve git info. Get git info with or without CI
|
12
|
+
- Fix utf8 encoding edge cases
|
13
|
+
|
6
14
|
## [2.0.3] - 2022-07-04
|
7
15
|
- [#237](https://github.com/boltops-tools/terraspace/pull/237) remove duplicate paths in layering
|
8
16
|
- [#244](https://github.com/boltops-tools/terraspace/pull/244) rename _cache2 to .cache2
|
@@ -2,6 +2,10 @@
|
|
2
2
|
Terraspace.configure do |config|
|
3
3
|
config.logger.level = :info
|
4
4
|
|
5
|
-
#
|
6
|
-
# config.cloud.
|
5
|
+
# To enable Terraspace Cloud set config.cloud.org
|
6
|
+
# config.cloud.org = "ORG" # required: replace with your org. only letters, numbers, underscore and dashes allowed
|
7
|
+
# config.cloud.project = "main" # optional. main is the default project name. only letters, numbers, underscore and dashes allowed
|
8
|
+
|
9
|
+
# Uncomment to enable Cost Estimation. See: http://terraspace.cloud/docs/cloud/cost-estimation/
|
10
|
+
# config.cloud.cost.enabled = true
|
7
11
|
end
|
data/lib/terraspace/app.rb
CHANGED
@@ -51,6 +51,10 @@ module Terraspace
|
|
51
51
|
config.cloud.org = ENV['TS_ORG'] # required for Terraspace cloud
|
52
52
|
config.cloud.record = "changes" # IE: changes or all
|
53
53
|
config.cloud.stack = ":APP-:ROLE-:MOD_NAME-:ENV-:EXTRA-:REGION"
|
54
|
+
config.cloud.cost = ActiveSupport::OrderedOptions.new
|
55
|
+
config.cloud.cost.enabled = cast_value(ENV['TS_COST'])
|
56
|
+
config.cloud.vcs = ActiveSupport::OrderedOptions.new
|
57
|
+
config.cloud.vcs.name = nil # github, gitlab, bitbucket. Else default to registered terraspace_vcs_* plugin
|
54
58
|
|
55
59
|
config.hooks = ActiveSupport::OrderedOptions.new
|
56
60
|
config.hooks.show = true
|
@@ -95,6 +99,18 @@ module Terraspace
|
|
95
99
|
config
|
96
100
|
end
|
97
101
|
|
102
|
+
# https://stackoverflow.com/questions/36228873/ruby-how-to-convert-a-string-to-boolean
|
103
|
+
# https://github.com/rails/rails/blob/5-1-stable/activemodel/lib/active_model/type/boolean.rb
|
104
|
+
# so dont have to add activemodel as a dependency just for this method
|
105
|
+
FALSE_VALUES = [false, 0, "0", "f", "F", "false", "FALSE", "off", "OFF"].to_set
|
106
|
+
def cast_value(value)
|
107
|
+
if value == ""
|
108
|
+
nil
|
109
|
+
else
|
110
|
+
!FALSE_VALUES.include?(value)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
98
114
|
def ts_logger
|
99
115
|
Logger.new(ENV['TS_LOG_PATH'] || $stderr)
|
100
116
|
end
|
data/lib/terraspace/cli/base.rb
CHANGED
@@ -8,7 +8,8 @@ class Terraspace::CLI
|
|
8
8
|
def run
|
9
9
|
Terraspace::Builder.new(@options).run unless @options[:build] # Up already ran build
|
10
10
|
Init.new(@options).run
|
11
|
-
Terraspace::Terraform::Runner.new(@name, @options)
|
11
|
+
@runner = Terraspace::Terraform::Runner.new(@name, @options)
|
12
|
+
@runner.run
|
12
13
|
end
|
13
14
|
end
|
14
15
|
end
|
data/lib/terraspace/cli/down.rb
CHANGED
@@ -1,11 +1,34 @@
|
|
1
1
|
class Terraspace::CLI
|
2
2
|
class Down < Base
|
3
|
-
include TfcConcern
|
4
3
|
include Concerns::PlanPath
|
4
|
+
include Terraspace::Cloud::Streamer
|
5
|
+
include Terraspace::Cloud::Vcs::Commenter
|
6
|
+
include TfcConcern
|
5
7
|
|
6
8
|
def run
|
7
|
-
|
8
|
-
|
9
|
+
cloud_update.cani?
|
10
|
+
@stream = cloud_stream.open("down")
|
11
|
+
success = perform
|
12
|
+
cloud_stream.close(success, @exception)
|
13
|
+
exit 1 unless success
|
14
|
+
end
|
15
|
+
|
16
|
+
def perform
|
17
|
+
success = nil
|
18
|
+
if @options[:yes] && !tfc?
|
19
|
+
success = plan
|
20
|
+
else
|
21
|
+
skip_plan = true
|
22
|
+
end
|
23
|
+
if success or skip_plan
|
24
|
+
success = destroy
|
25
|
+
end
|
26
|
+
success
|
27
|
+
rescue Exception => e
|
28
|
+
@exception = true
|
29
|
+
logger.info "Exception #{e.class}: #{e.message}".color(:red)
|
30
|
+
logger.info e.backtrace.join("\n")
|
31
|
+
false
|
9
32
|
end
|
10
33
|
|
11
34
|
private
|
@@ -13,12 +36,25 @@ class Terraspace::CLI
|
|
13
36
|
if Terraspace.cloud? && !@options[:out]
|
14
37
|
@options[:out] = plan_path
|
15
38
|
end
|
16
|
-
|
39
|
+
plan = Plan.new(@options.merge(destroy: true))
|
40
|
+
plan.plan_only # returns success: true or false
|
17
41
|
end
|
18
42
|
|
19
43
|
def destroy
|
20
|
-
Commander.new("destroy", @options.merge(command: "down"))
|
21
|
-
|
44
|
+
commander = Commander.new("destroy", @options.merge(command: "down"))
|
45
|
+
success = commander.run
|
46
|
+
update = cloud_update.create(success, @stream)
|
47
|
+
|
48
|
+
if success && @options[:destroy_workspace]
|
49
|
+
Terraspace::Terraform::Tfc::Workspace.new(@options).destroy
|
50
|
+
end
|
51
|
+
|
52
|
+
logger.info "Terraspace Cloud #{update['data']['attributes']['url']}" if update
|
53
|
+
end
|
54
|
+
|
55
|
+
def cloud_update
|
56
|
+
Terraspace::Cloud::Update.new(@options.merge(stack: @mod.name, kind: kind, vcs_vars: vcs_vars))
|
22
57
|
end
|
58
|
+
memoize :cloud_update
|
23
59
|
end
|
24
60
|
end
|
@@ -7,7 +7,6 @@ class Terraspace::CLI::New::Plugin
|
|
7
7
|
def self.options
|
8
8
|
[
|
9
9
|
[:force, aliases: %w[y], type: :boolean, desc: "Bypass overwrite are you sure prompt for existing files"],
|
10
|
-
[:pr, type: :boolean, desc: "Generate pr code also. Most CI systems don't have PR support"],
|
11
10
|
]
|
12
11
|
end
|
13
12
|
options.each { |args| class_option(*args) }
|
@@ -15,8 +14,7 @@ class Terraspace::CLI::New::Plugin
|
|
15
14
|
def create_plugin
|
16
15
|
puts "=> Creating new ci plugin: #{name}"
|
17
16
|
core_template_source("plugin/ci")
|
18
|
-
|
19
|
-
directory ".", "terraspace_ci_#{name}", exclude_pattern: exclude_pattern
|
17
|
+
directory ".", "terraspace_ci_#{name}"
|
20
18
|
end
|
21
19
|
|
22
20
|
def finish_message
|
@@ -27,7 +25,6 @@ class Terraspace::CLI::New::Plugin
|
|
27
25
|
"lib/#{gem_name}/interface.rb",
|
28
26
|
"README.md",
|
29
27
|
]
|
30
|
-
files << "lib/#{gem_name}/pr.rb" if @options[:pr]
|
31
28
|
files.sort!
|
32
29
|
list = files.map { |file| " #{file}" }.join("\n")
|
33
30
|
puts <<~EOL
|
data/lib/terraspace/cli/plan.rb
CHANGED
@@ -1,13 +1,77 @@
|
|
1
1
|
class Terraspace::CLI
|
2
2
|
class Plan < Base
|
3
|
-
include TfcConcern
|
4
3
|
include Concerns::PlanPath
|
4
|
+
include Terraspace::Cloud::Streamer
|
5
|
+
include Terraspace::Cloud::Vcs::Commenter
|
6
|
+
include TfcConcern
|
5
7
|
|
6
8
|
def run
|
9
|
+
cloud_plan.cani?
|
10
|
+
@stream = cloud_stream.open("plan")
|
11
|
+
success = perform
|
12
|
+
cloud_stream.close(success, @exception)
|
13
|
+
exit 1 unless success
|
14
|
+
end
|
15
|
+
|
16
|
+
def perform
|
17
|
+
success = plan_only
|
18
|
+
plan = cloud_plan.create(success, @stream)
|
19
|
+
if success && plan # possible from no changes / recording is disabled
|
20
|
+
resp = cloud_cost.cani?(exit_on_error: false)
|
21
|
+
if resp['errors'] # info on why cannot create a plan from resp
|
22
|
+
logger.info "WARN: Not creating a cost estimate."
|
23
|
+
logger.info resp['errors'][0]['detail']
|
24
|
+
else
|
25
|
+
cost = cloud_cost.create(uid: plan['data']['id'], stream: @stream)
|
26
|
+
pr_comment(plan, cost)
|
27
|
+
end
|
28
|
+
logger.info "Terraspace Cloud #{plan['data']['attributes']['url']}"
|
29
|
+
end
|
30
|
+
success
|
31
|
+
rescue Exception => e
|
32
|
+
@exception = true
|
33
|
+
logger.info "Exception #{e.class}: #{e.message}".color(:red)
|
34
|
+
logger.info e.backtrace.join("\n")
|
35
|
+
false
|
36
|
+
end
|
37
|
+
|
38
|
+
def plan_only
|
7
39
|
if Terraspace.cloud? && !@options[:out]
|
8
40
|
@options[:out] = plan_path
|
9
41
|
end
|
10
|
-
|
42
|
+
cloud_plan.setup
|
43
|
+
success = commander.run
|
44
|
+
copy_out_file_to_root
|
45
|
+
success
|
46
|
+
end
|
47
|
+
|
48
|
+
def commander
|
49
|
+
Commander.new("plan", @options)
|
50
|
+
end
|
51
|
+
memoize :commander
|
52
|
+
|
53
|
+
def copy_out_file_to_root
|
54
|
+
file = @mod.out_option
|
55
|
+
return if !file || @options[:copy_to_root] == false
|
56
|
+
return if file =~ %r{^/} # not need to copy absolute path
|
57
|
+
|
58
|
+
name = file.sub("#{Terraspace.root}/",'')
|
59
|
+
src = "#{@mod.cache_dir}/#{name}"
|
60
|
+
dest = name
|
61
|
+
return unless File.exist?(src) # plan wont exists if the plan errors
|
62
|
+
FileUtils.mkdir_p(File.dirname(dest))
|
63
|
+
FileUtils.cp(src, dest)
|
64
|
+
!!dest
|
65
|
+
end
|
66
|
+
|
67
|
+
def cloud_plan
|
68
|
+
Terraspace::Cloud::Plan.new(@options.merge(stack: @mod.name, kind: kind, vcs_vars: vcs_vars))
|
69
|
+
end
|
70
|
+
memoize :cloud_plan
|
71
|
+
|
72
|
+
def cloud_cost
|
73
|
+
Terraspace::Cloud::Cost.new(@options.merge(stack: @mod.name, kind: kind, vcs_vars: vcs_vars))
|
11
74
|
end
|
75
|
+
memoize :cloud_cost
|
12
76
|
end
|
13
77
|
end
|
data/lib/terraspace/cli/up.rb
CHANGED
@@ -1,26 +1,80 @@
|
|
1
|
-
require 'securerandom'
|
2
|
-
|
3
1
|
class Terraspace::CLI
|
4
2
|
class Up < Base
|
5
|
-
include TfcConcern
|
6
3
|
include Concerns::PlanPath
|
4
|
+
include Terraspace::Cloud::Streamer
|
5
|
+
include Terraspace::Cloud::Vcs::Commenter
|
6
|
+
include TfcConcern
|
7
7
|
|
8
8
|
def run
|
9
|
+
cloud_update.cani?
|
10
|
+
@stream = cloud_stream.open("up")
|
11
|
+
success = perform
|
12
|
+
create_cloud_records(success)
|
13
|
+
cloud_stream.close(success, @exception)
|
14
|
+
exit 1 unless success
|
15
|
+
end
|
16
|
+
|
17
|
+
def perform
|
18
|
+
success = nil
|
9
19
|
build
|
10
20
|
if @options[:yes] && !@options[:plan] && !tfc?
|
11
|
-
|
12
|
-
@options[:plan] = plan_path # for terraform apply
|
13
|
-
@options[:out] = plan_path # for terraform plan
|
14
|
-
end
|
15
|
-
Commander.new("plan", @options).run
|
16
|
-
return unless File.exist?(plan_path) if Terraspace.cloud? # happens if plan fails
|
17
|
-
Commander.new("apply", @options).run
|
21
|
+
success = plan
|
18
22
|
else
|
19
|
-
|
23
|
+
skip_plan = true
|
24
|
+
end
|
25
|
+
if success or skip_plan
|
26
|
+
success = apply
|
20
27
|
end
|
28
|
+
success
|
29
|
+
rescue Exception => e
|
30
|
+
@exception = true
|
31
|
+
logger.info "Exception #{e.class}: #{e.message}".color(:red)
|
32
|
+
logger.info e.backtrace.join("\n")
|
33
|
+
false
|
21
34
|
end
|
22
35
|
|
23
36
|
private
|
37
|
+
def plan
|
38
|
+
if Terraspace.cloud? && !@options[:plan]
|
39
|
+
@options[:plan] = plan_path # for terraform apply
|
40
|
+
@options[:out] = plan_path # for terraform plan
|
41
|
+
end
|
42
|
+
|
43
|
+
plan = Plan.new(@options)
|
44
|
+
plan.plan_only # returns success: true or false
|
45
|
+
end
|
46
|
+
|
47
|
+
def apply
|
48
|
+
commander = Commander.new("apply", @options)
|
49
|
+
commander.run
|
50
|
+
end
|
51
|
+
|
52
|
+
def create_cloud_records(success)
|
53
|
+
update = cloud_update.create(success, @stream)
|
54
|
+
return unless success && update # possible from no changes / recording is disabled
|
55
|
+
|
56
|
+
resp = cloud_cost.cani?(exit_on_error: false)
|
57
|
+
if resp['errors'] # info on why cannot create a plan from resp
|
58
|
+
logger.info "WARN: Not creating a cost estimate."
|
59
|
+
logger.info resp['errors'][0]['detail']
|
60
|
+
else
|
61
|
+
cost = cloud_cost.create(uid: update['data']['id'], stream: @stream)
|
62
|
+
pr_comment(update, cost)
|
63
|
+
end
|
64
|
+
|
65
|
+
logger.info "Terraspace Cloud #{update['data']['attributes']['url']}" if update
|
66
|
+
end
|
67
|
+
|
68
|
+
def cloud_update
|
69
|
+
Terraspace::Cloud::Update.new(@options.merge(stack: @mod.name, kind: kind, vcs_vars: vcs_vars))
|
70
|
+
end
|
71
|
+
memoize :cloud_update
|
72
|
+
|
73
|
+
def cloud_cost
|
74
|
+
Terraspace::Cloud::Cost.new(@options.merge(stack: @mod.name, kind: kind, vcs_vars: vcs_vars))
|
75
|
+
end
|
76
|
+
memoize :cloud_cost
|
77
|
+
|
24
78
|
# must build to compute tfc?
|
25
79
|
def build
|
26
80
|
Terraspace::Builder.new(@options).run
|
@@ -1,14 +1,16 @@
|
|
1
1
|
class Terraspace::Cloud::Api
|
2
2
|
class Cani
|
3
|
+
class Cannot < Terraspace::Error; end
|
3
4
|
include Terraspace::Util::Logging
|
4
5
|
|
5
6
|
def initialize(result)
|
6
7
|
@result = result
|
7
8
|
end
|
8
9
|
|
9
|
-
#
|
10
|
+
# Example http responses:
|
11
|
+
# {"data":{"attributes":{"detail":"You are authorized to perform this action.","status":200,"title":"Authoriz...
|
10
12
|
# {"errors":[{"detail":"You are not authorized to perform this action. Double check your token or check with your admin that you have permissions.","status":403,"title":"Forbidden"}]}
|
11
|
-
def handle
|
13
|
+
def handle(exit_on_error=true)
|
12
14
|
yes = false # assume do not have permission
|
13
15
|
detail = @result&.dig('data', 'attributes', 'detail')
|
14
16
|
if detail&.include?('You are authorized to perform this action')
|
@@ -16,15 +18,19 @@ class Terraspace::Cloud::Api
|
|
16
18
|
end
|
17
19
|
return if yes
|
18
20
|
|
19
|
-
if
|
20
|
-
|
21
|
+
if exit_on_error
|
22
|
+
if @result.nil? # 400 Bad Request
|
23
|
+
logger.info "ERROR: It doesn't look like TS_TOKEN is valid".color(:red)
|
24
|
+
else
|
25
|
+
errors = @result.dig('errors')
|
26
|
+
detail = errors.first['detail']
|
27
|
+
# {"errors":[{"detail":"You are not authorized to perform this action. Double check your token or check with your admin that you have permissions.","status":403,"title":"Forbidden"}]}
|
28
|
+
logger.info "ERROR: #{detail}".color(:red)
|
29
|
+
end
|
30
|
+
exit 1
|
21
31
|
else
|
22
|
-
|
23
|
-
detail = errors.first['detail']
|
24
|
-
# {"errors":[{"detail":"You are not authorized to perform this action. Double check your token or check with your admin that you have permissions.","status":403,"title":"Forbidden"}]}
|
25
|
-
logger.info "ERROR: #{detail}".color(:red)
|
32
|
+
@result # original http response with error info
|
26
33
|
end
|
27
|
-
exit 1
|
28
34
|
end
|
29
35
|
end
|
30
36
|
end
|
@@ -5,6 +5,7 @@ class Terraspace::Cloud::Api
|
|
5
5
|
|
6
6
|
# Always translate raw json response to ruby Hash
|
7
7
|
def request(klass, path, data={})
|
8
|
+
exit_on_error = data.delete(:exit_on_error) # for cani logic
|
8
9
|
url = url(path)
|
9
10
|
req = build_request(klass, url, data)
|
10
11
|
retries = 0
|
@@ -26,7 +27,7 @@ class Terraspace::Cloud::Api
|
|
26
27
|
end
|
27
28
|
end
|
28
29
|
result = load_json(url, resp)
|
29
|
-
Cani.new(result).handle if data[:cani]
|
30
|
+
Cani.new(result).handle(exit_on_error) if data[:cani]
|
30
31
|
result
|
31
32
|
end
|
32
33
|
|
@@ -93,7 +94,11 @@ class Terraspace::Cloud::Api
|
|
93
94
|
"#{endpoint}/#{path}"
|
94
95
|
end
|
95
96
|
|
96
|
-
def get(path)
|
97
|
+
def get(path, data={})
|
98
|
+
unless data.empty?
|
99
|
+
separator = path.include?('?') ? '&' : '?'
|
100
|
+
path += separator + data.to_query
|
101
|
+
end
|
97
102
|
request(Net::HTTP::Get, path)
|
98
103
|
end
|
99
104
|
|
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
|