Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# 0004 Ruby Checklist

## Baseline

- [x] Plugin work lives under `extended/plugins/send-message-ruby/`.
- [x] The subtree stands alone.
- [x] No committed Slack credential appears anywhere.
- [x] The plugin owns Kubernetes reads and Slack calls.

## Phase 0: pin the contract

- [x] Re-read `proposal.md`.
- [x] Re-read `spec.md`.
- [x] Re-read the `send-message` slice in proposal `0002`.
- [x] Keep scope to Slack only.

## Phase 1: create the tiny repo

- [x] Create `extended/plugins/send-message-ruby/`.
- [x] Add runtime source.
- [x] Add image build.
- [x] Add `plugin.yaml`.
- [x] Add CRD manifests.
- [x] Add RBAC manifests.
- [x] Add smoke assets.

## Phase 2: implement the runtime

- [x] Implement `POST /api/v1/step.execute`.
- [x] Enforce bearer auth from `/var/run/kargo/token`.
- [x] Read `MessageChannel`.
- [x] Read `ClusterMessageChannel`.
- [x] Read referenced `Secret`.
- [x] Send plaintext Slack payloads.
- [x] Send encoded Slack payloads.
- [x] Return `slack.threadTS`.

## Phase 3: test the contract

- [x] Add auth tests.
- [x] Add channel lookup tests.
- [x] Add Secret lookup tests.
- [x] Add plaintext payload tests.
- [x] Add encoded payload tests.
- [x] Add XML decode tests.
- [x] Add Slack failure tests.

## Phase 4: smoke

- [x] Add plugin-owned `smoke/smoke_test.rb`.
- [x] Keep smoke orchestration in Ruby, not shell.
- [x] Build the image.
- [x] Load it into kind.
- [x] Install CRDs and RBAC.
- [x] Install StepPlugin `ConfigMap`.
- [x] Create local-only test Secret.
- [x] Create test `MessageChannel`.
- [x] Run a `Stage` with `uses: send-message`.
- [x] Assert `Succeeded`.
- [x] Assert non-empty `slack.threadTS`.

## Phase 5: mandatory radical simplification pass 1

- [x] Ask "can I replace this gem with stdlib and make the repo smaller?"
- [x] Ask "can I collapse this into fewer files and still keep it readable?"
- [x] Ask "am I carrying Ruby framework habits into a tiny sidecar?"
- [x] Delete anything that fails those checks.

## Phase 6: mandatory radical simplification pass 2

- [x] Re-run tests and smoke from a green tree.
- [x] Ask "can I make this radically simpler?"
- [x] Remove wrappers that only save a few obvious lines.
- [x] Remove folder structure that makes the plugin look more official than
small.
- [x] Stop only when the repo still reads like a small third-party plugin.
93 changes: 93 additions & 0 deletions extended/docs/proposals/0004-send-message-step-plugin/plan.ruby.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# 0004 Ruby Plan

## Goal

- Show the same `send-message` plugin contract in a form that is tiny,
readable, and comfortable for a scripting-language user.
- Keep all plugin-owned code under `extended/plugins/send-message-ruby/`.
- Keep the subtree standalone enough to behave like its own Git repo.
- Match the same Slack-only contract as `spec.md`.

## Design Rule

- Prefer stdlib unless a gem removes real complexity.
- Prefer direct HTTPS to Kubernetes and Slack if that keeps the repo smaller.
- Do not chase a Ruby-flavored framework stack.
- If a gem exists only to save a small amount of obvious code, do not use it.

## Runtime Shape

- Runtime:
- Ruby
- Suggested layout:
- `extended/plugins/send-message-ruby/`
- `server.rb`
- `smoke/smoke_test.rb`
- `Gemfile`
- `Dockerfile`
- `plugin.yaml`
- `manifests/`
- `smoke/`
- Suggested server shape:
- one small HTTP server
- one request parser
- one Kubernetes reader
- one Slack sender

## Minimal Dependency Target

- Strong preference:
- stdlib `webrick`
- stdlib `json`
- stdlib `psych`
- stdlib `rexml`
- stdlib `net/http`
- Avoid:
- Rails-adjacent stacks
- Sinatra unless it clearly removes code
- large Kubernetes or Slack client wrappers

## Behavior

- Implement `POST /api/v1/step.execute`.
- Enforce bearer auth from `/var/run/kargo/token`.
- Read `MessageChannel` and `ClusterMessageChannel` directly from Kubernetes.
- Read referenced `Secret` directly from Kubernetes.
- Send Slack messages directly or through a tiny client if one clearly helps.
- Support:
- plaintext
- `json`
- `yaml`
- `xml`
- Match the response contract in `spec.md`.

## Tests

- Keep tests inside the subtree.
- Prefer a small test file and direct fixtures.
- Cover:
- auth
- channel lookup
- Secret lookup
- plaintext payload shaping
- encoded payload shaping
- XML decode shape
- Slack error handling

## Smoke

- Own `smoke/smoke_test.rb` inside the subtree.
- Assume Kargo already exists.
- Use Ruby for the smoke orchestration too.
- Build image, install manifests, create Secret and channel, run a Stage,
assert `Succeeded`, assert non-empty `slack.threadTS`.

## Mandatory Simplify Passes

- Simplify pass 1, before full smoke:
- ask "can I replace this gem with stdlib and make the repo smaller?"
- ask "can I collapse this into fewer files?"
- Simplify pass 2, after green:
- ask "can I make this radically simpler?"
- remove any Ruby structure that exists to feel proper rather than to keep
the plugin clear
14 changes: 14 additions & 0 deletions extended/plugins/send-message-ruby/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM ruby:3.4.7-alpine3.22

RUN gem install --no-document webrick

RUN addgroup -S plugin && adduser -S -G plugin -u 65532 plugin

WORKDIR /app

COPY send_message_plugin.rb /app/send_message_plugin.rb
COPY lib /app/lib

USER 65532:65532

ENTRYPOINT ["ruby", "/app/send_message_plugin.rb"]
39 changes: 39 additions & 0 deletions extended/plugins/send-message-ruby/lib/send_message_plugin.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
require "json"
require "logger"
require "net/http"
require "openssl"
require "psych"
require "rexml/document"
require "uri"
require "webrick"

require_relative "send_message_plugin/errors"
require_relative "send_message_plugin/kubernetes_client"
require_relative "send_message_plugin/payload_builder"
require_relative "send_message_plugin/slack_client"
require_relative "send_message_plugin/app"

module SendMessagePlugin
STEP_EXECUTE_PATH = "/api/v1/step.execute"
AUTH_HEADER = "Authorization"
BEARER_PREFIX = "Bearer "
DEFAULT_TOKEN_PATH = "/var/run/kargo/token"
DEFAULT_SYSTEM_RESOURCES_NAMESPACE = "kargo-system-resources"
DEFAULT_SLACK_API_BASE_URL = "https://slack.com/api"

def self.run!
app = App.new
server = WEBrick::HTTPServer.new(
BindAddress: "0.0.0.0",
Port: Integer(ENV.fetch("PORT", "9765")),
AccessLog: [],
Logger: WEBrick::Log.new($stderr, WEBrick::Log::WARN)
)
server.mount_proc(STEP_EXECUTE_PATH) do |req, res|
app.serve(req, res)
end
trap("INT") { server.shutdown }
trap("TERM") { server.shutdown }
server.start
end
end
Loading
Loading