Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,16 @@

This file is a history of the changes made to @idearium/cli.

## Unreleased
## v6.1.0-beta.1 - 2026-09-15

### Added

- Support for 1Password secret references (`op://`) within Kubernetes manifest templates. References are resolved with `op inject` (only when present) before templates are rendered, and `type: secret` services can use `stringData`, which is base64 encoded into `data` within the compiled manifest. When references are present, the 1Password cli is authenticated up front (`op whoami`), halting with a friendly signin message otherwise.
- Compiled secret manifests are now written with `0600` permissions, and removed after they've been applied to Kubernetes (`c kc start`, `c kc apply`) or when `c skaffold dev` exits (new `c kc secrets-clean` command, quiet via `-q`). This only applies to the local environment; other environments are unaffected. `c kc manifests` intentionally leaves the compiled files in place, as its output is its purpose.

### Changed

- `c skaffold dev` now halts when manifest compilation fails, rather than continuing into skaffold with stale compiled manifests.

## v6.0.0 - 2026-04-21

Expand Down
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,29 @@ You'll then need to supply all of the values that the template file requires. Yo

The `c kc apply` will automatically provide the values for `namespace`, `prefix` and `tag`. If you'd like to provide something else, simply write a function that returns an object with `label` and `value`. Then use the value of `label` within a template placeholder (i.e. `{{a}}`) and it will be updated with the `value` (i.e. `{{b}}`).

##### Secret templates

Any template (`.yaml.tmpl`) can contain [1Password secret references](https://www.1password.dev/connect/knowledge-base/secrets-references/) (i.e. `op://vault/item/section/field`). When a template contains at least one reference, the cli will first ensure the 1Password cli is installed and authenticated (via `op whoami`), halting with a friendly message if not (`eval $(op signin)`). It will then resolve the references with the 1Password cli (`op inject`) before the template is rendered, so references are never interfered with by template placeholders. Templates without secret references never invoke `op`.

This makes it possible to commit a template containing secret references, and have the compiled manifest (within `.compiled`, which should be gitignored) contain the actual secrets, ready to be deployed to Kubernetes.

For a service of `type: secret`, author the template with `stringData` instead of `data`:

```
apiVersion: v1
kind: Secret
metadata:
name: site
namespace: ras-rsc-local
type: Opaque
stringData:
ALGOLIA_SEARCH_API_KEY: op://ras-rsc/site/LOCAL/ALGOLIA_SEARCH_API_KEY
```

When the template is compiled, each `stringData` value will be base64 encoded and written to the compiled manifest as `data`, just as Kubernetes expects. `stringData` values must be single-line, and shouldn't be quoted or contain inline comments.

Compiled secret manifests contain plaintext secrets, so they are treated as sensitive: they're written with `0600` permissions, and removed once they've been applied to Kubernetes. `c kc start` and `c kc apply` remove them after applying, and `c skaffold dev` removes them (via `c kc secrets-clean`) when it exits. `c kc manifests` intentionally leaves them in place, as its compiled output is its purpose. This only applies to the local environment; other environments are unaffected.

### MongoDB configuration

The Idearium cli supports a MongoDB configuration. The MongoDB configuration can be used to access local and remote databases.
Expand Down
19 changes: 12 additions & 7 deletions bin/c-kc-apply.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const {
const { formatProjectPrefix } = require('./lib/c-project');
const {
ensureServiceFilesExist,
removeCompiledSecrets,
renderServicesTemplates,
setLocalsForServices,
} = require('./lib/c-kc');
Expand Down Expand Up @@ -100,35 +101,35 @@ return Promise.all([loadConfig(), loadState()])
return reject(e);
}

return resolve([services, path]);
return resolve([services, path, state]);
})
)
.then(
([services, path]) =>
([services, path, state]) =>
new Promise(async (resolve, reject) => {
try {
await ensureServiceFilesExist(path, services);
} catch (e) {
reject(e);
}

return resolve([services, path]);
return resolve([services, path, state]);
})
)
.then(
([services, path]) =>
([services, path, state]) =>
new Promise(async (resolve, reject) => {
try {
await renderServicesTemplates(path, services);
} catch (e) {
reject(e);
}

return resolve([services, path]);
return resolve([services, path, state]);
})
)
.then(
([services, path]) =>
([services, path, state]) =>
new Promise((resolve) => {
services.forEach((service) => {
exec(
Expand All @@ -141,7 +142,11 @@ return Promise.all([loadConfig(), loadState()])
);
});

return resolve();
return removeCompiledSecrets({
env: state.env,
path,
services,
}).then(resolve);
})
)
.catch((err) => {
Expand Down
69 changes: 69 additions & 0 deletions bin/c-kc-secrets-clean.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
'use strict';

const program = require('commander');
const getPropertyPath = require('get-value');
const {
kubernetesLocationsToObjects,
loadConfig,
loadState,
reportError,
} = require('./lib/c');
const { removeCompiledSecrets } = require('./lib/c-kc');

program
.description(
'This command will remove any compiled secret manifests, so that plaintext secrets do not linger on disk. It only applies to the local environment.'
)
.option('-q', 'Do not print the removed files.')
.parse(process.argv);

return Promise.all([loadState(), loadConfig()])
.then(([state, config]) => {
const { locations, path } = getPropertyPath(
config,
`kubernetes.environments.${state.env}`
);

return [
removeCompiledSecrets({
env: state.env,
path,
services: kubernetesLocationsToObjects(locations),
}),
state.env,
];
})
.then(async ([removal, env]) => {
const removed = await removal;

if (program.Q) {
return;
}

if (removed.length === 0) {
// eslint-disable-next-line no-console
return console.log(
env === 'local'
? 'No compiled secret manifests to remove.'
: `Nothing to do: secrets are only removed for the local environment (currently ${env}).`
);
}

removed.forEach((file) => {
// eslint-disable-next-line no-console
console.log(`Removed ${file}`);
});
})
.catch((err) => {
if (err.code === 'ENOENT') {
return reportError(
new Error(
'Please create a c.js file with your project configuration. See https://github.com/idearium/cli#configuration'
),
false,
true
);
}

return reportError(err, false, true);
});
19 changes: 12 additions & 7 deletions bin/c-kc-start.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const {
const { formatProjectPrefix } = require('./lib/c-project');
const {
ensureServiceFilesExist,
removeCompiledSecrets,
renderServicesTemplates,
setLocalsForServices,
} = require('./lib/c-kc');
Expand Down Expand Up @@ -57,35 +58,35 @@ return Promise.all([loadState(), loadConfig()])
return reject(e);
}

return resolve([services, path]);
return resolve([services, path, state]);
})
)
.then(
([services, path]) =>
([services, path, state]) =>
new Promise(async (resolve, reject) => {
try {
await ensureServiceFilesExist(path, services);
} catch (e) {
reject(e);
}

return resolve([services, path]);
return resolve([services, path, state]);
})
)
.then(
([services, path]) =>
([services, path, state]) =>
new Promise(async (resolve, reject) => {
try {
await renderServicesTemplates(path, services);
} catch (e) {
reject(e);
}

return resolve([services, path]);
return resolve([services, path, state]);
})
)
.then(
([services, path]) =>
([services, path, state]) =>
new Promise((resolve, reject) => {
const [namespace] = services
.filter((service) => service.type === 'namespace')
Expand Down Expand Up @@ -116,7 +117,11 @@ return Promise.all([loadState(), loadConfig()])
)}`
);

return resolve();
return removeCompiledSecrets({
env: state.env,
path,
services,
}).then(resolve);
})
)
.catch((err) => {
Expand Down
4 changes: 4 additions & 0 deletions bin/c-kc.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ program
'Get the name of a pod for a Kubernetes location.'
)
.command('secret', 'Base64 encode a string, ready for a Kubernetes secret.')
.command(
'secrets-clean',
'Remove compiled secret manifests, so plaintext secrets do not linger on disk.'
)
.command('start', 'Deploy all Kubernetes locations.')
.command(
'stop',
Expand Down
2 changes: 2 additions & 0 deletions bin/c-skaffold-dev
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#!/usr/bin/env bash
set -e
trap 'npx c kc secrets-clean -q' EXIT

npx c kc manifests
DOCKER_SCAN_SUGGEST=false skaffold dev
Loading