yle_tf 1.3.0 → 1.4.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/lib/yle_tf/action/terraform_init.rb +41 -8
- data/lib/yle_tf/action/write_terraformrc_defaults.rb +31 -16
- data/lib/yle_tf/backend.rb +5 -0
- data/lib/yle_tf/config/defaults.rb +4 -1
- data/lib/yle_tf/version.rb +1 -1
- data/lib/yle_tf_plugins/backends/file/backend.rb +64 -5
- data/lib/yle_tf_plugins/commands/help/command.rb +7 -20
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e8df78c6d65e3180770988e0fdc1655bfa0af42d57d8f1b5dd19f26de1281a39
|
4
|
+
data.tar.gz: 10f59a1cdd3757d19dbeebfaaeb1328e41c367be464d87486b9bb8b93d6ef845
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dc31eb920ce29b2aeb8616739b89e370a0c36a8ff395e3a41b3a1176d4ac592a2da913b6ff11eb0a0c015c33f5819cf5e888b9056b324e7ef81c156f75fa4fc6
|
7
|
+
data.tar.gz: 2fbcf63e27d8a817b654087d676a9252db1acde67629ad66a07e4d12ac20e44396c707718c42c3ee594593bcad74fb5d1be108026b5a17c615dc4663eca42a97
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'fileutils'
|
3
4
|
require 'pathname'
|
5
|
+
require 'shellwords'
|
4
6
|
|
5
7
|
require 'yle_tf/logger'
|
6
8
|
require 'yle_tf/plugin'
|
@@ -28,26 +30,53 @@ class YleTf
|
|
28
30
|
Logger.info('Initializing Terraform')
|
29
31
|
Logger.debug("Backend configuration: #{backend}")
|
30
32
|
|
31
|
-
|
33
|
+
init_dir
|
32
34
|
|
33
|
-
|
35
|
+
if env[:tf_command] == 'init'
|
36
|
+
# Skip initializing Terraform here, as it will be done by the
|
37
|
+
# actuall command later in the middleware stack.
|
38
|
+
@app.call(env)
|
39
|
+
store_terraform_lock
|
40
|
+
else
|
41
|
+
init_terraform
|
42
|
+
store_terraform_lock
|
43
|
+
@app.call(env)
|
44
|
+
end
|
45
|
+
|
46
|
+
tear_down
|
34
47
|
end
|
35
48
|
|
36
|
-
def
|
49
|
+
def init_dir
|
37
50
|
Logger.debug('Configuring the backend')
|
38
51
|
backend.configure
|
39
52
|
|
40
53
|
Logger.debug('Symlinking errored.tfstate')
|
41
|
-
|
54
|
+
symlink_to_module_dir('errored.tfstate')
|
55
|
+
end
|
42
56
|
|
57
|
+
def tear_down
|
58
|
+
Logger.debug('Tearing down backend')
|
59
|
+
backend.tear_down
|
60
|
+
end
|
61
|
+
|
62
|
+
def init_terraform
|
43
63
|
Logger.debug('Initializing Terraform')
|
44
|
-
YleTf::System.cmd('terraform', 'init', *
|
64
|
+
YleTf::System.cmd('terraform', 'init', *tf_init_args, **TF_CMD_OPTS)
|
65
|
+
end
|
66
|
+
|
67
|
+
def store_terraform_lock
|
68
|
+
Logger.debug('Storing .terraform.lock.hcl')
|
69
|
+
copy_to_module_dir('.terraform.lock.hcl')
|
45
70
|
end
|
46
71
|
|
47
72
|
def backend
|
48
73
|
@backend ||= find_backend
|
49
74
|
end
|
50
75
|
|
76
|
+
def tf_init_args
|
77
|
+
TF_CMD_ARGS + Shellwords.split(ENV.fetch('TF_INIT_ARGS', ''))
|
78
|
+
end
|
79
|
+
|
51
80
|
def find_backend
|
52
81
|
backend_type = config.fetch('backend', 'type').downcase
|
53
82
|
backend_proc = Plugin.manager.backends[backend_type]
|
@@ -56,15 +85,19 @@ class YleTf
|
|
56
85
|
klass.new(config)
|
57
86
|
end
|
58
87
|
|
59
|
-
def
|
60
|
-
local_path = Pathname.pwd.join(
|
61
|
-
remote_path = config.module_dir.join(
|
88
|
+
def symlink_to_module_dir(file)
|
89
|
+
local_path = Pathname.pwd.join(file)
|
90
|
+
remote_path = config.module_dir.join(file)
|
62
91
|
|
63
92
|
# Remove the possibly copied old file
|
64
93
|
local_path.unlink if local_path.exist?
|
65
94
|
|
66
95
|
local_path.make_symlink(remote_path)
|
67
96
|
end
|
97
|
+
|
98
|
+
def copy_to_module_dir(file)
|
99
|
+
FileUtils.cp(file, config.module_dir.to_s) if File.exist?(file)
|
100
|
+
end
|
68
101
|
end
|
69
102
|
end
|
70
103
|
end
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'fileutils'
|
4
|
+
require 'pathname'
|
5
|
+
|
4
6
|
require 'yle_tf/logger'
|
5
7
|
|
6
8
|
class YleTf
|
@@ -10,45 +12,58 @@ class YleTf
|
|
10
12
|
RC_PATH = '~/.terraformrc'
|
11
13
|
|
12
14
|
# Path of the plugin cache directory
|
13
|
-
DEFAULT_PLUGIN_CACHE_PATH = '
|
15
|
+
DEFAULT_PLUGIN_CACHE_PATH = '~/.terraform.d/plugin-cache'
|
14
16
|
|
15
17
|
def initialize(app)
|
16
18
|
@app = app
|
17
19
|
end
|
18
20
|
|
19
21
|
def call(env)
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
22
|
+
if rc_file.exist?
|
23
|
+
Logger.debug("Terraform configuration file '#{RC_PATH}' already exists")
|
24
|
+
if !existing_keys.include?('plugin_cache_dir')
|
25
|
+
Logger.warn("'plugin_cache_dir' not configured in '#{RC_PATH}'")
|
26
|
+
end
|
27
|
+
else
|
28
|
+
Logger.debug("Writing default configuration to '#{RC_PATH}'")
|
29
|
+
write_default_config
|
26
30
|
end
|
27
31
|
|
28
32
|
@app.call(env)
|
29
33
|
end
|
30
34
|
|
31
|
-
def
|
32
|
-
|
35
|
+
def rc_file
|
36
|
+
@rc_file ||= Pathname.new(RC_PATH).expand_path
|
33
37
|
end
|
34
38
|
|
35
|
-
def existing_keys
|
36
|
-
[].tap do |keys|
|
37
|
-
rc_file.
|
39
|
+
def existing_keys
|
40
|
+
@existing_keys ||= [].tap do |keys|
|
41
|
+
rc_file.readlines.each do |line|
|
42
|
+
# The matcher is a bit naive, but enough for out use
|
38
43
|
keys << Regexp.last_match(1) if line =~ /^(.+?)[ \t]*=/
|
39
44
|
end
|
40
45
|
end
|
41
46
|
end
|
42
47
|
|
43
|
-
def
|
48
|
+
def write_default_config
|
49
|
+
rc_file.open('w') do |rc_file|
|
50
|
+
configure_checkpoint(rc_file)
|
51
|
+
configure_plugin_cache_dir(rc_file)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def configure_checkpoint(file)
|
44
56
|
Logger.info("Disabling Terraform upgrade and security bulletin checks by '#{RC_PATH}'")
|
45
57
|
|
46
|
-
|
58
|
+
file.puts('disable_checkpoint = true')
|
47
59
|
end
|
48
60
|
|
49
|
-
def configure_plugin_cache_dir(
|
61
|
+
def configure_plugin_cache_dir(file)
|
50
62
|
Logger.info("Configuring global Terraform plugin cache by '#{RC_PATH}'")
|
51
|
-
|
63
|
+
# Replace `~` with `$HOME` as it is not expanded correctly in all architectures.
|
64
|
+
# Can't use `$HOME` in the constant though, as it won't be expanded by
|
65
|
+
# `expand_path` below. Can't win this game.
|
66
|
+
file.puts("plugin_cache_dir = \"#{DEFAULT_PLUGIN_CACHE_PATH.sub(/^~/, '$HOME')}\"")
|
52
67
|
|
53
68
|
dir = File.expand_path(DEFAULT_PLUGIN_CACHE_PATH)
|
54
69
|
return if File.directory?(dir)
|
data/lib/yle_tf/backend.rb
CHANGED
@@ -30,6 +30,11 @@ class YleTf
|
|
30
30
|
File.write(BACKEND_CONFIG_FILE, JSON.pretty_generate(data))
|
31
31
|
end
|
32
32
|
|
33
|
+
# Tear down the backend
|
34
|
+
def tear_down
|
35
|
+
# Nothing to do by default
|
36
|
+
end
|
37
|
+
|
33
38
|
# Returns the backend configuration as a `Hash` for Terraform
|
34
39
|
def to_h
|
35
40
|
{ type => backend_specific_config }
|
@@ -13,7 +13,10 @@ class YleTf
|
|
13
13
|
'backend' => {
|
14
14
|
'type' => 'file',
|
15
15
|
'file' => {
|
16
|
-
'path'
|
16
|
+
'path' => '<%= @module %>_<%= @env %>.tfstate',
|
17
|
+
'encrypt' => false,
|
18
|
+
'encrypt_command' => 'sops --encrypt --input-type binary --output-type binary --output "{{TO}}" "{{FROM}}"',
|
19
|
+
'decrypt_command' => 'sops --decrypt --input-type binary --output-type binary --output "{{TO}}" "{{FROM}}"'
|
17
20
|
},
|
18
21
|
's3' => {
|
19
22
|
'key' => '<%= @module %>_<%= @env %>.tfstate'
|
data/lib/yle_tf/version.rb
CHANGED
@@ -1,24 +1,83 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'fileutils'
|
3
4
|
require 'pathname'
|
5
|
+
require 'shellwords'
|
6
|
+
|
4
7
|
require 'yle_tf/backend'
|
5
8
|
require 'yle_tf/logger'
|
9
|
+
require 'yle_tf/system'
|
6
10
|
|
7
11
|
module YleTfPlugins
|
8
12
|
module Backends
|
9
13
|
module File
|
10
14
|
class Backend < YleTf::Backend
|
11
|
-
# Symlinks local "terraform.tfstate" to the specified path
|
12
15
|
def configure
|
16
|
+
if !encrypt?
|
17
|
+
create_tfstate(tfstate_path)
|
18
|
+
symlink_tfstate
|
19
|
+
elsif tfstate_path.exist?
|
20
|
+
decrypt_tfstate
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def tear_down
|
25
|
+
encrypt_tfstate if encrypt? && local_tfstate_path.exist?
|
26
|
+
end
|
27
|
+
|
28
|
+
def create_tfstate(path)
|
29
|
+
return if path.exist?
|
30
|
+
|
31
|
+
YleTf::Logger.debug('Creating state file')
|
32
|
+
path.write(tfstate_template, perm: 0o644)
|
33
|
+
end
|
34
|
+
|
35
|
+
def symlink_tfstate
|
13
36
|
YleTf::Logger.info("Symlinking state to '#{tfstate_path}'")
|
14
|
-
|
15
|
-
|
37
|
+
local_tfstate_path.make_symlink(tfstate_path)
|
38
|
+
end
|
16
39
|
|
17
|
-
|
40
|
+
def encrypt?
|
41
|
+
config.fetch('backend', type, 'encrypt')
|
42
|
+
end
|
43
|
+
|
44
|
+
def decrypt_tfstate
|
45
|
+
YleTf::Logger.info("Decrypting state from '#{tfstate_path}'")
|
46
|
+
|
47
|
+
cmd = config.fetch('backend', type, 'decrypt_command')
|
48
|
+
cmd.gsub!('{{FROM}}', tfstate_path.to_s)
|
49
|
+
cmd.gsub!('{{TO}}', local_tfstate_path.to_s)
|
50
|
+
|
51
|
+
# Split the command to have nicer logs
|
52
|
+
YleTf::System.cmd(*Shellwords.split(cmd))
|
53
|
+
end
|
54
|
+
|
55
|
+
def encrypt_tfstate
|
56
|
+
YleTf::Logger.info("Encrypting state to '#{tfstate_path}'")
|
57
|
+
|
58
|
+
cmd = config.fetch('backend', type, 'encrypt_command')
|
59
|
+
cmd.gsub!('{{FROM}}', local_tfstate_path.to_s)
|
60
|
+
cmd.gsub!('{{TO}}', tfstate_path.to_s)
|
61
|
+
|
62
|
+
YleTf::System.cmd(*Shellwords.split(cmd),
|
63
|
+
error_handler: method(:on_encrypt_error))
|
64
|
+
end
|
65
|
+
|
66
|
+
def on_encrypt_error(_exit_code, error)
|
67
|
+
plain_tfstate_path = "#{tfstate_path}.plaintext"
|
68
|
+
|
69
|
+
YleTf::Logger.warn("Copying unencrypted state to '#{plain_tfstate_path}'")
|
70
|
+
FileUtils.cp(local_tfstate_path.to_s, plain_tfstate_path)
|
71
|
+
|
72
|
+
raise error
|
18
73
|
end
|
19
74
|
|
20
75
|
def tfstate_path
|
21
|
-
@tfstate_path ||= config.module_dir.join(config.fetch('backend',
|
76
|
+
@tfstate_path ||= config.module_dir.join(config.fetch('backend', type, 'path'))
|
77
|
+
end
|
78
|
+
|
79
|
+
def local_tfstate_path
|
80
|
+
@local_tfstate_path ||= Pathname.pwd.join('terraform.tfstate')
|
22
81
|
end
|
23
82
|
|
24
83
|
def tfstate_template
|
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
require 'optparse'
|
4
4
|
|
5
|
-
require 'yle_tf/system'
|
6
5
|
require 'yle_tf/plugin'
|
7
6
|
|
8
7
|
module YleTfPlugins
|
@@ -20,18 +19,17 @@ module YleTfPlugins
|
|
20
19
|
o.banner = 'Usage: tf <environment> <command> [<args>]'
|
21
20
|
o.separator ''
|
22
21
|
o.separator 'YleTf options:'
|
23
|
-
o.on('-h', '--help',
|
22
|
+
o.on('-h', '--help', 'Prints this help')
|
24
23
|
o.on('-v', '--version', 'Prints the version information')
|
25
|
-
o.on('--debug',
|
26
|
-
o.on('--no-color',
|
27
|
-
o.on('--no-hooks',
|
28
|
-
o.on('--only-hooks',
|
24
|
+
o.on('--debug', 'Print debug information')
|
25
|
+
o.on('--no-color', 'Do not output with colors')
|
26
|
+
o.on('--no-hooks', 'Do not run any hooks')
|
27
|
+
o.on('--only-hooks', 'Only run the hooks')
|
29
28
|
o.separator ''
|
30
|
-
o.separator 'Special
|
29
|
+
o.separator 'Special YleTf commands:'
|
31
30
|
o.separator tf_command_help
|
32
31
|
o.separator ''
|
33
|
-
o.separator 'Terraform commands
|
34
|
-
o.separator terraform_help
|
32
|
+
o.separator 'Run `terraform -help` to get list of all Terraform commands.'
|
35
33
|
end
|
36
34
|
end
|
37
35
|
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
@@ -49,17 +47,6 @@ module YleTfPlugins
|
|
49
47
|
" #{command.ljust(18)} #{data[:synopsis]}"
|
50
48
|
end
|
51
49
|
end
|
52
|
-
|
53
|
-
def terraform_help
|
54
|
-
on_error = proc do |exit_code|
|
55
|
-
# exit_code is nil if the command was not found
|
56
|
-
# Ignore other exit codes, as older Terraform versions return
|
57
|
-
# non-zero exit codes for the help.
|
58
|
-
return ' [Terraform not found]' if exit_code.nil?
|
59
|
-
end
|
60
|
-
help = YleTf::System.read_cmd('terraform', '--help', error_handler: on_error)
|
61
|
-
help.lines.grep(/^ /)
|
62
|
-
end
|
63
50
|
end
|
64
51
|
end
|
65
52
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: yle_tf
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Yleisradio
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2021-01-27 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: thwait
|
@@ -182,7 +182,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
182
182
|
- !ruby/object:Gem::Version
|
183
183
|
version: '0'
|
184
184
|
requirements: []
|
185
|
-
rubygems_version: 3.
|
185
|
+
rubygems_version: 3.2.3
|
186
186
|
signing_key:
|
187
187
|
specification_version: 4
|
188
188
|
summary: Tooling for Terraform
|