Class: WideEvent::Kamal::DeployEditor

Inherits:
Object
  • Object
show all
Defined in:
lib/wide_event/kamal/deploy_editor.rb

Overview

Applies the wide_events Kamal accessory to a host application's config/deploy.yml without disturbing unrelated comments, ordering, or sections. Detection of structure and conflicts uses Psych.safe_load; every actual mutation is a targeted line insertion/replacement on the original text so byte-identical content survives everywhere we did not intentionally touch it.

Anything the line-based patcher cannot safely reason about (ERB, YAML aliases/merge keys, tabs, duplicate root keys, non-mapping env/ accessories/aliases, an existing non-owned wide_events accessory, conflicting WIDE_EVENTS_* values, or any other shape this editor was not built to understand) causes a refusal: the original content is returned completely unchanged, alongside a complete mergeable snippet the caller can hand to a human or an agent.

Defined Under Namespace

Classes: Result

Constant Summary collapse

MINIMUM_VERSION =
"2.10.0"
BEGIN_MARKER =
"# BEGIN wide_events store"
END_MARKER =
"# END wide_events store"
CLEAR_VARS =
%w[WIDE_EVENTS_URL WIDE_EVENTS_SERVICE WIDE_EVENTS_ENVIRONMENT].freeze
SECRET_VARS =
%w[WIDE_EVENTS_INGEST_TOKEN WIDE_EVENTS_QUERY_TOKEN].freeze
ALIAS_TELEMETRY_KEY =
"telemetry"
ALIAS_TELEMETRY_LINE =
'telemetry: app exec -i --reuse "bin/rails wide_events:sql"'
ALIAS_BACKUP_KEY =
"wide-events-backup"
ALIAS_BACKUP_LINE =

--reuse is load-bearing: without it Kamal's accessory exec starts a new container from the store image, where (a) the image entrypoint already is /usr/local/bin/wide-events, so the appended command becomes argv and main exits 2 with usage, and (b) there is no running server holding /run/wide-events/admin.sock to take the backup through. With --reuse this is a docker exec into the live accessory container, which is the only place an on-demand backup can be requested.

'wide-events-backup: accessory exec wide_events --reuse "wide-events admin backup"'
ACCESSORY_TEMPLATE_PATH =
File.expand_path(
  "../../generators/wide_events/store/templates/accessory.yml.erb", __dir__
)
ROOT_KEY_RE =
/\A([A-Za-z_][A-Za-z0-9_.-]*):(.*)\n?\z/
Refusal =

Raised internally to unwind straight to a refusal; never escapes #apply.

Class.new(StandardError)

Instance Method Summary collapse

Constructor Details

#initialize(content, service:, host:, hostname:, environment: "production", retention_days: 30, image: nil) ⇒ DeployEditor

Returns a new instance of DeployEditor.



55
56
57
58
59
60
61
62
63
# File 'lib/wide_event/kamal/deploy_editor.rb', line 55

def initialize(content, service:, host:, hostname:, environment: "production", retention_days: 30, image: nil)
  @original = content.to_s
  @service = service
  @host = host
  @hostname = hostname
  @environment = environment
  @retention_days = retention_days
  @image = image || "ghcr.io/adammiribyan/wide-events-store:#{WideEvent::VERSION}"
end

Instance Method Details

#applyObject



65
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
# File 'lib/wide_event/kamal/deploy_editor.rb', line 65

def apply
  parsed = preflight!
  lines = @original.lines
  changed = false

  changed |= apply_minimum_version(lines)

  env_range = root_block_range(lines, "env")
  changed |= apply_env_in_place(lines, env_range) if env_range

  aliases_range = root_block_range(lines, "aliases")
  changed |= apply_aliases_in_place(lines, aliases_range, parsed["aliases"] || {}) if aliases_range

  accessories_range = root_block_range(lines, "accessories")
  changed |= apply_accessories_in_place(lines, accessories_range) if accessories_range

  changed |= append_env_section(lines) unless env_range
  changed |= append_aliases_section(lines) unless aliases_range
  changed |= append_accessories_section(lines) unless accessories_range

  new_content = lines.join
  verify_parseable!(new_content)

  Result.new(content: new_content, changed: changed, snippet: nil, conflicts: [])
rescue Refusal => e
  Result.new(content: @original, changed: false, snippet: build_snippet, conflicts: [ e.message ])
end