Class: Dctl::Main

Inherits:
Object
  • Object
show all
Defined in:
lib/dctl/main.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(env: "dev", config: nil) ⇒ Main

Returns a new instance of Main.



5
6
7
8
# File 'lib/dctl/main.rb', line 5

def initialize(env: "dev", config: nil)
  @env = env
  load_config!(config)
end

Instance Attribute Details

#envObject (readonly)

Returns the value of attribute env.



3
4
5
# File 'lib/dctl/main.rb', line 3

def env
  @env
end

#settingsObject (readonly)

Returns the value of attribute settings.



3
4
5
# File 'lib/dctl/main.rb', line 3

def settings
  @settings
end

Instance Method Details

#bump(image) ⇒ Object



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/dctl/main.rb', line 63

def bump(image)
  check_image(image)

  parsed  = parsed_compose_file
  service = parsed.dig "services", image
  old_tag = service["image"]
  puts "Found existing image #{old_tag}"

  version = versions[image].to_i
  new_tag = image_tag image, version: version + 1
  puts "New tag will be #{new_tag}"

  service["image"] = new_tag

  print "Updating..."
  File.write(compose_file_path, parsed.to_yaml)
  puts "done"

  # Cache bust
  @parsed_compose_file = nil
  @versions = nil

  puts Rainbow("#{image} is now at version #{version + 1}").fg :green
end

#check_image(image) ⇒ Object

Confirms that there is an entry for the given image in the compose file for this environment, and that the image tag within is formatted as we expect it to be.

Prints a warning if the tag has the wrong name, but errors out if the service tag is not present

Expected names look like org/project-env-image:version



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
# File 'lib/dctl/main.rb', line 111

def check_image(image)
  tag = image_tag(image)

  # Check that a service exists for the image
  service = parsed_compose_file.dig "services", image
  unless service
    error = "The service \"#{image}\" is not present in the compose " \
      "file for this environment. Please add a service entry for " \
      "#{image} to #{compose_file_path}\n"
    puts Rainbow(error).fg :red

    puts <<~EOL
      It might look something like this:

      version: '3'
      services:
        #{image}:
          image: #{image_tag(image)}
    EOL
    exit 1
  end

  # Check that the image has the correct tag
  expected_tag = image_tag(image)
  actual_tag = service["image"]
  if actual_tag != expected_tag
    warning = "Expected the tag for the image \"#{image}\" to be " \
      "\"#{expected_tag}\", but it was \"#{actual_tag}\". While not " \
      "critical, this can cause issues with some commands."
    puts Rainbow(warning).fg :orange
  end
end

#check_settings!Object

Ensure the current project’s .dctl.yml contains all the requisite keys.



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/dctl/main.rb', line 181

def check_settings!
  required_keys = %w(
    org
    project
  )

  required_keys.each do |key|
    unless Settings.send key
      error = "Config is missing required key '#{key}'. Please add it " \
        "to #{config_path} and try again."
      error += "\n\nFor more info, see https://github.com/jutonz/dctl_rb#required-keys"
      puts Rainbow(error).red
      exit 1
    end
  end
end

#compose_file_pathObject



208
209
210
211
212
213
214
215
216
217
218
# File 'lib/dctl/main.rb', line 208

def compose_file_path
  path = File.expand_path "docker/#{env}/docker-compose.yml"

  unless File.exist? path
    err = "There is no docker compose file for env #{env} (I expected to find it at #{path})"
    puts Rainbow(err).red
    exit 1
  end

  path
end

#config_pathObject

Returns the path to the .dctl.yml file for the current project



90
91
92
93
94
95
96
97
98
99
100
# File 'lib/dctl/main.rb', line 90

def config_path
  path = File.expand_path ".dctl.yml", Dir.pwd

  unless File.exist? path
    error = "Could not find config file at #{path}"
    puts Rainbow(error).red
    exit 1
  end

  path
end

#current_version_for_image(image) ⇒ Object



37
38
39
# File 'lib/dctl/main.rb', line 37

def current_version_for_image(image)
  versions[image]
end

#define_custom_commands(klass) ⇒ Object

If there are user defined commands in .dctl.yml, dynamically add them to the passed thor CLI so they may be executed.



164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/dctl/main.rb', line 164

def define_custom_commands(klass)
  Array(settings.custom_commands).each do |command, args|
    klass.send(:desc, command, "[Custom Command] #{command}")

    # Concat with string so we can use exec rather than executing multiple
    # subshells. Exec allows us to reuse the shell in which dctl is being
    # executed, so we get to do things like reuse sudo authorizations
    # rather than always having to prmopt.
    concatenated = Array(args).join(" && ").strip
    klass.send(:define_method, command, -> do
      stream_output(concatenated, exec: true)
    end)
  end
end

#expand_images(*images) ⇒ Object



54
55
56
57
58
59
60
61
# File 'lib/dctl/main.rb', line 54

def expand_images(*images)
  images = versions.keys if images.empty?
  images = Array(images)

  images.each { |image| check_image(image) }

  images
end

#image_dir(image) ⇒ Object

Returns the path to the given image’s data directory (which includes at minimum the Dockerfile, plus any other relevant files the user may have placed there).



45
46
47
48
# File 'lib/dctl/main.rb', line 45

def image_dir(image)
  relative = File.join "docker", env, image
  File.expand_path relative, Dir.pwd
end

#image_dockerfile(image) ⇒ Object



50
51
52
# File 'lib/dctl/main.rb', line 50

def image_dockerfile(image)
  File.expand_path "Dockerfile", image_dir(image)
end

#image_tag(image, version: current_version_for_image(image)) ⇒ Object

Generate the full tag for the given image, concatenating the org, project, env, image name, and version.

Pass ‘version: nil` to exclude the version portion.

Examples:

image_tag("app") # => jutonz/dctl-dev-app:1


18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/dctl/main.rb', line 18

def image_tag(image, version: current_version_for_image(image))
  org       = settings.org
  project   = settings.project

  tag = "#{org}/#{project}-#{env}-#{image}"
  if !version.nil?
    version = version.to_i
    tag +=
      if version.negative?
        current_version = current_version_for_image(image)
        ":#{current_version.to_i + version}"
      else
        ":#{version}"
      end
  end

  tag
end

#load_config!(custom_config_path = nil) ⇒ Object

Load the current project’s config file, complaining if it does not exist or is malformed.



201
202
203
204
205
206
# File 'lib/dctl/main.rb', line 201

def load_config!(custom_config_path = nil)
  Config.load_and_set_settings(custom_config_path || config_path)
  check_settings!

  @settings = Settings
end

#parsed_compose_fileObject



157
158
159
# File 'lib/dctl/main.rb', line 157

def parsed_compose_file
  @parsed_compose_file ||= YAML.load_file compose_file_path
end

#versionsObject



144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/dctl/main.rb', line 144

def versions
  @versions ||= begin
    images = parsed_compose_file["services"].keys
    version_map = {}

    images.each do |image|
      version_map[image] = parsed_compose_file["services"][image]["image"].split(":").last
    end

    version_map
  end
end