@localctl/cli 0.5.0-beta.2 → 0.5.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.
Files changed (45) hide show
  1. package/README.md +32 -76
  2. package/package.json +3 -10
  3. package/schema/app.schema.json +678 -44
  4. package/src/commands/doctor.js +1 -16
  5. package/src/commands/down.js +3 -1
  6. package/src/commands/generateManifests.js +4 -0
  7. package/src/commands/logs.js +1 -0
  8. package/src/commands/new.js +42 -20
  9. package/src/commands/prune.js +4 -0
  10. package/src/commands/reload.js +11 -3
  11. package/src/commands/secrets.js +3 -3
  12. package/src/commands/setup.js +1 -11
  13. package/src/commands/tiltConfig.js +11 -0
  14. package/src/commands/uninstall.js +5 -6
  15. package/src/commands/up.js +9 -1
  16. package/src/commands/validate.js +48 -0
  17. package/src/index.js +2 -0
  18. package/src/lib/addonDirs.js +1 -2
  19. package/src/lib/addonLoader.js +38 -17
  20. package/src/lib/addons/mongo/addon.yaml +2 -0
  21. package/src/lib/addons/postgres/addon.yaml +5 -1
  22. package/src/lib/addons/redis/addon.yaml +2 -0
  23. package/src/lib/appSecrets.js +107 -36
  24. package/src/lib/clusterProvision.js +45 -211
  25. package/src/lib/configSchema.js +412 -41
  26. package/src/lib/engine.js +3 -13
  27. package/src/lib/envFile.js +58 -0
  28. package/src/lib/exec.js +6 -7
  29. package/src/lib/hostGuard.js +107 -0
  30. package/src/lib/hostsFile.js +9 -8
  31. package/src/lib/kubeErrors.js +22 -0
  32. package/src/lib/loadConfig.js +183 -9
  33. package/src/lib/manifestGen.js +96 -32
  34. package/src/lib/namespaces.js +83 -0
  35. package/src/lib/pathGuard.js +79 -0
  36. package/src/lib/paths.js +2 -6
  37. package/src/lib/podman.js +3 -93
  38. package/src/lib/projectContext.js +2 -2
  39. package/src/lib/quantities.js +20 -0
  40. package/src/lib/safeRead.js +41 -0
  41. package/src/lib/tiltConfig.js +25 -0
  42. package/src/lib/toolInstall.js +2 -141
  43. package/src/lib/yamlLoad.js +48 -0
  44. package/templates/Tiltfile.tmpl +22 -14
  45. package/src/lib/linuxDistro.js +0 -122
@@ -1,8 +1,11 @@
1
1
  # Generated by `localctl app new`.
2
- # Most configuration lives in .local/config.json - edit that instead of this file
2
+ # Most configuration lives in .local/config.yaml - edit that instead of this file
3
3
  # unless you need custom Tilt behavior (extra resources, manual k8s_yaml, etc).
4
4
 
5
- config_json = read_json('.local/config.json')
5
+ # Build, sync and port settings come resolved from the CLI (defaults applied), so this file
6
+ # doesn't parse the config itself. Tilt re-runs it whenever the config file changes.
7
+ watch_file('.local/config.yaml')
8
+ app_config = decode_json(str(local(['localctl', 'app', 'tilt-config'], quiet=True)))
6
9
 
7
10
  # Images are built and pushed from your machine to this app's project registry (localhost:5050
8
11
  # for the default project - port 5050, not 5000, to dodge macOS Control Center's claim on 5000 -
@@ -12,24 +15,29 @@ config_json = read_json('.local/config.json')
12
15
  registry_info = decode_json(str(local(['localctl', 'app', 'registry-info'], quiet=True)))
13
16
  default_registry(registry_info['pushHost'], host_from_cluster=registry_info['clusterHost'])
14
17
 
18
+ # `target` is only passed when set: Tilt rejects None for it.
19
+ build = app_config['build']
20
+ extra = {}
21
+ if build['target']:
22
+ extra['target'] = build['target']
15
23
  docker_build(
16
- config_json['name'],
17
- config_json['build'].get('context', '.'),
18
- dockerfile=config_json['build'].get('dockerfile', 'Dockerfile'),
24
+ app_config['name'],
25
+ build['context'],
26
+ dockerfile=build['dockerfile'],
27
+ build_args=build['args'],
28
+ ignore=build['ignore'],
19
29
  live_update=[
20
30
  sync(s['localPath'], s['remotePath'])
21
- for s in config_json.get('sync', [])
31
+ for s in app_config['sync']
32
+ ] + [
33
+ run(r['command'], trigger=r['trigger'])
34
+ for r in app_config['run']
22
35
  ],
36
+ **extra
23
37
  )
24
38
 
25
- # Manifests are generated by the localctl CLI from .local/config.json so there is a single
39
+ # Manifests are generated by the localctl CLI from .local/config.yaml so there is a single
26
40
  # source of truth for the app's k8s definition.
27
41
  k8s_yaml(local(['localctl', 'app', 'generate-manifests']))
28
42
 
29
- port_forwards = ['%s:%s' % (config_json['port'], config_json['port'])]
30
- debug_cfg = config_json.get('debug', {})
31
- if debug_cfg.get('enabled'):
32
- dp = debug_cfg['port']
33
- port_forwards.append('%s:%s' % (dp, dp))
34
-
35
- k8s_resource(config_json['name'], port_forwards=port_forwards)
43
+ k8s_resource(app_config['name'], port_forwards=app_config['portForwards'])
@@ -1,122 +0,0 @@
1
- const fs = require('fs');
2
- const os = require('os');
3
- const path = require('path');
4
- const { run, commandExists } = require('./exec');
5
- const logger = require('./logger');
6
-
7
- // Families localctl knows how to give exact commands for. Anything else is "unknown" and gets
8
- // generic guidance instead of a guess.
9
- const FAMILY_BY_ID = [
10
- ['debian', ['debian', 'ubuntu']],
11
- ['rhel', ['fedora', 'rhel', 'centos', 'rocky', 'almalinux']],
12
- ['arch', ['arch']],
13
- ['suse', ['suse', 'opensuse']],
14
- ['alpine', ['alpine']],
15
- ];
16
-
17
- // Reads ID and ID_LIKE from os-release, so derivatives resolve to their parent (Mint, Pop!_OS ->
18
- // debian; Rocky, Alma -> rhel; Manjaro -> arch).
19
- function detectDistro() {
20
- let release = null;
21
- // /usr/lib/os-release is the documented fallback when /etc/os-release is missing.
22
- for (const file of ['/etc/os-release', '/usr/lib/os-release']) {
23
- try {
24
- release = fs.readFileSync(file, 'utf8');
25
- break;
26
- } catch (e) {
27
- // try the next location
28
- }
29
- }
30
- if (release === null) return { id: 'unknown', family: 'unknown' };
31
- const value = (key) => {
32
- const m = release.match(new RegExp(`^${key}=(.*)$`, 'm'));
33
- return m ? m[1].replace(/"/g, '').trim() : '';
34
- };
35
- const id = value('ID') || 'unknown';
36
- const ids = [id, ...value('ID_LIKE').split(/\s+/)].filter(Boolean);
37
- const match = FAMILY_BY_ID.find(([, names]) => names.some((n) => ids.includes(n)));
38
- return { id, family: match ? match[0] : 'unknown' };
39
- }
40
-
41
- function hasSystemd() {
42
- return fs.existsSync('/run/systemd/system');
43
- }
44
-
45
- function isRoot() {
46
- return typeof process.getuid === 'function' && process.getuid() === 0;
47
- }
48
-
49
- // What `localctl setup --install-docker` would do, as printable steps. Shown in the "no engine"
50
- // error too, so the user can run them by hand. Returns null for families without a known recipe.
51
- function dockerInstallSteps(family, id) {
52
- const sudo = isRoot() ? '' : 'sudo ';
53
- const user = os.userInfo().username;
54
- let install;
55
- if (family === 'rhel' && id !== 'fedora') {
56
- // Docker's convenience script derives the repo path from the distro ID, and publishes
57
- // .../linux/centos and .../linux/rhel but not .../linux/rocky or .../linux/almalinux: it adds
58
- // a repo whose packages don't exist ("Unable to find a match: docker-ce"). The CentOS repo is
59
- // what Docker documents for the RHEL rebuilds.
60
- install = [
61
- `${sudo}dnf -y install dnf-plugins-core`,
62
- `${sudo}dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo`,
63
- `${sudo}dnf -y install docker-ce docker-ce-cli containerd.io`,
64
- ];
65
- } else if (family === 'debian' || family === 'rhel') {
66
- install = [`curl -fsSL https://get.docker.com -o get-docker.sh && ${sudo}sh get-docker.sh`];
67
- } else if (family === 'arch') {
68
- install = [`${sudo}pacman -S --noconfirm docker`];
69
- } else if (family === 'suse') {
70
- install = [`${sudo}zypper --non-interactive install docker`];
71
- } else {
72
- return null;
73
- }
74
- const steps = [...install];
75
- if (hasSystemd()) steps.push(`${sudo}systemctl enable --now docker`);
76
- if (!isRoot()) steps.push(`${sudo}usermod -aG docker ${user}`);
77
- return steps;
78
- }
79
-
80
- // Runs dockerInstallSteps() for real. The download goes to a temp file first (never piped into a
81
- // shell) and Docker's official script is the only remote code involved. Needs sudo, so it only
82
- // ever runs on an explicit `--install-docker`.
83
- function installDocker() {
84
- const { family, id } = detectDistro();
85
- if (!dockerInstallSteps(family, id)) {
86
- throw new Error(`No automatic Docker install for "${id}". Install Docker Engine for your distro (https://docs.docker.com/engine/install/) and re-run.`);
87
- }
88
- const viaScript = family === 'debian' || (family === 'rhel' && id === 'fedora');
89
- if (!commandExists('curl') && viaScript) {
90
- throw new Error('curl is needed to download the Docker install script. Install it and re-run.');
91
- }
92
- logger.step(`Installing Docker Engine (${id}). This needs sudo and will prompt for your password...`);
93
- const sudo = isRoot() ? [] : ['sudo'];
94
- const exec = (cmd, args) => run(sudo.length ? sudo[0] : cmd, sudo.length ? [cmd, ...args] : args);
95
-
96
- if (viaScript) {
97
- const script = path.join(os.tmpdir(), 'localctl-get-docker.sh');
98
- run('curl', ['-fsSL', 'https://get.docker.com', '-o', script]);
99
- try {
100
- exec('sh', [script]);
101
- } finally {
102
- fs.rmSync(script, { force: true });
103
- }
104
- } else if (family === 'rhel') {
105
- exec('dnf', ['-y', 'install', 'dnf-plugins-core']);
106
- exec('dnf', ['config-manager', '--add-repo', 'https://download.docker.com/linux/centos/docker-ce.repo']);
107
- exec('dnf', ['-y', 'install', 'docker-ce', 'docker-ce-cli', 'containerd.io']);
108
- } else if (family === 'arch') {
109
- exec('pacman', ['-S', '--noconfirm', 'docker']);
110
- } else if (family === 'suse') {
111
- exec('zypper', ['--non-interactive', 'install', 'docker']);
112
- }
113
- if (hasSystemd()) exec('systemctl', ['enable', '--now', 'docker']);
114
- if (!isRoot()) exec('usermod', ['-aG', 'docker', os.userInfo().username]);
115
-
116
- logger.success('Docker installed.');
117
- if (!isRoot()) {
118
- logger.warn('Group membership only applies to new sessions: log out and back in (or run "newgrp docker"), then re-run "localctl setup".');
119
- }
120
- }
121
-
122
- module.exports = { detectDistro, hasSystemd, dockerInstallSteps, installDocker };