Skip to content

Commit

Permalink
feat: create Kubernetes secret with certificate
Browse files Browse the repository at this point in the history
  • Loading branch information
edosrecki committed Jul 23, 2023
1 parent d044a27 commit 58a56d8
Show file tree
Hide file tree
Showing 32 changed files with 314 additions and 158 deletions.
11 changes: 8 additions & 3 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ Connection is established by running an https://www.stunnel.org/[stunnel] pod in

The app relies on local `gcloud` and `kubectl` commands which have to be configured and authenticated with the proper Google Cloud user and GKE Kubernetes cluster.

The app also needs an existing Kubernetes Secret containing the Google Cloud Redis certificate to exist in the Kubernetes
cluster. You can download a Redis certificate file and create a Kubernetes secret manually, or you can use
`google-cloud-redis secrets create` command to do so.

image::screenshot.png[]

---
Expand Down Expand Up @@ -43,23 +47,24 @@ _Package_ sections.
* Install https://kubernetes.io/docs/tasks/tools/#kubectl[`kubectl`] tool
* Authenticate to Google Cloud: `gcloud auth login`
* Get GKE cluster credentials: `gcloud container clusters get-credentials`
* Download Google Cloud Redis certificate and store it in a Kubernetes secret: `kubectl create secret generic $SECRET --namespace $NAMESPACE --from-file=./server-ca.pem`
* Download Google Cloud Redis certificate and store it in a Kubernetes secret (see `google-cloud-redis secrets create`).

=== Run
[source,bash]
----
# Help
google-cloud-redis help
# Create Kubernetes secret containing Gogle Cloud Redis certificate
google-cloud-redis secrets create
# Create (or override existing) configuration
google-cloud-redis configurations create
# Run configuration (interactive mode)
google-cloud-redis configurations run
# Run configuration (non-interactive mode)
google-cloud-redis configurations run $NAME
# HINT: Add alias
alias myDbProd="google-cloud-redis configurations run $NAME"
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"fuse.js": "6.6.2",
"inquirer": "8.2.5",
"inquirer-autocomplete-prompt": "1.4.0",
"js-yaml": "4.1.0",
"kubernetes-models": "4.3.1",
"lodash": "4.17.21",
"memoizee": "0.4.15",
Expand All @@ -52,6 +53,7 @@
"@tsconfig/node14": "1.0.3",
"@types/inquirer": "8.2.4",
"@types/inquirer-autocomplete-prompt": "1.3.5",
"@types/js-yaml": "4.0.5",
"@types/lodash": "4.14.187",
"@types/memoizee": "0.4.8",
"@types/node": "18.11.9",
Expand Down
18 changes: 9 additions & 9 deletions src/commands/configurations/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import inquirer from 'inquirer'
import autocomplete from 'inquirer-autocomplete-prompt'
import { saveConfiguration } from '../../lib/configurations'
import { ConfigurationCreateAnswers } from '../../lib/types'
import { configurationNamePrompt } from './prompts/configuration-name'
import { confirmationPrompt } from './prompts/confirmation'
import { googleCloudProjectPrompt } from './prompts/google-cloud-project'
import { googleCloudRedisInstancePrompt } from './prompts/google-cloud-redis-instance'
import { kubernetesContextPrompt } from './prompts/kubernetes-context'
import { kubernetesNamespacePrompt } from './prompts/kubernetes-namespace'
import { kubernetesSecretPrompt } from './prompts/kubernetes-secret'
import { kubernetesSecretKeyPrompt } from './prompts/kubernetes-secret-key'
import { localPortPrompt } from './prompts/local-port'
import { configurationNamePrompt } from '../../lib/prompts/configuration-name'
import { confirmationPrompt } from '../../lib/prompts/confirmation'
import { googleCloudProjectPrompt } from '../../lib/prompts/google-cloud-project'
import { googleCloudRedisInstancePrompt } from '../../lib/prompts/google-cloud-redis-instance'
import { kubernetesContextPrompt } from '../../lib/prompts/kubernetes-context'
import { kubernetesNamespacePrompt } from '../../lib/prompts/kubernetes-namespace'
import { kubernetesSecretPrompt } from '../../lib/prompts/kubernetes-secret'
import { kubernetesSecretKeyPrompt } from '../../lib/prompts/kubernetes-secret-key'
import { localPortPrompt } from '../../lib/prompts/local-port'

export const createConfiguration = async () => {
inquirer.registerPrompt('autocomplete', autocomplete)
Expand Down
16 changes: 0 additions & 16 deletions src/commands/configurations/prompts/google-cloud-project.ts

This file was deleted.

34 changes: 0 additions & 34 deletions src/commands/configurations/prompts/google-cloud-redis-instance.ts

This file was deleted.

16 changes: 0 additions & 16 deletions src/commands/configurations/prompts/kubernetes-context.ts

This file was deleted.

16 changes: 0 additions & 16 deletions src/commands/configurations/prompts/kubernetes-namespace.ts

This file was deleted.

20 changes: 0 additions & 20 deletions src/commands/configurations/prompts/kubernetes-secret-key.ts

This file was deleted.

19 changes: 0 additions & 19 deletions src/commands/configurations/prompts/kubernetes-secret.ts

This file was deleted.

4 changes: 2 additions & 2 deletions src/commands/configurations/remove.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import inquirer from 'inquirer'
import autocomplete from 'inquirer-autocomplete-prompt'
import { deleteConfiguration } from '../../lib/configurations'
import { ConfigurationChooseAnswers } from '../../lib/types'
import { configurationPrompt } from './prompts/configuration'
import { confirmationPrompt } from './prompts/confirmation'
import { configurationPrompt } from '../../lib/prompts/configuration'
import { confirmationPrompt } from '../../lib/prompts/confirmation'

export const removeConfiguration = async () => {
inquirer.registerPrompt('autocomplete', autocomplete)
Expand Down
4 changes: 2 additions & 2 deletions src/commands/configurations/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import inquirer from 'inquirer'
import autocomplete from 'inquirer-autocomplete-prompt'
import { execConfiguration, getConfiguration } from '../../lib/configurations'
import { ConfigurationChooseAnswers } from '../../lib/types'
import { configurationPrompt } from './prompts/configuration'
import { confirmationPrompt } from './prompts/confirmation'
import { configurationPrompt } from '../../lib/prompts/configuration'
import { confirmationPrompt } from '../../lib/prompts/confirmation'

export const runConfiguration = async () => {
inquirer.registerPrompt('autocomplete', autocomplete)
Expand Down
2 changes: 1 addition & 1 deletion src/commands/configurations/show.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import inquirer from 'inquirer'
import autocomplete from 'inquirer-autocomplete-prompt'
import { ConfigurationChooseAnswers } from '../../lib/types'
import { configurationPrompt } from './prompts/configuration'
import { configurationPrompt } from '../../lib/prompts/configuration'

export const showConfiguration = async () => {
inquirer.registerPrompt('autocomplete', autocomplete)
Expand Down
32 changes: 32 additions & 0 deletions src/commands/secrets/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { bold, green, red } from 'chalk'
import inquirer from 'inquirer'
import autocomplete from 'inquirer-autocomplete-prompt'
import { confirmationPrompt } from '../../lib/prompts/confirmation'
import { googleCloudProjectPrompt } from '../../lib/prompts/google-cloud-project'
import { googleCloudRedisInstancePrompt } from '../../lib/prompts/google-cloud-redis-instance'
import { kubernetesContextPrompt } from '../../lib/prompts/kubernetes-context'
import { kubernetesNamespacePrompt } from '../../lib/prompts/kubernetes-namespace'
import { kubernetesSecretNamePrompt } from '../../lib/prompts/kubernetes-secret-name'
import { saveSecret } from '../../lib/secrets'
import { SecretCreateAnswers } from '../../lib/types'

export const createSecret = async () => {
inquirer.registerPrompt('autocomplete', autocomplete)

const answers = await inquirer.prompt<SecretCreateAnswers>([
googleCloudProjectPrompt,
googleCloudRedisInstancePrompt,
kubernetesContextPrompt,
kubernetesNamespacePrompt,
kubernetesSecretNamePrompt,
confirmationPrompt,
])

if (answers.confirmation) {
saveSecret(answers)

console.log(green(`Created secret '${bold(answers.kubernetesSecretName)}'.`))
} else {
console.log(red('You are excused.'))
}
}
21 changes: 21 additions & 0 deletions src/commands/secrets/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Command } from 'commander'
import { logError } from '../../lib/util/error'
import { createSecret } from './create'

export async function addSecretsCommands(program: Command) {
const secrets = new Command('secrets')
secrets.description('manage Kubernetes secrets for Redis certificates')

secrets
.command('create')
.description('create Kubernetes secret containing Google Cloud Redis certificate')
.action(async () => {
try {
await createSecret()
} catch (error) {
logError(error)
}
})

program.addCommand(secrets)
}
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Command } from 'commander'
import { addConfigurationsCommands } from './commands/configurations'
import { notifyForUpdates } from './lib/updates'
import { version } from './lib/version'
import { addSecretsCommands } from './commands/secrets'

async function main() {
notifyForUpdates()
Expand All @@ -12,6 +13,7 @@ async function main() {
program.name('google-cloud-redis').version(version)

addConfigurationsCommands(program)
addSecretsCommands(program)

program.parse(process.argv)
}
Expand Down
10 changes: 0 additions & 10 deletions src/lib/gcloud/redis-regions.ts

This file was deleted.

28 changes: 27 additions & 1 deletion src/lib/gcloud/redis-instances.ts → src/lib/gcloud/redis.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import memoize from 'memoizee'
import { execCommandMultilineWithHeaderAsync } from '../util/exec'
import {
execCommand,
execCommandMultilineWithHeader,
execCommandMultilineWithHeaderAsync,
} from '../util/exec'

export type GoogleCloudRedisInstance = {
name: string
Expand All @@ -15,6 +19,14 @@ const parseInstance = (instance: string): GoogleCloudRedisInstance => {
return { name, region, host, port }
}

export const fetchGoogleCloudRedisRegions = memoize((project: string): string[] => {
return execCommandMultilineWithHeader(`
gcloud redis regions list \
--project=${project} \
--quiet
`)
})

const fetchGoogleCloudRedisInstancesForRegion = memoize(
async (project: string, region: string): Promise<GoogleCloudRedisInstance[]> => {
const instances = await execCommandMultilineWithHeaderAsync(`
Expand All @@ -38,3 +50,17 @@ export const fetchGoogleCloudRedisInstances = memoize(
return instances.flat()
}
)

export const fetchGoogleCloudRedisCertificate = (
project: string,
region: string,
instanceName: string
): string => {
return execCommand(`
gcloud redis instances describe ${instanceName} \
--project=${project} \
--region=${region} \
--format='value(serverCaCerts[0].cert)' \
--quiet
`)
}
5 changes: 3 additions & 2 deletions src/lib/kubectl/pods.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import yaml from 'js-yaml'
import { bold, cyan } from 'chalk'
import { Pod } from 'kubernetes-models/v1'
import { execCommand, execCommandAttached } from '../util/exec'
Expand Down Expand Up @@ -59,10 +60,10 @@ export const runCloudRedisProxyPod = (pod: CloudRedisProxyPod): string => {
],
},
})
const podJson = JSON.stringify(podModel.toJSON())
const podYaml = yaml.dump(podModel.toJSON())

return execCommand(`
echo '${podJson}' | kubectl create --context=${pod.context} -f -
echo "${podYaml}" | kubectl create --context=${pod.context} -f -
`)
}

Expand Down
Loading

0 comments on commit 58a56d8

Please sign in to comment.