Class: KubeDeployTools::Deploy

Inherits:
Object
  • Object
show all
Defined in:
lib/kube_deploy_tools/deploy.rb

Defined Under Namespace

Classes: Optparser

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(kubectl:, namespace: nil, input_path:, glob_files: [], max_retries: 3, retry_delay: 1) ⇒ Deploy

Returns a new instance of Deploy.



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
# File 'lib/kube_deploy_tools/deploy.rb', line 34

def initialize(
  kubectl:,
  namespace: nil,
  input_path:,
  glob_files: [],
  max_retries: 3,
  retry_delay: 1
)
  @kubectl = kubectl
  @namespace = namespace
  @input_path = input_path

  if !File.exists?(@input_path)
    Logger.error("Path doesn't exist: #{@input_path}")
    raise ArgumentError, "Path doesn't exist #{@input_path}"
  elsif File.directory?(@input_path)
    @glob_files = glob_files
    @filtered_files = FileFilter
                      .filter_files(filters: @glob_files, files_path: @input_path)
                      .select { |f| f.end_with?('.yml', '.yaml') }
  elsif File.file?(@input_path)
    @filtered_files = [@input_path]
    if !@glob_files.nil? && @glob_files.length > 0
      Logger.error("Single-file artifacts do not support glob exclusions: #{@input_path}")
      raise ArgumentError
    end
  end

  @max_retries = max_retries.nil? ? 3 : max_retries.to_i
  @retry_delay = retry_delay.to_i
end

Class Method Details

.kube_namespace(context:, kubeconfig: nil) ⇒ Object



199
200
201
202
203
204
205
206
207
208
209
# File 'lib/kube_deploy_tools/deploy.rb', line 199

def self.kube_namespace(context:, kubeconfig: nil)
  args = [
    'kubectl', 'config', 'view', '--minify', '--output=jsonpath={..namespace}',
    "--context=#{context}"
  ]
  args.push("--kubeconfig=#{kubeconfig}") if kubeconfig.present?
  namespace, = Shellrunner.check_call(*args)
  namespace = 'default' if namespace.to_s.empty?

  namespace
end

Instance Method Details

#current_userObject



211
212
213
# File 'lib/kube_deploy_tools/deploy.rb', line 211

def current_user
  Shellrunner.run_call('gcloud', 'config', 'list', 'account', '--format', 'value(core.account)')[0]
end

#do_deploy(dry_run) ⇒ Object



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/kube_deploy_tools/deploy.rb', line 66

def do_deploy(dry_run)
  success = false
  Logger.reset
  Logger.phase_heading('Initializing deploy')
  Logger.warn('Running in dry-run mode') if dry_run

  if !@namespace.nil? && @namespace != 'default'
    Logger.warn("Deploying to non-default Namespace: #{@namespace}")
  end

  resources = read_resources(@filtered_files)

  Logger.phase_heading('Checking initial resource statuses')
  KubernetesDeploy::Concurrency.split_across_threads(resources, &:sync)

  Logger.phase_heading('Checking deployment replicas match')
  deployments = resources
                .select { |resource| resource.definition['kind'] == 'Deployment' }
  KubernetesDeploy::Concurrency.split_across_threads(deployments, &:warn_replicas_mismatch)

  Logger.phase_heading('Deploying all resources')
  # Deploy predeploy resources first, in order.
  # Then deploy the remaining resources in any order.
  deploy_resources = resources.sort_by do |r|
    PREDEPLOY_RESOURCES.index(r.definition['kind']) || PREDEPLOY_RESOURCES.length
  end

  kubectl_apply(deploy_resources, dry_run: dry_run)

  success = true
ensure
  Logger.print_summary(success)
  success
end

#git_annotationsObject



137
138
139
140
141
142
143
144
145
146
# File 'lib/kube_deploy_tools/deploy.rb', line 137

def git_annotations
  resource_definition(@filtered_files.first) do |resource|
    if resource.annotations
      git_commit  = resource.annotations['git_commit']
      git_project = resource.annotations['git_project']
      return [git_commit, git_project]
    end
  end
  [nil, nil]
end

#kubectl_apply(resources, dry_run: true) ⇒ Object



164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/kube_deploy_tools/deploy.rb', line 164

def kubectl_apply(resources, dry_run: true)
  resources.each do |resource|
    @max_retries.times do |try|
      args = ['apply', '-f', resource.filepath, "--dry-run=#{dry_run}"]
      out, _, status = @kubectl.run(*args)
      if status.success?
        Logger.info(out)
        break
      elsif try < @max_retries - 1
        sleep(@retry_delay)
        next
      end
      raise FatalDeploymentError, "Failed to apply resource '#{resource.filepath}'"
    end
  end
end

#kubectl_cluster_nameObject



181
182
183
184
185
186
187
188
# File 'lib/kube_deploy_tools/deploy.rb', line 181

def kubectl_cluster_name
  args = ['config', 'view', '--minify', '--output=jsonpath={..clusters[0].name}']
  name, _, status = @kubectl.run(*args)
  unless status.success?
    raise FatalDeploymentError, 'Failed to determine cluster name'
  end
  name
end

#kubectl_cluster_serverObject



190
191
192
193
194
195
196
197
# File 'lib/kube_deploy_tools/deploy.rb', line 190

def kubectl_cluster_server
  args = ['config', 'view', '--minify', '--output=jsonpath={..cluster.server}']
  server, _, status = @kubectl.run(*args)
  unless status.success?
    raise FatalDeploymentError, 'Failed to determine cluster server'
  end
  server
end

#project_infoObject



105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/kube_deploy_tools/deploy.rb', line 105

def project_info
  git_commit, git_project = git_annotations
  # send a notification about the deployed code
  {
    'git_commit': git_commit,
    'git_project': git_project,
    'kubernetes-cluster': kubectl_cluster_server,
    'kubernetes-cluster-name': kubectl_cluster_name,
    'time': DateTime.now,
    'user': current_user
  }
end

#read_resource_definition(filepath) ⇒ Object



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/kube_deploy_tools/deploy.rb', line 148

def read_resource_definition(filepath)
  file_content = File.read(filepath)
  YAML.load_stream(file_content) do |doc|
    yield doc if !doc.nil? && !doc.empty?
  end
rescue Psych::SyntaxError => e
  debug_msg = <<~INFO
    Error message: #{e}
    Template content:
    ---
  INFO
  debug_msg += file_content
  Logger.debug(debug_msg)
  raise FatalDeploymentError, "Template '#{filepath}' cannot be parsed"
end

#read_resources(filtered_files = ) ⇒ Object



118
119
120
121
122
123
124
125
126
# File 'lib/kube_deploy_tools/deploy.rb', line 118

def read_resources(filtered_files = Dir[File.join(@input_path, '**', '*')])
  resources = []
  filtered_files.each do |filepath|
    resource_definition(filepath) do |resource|
      resources << resource
    end
  end
  resources
end

#resource_definition(filepath) ⇒ Object



128
129
130
131
132
133
134
135
# File 'lib/kube_deploy_tools/deploy.rb', line 128

def resource_definition(filepath)
  read_resource_definition(filepath) do |resource_definition|
    yield KubeDeployTools::KubernetesResource.build(
      definition: resource_definition,
      kubectl: @kubectl
    )
  end
end

#run(dry_run: true) ⇒ Object



101
102
103
# File 'lib/kube_deploy_tools/deploy.rb', line 101

def run(dry_run: true)
  do_deploy(dry_run)
end