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,94 @@
# 0004 Implementation Checklist

Update this as implementation teaches us things.

## Baseline

- [x] Plugin work lives under `extended/plugins/send-message`.
- [x] The plugin subtree does not rely on repo resources outside itself.
- [x] Slack credentials stay local-only and out of git.
- [x] The host does not own channel lookup, Secret reads, or Slack API calls.

## Phase 0: read and pin the seams

- [x] Read proposal 0004 again before starting code.
- [x] Re-read the `send-message` section in proposal 0002 spec.
- [x] Read the current StepPlugin host runtime and e2e harness seams.
- [x] Record any host-side gap found during that read.

## Phase 1: create the standalone subtree

- [x] Create `extended/plugins/send-message/`.
- [x] Add plugin-owned runtime source there.
- [x] Add plugin-owned image build there.
- [x] Add plugin-owned tests there.
- [x] Add plugin-owned CRD manifests there.
- [x] Add plugin-owned RBAC manifests there.
- [x] Add plugin-owned smoke assets or smoke helper there.

## Phase 2: implement the runtime

- [x] Implement `POST /api/v1/step.execute`.
- [x] Enforce bearer-token auth from `/var/run/kargo/token`.
- [x] Return `403` on bad auth.
- [x] Reject unsupported methods cleanly.
- [x] Parse the v1 step-config subset.
- [x] Resolve `MessageChannel` from the Project namespace.
- [x] Resolve `ClusterMessageChannel` from the system-resources namespace.
- [x] Resolve referenced Secrets from the correct namespace.
- [x] Read Slack token from Secret key `apiKey`.
- [x] Send the Slack message from the plugin.
- [x] Return `slack.threadTS` output.

## Phase 3: own the OSS CRDs and RBAC

- [x] Add Slack-only `MessageChannel` CRD manifest.
- [x] Add Slack-only `ClusterMessageChannel` CRD manifest.
- [x] Keep the API group `ee.kargo.akuity.io/v1alpha1`.
- [x] Add plugin-owned RBAC manifests for channel and Secret reads.
- [x] Keep RBAC setup out of host code unless a real host gap is proven.

## Phase 4: tests inside the subtree

- [x] Add runtime tests for auth.
- [x] Add runtime tests for namespaced channel lookup.
- [x] Add runtime tests for cluster-scoped channel lookup.
- [x] Add runtime tests for referenced Secret lookup.
- [x] Add runtime tests for Slack request shaping.
- [x] Add runtime tests for `slack.channelID` override.
- [x] Add runtime tests for `slack.threadTS` override and output.
- [x] Add runtime tests for missing channel or Secret failures.
- [x] Add runtime tests for Slack API failure handling.

## Phase 5: local smoke path

- [x] Build the plugin image from `extended/plugins/send-message`.
- [x] Load the image into the local kind cluster.
- [x] Keep kube access on an isolated `KUBECONFIG`.
- [x] Keep the user's existing kube context untouched.
- [x] Install plugin CRDs and RBAC.
- [x] Generate and install the StepPlugin `ConfigMap`.
- [x] Inject a local-only Slack token into a cluster `Secret`.
- [x] Create a test `MessageChannel` or `ClusterMessageChannel`.
- [x] Run a `Stage` with `uses: send-message`.
- [x] Prove promotion success in-cluster.
- [x] Prove `slack.threadTS` output is populated.
- [x] Verify the message appeared in the target Slack channel.

## Phase 6: repo harness integration

- [x] Extend `extended/tests/e2e_stepplugins.sh` to support the real
`send-message` smoke path.
- [x] Keep the committed harness credential-free.
- [x] Gate Slack smoke on a local env var for the token.
- [x] Keep any non-`extended/` edit to a tiny hook only, if one is needed.

## Phase Post-Green: Minimize Diff Of Files Outside ./extended Against Kargo Upstream

- [x] Fetch `upstream`.
- [x] Review every edited file outside `extended/`, if any, against
`upstream/main`.
- [x] Move more logic behind `extended/` helpers if that shrinks the outside
diff safely.
- [x] Re-run matching tests after each cleanup pass.
- [x] Stop only when no obvious outside-`extended/` shrink remains.
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# 0004 Implementation Notes

- Plugin subtree:
- `extended/plugins/send-message/`
- standalone `go.mod`
- standalone `Dockerfile`
- standalone `plugin.yaml`
- standalone CRDs and RBAC under `manifests/`
- standalone smoke helper under `smoke/`

- Runtime contract implemented in the plugin:
- `POST /api/v1/step.execute`
- bearer auth from `/var/run/kargo/token`
- direct Kubernetes API reads from projected service-account credentials
- direct Slack API call from the plugin
- encoded `json`, `yaml`, and `xml` payload support

- V1 scope shipped:
- `send-message`
- `MessageChannel`
- `ClusterMessageChannel`
- Slack only
- no SMTP

- Secret and channel rules implemented:
- `secretRef.name` resolves in the Project namespace for `MessageChannel`
- `secretRef.name` resolves in the system-resources namespace for
`ClusterMessageChannel`
- Slack token key is `apiKey`
- `spec.slack.channelID` is required in CRD schema

- Smoke harness knobs:
- `STEPPLUGIN_SEND_MESSAGE_SMOKE=true`
- `STEPPLUGIN_SEND_MESSAGE_CHANNEL_ID=<channel-id>`
- `STEPPLUGIN_SEND_MESSAGE_SLACK_API_KEY=<token>`
- Plugin-local smoke entrypoint:
- `extended/plugins/send-message/smoke/smoke-test.sh`
- repo harness calls that script rather than owning the full orchestration

- Host gap found during smoke:
- the StepPlugin auth-token mount was not readable by non-root sidecars
- fix was in `extended/pkg/stepplugin/agent/command_bridge.go`
- auth directory mode is now `0755`
- auth file mode is now `0444`

- Smoke-result note:
- plugin response includes `output.slack.threadTS`
- Promotion state observed in-cluster stored the value under
`status.state.step-1.slack.threadTS`
- the smoke harness accepts either that path or
`status.stepExecutionMetadata[0].output.slack.threadTS`

- Encoded-message note:
- when `encodingType` is set, `config.slack.channelID` and
`config.slack.threadTS` are ignored
- encoded body uses Slack-native field names such as `channel` and
`thread_ts`
- if encoded body omits `channel`, plugin fills it from channel resource
`spec.slack.channelID`

- XML note:
- no public exact upstream XML example was found
- this implementation treats XML as a Kargo-owned alternate serialization of
the same Slack payload object used for `json` and `yaml`
- root name is ignored
- repeated sibling names become arrays

- Non-plugin repo fix found during smoke:
- `pkg/cli/cmd/promote/promote.go` assumed wrapped REST payloads
- local smoke showed direct-object and direct-list payloads
- decoder helpers now accept both shapes
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# 0004 Implementation Plan

## Goal

- Deliver a real Slack-only `send-message` StepPlugin under
`extended/plugins/send-message`.
- Keep that subtree self-contained enough to behave like its own Git repo.
- Do not rely on code, files, build helpers, tests, or other resources outside
`extended/plugins/send-message` for the plugin implementation itself.
- Use the StepPlugin host slice from proposal
[0002-kargo-executor-plugins-from-argo-workflows](../0002-kargo-executor-plugins-from-argo-workflows/proposal.md)
without widening host responsibilities.
- Keep channel lookup and referenced Secret reads in the plugin, not the host.
- Use local-only Slack credentials for smoke testing. Do not commit any real
token or local token source.
- Make the plugin match the documented Slack subset, including encoded-message
behavior, except for SMTP which stays out of scope.

## Implementation Shape

- Put all plugin-owned source, manifests, tests, and smoke assets under:
- `extended/plugins/send-message/`
- Treat that subtree as a standalone plugin repo:
- own runtime source
- own image build
- own CRDs
- own RBAC manifests
- own tests
- own smoke-test helpers
- own smoke entrypoint script
- It is acceptable for repo-level smoke harness code outside that subtree to
call into the subtree's smoke entrypoint.
- It is not acceptable for plugin runtime code inside that subtree to import or
shell out to helpers elsewhere in this repo.

## Plugin Runtime

- Build a plugin-owned runtime image from `extended/plugins/send-message/`.
- Implement `POST /api/v1/step.execute`.
- Enforce the StepPlugin bearer-token contract:
- read `/var/run/kargo/token`
- require `Authorization: Bearer ...`
- return `403` on bad auth
- Read Kubernetes credentials from the mounted service account projection when
present.
- Use direct Kubernetes API reads from the plugin:
- `MessageChannel` in the Project namespace
- `ClusterMessageChannel` cluster-scoped
- referenced `Secret` in the Project namespace or system-resources namespace
- Call Slack from the plugin runtime, not through the host.

## V1 Behavior

- Scope:
- `send-message`
- `MessageChannel`
- `ClusterMessageChannel`
- Slack only
- Do not implement SMTP.
- Support this step-config subset in v1:
- `channel.kind`
- `channel.name`
- `message`
- `encodingType`
- `slack.channelID`
- `slack.threadTS`
- Support this output subset in v1:
- `slack.threadTS`
- Plaintext behavior:
- `message` is sent as Slack `text`
- `slack.channelID` overrides channel resource `spec.slack.channelID`
- `slack.threadTS` is sent as Slack `thread_ts`
- output `slack.threadTS` is `config.slack.threadTS` if set, else Slack
response `ts`
- Encoded-message behavior:
- support `json`, `yaml`, and `xml`
- when `encodingType` is set, treat the body as the Slack payload object
- ignore `config.slack.channelID` and `config.slack.threadTS`
- if encoded body omits `channel`, fill it from channel resource
`spec.slack.channelID`
- if encoded body sets `thread_ts`, return that value in output
- otherwise return Slack response `ts` in output
- XML behavior:
- XML is a Kargo-owned alternate serialization of the same Slack payload
object used for `json` and `yaml`
- root element name is ignored
- attributes become keys
- repeated sibling element names become arrays
- nested elements become nested objects
- `MessageChannel` lookup rules:
- resource kind is `MessageChannel`
- resource namespace is the Project namespace from the step context
- `secretRef.name` resolves in the same namespace
- `spec.slack.channelID` is required
- Slack token key is `apiKey`
- `ClusterMessageChannel` lookup rules:
- resource kind is `ClusterMessageChannel`
- `secretRef.name` resolves in the system-resources namespace configured for
the plugin install
- `spec.slack.channelID` is required
- Slack token key is `apiKey`

## CRDs And RBAC

- The plugin subtree owns the OSS CRD manifests for:
- `MessageChannel`
- `ClusterMessageChannel`
- Use the existing Akuity API group:
- `ee.kargo.akuity.io/v1alpha1`
- Keep schemas minimal but structurally valid for the Slack-only slice.
- Ship plugin-owned RBAC manifests under the subtree.
- For the local smoke path:
- bind only the test Project default `ServiceAccount`
- grant channel reads and referenced Secret reads needed by the plugin
- keep that setup in the smoke assets or smoke helper, not in host code

## Tests

- Keep plugin tests under `extended/plugins/send-message/`.
- Cover at least:
- bearer-token auth
- `MessageChannel` lookup
- `ClusterMessageChannel` lookup
- referenced Secret lookup
- Slack request shaping for plain text
- `slack.channelID` override
- `slack.threadTS` override
- `slack.threadTS` output
- encoded-body behavior ignores `config.slack.*`
- `xml` decode path
- failure path when channel or Secret is missing
- failure path when Slack returns an error

## Smoke Test

- Put the primary smoke script in:
- `extended/plugins/send-message/smoke/smoke-test.sh`
- That script assumes Kargo is already available and takes its inputs from env.
- Keep the committed smoke path credential-free.
- Expect a local env var for the Slack API token during local smoke testing.
- Build the plugin image from `extended/plugins/send-message/`.
- Load that image into the local kind cluster without touching the user's
global kube context.
- Install plugin CRDs, RBAC, and generated StepPlugin `ConfigMap`.
- Create either a `MessageChannel` or `ClusterMessageChannel` test resource and
the referenced Secret in-cluster from the local-only token source.
- Run a `Stage` with `uses: send-message`.
- Prove in-cluster success from `Promotion` status, including non-empty
`slack.threadTS`.
- For interactive local verification, also confirm the message appeared in the
target Slack channel.
- The repo harness may call this script at the right point in e2e flow, but the
smoke orchestration lives in the plugin subtree.

## Host Changes

- Prefer zero host changes.
- If the plugin proves a host gap, keep any host fix thin and behind
`extended/`.
- Do not move channel lookup, Secret reads, or Slack API calls into the host.
- Host gap actually found:
- non-root sidecars could not read `/var/run/kargo/token`
- keep the fix thin in `extended/pkg/stepplugin/agent/command_bridge.go`
- auth directory mode must permit traversal by non-root sidecars
- auth file mode must permit read by non-root sidecars

## Phase Post-Green: Minimize Diff Of Files Outside ./extended Against Kargo Upstream

1. Get the feature green first.
2. Fetch `upstream/main`.
3. Review every edited file outside `extended/`, if any.
4. Move logic behind `extended/` helpers where that shrinks the conflict
surface.
5. Re-run the matching tests after each cleanup pass.
6. Stop when no obvious helper extraction or edit-block reduction remains.
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ Date: 2026-03-24
`extended/plugins/send-message`.
- Use that subtree to test whether a third party can implement the plugin
without merge permissions in the main repo.
- Match the public Kargo `send-message` step shape for the Slack subset as
closely as public docs allow.
- Keep channel lookup and referenced Secret reads in the plugin, not the host.
- Do not implement SMTP in this slice.

Expand Down
Loading
Loading