@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,100 @@
|
|
|
1
|
+
import type { KatmerProvider } from "./provider.interface"
|
|
2
|
+
import type { KatmerConfig } from "./config.interface"
|
|
3
|
+
import type { TwigExpression } from "./executor.interface"
|
|
4
|
+
import type { Logger } from "pino"
|
|
5
|
+
import type { ProviderResponse } from "../providers/provider_response"
|
|
6
|
+
import { KatmerModule } from "../module"
|
|
7
|
+
|
|
8
|
+
export namespace Katmer {
|
|
9
|
+
/*
|
|
10
|
+
* For plugins
|
|
11
|
+
*/
|
|
12
|
+
export interface CustomInclude {
|
|
13
|
+
[key: string]: unknown
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type IncludeConfig =
|
|
17
|
+
| string
|
|
18
|
+
| {
|
|
19
|
+
file: string
|
|
20
|
+
}
|
|
21
|
+
| {
|
|
22
|
+
remote: string
|
|
23
|
+
}
|
|
24
|
+
| CustomInclude
|
|
25
|
+
|
|
26
|
+
/*
|
|
27
|
+
* For plugins
|
|
28
|
+
*/
|
|
29
|
+
export interface TaskActions {}
|
|
30
|
+
|
|
31
|
+
export interface TaskContext<
|
|
32
|
+
Provider extends KatmerProvider = KatmerProvider
|
|
33
|
+
> {
|
|
34
|
+
exec: (
|
|
35
|
+
command: string,
|
|
36
|
+
options?: Parameters<Provider["executor"]>[0]
|
|
37
|
+
) => Promise<ProviderResponse>
|
|
38
|
+
execSafe: (
|
|
39
|
+
command: string,
|
|
40
|
+
options?: Parameters<Provider["executor"]>[0]
|
|
41
|
+
) => Promise<ProviderResponse>
|
|
42
|
+
config: KatmerConfig
|
|
43
|
+
provider: Provider
|
|
44
|
+
variables: Record<string, any>
|
|
45
|
+
progress: (data: Record<string, any>) => void
|
|
46
|
+
logger: Logger
|
|
47
|
+
log: (
|
|
48
|
+
level: "fatal" | "error" | "warn" | "info" | "debug" | "trace",
|
|
49
|
+
...message: any
|
|
50
|
+
) => void
|
|
51
|
+
|
|
52
|
+
warn(opts: { message: string }): void
|
|
53
|
+
warn(msg: string): void
|
|
54
|
+
|
|
55
|
+
fail(msg: string): never
|
|
56
|
+
fail(opts: { message: string }): never
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface RuleVariables {
|
|
60
|
+
[key: string]: string
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface UntilControl {
|
|
64
|
+
condition: string | TwigExpression
|
|
65
|
+
delay?: number
|
|
66
|
+
retries?: number
|
|
67
|
+
}
|
|
68
|
+
export interface LoopControl {
|
|
69
|
+
for: TwigExpression | (boolean | number | string)[]
|
|
70
|
+
loop_var?: string
|
|
71
|
+
index_var?: string
|
|
72
|
+
pause?: number
|
|
73
|
+
break_when?: string | TwigExpression | (string | TwigExpression)[]
|
|
74
|
+
label?: string | TwigExpression
|
|
75
|
+
extended?: boolean
|
|
76
|
+
extended_allitems?: boolean
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface TaskRule {
|
|
80
|
+
loop?: LoopControl | LoopControl["for"]
|
|
81
|
+
until?: UntilControl | UntilControl["condition"]
|
|
82
|
+
when?: string
|
|
83
|
+
register?: string
|
|
84
|
+
allow_failure?: boolean
|
|
85
|
+
variables?: RuleVariables
|
|
86
|
+
environment?: string | Record<string, string>
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export interface Task extends TaskActions, TaskRule {
|
|
90
|
+
name?: string
|
|
91
|
+
targets: string[]
|
|
92
|
+
script?: string[]
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export type Config = {
|
|
96
|
+
include?: IncludeConfig[]
|
|
97
|
+
} & {
|
|
98
|
+
[key: string]: Task
|
|
99
|
+
}
|
|
100
|
+
}
|
package/lib/katmer.ts
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { parseKatmerFile, readKatmerFile } from "./utils/file.utils"
|
|
2
|
+
import type {
|
|
3
|
+
KatmerConfig,
|
|
4
|
+
KatmerCLIOptions,
|
|
5
|
+
StandardLogger
|
|
6
|
+
} from "./interfaces/config.interface"
|
|
7
|
+
import { KatmerTargetResolver } from "./target_resolver"
|
|
8
|
+
import "./module_registry"
|
|
9
|
+
import { KatmerModuleRegistry } from "./module_registry"
|
|
10
|
+
import * as pino from "pino"
|
|
11
|
+
import pinoPretty from "pino-pretty"
|
|
12
|
+
import { KatmerConfigLoader } from "./config"
|
|
13
|
+
import { evalExpr } from "./utils/renderer/renderer"
|
|
14
|
+
import type { ModuleCommonReturn } from "./interfaces/module.interface"
|
|
15
|
+
import { wrapInArray } from "./utils/json.utils"
|
|
16
|
+
import { defaults, isObjectLike } from "es-toolkit/compat"
|
|
17
|
+
import type { Katmer } from "./interfaces/task.interface"
|
|
18
|
+
import { KatmerTask } from "./task/task"
|
|
19
|
+
|
|
20
|
+
export interface KatmerInitOptions extends KatmerCLIOptions {
|
|
21
|
+
logging?: {
|
|
22
|
+
dir?: string
|
|
23
|
+
level?: "trace" | "debug" | "info" | "warn" | "error" | "silent"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export class KatmerCore {
|
|
28
|
+
config!: KatmerConfig
|
|
29
|
+
registry!: KatmerModuleRegistry
|
|
30
|
+
logger!: StandardLogger
|
|
31
|
+
constructor(private opts: KatmerCLIOptions) {}
|
|
32
|
+
|
|
33
|
+
async init() {
|
|
34
|
+
await this.loadConfig()
|
|
35
|
+
this.initLogger()
|
|
36
|
+
this.initRegistry()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async loadConfig(config?: Partial<KatmerConfig>) {
|
|
40
|
+
if (config) {
|
|
41
|
+
this.config = KatmerConfigLoader.validate(config)
|
|
42
|
+
} else {
|
|
43
|
+
const { target, cwd } = this.opts
|
|
44
|
+
this.config = await KatmerConfigLoader.load(target, { cwd })
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
initRegistry() {
|
|
49
|
+
this.registry = new KatmerModuleRegistry(this)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
initLogger() {
|
|
53
|
+
this.logger = pino.pino(
|
|
54
|
+
{
|
|
55
|
+
formatters: {
|
|
56
|
+
bindings() {
|
|
57
|
+
return {}
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
level: this.config.logging?.level || "trace",
|
|
61
|
+
timestamp: pino.stdTimeFunctions.isoTime
|
|
62
|
+
},
|
|
63
|
+
pinoPretty({
|
|
64
|
+
colorizeObjects: true,
|
|
65
|
+
singleLine: false,
|
|
66
|
+
useOnlyCustomProps: true,
|
|
67
|
+
colorize: true,
|
|
68
|
+
ignore: "provider,module",
|
|
69
|
+
|
|
70
|
+
customPrettifiers: {
|
|
71
|
+
command: (output, keyName, logObj, extras) => `${output}`,
|
|
72
|
+
provider: (output, keyName, logObj, extras) => `${output}`,
|
|
73
|
+
module: (output, keyName, logObj, extras) => `${output}`
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
)
|
|
77
|
+
// TODO: initialize logger
|
|
78
|
+
// new ConsoleReadableStream().pipeTo(
|
|
79
|
+
// new WritableStream({
|
|
80
|
+
// write(log) {
|
|
81
|
+
// appendFile(logFile, `${JSON.stringify(log)}\n`)
|
|
82
|
+
// Bun.write(Bun.stdout, `${JSON.stringify(log)}\n`)
|
|
83
|
+
// for (const client of clients) {
|
|
84
|
+
// client.send(new WSMessage("log", log as any))
|
|
85
|
+
// }
|
|
86
|
+
// }
|
|
87
|
+
// })
|
|
88
|
+
// )
|
|
89
|
+
}
|
|
90
|
+
async check() {
|
|
91
|
+
await using targetResolver = new KatmerTargetResolver(this)
|
|
92
|
+
const providers = targetResolver.resolveTargets("all")
|
|
93
|
+
console.log(targetResolver.hosts)
|
|
94
|
+
console.log(this.config, providers)
|
|
95
|
+
for (const runFor of providers) {
|
|
96
|
+
const provider = await targetResolver.resolveProvider(runFor)
|
|
97
|
+
await provider.ensureReady()
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async run(file: string) {
|
|
102
|
+
const contents = await readKatmerFile(file, {
|
|
103
|
+
cwd: this.opts.cwd,
|
|
104
|
+
errorMessage: "Failed to run task file"
|
|
105
|
+
})
|
|
106
|
+
await using targetResolver = new KatmerTargetResolver(
|
|
107
|
+
this,
|
|
108
|
+
contents.targets
|
|
109
|
+
)
|
|
110
|
+
const defaultOpts = contents.defaults || {}
|
|
111
|
+
defaultOpts.targets = wrapInArray(defaultOpts.targets)
|
|
112
|
+
|
|
113
|
+
const fileContext = {}
|
|
114
|
+
|
|
115
|
+
for (const taskConfig of contents.tasks || []) {
|
|
116
|
+
const task = new KatmerTask(this, targetResolver, taskConfig)
|
|
117
|
+
|
|
118
|
+
await task.run(fileContext)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async [Symbol.asyncDispose]() {
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export type { Katmer } from "./interfaces/task.interface"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { get } from "es-toolkit/compat"
|
|
2
|
+
import type { Katmer } from "../interfaces/task.interface"
|
|
3
|
+
|
|
4
|
+
export const EnvLookup = {
|
|
5
|
+
key: "env",
|
|
6
|
+
handler: async (
|
|
7
|
+
_ctx: Katmer.TaskContext,
|
|
8
|
+
envKeyParts: string[],
|
|
9
|
+
_opts: Record<string, any>
|
|
10
|
+
) => {
|
|
11
|
+
return get(process.env, envKeyParts)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Katmer } from "../interfaces/task.interface"
|
|
2
|
+
import fs from "node:fs/promises"
|
|
3
|
+
import path from "node:path"
|
|
4
|
+
|
|
5
|
+
export const FileLookup = {
|
|
6
|
+
key: "file",
|
|
7
|
+
handler: async (
|
|
8
|
+
ctx: Katmer.TaskContext,
|
|
9
|
+
pathParts: string[],
|
|
10
|
+
options = {} as {
|
|
11
|
+
cwd?: string
|
|
12
|
+
encoding?: BufferEncoding
|
|
13
|
+
}
|
|
14
|
+
) => {
|
|
15
|
+
return await fs.readFile(
|
|
16
|
+
path.resolve(options.cwd || ctx.config.cwd || "", ...pathParts),
|
|
17
|
+
{
|
|
18
|
+
encoding: "utf-8",
|
|
19
|
+
...options
|
|
20
|
+
}
|
|
21
|
+
)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { EnvLookup } from "./env.lookup"
|
|
2
|
+
import { VarLookup } from "./var.lookup"
|
|
3
|
+
import { URLLookup } from "./url.lookup"
|
|
4
|
+
import { FileLookup } from "./file.lookup"
|
|
5
|
+
import { isPlainObject } from "es-toolkit"
|
|
6
|
+
import { cls } from "../utils/cls"
|
|
7
|
+
|
|
8
|
+
export const Lookup = {
|
|
9
|
+
handlers: {
|
|
10
|
+
[EnvLookup.key]: EnvLookup,
|
|
11
|
+
[VarLookup.key]: VarLookup,
|
|
12
|
+
[URLLookup.key]: URLLookup,
|
|
13
|
+
[FileLookup.key]: FileLookup
|
|
14
|
+
},
|
|
15
|
+
async execute(store: string, ...args: any[]) {
|
|
16
|
+
if (!this.handlers[store]) {
|
|
17
|
+
throw new Error(`Unknown lookup store: ${store}`)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const lastArg = args.at(-1)
|
|
21
|
+
let keys: any[]
|
|
22
|
+
let opts = {} as Record<string, any>
|
|
23
|
+
if (isPlainObject(lastArg)) {
|
|
24
|
+
opts = lastArg
|
|
25
|
+
keys = args.slice(0, -1)
|
|
26
|
+
} else {
|
|
27
|
+
keys = args
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const { default: defaultValue, error, ...options } = opts || {}
|
|
31
|
+
const ctx = cls.getStore()!
|
|
32
|
+
try {
|
|
33
|
+
return (
|
|
34
|
+
(await this.handlers[store].handler(ctx, keys, options)) ?? defaultValue
|
|
35
|
+
)
|
|
36
|
+
} catch (e: any) {
|
|
37
|
+
if (error === "ignore") {
|
|
38
|
+
return defaultValue
|
|
39
|
+
} else if (error === "warn") {
|
|
40
|
+
ctx.logger.warn(`Lookup to ${store} failed: ${e.message}`)
|
|
41
|
+
} else {
|
|
42
|
+
throw e
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { isPlainObject } from "es-toolkit"
|
|
2
|
+
import type { Katmer } from "../interfaces/task.interface"
|
|
3
|
+
|
|
4
|
+
export const URLLookup = {
|
|
5
|
+
key: "url",
|
|
6
|
+
handler: async (
|
|
7
|
+
_ctx: Katmer.TaskContext,
|
|
8
|
+
urlParts: string[],
|
|
9
|
+
options = {} as RequestInit
|
|
10
|
+
) => {
|
|
11
|
+
const url = new URL(urlParts.join("/"))
|
|
12
|
+
const res = await fetch(url, options)
|
|
13
|
+
if (res.ok) {
|
|
14
|
+
return await res.text()
|
|
15
|
+
} else {
|
|
16
|
+
throw new Error(
|
|
17
|
+
`Failed to fetch url: ${url} status: ${res.status} response: ${(await res.text()) ?? "no response"}`
|
|
18
|
+
)
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { get } from "es-toolkit/compat"
|
|
2
|
+
import type { Katmer } from "../interfaces/task.interface"
|
|
3
|
+
|
|
4
|
+
export const VarLookup = {
|
|
5
|
+
key: "var",
|
|
6
|
+
handler: async (
|
|
7
|
+
ctx: Katmer.TaskContext,
|
|
8
|
+
varParts: string[],
|
|
9
|
+
_opts: Record<string, any>
|
|
10
|
+
) => {
|
|
11
|
+
return get(ctx.variables, varParts)
|
|
12
|
+
}
|
|
13
|
+
}
|