@daisy-workflow/plugin-aws-s3 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.dockerignore ADDED
@@ -0,0 +1,9 @@
1
+ .git
2
+ .gitignore
3
+ .DS_Store
4
+ node_modules
5
+ npm-debug.log
6
+ *.tgz
7
+ README.md
8
+ LICENSE
9
+ publish-docker.sh
@@ -0,0 +1,185 @@
1
+ # ============================================================================
2
+ # Release pipeline
3
+ # ============================================================================
4
+ # Fires on a SemVer tag push (vMAJOR.MINOR.PATCH). Publishes the package to
5
+ # the npm registry and the container image to Docker Hub, then cuts a
6
+ # GitHub Release with auto-generated notes.
7
+ #
8
+ # To release:
9
+ # 1. Bump "version" in package.json
10
+ # 2. git commit + git push
11
+ # 3. git tag v0.1.0 && git push origin v0.1.0
12
+ # 4. Sit back — GitHub Actions does the rest.
13
+ #
14
+ # Required repository secrets (Settings → Secrets and variables → Actions):
15
+ # NPM_TOKEN npm automation token with publish access to the
16
+ # @daisy-workflow scope. Create at npmjs.com → Access
17
+ # Tokens → Generate New Token → Automation.
18
+ # DOCKERHUB_USERNAME Docker Hub user that owns the daisy-plugin-* images
19
+ # (currently: vivek13186).
20
+ # DOCKERHUB_TOKEN Docker Hub access token (Account → Security →
21
+ # New Access Token, read + write + delete).
22
+ #
23
+ # Derived values (no need to edit per repo):
24
+ # npm name = read from package.json (e.g. @daisy-workflow/plugin-jira)
25
+ # docker img = vivek13186/daisy-<plugin-name> (e.g. vivek13186/daisy-plugin-jira)
26
+ # version = read from package.json; tag must match.
27
+ #
28
+ # Action versions:
29
+ # actions/checkout@v5 and actions/setup-node@v5 run on Node 24 — required
30
+ # to avoid the deprecation warning announced 2025-09-19, where GitHub
31
+ # begins migrating JavaScript-based actions off Node 20 on 2026-06-02.
32
+ # The docker/* actions (login@v3, setup-buildx@v3, setup-qemu@v3,
33
+ # build-push@v6) are already on majors that ship Node 24-compatible
34
+ # minor releases, so no version bump needed there.
35
+ # ============================================================================
36
+
37
+ name: release
38
+
39
+ on:
40
+ push:
41
+ tags:
42
+ - "v*.*.*"
43
+ workflow_dispatch:
44
+ # Manual fallback — useful when a previous run failed half-way through
45
+ # and we want to re-publish without bumping the tag.
46
+
47
+ permissions:
48
+ contents: write # for `gh release create`
49
+ id-token: write # for `npm publish --provenance`
50
+
51
+ jobs:
52
+
53
+ # --------------------------------------------------------------------------
54
+ # 1. Verify — package.json sanity check + derive image name once.
55
+ # --------------------------------------------------------------------------
56
+ verify:
57
+ name: Verify version
58
+ runs-on: ubuntu-latest
59
+ outputs:
60
+ version: ${{ steps.read.outputs.version }}
61
+ image: ${{ steps.read.outputs.image }}
62
+ npm: ${{ steps.read.outputs.npm }}
63
+ steps:
64
+ - uses: actions/checkout@v5
65
+
66
+ - name: Read package.json
67
+ id: read
68
+ shell: bash
69
+ run: |
70
+ VERSION=$(node -p "require('./package.json').version")
71
+ NPM_NAME=$(node -p "require('./package.json').name")
72
+ # Strip the @daisy-workflow/ scope to get the short plugin name,
73
+ # then prefix with the Docker Hub namespace + daisy- convention.
74
+ SHORT_NAME="${NPM_NAME#@daisy-workflow/}"
75
+ IMAGE="vivek13186/daisy-${SHORT_NAME}"
76
+ echo "version=$VERSION" >> "$GITHUB_OUTPUT"
77
+ echo "npm=$NPM_NAME" >> "$GITHUB_OUTPUT"
78
+ echo "image=$IMAGE" >> "$GITHUB_OUTPUT"
79
+ echo "::notice::Releasing $NPM_NAME@$VERSION → $IMAGE:$VERSION"
80
+
81
+ - name: Check tag matches package.json
82
+ if: github.event_name == 'push'
83
+ shell: bash
84
+ run: |
85
+ TAG="${GITHUB_REF#refs/tags/}"
86
+ EXPECTED="v${{ steps.read.outputs.version }}"
87
+ if [ "$TAG" != "$EXPECTED" ]; then
88
+ echo "::error::Tag '$TAG' does not match package.json version '$EXPECTED'."
89
+ echo "Either fix the tag or bump package.json before tagging."
90
+ exit 1
91
+ fi
92
+
93
+ # --------------------------------------------------------------------------
94
+ # 2. npm — publish with provenance attestation.
95
+ # --------------------------------------------------------------------------
96
+ npm:
97
+ name: Publish to npm
98
+ needs: verify
99
+ runs-on: ubuntu-latest
100
+ steps:
101
+ - uses: actions/checkout@v5
102
+
103
+ - uses: actions/setup-node@v5 # v5 runs on Node 24
104
+ with:
105
+ node-version: "22" # Node version used to BUILD/PUBLISH the package (independent of the runtime above)
106
+ registry-url: "https://registry.npmjs.org"
107
+
108
+ - name: Install dependencies
109
+ run: npm install --omit=dev
110
+
111
+ - name: npm publish
112
+ # --access public is required for scoped packages on the free tier.
113
+ # --provenance ties the package to this exact workflow run; npm
114
+ # surfaces it as a "verified" badge on the package page.
115
+ run: npm publish --access public --provenance
116
+ env:
117
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
118
+
119
+ # --------------------------------------------------------------------------
120
+ # 3. Docker — multi-arch build + push to Docker Hub.
121
+ # --------------------------------------------------------------------------
122
+ docker:
123
+ name: Build & push Docker image
124
+ needs: verify
125
+ runs-on: ubuntu-latest
126
+ steps:
127
+ - uses: actions/checkout@v5
128
+
129
+ - name: Set up QEMU
130
+ # Lets us cross-compile arm64 on an amd64 GitHub runner.
131
+ uses: docker/setup-qemu-action@v3
132
+
133
+ - name: Set up Buildx
134
+ uses: docker/setup-buildx-action@v3
135
+
136
+ - name: Log in to Docker Hub
137
+ uses: docker/login-action@v3
138
+ with:
139
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
140
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
141
+
142
+ - name: Build & push (linux/amd64, linux/arm64)
143
+ uses: docker/build-push-action@v6
144
+ with:
145
+ context: .
146
+ file: ./Dockerfile
147
+ platforms: linux/amd64,linux/arm64
148
+ push: true
149
+ tags: |
150
+ ${{ needs.verify.outputs.image }}:${{ needs.verify.outputs.version }}
151
+ ${{ needs.verify.outputs.image }}:latest
152
+ # GitHub Actions cache keeps subsequent builds fast (especially
153
+ # the arm64 leg, which would otherwise emulate from scratch).
154
+ cache-from: type=gha
155
+ cache-to: type=gha,mode=max
156
+ # OCI labels so the image is self-describing on Docker Hub.
157
+ labels: |
158
+ org.opencontainers.image.title=${{ needs.verify.outputs.npm }}
159
+ org.opencontainers.image.version=${{ needs.verify.outputs.version }}
160
+ org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
161
+ org.opencontainers.image.revision=${{ github.sha }}
162
+ org.opencontainers.image.licenses=MIT
163
+
164
+ # --------------------------------------------------------------------------
165
+ # 4. Cut a GitHub Release with notes generated from commit history.
166
+ # Skipped on workflow_dispatch — manual re-publishes don't create a
167
+ # release because there's no new tag to attach it to.
168
+ # --------------------------------------------------------------------------
169
+ github-release:
170
+ name: GitHub release
171
+ needs: [verify, npm, docker]
172
+ if: github.event_name == 'push'
173
+ runs-on: ubuntu-latest
174
+ steps:
175
+ - uses: actions/checkout@v5
176
+ with:
177
+ fetch-depth: 0 # full history so generate-notes can diff to prev tag
178
+
179
+ - name: Create release
180
+ env:
181
+ GH_TOKEN: ${{ github.token }}
182
+ run: |
183
+ gh release create "${GITHUB_REF#refs/tags/}" \
184
+ --title "v${{ needs.verify.outputs.version }}" \
185
+ --generate-notes
package/Dockerfile ADDED
@@ -0,0 +1,20 @@
1
+ # aws-s3 plugin — one container, one endpoint, many operations.
2
+ #
3
+ # Self-contained: pulls @daisy-workflow/plugin-sdk from npm; no
4
+ # repo-root build context needed. No native or system dependencies —
5
+ # SigV4 (both header-based and query-string presigned) is hand-rolled
6
+ # against node:crypto.
7
+
8
+ FROM node:22-alpine
9
+ WORKDIR /workspace
10
+
11
+ COPY package.json ./
12
+ RUN npm install --omit=dev
13
+
14
+ COPY . ./
15
+
16
+ ENV PORT=8080
17
+ EXPOSE 8080
18
+ USER node
19
+
20
+ CMD ["node", "index.js"]
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vivek Gangadharan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,163 @@
1
+ # aws-s3 plugin for Daisy-workflow
2
+
3
+ One Daisy node that talks to **AWS S3 specifically** — with full
4
+ support for AWS-only features: storage classes, server-side encryption,
5
+ KMS, object tags, requester pays, and presigned URLs. Mirrors n8n's
6
+ [AWS S3 node](https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.awss3/).
7
+
8
+ [![Docker Hub](https://img.shields.io/badge/Docker%20Hub-Image-blue?logo=docker)](https://hub.docker.com/repository/docker/vivek13186/daisy-plugin-aws-s3)
9
+
10
+
11
+ > Need Wasabi, MinIO, Cloudflare R2, DigitalOcean Spaces, Backblaze B2,
12
+ > or another S3-compatible provider? Use the **generic `s3` plugin**
13
+ > instead — it accepts an arbitrary endpoint and `forcePathStyle`.
14
+
15
+ The action is selected per-node via the **operation** dropdown.
16
+
17
+ ## Operations
18
+
19
+ | operation | What it does |
20
+ |---|---|
21
+ | `bucket.getAll` | List all buckets the credential has access to. |
22
+ | `bucket.create` | Create a new bucket. Sends `LocationConstraint` when region ≠ us-east-1. |
23
+ | `bucket.delete` | Delete a bucket (bucket must be empty). |
24
+ | `bucket.search` | List objects in a bucket matching a `prefix`. |
25
+ | `bucket.location` | Get the bucket's region (`?location` API). |
26
+ | `file.getAll` | List objects with optional `prefix` + pagination. |
27
+ | `file.head` | Get object metadata only (no body) — content-type, size, storage class, SSE config, user metadata. |
28
+ | `file.upload` | PUT an object. Supports `storageClass`, `serverSideEncryption`, `ssekmsKeyId`, `tags`, `acl`, `requesterPays`. |
29
+ | `file.download` | GET an object. Body returned as `base64` (default) or `utf8`. |
30
+ | `file.copy` | Copy with optional destination SSE / storage class / tags. |
31
+ | `file.delete` | Delete an object. |
32
+ | `file.presignedUrl` | Generate a time-limited URL that anyone can use to GET (or PUT) the object — no API call, pure crypto. |
33
+ | `folder.create` | Create a "folder" placeholder (empty object with key ending in `/`). |
34
+ | `folder.getAll` | List "folders" using `delimiter` (default `/`) → CommonPrefixes. |
35
+ | `folder.delete` | Recursively delete everything under a prefix via bulk MultiObjectDelete. |
36
+
37
+ ## Configure auth
38
+
39
+ Create one **generic** config on the **Configurations** page (default
40
+ name `aws-s3`):
41
+
42
+ | Key | Example | Notes |
43
+ |-------------------|-----------------------------------------------|----------------------------------------------------|
44
+ | `accessKeyId` | `AKIA…` | IAM user or role access key |
45
+ | `secretAccessKey` | `…` | |
46
+ | `region` | `us-east-1` / `eu-central-1` / `ap-south-1` | Drives the endpoint and the SigV4 signing region |
47
+ | `sessionToken` | `…` | Optional — STS / AssumeRole / EC2 instance role |
48
+ | `customEndpoint` | `https://bucket.vpce-….s3.us-east-1.vpce.amazonaws.com` | Optional — VPC interface endpoint, S3 Transfer Acceleration host, AWS GovCloud, AWS China |
49
+
50
+ The endpoint is auto-derived from the region. `cn-*` regions
51
+ automatically use `amazonaws.com.cn`. Override with `customEndpoint`
52
+ for transfer acceleration (`s3-accelerate.amazonaws.com`),
53
+ VPC interface endpoints, or air-gapped AWS partitions.
54
+
55
+ A node can override the config name per-call via the `config` input
56
+ and the region per-call via the `region` input.
57
+
58
+ ## AWS-specific upload extras
59
+
60
+ | Input | Sent as | Notes |
61
+ |---|---|---|
62
+ | `storageClass` | `x-amz-storage-class` | `STANDARD` / `INTELLIGENT_TIERING` / `STANDARD_IA` / `ONEZONE_IA` / `GLACIER` / `DEEP_ARCHIVE` / `GLACIER_IR` / `REDUCED_REDUNDANCY` |
63
+ | `serverSideEncryption` | `x-amz-server-side-encryption` | `AES256` / `aws:kms` / `aws:kms:dsse` |
64
+ | `ssekmsKeyId` | `x-amz-server-side-encryption-aws-kms-key-id` | KMS key ID or ARN when using `aws:kms*` |
65
+ | `tags` | `x-amz-tagging` | Object → URL-encoded query string: `{env:'prod', team:'data'}` → `env=prod&team=data` |
66
+ | `acl` | `x-amz-acl` | Note: many AWS accounts disable ACLs ("Object Ownership = Bucket owner enforced"); the header is then rejected |
67
+ | `requesterPays` | `x-amz-request-payer: requester` | Required when reading from a Requester-Pays bucket |
68
+ | `metadata` | `x-amz-meta-<key>` | Custom user metadata; one header per key |
69
+
70
+ `file.copy` accepts the same extras for the destination object.
71
+
72
+ ## Presigned URLs
73
+
74
+ ```jsonc
75
+ // input
76
+ {
77
+ "operation": "file.presignedUrl",
78
+ "bucket": "uploads",
79
+ "key": "users/42/avatar.png",
80
+ "presignedMethod": "PUT", // GET (default) or PUT
81
+ "presignedExpiresIn": 3600 // seconds; max 604800 (7 days)
82
+ }
83
+
84
+ // result
85
+ {
86
+ "bucket": "uploads",
87
+ "key": "users/42/avatar.png",
88
+ "method": "PUT",
89
+ "expiresIn": 3600,
90
+ "expiresAt": "2026-05-18T09:30:00.000Z",
91
+ "url": "https://uploads.s3.us-east-1.amazonaws.com/users/42/avatar.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=…&X-Amz-Signature=…"
92
+ }
93
+ ```
94
+
95
+ No HTTP call is made — pure crypto. Hand this URL to a browser or curl
96
+ to upload / download without exposing the AWS credentials.
97
+
98
+ ## Install
99
+
100
+ ```bash
101
+ docker compose -f docker-compose.yml -f docker-compose.plugins.yml \
102
+ --profile aws-s3 up -d
103
+
104
+ npm run install-plugin -- --endpoint http://daisy-aws-s3:8080
105
+ ```
106
+
107
+ ## Output envelope
108
+
109
+ ```json
110
+ {
111
+ "ok": true,
112
+ "operation": "file.upload",
113
+ "status": 200,
114
+ "result": { "bucket": "logs", "key": "2026/05/event.json", "size": 412, "etag": "ab12…", "serverSideEncryption": "AES256" },
115
+ "url": "https://logs.s3.us-east-1.amazonaws.com/2026/05/event.json"
116
+ }
117
+ ```
118
+
119
+ Operation-specific `result` shapes are documented inline in
120
+ `lib/actions.js`. Highlights:
121
+
122
+ - `bucket.getAll` → `{ owner, buckets: [{ name, creationDate }], count }`
123
+ - `bucket.location` → `{ bucket, location }` (always real region; us-east-1 is normalized from the empty `<LocationConstraint/>` AWS returns)
124
+ - `file.head` → `{ bucket, key, contentType, contentLength, etag, lastModified, versionId, storageClass, serverSideEncryption, ssekmsKeyId, metadata }`
125
+ - `file.download` → `{ bucket, key, contentType, contentLength, etag, lastModified, encoding, data }`
126
+ - `file.presignedUrl` → see above
127
+
128
+ ## Auth model — why hand-rolled SigV4?
129
+
130
+ Both header-based signing and query-string presigning are implemented in
131
+ `lib/sigv4.js` (~200 lines, zero deps, validated against AWS's
132
+ canonical S3 test vector). Keeps the container tiny and the dependency
133
+ surface to exactly one package (the Daisy plugin SDK).
134
+
135
+ If you need features that go beyond what this plugin offers — multipart
136
+ upload for >5GB files, S3 Select, batch operations, event subscriptions,
137
+ inventory configs — drop in `@aws-sdk/client-s3` and add a new
138
+ operation that uses it.
139
+
140
+ ## Files
141
+
142
+ ```
143
+ plugins-external/aws-s3/
144
+ ├── manifest.json
145
+ ├── index.js
146
+ ├── lib/
147
+ │ ├── sigv4.js # signRequest() + presignUrl(), no deps
148
+ │ ├── client.js # auth + endpoint derivation + signed fetch
149
+ │ └── actions.js # one async handler per operation
150
+ ├── package.json
151
+ ├── Dockerfile
152
+ ├── publish-docker.sh
153
+ └── README.md
154
+ ```
155
+
156
+ ## Publish the image
157
+
158
+ ```bash
159
+ docker login
160
+ ./publish-docker.sh
161
+ ```
162
+
163
+ Env overrides: `IMAGE=foo/bar`, `PLATFORMS=linux/amd64`, `PUSH=0`, `NO_LATEST=1`.
package/index.js ADDED
@@ -0,0 +1,51 @@
1
+ // aws-s3 — AWS-specific S3 from a workflow. The action is selected
2
+ // per-node via the `operation` input. Mirrors n8n's AWS S3 node.
3
+ //
4
+ // Wire it up:
5
+ // 1. `docker compose -f docker-compose.yml -f docker-compose.plugins.yml \
6
+ // --profile aws-s3 up -d`
7
+ // `npm run install-plugin -- --endpoint http://daisy-aws-s3:8080`
8
+ // 2. Create a workspace `generic` config named "aws-s3" with:
9
+ // accessKeyId, secretAccessKey, region (+ optional sessionToken, customEndpoint)
10
+ // 3. Use the node in any workflow.
11
+ //
12
+ // For non-AWS S3-compatible providers (Wasabi, MinIO, R2, B2…) use the
13
+ // generic `s3` plugin instead — it accepts an arbitrary endpoint and
14
+ // supports forcePathStyle.
15
+
16
+ import { servePlugin } from "@daisy-workflow/plugin-sdk";
17
+ import fs from "node:fs";
18
+
19
+ import { loadAwsAuth } from "./lib/client.js";
20
+ import { OPERATIONS } from "./lib/actions.js";
21
+
22
+ const manifest = JSON.parse(
23
+ fs.readFileSync(new URL("./manifest.json", import.meta.url), "utf8"),
24
+ );
25
+
26
+ servePlugin({
27
+ manifest,
28
+ async execute(input, ctx) {
29
+ const { operation, config = "aws-s3" } = input || {};
30
+ if (!operation) throw new Error("`operation` is required (see manifest enum for valid values)");
31
+
32
+ const handler = OPERATIONS[operation];
33
+ if (!handler) {
34
+ throw new Error(
35
+ `unknown operation "${operation}". Valid: ${Object.keys(OPERATIONS).join(", ")}`,
36
+ );
37
+ }
38
+
39
+ const auth = loadAwsAuth(ctx, config, input?.region);
40
+ const { status, result, url } = await handler(auth, input, ctx?.signal);
41
+
42
+ return {
43
+ ok: true,
44
+ operation,
45
+ status,
46
+ result,
47
+ url,
48
+ };
49
+ },
50
+ async readyz() { return true; },
51
+ });