terraspace 1.1.7 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.cody/all/{role.rb → iam_role.rb} +0 -0
- data/.cody/all/project.rb +6 -2
- data/.cody/aws/bin/build.sh +5 -0
- data/.cody/aws/{role.rb → iam_role.rb} +0 -0
- data/.cody/aws/project.rb +6 -2
- data/.cody/azurerm/bin/build.sh +5 -0
- data/.cody/azurerm/{role.rb → iam_role.rb} +0 -0
- data/.cody/azurerm/project.rb +5 -3
- data/.cody/google/bin/build.sh +5 -0
- data/.cody/google/{role.rb → iam_role.rb} +0 -0
- data/.cody/google/project.rb +5 -3
- data/.cody/none/bin/build.sh +5 -0
- data/.cody/none/{role.rb → iam_role.rb} +0 -0
- data/.cody/none/project.rb +6 -2
- data/.cody/shared/script/install/terraform.sh +2 -1
- data/.cody/shared/script/update/gemfile.sh +2 -0
- data/.cody/unit/project.rb +2 -2
- data/.gitignore +2 -0
- data/.pipedream/pipeline.rb +3 -3
- data/CHANGELOG.md +24 -0
- data/exe/terraspace +0 -7
- data/lib/templates/base/project/config/app.rb +3 -0
- data/lib/templates/plugin/ci/%gem_name%.gemspec.tt +32 -0
- data/lib/templates/plugin/ci/.gitignore +12 -0
- data/lib/templates/plugin/{.rspec → ci/.rspec} +0 -0
- data/lib/templates/plugin/ci/.rubocop.yml +13 -0
- data/lib/templates/plugin/ci/CHANGELOG.md.tt +5 -0
- data/lib/templates/plugin/ci/Gemfile +10 -0
- data/lib/templates/plugin/ci/LICENSE.txt +21 -0
- data/lib/templates/plugin/ci/README.md.tt +19 -0
- data/lib/templates/plugin/ci/Rakefile +12 -0
- data/lib/templates/plugin/ci/lib/%gem_name%/autoloader.rb.tt +23 -0
- data/lib/templates/plugin/ci/lib/%gem_name%/interface.rb.tt +15 -0
- data/lib/templates/plugin/ci/lib/%gem_name%/pr.rb.tt +15 -0
- data/lib/templates/plugin/ci/lib/%gem_name%/vars.rb.tt +26 -0
- data/lib/templates/plugin/ci/lib/%gem_name%/version.rb.tt +5 -0
- data/lib/templates/plugin/ci/lib/%gem_name%.rb.tt +17 -0
- data/lib/templates/plugin/ci/spec/%gem_name%_spec.rb.tt +7 -0
- data/lib/templates/plugin/ci/spec/spec_helper.rb.tt +15 -0
- data/lib/templates/plugin/{.gitignore → core/.gitignore} +0 -0
- data/lib/templates/plugin/core/.rspec +3 -0
- data/lib/templates/plugin/{CHANGELOG.md → core/CHANGELOG.md} +0 -0
- data/lib/templates/plugin/{Gemfile → core/Gemfile} +0 -0
- data/lib/templates/plugin/{LICENSE.txt → core/LICENSE.txt} +0 -0
- data/lib/templates/plugin/{README.md.tt → core/README.md.tt} +0 -0
- data/lib/templates/plugin/{Rakefile → core/Rakefile} +0 -0
- data/lib/templates/plugin/{bin → core/bin}/console.tt +0 -0
- data/lib/templates/plugin/{bin → core/bin}/setup +0 -0
- data/lib/templates/plugin/{lib → core/lib}/templates/hcl/module/main.tf +0 -0
- data/lib/templates/plugin/{lib → core/lib}/templates/hcl/module/outputs.tf +0 -0
- data/lib/templates/plugin/{lib → core/lib}/templates/hcl/module/variables.tf +0 -0
- data/lib/templates/plugin/{lib → core/lib}/templates/hcl/project/config/terraform/backend.tf.tt +0 -0
- data/lib/templates/plugin/{lib → core/lib}/templates/hcl/project/config/terraform/provider.tf +0 -0
- data/lib/templates/plugin/{lib → core/lib}/templates/hcl/stack/main.tf +0 -0
- data/lib/templates/plugin/{lib → core/lib}/templates/hcl/stack/outputs.tf +0 -0
- data/lib/templates/plugin/{lib → core/lib}/templates/hcl/stack/variables.tf +0 -0
- data/lib/templates/plugin/{lib → core/lib}/templates/ruby/module/main.rb +0 -0
- data/lib/templates/plugin/{lib → core/lib}/templates/ruby/module/outputs.rb +0 -0
- data/lib/templates/plugin/{lib → core/lib}/templates/ruby/module/variables.rb +0 -0
- data/lib/templates/plugin/{lib → core/lib}/templates/ruby/project/config/terraform/backend.rb.tt +0 -0
- data/lib/templates/plugin/{lib → core/lib}/templates/ruby/project/config/terraform/provider.rb +0 -0
- data/lib/templates/plugin/{lib → core/lib}/templates/ruby/stack/main.rb +0 -0
- data/lib/templates/plugin/{lib → core/lib}/templates/ruby/stack/outputs.rb +0 -0
- data/lib/templates/plugin/{lib → core/lib}/templates/ruby/stack/variables.rb +0 -0
- data/lib/templates/plugin/{lib → core/lib}/templates/test/rspec/module/test/.rspec +0 -0
- data/lib/templates/plugin/{lib → core/lib}/templates/test/rspec/module/test/Gemfile +0 -0
- data/lib/templates/plugin/{lib → core/lib}/templates/test/rspec/module/test/spec/fixtures/stack/main.tf +0 -0
- data/lib/templates/plugin/{lib → core/lib}/templates/test/rspec/module/test/spec/fixtures/stack/outputs.tf +0 -0
- data/lib/templates/plugin/{lib → core/lib}/templates/test/rspec/module/test/spec/fixtures/stack/variables.tf +0 -0
- data/lib/templates/plugin/{lib → core/lib}/templates/test/rspec/module/test/spec/main_spec.rb +0 -0
- data/lib/templates/plugin/{lib → core/lib}/templates/test/rspec/module/test/spec/spec_helper.rb +0 -0
- data/lib/templates/plugin/{lib → core/lib}/terraspace_plugin_%name%/autoloader.rb.tt +0 -0
- data/lib/templates/plugin/{lib → core/lib}/terraspace_plugin_%name%/clients.rb.tt +0 -0
- data/lib/templates/plugin/{lib → core/lib}/terraspace_plugin_%name%/interfaces/backend.rb.tt +0 -0
- data/lib/templates/plugin/{lib → core/lib}/terraspace_plugin_%name%/interfaces/config.rb.tt +0 -0
- data/lib/templates/plugin/{lib → core/lib}/terraspace_plugin_%name%/interfaces/expander.rb.tt +0 -0
- data/lib/templates/plugin/{lib → core/lib}/terraspace_plugin_%name%/interfaces/layer.rb.tt +0 -0
- data/lib/templates/plugin/{lib → core/lib}/terraspace_plugin_%name%/version.rb.tt +0 -0
- data/lib/templates/plugin/{lib → core/lib}/terraspace_plugin_%name%.rb.tt +0 -0
- data/lib/templates/plugin/{spec → core/spec}/spec_helper.rb.tt +0 -0
- data/lib/templates/plugin/{spec → core/spec}/terraspace_provider_%name%_spec.rb.tt +0 -0
- data/lib/templates/plugin/{terraspace_plugin_%name%.gemspec.tt → core/terraspace_plugin_%name%.gemspec.tt} +0 -0
- data/lib/terraspace/all/runner.rb +2 -0
- data/lib/terraspace/all/summary.rb +2 -0
- data/lib/terraspace/app.rb +23 -4
- data/lib/terraspace/builder.rb +1 -1
- data/lib/terraspace/cli/concerns/plan_path.rb +8 -0
- data/lib/terraspace/cli/down.rb +4 -0
- data/lib/terraspace/cli/init.rb +1 -1
- data/lib/terraspace/cli/new/ci.rb +121 -0
- data/lib/terraspace/cli/new/example.rb +1 -1
- data/lib/terraspace/cli/new/helpers/plugin_gem.rb +1 -1
- data/lib/terraspace/cli/new/plugin/ci.rb +46 -0
- data/lib/terraspace/cli/new/plugin/core.rb +26 -0
- data/lib/terraspace/cli/new/plugin/helper.rb +4 -1
- data/lib/terraspace/cli/new/plugin.rb +8 -15
- data/lib/terraspace/cli/new/test.rb +1 -1
- data/lib/terraspace/cli/new.rb +17 -13
- data/lib/terraspace/cli/plan.rb +13 -0
- data/lib/terraspace/cli/setup/check.rb +8 -0
- data/lib/terraspace/cli/up.rb +8 -12
- data/lib/terraspace/cli.rb +2 -2
- data/lib/terraspace/cloud/api/cani.rb +30 -0
- data/lib/terraspace/cloud/api/concern/errors.rb +12 -0
- data/lib/terraspace/cloud/api/concern/record.rb +18 -0
- data/lib/terraspace/cloud/api/concern.rb +38 -0
- data/lib/terraspace/cloud/api/http_methods.rb +116 -0
- data/lib/terraspace/cloud/api/validate.rb +24 -0
- data/lib/terraspace/cloud/api.rb +33 -0
- data/lib/terraspace/cloud/base.rb +97 -0
- data/lib/terraspace/cloud/ci/generic.rb +25 -0
- data/lib/terraspace/cloud/ci/manual.rb +81 -0
- data/lib/terraspace/cloud/ci/vcs/base.rb +36 -0
- data/lib/terraspace/cloud/ci/vcs/bitbucket.rb +11 -0
- data/lib/terraspace/cloud/ci/vcs/github.rb +11 -0
- data/lib/terraspace/cloud/ci/vcs/gitlab.rb +11 -0
- data/lib/terraspace/cloud/ci/vcs.rb +18 -0
- data/lib/terraspace/cloud/ci.rb +56 -0
- data/lib/terraspace/cloud/context.rb +14 -0
- data/lib/terraspace/cloud/folder/base.rb +17 -0
- data/lib/terraspace/cloud/folder/package.rb +33 -0
- data/lib/terraspace/cloud/folder/tidy.rb +54 -0
- data/lib/terraspace/cloud/folder/uploader.rb +37 -0
- data/lib/terraspace/cloud/folder.rb +11 -0
- data/lib/terraspace/cloud/plan.rb +47 -0
- data/lib/terraspace/cloud/update.rb +37 -0
- data/lib/terraspace/command.rb +16 -1
- data/lib/terraspace/compiler/dsl/syntax/mod.rb +2 -2
- data/lib/terraspace/compiler/dsl/syntax/tfvar.rb +1 -1
- data/lib/terraspace/compiler/expander/backend.rb +1 -1
- data/lib/terraspace/compiler/expander.rb +1 -1
- data/lib/terraspace/compiler/strategy/tfvar/layer.rb +56 -29
- data/lib/terraspace/core.rb +36 -3
- data/lib/terraspace/ext/core/module.rb +9 -4
- data/lib/terraspace/hooks/builder.rb +1 -1
- data/lib/terraspace/logger.rb +32 -5
- data/lib/terraspace/mod.rb +21 -9
- data/lib/terraspace/plugin/expander/interface.rb +15 -11
- data/lib/terraspace/plugin.rb +14 -5
- data/lib/terraspace/shell.rb +7 -2
- data/lib/terraspace/terraform/args/thor.rb +8 -2
- data/lib/terraspace/terraform/ihooks/after/apply.rb +8 -0
- data/lib/terraspace/terraform/ihooks/after/destroy.rb +8 -0
- data/lib/terraspace/terraform/ihooks/after/plan.rb +31 -2
- data/lib/terraspace/terraform/ihooks/base.rb +7 -3
- data/lib/terraspace/terraform/ihooks/before/apply.rb +8 -0
- data/lib/terraspace/terraform/ihooks/before/destroy.rb +8 -0
- data/lib/terraspace/terraform/ihooks/before/plan.rb +11 -3
- data/lib/terraspace/terraform/runner.rb +19 -5
- data/lib/terraspace/version.rb +1 -1
- data/lib/terraspace.rb +2 -0
- data/terraspace.gemspec +1 -0
- metadata +116 -52
- data/.pipedream/schedule.rb +0 -3
@@ -0,0 +1,116 @@
|
|
1
|
+
class Terraspace::Cloud::Api
|
2
|
+
module HttpMethods
|
3
|
+
extend Memoist
|
4
|
+
include Terraspace::Util
|
5
|
+
|
6
|
+
# Always translate raw json response to ruby Hash
|
7
|
+
def request(klass, path, data={})
|
8
|
+
url = url(path)
|
9
|
+
req = build_request(klass, url, data)
|
10
|
+
retries = 0
|
11
|
+
begin
|
12
|
+
resp = http.request(req) # send request
|
13
|
+
rescue Errno::ECONNREFUSED, Errno::EAFNOSUPPORT
|
14
|
+
delay = 2 ** retries
|
15
|
+
logger.info "Unable to connect to #{url}. Delay for #{delay}s and trying again."
|
16
|
+
sleep(delay)
|
17
|
+
retries += 1
|
18
|
+
# Final retry time: 2 * 4 = 16s
|
19
|
+
# Total retry time: 2 ** 4 + 2 ** 3 + 2 ** 2 + 2 ** 1 + 2 ** 0 = 31s
|
20
|
+
if retries < 5 # max_retries is 4
|
21
|
+
retry
|
22
|
+
else
|
23
|
+
logger.error "Error connecting to #{url}"
|
24
|
+
message = "#{$!.class}: #{$!.message}"
|
25
|
+
raise Terraspace::NetworkError.new(message)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
result = load_json(url, resp)
|
29
|
+
Cani.new(result).handle if data[:cani]
|
30
|
+
result
|
31
|
+
end
|
32
|
+
|
33
|
+
def build_request(klass, url, data={})
|
34
|
+
req = klass.new(url) # url includes query string and uri.path does not, must used url
|
35
|
+
set_headers!(req)
|
36
|
+
if [Net::HTTP::Delete, Net::HTTP::Patch, Net::HTTP::Post, Net::HTTP::Put].include?(klass)
|
37
|
+
text = JSON.dump(data)
|
38
|
+
req.body = text
|
39
|
+
req.content_length = text.bytesize
|
40
|
+
end
|
41
|
+
|
42
|
+
logger.debug "API klass: #{klass}"
|
43
|
+
logger.debug "API url: #{url}"
|
44
|
+
logger.debug "API data: #{data}"
|
45
|
+
|
46
|
+
req
|
47
|
+
end
|
48
|
+
|
49
|
+
def set_headers!(req)
|
50
|
+
req['Authorization'] = "Bearer #{token}" if token
|
51
|
+
req['Content-Type'] = 'application/json'
|
52
|
+
end
|
53
|
+
|
54
|
+
def token
|
55
|
+
ENV['TS_TOKEN']
|
56
|
+
end
|
57
|
+
|
58
|
+
def load_json(url, resp)
|
59
|
+
uri = URI(url)
|
60
|
+
|
61
|
+
logger.debug "resp.code #{resp.code}"
|
62
|
+
logger.debug "resp.body #{resp.body}" # {"errors":[{"message":"403 Forbidden"}]}
|
63
|
+
|
64
|
+
if parseable?(resp.code)
|
65
|
+
JSON.load(resp.body)
|
66
|
+
else
|
67
|
+
logger.error "Error: #{url}"
|
68
|
+
logger.error "Error: Non-successful http response status code: #{resp.code}"
|
69
|
+
# logger.debug "Error: Non-successful http response body: #{resp.body}"
|
70
|
+
logger.error "headers: #{resp.each_header.to_h.inspect}"
|
71
|
+
logger.error "Terraspace Cloud API #{url}"
|
72
|
+
raise "Terraspace Cloud API called failed: #{uri.host}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Note: 422 is Unprocessable Entity. This means an invalid data payload was sent.
|
77
|
+
# We want that to error and raise
|
78
|
+
def parseable?(http_code)
|
79
|
+
http_code =~ /^20/ || http_code =~ /^40/
|
80
|
+
end
|
81
|
+
|
82
|
+
def http
|
83
|
+
uri = URI(endpoint)
|
84
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
85
|
+
http.open_timeout = http.read_timeout = 30
|
86
|
+
http.use_ssl = true if uri.scheme == 'https'
|
87
|
+
http
|
88
|
+
end
|
89
|
+
memoize :http
|
90
|
+
|
91
|
+
# API does not include the /. IE: https://app.terraform.io/api/v2
|
92
|
+
def url(path)
|
93
|
+
"#{endpoint}/#{path}"
|
94
|
+
end
|
95
|
+
|
96
|
+
def get(path)
|
97
|
+
request(Net::HTTP::Get, path)
|
98
|
+
end
|
99
|
+
|
100
|
+
def post(path, data={})
|
101
|
+
request(Net::HTTP::Post, path, data)
|
102
|
+
end
|
103
|
+
|
104
|
+
def put(path, data={})
|
105
|
+
request(Net::HTTP::Put, path, data)
|
106
|
+
end
|
107
|
+
|
108
|
+
def patch(path, data={})
|
109
|
+
request(Net::HTTP::Patch, path, data)
|
110
|
+
end
|
111
|
+
|
112
|
+
def delete(path, data={})
|
113
|
+
request(Net::HTTP::Delete, path, data)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class Terraspace::Cloud::Api
|
2
|
+
module Validate
|
3
|
+
def validate(name, value)
|
4
|
+
unless value =~ /^[\w-]*$/
|
5
|
+
message = "ERROR: #{name}: only allows letters, numbers, dashes, and underscores"
|
6
|
+
end
|
7
|
+
if value =~ /^[-_]/ || value =~ /[-_]$/
|
8
|
+
message = "ERROR: #{name}: no leading or trailing underscore or dash allowed"
|
9
|
+
end
|
10
|
+
if message
|
11
|
+
puts message.color(:red)
|
12
|
+
puts <<~EOL
|
13
|
+
Please fix the configuration
|
14
|
+
|
15
|
+
config/app.rb
|
16
|
+
|
17
|
+
config.cloud.#{name} = '...'
|
18
|
+
|
19
|
+
EOL
|
20
|
+
exit 1
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Terraspace::Cloud
|
2
|
+
class Api
|
3
|
+
include Context
|
4
|
+
include HttpMethods
|
5
|
+
|
6
|
+
def initialize(options)
|
7
|
+
@options = options
|
8
|
+
setup_context(@options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def endpoint
|
12
|
+
ENV['TS_API'].blank? ? 'https://api.terraspace.cloud/api/v1' : ENV['TS_API']
|
13
|
+
end
|
14
|
+
|
15
|
+
def stack_path
|
16
|
+
"orgs/#{@org}/projects/#{@project}/stacks/#{@stack}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def create_upload
|
20
|
+
post("#{stack_path}/uploads", @options)
|
21
|
+
end
|
22
|
+
|
23
|
+
# record_attrs: {upload_id: "upload-nRPSpyWd65Ps6978", kind: "apply", stack_id: '...'}
|
24
|
+
def create_plan(data)
|
25
|
+
post("#{stack_path}/plans", data.merge(@options))
|
26
|
+
end
|
27
|
+
|
28
|
+
# data: {upload_id: "upload-nRPSpyWd65Ps6978", kind: "apply", stack_id: '...'}
|
29
|
+
def create_update(data)
|
30
|
+
post("#{stack_path}/updates", data.merge(@options))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module Terraspace::Cloud
|
2
|
+
class Base < Terraspace::CLI::Base
|
3
|
+
extend Memoist
|
4
|
+
include Api::Concern
|
5
|
+
include Context
|
6
|
+
include Terraspace::Util
|
7
|
+
|
8
|
+
def initialize(options={})
|
9
|
+
super
|
10
|
+
@cani = options[:cani]
|
11
|
+
@kind = options[:kind]
|
12
|
+
@success = options[:success]
|
13
|
+
setup_context(options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def stage_attrs
|
17
|
+
status = @success ? "success" : "fail"
|
18
|
+
attrs = {
|
19
|
+
status: status,
|
20
|
+
kind: @kind,
|
21
|
+
terraspace_version: check.terraspace_version,
|
22
|
+
terraform_version: check.terraform_version,
|
23
|
+
}
|
24
|
+
attrs.merge!(ci_vars) if ci_vars
|
25
|
+
attrs
|
26
|
+
end
|
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)
|
35
|
+
end
|
36
|
+
|
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
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def ci
|
48
|
+
Terraspace::Cloud::Ci.detect
|
49
|
+
end
|
50
|
+
memoize :ci
|
51
|
+
|
52
|
+
def sh(command, exit_on_fail: true)
|
53
|
+
logger.debug "=> #{command}"
|
54
|
+
system command
|
55
|
+
if $?.exitstatus != 0 && exit_on_fail
|
56
|
+
logger.info "ERROR RUNNING: #{command}"
|
57
|
+
exit $?.exitstatus
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def clean_cache2_stage
|
62
|
+
# terraform plan can be a kind of apply or destroy
|
63
|
+
# terraform apply can be a kind of apply or destroy
|
64
|
+
kind = self.class.name.to_s.split('::').last.underscore # IE: apply or destroy
|
65
|
+
dir = "#{@mod.cache_dir}/.terraspace-cache/_cache2/#{kind}"
|
66
|
+
FileUtils.rm_rf(dir)
|
67
|
+
FileUtils.mkdir_p(dir)
|
68
|
+
end
|
69
|
+
|
70
|
+
def record?
|
71
|
+
changes? && !cancelled? || Terraspace.config.cloud.record == "all"
|
72
|
+
end
|
73
|
+
|
74
|
+
def changes?
|
75
|
+
no_changes = Terraspace::Logger.buffer.detect do |line|
|
76
|
+
line.include?('No changes')
|
77
|
+
end
|
78
|
+
zero_destroyed = Terraspace::Logger.buffer.detect do |line|
|
79
|
+
line.include?('Destroy complete! Resources: 0 destroyed')
|
80
|
+
end
|
81
|
+
!no_changes && !zero_destroyed
|
82
|
+
end
|
83
|
+
|
84
|
+
def cancelled?
|
85
|
+
!!Terraspace::Logger.buffer.detect do |line|
|
86
|
+
line.include?(' cancelled')
|
87
|
+
end
|
88
|
+
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
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Terraspace::Cloud::Ci
|
2
|
+
class Generic
|
3
|
+
def vars
|
4
|
+
{
|
5
|
+
build_system: "generic",
|
6
|
+
host: ENV['TS_CI_HOST'] || ENV['TS_VCS_HOST'],
|
7
|
+
full_repo: ENV['TS_CI_REPO'],
|
8
|
+
branch_name: ENV['TS_CI_BRANCH'],
|
9
|
+
# urls
|
10
|
+
commit_url: ENV['TS_CI_COMMIT_URL'],
|
11
|
+
branch_url: ENV['TS_CI_BRANCH_URL'],
|
12
|
+
pr_url: ENV['TS_CI_PR_URL'],
|
13
|
+
build_url: ENV['TS_CI_BUILD_URL'],
|
14
|
+
# additional properties
|
15
|
+
build_type: ENV['TS_CI_BUILD_TYPE'],
|
16
|
+
pr_number: ENV['TS_CI_PR_NUMBER'],
|
17
|
+
sha: ENV['TS_CI_SHA'],
|
18
|
+
# additional properties
|
19
|
+
commit_message: ENV['TS_CI_COMMIT_MESSAGE'],
|
20
|
+
build_id: ENV['TS_CI_BUILD_ID'],
|
21
|
+
build_number: ENV['TS_CI_BUILD_NUMBER'],
|
22
|
+
}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
class Terraspace::Cloud::Ci
|
2
|
+
class Manual
|
3
|
+
extend Memoist
|
4
|
+
include Terraspace::Util::Logging
|
5
|
+
|
6
|
+
def vars
|
7
|
+
if git_repo? && git_installed?
|
8
|
+
vars_data
|
9
|
+
else
|
10
|
+
{ build_system: "manual" }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def vars_data
|
15
|
+
{
|
16
|
+
build_system: "manual", # required
|
17
|
+
host: host,
|
18
|
+
full_repo: full_repo,
|
19
|
+
branch_name: branch_name,
|
20
|
+
sha: sha,
|
21
|
+
dirty: dirty?,
|
22
|
+
# commit_url: commit_url, # provided by core
|
23
|
+
# branch_url: branch_url, # provided by core
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
def host
|
28
|
+
return nil unless File.exist?('.git')
|
29
|
+
return nil if git_url.blank?
|
30
|
+
uri = URI(git_url)
|
31
|
+
"#{uri.scheme}://#{uri.host}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def full_repo
|
35
|
+
uri = URI(git_url)
|
36
|
+
uri.path.sub(/^\//,'')
|
37
|
+
end
|
38
|
+
|
39
|
+
def dirty?
|
40
|
+
out = git "status --porcelain"
|
41
|
+
!out.blank?
|
42
|
+
end
|
43
|
+
|
44
|
+
# Works for
|
45
|
+
# git@github.com: => https://github.com/
|
46
|
+
# git@bitbucket.org: => https://bitbucket.org/
|
47
|
+
# git@gitlab.com: => https://gitlab.com/
|
48
|
+
def git_url
|
49
|
+
out = git "config --get remote.origin.url"
|
50
|
+
out.sub(/\.git/,'').sub(/^git@/,'https://').sub(/\.(.*):/,'.\1/')
|
51
|
+
end
|
52
|
+
|
53
|
+
def branch_name
|
54
|
+
out = git "rev-parse --abbrev-ref HEAD"
|
55
|
+
out unless out == "HEAD" # edge case: when branch has never been pushed
|
56
|
+
end
|
57
|
+
|
58
|
+
def sha
|
59
|
+
out = git "rev-parse HEAD"
|
60
|
+
out unless out == "HEAD" # edge case: when branch has never been pushed
|
61
|
+
end
|
62
|
+
|
63
|
+
def git(command)
|
64
|
+
return unless git_installed?
|
65
|
+
out = `git #{command}`
|
66
|
+
unless $?.success?
|
67
|
+
logger.debug "WARN Command Failed: git #{command}".color(:yellow)
|
68
|
+
end
|
69
|
+
out.strip
|
70
|
+
end
|
71
|
+
memoize :git
|
72
|
+
|
73
|
+
def git_installed?
|
74
|
+
system "type git > /dev/null 2>&1"
|
75
|
+
end
|
76
|
+
|
77
|
+
def git_repo?
|
78
|
+
File.exist?('.git')
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
class Terraspace::Cloud::Ci::Vcs
|
2
|
+
class Base
|
3
|
+
extend Memoist
|
4
|
+
|
5
|
+
def initialize(vars)
|
6
|
+
@vars = vars
|
7
|
+
end
|
8
|
+
|
9
|
+
def vars
|
10
|
+
{
|
11
|
+
commit_url: commit_url, # implemented by subclass
|
12
|
+
branch_url: branch_url, # implemented by subclass
|
13
|
+
}
|
14
|
+
end
|
15
|
+
|
16
|
+
def merged_vars
|
17
|
+
@vars.merge(vars)
|
18
|
+
end
|
19
|
+
|
20
|
+
class << self
|
21
|
+
def vars_methods(*names)
|
22
|
+
names.each do |name|
|
23
|
+
vars_method(name)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def vars_method(name)
|
28
|
+
define_method name do
|
29
|
+
@vars[name]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
vars_methods :host, :full_repo, :sha, :branch_name
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Terraspace::Cloud::Ci
|
2
|
+
class Vcs
|
3
|
+
extend Memoist
|
4
|
+
|
5
|
+
def initialize(vars)
|
6
|
+
@vars = vars
|
7
|
+
end
|
8
|
+
|
9
|
+
def merged_vars
|
10
|
+
vcs_class = case @vars[:host]
|
11
|
+
when /github/ then Github
|
12
|
+
when /gitlab/ then Gitlab
|
13
|
+
when /bitbucket/ then Bitbucket
|
14
|
+
end
|
15
|
+
vcs_class ? vcs_class.new(@vars).merged_vars : {}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Terraspace::Cloud
|
2
|
+
class Ci
|
3
|
+
# Example meta:
|
4
|
+
#
|
5
|
+
# [
|
6
|
+
# {name: "github", interface_class: TerrapaceCiGithub::Interface},
|
7
|
+
# {name: "gitlab", interface_class: TerrapaceCiGitlab::Interface},
|
8
|
+
# ]
|
9
|
+
#
|
10
|
+
class_attribute :meta # not shared with child classes
|
11
|
+
self.meta = []
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def register(data)
|
15
|
+
self.meta << data unless meta.find do |m|
|
16
|
+
m[:name] == data[:name]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def detect
|
21
|
+
return Generic if ENV['TS_CI_REPO']
|
22
|
+
|
23
|
+
detected = meta.find do |data|
|
24
|
+
env_key = data[:env_key] # IE: ENV['GITHUB_ACTIONS']
|
25
|
+
env_value = data[:env_value] # IE: "string" or /pattern/
|
26
|
+
if env_value
|
27
|
+
v = ENV[env_key]
|
28
|
+
v && match?(v, env_value)
|
29
|
+
else
|
30
|
+
ENV[env_key] # only env_key
|
31
|
+
end
|
32
|
+
end
|
33
|
+
klass = if detected
|
34
|
+
interface_class(detected)
|
35
|
+
else
|
36
|
+
Manual
|
37
|
+
end
|
38
|
+
klass.new
|
39
|
+
end
|
40
|
+
|
41
|
+
# IE: TerraspaceCiGithub::Interface
|
42
|
+
def interface_class(meta)
|
43
|
+
"terraspace_ci_#{meta[:name]}::Interface".classify.constantize
|
44
|
+
end
|
45
|
+
|
46
|
+
def match?(v, env_value)
|
47
|
+
case v
|
48
|
+
when String
|
49
|
+
v == env_value
|
50
|
+
when Regexp
|
51
|
+
v.match(env_value)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Terraspace::Cloud
|
2
|
+
module Context
|
3
|
+
include Terraspace::Cloud::Api::Validate
|
4
|
+
|
5
|
+
def setup_context(options)
|
6
|
+
cloud = Terraspace.config.cloud
|
7
|
+
@org = cloud.org
|
8
|
+
@project = cloud.project
|
9
|
+
@stack = options[:stack]
|
10
|
+
validate("org", @org)
|
11
|
+
validate("project", @project)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Terraspace::Cloud::Folder
|
2
|
+
class Base < Terraspace::Cloud::Base
|
3
|
+
def initialize(options={})
|
4
|
+
super
|
5
|
+
@type = options[:type]
|
6
|
+
end
|
7
|
+
|
8
|
+
# final zip dest
|
9
|
+
def zip_path
|
10
|
+
"#{artifacts_path}.zip"
|
11
|
+
end
|
12
|
+
|
13
|
+
def artifacts_path
|
14
|
+
"#{@mod.cache_dir}/.terraspace-cache/_cache2/artifacts/#{@type}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'zip_folder'
|
2
|
+
|
3
|
+
class Terraspace::Cloud::Folder
|
4
|
+
class Package < Base
|
5
|
+
def build
|
6
|
+
copy
|
7
|
+
tidy
|
8
|
+
zip # returns zip path
|
9
|
+
end
|
10
|
+
|
11
|
+
def copy
|
12
|
+
FileUtils.rm_rf(artifacts_path)
|
13
|
+
FileUtils.mkdir_p(File.dirname(artifacts_path))
|
14
|
+
|
15
|
+
expr = "#{@mod.cache_dir}/.terraspace-cache/_cache2/#{@type}/*"
|
16
|
+
Dir.glob(expr).each do |src|
|
17
|
+
dest = "#{artifacts_path}/#{File.basename(src)}"
|
18
|
+
FileUtils.mkdir_p(File.dirname(dest))
|
19
|
+
FileUtils.cp(src, dest)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def tidy
|
24
|
+
Tidy.new(@options).cleanup
|
25
|
+
end
|
26
|
+
|
27
|
+
def zip
|
28
|
+
FileUtils.rm_f(zip_path)
|
29
|
+
ZipFolder.zip(artifacts_path, zip_path)
|
30
|
+
zip_path
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
class Terraspace::Cloud::Folder
|
2
|
+
class Tidy < Base
|
3
|
+
def cleanup
|
4
|
+
removals.each do |removal|
|
5
|
+
removal = removal.sub(%r{^/},'') # remove leading slash
|
6
|
+
path = "#{artifacts_path}/#{removal}"
|
7
|
+
rm_rf(path)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def removals
|
12
|
+
removals = always_removals
|
13
|
+
removals += get_removals("#{artifacts_path}/.gitignore")
|
14
|
+
removals = removals.reject do |p|
|
15
|
+
tskeep.find do |keep|
|
16
|
+
p.include?(keep)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
removals.uniq
|
20
|
+
end
|
21
|
+
|
22
|
+
def get_removals(file)
|
23
|
+
path = file
|
24
|
+
return [] unless File.exist?(path)
|
25
|
+
|
26
|
+
removal = File.read(path).split("\n")
|
27
|
+
removal.map {|i| i.strip}.reject {|i| i =~ /^#/ || i.empty?}
|
28
|
+
end
|
29
|
+
|
30
|
+
# We clean out ignored files pretty aggressively. So provide
|
31
|
+
# a way for users to keep files from being cleaned out.
|
32
|
+
def tskeep
|
33
|
+
always_keep = %w[]
|
34
|
+
path = "#{artifacts_path}/.tskeep"
|
35
|
+
return always_keep unless File.exist?(path)
|
36
|
+
|
37
|
+
keep = IO.readlines(path)
|
38
|
+
keep = keep.map {|i| i.strip}.reject { |i| i =~ /^#/ || i.empty? }
|
39
|
+
(always_keep + keep).uniq
|
40
|
+
end
|
41
|
+
|
42
|
+
def rm_rf(path)
|
43
|
+
exists = File.exist?("#{path}/.gitkeep") || File.exist?("#{path}/.keep")
|
44
|
+
return if exists
|
45
|
+
|
46
|
+
FileUtils.rm_rf(path)
|
47
|
+
end
|
48
|
+
|
49
|
+
# These directories will be removed regardless of dir level
|
50
|
+
def always_removals
|
51
|
+
%w[.git spec tmp]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class Terraspace::Cloud::Folder
|
2
|
+
class Uploader < Base
|
3
|
+
attr_reader :record
|
4
|
+
def upload
|
5
|
+
@record = create_record # set @record for start_plan(uploader.record)
|
6
|
+
upload_project(@record['url'], zip_path)
|
7
|
+
end
|
8
|
+
|
9
|
+
def create_record
|
10
|
+
result = api.create_upload
|
11
|
+
if errors?(result)
|
12
|
+
error_message(result)
|
13
|
+
exit 1 # Consider: raise exception can rescue higher up
|
14
|
+
else
|
15
|
+
load_record(result)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def upload_project(url, path)
|
20
|
+
uri = URI.parse(url)
|
21
|
+
object_content = IO.read(path)
|
22
|
+
resp = Net::HTTP.start(uri.host) do |http|
|
23
|
+
http.send_request(
|
24
|
+
'PUT',
|
25
|
+
uri.request_uri,
|
26
|
+
object_content,
|
27
|
+
'content-type' => ''
|
28
|
+
)
|
29
|
+
end
|
30
|
+
unless resp.code =~ /^20/
|
31
|
+
puts "ERROR: Uploading code"
|
32
|
+
puts "resp.body #{resp.body}"
|
33
|
+
exit 1 # TODO: consider raising error
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|