@katmer/core 0.0.3
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 +1 -0
- package/cli/katmer.js +28 -0
- package/cli/run.ts +16 -0
- package/index.ts +5 -0
- package/lib/config.ts +82 -0
- package/lib/interfaces/config.interface.ts +113 -0
- package/lib/interfaces/executor.interface.ts +13 -0
- package/lib/interfaces/module.interface.ts +170 -0
- package/lib/interfaces/provider.interface.ts +214 -0
- package/lib/interfaces/task.interface.ts +100 -0
- package/lib/katmer.ts +126 -0
- package/lib/lookup/env.lookup.ts +13 -0
- package/lib/lookup/file.lookup.ts +23 -0
- package/lib/lookup/index.ts +46 -0
- package/lib/lookup/url.lookup.ts +21 -0
- package/lib/lookup/var.lookup.ts +13 -0
- package/lib/module.ts +560 -0
- package/lib/module_registry.ts +64 -0
- package/lib/modules/apt-repository/apt-repository.module.ts +435 -0
- package/lib/modules/apt-repository/apt-sources-list.ts +363 -0
- package/lib/modules/apt.module.ts +546 -0
- package/lib/modules/archive.module.ts +280 -0
- package/lib/modules/become.module.ts +119 -0
- package/lib/modules/copy.module.ts +807 -0
- package/lib/modules/cron.module.ts +541 -0
- package/lib/modules/debug.module.ts +231 -0
- package/lib/modules/gather_facts.module.ts +605 -0
- package/lib/modules/git.module.ts +243 -0
- package/lib/modules/hostname.module.ts +213 -0
- package/lib/modules/http/http.curl.module.ts +342 -0
- package/lib/modules/http/http.local.module.ts +253 -0
- package/lib/modules/http/http.module.ts +298 -0
- package/lib/modules/index.ts +14 -0
- package/lib/modules/package.module.ts +283 -0
- package/lib/modules/script.module.ts +121 -0
- package/lib/modules/set_fact.module.ts +171 -0
- package/lib/modules/systemd_service.module.ts +373 -0
- package/lib/modules/template.module.ts +478 -0
- package/lib/providers/local.provider.ts +336 -0
- package/lib/providers/provider_response.ts +20 -0
- package/lib/providers/ssh/ssh.provider.ts +420 -0
- package/lib/providers/ssh/ssh.utils.ts +31 -0
- package/lib/schemas/katmer_config.schema.json +358 -0
- package/lib/target_resolver.ts +298 -0
- package/lib/task/controls/environment.control.ts +42 -0
- package/lib/task/controls/index.ts +13 -0
- package/lib/task/controls/loop.control.ts +89 -0
- package/lib/task/controls/register.control.ts +23 -0
- package/lib/task/controls/until.control.ts +64 -0
- package/lib/task/controls/when.control.ts +25 -0
- package/lib/task/task.ts +225 -0
- package/lib/utils/ajv.utils.ts +24 -0
- package/lib/utils/cls.ts +4 -0
- package/lib/utils/datetime.utils.ts +15 -0
- package/lib/utils/errors.ts +25 -0
- package/lib/utils/execute-shell.ts +116 -0
- package/lib/utils/file.utils.ts +68 -0
- package/lib/utils/http.utils.ts +10 -0
- package/lib/utils/json.utils.ts +15 -0
- package/lib/utils/number.utils.ts +9 -0
- package/lib/utils/object.utils.ts +11 -0
- package/lib/utils/os.utils.ts +31 -0
- package/lib/utils/path.utils.ts +9 -0
- package/lib/utils/renderer/render_functions.ts +3 -0
- package/lib/utils/renderer/renderer.ts +89 -0
- package/lib/utils/renderer/twig.ts +191 -0
- package/lib/utils/string.utils.ts +33 -0
- package/lib/utils/typed-event-emitter.ts +26 -0
- package/lib/utils/unix.utils.ts +91 -0
- package/lib/utils/windows.utils.ts +92 -0
- package/package.json +67 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { type ModuleCommonReturn } from "../interfaces/module.interface"
|
|
2
|
+
import type { Katmer } from "../interfaces/task.interface"
|
|
3
|
+
import type { KatmerProvider } from "../interfaces/provider.interface"
|
|
4
|
+
import { KatmerModule } from "../module"
|
|
5
|
+
|
|
6
|
+
declare module "../interfaces/task.interface" {
|
|
7
|
+
export namespace Katmer {
|
|
8
|
+
export interface TaskActions {
|
|
9
|
+
archive?: ArchiveModuleOptions
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Archive/create/extract/list files using the system tar on all OSes.
|
|
15
|
+
*
|
|
16
|
+
* @remarks
|
|
17
|
+
* - Supports formats: tar, tar.gz, tar.bz2, tar.xz, tar.zst, zip (via bsdtar).
|
|
18
|
+
* - Works on SSHProvider and LocalProvider.
|
|
19
|
+
* - Matches flags available in bsdtar(1) with sensible defaults.
|
|
20
|
+
*
|
|
21
|
+
* @example Create a gzip archive:
|
|
22
|
+
* ```yaml
|
|
23
|
+
* - name: Create a gzip archive
|
|
24
|
+
* archive:
|
|
25
|
+
* path:
|
|
26
|
+
* - /var/log
|
|
27
|
+
* - /etc/nginx
|
|
28
|
+
* dest: /tmp/system.tar.gz
|
|
29
|
+
* gzip: true
|
|
30
|
+
* verbose: true
|
|
31
|
+
* ```
|
|
32
|
+
*
|
|
33
|
+
* @example Extract with strip components:
|
|
34
|
+
* ```yaml
|
|
35
|
+
* - name: Extract release
|
|
36
|
+
* archive:
|
|
37
|
+
* src: /tmp/release.tar.gz
|
|
38
|
+
* dest: /opt/app
|
|
39
|
+
* strip_components: 1
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* @example List archive contents:
|
|
43
|
+
* ```yaml
|
|
44
|
+
* - name: List archive
|
|
45
|
+
* archive:
|
|
46
|
+
* src: /tmp/archive.tar.xz
|
|
47
|
+
* list: true
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export class ArchiveModule extends KatmerModule<
|
|
51
|
+
ArchiveModuleOptions,
|
|
52
|
+
ArchiveModuleResult,
|
|
53
|
+
KatmerProvider
|
|
54
|
+
> {
|
|
55
|
+
static name = "archive" as const
|
|
56
|
+
|
|
57
|
+
constraints = {
|
|
58
|
+
platform: { any: { packages: ["tar"] } }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async check(): Promise<void> {
|
|
62
|
+
const p = this.params
|
|
63
|
+
if (!p) throw new Error("archive: options are required")
|
|
64
|
+
if (!p.src && !p.path)
|
|
65
|
+
throw new Error("archive: one of 'src' or 'path' is required")
|
|
66
|
+
if (!p.dest && !p.list && !p.options)
|
|
67
|
+
throw new Error(
|
|
68
|
+
"archive: 'dest' is required unless listing or raw options"
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async initialize(): Promise<void> {}
|
|
73
|
+
async cleanup(): Promise<void> {}
|
|
74
|
+
|
|
75
|
+
async execute(ctx: Katmer.TaskContext): Promise<ArchiveModuleResult> {
|
|
76
|
+
const p = normalizeOptions(this.params)
|
|
77
|
+
const sh = (v: string) => JSON.stringify(v)
|
|
78
|
+
|
|
79
|
+
const tarCmd = await this.detectTar(ctx)
|
|
80
|
+
|
|
81
|
+
// Build args
|
|
82
|
+
const args: string[] = []
|
|
83
|
+
|
|
84
|
+
// Determine mode
|
|
85
|
+
if (p.list) args.push("-t")
|
|
86
|
+
else if (p.src) args.push("-x")
|
|
87
|
+
else args.push("-c")
|
|
88
|
+
|
|
89
|
+
// Verbose
|
|
90
|
+
if (p.verbose) args.push("-v")
|
|
91
|
+
|
|
92
|
+
// Compression
|
|
93
|
+
if (p.gzip) args.push("--gzip")
|
|
94
|
+
if (p.bzip2) args.push("--bzip2")
|
|
95
|
+
if (p.xz) args.push("--xz")
|
|
96
|
+
if (p.zstd) args.push("--zstd")
|
|
97
|
+
|
|
98
|
+
// Archive file
|
|
99
|
+
const archiveArg = p.src ?? p.dest
|
|
100
|
+
if (archiveArg) args.push("-f", sh(archiveArg))
|
|
101
|
+
|
|
102
|
+
// Directory change before action
|
|
103
|
+
if (p.chdir) args.push("-C", sh(p.chdir))
|
|
104
|
+
|
|
105
|
+
// Creation paths
|
|
106
|
+
if (p.path && !p.src) {
|
|
107
|
+
const list = Array.isArray(p.path) ? p.path : [p.path]
|
|
108
|
+
for (const item of list) args.push(sh(item))
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Filter flags
|
|
112
|
+
if (p.strip_components != null)
|
|
113
|
+
args.push(`--strip-components=${p.strip_components}`)
|
|
114
|
+
|
|
115
|
+
if (p.exclude) for (const ex of p.exclude) args.push(`--exclude=${sh(ex)}`)
|
|
116
|
+
|
|
117
|
+
// Ownership/Permission
|
|
118
|
+
if (p.numeric_owner) args.push("--numeric-owner")
|
|
119
|
+
if (p.uid != null) args.push(`--uid=${p.uid}`)
|
|
120
|
+
if (p.gid != null) args.push(`--gid=${p.gid}`)
|
|
121
|
+
|
|
122
|
+
if (p.preserve_permissions) args.push("--preserve-permissions")
|
|
123
|
+
if (p.no_same_owner) args.push("--no-same-owner")
|
|
124
|
+
if (p.no_same_permissions) args.push("--no-same-permissions")
|
|
125
|
+
|
|
126
|
+
// Raw extra options
|
|
127
|
+
if (p.options) args.push(...p.options)
|
|
128
|
+
|
|
129
|
+
// Final command
|
|
130
|
+
const cmd = `${tarCmd} ${args.join(" ")}`
|
|
131
|
+
|
|
132
|
+
const r = await ctx.exec(cmd)
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
changed: !p.list,
|
|
136
|
+
failed: r.code !== 0,
|
|
137
|
+
stdout: r.stdout,
|
|
138
|
+
stderr: r.stderr,
|
|
139
|
+
dest: p.dest
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private async detectTar(ctx: Katmer.TaskContext): Promise<string> {
|
|
144
|
+
// Try bsdtar first, then tar
|
|
145
|
+
const bins = ["tar", "tar.exe"]
|
|
146
|
+
for (const b of bins) {
|
|
147
|
+
try {
|
|
148
|
+
await ctx.exec(`${b} --version`)
|
|
149
|
+
return b
|
|
150
|
+
} catch {
|
|
151
|
+
/* ignore */
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
throw new Error("archive: neither bsdtar nor tar was found on target")
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Options for the {@link ArchiveModule | `archive`} module.
|
|
160
|
+
*
|
|
161
|
+
* @public
|
|
162
|
+
*/
|
|
163
|
+
export interface ArchiveModuleOptions {
|
|
164
|
+
/**
|
|
165
|
+
* Archive file to extract or list
|
|
166
|
+
*/
|
|
167
|
+
src?: string
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Destination directory (where to extract or where the created archive is saved)
|
|
171
|
+
*/
|
|
172
|
+
dest?: string
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Path(s) to include when creating an archive
|
|
176
|
+
*/
|
|
177
|
+
path?: string | string[]
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Change to this directory before running tar (`-C`)
|
|
181
|
+
*/
|
|
182
|
+
chdir?: string
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* When true, list archive contents instead of extract/create
|
|
186
|
+
*/
|
|
187
|
+
list?: boolean
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Compression options (mutually exclusive)
|
|
191
|
+
*/
|
|
192
|
+
gzip?: boolean
|
|
193
|
+
bzip2?: boolean
|
|
194
|
+
xz?: boolean
|
|
195
|
+
zstd?: boolean
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Strip leading path components on extract
|
|
199
|
+
*/
|
|
200
|
+
strip_components?: number
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Exclude patterns (glob-like)
|
|
204
|
+
*/
|
|
205
|
+
exclude?: string[]
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Preserve permissions on extract
|
|
209
|
+
*/
|
|
210
|
+
preserve_permissions?: boolean
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Don’t restore owner on extract
|
|
214
|
+
*/
|
|
215
|
+
no_same_owner?: boolean
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Don’t restore permissions on extract
|
|
219
|
+
*/
|
|
220
|
+
no_same_permissions?: boolean
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* When true, use numeric owner/gid
|
|
224
|
+
*/
|
|
225
|
+
numeric_owner?: boolean
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Force a specific uid on extract
|
|
229
|
+
*/
|
|
230
|
+
uid?: number
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Force a specific gid on extract
|
|
234
|
+
*/
|
|
235
|
+
gid?: number
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Verbose output (`-v`)
|
|
239
|
+
*/
|
|
240
|
+
verbose?: boolean
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Extra raw flags passed directly to tar (after bsdtar detection)
|
|
244
|
+
*/
|
|
245
|
+
options?: string[]
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Result of the archive operation.
|
|
250
|
+
*
|
|
251
|
+
* @public
|
|
252
|
+
*/
|
|
253
|
+
export interface ArchiveModuleResult extends ModuleCommonReturn {
|
|
254
|
+
/**
|
|
255
|
+
* Destination directory or archive path that was acted upon
|
|
256
|
+
*/
|
|
257
|
+
dest?: string
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/* normalize and defaults */
|
|
261
|
+
function normalizeOptions(opts: ArchiveModuleOptions): ArchiveModuleOptions {
|
|
262
|
+
return {
|
|
263
|
+
...opts,
|
|
264
|
+
list: opts.list ?? false,
|
|
265
|
+
gzip: opts.gzip ?? false,
|
|
266
|
+
bzip2: opts.bzip2 ?? false,
|
|
267
|
+
xz: opts.xz ?? false,
|
|
268
|
+
zstd: opts.zstd ?? false,
|
|
269
|
+
strip_components: opts.strip_components ?? 0,
|
|
270
|
+
exclude: opts.exclude ?? [],
|
|
271
|
+
preserve_permissions: opts.preserve_permissions ?? false,
|
|
272
|
+
no_same_owner: opts.no_same_owner ?? false,
|
|
273
|
+
no_same_permissions: opts.no_same_permissions ?? false,
|
|
274
|
+
numeric_owner: opts.numeric_owner ?? false,
|
|
275
|
+
uid: opts.uid ?? undefined,
|
|
276
|
+
gid: opts.gid ?? undefined,
|
|
277
|
+
verbose: opts.verbose ?? false,
|
|
278
|
+
options: opts.options ?? []
|
|
279
|
+
}
|
|
280
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { type ModuleConstraints } from "../interfaces/module.interface"
|
|
2
|
+
import type { SSHProvider } from "../providers/ssh/ssh.provider"
|
|
3
|
+
import type { Katmer } from "../interfaces/task.interface"
|
|
4
|
+
import { KatmerModule } from "../module"
|
|
5
|
+
|
|
6
|
+
declare module "../interfaces/task.interface" {
|
|
7
|
+
export namespace Katmer {
|
|
8
|
+
export interface TaskActions {
|
|
9
|
+
become?: BecomeModuleOptions
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* When `true`, enable sudo with defaults.
|
|
15
|
+
* When `false`, disable privilege escalation (no-op).
|
|
16
|
+
* Or provide an object to customize user/prompt/password.
|
|
17
|
+
*/
|
|
18
|
+
export type BecomeModuleOptions =
|
|
19
|
+
| boolean
|
|
20
|
+
| {
|
|
21
|
+
/**
|
|
22
|
+
* Target user to run commands as (e.g. "root" or "deploy").
|
|
23
|
+
*/
|
|
24
|
+
user?: string
|
|
25
|
+
/**
|
|
26
|
+
* Password for sudo (falls back to provider's password if omitted).
|
|
27
|
+
*/
|
|
28
|
+
password?: string
|
|
29
|
+
/**
|
|
30
|
+
* Prompt marker for sudo; used to detect when to send the password.
|
|
31
|
+
*/
|
|
32
|
+
prompt?: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Control privilege escalation.
|
|
37
|
+
*
|
|
38
|
+
* @remarks
|
|
39
|
+
* - Set to `true` to enable sudo with sensible defaults (uses the provider's password if available).
|
|
40
|
+
* - Set to `false` to disable privilege escalation (no-op).
|
|
41
|
+
* - Provide an object to override sudo behavior (`user`, `password`, `prompt`).
|
|
42
|
+
* - The module rewrites subsequent commands to `sudo -S -p [prompt] [-u <user>] ...`
|
|
43
|
+
* and automatically responds to the prompt with the configured password.
|
|
44
|
+
*
|
|
45
|
+
* @examples
|
|
46
|
+
* ```yaml
|
|
47
|
+
* # Enable with defaults (use provider password, run as root)
|
|
48
|
+
* - name: Run with sudo
|
|
49
|
+
* become: true
|
|
50
|
+
*
|
|
51
|
+
* # Custom sudo user and prompt
|
|
52
|
+
* - name: Run as deploy user with custom prompt
|
|
53
|
+
* become:
|
|
54
|
+
* user: deploy
|
|
55
|
+
* prompt: "SUDO:"
|
|
56
|
+
*
|
|
57
|
+
* # Explicit password override (falls back to provider password if omitted)
|
|
58
|
+
* - name: Use an explicit sudo password
|
|
59
|
+
* become:
|
|
60
|
+
* user: root
|
|
61
|
+
* password: "{{ VAULT_SUDO_PASSWORD }}"
|
|
62
|
+
*
|
|
63
|
+
* # Disable privilege escalation
|
|
64
|
+
* - name: Run without sudo
|
|
65
|
+
* become: false
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
export class BecomeModule extends KatmerModule<
|
|
69
|
+
BecomeModuleOptions,
|
|
70
|
+
{},
|
|
71
|
+
SSHProvider
|
|
72
|
+
> {
|
|
73
|
+
static internal = true
|
|
74
|
+
|
|
75
|
+
static name = "become" as const
|
|
76
|
+
|
|
77
|
+
constraints = {
|
|
78
|
+
platform: {
|
|
79
|
+
any: true
|
|
80
|
+
}
|
|
81
|
+
} satisfies ModuleConstraints
|
|
82
|
+
|
|
83
|
+
async check(_ctx: Katmer.TaskContext<SSHProvider>): Promise<void> {}
|
|
84
|
+
|
|
85
|
+
async initialize(ctx: Katmer.TaskContext<SSHProvider>): Promise<void> {
|
|
86
|
+
if (this.params === false) return
|
|
87
|
+
|
|
88
|
+
const opts = Object.assign(
|
|
89
|
+
{
|
|
90
|
+
prompt: "KATMER_SUDO_PROMPT:",
|
|
91
|
+
user: "",
|
|
92
|
+
password: (ctx.provider as any).options?.password
|
|
93
|
+
},
|
|
94
|
+
this.params === true ? {} : this.params
|
|
95
|
+
)
|
|
96
|
+
const promptMarker = opts.prompt ?? "KATMER_SUDO_PROMPT:"
|
|
97
|
+
const interactivePassword =
|
|
98
|
+
opts.password ?? (ctx.provider as any).options?.password ?? ""
|
|
99
|
+
const userPart = opts.user ? ` -u ${opts.user}` : ""
|
|
100
|
+
|
|
101
|
+
// Supply rewrite + prompt handling hints for downstream exec()
|
|
102
|
+
const rewriteCommand = (prepared: string) => {
|
|
103
|
+
return `sudo -S -p '${promptMarker}'${userPart} ${prepared}`
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
ctx.exec = ctx.provider.executor({
|
|
107
|
+
rewriteCommand,
|
|
108
|
+
promptMarker: promptMarker,
|
|
109
|
+
interactivePassword: interactivePassword,
|
|
110
|
+
hidePromptLine: true
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async execute(_ctx: Katmer.TaskContext<SSHProvider>) {
|
|
115
|
+
return { changed: false }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async cleanup(_ctx: Katmer.TaskContext<SSHProvider>): Promise<void> {}
|
|
119
|
+
}
|