From 5f2cc3fe72e710f3bf0f26a5d55fbb07b1c013ed Mon Sep 17 00:00:00 2001 From: Scott Mebberson <74628+smebberson@users.noreply.github.com> Date: Mon, 14 Sep 2026 16:20:19 +0930 Subject: [PATCH 1/5] Add 1Password secret reference support to manifest templates --- CHANGELOG.md | 4 ++ README.md | 21 ++++++++ bin/lib/c-kc.js | 134 +++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 141 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c9d12d5..d5de661 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ This file is a history of the changes made to @idearium/cli. ## Unreleased +### 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. + ## v6.0.0 - 2026-04-21 ### Removed diff --git a/README.md b/README.md index ebec1b9..8713093 100644 --- a/README.md +++ b/README.md @@ -269,6 +269,27 @@ 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 resolve them with the 1Password cli (`op inject`) before the template is rendered, so references are never interfered with by template placeholders. This requires the 1Password cli to be installed and signed in (`op signin`). 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. + ### MongoDB configuration The Idearium cli supports a MongoDB configuration. The MongoDB configuration can be used to access local and remote databases. diff --git a/bin/lib/c-kc.js b/bin/lib/c-kc.js index 8190294..ed055dc 100644 --- a/bin/lib/c-kc.js +++ b/bin/lib/c-kc.js @@ -1,14 +1,17 @@ 'use strict'; const fs = require('fs'); -const { copy, ensureDir } = require('fs-extra'); +const { copy, ensureDir, mkdtemp, remove } = require('fs-extra'); +const { tmpdir } = require('os'); const { join, resolve: resolvePath } = require('path'); const Mustache = require('mustache'); const { promisify } = require('util'); +const { execFile } = require('child_process'); const { constants } = fs; const access = promisify(fs.access); +const execFileAsync = promisify(execFile); const readFile = promisify(fs.readFile); const writeFile = promisify(fs.writeFile); @@ -56,8 +59,70 @@ const ensureServiceFilesExist = async (path = '', services = []) => { }); }; +/** + * Check if a template's content contains 1Password secret references. + * @param {String} content The content of a template file. + * @returns {Boolean} True if the content contains secret references. + */ +const containsSecretReferences = (content) => content.includes('op://'); + +/** + * Convert a stringData block (single-line values only) into a data block, with each value base64 encoded. + * @param {String} content A rendered Kubernetes Secret manifest. + * @returns {String} The manifest with stringData converted to base64 encoded data. + */ +const encodeSecretStringData = (content) => { + const lines = content.split('\n'); + const output = []; + let inStringData = false; + + lines.forEach((line) => { + const stringData = /^(\s*)stringData:\s*$/.exec(line); + + if (stringData) { + inStringData = true; + output.push(`${stringData[1]}data:`); + + return; + } + + const field = /^(\s+)([^:\s]+):\s*(.*)$/.exec(line); + + if (inStringData && field) { + output.push( + `${field[1]}${field[2]}: ${Buffer.from( + field[3].trim() + ).toString('base64')}` + ); + + return; + } + + if (inStringData) { + inStringData = false; + } + + output.push(line); + }); + + return output.join('\n'); +}; + const flagBuildArgs = (args = []) => args.map((arg) => `--build-arg ${arg}`); +/** + * Pad a string with a left space, if the string has a length. + * @param {String} str A string to pad with a left space. + * @return {String} A string left-padded with a space. + */ +const leftSpace = (str) => { + if (str && typeof str === 'string') { + return ` ${str}`; + } + + return str; +}; + const formatBuildArgs = (args) => { if (Array.isArray(args)) { return args.length > 0 ? `${leftSpace(args.join(' '))}` : ''; @@ -86,16 +151,32 @@ const formatBuildArgs = (args) => { }; /** - * Pad a string with a left space, if the string has a length. - * @param {String} str A string to pad with a left space. - * @return {String} A string left-padded with a space. + * Resolve any 1Password secret references within template content using the op cli. + * Uses file mode (rather than a stdin pipe) because the op cli can miss data + * written to a stdin pipe before it starts reading. + * @param {String} content The content of a template file, containing secret references. + * @returns {Promise} The content with all secret references resolved to their actual values. */ -const leftSpace = (str) => { - if (str && typeof str === 'string') { - return ` ${str}`; - } +const injectSecretReferences = async (content) => { + const tempFolder = await mkdtemp(join(tmpdir(), 'c-kc-')); + const inPath = join(tempFolder, 'inject.yaml.tmpl'); + const outPath = join(tempFolder, 'inject.yaml'); - return str; + try { + await writeFile(inPath, content, { mode: 0o600 }); + + await execFileAsync('op', [ + 'inject', + '--in-file', + inPath, + '--out-file', + outPath, + ]); + + return await readFile(outPath, 'utf-8'); + } finally { + await remove(tempFolder); + } }; const renderServicesTemplates = async (path = '', services = []) => { @@ -105,19 +186,36 @@ const renderServicesTemplates = async (path = '', services = []) => { const destinationFolder = join(sourceFolder, '.compiled'); const destinationPath = join(destinationFolder, service.path); - try { - const content = await readFile(`${sourcePath}.yaml.tmpl`, 'utf-8'); + let content; - await ensureDir(destinationFolder); - await writeFile( - `${destinationPath}.yaml`, - Mustache.render(content, service.locals), - 'utf8' - ); + try { + content = await readFile(`${sourcePath}.yaml.tmpl`, 'utf-8'); } catch (e) { // Do nothing. - // It just means we don't have a templ file to render. + // It just means we don't have a template file to render. + return; + } + + // Secret references are resolved before the template is rendered, so + // that Mustache never has the opportunity to interfere with them. + if (containsSecretReferences(content)) { + try { + content = await injectSecretReferences(content); + } catch (e) { + throw new Error( + `Could not inject 1Password secret references: ${e.message}. Please ensure the 1Password cli is installed and you are signed in (op signin).` + ); + } } + + let rendered = Mustache.render(content, service.locals); + + if (service.type === 'secret') { + rendered = encodeSecretStringData(rendered); + } + + await ensureDir(destinationFolder); + await writeFile(`${destinationPath}.yaml`, rendered, 'utf8'); }); }; From 649731fba4c1e9c390d05ff551def9e5693f9806 Mon Sep 17 00:00:00 2001 From: Scott Mebberson <74628+smebberson@users.noreply.github.com> Date: Tue, 15 Sep 2026 11:52:31 +0930 Subject: [PATCH 2/5] Halt with a friendly error when op is missing or unauthenticated, and halt skaffold on compile failure --- CHANGELOG.md | 6 +++++- README.md | 2 +- bin/c-skaffold-dev | 1 + bin/lib/c-kc.js | 30 +++++++++++++++++++++++++++++- 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5de661..d124be7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,11 @@ This file is a history of the changes made to @idearium/cli. ### 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. +- 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. + +### 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 diff --git a/README.md b/README.md index 8713093..d3dcc00 100644 --- a/README.md +++ b/README.md @@ -271,7 +271,7 @@ The `c kc apply` will automatically provide the values for `namespace`, `prefix` ##### 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 resolve them with the 1Password cli (`op inject`) before the template is rendered, so references are never interfered with by template placeholders. This requires the 1Password cli to be installed and signed in (`op signin`). Templates without secret references never invoke `op`. +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. diff --git a/bin/c-skaffold-dev b/bin/c-skaffold-dev index 1ab8037..de30198 100755 --- a/bin/c-skaffold-dev +++ b/bin/c-skaffold-dev @@ -1,4 +1,5 @@ #!/usr/bin/env bash +set -e npx c kc manifests DOCKER_SCAN_SUGGEST=false skaffold dev diff --git a/bin/lib/c-kc.js b/bin/lib/c-kc.js index ed055dc..0986a95 100644 --- a/bin/lib/c-kc.js +++ b/bin/lib/c-kc.js @@ -157,6 +157,32 @@ const formatBuildArgs = (args) => { * @param {String} content The content of a template file, containing secret references. * @returns {Promise} The content with all secret references resolved to their actual values. */ +/** + * Ensure the 1Password cli is installed and an authenticated session is + * available, before any op command is attempted. The check is memoized: it + * runs at most once per cli invocation. + * @returns {Promise} Rejects with a friendly error when the cli is missing or not signed in. + */ +let opAuthenticated = null; + +const ensureOpAuthenticated = () => { + if (!opAuthenticated) { + opAuthenticated = execFileAsync('op', ['whoami']).catch((e) => { + if (e.code === 'ENOENT') { + throw new Error( + 'The 1Password cli (op) is not installed. Install it to resolve secret references within templates.' + ); + } + + throw new Error( + 'Not signed in to the 1Password cli. Run `eval $(op signin)` in this shell and try again.' + ); + }); + } + + return opAuthenticated; +}; + const injectSecretReferences = async (content) => { const tempFolder = await mkdtemp(join(tmpdir(), 'c-kc-')); const inPath = join(tempFolder, 'inject.yaml.tmpl'); @@ -199,11 +225,13 @@ const renderServicesTemplates = async (path = '', services = []) => { // Secret references are resolved before the template is rendered, so // that Mustache never has the opportunity to interfere with them. if (containsSecretReferences(content)) { + await ensureOpAuthenticated(); + try { content = await injectSecretReferences(content); } catch (e) { throw new Error( - `Could not inject 1Password secret references: ${e.message}. Please ensure the 1Password cli is installed and you are signed in (op signin).` + `Could not inject 1Password secret references: ${e.message}. Please ensure the 1Password cli is installed and you are signed in (eval $(op signin)).` ); } } From 5b9c2a243bea22d39d685bdcbd1e3fae46387a07 Mon Sep 17 00:00:00 2001 From: Scott Mebberson <74628+smebberson@users.noreply.github.com> Date: Tue, 15 Sep 2026 13:01:05 +0930 Subject: [PATCH 3/5] Remove compiled secret manifests after use, and write them 0600 --- CHANGELOG.md | 1 + README.md | 2 ++ bin/c-kc-apply.js | 19 +++++++----- bin/c-kc-secrets-clean.js | 64 +++++++++++++++++++++++++++++++++++++++ bin/c-kc-start.js | 19 +++++++----- bin/c-kc.js | 4 +++ bin/c-skaffold-dev | 1 + bin/lib/c-kc.js | 55 ++++++++++++++++++++++++++++++++- 8 files changed, 150 insertions(+), 15 deletions(-) create mode 100644 bin/c-kc-secrets-clean.js diff --git a/CHANGELOG.md b/CHANGELOG.md index d124be7..4e9a0a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ This file is a history of the changes made to @idearium/cli. ### 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). 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 diff --git a/README.md b/README.md index d3dcc00..e3dd2d5 100644 --- a/README.md +++ b/README.md @@ -290,6 +290,8 @@ stringData: 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. diff --git a/bin/c-kc-apply.js b/bin/c-kc-apply.js index 99d4bf2..f5000c1 100644 --- a/bin/c-kc-apply.js +++ b/bin/c-kc-apply.js @@ -14,6 +14,7 @@ const { const { formatProjectPrefix } = require('./lib/c-project'); const { ensureServiceFilesExist, + removeCompiledSecrets, renderServicesTemplates, setLocalsForServices, } = require('./lib/c-kc'); @@ -100,11 +101,11 @@ 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); @@ -112,11 +113,11 @@ return Promise.all([loadConfig(), loadState()]) 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); @@ -124,11 +125,11 @@ return Promise.all([loadConfig(), loadState()]) reject(e); } - return resolve([services, path]); + return resolve([services, path, state]); }) ) .then( - ([services, path]) => + ([services, path, state]) => new Promise((resolve) => { services.forEach((service) => { exec( @@ -141,7 +142,11 @@ return Promise.all([loadConfig(), loadState()]) ); }); - return resolve(); + return removeCompiledSecrets({ + env: state.env, + path, + services, + }).then(resolve); }) ) .catch((err) => { diff --git a/bin/c-kc-secrets-clean.js b/bin/c-kc-secrets-clean.js new file mode 100644 index 0000000..aa3bfd9 --- /dev/null +++ b/bin/c-kc-secrets-clean.js @@ -0,0 +1,64 @@ +'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.' + ) + .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 (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); + }); diff --git a/bin/c-kc-start.js b/bin/c-kc-start.js index 4c84bfc..c62d09b 100644 --- a/bin/c-kc-start.js +++ b/bin/c-kc-start.js @@ -14,6 +14,7 @@ const { const { formatProjectPrefix } = require('./lib/c-project'); const { ensureServiceFilesExist, + removeCompiledSecrets, renderServicesTemplates, setLocalsForServices, } = require('./lib/c-kc'); @@ -57,11 +58,11 @@ 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); @@ -69,11 +70,11 @@ return Promise.all([loadState(), loadConfig()]) 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); @@ -81,11 +82,11 @@ return Promise.all([loadState(), loadConfig()]) 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') @@ -116,7 +117,11 @@ return Promise.all([loadState(), loadConfig()]) )}` ); - return resolve(); + return removeCompiledSecrets({ + env: state.env, + path, + services, + }).then(resolve); }) ) .catch((err) => { diff --git a/bin/c-kc.js b/bin/c-kc.js index 5fa704f..333b110 100644 --- a/bin/c-kc.js +++ b/bin/c-kc.js @@ -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', diff --git a/bin/c-skaffold-dev b/bin/c-skaffold-dev index de30198..4c93653 100755 --- a/bin/c-skaffold-dev +++ b/bin/c-skaffold-dev @@ -1,5 +1,6 @@ #!/usr/bin/env bash set -e +trap 'npx c kc secrets-clean' EXIT npx c kc manifests DOCKER_SCAN_SUGGEST=false skaffold dev diff --git a/bin/lib/c-kc.js b/bin/lib/c-kc.js index 0986a95..34e4ac2 100644 --- a/bin/lib/c-kc.js +++ b/bin/lib/c-kc.js @@ -11,8 +11,10 @@ const { execFile } = require('child_process'); const { constants } = fs; const access = promisify(fs.access); +const chmod = promisify(fs.chmod); const execFileAsync = promisify(execFile); const readFile = promisify(fs.readFile); +const unlink = promisify(fs.unlink); const writeFile = promisify(fs.writeFile); /** @@ -237,14 +239,64 @@ const renderServicesTemplates = async (path = '', services = []) => { } let rendered = Mustache.render(content, service.locals); + const destinationFile = `${destinationPath}.yaml`; if (service.type === 'secret') { rendered = encodeSecretStringData(rendered); } await ensureDir(destinationFolder); - await writeFile(`${destinationPath}.yaml`, rendered, 'utf8'); + await writeFile(destinationFile, rendered, { + encoding: 'utf8', + mode: service.type === 'secret' ? 0o600 : 0o644, + }); + + if (service.type === 'secret') { + // writeFile's mode only applies at creation; chmod catches files + // left over from a previous run with looser permissions. + await chmod(destinationFile, 0o600); + } + }); +}; + +/** + * Remove compiled secret manifests, so that plaintext secrets don't linger on + * disk after they've been applied to Kubernetes. Only acts on the local + * environment; other environments are out of scope. + * @param {Object} options + * @param {String} options.env The current project environment. + * @param {String} options.path The path to a bunch of Kubernetes manifests. + * @param {Array} options.services An array of Kubernetes location services. + * @returns {Promise} The paths of the secret files that were removed. + */ +const removeCompiledSecrets = async ({ env, path = '', services = [] }) => { + if (env !== 'local') { + return []; + } + + const removed = []; + + await asyncForEach(services, async (service) => { + if (service.type !== 'secret') { + return; + } + + const destinationFolder = join( + resolvePath(process.cwd(), path), + '.compiled' + ); + const destinationFile = join(destinationFolder, `${service.path}.yaml`); + + try { + await unlink(destinationFile); + removed.push(destinationFile); + } catch (e) { + // Do nothing. + // It just means there was no compiled secret to remove. + } }); + + return removed; }; /** @@ -307,6 +359,7 @@ module.exports = { flagBuildArgs, formatBuildArgs, renderServicesTemplates, + removeCompiledSecrets, setLocalsForServices, validateBuildArgs, }; From 432d2e2e3a2a2b05d998babb53e29b83039c053d Mon Sep 17 00:00:00 2001 From: Scott Mebberson <74628+smebberson@users.noreply.github.com> Date: Tue, 15 Sep 2026 13:13:44 +0930 Subject: [PATCH 4/5] Run c skaffold dev's secret cleanup quietly via -q --- CHANGELOG.md | 2 +- bin/c-kc-secrets-clean.js | 5 +++++ bin/c-skaffold-dev | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e9a0a7..c36b071 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ This file is a history of the changes made to @idearium/cli. ### 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). 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. +- 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 diff --git a/bin/c-kc-secrets-clean.js b/bin/c-kc-secrets-clean.js index aa3bfd9..b6872f2 100644 --- a/bin/c-kc-secrets-clean.js +++ b/bin/c-kc-secrets-clean.js @@ -14,6 +14,7 @@ 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()]) @@ -35,6 +36,10 @@ return Promise.all([loadState(), loadConfig()]) .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( diff --git a/bin/c-skaffold-dev b/bin/c-skaffold-dev index 4c93653..c550fd1 100755 --- a/bin/c-skaffold-dev +++ b/bin/c-skaffold-dev @@ -1,6 +1,6 @@ #!/usr/bin/env bash set -e -trap 'npx c kc secrets-clean' EXIT +trap 'npx c kc secrets-clean -q' EXIT npx c kc manifests DOCKER_SCAN_SUGGEST=false skaffold dev From a6185ed8db9c6bde246e395b6815e9d35467f146 Mon Sep 17 00:00:00 2001 From: Scott Mebberson <74628+smebberson@users.noreply.github.com> Date: Tue, 15 Sep 2026 13:49:35 +0930 Subject: [PATCH 5/5] Version changelog for v6.1.0-beta.1 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c36b071..bb6bd5b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ This file is a history of the changes made to @idearium/cli. -## Unreleased +## v6.1.0-beta.1 - 2026-09-15 ### Added