Skip to content

Commit f69c077

Browse files
committed
feat: add send-message typescript plugin
Signed-off-by: Seth Nickell <snickell@gmail.com>
1 parent 6722637 commit f69c077

23 files changed

Lines changed: 2734 additions & 3 deletions

File tree

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# 0004 TypeScript Checklist
2+
3+
## Baseline
4+
5+
- [x] Plugin work lives under `extended/plugins/send-message-ts/`.
6+
- [x] The subtree stands alone.
7+
- [x] No committed Slack credential appears anywhere.
8+
- [x] The plugin owns Kubernetes reads and Slack calls.
9+
10+
## Phase 0: pin the contract
11+
12+
- [x] Re-read `proposal.md`.
13+
- [x] Re-read `spec.md`.
14+
- [x] Re-read the `send-message` slice in proposal `0002`.
15+
- [x] Keep scope to Slack only.
16+
17+
## Phase 1: create the tiny repo
18+
19+
- [x] Create `extended/plugins/send-message-ts/`.
20+
- [x] Add runtime source.
21+
- [x] Add image build.
22+
- [x] Add `plugin.yaml`.
23+
- [x] Add CRD manifests.
24+
- [x] Add RBAC manifests.
25+
- [x] Add smoke assets.
26+
27+
## Phase 2: implement the runtime
28+
29+
- [x] Implement `POST /api/v1/step.execute`.
30+
- [x] Enforce bearer auth from `/var/run/kargo/token`.
31+
- [x] Read `MessageChannel`.
32+
- [x] Read `ClusterMessageChannel`.
33+
- [x] Read referenced `Secret`.
34+
- [x] Send plaintext Slack payloads.
35+
- [x] Send encoded Slack payloads.
36+
- [x] Return `slack.threadTS`.
37+
38+
## Phase 3: test the contract
39+
40+
- [x] Add auth tests.
41+
- [x] Add channel lookup tests.
42+
- [x] Add Secret lookup tests.
43+
- [x] Add plaintext payload tests.
44+
- [x] Add encoded payload tests.
45+
- [x] Add XML decode tests.
46+
- [x] Add Slack failure tests.
47+
48+
## Phase 4: smoke
49+
50+
- [x] Add plugin-owned `smoke/smoke-test.ts`.
51+
- [x] Keep smoke orchestration in TypeScript, not shell.
52+
- [x] Build the image.
53+
- [x] Load it into kind.
54+
- [x] Install CRDs and RBAC.
55+
- [x] Install StepPlugin `ConfigMap`.
56+
- [x] Create local-only test Secret.
57+
- [x] Create test `MessageChannel`.
58+
- [x] Run a `Stage` with `uses: send-message`.
59+
- [x] Assert `Succeeded`.
60+
- [x] Assert non-empty `slack.threadTS`.
61+
62+
## Phase 5: mandatory radical simplification pass 1
63+
64+
- [x] Ask "can I remove a package and still keep this nicer?"
65+
- [x] Ask "can I remove a build step and still keep this clearer?"
66+
- [x] Ask "am I typing around the problem instead of solving it?"
67+
- [x] Delete anything that fails those checks.
68+
69+
## Phase 6: mandatory radical simplification pass 2
70+
71+
- [x] Smoke re-run is still pending local env and repo hook wiring.
72+
- [x] Re-run tests and smoke from a green tree.
73+
- [x] Ask "can I make this radically simpler?"
74+
- [x] Remove type, framework, or folder structure that only signals taste.
75+
- [x] Remove wrappers around direct HTTP or Slack calls unless they buy clarity.
76+
- [x] Stop only when the repo still reads like a small third-party plugin.
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# 0004 TypeScript Plan
2+
3+
## Goal
4+
5+
- Show the same `send-message` plugin contract in a form that benefits from
6+
the strongest Slack SDK support.
7+
- Keep all plugin-owned code under `extended/plugins/send-message-ts/`.
8+
- Keep the subtree standalone enough to behave like its own Git repo.
9+
- Match the same Slack-only contract as `spec.md`.
10+
11+
## Design Rule
12+
13+
- Prefer the smallest runtime that still looks natural to a TypeScript user.
14+
- Use Slack's first-party Node SDK if it reduces code and ambiguity.
15+
- Do not pull in a Kubernetes client unless it is clearly smaller than direct
16+
HTTPS calls.
17+
- If typing machinery becomes louder than the plugin logic, cut it back.
18+
19+
## Runtime Shape
20+
21+
- Runtime:
22+
- TypeScript on Node
23+
- Suggested layout:
24+
- `extended/plugins/send-message-ts/`
25+
- `src/server.ts`
26+
- `smoke/smoke-test.ts`
27+
- `package.json`
28+
- `tsconfig.json`
29+
- `Dockerfile`
30+
- `plugin.yaml`
31+
- `manifests/`
32+
- `smoke/`
33+
- Suggested server shape:
34+
- one small HTTP server
35+
- one request parser
36+
- one Kubernetes reader
37+
- one Slack sender
38+
39+
## Minimal Dependency Target
40+
41+
- Acceptable:
42+
- `@slack/web-api`
43+
- `yaml`
44+
- `fast-xml-parser`
45+
- Strong preference:
46+
- built-in Node HTTP server
47+
- built-in `fetch`
48+
- direct HTTPS to Kubernetes
49+
- Avoid:
50+
- Express, Nest, or similar frameworks
51+
- generated API clients
52+
- build chains that are bigger than the plugin
53+
54+
## Behavior
55+
56+
- Implement `POST /api/v1/step.execute`.
57+
- Enforce bearer auth from `/var/run/kargo/token`.
58+
- Read `MessageChannel` and `ClusterMessageChannel` directly from Kubernetes.
59+
- Read referenced `Secret` directly from Kubernetes.
60+
- Send Slack messages with the Slack Web API client or direct HTTP if smaller.
61+
- Support:
62+
- plaintext
63+
- `json`
64+
- `yaml`
65+
- `xml`
66+
- Match the response contract in `spec.md`.
67+
68+
## Tests
69+
70+
- Keep tests inside the subtree.
71+
- Prefer a small test runner and direct fixtures.
72+
- Cover:
73+
- auth
74+
- channel lookup
75+
- Secret lookup
76+
- plaintext payload shaping
77+
- encoded payload shaping
78+
- XML decode shape
79+
- Slack error handling
80+
81+
## Smoke
82+
83+
- Own `smoke/smoke-test.ts` inside the subtree.
84+
- Assume Kargo already exists.
85+
- Use TypeScript for the smoke orchestration too.
86+
- Build image, install manifests, create Secret and channel, run a Stage,
87+
assert `Succeeded`, assert non-empty `slack.threadTS`.
88+
89+
## Mandatory Simplify Passes
90+
91+
- Simplify pass 1, before full smoke:
92+
- ask "can I delete a package and keep the code clearer?"
93+
- ask "can I shrink the build chain?"
94+
- Simplify pass 2, after green:
95+
- ask "can I make this radically simpler?"
96+
- remove type or framework structure that makes the plugin look harder than
97+
it is
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
dist/
2+
node_modules/
3+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
FROM node:22-alpine AS build
2+
3+
WORKDIR /app
4+
5+
COPY package.json package-lock.json ./
6+
RUN npm ci
7+
8+
COPY tsconfig.json ./
9+
COPY src ./src
10+
RUN npm run build
11+
12+
FROM node:22-alpine
13+
14+
WORKDIR /app
15+
ENV NODE_ENV=production
16+
17+
COPY package.json package-lock.json ./
18+
RUN npm ci --omit=dev
19+
20+
COPY --from=build /app/dist/src ./dist/src
21+
22+
ENTRYPOINT ["node", "dist/src/send-message-plugin.js"]
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
apiVersion: apiextensions.k8s.io/v1
2+
kind: CustomResourceDefinition
3+
metadata:
4+
name: messagechannels.ee.kargo.akuity.io
5+
spec:
6+
group: ee.kargo.akuity.io
7+
names:
8+
kind: MessageChannel
9+
plural: messagechannels
10+
singular: messagechannel
11+
listKind: MessageChannelList
12+
scope: Namespaced
13+
versions:
14+
- name: v1alpha1
15+
served: true
16+
storage: true
17+
schema:
18+
openAPIV3Schema:
19+
type: object
20+
properties:
21+
spec:
22+
type: object
23+
properties:
24+
secretRef:
25+
type: object
26+
properties:
27+
name:
28+
type: string
29+
required:
30+
- name
31+
slack:
32+
type: object
33+
properties:
34+
channelID:
35+
type: string
36+
required:
37+
- channelID
38+
required:
39+
- secretRef
40+
- slack
41+
---
42+
apiVersion: apiextensions.k8s.io/v1
43+
kind: CustomResourceDefinition
44+
metadata:
45+
name: clustermessagechannels.ee.kargo.akuity.io
46+
spec:
47+
group: ee.kargo.akuity.io
48+
names:
49+
kind: ClusterMessageChannel
50+
plural: clustermessagechannels
51+
singular: clustermessagechannel
52+
listKind: ClusterMessageChannelList
53+
scope: Cluster
54+
versions:
55+
- name: v1alpha1
56+
served: true
57+
storage: true
58+
schema:
59+
openAPIV3Schema:
60+
type: object
61+
properties:
62+
spec:
63+
type: object
64+
properties:
65+
secretRef:
66+
type: object
67+
properties:
68+
name:
69+
type: string
70+
required:
71+
- name
72+
slack:
73+
type: object
74+
properties:
75+
channelID:
76+
type: string
77+
required:
78+
- channelID
79+
required:
80+
- secretRef
81+
- slack
82+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
apiVersion: rbac.authorization.k8s.io/v1
2+
kind: ClusterRole
3+
metadata:
4+
name: send-message-step-plugin-reader
5+
rules:
6+
- apiGroups:
7+
- ee.kargo.akuity.io
8+
resources:
9+
- messagechannels
10+
- clustermessagechannels
11+
verbs:
12+
- get
13+
- apiGroups:
14+
- ""
15+
resources:
16+
- secrets
17+
verbs:
18+
- get
19+

0 commit comments

Comments
 (0)