@zincapp/znvault-plugin-payara 1.0.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/README.md ADDED
@@ -0,0 +1,316 @@
1
+ # @zincapp/znvault-plugin-payara
2
+
3
+ Payara application server management plugin for ZN-Vault Agent and CLI. Enables incremental WAR deployment with diff-based file transfer.
4
+
5
+ ## Features
6
+
7
+ - **WAR Diff Deployment**: Only transfer changed files, not entire WAR
8
+ - **Payara Lifecycle Management**: Start, stop, restart Payara domains
9
+ - **Certificate Integration**: Auto-restart on certificate deployment
10
+ - **Health Monitoring**: Plugin health status in agent health endpoint
11
+ - **CLI Commands**: Deploy WAR files from development machine
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install @zincapp/znvault-plugin-payara
17
+ ```
18
+
19
+ ## Agent Configuration
20
+
21
+ Add the plugin to your agent's `config.json`:
22
+
23
+ ```json
24
+ {
25
+ "vaultUrl": "https://vault.example.com",
26
+ "tenantId": "my-tenant",
27
+ "auth": { "apiKey": "znv_..." },
28
+ "plugins": [
29
+ {
30
+ "package": "@zincapp/znvault-plugin-payara",
31
+ "config": {
32
+ "payaraHome": "/opt/payara",
33
+ "domain": "domain1",
34
+ "user": "payara",
35
+ "warPath": "/opt/app/MyApp.war",
36
+ "appName": "MyApp",
37
+ "healthEndpoint": "http://localhost:8080/health",
38
+ "restartOnCertChange": true
39
+ }
40
+ }
41
+ ]
42
+ }
43
+ ```
44
+
45
+ ### Configuration Options
46
+
47
+ | Option | Type | Required | Description |
48
+ |--------|------|----------|-------------|
49
+ | `payaraHome` | string | Yes | Path to Payara installation |
50
+ | `domain` | string | Yes | Payara domain name |
51
+ | `user` | string | Yes | System user to run asadmin commands as |
52
+ | `warPath` | string | Yes | Path to the WAR file |
53
+ | `appName` | string | Yes | Application name in Payara |
54
+ | `healthEndpoint` | string | No | HTTP endpoint to check application health |
55
+ | `restartOnCertChange` | boolean | No | Restart Payara when certificates are deployed |
56
+
57
+ ## HTTP API
58
+
59
+ The plugin registers routes under `/plugins/payara/`:
60
+
61
+ ### GET /plugins/payara/hashes
62
+
63
+ Returns SHA-256 hashes of all files in the current WAR.
64
+
65
+ ```bash
66
+ curl http://localhost:9100/plugins/payara/hashes
67
+ ```
68
+
69
+ Response:
70
+ ```json
71
+ {
72
+ "hashes": {
73
+ "WEB-INF/web.xml": "abc123...",
74
+ "index.html": "def456..."
75
+ }
76
+ }
77
+ ```
78
+
79
+ ### POST /plugins/payara/deploy
80
+
81
+ Apply file changes and deploy. Files are base64-encoded.
82
+
83
+ ```bash
84
+ curl -X POST http://localhost:9100/plugins/payara/deploy \
85
+ -H "Content-Type: application/json" \
86
+ -d '{
87
+ "files": [
88
+ {"path": "index.html", "content": "PGh0bWw+Li4uPC9odG1sPg=="}
89
+ ],
90
+ "deletions": ["old-file.css"]
91
+ }'
92
+ ```
93
+
94
+ Response:
95
+ ```json
96
+ {
97
+ "status": "deployed",
98
+ "filesChanged": 1,
99
+ "filesDeleted": 1,
100
+ "message": "Deployment successful"
101
+ }
102
+ ```
103
+
104
+ ### POST /plugins/payara/deploy/full
105
+
106
+ Trigger a full WAR deployment (no diff).
107
+
108
+ ### POST /plugins/payara/restart
109
+
110
+ Restart the Payara domain.
111
+
112
+ ### POST /plugins/payara/start
113
+
114
+ Start the Payara domain.
115
+
116
+ ### POST /plugins/payara/stop
117
+
118
+ Stop the Payara domain.
119
+
120
+ ### GET /plugins/payara/status
121
+
122
+ Get current Payara status.
123
+
124
+ ```json
125
+ {
126
+ "running": true,
127
+ "healthy": true,
128
+ "domain": "domain1"
129
+ }
130
+ ```
131
+
132
+ ### GET /plugins/payara/applications
133
+
134
+ List deployed applications.
135
+
136
+ ```json
137
+ {
138
+ "applications": ["MyApp", "OtherApp"]
139
+ }
140
+ ```
141
+
142
+ ### GET /plugins/payara/file/*
143
+
144
+ Get a specific file from the WAR.
145
+
146
+ ```bash
147
+ curl http://localhost:9100/plugins/payara/file/WEB-INF/web.xml
148
+ ```
149
+
150
+ ## CLI Commands
151
+
152
+ The plugin adds commands to `znvault`:
153
+
154
+ ### Deploy WAR with diff
155
+
156
+ ```bash
157
+ # Deploy changed files only
158
+ znvault deploy war ./target/MyApp.war --target server.example.com
159
+
160
+ # Force full deployment
161
+ znvault deploy war ./target/MyApp.war --target server.example.com --force
162
+
163
+ # Dry run - show what would be deployed
164
+ znvault deploy war ./target/MyApp.war --target server.example.com --dry-run
165
+ ```
166
+
167
+ ### Restart Payara
168
+
169
+ ```bash
170
+ znvault deploy restart --target server.example.com
171
+ ```
172
+
173
+ ### Check Status
174
+
175
+ ```bash
176
+ znvault deploy status --target server.example.com
177
+ ```
178
+
179
+ ### List Applications
180
+
181
+ ```bash
182
+ znvault deploy applications --target server.example.com
183
+ # or
184
+ znvault deploy apps --target server.example.com
185
+ ```
186
+
187
+ ## CLI Configuration
188
+
189
+ Add the plugin to your CLI config (`~/.znvault/config.json`):
190
+
191
+ ```json
192
+ {
193
+ "plugins": [
194
+ {
195
+ "package": "@zincapp/znvault-plugin-payara",
196
+ "enabled": true
197
+ }
198
+ ]
199
+ }
200
+ ```
201
+
202
+ ## How Diff Deployment Works
203
+
204
+ 1. CLI calculates SHA-256 hash for every file in local WAR
205
+ 2. CLI requests current hashes from agent (`GET /plugins/payara/hashes`)
206
+ 3. CLI compares hashes to determine:
207
+ - **Changed files**: Hash differs or file is new
208
+ - **Deleted files**: Exists remotely but not locally
209
+ 4. CLI sends only changed files (base64-encoded) and deletion list
210
+ 5. Agent extracts current WAR to temp directory
211
+ 6. Agent applies changes (updates, creates, deletes files)
212
+ 7. Agent repackages WAR
213
+ 8. Agent stops Payara, deploys WAR, starts Payara
214
+
215
+ This reduces deployment time from minutes (full WAR transfer) to seconds (incremental changes).
216
+
217
+ ## Architecture
218
+
219
+ ```
220
+ ┌─────────────────┐ ┌─────────────────┐
221
+ │ Development │ │ Production │
222
+ │ Machine │ │ Server │
223
+ │ │ │ │
224
+ │ ┌───────────┐ │ Diff Transfer │ ┌───────────┐ │
225
+ │ │ Local WAR │ │ (changed files only) │ │ Agent │ │
226
+ │ └─────┬─────┘ │ ────────────────────────>│ │ + Plugin │ │
227
+ │ │ │ │ └─────┬─────┘ │
228
+ │ ┌─────▼─────┐ │ │ │ │
229
+ │ │ znvault │ │ GET /hashes │ ┌─────▼─────┐ │
230
+ │ │ deploy │◄─┼──────────────────────────┼──│ WAR File │ │
231
+ │ │ war │ │ │ └─────┬─────┘ │
232
+ │ └───────────┘ │ POST /deploy │ │ │
233
+ │ │ ────────────────────────>│ ┌─────▼─────┐ │
234
+ │ │ │ │ Payara │ │
235
+ │ │ │ │ Server │ │
236
+ │ │ │ └───────────┘ │
237
+ └─────────────────┘ └─────────────────┘
238
+ ```
239
+
240
+ ## Plugin Events
241
+
242
+ The plugin responds to zn-vault-agent lifecycle events:
243
+
244
+ ### onCertificateDeployed
245
+
246
+ When `restartOnCertChange: true`, automatically restarts Payara after certificate deployment:
247
+
248
+ ```javascript
249
+ // Plugin automatically handles this when certificates change
250
+ // No action needed from user
251
+ ```
252
+
253
+ ### healthCheck
254
+
255
+ Reports Payara status to the agent's `/health` endpoint:
256
+
257
+ ```json
258
+ {
259
+ "plugins": {
260
+ "payara": {
261
+ "status": "healthy",
262
+ "details": {
263
+ "domain": "domain1",
264
+ "warPath": "/opt/app/MyApp.war"
265
+ }
266
+ }
267
+ }
268
+ }
269
+ ```
270
+
271
+ ## Development
272
+
273
+ ```bash
274
+ # Install dependencies
275
+ npm install
276
+
277
+ # Build
278
+ npm run build
279
+
280
+ # Run tests
281
+ npm test
282
+
283
+ # Run specific test suite
284
+ npm test test/integration/war-deployer.test.ts
285
+
286
+ # Type check
287
+ npm run typecheck
288
+
289
+ # Lint
290
+ npm run lint
291
+ ```
292
+
293
+ ### Test Coverage
294
+
295
+ | Suite | Tests | Description |
296
+ |-------|-------|-------------|
297
+ | Unit | 31 | Core plugin, CLI, WAR deployer logic |
298
+ | Integration | 49 | PayaraManager, WarDeployer, HTTP routes |
299
+ | E2E | 17 | Full deployment flow, plugin factory |
300
+ | **Total** | **97** | All tests passing |
301
+
302
+ ## Requirements
303
+
304
+ - Node.js 18+
305
+ - Payara Server 5.x or 6.x
306
+ - `asadmin` in PATH or at `$PAYARA_HOME/bin/asadmin`
307
+ - Write access to WAR file location
308
+ - sudo access for running as different user (if configured)
309
+
310
+ ## Migration from Python zinc_updater
311
+
312
+ See [MIGRATION.md](./MIGRATION.md) for step-by-step migration guide from the Python-based zinc_updater.
313
+
314
+ ## License
315
+
316
+ MIT
package/dist/cli.d.ts ADDED
@@ -0,0 +1,40 @@
1
+ import type { Command } from 'commander';
2
+ /**
3
+ * CLI Plugin context interface
4
+ * Matches the CLIPluginContext from znvault-cli
5
+ */
6
+ interface CLIPluginContext {
7
+ client: {
8
+ get<T>(path: string): Promise<T>;
9
+ post<T>(path: string, body: unknown): Promise<T>;
10
+ };
11
+ output: {
12
+ success(message: string): void;
13
+ error(message: string): void;
14
+ warn(message: string): void;
15
+ info(message: string): void;
16
+ table(headers: string[], rows: unknown[][]): void;
17
+ keyValue(data: Record<string, unknown>): void;
18
+ };
19
+ getConfig(): {
20
+ url: string;
21
+ };
22
+ isPlainMode(): boolean;
23
+ }
24
+ /**
25
+ * CLI Plugin interface
26
+ */
27
+ export interface CLIPlugin {
28
+ name: string;
29
+ version: string;
30
+ description?: string;
31
+ registerCommands(program: Command, ctx: CLIPluginContext): void;
32
+ }
33
+ /**
34
+ * Payara CLI plugin
35
+ *
36
+ * Adds deploy commands to znvault CLI
37
+ */
38
+ export declare function createPayaraCLIPlugin(): CLIPlugin;
39
+ export default createPayaraCLIPlugin;
40
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMzC;;;GAGG;AACH,UAAU,gBAAgB;IACxB,MAAM,EAAE;QACN,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;QACjC,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;KAClD,CAAC;IACF,MAAM,EAAE;QACN,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QAC/B,KAAK,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QAC7B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,GAAG,IAAI,CAAC;QAClD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;KAC/C,CAAC;IACF,SAAS,IAAI;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7B,WAAW,IAAI,OAAO,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,gBAAgB,GAAG,IAAI,CAAC;CACjE;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,SAAS,CAgOjD;AAgDD,eAAe,qBAAqB,CAAC"}
package/dist/cli.js ADDED
@@ -0,0 +1,234 @@
1
+ // Path: src/cli.ts
2
+ // CLI commands for Payara plugin
3
+ import { createHash } from 'node:crypto';
4
+ import { stat } from 'node:fs/promises';
5
+ import AdmZip from 'adm-zip';
6
+ /**
7
+ * Payara CLI plugin
8
+ *
9
+ * Adds deploy commands to znvault CLI
10
+ */
11
+ export function createPayaraCLIPlugin() {
12
+ return {
13
+ name: 'payara',
14
+ version: '1.0.0',
15
+ description: 'Payara WAR deployment commands',
16
+ registerCommands(program, ctx) {
17
+ // Create deploy command group
18
+ const deploy = program
19
+ .command('deploy')
20
+ .description('Deploy WAR files to remote Payara servers');
21
+ // deploy war <file>
22
+ deploy
23
+ .command('war <warFile>')
24
+ .description('Deploy WAR file using diff transfer')
25
+ .option('-t, --target <host>', 'Target server URL (default: from profile)')
26
+ .option('-p, --port <port>', 'Agent health port (default: 9100)', '9100')
27
+ .option('-f, --force', 'Force full deployment (no diff)')
28
+ .option('--dry-run', 'Show what would be deployed without deploying')
29
+ .action(async (warFile, options) => {
30
+ try {
31
+ // Verify WAR file exists
32
+ try {
33
+ await stat(warFile);
34
+ }
35
+ catch {
36
+ ctx.output.error(`WAR file not found: ${warFile}`);
37
+ process.exit(1);
38
+ }
39
+ ctx.output.info(`Analyzing WAR file: ${warFile}`);
40
+ // Calculate local hashes
41
+ const localHashes = await calculateWarHashes(warFile);
42
+ const fileCount = Object.keys(localHashes).length;
43
+ ctx.output.info(`Found ${fileCount} files in WAR`);
44
+ // Build target URL
45
+ const target = options.target ?? ctx.getConfig().url;
46
+ const baseUrl = target.replace(/\/$/, '');
47
+ const pluginUrl = `${baseUrl}:${options.port}/plugins/payara`;
48
+ // Get remote hashes
49
+ let remoteHashes = {};
50
+ if (!options.force) {
51
+ try {
52
+ const response = await ctx.client.get(`${pluginUrl}/hashes`);
53
+ remoteHashes = response.hashes;
54
+ }
55
+ catch {
56
+ ctx.output.warn('Could not fetch remote hashes, doing full deployment');
57
+ }
58
+ }
59
+ // Calculate diff
60
+ const { changed, deleted } = calculateDiff(localHashes, remoteHashes);
61
+ ctx.output.info(`Diff: ${changed.length} changed, ${deleted.length} deleted`);
62
+ // Dry run - just show diff
63
+ if (options.dryRun) {
64
+ if (changed.length > 0) {
65
+ ctx.output.info('\nFiles to update:');
66
+ for (const file of changed.slice(0, 20)) {
67
+ console.log(` + ${file}`);
68
+ }
69
+ if (changed.length > 20) {
70
+ console.log(` ... and ${changed.length - 20} more`);
71
+ }
72
+ }
73
+ if (deleted.length > 0) {
74
+ ctx.output.info('\nFiles to delete:');
75
+ for (const file of deleted.slice(0, 20)) {
76
+ console.log(` - ${file}`);
77
+ }
78
+ if (deleted.length > 20) {
79
+ console.log(` ... and ${deleted.length - 20} more`);
80
+ }
81
+ }
82
+ if (changed.length === 0 && deleted.length === 0) {
83
+ ctx.output.success('No changes to deploy');
84
+ }
85
+ return;
86
+ }
87
+ // No changes
88
+ if (changed.length === 0 && deleted.length === 0) {
89
+ ctx.output.success('No changes to deploy');
90
+ return;
91
+ }
92
+ // Prepare files for upload
93
+ ctx.output.info('Preparing files for deployment...');
94
+ const zip = new AdmZip(warFile);
95
+ const files = changed.map(path => {
96
+ const entry = zip.getEntry(path);
97
+ if (!entry) {
98
+ throw new Error(`Entry not found in WAR: ${path}`);
99
+ }
100
+ return {
101
+ path,
102
+ content: entry.getData().toString('base64'),
103
+ };
104
+ });
105
+ // Deploy
106
+ ctx.output.info('Deploying changes...');
107
+ const deployResponse = await ctx.client.post(`${pluginUrl}/deploy`, {
108
+ files,
109
+ deletions: deleted,
110
+ });
111
+ if (deployResponse.status === 'deployed') {
112
+ ctx.output.success(`Deployed: ${deployResponse.filesChanged} files changed, ${deployResponse.filesDeleted} deleted`);
113
+ }
114
+ else {
115
+ ctx.output.error(`Deployment failed: ${deployResponse.message}`);
116
+ process.exit(1);
117
+ }
118
+ }
119
+ catch (err) {
120
+ ctx.output.error(`Deployment failed: ${err instanceof Error ? err.message : String(err)}`);
121
+ process.exit(1);
122
+ }
123
+ });
124
+ // deploy restart
125
+ deploy
126
+ .command('restart')
127
+ .description('Restart Payara on remote server')
128
+ .option('-t, --target <host>', 'Target server URL')
129
+ .option('-p, --port <port>', 'Agent health port (default: 9100)', '9100')
130
+ .action(async (options) => {
131
+ try {
132
+ const target = options.target ?? ctx.getConfig().url;
133
+ const baseUrl = target.replace(/\/$/, '');
134
+ const pluginUrl = `${baseUrl}:${options.port}/plugins/payara`;
135
+ ctx.output.info('Restarting Payara...');
136
+ await ctx.client.post(`${pluginUrl}/restart`, {});
137
+ ctx.output.success('Payara restarted');
138
+ }
139
+ catch (err) {
140
+ ctx.output.error(`Restart failed: ${err instanceof Error ? err.message : String(err)}`);
141
+ process.exit(1);
142
+ }
143
+ });
144
+ // deploy status
145
+ deploy
146
+ .command('status')
147
+ .description('Get Payara status from remote server')
148
+ .option('-t, --target <host>', 'Target server URL')
149
+ .option('-p, --port <port>', 'Agent health port (default: 9100)', '9100')
150
+ .action(async (options) => {
151
+ try {
152
+ const target = options.target ?? ctx.getConfig().url;
153
+ const baseUrl = target.replace(/\/$/, '');
154
+ const pluginUrl = `${baseUrl}:${options.port}/plugins/payara`;
155
+ const status = await ctx.client.get(`${pluginUrl}/status`);
156
+ ctx.output.keyValue({
157
+ 'Domain': status.domain,
158
+ 'Running': status.running,
159
+ 'Healthy': status.healthy,
160
+ 'PID': status.pid ?? 'N/A',
161
+ });
162
+ }
163
+ catch (err) {
164
+ ctx.output.error(`Failed to get status: ${err instanceof Error ? err.message : String(err)}`);
165
+ process.exit(1);
166
+ }
167
+ });
168
+ // deploy applications
169
+ deploy
170
+ .command('applications')
171
+ .alias('apps')
172
+ .description('List deployed applications')
173
+ .option('-t, --target <host>', 'Target server URL')
174
+ .option('-p, --port <port>', 'Agent health port (default: 9100)', '9100')
175
+ .action(async (options) => {
176
+ try {
177
+ const target = options.target ?? ctx.getConfig().url;
178
+ const baseUrl = target.replace(/\/$/, '');
179
+ const pluginUrl = `${baseUrl}:${options.port}/plugins/payara`;
180
+ const response = await ctx.client.get(`${pluginUrl}/applications`);
181
+ if (response.applications.length === 0) {
182
+ ctx.output.info('No applications deployed');
183
+ return;
184
+ }
185
+ ctx.output.info(`Deployed applications (${response.applications.length}):`);
186
+ for (const app of response.applications) {
187
+ console.log(` - ${app}`);
188
+ }
189
+ }
190
+ catch (err) {
191
+ ctx.output.error(`Failed to list applications: ${err instanceof Error ? err.message : String(err)}`);
192
+ process.exit(1);
193
+ }
194
+ });
195
+ },
196
+ };
197
+ }
198
+ /**
199
+ * Calculate SHA-256 hashes for all files in a WAR
200
+ */
201
+ async function calculateWarHashes(warPath) {
202
+ const hashes = {};
203
+ const zip = new AdmZip(warPath);
204
+ for (const entry of zip.getEntries()) {
205
+ if (!entry.isDirectory) {
206
+ const content = entry.getData();
207
+ const hash = createHash('sha256').update(content).digest('hex');
208
+ hashes[entry.entryName] = hash;
209
+ }
210
+ }
211
+ return hashes;
212
+ }
213
+ /**
214
+ * Calculate diff between local and remote hashes
215
+ */
216
+ function calculateDiff(localHashes, remoteHashes) {
217
+ const changed = [];
218
+ const deleted = [];
219
+ // Find changed/new files
220
+ for (const [path, hash] of Object.entries(localHashes)) {
221
+ if (!remoteHashes[path] || remoteHashes[path] !== hash) {
222
+ changed.push(path);
223
+ }
224
+ }
225
+ // Find deleted files
226
+ for (const path of Object.keys(remoteHashes)) {
227
+ if (!localHashes[path]) {
228
+ deleted.push(path);
229
+ }
230
+ }
231
+ return { changed, deleted };
232
+ }
233
+ // Default export for CLI plugin
234
+ export default createPayaraCLIPlugin;
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}