Class: Hippo::Kubernetes

Inherits:
Object
  • Object
show all
Includes:
Util
Defined in:
lib/hippo/kubernetes.rb

Constant Summary collapse

OBJECT_DIRECTORY_NAMES =
%w[config deployments jobs/install jobs/deploy services].freeze

Instance Method Summary collapse

Methods included from Util

#load_yaml_from_data, #load_yaml_from_directory, #load_yaml_from_path, #open_in_editor

Constructor Details

#initialize(recipe, options) ⇒ Kubernetes



12
13
14
15
# File 'lib/hippo/kubernetes.rb', line 12

def initialize(recipe, options)
  @recipe = recipe
  @options = options
end

Instance Method Details

#apply_namespace(stage) ⇒ Object



71
72
73
74
75
76
77
78
79
80
81
# File 'lib/hippo/kubernetes.rb', line 71

def apply_namespace(stage)
  namespace = {
    'kind' => 'Namespace',
    'apiVersion' => 'v1',
    'metadata' => {
      'name' => stage.namespace
    }
  }
  add_default_labels(namespace, stage)
  apply_with_kubectl(namespace.to_yaml)
end

#apply_with_kubectl(yaml_parts) ⇒ void

This method returns an undefined value.

Apply the given configuration with kubectl



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/hippo/kubernetes.rb', line 87

def apply_with_kubectl(yaml_parts)
  unless yaml_parts.is_a?(String)
    yaml_parts = [yaml_parts] unless yaml_parts.is_a?(Array)
    yaml_parts = yaml_parts.map { |yp| yp.hash.to_yaml }.join("\n---\n")
  end

  Open3.popen3('kubectl apply -f -') do |stdin, stdout, stderr, wt|
    stdin.puts yaml_parts
    stdin.close

    stdout = stdout.read.strip
    stderr = stderr.read.strip

    if wt.value.success?
      puts stdout
      stdout.split("\n").each_with_object({}) do |line, hash|
        if line =~ %r{\A([\w\/\-\.]+) (\w+)\z}
          hash[Regexp.last_match(1)] = Regexp.last_match(2)
        end
      end
    else
      raise Error, "[kubectl] #{stderr}"
    end
  end
end

#delete_job(stage, name) ⇒ void

This method returns an undefined value.

Delete a named job from the cluster



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/hippo/kubernetes.rb', line 144

def delete_job(stage, name)
  command = [
    'kubectl',
    '-n', stage.namespace,
    'delete',
    'job',
    name
  ]

  Open3.popen3(*command) do |_, stdout, stderr, wt|
    if wt.value.success?
      puts stdout.read
      true
    else
      stderr = stderr.read
      if stderr =~ /\"#{name}\" not found/
        false
      else
        raise Error, "[kutectl] #{stderr.read}"
      end
    end
  end
end

#get_with_kubectl(stage, *names) ⇒ Array<Hash>

Get details of objects using kubectl.



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/hippo/kubernetes.rb', line 119

def get_with_kubectl(stage, *names)
  command = [
    'kubectl',
    '-n', stage.namespace,
    'get',
    names,
    '-o', 'yaml'
  ].flatten.reject(&:nil?)

  Open3.popen3(*command) do |_, stdout, stderr, wt|
    if wt.value.success?
      yaml = YAML.safe_load(stdout.read, permitted_classes: [Time])
      yaml['items'] || [yaml]
    else
      raise Error, "[kutectl] #{stderr.read}"
    end
  end
end

#objects(path, stage, commit) ⇒ Object

Load and return a set of objects from a given path. Parse them through the templating and return them in the appropriate context.



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/hippo/kubernetes.rb', line 25

def objects(path, stage, commit)
  time = Time.now

  yamls = load_yaml_from_directory(path)
  yamls |= load_yaml_from_directory(File.join(path, stage.name))

  yamls.map do |yaml_part|
    object = yaml_part.parse(@recipe, stage, commit)

    # Unless a namespace has been specified in the metadata we will
    # want to add the namespace for the current stage.
    if object['metadata'].nil? || object['metadata']['namespace'].nil?
      object['metadata'] ||= {}
      object['metadata']['namespace'] = stage.namespace
    end

    # Add our own details to the metadata of all objets created by us so
    # we know where they came from.
    object['metadata']['annotations'] ||= {}
    object['metadata']['annotations']['hippo.adam.ac/builtAt'] ||= time.to_s
    object['metadata']['annotations']['hippo.adam.ac/builtBy'] ||= ENV['USER'] || 'unknown'

    add_default_labels(object, stage)

    # Add some information to Deployments to reflect the latest
    # information about this deployment.
    if object['kind'] == 'Deployment'
      object['metadata']['annotations']['hippo.adam.ac/deployID'] ||= time.to_i.to_s
      if commit
        object['metadata']['annotations']['hippo.adam.ac/commitRef'] ||= commit.objectish
        object['metadata']['annotations']['hippo.adam.ac/commitMessage'] ||= commit.message
      end

      if  = object.dig('spec', 'template', 'metadata')
        ['annotations'] ||= {}
        ['annotations']['hippo.adam.ac/deployID'] ||= time.to_i.to_s
        if commit
          ['annotations']['hippo.adam.ac/commitRef'] ||= commit.objectish
        end
      end
    end

    object
  end
end

#wait_for_jobs(stage, names, times = 120) ⇒ Array<Boolean, Array<Hash>]

Poll the named jobs and return them when all are complete or the number of checks is exceeded.



175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/hippo/kubernetes.rb', line 175

def wait_for_jobs(stage, names, times = 120)
  jobs = nil
  times.times do
    jobs = get_with_kubectl(stage, *names)

    # Are all the jobs completed?
    if jobs.all? { |j| j['status']['active'].nil? }
      return [false, jobs]
    else
      sleep 2
    end
  end
  [true, jobs]
end