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



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

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



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

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

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

  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



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

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: 'client') ⇒ Object



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

def kubectl_apply(resources, dry_run: 'client')
  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



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

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



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

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



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

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



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

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



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

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



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

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: 'client') ⇒ Object



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

def run(dry_run: 'client')
  do_deploy(dry_run)
end