Skip to content

Commit 063d65c

Browse files
committed
refactor(alloydb): use migration feature from conf
SUITEDEV-39317
1 parent 1dd7531 commit 063d65c

File tree

10 files changed

+98
-120
lines changed

10 files changed

+98
-120
lines changed

src/commands/configurations/create.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import inquirer from 'inquirer'
33
import autocomplete from 'inquirer-autocomplete-prompt'
44
import { saveConfiguration } from '../../lib/configurations'
55
import { ConfigurationCreateAnswers } from '../../lib/types'
6-
import { alloyDbInstancePrompt } from './prompts/alloydb-instance'
6+
import { googleAlloyDbInstancePrompt } from './prompts/google-alloydb-instance'
77
import { configurationNamePrompt } from './prompts/configuration-name'
88
import { confirmationPrompt } from './prompts/confirmation'
99
import { databaseTypePrompt } from './prompts/database-type'
@@ -21,7 +21,7 @@ export const createConfiguration = async () => {
2121
googleCloudProjectPrompt,
2222
databaseTypePrompt,
2323
googleCloudSqlInstancePrompt,
24-
alloyDbInstancePrompt,
24+
googleAlloyDbInstancePrompt,
2525
kubernetesContextPrompt,
2626
kubernetesNamespacePrompt,
2727
kubernetesServiceAccountPrompt,

src/commands/configurations/prompts/alloydb-instance.ts renamed to src/commands/configurations/prompts/google-alloydb-instance.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { pick } from 'lodash'
22
import {
3-
fetchAlloyDbInstances,
4-
AlloyDbInstance,
3+
fetchGoogleAlloyDbInstances,
4+
GoogleAlloyDbInstance,
55
} from '../../../lib/gcloud/alloydb-instances'
66
import { ConfigurationCreateAnswers } from '../../../lib/types'
77
import { searchByKey } from '../../../lib/util/search'
88
import { tryCatch } from '../../../lib/util/error'
99

10-
const formatInstance = (instance: AlloyDbInstance) => {
10+
const formatInstance = (instance: GoogleAlloyDbInstance) => {
1111
const { name, region, cluster } = instance
1212
return {
1313
name: `${name} (cluster: ${cluster}, region: ${region})`,
@@ -17,16 +17,16 @@ const formatInstance = (instance: AlloyDbInstance) => {
1717
}
1818

1919
const source = tryCatch((answers: ConfigurationCreateAnswers, input?: string) => {
20-
const instances = fetchAlloyDbInstances(answers.googleCloudProject)
20+
const instances = fetchGoogleAlloyDbInstances(answers.googleCloudProject)
2121
const filtered = searchByKey(instances, 'connectionName', input)
2222

2323
return filtered.map(formatInstance)
2424
})
2525

26-
export const alloyDbInstancePrompt = {
26+
export const googleAlloyDbInstancePrompt = {
2727
type: 'autocomplete',
2828
name: 'databaseInstance',
29-
message: 'Choose AlloyDB instance:',
29+
message: 'Choose Google AlloyDB instance:',
3030
source,
3131
when: (answers: ConfigurationCreateAnswers) => answers.databaseType === 'alloydb',
3232
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export const versionKey = 'version' as const
2+
export const configurationsKey = 'configurations' as const
3+
4+
// Must be semver ('conf' library requirement)
5+
export const currentVersion = '2.0.0'

src/lib/configurations/index.ts

Lines changed: 10 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -3,70 +3,22 @@ import { omit, kebabCase } from 'lodash'
33
import {
44
deletePod,
55
portForward,
6-
runCloudSqlProxyPod,
7-
runAlloyDbProxyPod,
6+
runProxyPod,
87
waitForPodReady,
98
} from '../kubectl/pods'
109
import { Configuration, ConfigurationCreateAnswers } from '../types'
1110
import { appendOrReplaceByKey, deleteByKey, findByKey } from '../util/array'
1211
import { randomString } from '../util/string'
13-
import { store, CURRENT_VERSION } from './store'
12+
import { store } from './store'
13+
import { configurationsKey } from './constants'
1414

15-
const storeKey = 'configurations' as const
1615
const searchKey = 'configurationName' as const
1716
const excludeProperties = ['googleCloudProject', 'confirmation'] as const
1817

1918
export const configurationPath = store.path
2019

21-
type LegacyConfiguration = Omit<Configuration, 'databaseType' | 'databaseInstance'> & {
22-
googleCloudSqlInstance: {
23-
connectionName: string
24-
port: number
25-
}
26-
}
27-
28-
const isLegacyConfiguration = (config: Configuration | LegacyConfiguration): config is LegacyConfiguration => {
29-
return 'googleCloudSqlInstance' in config && !('databaseType' in config)
30-
}
31-
32-
const migrateLegacyConfiguration = (legacy: LegacyConfiguration): Configuration => {
33-
return {
34-
configurationName: legacy.configurationName,
35-
databaseType: 'cloudsql',
36-
databaseInstance: {
37-
connectionName: legacy.googleCloudSqlInstance.connectionName,
38-
port: legacy.googleCloudSqlInstance.port,
39-
},
40-
kubernetesContext: legacy.kubernetesContext,
41-
kubernetesNamespace: legacy.kubernetesNamespace,
42-
kubernetesServiceAccount: legacy.kubernetesServiceAccount,
43-
localPort: legacy.localPort,
44-
}
45-
}
46-
47-
const migrateConfigurationsIfNeeded = (): void => {
48-
const currentVersion = store.get('version')
49-
const configurations = store.get(storeKey) as (Configuration | LegacyConfiguration)[]
50-
51-
// Check if migration is needed (no version or configurations in old format)
52-
const needsMigration = !currentVersion || configurations.some(isLegacyConfiguration)
53-
54-
if (needsMigration) {
55-
const migratedConfigurations = configurations.map((config) => {
56-
if (isLegacyConfiguration(config)) {
57-
return migrateLegacyConfiguration(config)
58-
}
59-
return config
60-
})
61-
62-
store.set(storeKey, migratedConfigurations)
63-
store.set('version', CURRENT_VERSION)
64-
}
65-
}
66-
6720
export const getConfigurations = (): Configuration[] => {
68-
migrateConfigurationsIfNeeded()
69-
return store.get(storeKey)
21+
return store.get(configurationsKey)
7022
}
7123

7224
export const getConfiguration = (name: string): Configuration | undefined => {
@@ -77,24 +29,20 @@ export const getConfiguration = (name: string): Configuration | undefined => {
7729
export const saveConfiguration = (answers: ConfigurationCreateAnswers): void => {
7830
const configuration = omit(answers, excludeProperties)
7931

80-
const configurations = store.get(storeKey)
32+
const configurations = store.get(configurationsKey)
8133
appendOrReplaceByKey(configurations, configuration, searchKey)
82-
store.set(storeKey, configurations)
83-
84-
if (!store.get('version')) {
85-
store.set('version', CURRENT_VERSION)
86-
}
34+
store.set(configurationsKey, configurations)
8735
}
8836

8937
export const deleteConfiguration = (configuratioName: string): void => {
90-
const configurations = store.get(storeKey)
38+
const configurations = store.get(configurationsKey)
9139
deleteByKey(configurations, searchKey, configuratioName)
92-
store.set(storeKey, configurations)
40+
store.set(configurationsKey, configurations)
9341
}
9442

9543
export const execConfiguration = (configuration: Configuration) => {
9644
const pod = {
97-
name: `${configuration.databaseType === 'alloydb' ? 'alloydb' : 'sql'}-proxy-${kebabCase(configuration.configurationName)}-${randomString()}`,
45+
name: `${configuration.databaseType}-proxy-${kebabCase(configuration.configurationName)}-${randomString()}`,
9846
context: configuration.kubernetesContext,
9947
namespace: configuration.kubernetesNamespace,
10048
serviceAccount: configuration.kubernetesServiceAccount,
@@ -108,12 +56,7 @@ export const execConfiguration = (configuration: Configuration) => {
10856
deletePod(pod)
10957
})
11058

111-
if (configuration.databaseType === 'alloydb') {
112-
runAlloyDbProxyPod(pod)
113-
}
114-
else {
115-
runCloudSqlProxyPod(pod)
116-
}
59+
runProxyPod(pod)
11760

11861
waitForPodReady(pod)
11962
portForward(pod)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import Conf from 'conf'
2+
import { Configuration } from '../../types'
3+
4+
interface V1Configuration {
5+
configurationName: string
6+
googleCloudSqlInstance: {
7+
connectionName: string
8+
port: number
9+
}
10+
kubernetesContext: string
11+
kubernetesNamespace: string
12+
kubernetesServiceAccount: string
13+
localPort: number
14+
}
15+
16+
export type V1Store = Conf<{
17+
configurations: V1Configuration[]
18+
}>
19+
20+
type V2Configuration = Configuration
21+
22+
const migrateConfigurationV1ToV2 = (v1: V1Configuration): V2Configuration => ({
23+
configurationName: v1.configurationName,
24+
databaseType: 'cloudsql',
25+
databaseInstance: {
26+
connectionName: v1.googleCloudSqlInstance.connectionName,
27+
port: v1.googleCloudSqlInstance.port,
28+
},
29+
kubernetesContext: v1.kubernetesContext,
30+
kubernetesNamespace: v1.kubernetesNamespace,
31+
kubernetesServiceAccount: v1.kubernetesServiceAccount,
32+
localPort: v1.localPort,
33+
})
34+
35+
export const migrateV1ToV2 = (store: V1Store): void => {
36+
const v1Configurations = store.get('configurations')
37+
const v2Configurations: V2Configuration[] = v1Configurations.map(migrateConfigurationV1ToV2)
38+
39+
// store.set('version', '2')
40+
store.set('configurations', v2Configurations)
41+
}

src/lib/configurations/store.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,24 @@
11
import Conf from 'conf'
22
import { Configuration } from '../types'
3-
4-
export const CURRENT_VERSION = 2
3+
import { migrateV1ToV2, V1Store } from './migrations/migrate-v1-v2'
4+
import { currentVersion } from './constants'
55

66
type Schema = {
7-
version?: number
7+
version: number
88
configurations: Configuration[]
99
}
1010

1111
export const store = new Conf<Schema>({
1212
configName: 'configurations',
1313
projectSuffix: '',
14+
projectVersion: currentVersion,
15+
migrations: {
16+
'2.0.0': store => migrateV1ToV2(store as unknown as V1Store),
17+
},
1418
schema: {
1519
version: {
16-
type: 'number',
20+
type: 'string',
21+
default: currentVersion,
1722
},
1823
configurations: {
1924
type: 'array',
@@ -22,7 +27,7 @@ export const store = new Conf<Schema>({
2227
type: 'object',
2328
properties: {
2429
configurationName: { type: 'string' },
25-
databaseType: { type: 'string' },
30+
databaseType: { type: 'string', enum: ['cloudsql', 'alloydb'] },
2631
databaseInstance: {
2732
type: 'object',
2833
properties: {

src/lib/gcloud/alloydb-instances.ts

Lines changed: 10 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,43 @@
11
import memoize from 'memoizee'
22
import { execCommandMultiline } from '../util/exec'
3-
import { parseJson } from '../util/parsers'
43

5-
export type AlloyDbInstance = {
4+
export type GoogleAlloyDbInstance = {
65
name: string
76
region: string
87
cluster: string
98
connectionName: string
109
port: number
1110
}
1211

13-
type AlloyDbInstanceData = {
14-
name: string
15-
databaseVersion?: string
16-
}
17-
18-
const parseInstance = (instanceData: AlloyDbInstanceData): AlloyDbInstance => {
19-
// AlloyDB instance name format: projects/{project}/locations/{region}/clusters/{cluster}/instances/{instance}
20-
const nameParts = instanceData.name.split('/')
12+
const parseInstance = (connectionName: string): GoogleAlloyDbInstance => {
13+
// projects/{project}/locations/{region}/clusters/{cluster}/instances/{instance}
14+
const nameParts = connectionName.split('/')
2115
const region = nameParts[3]
2216
const cluster = nameParts[5]
2317
const instance = nameParts[7]
2418

25-
// Connection name format: Full resource path (same as name)
26-
const connectionName = instanceData.name
27-
const port = 5432
28-
2919
return {
3020
name: instance,
3121
region,
3222
cluster,
3323
connectionName,
34-
port,
24+
port: 5432,
3525
}
3626
}
3727

38-
export const fetchAlloyDbInstances = memoize(
39-
(project: string): AlloyDbInstance[] => {
28+
export const fetchGoogleAlloyDbInstances = memoize(
29+
(project: string): GoogleAlloyDbInstance[] => {
4030
try {
41-
const output = execCommandMultiline(`
31+
const instances = execCommandMultiline(`
4232
gcloud alloydb instances list \
4333
--project=${project} \
44-
--format=json \
34+
--format='csv(name)' \
4535
--quiet
4636
`)
4737

48-
if (output.length === 0 || output[0].trim() === '') {
49-
return []
50-
}
51-
52-
const instances = parseJson(output.join('\n'))
53-
54-
if (!Array.isArray(instances)) {
55-
return []
56-
}
57-
58-
return instances.map(parseInstance)
38+
return instances.slice(1).map(parseInstance)
5939
}
6040
catch {
61-
// If AlloyDB API is not enabled or there are no instances, return empty array
6241
return []
6342
}
6443
},

src/lib/kubectl/pods.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ type ProxyPod = {
1010
instance: string
1111
localPort: number
1212
remotePort: number
13-
databaseType?: DatabaseType
13+
databaseType: DatabaseType
1414
}
1515

1616
export const runCloudSqlProxyPod = (pod: ProxyPod): string => {
@@ -41,6 +41,15 @@ export const runAlloyDbProxyPod = (pod: ProxyPod): string => {
4141
`)
4242
}
4343

44+
export const runProxyPod = (pod: ProxyPod) => {
45+
if (pod.databaseType === 'alloydb') {
46+
runAlloyDbProxyPod(pod)
47+
}
48+
else {
49+
runCloudSqlProxyPod(pod)
50+
}
51+
}
52+
4453
export const deletePod = (pod: ProxyPod) => {
4554
console.log(`Deleting pod '${bold(cyan(pod.name))}'.`)
4655
execCommand(`

src/lib/types.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
import { GoogleCloudSqlInstance } from './gcloud/sql-instances'
2-
import { AlloyDbInstance } from './gcloud/alloydb-instances'
3-
41
export type DatabaseType = 'cloudsql' | 'alloydb'
52

6-
export type DatabaseInstance
7-
= | Pick<GoogleCloudSqlInstance, 'connectionName' | 'port'>
8-
| Pick<AlloyDbInstance, 'connectionName' | 'port'>
3+
export type DatabaseInstance = {
4+
connectionName: string
5+
port: number
6+
}
97

108
export type Configuration = {
119
configurationName: string

src/lib/util/parsers.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,3 @@ export const toInt = (value: string): number => parseInt(value, 10)
44

55
const trueValues = new Set(['true', 'yes', 'on', '1'])
66
export const toBoolean = (value: string): boolean => trueValues.has(value)
7-
8-
export const parseJson = <T = unknown>(value: string): T => JSON.parse(value)

0 commit comments

Comments
 (0)