@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.
Files changed (71) hide show
  1. package/README.md +1 -0
  2. package/cli/katmer.js +28 -0
  3. package/cli/run.ts +16 -0
  4. package/index.ts +5 -0
  5. package/lib/config.ts +82 -0
  6. package/lib/interfaces/config.interface.ts +113 -0
  7. package/lib/interfaces/executor.interface.ts +13 -0
  8. package/lib/interfaces/module.interface.ts +170 -0
  9. package/lib/interfaces/provider.interface.ts +214 -0
  10. package/lib/interfaces/task.interface.ts +100 -0
  11. package/lib/katmer.ts +126 -0
  12. package/lib/lookup/env.lookup.ts +13 -0
  13. package/lib/lookup/file.lookup.ts +23 -0
  14. package/lib/lookup/index.ts +46 -0
  15. package/lib/lookup/url.lookup.ts +21 -0
  16. package/lib/lookup/var.lookup.ts +13 -0
  17. package/lib/module.ts +560 -0
  18. package/lib/module_registry.ts +64 -0
  19. package/lib/modules/apt-repository/apt-repository.module.ts +435 -0
  20. package/lib/modules/apt-repository/apt-sources-list.ts +363 -0
  21. package/lib/modules/apt.module.ts +546 -0
  22. package/lib/modules/archive.module.ts +280 -0
  23. package/lib/modules/become.module.ts +119 -0
  24. package/lib/modules/copy.module.ts +807 -0
  25. package/lib/modules/cron.module.ts +541 -0
  26. package/lib/modules/debug.module.ts +231 -0
  27. package/lib/modules/gather_facts.module.ts +605 -0
  28. package/lib/modules/git.module.ts +243 -0
  29. package/lib/modules/hostname.module.ts +213 -0
  30. package/lib/modules/http/http.curl.module.ts +342 -0
  31. package/lib/modules/http/http.local.module.ts +253 -0
  32. package/lib/modules/http/http.module.ts +298 -0
  33. package/lib/modules/index.ts +14 -0
  34. package/lib/modules/package.module.ts +283 -0
  35. package/lib/modules/script.module.ts +121 -0
  36. package/lib/modules/set_fact.module.ts +171 -0
  37. package/lib/modules/systemd_service.module.ts +373 -0
  38. package/lib/modules/template.module.ts +478 -0
  39. package/lib/providers/local.provider.ts +336 -0
  40. package/lib/providers/provider_response.ts +20 -0
  41. package/lib/providers/ssh/ssh.provider.ts +420 -0
  42. package/lib/providers/ssh/ssh.utils.ts +31 -0
  43. package/lib/schemas/katmer_config.schema.json +358 -0
  44. package/lib/target_resolver.ts +298 -0
  45. package/lib/task/controls/environment.control.ts +42 -0
  46. package/lib/task/controls/index.ts +13 -0
  47. package/lib/task/controls/loop.control.ts +89 -0
  48. package/lib/task/controls/register.control.ts +23 -0
  49. package/lib/task/controls/until.control.ts +64 -0
  50. package/lib/task/controls/when.control.ts +25 -0
  51. package/lib/task/task.ts +225 -0
  52. package/lib/utils/ajv.utils.ts +24 -0
  53. package/lib/utils/cls.ts +4 -0
  54. package/lib/utils/datetime.utils.ts +15 -0
  55. package/lib/utils/errors.ts +25 -0
  56. package/lib/utils/execute-shell.ts +116 -0
  57. package/lib/utils/file.utils.ts +68 -0
  58. package/lib/utils/http.utils.ts +10 -0
  59. package/lib/utils/json.utils.ts +15 -0
  60. package/lib/utils/number.utils.ts +9 -0
  61. package/lib/utils/object.utils.ts +11 -0
  62. package/lib/utils/os.utils.ts +31 -0
  63. package/lib/utils/path.utils.ts +9 -0
  64. package/lib/utils/renderer/render_functions.ts +3 -0
  65. package/lib/utils/renderer/renderer.ts +89 -0
  66. package/lib/utils/renderer/twig.ts +191 -0
  67. package/lib/utils/string.utils.ts +33 -0
  68. package/lib/utils/typed-event-emitter.ts +26 -0
  69. package/lib/utils/unix.utils.ts +91 -0
  70. package/lib/utils/windows.utils.ts +92 -0
  71. 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
+ }