terraspace 0.2.1 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +31 -0
  3. data/README.md +34 -13
  4. data/lib/templates/base/git_hook/hook.sh +1 -1
  5. data/lib/templates/base/project/.gitignore +1 -0
  6. data/lib/templates/base/project/README.md +17 -0
  7. data/lib/terraspace.rb +4 -0
  8. data/lib/terraspace/all/base.rb +8 -0
  9. data/lib/terraspace/all/grapher.rb +129 -0
  10. data/lib/terraspace/all/preview.rb +43 -0
  11. data/lib/terraspace/all/runner.rb +169 -0
  12. data/lib/terraspace/all/summary.rb +99 -0
  13. data/lib/terraspace/app.rb +31 -8
  14. data/lib/terraspace/booter.rb +9 -0
  15. data/lib/terraspace/builder.rb +65 -20
  16. data/lib/terraspace/cli.rb +39 -12
  17. data/lib/terraspace/cli/all.rb +63 -0
  18. data/lib/terraspace/cli/build/placeholder.rb +2 -5
  19. data/lib/terraspace/cli/bundle.rb +1 -1
  20. data/lib/terraspace/cli/check_setup.rb +17 -5
  21. data/lib/terraspace/cli/cloud.rb +19 -3
  22. data/lib/terraspace/cli/cloud/runs.rb +24 -0
  23. data/lib/terraspace/cli/commander.rb +1 -8
  24. data/lib/terraspace/cli/down.rb +20 -0
  25. data/lib/terraspace/cli/help/cloud/runs/list.md +36 -0
  26. data/lib/terraspace/cli/help/cloud/runs/prune.md +25 -0
  27. data/lib/terraspace/cli/help/cloud/sync.md +19 -0
  28. data/lib/terraspace/cli/help/log.md +42 -0
  29. data/lib/terraspace/cli/init.rb +35 -7
  30. data/lib/terraspace/cli/list.rb +14 -1
  31. data/lib/terraspace/cli/log.rb +112 -0
  32. data/lib/terraspace/cli/log/concern.rb +24 -0
  33. data/lib/terraspace/cli/logs.rb +15 -0
  34. data/lib/terraspace/cli/logs/tasks.rb +32 -0
  35. data/lib/terraspace/cli/new/git_hook.rb +1 -1
  36. data/lib/terraspace/cli/tfc_concern.rb +14 -0
  37. data/lib/terraspace/cli/up.rb +32 -0
  38. data/lib/terraspace/compiler/builder.rb +3 -3
  39. data/lib/terraspace/compiler/cleaner.rb +1 -1
  40. data/lib/terraspace/compiler/cleaner/backend_change.rb +21 -7
  41. data/lib/terraspace/compiler/dirs_concern.rb +47 -0
  42. data/lib/terraspace/compiler/dsl/syntax/helpers/common.rb +26 -1
  43. data/lib/terraspace/core.rb +11 -2
  44. data/lib/terraspace/dependency/graph.rb +139 -0
  45. data/lib/terraspace/dependency/node.rb +38 -0
  46. data/lib/terraspace/dependency/registry.rb +11 -0
  47. data/lib/terraspace/logger.rb +6 -18
  48. data/lib/terraspace/logger/formatter.rb +13 -0
  49. data/lib/terraspace/mod.rb +7 -1
  50. data/lib/terraspace/seeder/where.rb +6 -2
  51. data/lib/terraspace/shell.rb +79 -0
  52. data/lib/terraspace/terraform/api.rb +7 -40
  53. data/lib/terraspace/terraform/api/base.rb +7 -0
  54. data/lib/terraspace/terraform/api/client.rb +23 -3
  55. data/lib/terraspace/terraform/api/http.rb +14 -34
  56. data/lib/terraspace/terraform/api/http/concern.rb +10 -0
  57. data/lib/terraspace/terraform/api/runs.rb +28 -0
  58. data/lib/terraspace/terraform/api/token.rb +65 -0
  59. data/lib/terraspace/terraform/api/var.rb +20 -6
  60. data/lib/terraspace/terraform/api/vars.rb +2 -1
  61. data/lib/terraspace/terraform/api/workspace.rb +98 -0
  62. data/lib/terraspace/terraform/args/default.rb +48 -21
  63. data/lib/terraspace/terraform/cloud/runs.rb +13 -0
  64. data/lib/terraspace/terraform/cloud/runs/base.rb +33 -0
  65. data/lib/terraspace/terraform/cloud/runs/item_presenter.rb +37 -0
  66. data/lib/terraspace/terraform/cloud/runs/lister.rb +22 -0
  67. data/lib/terraspace/terraform/cloud/runs/pruner.rb +109 -0
  68. data/lib/terraspace/terraform/cloud/sync.rb +41 -0
  69. data/lib/terraspace/terraform/cloud/syncer.rb +52 -0
  70. data/lib/terraspace/terraform/cloud/workspace.rb +10 -21
  71. data/lib/terraspace/terraform/hooks/builder.rb +1 -1
  72. data/lib/terraspace/terraform/remote_state/fetcher.rb +122 -0
  73. data/lib/terraspace/terraform/remote_state/marker/output.rb +39 -0
  74. data/lib/terraspace/terraform/remote_state/marker/pretty_tracer.rb +37 -0
  75. data/lib/terraspace/terraform/remote_state/output_proxy.rb +29 -0
  76. data/lib/terraspace/terraform/runner.rb +24 -14
  77. data/lib/terraspace/util.rb +1 -5
  78. data/lib/terraspace/util/pretty.rb +18 -0
  79. data/lib/terraspace/version.rb +1 -1
  80. data/spec/fixtures/fetcher/c1.json +37 -0
  81. data/spec/fixtures/parser/cache_dirs/all/01-test.auto.tfvars +5 -0
  82. data/spec/fixtures/parser/cache_dirs/depends_on/01-test.auto.tfvars +2 -0
  83. data/spec/fixtures/parser/cache_dirs/output/01-test.auto.tfvars +2 -0
  84. data/spec/fixtures/summary/down.log +12 -0
  85. data/spec/fixtures/summary/output.log +5 -0
  86. data/spec/fixtures/summary/plan/error.log +20 -0
  87. data/spec/fixtures/summary/plan/success.log +17 -0
  88. data/spec/fixtures/summary/show.log +22 -0
  89. data/spec/fixtures/summary/up/error.log +13 -0
  90. data/spec/fixtures/summary/up/success.log +63 -0
  91. data/spec/fixtures/summary/validate/error.log +13 -0
  92. data/spec/fixtures/summary/validate/success.log +5 -0
  93. data/spec/terraspace/all/grapher_spec.rb +38 -0
  94. data/spec/terraspace/all/runner_spec.rb +48 -0
  95. data/spec/terraspace/all/summary_spec.rb +93 -0
  96. data/spec/terraspace/dependency/graph_spec.rb +162 -0
  97. data/spec/terraspace/seeder_spec.rb +0 -1
  98. data/spec/terraspace/terraform/remote_state/fetcher_spec.rb +52 -0
  99. data/terraspace.gemspec +5 -1
  100. metadata +137 -5
  101. data/lib/terraspace/terraform/cloud.rb +0 -25
  102. data/lib/terraspace/util/sh.rb +0 -19
@@ -16,7 +16,7 @@ module Terraspace::CLI::Build
16
16
  return if ENV['TS_SUMMARY_BUILD'] == '0'
17
17
 
18
18
  mod = @options[:mod]
19
- if !mod or mod == "placeholder"
19
+ if !mod or %w[placeholder].include?(mod)
20
20
  logger.info "Building one of the modules to get backend.tf info"
21
21
  mod = find_mod
22
22
  end
@@ -28,10 +28,7 @@ module Terraspace::CLI::Build
28
28
  def find_mod
29
29
  mod_path = Dir.glob("{app,vendor}/{modules,stacks}/*").last
30
30
  unless mod_path
31
- logger.info <<~EOL
32
- No modules or stacks found.
33
- Unable to determine the backend state path without at least one module.
34
- EOL
31
+ logger.info "No modules or stacks found."
35
32
  exit 0
36
33
  end
37
34
  File.basename(mod_path) # mod name
@@ -1,5 +1,5 @@
1
1
  require "terraspace-bundler"
2
- TerraspaceBundler.logger = Terraspace.logger
2
+ TerraspaceBundler.config.logger = Terraspace.logger
3
3
 
4
4
  class Terraspace::CLI
5
5
  class Bundle
@@ -23,17 +23,27 @@ class Terraspace::CLI
23
23
  end
24
24
 
25
25
  def check_required_version!
26
- puts "Terraspace requires Terraform v#{REQUIRED_TERRAFORM_VERSION}.x"
26
+ puts "Terraspace requires Terraform v#{REQUIRED_TERRAFORM_VERSION}.x and above"
27
27
  if ok?
28
28
  puts "You're all set!"
29
29
  else
30
- puts "The installed version of terraform may not work with terraspace. Recommend using terraform v#{REQUIRED_TERRAFORM_VERSION}.x"
31
- exit 1
30
+ puts "The installed version of terraform may not work with terraspace."
31
+ puts "Recommend using at least terraform v#{REQUIRED_TERRAFORM_VERSION}.x"
32
+ puts "If you would like to bypass this check. Use TS_VERSION_CHECK=0" unless check_command?
33
+ exit 1 unless ENV['TS_VERSION_CHECK'] == '0'
32
34
  end
33
35
  end
34
36
 
37
+ def check_command?
38
+ ARGV[0] == "check_setup"
39
+ end
40
+
35
41
  def ok?
36
42
  version = terraform_version_message.sub(/.*v/,'') # => 0.12.24
43
+ unless version.match(/\d+\.\d+\.\d+/) # just parse did not find the version number
44
+ puts "WARN: Unable to get the terraform version".color(:yellow)
45
+ return true
46
+ end
37
47
  major, minor, _ = version.split('.')
38
48
  required_major, required_minor = REQUIRED_TERRAFORM_VERSION.split('.')
39
49
  x = major.to_i >= required_major.to_i
@@ -47,7 +57,8 @@ class Terraspace::CLI
47
57
  end
48
58
  memoize :terraform_bin
49
59
 
50
- # First line contains the Terraform version info:
60
+ # Sometimes Terraform shows the version info on the first line and sometimes on the bottom line.
61
+ # Account for that by finding the line.
51
62
  #
52
63
  # $ terraform --version
53
64
  # Terraform v0.12.24
@@ -55,8 +66,9 @@ class Terraspace::CLI
55
66
  # Your version of Terraform is out of date! The latest version
56
67
  # is 0.12.26. You can update by downloading from https://www.terraform.io/downloads.html
57
68
  #
69
+ # Note: The -json option is only available in v0.13+
58
70
  def terraform_version_message
59
- `terraform --version`.split("\n").first.strip
71
+ `terraform --version`.split("\n").find { |l| l =~ /^Terraform / }.strip
60
72
  end
61
73
  memoize :terraform_version_message
62
74
 
@@ -1,24 +1,40 @@
1
1
  class Terraspace::CLI
2
2
  class Cloud < Terraspace::Command
3
+ Syncer = Terraspace::Terraform::Cloud::Syncer
3
4
  Workspace = Terraspace::Terraform::Cloud::Workspace
4
5
 
6
+ yes_option = Proc.new {
7
+ option :yes, aliases: :y, type: :boolean, desc: "bypass are you sure prompt"
8
+ }
9
+
5
10
  desc "list", "List workspaces"
6
11
  long_desc Help.text("cloud:list")
12
+ yes_option.call
7
13
  def list
8
14
  Workspace.new(options).list
9
15
  end
10
16
 
11
- desc "destroy", "Destroy workspace"
17
+ desc "destroy STACK", "Destroy workspace by specifying the stack"
12
18
  long_desc Help.text("cloud:destroy")
13
- option :yes, aliases: :y, type: :boolean, desc: "bypass are you sure prompt"
14
19
  def destroy(mod)
15
20
  Workspace.new(options.merge(mod: mod)).destroy
16
21
  end
17
22
 
18
- desc "setup", "Setup workspace"
23
+ desc "setup STACK", "Setup workspace"
19
24
  long_desc Help.text("cloud:setup")
20
25
  def setup(mod)
21
26
  Workspace.new(options.merge(mod: mod)).setup
22
27
  end
28
+
29
+ desc "sync [STACK]", "sync workspace"
30
+ long_desc Help.text("cloud:sync")
31
+ yes_option.call
32
+ def sync(mod=nil)
33
+ Syncer.new(options.merge(mod: mod, override_auto_sync: true)).run
34
+ end
35
+
36
+ desc "runs SUBCOMMAND", "runs subcommands"
37
+ long_desc Help.text(:runs)
38
+ subcommand "runs", Runs
23
39
  end
24
40
  end
@@ -0,0 +1,24 @@
1
+ require 'cli-format'
2
+
3
+ class Terraspace::CLI::Cloud
4
+ class Runs < Terraspace::Command
5
+ Help = Terraspace::CLI::Help
6
+ Runs = Terraspace::Terraform::Cloud::Runs
7
+
8
+ desc "list STACK", "List runs."
9
+ long_desc Help.text("cloud:runs:list")
10
+ option :format, desc: "Output formats: #{CliFormat.formats.join(', ')}"
11
+ option :status, default: %w[pending planned], type: :array, desc: "Filter by statuses: pending, planned, all"
12
+ def list(mod)
13
+ Runs.new(options.merge(mod: mod)).list
14
+ end
15
+
16
+ desc "prune STACK", "Prune runs that are possible to cancel or discard."
17
+ long_desc Help.text("cloud:runs:prune")
18
+ option :noop, type: :boolean, desc: "Shows what would be cancelled/discarded."
19
+ option :yes, aliases: :y, type: :boolean, desc: "bypass are you sure prompt"
20
+ def prune(mod)
21
+ Runs.new(options.merge(mod: mod)).prune
22
+ end
23
+ end
24
+ end
@@ -5,17 +5,10 @@ class Terraspace::CLI
5
5
  super(options)
6
6
  end
7
7
 
8
- # Commander always runs Build#run
9
8
  def run
10
- Terraspace::Builder.new(@options).run # generate and init
11
- auto_create_backend
9
+ Terraspace::Builder.new(@options).run unless @options[:build] # Up already ran build
12
10
  Init.new(@options.merge(calling_command: @name)).run
13
11
  Terraspace::Terraform::Runner.new(@name, @options).run
14
12
  end
15
-
16
- def auto_create_backend
17
- return unless @name == "apply"
18
- Terraspace::Compiler::Backend.new(@mod).create
19
- end
20
13
  end
21
14
  end
@@ -0,0 +1,20 @@
1
+ class Terraspace::CLI
2
+ class Down < Base
3
+ include TfcConcern
4
+
5
+ def run
6
+ plan if @options[:yes] && !tfc?
7
+ destroy
8
+ end
9
+
10
+ private
11
+ def plan
12
+ Commander.new("plan", @options.merge(destroy: true)).run
13
+ end
14
+
15
+ def destroy
16
+ Commander.new("destroy", @options.merge(command: "down")).run
17
+ Terraspace::Terraform::Cloud::Workspace.new(@options).destroy if @options[:destroy_workspace]
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,36 @@
1
+ ## Example
2
+
3
+ Statuses of pending and planned are shown by default.
4
+
5
+ $ terraspace cloud runs list pet
6
+ +----------------------+---------+---------------------------------+---------------------+
7
+ | Id | Status | Message | Created At |
8
+ +----------------------+---------+---------------------------------+---------------------+
9
+ | run-AuTXsYU1svQEzQVg | pending | Queued manually using Terraform | 2020-09-18T11:30:41 |
10
+ | run-LuwMibh3ebiG7KQZ | planned | test | 2020-09-17T23:16:36 |
11
+ +----------------------+---------+---------------------------------+---------------------+
12
+ $
13
+
14
+ To see all most recent runs, use `--status all`.
15
+
16
+ $ terraspace cloud runs list pet --status all
17
+ +----------------------+-----------+--------------------------------+---------------------+
18
+ | Id | Status | Message | Created At |
19
+ +----------------------+-----------+--------------------------------+---------------------+
20
+ | run-LuwMibh3ebiG7KQZ | planned | test 3 | 2020-09-17T23:16:36 |
21
+ | run-z9f67TNMRamZiGMR | canceled | test 2 | 2020-09-17T23:15:55 |
22
+ | run-cN3CKT5po29p35Ta | discarded | test | 2020-09-17T22:24:31 |
23
+ | run-rXMd7dm3fHvVsA36 | discarded | Queued from Terraform Cloud UI | 2020-09-17T20:00:20 |
24
+ +----------------------+-----------+--------------------------------+---------------------+
25
+ $
26
+
27
+ You can provide a list of statuses to the `--status` filter option.
28
+
29
+ $ terraspace cloud runs list pet --status canceled discarded
30
+ +----------------------+-----------+--------------------------------+---------------------+
31
+ | Id | Status | Message | Created At |
32
+ +----------------------+-----------+--------------------------------+---------------------+
33
+ | run-gS2m1avc3U4j1fip | canceled | test | 2020-09-17T23:15:43 |
34
+ | run-ojwQ4r7MxuzyK3d9 | discarded | test | 2020-09-17T22:33:00 |
35
+ | run-dczeLQsMc3ya4XnY | canceled | test | 2020-09-17T22:32:55 |
36
+ +----------------------+-----------+--------------------------------+---------------------+
@@ -0,0 +1,25 @@
1
+ This leaves the to most recent run alone. The top run will immediately start planning. Runs that are also in "Needs Confirmation" will be cancelled.
2
+
3
+ ## Examples
4
+
5
+ terraspace cloud runs prune demo --noop
6
+ terraspace cloud runs prune demo # live run
7
+
8
+ ## Example with Output
9
+
10
+ $ terraspace cloud runs prune pet
11
+ Will keep:
12
+
13
+ run-9muMrjrd22vhsP4u pending test 2020-09-18T12:47:11
14
+
15
+ Will prune:
16
+
17
+ run-fYTDzmKfCQf558UN pending test 2020-09-18T12:46:34
18
+ run-6bgSTattJGpaRn9X pending test 2020-09-18T12:46:28
19
+ run-jDHEtZb3vuFnuXqJ planned test 2020-09-18T12:38:35
20
+
21
+ Are you sure? (y/N) y
22
+ Cancelled run-fYTDzmKfCQf558UN test
23
+ Cancelled run-6bgSTattJGpaRn9X test
24
+ Discarded run-jDHEtZb3vuFnuXqJ test
25
+ $
@@ -0,0 +1,19 @@
1
+ ## Example
2
+
3
+ $ terraspace cloud sync
4
+ About to sync these project stacks with Terraform Cloud workspaces:
5
+
6
+ Stack => Workspace
7
+ demo => demo-dev-us-west-2
8
+ demo2 => demo2-dev-us-west-2
9
+
10
+ A sync does the following for each workspace:
11
+
12
+ 1. Create or update workspace, including the VCS settings.
13
+ 2. Set the working dir.
14
+ 3. Set env and terraform variables.
15
+
16
+ Are you sure? (y/N) y
17
+ Syncing to Terraform Cloud: demo => demo-dev-us-west-2
18
+ Syncing to Terraform Cloud: demo2 => demo2-dev-us-west-2
19
+ $
@@ -0,0 +1,42 @@
1
+ The log commands will filter out the logs for the last ran terraspace command. It does this by filtering for the last found PID in the log files.
2
+
3
+ ## Quick Start
4
+
5
+ Follow all the logs as you're running `terraspace all up`:
6
+
7
+ terraspace log -f
8
+
9
+ ## View Logs
10
+
11
+ View last 10 lines of each log file.
12
+
13
+ terraspace log up network # view up log on specific stack
14
+ terraspace log up # view all up logs
15
+ terraspace log down # view all down logs
16
+ terraspace log # view all logs: up, down, etc
17
+
18
+ By default, the log command shows the last 10 lines of the logs for each log file. You can use the `-n` option to adjust this.
19
+
20
+ terraspace log -n 2 # view last 2 lines of all logs: up, down, etc
21
+
22
+ To show all logs, use the `-a` option.
23
+
24
+ terraspace log up -a
25
+
26
+ Note, if both an action and stack is specified, then it defaults to showing all logs. If you want to not show all logs in thta case, then you can use `--no-all`.
27
+
28
+ ## Tail Logs
29
+
30
+ To tail logs, use the `-f` option.
31
+
32
+ terraspace log up network -f # view up log on specific stack
33
+ terraspace log up -f # view all up logs
34
+ terraspace log down -f # view all down logs
35
+ terraspace log -f # view all logs: up, down, etc
36
+
37
+ ## Timestamps
38
+
39
+ The timestamps are shown by default when you are looking for multiple files. When you specify a both the action and stack for a single log file, then timestamps are not shown.
40
+
41
+ terraspace log up # timestamps will be shown in this case
42
+ terraspace log up network # timestamps not be shown in this case
@@ -10,13 +10,14 @@ class Terraspace::CLI
10
10
  end
11
11
 
12
12
  def run
13
- init if init?
14
- build_remote_dependencies # runs after terraform init, which downloads remote modules
13
+ init
14
+ # build_remote_dependencies # runs after terraform init, which downloads remote modules
15
15
  sync_cloud
16
16
  end
17
17
 
18
18
  # Note the init will always create the Terraform Cloud Workspace
19
19
  def init
20
+ return unless run_init? # check here because RemoteState::Fetcher#pull calls init directly
20
21
  # default init timeout is pretty generous in case of slow internet to download the provider plugins
21
22
  init_timeout = Integer(ENV['TS_INIT_TIMEOUT'] || 600)
22
23
  Timeout::timeout(init_timeout) do
@@ -28,7 +29,7 @@ class Terraspace::CLI
28
29
  end
29
30
 
30
31
  def sync_cloud
31
- Terraspace::Terraform::Cloud.new(@options).run if %w[apply plan destroy cloud-setup].include?(@calling_command)
32
+ Terraspace::Terraform::Cloud::Sync.new(@options).run if %w[apply plan destroy cloud-setup].include?(@calling_command)
32
33
  end
33
34
 
34
35
  # Currently only handles remote modules only one-level deep.
@@ -47,8 +48,8 @@ class Terraspace::CLI
47
48
  return if local_source?(meta["Source"])
48
49
  return if meta['Dir'] == '.' # root is already built
49
50
 
50
- remote_mod = Mod::Remote.new(meta, @mod)
51
- Compiler::Builder.new(remote_mod).build
51
+ remote_mod = Terraspace::Mod::Remote.new(meta, @mod)
52
+ Terraspace::Compiler::Builder.new(remote_mod).build
52
53
  end
53
54
 
54
55
  def auto?
@@ -60,8 +61,35 @@ class Terraspace::CLI
60
61
  s =~ %r{^\.} || s =~ %r{^/}
61
62
  end
62
63
 
63
- def init?
64
- %w[apply console destroy output plan providers refresh show validate cloud-setup].include?(@calling_command)
64
+ def run_init?
65
+ commands = %w[apply console destroy output plan providers refresh show validate cloud-setup]
66
+ return false unless commands.include?(@calling_command)
67
+ mode = ENV['TS_INIT_MODE'] || Terraspace.config.init.mode
68
+ case mode.to_sym
69
+ when :auto
70
+ !already_initialized?
71
+ when :always
72
+ true
73
+ when :never
74
+ false
75
+ end
76
+ end
77
+
78
+ # Would like to improve this detection
79
+ #
80
+ # Traverse symlink dirs also: linux_amd64 is a symlink
81
+ # plugins/registry.terraform.io/hashicorp/google/3.39.0/linux_amd64/terraform-provider-google_v3.39.0_x5
82
+ #
83
+ # Check modules/modules.json also because during the tfvars dependency pass main.tf modules are not built.
84
+ # So init happens again during the second pass.
85
+ #
86
+ def already_initialized?
87
+ terraform = "#{@mod.cache_dir}/.terraform"
88
+ provider = Dir.glob("#{terraform}/**{,/*/**}/*").find do |path|
89
+ path.include?("terraform-provider-")
90
+ end
91
+ modules = File.exist?("#{terraform}/modules/modules.json")
92
+ !!(provider && modules)
65
93
  end
66
94
  end
67
95
  end
@@ -2,11 +2,24 @@ class Terraspace::CLI
2
2
  class List
3
3
  def initialize(options={})
4
4
  @options = options
5
+ @type_dir = normalized_type
5
6
  end
6
7
 
7
8
  def run
8
9
  Dir.glob("{app,vendor}/{modules,stacks}/*").sort.each do |path|
9
- puts path
10
+ if @type_dir
11
+ puts path if path.include?("/#{@type_dir}/")
12
+ else
13
+ puts path
14
+ end
15
+ end
16
+ end
17
+
18
+ private
19
+ def normalized_type
20
+ type = @options[:type]
21
+ if %w[stack module].include?(type)
22
+ type.pluralize
10
23
  end
11
24
  end
12
25
  end
@@ -0,0 +1,112 @@
1
+ require "eventmachine"
2
+ require "eventmachine-tail"
3
+
4
+ class Terraspace::CLI
5
+ class Log < Base
6
+ include Log::Concern
7
+
8
+ def initialize(options={})
9
+ super
10
+ @action, @stack = options[:action], options[:stack]
11
+ @action ||= '**'
12
+ @stack ||= '*'
13
+ end
14
+
15
+ def run
16
+ check_logs!
17
+ if @options[:follow]
18
+ follow_logs
19
+ else
20
+ all_log_paths.each { |path| show_log(path) }
21
+ end
22
+ end
23
+
24
+ def follow_logs
25
+ glob_path = "#{Terraspace.log_root}/#{@action}/#{@stack}.log"
26
+ Dir.glob(glob_path).each do |path|
27
+ puts "Following #{pretty(path)}".color(:purple)
28
+ end
29
+ EventMachine.run do
30
+ interval = Integer(ENV['TS_LOG_GLOB_INTERNAL'] || 1)
31
+ EventMachine::FileGlobWatchTail.new(glob_path, nil, interval) do |filetail, line|
32
+ puts line # always show timestamp in follow mode
33
+ end
34
+ end
35
+ end
36
+
37
+ def show_log(path)
38
+ report_log(path)
39
+ lines = readlines(path)
40
+ lines = apply_limit(lines)
41
+ lines.each do |line|
42
+ puts format(line)
43
+ end
44
+ end
45
+
46
+ def report_log(path)
47
+ pretty_path = pretty(path)
48
+ if File.exist?(path)
49
+ puts "Showing: #{pretty_path}".color(:purple)
50
+ end
51
+ end
52
+
53
+ def format(line)
54
+ if timestamps
55
+ line
56
+ else
57
+ line.sub(/.*\]: /,'')
58
+ end
59
+ end
60
+
61
+ def all_log_paths
62
+ Dir.glob("#{Terraspace.log_root}/#{@action}/#{@stack}.log")
63
+ end
64
+
65
+ def check_logs!
66
+ return unless all_log_paths.empty?
67
+ puts "WARN: No logs found".color(:yellow)
68
+ end
69
+
70
+ # Only need to check if both action and stack are provided. Otherwise the Dir.globs are used to discover the files
71
+ def check_log!
72
+ return unless single_log?
73
+ path = "#{Terraspace.log_root}/#{@action}/#{@stack}.log"
74
+ return if File.exist?(path)
75
+ puts "ERROR: Log file was not found: #{pretty(path)}".color(:red)
76
+ exit 1
77
+ end
78
+
79
+ def single_log?
80
+ @action != '**' && @stack != '*'
81
+ end
82
+
83
+ def apply_limit(lines)
84
+ return lines if all
85
+ left = limit * -1
86
+ lines[left..-1] || []
87
+ end
88
+
89
+ def all
90
+ if single_log?
91
+ @options[:all].nil? ? true : @options[:all]
92
+ else # multiple
93
+ @options[:all].nil? ? false : @options[:all]
94
+ end
95
+ end
96
+
97
+ def limit
98
+ @options[:limit].nil? ? 10 : @options[:limit]
99
+ end
100
+
101
+ def timestamps
102
+ if single_log?
103
+ @options[:timestamps].nil? ? false : @options[:timestamps]
104
+ else
105
+ @options[:timestamps].nil? ? true : @options[:timestamps]
106
+ end
107
+ end
108
+ def pretty(path)
109
+ Terraspace::Util.pretty_path(path)
110
+ end
111
+ end
112
+ end