Class: Kitchen::Driver::Kubernetes

Inherits:
Base
  • Object
show all
Includes:
KitchenKubernetes::Helper, ShellOut
Defined in:
lib/kitchen/driver/kubernetes.rb

Overview

Kubernetes driver for Kitchen.

See Also:

Author:

  • Noah Kantrowitz <noah@coderanger>

Since:

  • 1.0

Instance Method Summary collapse

Methods included from KitchenKubernetes::Helper

#kube_options, #kubectl_command

Instance Method Details

#create(state) ⇒ Object

Since:

  • 1.0



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/kitchen/driver/kubernetes.rb', line 159

def create(state)
  # Already created, we're good.
  return if state[:pod_id]
  # Lock in our name with randomness and whatever.
  pod_id = config[:pod_name]
  # Render the pod YAML and feed it to kubectl.
  tpl = ERB.new(IO.read(config[:pod_template]), 0, '-')
  tpl.filename = config[:pod_template]
  pod_yaml = tpl.result(binding)
  debug("Creating pod with YAML:\n#{pod_yaml}\n")
  run_command(kubectl_command('create', '--filename', '-'), input: pod_yaml)
  # Wait until the pod reaches Running status.
  status = nil
  start_time = Time.now
  while status != 'Running'
    if Time.now - start_time > 20
      # More than 20 seconds, start giving user feedback. 20 second threshold
      # was 100% pulled from my ass based on how long it takes to launch
      # on my local minikube, may need changing for reality.
      info("Waiting for pod #{pod_id} to be running, currently #{status}")
    end
    sleep(1)
    # Can't use run_command here because error! is unwanted and logging is a bit much.
    status_cmd = Mixlib::ShellOut.new(kubectl_command('get', 'pod', pod_id, '--output=json'))
    status_cmd.run_command
    unless status_cmd.error? || status_cmd.stdout.empty?
      status = JSON.parse(status_cmd.stdout)['status']['phase']
    end
  end
  # Save the pod ID.
  state[:pod_id] = pod_id
rescue Exception => ex
  # If something goes wrong, try to clean up.
  if pod_id
    begin
      debug("Failure during create, trying to clean up pod #{pod_id}")
      run_command(kubectl_command('delete', 'pod', pod_id, '--now'))
    rescue ShellCommandFailed => cleanup_ex
      # Welp, we tried.
      debug("Cleanup failed, continuing anyway: #{cleanup_ex}")
    end
  end
  raise ex
end

#default_imageString

Work out the default primary container image to use for this instance. Can be overridden by subclasses. Must return a string compatible with a Kubernetes container image specification.

Returns:

  • (String)

Since:

  • 1.0



92
93
94
95
96
97
98
# File 'lib/kitchen/driver/kubernetes.rb', line 92

def default_image
  if instance.platform.name =~ /^(.*)-([^-]*)$/
    "#{$1}:#{$2}"
  else
    instance.platform.name
  end
end

#destroy(state) ⇒ Object

Since:

  • 1.0



205
206
207
208
209
210
211
212
# File 'lib/kitchen/driver/kubernetes.rb', line 205

def destroy(state)
  return unless state[:pod_id]
  run_command(kubectl_command('delete', 'pod', state[:pod_id], '--now'))
  # Explicitly not waiting for the delete to finish, if k8s has problems
  # with deletes in the future, I can add a wait here.
rescue ShellCommandFailed => ex
  raise unless ex.to_s.include?('(NotFound)')
end

#finalize_config!(instance) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Muck with some other plugins to make the UX easier. Haxxxx.

Since:

  • 1.0



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/kitchen/driver/kubernetes.rb', line 103

def finalize_config!(instance)
  super.tap do
    # Force the use of the Kubernetes transport since it isn't much use
    # without that.
    instance.transport = Kitchen::Transport::Kubernetes.new(config)
    # Leave room for the possibility of other provisioners in the future,
    # but force some options we need.
    if instance.provisioner.is_a?(Kitchen::Provisioner::ChefBase)
      instance.provisioner.send(:config).update(
        require_chef_omnibus: false,
        product_name: nil,
        chef_omnibus_root: '/opt/chef',
        sudo: false,
      )
    end
    # Ditto to the above, other verifiers will need their own hacks, but
    # this is a start at least.
    if instance.verifier.is_a?(Kitchen::Verifier::Busser)
      instance.verifier.send(:config).update(
        root_path: '/tmp/kitchen/verifier',
        sudo: false,
      )
    elsif defined?(Kitchen::Verifier::Inspec) && instance.verifier.is_a?(Kitchen::Verifier::Inspec)
      # Monkeypatch kitchen-inspec to use my copy of the kubernetes train transport.
      # Pending https://github.com/chef/train/pull/205 and https://github.com/chef/kitchen-inspec/pull/148
      # or https://github.com/chef/kitchen-inspec/pull/149.
      require 'kitchen/verifier/train_kubernetes_hack'
      _config = config # Because closure madness.
      old_runner_options = instance.verifier.method(:runner_options)
      instance.verifier.send(:define_singleton_method, :runner_options) do |transport, state = {}, platform = nil, suite = nil|
        if transport.is_a?(Kitchen::Transport::Kubernetes)
          # Initiate 1337 ha><0rz.
          {
            "backend" => "kubernetes_hack",
            "logger" => logger,
            "pod" => state[:pod_id],
            "container" => "default",
            "kubectl_path" => _config[:kubectl_path],
            "context" => _config[:context],
          }.tap do |runner_options|
            # Copied directly from kitchen-inspec because there is no way not to. Sigh.
            runner_options["color"] = (config[:color].nil? ? true : config[:color])
            runner_options["format"] = config[:format] unless config[:format].nil?
            runner_options["output"] = config[:output] % { platform: platform, suite: suite } unless config[:output].nil?
            runner_options["profiles_path"] = config[:profiles_path] unless config[:profiles_path].nil?
            runner_options[:controls] = config[:controls]
          end
        else
          old_runner_options.call(transport, state, platform, suite)
        end
      end
    end
  end
end