lib/cell_manager.rb (174 lines of code) (raw):

# frozen_string_literal: true class CellManager include GDK::CoreHelper::DeepHash PORT_BASE_OFFSET = 12_000 PORT_OFFSET = 150 LEGACY_CELL_ID = 1 LEGACY_CELL_SEQUENCE_MAXVAL = (2**63) - 1 DEFAULT_SEQUENCE_RANGE = 100_000_000_000 AUTO_GENERATED_HEADER = <<~TEXT # This configuration was generated by `gdk cells up` on %<date>s. # # Do not change this file! # Change `%<main_gdk>s` instead and rerun the command. TEXT Result = Struct.new('Result', :success?) def up return true unless enabled? cells.all? do |cell| break unless gdk_build(cell) sh = run_in_cell(cell[:id], %w[reconfigure], skip_directory_check: true) if sh.success? GDK::Output.success("Cell #{cell[:id]} is ready.") else GDK::Output.error("Cell #{cell[:id]} failed to reconfigure.") end sh.success? end end def update return true unless enabled? cells.all? do |cell| GDK::Output.info("Updating cell #{cell[:id]}") run_in_cell(cell[:id], %w[update]).success? end end def start return true unless enabled? cells.all? do |cell| run_in_cell(cell[:id], %w[start]).success? end end def stop return true unless enabled? cells.all? do |cell| run_in_cell(cell[:id], %w[stop]).success? end end def restart return true unless enabled? cells.all? do |cell| run_in_cell(cell[:id], %w[restart]).success? end end def status return true unless enabled? cells.all? do |cell| GDK::Output.info("cell-#{cell[:id]}") run_in_cell(cell[:id], %w[status]).success? end end def run_in_cell(cell_id, args, quiet: false, skip_directory_check: false) return Result.new(false) unless enabled? cell, _index = find_cell(cell_id) unless cell GDK::Output.error("Cell #{cell_id} not found. #{help_cell_list}") return Result.new(false) end dir = directory_for_cell(cell) unless skip_directory_check || Dir.exist?(dir) GDK::Output.error("Cell #{cell[:id]} doesn’t exist yet, run `gdk cells up` first.") return Result.new(false) end opts = { chdir: dir } sh = GDK::Shellout.new("gdk #{args.join ' '};", **opts) sh.execute(display_output: !quiet) sh end def enabled? GDK.config.cells.enabled end def cell_exist?(id) !!find_cell(id) end def get_config_for(id) cell = find_cell(id) raise "No config for cell `#{id}` found" unless cell cell_gdk_root = Pathname.new(directory_for_cell(cell)) cell_gdk_config_path = cell_gdk_root.join('gdk.yml') merged_cell_config(cell_gdk_config_path, cell) end private def find_cell(id) cells.find { |cell| cell[:id] == id } end def port_offset_for(index) PORT_BASE_OFFSET + (index * PORT_OFFSET) end def gdk_build(cell) cell_gdk_root = directory_for_cell(cell) if Dir.exist?(cell_gdk_root) write_cell_config(cell_gdk_root, cell) else GDK::Output.info("Cloning into GDK for #{cell[:id]}") sh = GDK::Shellout.new(*%W[git clone #{GDK.root} #{cell_gdk_root}]) sh.execute return false unless sh.success? sh = GDK::Shellout.new(*%w[git remote get-url origin], chdir: GDK.root) sh.execute original_origin = sh.success? ? sh.read_stdout.chomp : 'https://gitlab.com/gitlab-org/gitlab-development-kit.git' sh = GDK::Shellout.new(*%W[git remote set-url origin #{original_origin}], chdir: cell_gdk_root) sh.execute return false unless sh.success? write_cell_config(cell_gdk_root, cell) return false unless gdk_install(cell) end true end def write_cell_config(cell_gdk_root, cell) cell_gdk_config_path = "#{cell_gdk_root}/gdk.yml" # Always start with an empty file FileUtils.rm_f(cell_gdk_config_path) config = merged_cell_config(cell_gdk_config_path, cell) config.save_yaml! header = format(AUTO_GENERATED_HEADER, date: Time.now, main_gdk: GDK::Config::FILE) File.write(cell_gdk_config_path, header + File.read(cell_gdk_config_path)) end def merged_cell_config(cell_gdk_config_path, cell) yaml = deep_merge(main_gdk_config_yaml, cell_instance_config(cell)) config = cell_config_for(cell_gdk_config_path, yaml) config.validate! config end def main_gdk_config_yaml yaml = GDK.config.yaml.except('cells') # deep dup to avoid side-effects JSON.parse(JSON.dump(yaml)) end # Returns a config value: # - Cell-specific value if it's defined in cell's gdk.yml # - Main's value otherwise if +fallback_value+ is a GDK::Config # - Passed +fallback_value+ otherwise def value_for(slug, config, fallback_value) if config.user_defined?(slug) config.dig(slug) # rubocop:disable Style/SingleArgumentDig -- That's a different dig else fallback_value.dig(slug) # rubocop:disable Style/SingleArgumentDig -- That's a different dig end end def cell_instance_config(cell) yaml = cell.config || {} # Ensure that old/non-synced cells GDKs still work as expected # See https://gitlab.com/gitlab-org/gitlab-development-kit/-/issues/2309 yaml['port_offset'] = port_offset_for(cell.__index) yaml['cells'] ||= {} yaml['cells']['port_offset'] = yaml['port_offset'] config = cell.root.class.new(yaml: yaml).tap(&:validate!) config.dump!(user_only: true) end def gdk_install(cell) pathname = directory_for_cell(cell) return if Dir.exist?("#{pathname}/gitlab") gitlab_repo = "#{GDK.root}/gitlab" # Force shell invocation by adding ';' # This is necessary to make `gdk` command work in the right directory. # TODO use YAML value, or default cmd = "gdk install gitlab_repo=#{gitlab_repo};" sh = GDK::Shellout.new(cmd, chdir: pathname) sh.execute(display_output: true) raise 'Failed to gdk install' unless sh.success? true end def help_cell_list return 'Check doc/howto/cells.md on how to add local cell instances.' if cells.empty? "Found: #{cells.map(&:id).join(', ')}." end def cells GDK.config.cells.instances.elems end def directory_for_cell(cell) "#{GDK.root}/gitlab-cells/cell-#{cell[:id]}" end def cell_config_for(gdk_config_path, yaml) # Currently, there's no good way of passing GDK root and config path to config # so we override constants and assign YAML just for Cells. # See https://gitlab.com/gitlab-org/gitlab-development-kit/-/merge_requests/4027#note_2148267092 klass = Class.new(GDK::Config) klass.const_set(:FILE, gdk_config_path) # Avoid messing up the superclass (i.e. `GDK::Config`) klass.attributes = GDK::Config.attributes.dup config = klass.load_from_file config.instance_variable_set(:@yaml, yaml) config end end