krane 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (100) hide show
  1. checksums.yaml +7 -0
  2. data/.buildkite/pipeline.nightly.yml +43 -0
  3. data/.github/probots.yml +2 -0
  4. data/.gitignore +20 -0
  5. data/.rubocop.yml +17 -0
  6. data/.shopify-build/VERSION +1 -0
  7. data/.shopify-build/kubernetes-deploy.yml +53 -0
  8. data/1.0-Upgrade.md +186 -0
  9. data/CHANGELOG.md +431 -0
  10. data/CODE_OF_CONDUCT.md +46 -0
  11. data/CONTRIBUTING.md +164 -0
  12. data/Gemfile +16 -0
  13. data/ISSUE_TEMPLATE.md +25 -0
  14. data/LICENSE.txt +21 -0
  15. data/README.md +655 -0
  16. data/Rakefile +36 -0
  17. data/bin/ci +21 -0
  18. data/bin/setup +16 -0
  19. data/bin/test +47 -0
  20. data/dev.yml +28 -0
  21. data/dev/flamegraph-from-tests +35 -0
  22. data/exe/krane +5 -0
  23. data/krane.gemspec +44 -0
  24. data/lib/krane.rb +7 -0
  25. data/lib/krane/bindings_parser.rb +88 -0
  26. data/lib/krane/cli/deploy_command.rb +75 -0
  27. data/lib/krane/cli/global_deploy_command.rb +54 -0
  28. data/lib/krane/cli/krane.rb +91 -0
  29. data/lib/krane/cli/render_command.rb +41 -0
  30. data/lib/krane/cli/restart_command.rb +34 -0
  31. data/lib/krane/cli/run_command.rb +54 -0
  32. data/lib/krane/cli/version_command.rb +13 -0
  33. data/lib/krane/cluster_resource_discovery.rb +113 -0
  34. data/lib/krane/common.rb +23 -0
  35. data/lib/krane/concerns/template_reporting.rb +29 -0
  36. data/lib/krane/concurrency.rb +18 -0
  37. data/lib/krane/container_logs.rb +106 -0
  38. data/lib/krane/deferred_summary_logging.rb +95 -0
  39. data/lib/krane/delayed_exceptions.rb +14 -0
  40. data/lib/krane/deploy_task.rb +363 -0
  41. data/lib/krane/deploy_task_config_validator.rb +29 -0
  42. data/lib/krane/duration_parser.rb +27 -0
  43. data/lib/krane/ejson_secret_provisioner.rb +156 -0
  44. data/lib/krane/errors.rb +28 -0
  45. data/lib/krane/formatted_logger.rb +57 -0
  46. data/lib/krane/global_deploy_task.rb +210 -0
  47. data/lib/krane/global_deploy_task_config_validator.rb +12 -0
  48. data/lib/krane/kubeclient_builder.rb +156 -0
  49. data/lib/krane/kubectl.rb +120 -0
  50. data/lib/krane/kubernetes_resource.rb +621 -0
  51. data/lib/krane/kubernetes_resource/cloudsql.rb +43 -0
  52. data/lib/krane/kubernetes_resource/config_map.rb +22 -0
  53. data/lib/krane/kubernetes_resource/cron_job.rb +18 -0
  54. data/lib/krane/kubernetes_resource/custom_resource.rb +87 -0
  55. data/lib/krane/kubernetes_resource/custom_resource_definition.rb +98 -0
  56. data/lib/krane/kubernetes_resource/daemon_set.rb +90 -0
  57. data/lib/krane/kubernetes_resource/deployment.rb +213 -0
  58. data/lib/krane/kubernetes_resource/horizontal_pod_autoscaler.rb +65 -0
  59. data/lib/krane/kubernetes_resource/ingress.rb +18 -0
  60. data/lib/krane/kubernetes_resource/job.rb +60 -0
  61. data/lib/krane/kubernetes_resource/network_policy.rb +22 -0
  62. data/lib/krane/kubernetes_resource/persistent_volume_claim.rb +80 -0
  63. data/lib/krane/kubernetes_resource/pod.rb +269 -0
  64. data/lib/krane/kubernetes_resource/pod_disruption_budget.rb +23 -0
  65. data/lib/krane/kubernetes_resource/pod_set_base.rb +71 -0
  66. data/lib/krane/kubernetes_resource/pod_template.rb +20 -0
  67. data/lib/krane/kubernetes_resource/replica_set.rb +92 -0
  68. data/lib/krane/kubernetes_resource/resource_quota.rb +22 -0
  69. data/lib/krane/kubernetes_resource/role.rb +22 -0
  70. data/lib/krane/kubernetes_resource/role_binding.rb +22 -0
  71. data/lib/krane/kubernetes_resource/secret.rb +24 -0
  72. data/lib/krane/kubernetes_resource/service.rb +104 -0
  73. data/lib/krane/kubernetes_resource/service_account.rb +22 -0
  74. data/lib/krane/kubernetes_resource/stateful_set.rb +70 -0
  75. data/lib/krane/label_selector.rb +42 -0
  76. data/lib/krane/oj.rb +4 -0
  77. data/lib/krane/options_helper.rb +39 -0
  78. data/lib/krane/remote_logs.rb +60 -0
  79. data/lib/krane/render_task.rb +118 -0
  80. data/lib/krane/renderer.rb +118 -0
  81. data/lib/krane/resource_cache.rb +68 -0
  82. data/lib/krane/resource_deployer.rb +265 -0
  83. data/lib/krane/resource_watcher.rb +171 -0
  84. data/lib/krane/restart_task.rb +228 -0
  85. data/lib/krane/rollout_conditions.rb +103 -0
  86. data/lib/krane/runner_task.rb +212 -0
  87. data/lib/krane/runner_task_config_validator.rb +18 -0
  88. data/lib/krane/statsd.rb +65 -0
  89. data/lib/krane/task_config.rb +22 -0
  90. data/lib/krane/task_config_validator.rb +96 -0
  91. data/lib/krane/template_sets.rb +173 -0
  92. data/lib/krane/version.rb +4 -0
  93. data/pull_request_template.md +8 -0
  94. data/screenshots/deploy-demo.gif +0 -0
  95. data/screenshots/migrate-logs.png +0 -0
  96. data/screenshots/missing-secret-fail.png +0 -0
  97. data/screenshots/success.png +0 -0
  98. data/screenshots/test-output.png +0 -0
  99. data/shipit.yml +4 -0
  100. metadata +376 -0
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+ require "bundler/gem_tasks"
3
+ require "rake/testtask"
4
+
5
+ desc("Run integration tests that can be run in parallel")
6
+ Rake::TestTask.new(:integration_test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList['test/integration/**/*_test.rb']
10
+ end
11
+
12
+ desc("Run integration tests that CANNOT be run in parallel")
13
+ Rake::TestTask.new(:serial_integration_test) do |t|
14
+ t.libs << "test"
15
+ t.libs << "lib"
16
+ t.test_files = FileList['test/integration-serial/**/*_test.rb']
17
+ end
18
+
19
+ desc("Run unit tests")
20
+ Rake::TestTask.new(:unit_test) do |t|
21
+ t.libs << "test"
22
+ t.libs << "lib"
23
+ t.test_files = FileList['test/unit/**/*_test.rb']
24
+ end
25
+
26
+ desc("Run cli tests")
27
+ Rake::TestTask.new(:cli_test) do |t|
28
+ t.libs << "test"
29
+ t.libs << "lib"
30
+ t.test_files = FileList['test/exe/**/*_test.rb']
31
+ end
32
+
33
+ desc("Run all tests")
34
+ task(test: %w(unit_test serial_integration_test integration_test cli_test))
35
+
36
+ task(default: :test)
data/bin/ci ADDED
@@ -0,0 +1,21 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ if [[ -n "${DEBUG:+set}" ]]; then
5
+ set -x
6
+ fi
7
+
8
+ docker run --rm \
9
+ --net=host \
10
+ -v "$HOME/.kube":"/root/.kube" \
11
+ -v "$HOME/.minikube":"$HOME/.minikube" \
12
+ -v "$PWD":/usr/src/app \
13
+ -v "/usr/bin/kubectl":"/usr/bin/kubectl" \
14
+ -e CI=1 \
15
+ -e CODECOV_TOKEN=$CODECOV_TOKEN \
16
+ -e COVERAGE=1 \
17
+ -e VERBOSE=1 \
18
+ -e PARALLELISM=$PARALLELISM \
19
+ -w /usr/src/app \
20
+ ruby:2.4 \
21
+ bin/test
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ if [ ! -x "$(which minikube)" ]; then
8
+ echo -e "\n\033[0;33mIf you're going to run the tests, please follow the minikube setup instructions for your operating system:\nhttps://kubernetes.io/docs/getting-started-guides/minikube/#installation\033[0m"
9
+ fi
10
+
11
+ if [ ! -x "$(which kubectl)" ]; then
12
+ echo -e "\n\033[0;33mPlease install kubectl version 1.11.0 or higher:\nhttps://kubernetes.io/docs/user-guide/prereqs/\033[0m"
13
+ else
14
+ KUBECTL_VERSION=$(kubectl version --short --client | grep -oe "v[[:digit:]\.]\+")
15
+ echo -e "\n\033[0;32mKubectl version $KUBECTL_VERSION is already installed. This gem requires version v1.11.0 or greater.\033[0m"
16
+ fi
@@ -0,0 +1,47 @@
1
+ #!/bin/bash
2
+ set -uo pipefail
3
+
4
+ err=0
5
+ trap 'err=1' ERR
6
+ trap 'exit 1' SIGINT
7
+
8
+ function print_header() {
9
+ if [[ ${CI:="0"} == "1" ]]; then
10
+ printf "+++ :kubernetes: %s\n" "${1}"
11
+ else
12
+ printf "\n\n\033[0;35m⎈ %s\033[0m\n" "${1}"
13
+ fi
14
+ }
15
+
16
+ if [[ ${PARALLELISM:=0} -lt 1 ]]; then
17
+ if [[ $(uname) == "Darwin" ]]; then
18
+ num_cpus=$(sysctl -n hw.ncpu)
19
+ else
20
+ num_cpus=$(nproc --all)
21
+ fi
22
+
23
+ if [[ $num_cpus -le 2 ]]; then
24
+ PARALLELISM=1
25
+ else
26
+ (( PARALLELISM=num_cpus/2 ))
27
+ fi
28
+ fi
29
+
30
+ if [[ ${CI:="0"} == "1" ]]; then
31
+ echo "--- :ruby: Bundle Install"
32
+ bundle install --jobs 4
33
+ fi
34
+
35
+ print_header "Run CLI Tests"
36
+ bundle exec rake cli_test
37
+
38
+ print_header "Run Unit Tests"
39
+ bundle exec rake unit_test
40
+
41
+ print_header "Run Non-Parallel Integration Tests"
42
+ bundle exec rake serial_integration_test
43
+
44
+ print_header "Run Parallel Integration Tests (N=$PARALLELISM)"
45
+ PARALLELIZE_ME=1 N=$PARALLELISM bundle exec rake integration_test
46
+
47
+ test $err -eq 0
data/dev.yml ADDED
@@ -0,0 +1,28 @@
1
+ ---
2
+ name: kubernetes-deploy
3
+ up:
4
+ - ruby: 2.4.6 # Matches gemspec
5
+ - bundler
6
+ - homebrew:
7
+ - Caskroom/cask/minikube
8
+ - custom:
9
+ name: Install the minikube fork of driver-hyperkit
10
+ met?: command -v docker-machine-driver-hyperkit
11
+ meet: curl -LO https://storage.googleapis.com/minikube/releases/latest/docker-machine-driver-hyperkit && sudo install -o root -g wheel -m 4755 docker-machine-driver-hyperkit /usr/local/bin/ && rm ./docker-machine-driver-hyperkit
12
+ - custom:
13
+ name: Minikube Cluster
14
+ met?: test $(minikube status | grep Running | wc -l) -ge 2 && $(minikube status | grep -q 'Correctly Configured')
15
+ meet: minikube start --kubernetes-version=v1.11.6 --vm-driver=hyperkit
16
+ down: minikube stop
17
+ commands:
18
+ reset-minikube: minikube delete && rm -rf ~/.minikube
19
+ test:
20
+ run: bin/test
21
+ tophat:
22
+ run: PRINT_LOGS=1 bundle exec ruby -I test test/integration/krane_deploy_test.rb -n/${1}/
23
+ desc: Tophat a change by running a test scenario with logging output enabled.
24
+ syntax:
25
+ optional:
26
+ argument: TEST_REGEX
27
+ doc:
28
+ run: bundle exec yard doc
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env bash
2
+ SVG_FILENAME="dev/flamegraph.svg"
3
+ PROFILE_FILENAME="dev/profile"
4
+ FLAMEGRAPH_PL="dev/flamegraph.pl"
5
+
6
+ if [[ -z $1 ]]; then
7
+ echo "Usage: `basename "$0"` TEST_NAME_OR_REGEX"
8
+ exit 1
9
+ fi
10
+
11
+ if ! [[ -s $FLAMEGRAPH_PL ]]; then
12
+ echo "Downloading flamegraph.pl"
13
+ curl -Lo $FLAMEGRAPH_PL https://raw.githubusercontent.com/brendangregg/FlameGraph/master/flamegraph.pl
14
+ fi
15
+
16
+ if [[ -f $SVG_FILENAME ]]; then
17
+ rm $SVG_FILENAME
18
+ fi
19
+
20
+ if [[ -f $PROFILE_FILENAME ]]; then
21
+ rm $PROFILE_FILENAME
22
+ fi
23
+
24
+ echo "Running test(s) with profiling"
25
+ PROFILE=1 bundle exec ruby -I test test/integration/krane_deploy_test.rb -n /$1/ > /dev/null
26
+
27
+ echo "Processing profile"
28
+ cat $PROFILE_FILENAME | perl -w $FLAMEGRAPH_PL --countname=ms --width=1500 --title=$1 > $SVG_FILENAME
29
+
30
+ if [[ -f $SVG_FILENAME ]]; then
31
+ echo "Done. Opening ${SVG_FILENAME}"
32
+ open $SVG_FILENAME
33
+ else
34
+ echo "Done, but ${SVG_FILENAME} does not exist. Something went wrong."
35
+ fi
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'krane/cli/krane'
5
+ Krane::CLI::Krane.start(ARGV)
@@ -0,0 +1,44 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'krane/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "krane"
9
+ spec.version = Krane::VERSION
10
+ spec.authors = ["Katrina Verey", "Daniel Turner", "Kir Shatrov"]
11
+ spec.email = ["ops-accounts+shipit@shopify.com"]
12
+
13
+ spec.summary = 'A command line tool that helps you ship changes to a Kubernetes' \
14
+ ' namespace and understand the result'
15
+ spec.description = spec.summary
16
+ spec.homepage = "https://github.com/Shopify/krane"
17
+ spec.license = "MIT"
18
+
19
+ spec.files = %x(git ls-files -z).split("\x0").reject do |f|
20
+ f.match(%r{^(test|spec|features)/})
21
+ end
22
+ spec.bindir = "exe"
23
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
24
+ spec.require_paths = %w(lib)
25
+
26
+ spec.required_ruby_version = '>= 2.4.0'
27
+ spec.add_dependency("activesupport", ">= 5.0")
28
+ spec.add_dependency("kubeclient", "~> 4.3")
29
+ spec.add_dependency("googleauth", "~> 0.8.0")
30
+ spec.add_dependency("ejson", "~> 1.0")
31
+ spec.add_dependency("colorize", "~> 0.8")
32
+ spec.add_dependency("statsd-instrument", ['>= 2.8', "< 3.1"])
33
+ spec.add_dependency("oj", "~> 3.0")
34
+ spec.add_dependency("concurrent-ruby", "~> 1.1")
35
+ spec.add_dependency("jsonpath", "~> 0.9.6")
36
+ spec.add_dependency("thor", "~> 0.20.3")
37
+
38
+ spec.add_development_dependency("bundler")
39
+ spec.add_development_dependency("rake", "~> 10.0")
40
+ spec.add_development_dependency("minitest", "~> 5.0")
41
+ spec.add_development_dependency("minitest-stub-const", "~> 0.6")
42
+ spec.add_development_dependency("webmock", "~> 3.0")
43
+ spec.add_development_dependency("mocha", "~> 1.5")
44
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'krane/deploy_task'
4
+ require 'krane/render_task'
5
+ require 'krane/restart_task'
6
+ require 'krane/runner_task'
7
+ require 'krane/global_deploy_task'
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+ require 'json'
3
+ require 'yaml'
4
+ require 'csv'
5
+
6
+ module Krane
7
+ class BindingsParser
8
+ def self.parse(string)
9
+ new(string).parse
10
+ end
11
+
12
+ def initialize(initial_string = nil)
13
+ @raw_bindings = Array(initial_string)
14
+ end
15
+
16
+ def add(string)
17
+ @raw_bindings << string
18
+ end
19
+
20
+ def parse
21
+ result = {}
22
+ @raw_bindings.each do |string|
23
+ bindings = parse_file(string) || parse_json(string) || parse_csv(string)
24
+ unless bindings
25
+ raise ArgumentError, "Failed to parse bindings."
26
+ end
27
+ result.deep_merge!(bindings)
28
+ end
29
+ result
30
+ end
31
+
32
+ private
33
+
34
+ def parse_file(string)
35
+ return unless string =~ /\A@/
36
+
37
+ begin
38
+ file_path = string.gsub(/\A@/, '')
39
+
40
+ case File.extname(file_path)
41
+ when '.json'
42
+ bindings = parse_json(File.read(file_path))
43
+ when '.yaml', '.yml'
44
+ bindings = YAML.safe_load(File.read(file_path), [], [], true, file_path)
45
+ else
46
+ raise ArgumentError, "Supplied file does not appear to be JSON or YAML"
47
+ end
48
+
49
+ bindings
50
+ rescue Errno::ENOENT
51
+ raise ArgumentError, "Supplied file does not exist: #{string}"
52
+ end
53
+ end
54
+
55
+ def parse_json(string)
56
+ bindings = JSON.parse(string)
57
+
58
+ unless bindings.is_a?(Hash)
59
+ raise ArgumentError, "Expected JSON data to be a hash."
60
+ end
61
+
62
+ bindings
63
+ rescue JSON::ParserError
64
+ nil
65
+ end
66
+
67
+ def parse_csv(string)
68
+ lines = CSV.parse(string)
69
+ bindings = {}
70
+
71
+ lines.each do |line|
72
+ line.each do |binding|
73
+ key, value = binding.split('=', 2)
74
+
75
+ if key.blank?
76
+ raise ArgumentError, "key is blank"
77
+ end
78
+
79
+ bindings[key] = value
80
+ end
81
+ end
82
+
83
+ bindings
84
+ rescue CSV::MalformedCSVError
85
+ nil
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Krane
4
+ module CLI
5
+ class DeployCommand
6
+ DEFAULT_DEPLOY_TIMEOUT = '300s'
7
+ PROTECTED_NAMESPACES = %w(
8
+ default
9
+ kube-system
10
+ kube-public
11
+ )
12
+ OPTIONS = {
13
+ "filenames" => { type: :array, banner: 'config/deploy/production config/deploy/my-extra-resource.yml',
14
+ aliases: :f, required: false, default: [],
15
+ desc: "Directories and files that contains the configuration to apply" },
16
+ "stdin" => { type: :boolean, default: false,
17
+ desc: "Read resources from stdin" },
18
+ "global-timeout" => { type: :string, banner: "duration", default: DEFAULT_DEPLOY_TIMEOUT,
19
+ desc: "Max duration to monitor workloads correctly deployed" },
20
+ "protected-namespaces" => { type: :array, banner: "namespace1 namespace2 namespaceN",
21
+ desc: "Enable deploys to a list of selected namespaces; set to an empty string "\
22
+ "to disable",
23
+ default: PROTECTED_NAMESPACES },
24
+ "prune" => { type: :boolean, desc: "Enable deletion of resources that do not appear in the template dir",
25
+ default: true },
26
+ "selector" => { type: :string, banner: "'label=value'",
27
+ desc: "Select workloads by selector(s)" },
28
+ "verbose-log-prefix" => { type: :boolean, desc: "Add [context][namespace] to the log prefix",
29
+ default: true },
30
+ "verify-result" => { type: :boolean, default: true,
31
+ desc: "Verify workloads correctly deployed" },
32
+ }
33
+
34
+ def self.from_options(namespace, context, options)
35
+ require 'krane/deploy_task'
36
+ require 'krane/options_helper'
37
+ require 'krane/label_selector'
38
+
39
+ selector = ::Krane::LabelSelector.parse(options[:selector]) if options[:selector]
40
+
41
+ logger = ::Krane::FormattedLogger.build(namespace, context,
42
+ verbose_prefix: options['verbose-log-prefix'])
43
+
44
+ protected_namespaces = options['protected-namespaces']
45
+ if options['protected-namespaces'].size == 1 && %w('' "").include?(options['protected-namespaces'][0])
46
+ protected_namespaces = []
47
+ end
48
+
49
+ # never mutate options directly
50
+ filenames = options[:filenames].dup
51
+ filenames << "-" if options[:stdin]
52
+ if filenames.empty?
53
+ raise Thor::RequiredArgumentMissingError, 'At least one of --filenames or --stdin must be set'
54
+ end
55
+
56
+ ::Krane::OptionsHelper.with_processed_template_paths(filenames) do |paths|
57
+ deploy = ::Krane::DeployTask.new(
58
+ namespace: namespace,
59
+ context: context,
60
+ filenames: paths,
61
+ logger: logger,
62
+ global_timeout: ::Krane::DurationParser.new(options["global-timeout"]).parse!.to_i,
63
+ selector: selector,
64
+ protected_namespaces: protected_namespaces,
65
+ )
66
+
67
+ deploy.run!(
68
+ verify_result: options["verify-result"],
69
+ prune: options[:prune]
70
+ )
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Krane
4
+ module CLI
5
+ class GlobalDeployCommand
6
+ DEFAULT_DEPLOY_TIMEOUT = '300s'
7
+ OPTIONS = {
8
+ "filenames" => { type: :array, banner: 'config/deploy/production config/deploy/my-extra-resource.yml',
9
+ aliases: :f, required: false, default: [],
10
+ desc: "Directories and files that contains the configuration to apply" },
11
+ "stdin" => { type: :boolean, default: false, desc: "Read resources from stdin" },
12
+ "global-timeout" => { type: :string, banner: "duration", default: DEFAULT_DEPLOY_TIMEOUT,
13
+ desc: "Max duration to monitor workloads correctly deployed" },
14
+ "verify-result" => { type: :boolean, default: true,
15
+ desc: "Verify workloads correctly deployed" },
16
+ "selector" => { type: :string, banner: "'label=value'", required: true,
17
+ desc: "Select workloads owned by selector(s)" },
18
+ "prune" => { type: :boolean, desc: "Enable deletion of resources that match"\
19
+ " the provided selector and do not appear in the provided templates",
20
+ default: true },
21
+ }
22
+
23
+ def self.from_options(context, options)
24
+ require 'krane/global_deploy_task'
25
+ require 'krane/options_helper'
26
+ require 'krane/label_selector'
27
+ require 'krane/duration_parser'
28
+
29
+ selector = ::Krane::LabelSelector.parse(options[:selector])
30
+
31
+ # never mutate options directly
32
+ filenames = options[:filenames].dup
33
+ filenames << "-" if options[:stdin]
34
+ if filenames.empty?
35
+ raise Thor::RequiredArgumentMissingError, 'At least one of --filenames or --stdin must be set'
36
+ end
37
+
38
+ ::Krane::OptionsHelper.with_processed_template_paths(filenames) do |paths|
39
+ deploy = ::Krane::GlobalDeployTask.new(
40
+ context: context,
41
+ filenames: paths,
42
+ global_timeout: ::Krane::DurationParser.new(options["global-timeout"]).parse!.to_i,
43
+ selector: selector,
44
+ )
45
+
46
+ deploy.run!(
47
+ verify_result: options["verify-result"],
48
+ prune: options[:prune],
49
+ )
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end