Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: allow using an existing cluster and do not require kind #643

Merged
merged 12 commits into from
Jan 10, 2024
Prev Previous commit
Next Next commit
fix: set current context based on selected cluster and namespace
Signed-off-by: Lenin Mehedy <lenin.mehedy@swirldslabs.com>
  • Loading branch information
leninmehedy committed Jan 10, 2024
commit 7eab0f58523229c50fa4c7ea5badf3c0d0c16e74
2 changes: 1 addition & 1 deletion fullstack-network-manager/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions fullstack-network-manager/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"listr2": "^7.0.2",
"uuid": "^9.0.1",
"winston": "^3.11.0",
"yaml": "^2.3.4",
"yargs": "^17.7.2"
},
"devDependencies": {
Expand Down
10 changes: 5 additions & 5 deletions fullstack-network-manager/src/commands/cluster.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -212,10 +212,9 @@ export class ClusterCommand extends BaseCommand {
{
title: 'Initialize',
task: async (ctx, task) => {
try {
await self.kubectl.config('current-context')
} catch (e) {
throw new FullstackTestingError('kubectl context is not set, set context by running: kubectl use-context <context-name>', e)
const kubeConfig = await self.clusterManager.getKubeConfig()
if (!kubeConfig['current-context']) {
throw new FullstackTestingError('kubectl context is not set, set context by running: kubectl config use-context <context-name>')
}

const cachedConfig = await self.configManager.setupConfig(argv)
Expand All @@ -233,7 +232,6 @@ export class ClusterCommand extends BaseCommand {

// get existing choices
const clusters = await self.clusterManager.getClusters()

const namespaces = await self.kubectl.getNamespace('--no-headers', '-o name')

// prompt if inputs are empty and set it in the context
Expand All @@ -250,6 +248,8 @@ export class ClusterCommand extends BaseCommand {

self.logger.debug('Prepare ctx.config', { config: ctx.config, argv })

// set current context based on cluster and namespace
await self.clusterManager.setContext(ctx.config.clusterName, ctx.config.namespace)
ctx.isChartInstalled = await this.chartManager.isChartInstalled(ctx.config.namespace, constants.CHART_FST_SETUP_NAME)
}
},
Expand Down
11 changes: 6 additions & 5 deletions fullstack-network-manager/src/commands/prompts.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import fs from 'fs'
import { FullstackTestingError, IllegalArgumentError } from '../core/errors.mjs'
import { constants } from '../core/index.mjs'
import * as flags from './flags.mjs'
import * as helpers from '../core/helpers.mjs'

export async function promptNamespaceArg (task, input) {
try {
Expand All @@ -27,11 +28,11 @@ export async function promptSelectNamespaceArg (task, input, choices = []) {
const input = await task.prompt(ListrEnquirerPromptAdapter).run({
type: 'select',
initial,
message: 'Which namespace do you wish to use?',
choices
message: 'Select namespace',
choices: helpers.cloneArray(choices)
})

return input.replace('namespace/', '')
return input.replaceAll('namespace/', '')
}

return input
Expand Down Expand Up @@ -195,9 +196,9 @@ export async function promptSelectClusterNameArg (task, input, choices = []) {
type: 'select',
initial,
message: 'Select cluster',
choices
choices: helpers.cloneArray(choices)
})
return input.replace('namespace/', '')
return input
}

return input
Expand Down
45 changes: 40 additions & 5 deletions fullstack-network-manager/src/core/cluster_manager.mjs
Original file line number Diff line number Diff line change
@@ -1,25 +1,60 @@
import { MissingArgumentError } from './errors.mjs'
import { FullstackTestingError, MissingArgumentError } from './errors.mjs'
import * as core from './index.mjs'
import YAML from 'yaml'

export class ClusterManager {
constructor (kind) {
constructor (kind, kubectl) {
if (!kind) throw new MissingArgumentError('An instance of core/Kind is required')
if (!kubectl) throw new MissingArgumentError('An instance of core/Kubectl is required')

this.kind = kind
this.kubectl = kubectl
}

parseKindClusterName (clusterName) {
return clusterName.replaceAll('kind-', '')
}

async getKubeConfig () {
const configYaml = await this.kubectl.config('view')
return YAML.parse(configYaml.join('\n'))
}

async getClusters () {
return this.kind.getClusters('-q')
const kubeConfig = await this.getKubeConfig()
const clusterNames = []
if (kubeConfig.clusters) {
kubeConfig.clusters.forEach(item => {
clusterNames.push(item.name)
})
}

return clusterNames
}

async createCluster (clusterName) {
return this.kind.createCluster(clusterName, `--config ${core.constants.RESOURCES_DIR}/dev-cluster.yaml`)
}

async deleteCluster (clusterName) {
return this.kind.deleteCluster(clusterName)
return this.kind.deleteCluster(this.parseKindClusterName(clusterName))
}

async getClusterInfo (clusterName) {
return this.kind.get(`kubeconfig -n ${clusterName}`)
return this.kind.get(`kubeconfig -n ${this.parseKindClusterName(clusterName)}`)
}

async setContext (clusterName, namespace) {
const kubeconfig = await this.getKubeConfig()
for (const item of kubeconfig.contexts) {
const context = item.context
if (context.cluster === clusterName) {
await this.kubectl.config(`use-context ${item.name}`)
await this.kubectl.config(`set-context --current --namespace ${namespace}`)
return true
}
}

throw new FullstackTestingError(`not context found for cluster: ${clusterName}`)
}
}
2 changes: 1 addition & 1 deletion fullstack-network-manager/src/core/constants.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const USER_SANITIZED = USER.replace(/[\W_]+/g, '-')
export const FST_HOME_DIR = `${process.env.HOME}/.fsnetman`
export const FST_LOGS_DIR = `${FST_HOME_DIR}/logs`
export const FST_CACHE_DIR = `${FST_HOME_DIR}/cache`
export const CLUSTER_NAME = 'fst'
export const CLUSTER_NAME = 'kind-fst'
export const RELEASE_NAME = 'fst'
export const NAMESPACE_NAME = `fst-${USER_SANITIZED}`
export const HELM = 'helm'
Expand Down
4 changes: 4 additions & 0 deletions fullstack-network-manager/src/core/helpers.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@ export function sleep (ms) {
setTimeout(resolve, ms)
})
}

export function cloneArray (arr) {
return JSON.parse(JSON.stringify(arr))
}
2 changes: 1 addition & 1 deletion fullstack-network-manager/src/core/shell_runner.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export class ShellRunner {
const items = d.toString().split(/\r?\n/)
items.forEach(item => {
if (item) {
output.push(item.trim())
output.push(item)
}
})
})
Expand Down
2 changes: 1 addition & 1 deletion fullstack-network-manager/src/index.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function main (argv) {
const chartManager = new ChartManager(helm, logger)
const configManager = new ConfigManager(logger)
const depManager = new DependencyManager(logger)
const clusterManager = new ClusterManager(kind)
const clusterManager = new ClusterManager(kind, kubectl)

const opts = {
logger,
Expand Down
38 changes: 28 additions & 10 deletions fullstack-network-manager/test/unit/core/cluster_manager.test.mjs
Original file line number Diff line number Diff line change
@@ -1,28 +1,38 @@
import { describe, expect, it, jest } from '@jest/globals'
import { FullstackTestingError } from '../../../src/core/errors.mjs'
import { ClusterManager, Kind, logging, constants } from '../../../src/core/index.mjs'
import { ClusterManager, Kind, logging, constants, Kubectl } from '../../../src/core/index.mjs'

describe('ClusterManager', () => {
const testLogger = logging.NewLogger('debug')
const kind = new Kind(testLogger)
const clusterCmd = new ClusterManager(kind)
const kubectl = new Kubectl(testLogger)
const clusterCmd = new ClusterManager(kind, kubectl)

it('should return a list of clusters', async () => {
const getClusterMock = jest
.spyOn(Kind.prototype, 'getClusters')
.mockImplementation(() => Promise.resolve(['test']))
.spyOn(ClusterManager.prototype, 'getKubeConfig')
.mockImplementation(() => Promise.resolve(
{
clusters: [
{ name: 'test' }
]
}
))

const clusters = await clusterCmd.getClusters()
expect(getClusterMock).toHaveBeenCalledWith('-q')
expect(getClusterMock).toHaveBeenCalled()
expect(clusters).toContain('test')
getClusterMock.mockReset()
})

it('should fail to list clusters on error', async () => {
const getClusterMock = jest
.spyOn(Kind.prototype, 'getClusters')
.mockImplementation(() => { throw new FullstackTestingError('mock error') })
.spyOn(ClusterManager.prototype, 'getKubeConfig')
.mockImplementation(() => {
throw new FullstackTestingError('mock error')
})
await expect(clusterCmd.getClusters()).rejects.toThrowError(FullstackTestingError)
expect(getClusterMock).toHaveBeenCalledWith('-q')
expect(getClusterMock).toHaveBeenCalled()
})

it('should be able to create a clusters', async () => {
Expand All @@ -36,7 +46,9 @@ describe('ClusterManager', () => {
it('should fail to create cluster on error', async () => {
const getClusterMock = jest
.spyOn(Kind.prototype, 'createCluster')
.mockImplementation(() => { throw new FullstackTestingError('mock error') })
.mockImplementation(() => {
throw new FullstackTestingError('mock error')
})
await expect(clusterCmd.createCluster('test')).rejects.toThrowError(FullstackTestingError)
expect(getClusterMock).toHaveBeenCalledWith('test', `--config ${constants.RESOURCES_DIR}/dev-cluster.yaml`)
})
Expand All @@ -52,8 +64,14 @@ describe('ClusterManager', () => {
it('should fail to delete cluster on error', async () => {
const getClusterMock = jest
.spyOn(Kind.prototype, 'deleteCluster')
.mockImplementation(() => { throw new FullstackTestingError('mock error') })
.mockImplementation(() => {
throw new FullstackTestingError('mock error')
})
await expect(clusterCmd.deleteCluster('test')).rejects.toThrowError(FullstackTestingError)
expect(getClusterMock).toHaveBeenCalledWith('test')
})
it('should strip kind name', async () => {
expect(clusterCmd.parseKindClusterName('kind-kind-test')).toBe('test')
expect(clusterCmd.parseKindClusterName('test')).toBe('test')
})
})
Loading