tobsch-krane 1.0.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.
Files changed (99) 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 +185 -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 +154 -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. metadata +375 -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: krane
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 'Configured')
15
+ meet: minikube start --kubernetes-version=v1.11.10 --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 = "tobsch-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