Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
db921d7
test: sealed secrets
ferruhcihan Feb 4, 2026
3dbdf77
Merge remote-tracking branch 'origin/main' into APL-1476
ferruhcihan Feb 4, 2026
c9578aa
test: sealed secrets
ferruhcihan Feb 4, 2026
24618a0
test: sealed secrets
ferruhcihan Feb 4, 2026
d34988f
test: env.old.gotmpl
ferruhcihan Feb 4, 2026
a50af0a
test: sealed secrets
ferruhcihan Feb 5, 2026
fab2727
test: sealed secrets
ferruhcihan Feb 5, 2026
5e7e6b3
test: sealed secrets
ferruhcihan Feb 5, 2026
ef02d3a
test: sealed secrets
ferruhcihan Feb 5, 2026
3146d3a
test: sealed secrets
ferruhcihan Feb 5, 2026
a657b0e
test: sealed secrets
ferruhcihan Feb 5, 2026
11aba23
test: sealed secrets & fix tls issue
ferruhcihan Feb 5, 2026
4c1e369
test: sealed secrets
ferruhcihan Feb 5, 2026
508859e
Merge branch 'main' into APL-1476
ferruhcihan Feb 9, 2026
f435dfd
revert: unnecessary changes
ferruhcihan Feb 9, 2026
03c7756
fix: sealed secrets
ferruhcihan Feb 9, 2026
90d88cf
test: sealed secrets
ferruhcihan Feb 9, 2026
aa3f05b
test: sealed secrets
ferruhcihan Feb 9, 2026
31cf3d8
test: sealed secrets
ferruhcihan Feb 9, 2026
a98cf71
test: sealed secrets
ferruhcihan Feb 9, 2026
6b1c101
test: sealed secrets
ferruhcihan Feb 9, 2026
7495fd3
test: sealed secrets
ferruhcihan Feb 9, 2026
c746235
Merge branch 'main' into APL-1476
svcAPLBot Feb 11, 2026
26d3256
Merge branch 'main' into APL-1476
svcAPLBot Feb 11, 2026
da09da9
Merge branch 'main' into APL-1476
svcAPLBot Feb 11, 2026
3d0fc51
Merge branch 'main' into APL-1476
svcAPLBot Feb 11, 2026
3d6dfb9
Merge branch 'main' into APL-1476
svcAPLBot Feb 11, 2026
aa1fc88
Merge branch 'main' into APL-1476
svcAPLBot Feb 12, 2026
ca80de0
Merge branch 'main' into APL-1476
svcAPLBot Feb 12, 2026
af15dcc
Merge branch 'main' into APL-1476
svcAPLBot Feb 12, 2026
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
4 changes: 0 additions & 4 deletions chart/apl/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,9 @@ spec:
value: {{ .Values.operator.pollIntervalMs | default "30000" | quote }}
- name: RECONCILE_INTERVAL_MS
value: {{ .Values.operator.reconcileIntervalMs | default "300000" | quote }}
{{- if hasKey $kms "sops" }}
envFrom:
- secretRef:
name: apl-sops-secrets
- secretRef:
name: gitea-credentials
{{- end }}
volumeMounts:
- name: otomi-values
mountPath: /home/app/stack/env
Expand Down
35 changes: 0 additions & 35 deletions chart/apl/templates/sops-secrets.yaml

This file was deleted.

1 change: 1 addition & 0 deletions charts/ingress-nginx/templates/clusterrole.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ rules:
- namespaces
{{- end}}
verbs:
- get
- list
- watch
- apiGroups:
Expand Down
7 changes: 0 additions & 7 deletions helmfile.d/helmfile-03.databases.yaml.gotmpl
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,6 @@ bases:
{{- $k := $a.keycloak }}

releases:
- name: gitea-db-secret-artifacts
installed: true
namespace: gitea
labels:
pkg: gitea
app: core
<<: *raw
- name: gitea-otomi-db
installed: true
namespace: gitea
Expand Down
24 changes: 0 additions & 24 deletions helmfile.d/snippets/sops-env.gotmpl
Original file line number Diff line number Diff line change
@@ -1,24 +0,0 @@
{{- with . | get "azure" nil }}
AZURE_CLIENT_ID: {{ .clientId }}
AZURE_CLIENT_SECRET: {{ .clientSecret }}
{{- with . | get "tenantId" nil }}
AZURE_TENANT_ID: {{ . }}{{ end }}
{{- with . | get "environment" nil }}
AZURE_ENVIRONMENT: {{ . }}{{ end }}
{{- end }}
{{- with . | get "aws" nil }}
AWS_ACCESS_KEY_ID: {{ .accessKey }}
AWS_SECRET_ACCESS_KEY: {{ .secretKey }}
{{- with . | get "region" nil }}
AWS_REGION: {{ . }}{{ end }}
{{- end }}
{{- with . | get "age" nil }}
SOPS_AGE_KEY: {{ .privateKey }}
{{- end }}
{{- with . | get "google" nil }}
GCLOUD_SERVICE_KEY: '{{ .accountJson | replace "\n" "" }}'
{{- with . | get "project" nil }}
GOOGLE_PROJECT: {{ . }}{{ end }}
{{- with . | get "region" nil }}
GOOGLE_REGION: {{ . }}{{ end }}
{{- end }}
11 changes: 7 additions & 4 deletions src/cmd/bootstrap.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import {
processValues,
} from './bootstrap'

jest.mock('@linode/kubeseal-encrypt')

const { terminal } = stubs

jest.mock('src/common/envalid', () => ({
Expand All @@ -37,6 +39,7 @@ describe('Bootstrapping values', () => {
}),
}),
bootstrapSops: jest.fn(),
bootstrapSealedSecrets: jest.fn(),
copyBasicFiles: jest.fn(),
copyFile: jest.fn(),
createCustomCA: jest.fn(),
Expand All @@ -59,14 +62,14 @@ describe('Bootstrapping values', () => {
}
})
it('should call relevant sub routines', async () => {
deps.processValues.mockReturnValue(values)
deps.processValues.mockReturnValue({ originalInput: values, allSecrets: {} })
deps.hfValues.mockReturnValue(values)
await bootstrap(deps)
expect(deps.copyBasicFiles).toHaveBeenCalled()
expect(deps.bootstrapSops).toHaveBeenCalled()
expect(deps.bootstrapSealedSecrets).toHaveBeenCalled()
})
it('should copy only skeleton files to env dir if it is empty or nonexisting', async () => {
deps.processValues.mockReturnValue(undefined)
deps.processValues.mockReturnValue({ originalInput: undefined, allSecrets: {} })
await bootstrap(deps)
expect(deps.hfValues).toHaveBeenCalledTimes(0)
})
Expand Down Expand Up @@ -385,7 +388,7 @@ describe('Bootstrapping values', () => {
{ id: 'user2', initialPassword: 'generated-password' },
],
})
expect(res).toEqual({
expect(res.originalInput).toEqual({
cluster: { name: 'bla', provider: 'dida' },
users: [{ id: 'user1', initialPassword: 'existing-password' }, { id: 'user2' }],
})
Expand Down
20 changes: 8 additions & 12 deletions src/cmd/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { randomUUID } from 'crypto'
import { existsSync } from 'fs'
import { copyFile, cp, mkdir, readFile, writeFile } from 'fs/promises'
import { generate as generatePassword } from 'generate-password'
import { cloneDeep, get, isEmpty, merge, set } from 'lodash'
import { cloneDeep, get, merge, set } from 'lodash'
import { pki } from 'node-forge'
import path from 'path'
import { bootstrapGit } from 'src/common/bootstrap'
Expand All @@ -14,6 +14,7 @@ import { env, isCli } from 'src/common/envalid'
import { hfValues } from 'src/common/hf'
import { createK8sSecret, getDeploymentState, getK8sSecret, secretId } from 'src/common/k8s'
import { getKmsSettings } from 'src/common/repo'
import { bootstrapSealedSecrets } from 'src/common/sealed-secrets'
import { ensureTeamGitOpsDirectories, getFilename, gucci, isCore, loadYaml, rootDir } from 'src/common/utils'
import { generateSecrets, writeValues } from 'src/common/values'
import { BasicArguments, setParsedArgs } from 'src/common/yargs'
Expand Down Expand Up @@ -84,11 +85,6 @@ export const bootstrapSops = async (
await deps.writeFile(targetPath, output)
d.log(`Ready generating sops files. The configuration is written to: ${targetPath}`)

d.info('Copying sops related files')
// add sops related files
const file = '.gitattributes'
await deps.copyFile(`${rootDir}/.values/${file}`, `${envDir}/${file}`)

// prepare some credential files the first time and crypt some
if (!exists) {
if (isCli || env.OTOMI_DEV) {
Expand Down Expand Up @@ -291,7 +287,7 @@ export const processValues = async (
addInitialPasswords,
addPlatformAdmin,
},
): Promise<Record<string, any>> => {
): Promise<{ originalInput: Record<string, any>; allSecrets: Record<string, any> }> => {
const d = deps.terminal(`cmd:${cmdName}:processValues`)
const { VALUES_INPUT } = env
d.log(`Loading app values from ${VALUES_INPUT}`)
Expand Down Expand Up @@ -324,7 +320,7 @@ export const processValues = async (
// and do some context dependent post processing:
// to support potential failing chart install we store secrets on cluster
if (!(env.isDev && env.DISABLE_SYNC)) await deps.createK8sSecret(DEPLOYMENT_PASSWORDS_SECRET, 'otomi', allSecrets)
return originalInput
return { originalInput, allSecrets }
}

// create file structure based on file entry
Expand Down Expand Up @@ -419,6 +415,7 @@ export const bootstrap = async (
hfValues,
writeValues,
bootstrapSops,
bootstrapSealedSecrets,
migrate,
encrypt,
decrypt,
Expand All @@ -434,10 +431,10 @@ export const bootstrap = async (
}
await deps.copyBasicFiles()
await deps.migrate()
const originalValues = await deps.processValues()
const { originalInput, allSecrets } = await deps.processValues()
await deps.handleFileEntry()
await deps.bootstrapSops()
await ensureTeamGitOpsDirectories(ENV_DIR, originalValues)
await deps.bootstrapSealedSecrets(allSecrets, ENV_DIR, originalInput)
await ensureTeamGitOpsDirectories(ENV_DIR, originalInput)
d.log(`Done bootstrapping values`)
}

Expand All @@ -456,7 +453,6 @@ export const module = {
handler: async (argv: BasicArguments): Promise<void> => {
setParsedArgs(argv)
await prepareEnvironment({ skipAllPreChecks: true })
await decrypt()
await bootstrap()
await bootstrapGit()
},
Expand Down
15 changes: 11 additions & 4 deletions src/cmd/commit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const commitAndPush = async (values: Record<string, any>, branch: string, initia
const d = terminal(`cmd:${cmdName}:commitAndPush`)
d.info('Committing values')
const message = initialInstall ? 'otomi commit' : 'updated values [ci skip]'
const { password } = getRepo(values)
const { password } = await getRepo(values)
cd(env.ENV_DIR)
try {
try {
Expand All @@ -79,8 +79,9 @@ const commitAndPush = async (values: Record<string, any>, branch: string, initia
}
await $`git commit -m ${message} --no-verify`.quiet()
} catch (e) {
d.log('commitAndPush error ', e?.message?.replace(password, '****'))
return
const errorMsg = `commitAndPush error: ${e?.message?.replace(password, '****')}`
d.error(errorMsg)
throw new Error(errorMsg)
}
if (values._derived?.untrustedCA) process.env.GIT_SSL_NO_VERIFY = '1'
await retry(
Expand Down Expand Up @@ -133,10 +134,16 @@ export const commit = async (initialInstall: boolean, overrideArgs?: HelmArgumen
await validateValues(overrideArgs)
d.info('Preparing values')
const values = (await hfValues()) as Record<string, any>
const { branch, remote, username, email } = getRepo(values)
const { branch, remote, username, email } = await getRepo(values)
if (initialInstall) {
// we call this here again, as we might not have completed (happens upon first install):
await bootstrapGit(values)
// Always update the remote URL after bootstrap - the initial bootstrapGit() (called during
// the bootstrap phase before install) may have set the URL with unresolved placeholder
// passwords because K8s secrets didn't exist yet. Now that secrets are decrypted,
// we need to update the URL with the real credentials.
cd(env.ENV_DIR)
await $`git remote set-url origin ${remote}`.nothrow().quiet()
} else {
cd(env.ENV_DIR)
await setIdentity(username, email)
Expand Down
40 changes: 36 additions & 4 deletions src/cmd/install.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jest.mock('src/common/k8s', () => ({
applyServerSide: jest.fn(),
restartOtomiApiDeployment: jest.fn(),
waitForCRD: jest.fn(),
getK8sSecret: jest.fn().mockResolvedValue({ password: 'test', username: 'test' }),
k8s: {
app: jest.fn(),
},
Expand All @@ -34,9 +35,41 @@ jest.mock('src/common/hf', () => ({
HF_DEFAULT_SYNC_ARGS: ['apply', '--sync-args', '--include-needs'],
}))

jest.mock('zx', () => ({
$: jest.fn(),
cd: jest.fn(),
jest.mock('zx', () => {
const mockResult = { exitCode: 0, stdout: '', stderr: '' }
const createMockProcessPromise = () => {
const promise = Promise.resolve(mockResult)
const chainable: any = promise
chainable.nothrow = jest.fn().mockReturnValue(chainable)
chainable.quiet = jest.fn().mockReturnValue(chainable)
return chainable
}
return {
$: jest.fn().mockImplementation(() => createMockProcessPromise()),
cd: jest.fn(),
}
})

jest.mock('src/common/sealed-secrets', () => ({
applySealedSecretManifestsFromDir: jest.fn().mockResolvedValue(undefined),
restartSealedSecretsController: jest.fn().mockResolvedValue(undefined),
APP_SECRET_OVERRIDES: {
'apps.gitea': [
{
secretName: 'gitea-admin-secret',
namespace: 'gitea',
data: {
username: { valuePath: 'apps.gitea.adminUsername', default: 'otomi-admin' },
password: { valuePath: 'apps.gitea.adminPassword' },
},
},
{
secretName: 'gitea-db-secret',
namespace: 'gitea',
data: { username: { static: 'gitea' }, password: { valuePath: 'apps.gitea.postgresqlPassword' } },
},
],
},
}))

jest.mock('./commit', () => ({
Expand Down Expand Up @@ -106,7 +139,6 @@ describe('Install command', () => {
stderr: '',
})
mockDeps.deployEssential.mockResolvedValue(true)
mockDeps.$.mockResolvedValue(undefined)
})

describe('module configuration', () => {
Expand Down
Loading
Loading