Skip to content

Commit

Permalink
Update "Add a cluster(s)" section in README
Browse files Browse the repository at this point in the history
Update UI of ClusterForm
  • Loading branch information
proAlexandr committed Jul 30, 2019
1 parent c14b615 commit 126b5b0
Show file tree
Hide file tree
Showing 10 changed files with 150 additions and 56 deletions.
40 changes: 33 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,39 @@ Use port-forwarding without installing kubectl and avoid explanations to develop

### Add a cluster(s)

Before you start forwarding interal resources to your local machine, you have to add cluster configuration. To do this we have 3 different options in the app:

- Auto-detection of ~/.kube/config file and parsing settings from it
- Manual adding of Kubernetes config
- Import of the JSON file that could be generated via Kube Forwarder export functionality

When you add a new cluster via auto-detection or manually, we could parse config and if there are multiple contexts inside we will suggest you to add multiple clusters to the app. Few examples of yaml files we expect to have you could find [there](https://github.com/pixel-point/kube-forwarder/issues/7)
Before you start forwarding internal resources to your local machine, you have to add cluster configuration.
To do this we have 3 different options in the app:

1) Auto-detection of ~/.kube/config file and parsing settings from it
1) Manual adding of Kubernetes config by selecting a file(s)
1) Manual adding of Kubernetes config by pasting a text
1) Import of the JSON file that could be generated via Kube Forwarder export functionality

When you add a new cluster via auto-detection (option 1) or manually using a file(a) selection (option 2), we could parse
configs and if there are multiple contexts inside we will suggest you to add multiple clusters to the app.
Few examples of yaml files we expect to have you could find [there](https://github.com/pixel-point/kube-forwarder/issues/7)

Also, you could add a cluster by filling a form manually (option 3). The form has the following fields:
* Name - the name of a cluster withing Kube Forwarder app.
* Storing method (Set destination to your kube config or paste it as a text) - the method of storing a config It has two options:
* `Set a path` - storing a path to the config file. It will be read every time when you forwarding a port. It allows
a user to don't do any changes in Kube Forwarder's settings when a third-party app updates the config file.
For example, when `azure-cli` updates an access token (#13).
* `Paste as a text` - storing a config just as a yml text.
* Path (if storing method is `Set a path`) - the path to a config file.
* Content (if storing method is `Paste as a text`) - Yml config as a text.
* Current Context (if storing method is `Set a path`) - When you use `Set a path`, you must select a context from a file
which will be used to connect to a resource. Let's see an example of a problem that the field solves.
1) Let's say we don't have `Current context` field.
1) A user has a config file with two contexts: `local-cluster` and `remote-cluster`.
`current-context` in the yml file is `local-cluster`.
1) The user configured a cluster in Kube Forwarder with `Set a path` option.
1) The user created a resource `postgres` and successfully forwarded ports for some time.
1) Then the user executed `kubectl config use-context remote-cluster`
1) If the user tries to forward the resource in Kube Forwarder again, most likely there will be an error
since a connection will be established with `remote-cluster`, not `local-cluster` as the user expected,
and `remote-cluster` couldn't have `postgres` resource.
So, to avoid the error we should store the current context in a separate field.

<a target="_blank" href="https://user-images.githubusercontent.com/2697570/60754775-58a4ca80-9fe6-11e9-8d67-d15a1423b506.png"><img width="320" alt="Screenshot 2019-07-06 at 12 04 45" src="https://user-images.githubusercontent.com/2697570/60754775-58a4ca80-9fe6-11e9-8d67-d15a1423b506.png"></a>

Expand Down
7 changes: 6 additions & 1 deletion src/renderer/assets/styles/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,14 @@ $font-size-base: 14px;
$font-size-big: 15px;
$font-size-large: 17px;

$border-width: 1px;
$border-radius: 2px;
$border-color: rgba($color-text, 0.1);
$border: 1px solid $border-color;
$border: $border-width solid $border-color;

$spacer: 20px;
$spacer-sm: $spacer / 2;
$spacer-xs: $spacer / 2;

$button-outline-hover-bg-opacity: 0.1;

Expand Down
23 changes: 19 additions & 4 deletions src/renderer/components/ClusterAdd.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,23 @@
<div class="page__block clusters-add__configs-block">
<template v-if="configs.length">
<span v-if="filesOpened">
We have detected the following clusters in the submitted config.
We have detected the following clusters in the
<span v-if="configs.length > 1">submitted configs</span>
<b v-else>{{ configs[0].filePath }}</b>.
Please choose clusters you want to add:
</span>
<span v-else>We have detected existing <b>~/.kube/config</b> file with the following clusters:</span>

<div class="clusters-add__configs">
<div v-for="config in configs" :key="config.filePath">
<div v-for="config in configs" :key="config.filePath" class="clusters-add__config">
<div v-if="configs.length > 1" class="clusters-add__file-path">{{ config.filePath }}</div>

<b v-if="config.error">
Sorry, the file does not contain a valid config.
<br />
Error: {{ config.error }}
</b>

<div v-for="context in config.contexts" :key="context.name" class="clusters-add__context">
<BaseCheckbox v-model="config.checkedContextsIndex[context.name]">
<b>{{ context.cluster }}</b>
Expand All @@ -29,7 +38,8 @@
</template>

<template v-else>
We haven't found a valid <b>~/.kube/config</b> file. Please choose one of options below.
<div>Please, open a file first.</div>
<Button layout="outline" theme="primary" @click="handleOpenFile">OPEN A FILE(S)</Button>
</template>
</div>

Expand Down Expand Up @@ -106,7 +116,12 @@ export default {
...mapActions('Clusters', ['createCluster']),
addConfig(filePath) {
const kubeConfig = new KubeConfig()
kubeConfig.loadFromFile(filePath)
try {
kubeConfig.loadFromFile(filePath)
} catch (error) {
this.configs.push({ filePath, error, contexts: [], checkedContextsIndex: {} })
return
}
const contexts = kubeConfig.contexts
const checkedContextsIndex = {}
Expand Down
1 change: 0 additions & 1 deletion src/renderer/components/shared/Button.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ export default {
<style lang="scss">
@import "../../assets/styles/mixins";
$border-width: 1px;
$sizes: s m l;
$heights: (
"s": 30px,
Expand Down
109 changes: 74 additions & 35 deletions src/renderer/components/shared/cluster/ClusterForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,33 @@

<ControlGroup
v-if="attributes.config.storingMethod === configStoringMethods.CONTENT"
class="cluster-form__control-group-content"
label="Config file"
hint="Get this from ~/.kube/config or your cloud provider"
:attribute="$v.attributes.config.content"
>
<Button theme="primary" size="s" @click="handleOpenFile(configStoringMethods.CONTENT)">Copy from a file</Button>
<BaseTextArea v-model.trim="$v.attributes.config.content.$model" />
<Button theme="primary" size="s" layout="outline" @click="handleOpenFile(configStoringMethods.CONTENT)">
Copy from a file
</Button>
</ControlGroup>

<ControlGroup
v-if="attributes.config.storingMethod === configStoringMethods.PATH"
label="Path"
:attribute="$v.attributes.config.path"
>
<Button theme="primary" size="s" @click="handleOpenFile(configStoringMethods.PATH)">Select a file</Button>
<BaseInput v-model.trim="$v.attributes.config.path.$model" />
</ControlGroup>
<template v-if="attributes.config.storingMethod === configStoringMethods.PATH">
<ControlGroup
class="cluster-form__control-group-path"
label="Path"
:attribute="$v.attributes.config.path"
>
<Button theme="primary" size="m" layout="outline" @click="handleOpenFile(configStoringMethods.PATH)">
Select a file
</Button>
<BaseInput v-model.trim="$v.attributes.config.path.$model" />
</ControlGroup>

<ControlGroup label="Current context" :attribute="$v.attributes.config.currentContext">
<AutocompleteInput v-model.trim="$v.attributes.config.currentContext.$model" :options="contextOptions"/>
</ControlGroup>
<ControlGroup label="Current context" :attribute="$v.attributes.config.currentContext">
<AutocompleteInput v-model.trim="$v.attributes.config.currentContext.$model" :options="contextOptions"/>
</ControlGroup>
</template>

<div class="control-actions">
<Button theme="danger" layout="outline" :to="backPath">Cancel</Button>
Expand Down Expand Up @@ -68,9 +75,10 @@ import BaseTextArea from '../form/BaseTextArea'
import Button from '../Button'
import ControlGroup from '../form/ControlGroup'
import { checkConnection, buildKubeConfig } from '../../../lib/helpers/cluster'
import { showMessageBox, showOpenDialog, showConfirmBox } from '../../../lib/helpers/ui'
import { showMessageBox, showOpenDialog, showConfirmBox, showErrorBox } from '../../../lib/helpers/ui'
import BaseRadioButtons from '../form/BaseRadioButtons'
import * as configStoringMethods from '../../../lib/constants/config-storing-methods'
import { size } from '../../../lib/constants'
export default {
name: 'ClusterForm',
Expand Down Expand Up @@ -104,13 +112,16 @@ export default {
validations() {
const config = {
storingMethod: { required },
currentContext: { required },
currentContext: {},
path: {},
content: {}
}
const storingMethod = this.attributes.config.storingMethod
if (storingMethod === configStoringMethods.PATH) config.path.required = required
if (storingMethod === configStoringMethods.PATH) {
config.path.required = required
config.currentContext.required = required
}
if (storingMethod === configStoringMethods.CONTENT) config.content.required = required
return {
Expand Down Expand Up @@ -198,33 +209,43 @@ export default {
const filePaths = await showOpenDialog({ properties: ['openFile'] })
if (!filePaths) return
const fileStats = await fs.stat(filePaths[0])
if (fileStats.size > size.MBYTE) return showErrorBox('Sorry, the file is too large (> 1MB)')
const content = await fs.readFile(filePaths[0], { encoding: 'utf8' })
let kubeConfig = new KubeConfig()
try {
kubeConfig.loadFromString(content)
} catch (error) {
kubeConfig = null
const result = await showConfirmBox(
`The files contains invalid config. \nError ${error.message}.\n\n Do you want to continue?`
)
if (!result) return
if (method === configStoringMethods.CONTENT) {
this.attributes.config.content = content
}
if (method === configStoringMethods.PATH) this.attributes.config.path = filePaths[0]
if (method === configStoringMethods.CONTENT) this.attributes.config.content = content
if (method === configStoringMethods.PATH) {
let kubeConfig = new KubeConfig()
try {
kubeConfig.loadFromString(content)
} catch (error) {
kubeConfig = null
const result = await showConfirmBox(
`The files contains invalid config. \nError ${error.message}.\n\n Do you want to continue?`
)
if (!result) return
}
if (kubeConfig) {
this.attributes.config.currentContext = kubeConfig.getCurrentContext()
this.contexts = kubeConfig.contexts
} else {
this.contexts = []
this.attributes.config.path = filePaths[0]
if (kubeConfig) {
this.attributes.config.currentContext = kubeConfig.getCurrentContext()
this.contexts = kubeConfig.contexts
} else {
this.contexts = []
}
}
}
}
}
</script>

<style lang="scss">
@import '../../../assets/styles/variables';
.cluster-form {
.controls > .base-textarea {
height: 193px;
Expand All @@ -233,13 +254,31 @@ export default {
.control-actions {
.button + .button {
margin-left: 10px;
margin-left: $spacer-sm;
}
}
.button + .base-textarea,
.button + .base-input {
margin-top: 4px;
.base-radio-buttons {
margin-top: $spacer-sm;
}
}
.cluster-form__control-group-content {
.button {
margin-top: $spacer-xs;
}
}
.cluster-form__control-group-path {
.controls {
display: flex;
align-items: center;
}
.button {
margin-right: $spacer-sm;
height: 40px;
line-height: 40px - $border-width * 2;
}
}
</style>
2 changes: 1 addition & 1 deletion src/renderer/components/shared/form/BaseRadioButtons.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export default {
.base-radio-buttons__option {
display: inline-block;
margin-right: 70px;
margin-right: 40px;
input {
width: 0;
Expand Down
9 changes: 6 additions & 3 deletions src/renderer/lib/constants/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
export configStoringMethods from './config-storing-methods'
export connectionStates from './connection-states'
export workloadTypes from './workload-types'
import * as configStoringMethods from './config-storing-methods'
import * as connectionStates from './connection-states'
import * as workloadTypes from './workload-types'

export { size } from './units'
export { configStoringMethods, connectionStates, workloadTypes }
7 changes: 7 additions & 0 deletions src/renderer/lib/constants/units.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const BYTE = 1
const KBYTE = 1024 * BYTE
const MBYTE = 1024 * KBYTE

export const size = {
BYTE, KBYTE, MBYTE
}
3 changes: 1 addition & 2 deletions src/renderer/lib/helpers/cluster.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,13 @@ export function buildKubeConfig(clusterConfig) {

if (clusterConfig.storingMethod === configStoringMethods.PATH) {
kubeConfig.loadFromFile(clusterConfig.path)
kubeConfig.setCurrentContext(clusterConfig.currentContext)
} else if (clusterConfig.storingMethod === configStoringMethods.CONTENT) {
kubeConfig.loadFromString(clusterConfig.content)
} else {
throw new Error(`storingMethod "${clusterConfig.storingMethod}" is invalid.`)
}

kubeConfig.setCurrentContext(clusterConfig.currentContext)

return kubeConfig
}

Expand Down
5 changes: 3 additions & 2 deletions src/renderer/store/modules/Clusters.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export const clusterSchema = {
folded: { type: 'boolean' },
config: {
type: 'object',
required: ['storingMethod', 'currentContext'],
required: ['storingMethod'],
additionalProperties: false,
properties: {
storingMethod: { type: 'string', enum: configStoringMethods.default },
path: { type: 'string' },
Expand All @@ -28,7 +29,7 @@ export const clusterSchema = {
oneOf: [
{
properties: { storingMethod: { const: configStoringMethods.PATH } },
required: ['path']
required: ['path', 'currentContext']
},
{
properties: { storingMethod: { const: configStoringMethods.CONTENT } },
Expand Down

0 comments on commit 126b5b0

Please sign in to comment.