Skip to content

Commit

Permalink
Installer for the latest stable version of k3d
Browse files Browse the repository at this point in the history
Signed-off-by: Alvaro Saurin <[email protected]>
  • Loading branch information
inercia committed Dec 8, 2020
1 parent bbdc16c commit bee458a
Show file tree
Hide file tree
Showing 14 changed files with 2,540 additions and 1,820 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Change Log

## 0.0.2

- Installer

## 0.0.1

- Basic cluster listing
- Create cluster
39 changes: 25 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,36 @@
# k3d extension for Visual Studio Code
# Kubernetes [k3d](https://github.com/rancher/k3d) extension for Visual Studio Code

This extension displays your `k3d` local cluster in the Kubernetes extension's Cloud Explorer.
You can use this to create and delete clusters, and to merge them into your kubeconfig.
## Overview

**This is an early stage preview. It's not feature complete! Feature requests welcome via the issues page.
(And let us know about bugs too!)**
This extension displays your [k3d](https://github.com/rancher/k3d) local clusters
in the Kubernetes extension's Cloud Explorer. You can use this to create and
delete clusters, and to merge them into your `kubeconfig`.

**This is an early stage preview. It's not feature complete! Feature requests
welcome via the issues page. (And let us know about bugs too!)**

This project is heavily based on the [KinD plugin for VSCode](https://github.com/deislabs/kind-vscode).

# Prerequisites
## Settings

Example:

You must have a recent `k3d` binary on your system path. You can download the binaries for your
operating system from https://github.com/rancher/k3d/releases. In particular, `k3d` must support
JSON output.
```JSON
"vs-kubernetes-k3d": {
"vs-kubernetes-k3d.k3d-path.linux": "/home/user/bin/k3d"
}
```

You can set the path to your k3d executble with the `k3d.path` setting.
* `vs-kubernetes-k3d.k3d-path`: this extension will download a recent version
of `k3d` automatically, but you can use your own binary by seting this
parameter.

# Development
## Development

The easiest way to start coding on the extension is by following these steps:

* `git clone https://github.com/inercia/vscode-k3d.git`
* `code vscode-k3d`
* `npm install` in the terminal, then `F5` to run it in a new window.

You can see the debug output in `View > Output` and choosing the `k3d` view.
* `npm install` in the terminal, then `F5` to start a new VSCode window
with the extension installed. You can add breakpoints and so on, and you
can see the debug output in `View > Output` and choosing the `k3d` view.
3,798 changes: 2,032 additions & 1,766 deletions package-lock.json

Large diffs are not rendered by default.

24 changes: 21 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "Work with the k3d local Kubernetes provider in Visual Studio Code",
"version": "0.0.1",
"preview": true,
"publisher": "ms-kubernetes-tools",
"publisher": "inercia",
"engines": {
"vscode": "^1.32.0"
},
Expand All @@ -31,11 +31,26 @@
{
"title": "K3D configuration",
"properties": {
"k3d": {
"vs-kubernetes-k3d": {
"type": "object",
"description": "Kubernetes configuration",
"properties": {
"k3d.path": {
"vs-kubernetes-k3d.k3d-path": {
"type": "string",
"default": "k3d",
"description": "k3d executable path"
},
"vs-kubernetes-k3d.k3d-path.linux": {
"type": "string",
"default": "k3d",
"description": "k3d executable path"
},
"vs-kubernetes-k3d.k3d-path.mac": {
"type": "string",
"default": "k3d",
"description": "k3d executable path"
},
"vs-kubernetes-k3d.k3d-path.windows": {
"type": "string",
"default": "k3d",
"description": "k3d executable path"
Expand Down Expand Up @@ -96,6 +111,9 @@
"webpack-cli": "^3.3.6"
},
"dependencies": {
"@types/mkdirp": "^0.5.2",
"download": "^7.1.0",
"mkdirp": "^0.5.1",
"replace-string": "^3.0.0",
"rxjs": "^6.4.0",
"shelljs": "^0.8.3",
Expand Down
43 changes: 43 additions & 0 deletions src/installer/downloads.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import * as path from 'path';
import * as stream from 'stream';
import * as tmp from 'tmp';

import { succeeded, Errorable } from '../utils/errorable';

type DownloadFunc =
(url: string, destination?: string, options?: any)
=> Promise<Buffer> & stream.Duplex; // Stream has additional events - see https://www.npmjs.com/package/download

let download: DownloadFunc | undefined;

function ensureDownloadFunc() {
if (!download) {
// Fix download module corrupting HOME environment variable on Windows
// See https://github.com/Azure/vscode-kubernetes-tools/pull/302#issuecomment-404678781
// and https://github.com/kevva/npm-conf/issues/13
const home = process.env['HOME'];
download = require('download');
if (home) {
process.env['HOME'] = home;
}
}
}

export async function toTempFile(sourceUrl: string): Promise<Errorable<string>> {
const tempFileObj = tmp.fileSync({ prefix: "vsk-k3d-autoinstall-" });
const downloadResult = await to(sourceUrl, tempFileObj.name);
if (succeeded(downloadResult)) {
return { succeeded: true, result: tempFileObj.name };
}
return { succeeded: false, error: downloadResult.error };
}

export async function to(sourceUrl: string, destinationFile: string): Promise<Errorable<null>> {
ensureDownloadFunc();
try {
await download!(sourceUrl, path.dirname(destinationFile), { filename: path.basename(destinationFile) }); // safe because we ensured it
return { succeeded: true, result: null };
} catch (e) {
return { succeeded: false, error: [e.message] };
}
}
65 changes: 65 additions & 0 deletions src/installer/installationlayout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import * as vscode from 'vscode';
import { Platform, platform } from "../utils/shell";
import * as path from 'path';
import * as shell from '../utils/shell';
import { VS_KUBE_K3D_EXTENSION_CONFIG_KEY } from "../utils/config";

function osKeyString(os: Platform): string | null {
switch (os) {
case Platform.Windows: return 'windows';
case Platform.MacOS: return 'mac';
case Platform.Linux: return 'linux';
default: return null;
}
}

export function platformUrlString(platform: Platform, supported?: Platform[]): string | null {
if (supported && supported.indexOf(platform) < 0) {
return null;
}
switch (platform) {
case Platform.Windows: return 'windows';
case Platform.MacOS: return 'darwin';
case Platform.Linux: return 'linux';
default: return null;
}
}

export function formatBin(tool: string, platform: Platform): string | null {
const platformString = platformUrlString(platform);
if (!platformString) {
return null;
}

const toolPath = `${platformString}-amd64/${tool}`;
if (platform === Platform.Windows) {
return toolPath + '.exe';
}
return toolPath;
}

// Functions for working with tool paths

export function getConfigK3DToolPath(tool: string): string | undefined {
const baseKey = getK3DToolPathBaseKey(tool);
return getK3DPathSetting(baseKey);
}

function getK3DToolPathBaseKey(tool: string): string {
return `${VS_KUBE_K3D_EXTENSION_CONFIG_KEY}.${tool}-path`;
}

function getK3DPathSetting(baseKey: string): string | undefined {
const os = platform();
const osOverridePath = vscode.workspace.getConfiguration(VS_KUBE_K3D_EXTENSION_CONFIG_KEY)[osOverrideKey(os, baseKey)];
return osOverridePath || vscode.workspace.getConfiguration(VS_KUBE_K3D_EXTENSION_CONFIG_KEY)[baseKey];
}

function osOverrideKey(os: Platform, baseKey: string): string {
const osKey = osKeyString(os);
return osKey ? `${baseKey}.${osKey}` : baseKey; // The 'else' clause should never happen so don't worry that this would result in double-checking a missing base key
}

export function getInstallFolder(tool: string): string {
return path.join(shell.home(), `.vs-kubernetes/tools/${tool}`);
}
141 changes: 141 additions & 0 deletions src/installer/installer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
'use strict';

import * as vscode from 'vscode';
import * as download from './downloads';
import * as fs from 'fs';
import * as path from 'path';
import mkdirp = require('mkdirp');
import * as shell from '../utils/shell';
import { logChannel } from '../utils/log';
import { Errorable, failed } from '../utils/errorable';
import { addPathToConfig, toolPathOSKey } from '../utils/config';
import { platformUrlString, getConfigK3DToolPath, getInstallFolder } from './installationlayout';

export enum EnsureMode {
Alert,
Silent,
}

export function getOrInstallK3D(mode: EnsureMode): Errorable<string> {
const configuredBin: string | undefined = getConfigK3DToolPath('k3d');
if (configuredBin) {
if (fs.existsSync(configuredBin)) {
return {
succeeded: true,
result: configuredBin
};
}

if (mode === EnsureMode.Alert) {
vscode.window.showErrorMessage(`${configuredBin} binary (specified in config file) does not exist!`,
"Install k3d").then((str) => {
if (str === "Install k3d") {
return installK3D();
}
});
}

return {
succeeded: false,
error: [`${configuredBin} does not exist!`]
};
}

const k3dFullPath = shell.which("k3d");
if (k3dFullPath) {
logChannel.appendLine(`[installer] already found at ${k3dFullPath}`);
return {
succeeded: true,
result: k3dFullPath
};
}

logChannel.appendLine(`[installer] k3d binary not found`);

if (mode === EnsureMode.Alert) {
vscode.window.showErrorMessage(`Could not find k3d binary.`,
"Install k3d").then((str) => {
if (str === "Install k3d") {
return installK3D();
}
});
}

logChannel.appendLine(`[installer] k3d not found and we did not try to install it`);
return {
succeeded: false,
error: [`k3d not found.`]
};
}

// installK3D installs K3D in a tools directory, updating the config
// for pointing to this file
export async function installK3D(): Promise<Errorable<string>> {
const tool = 'k3d';
const binFile = (shell.isUnix()) ? 'k3d' : 'k3d.exe';
const binFileExtension = (shell.isUnix()) ? '' : '.exe';
const platform = shell.platform();
const os = platformUrlString(platform);

const version = await getStableK3DVersion();
if (failed(version)) {
return {
succeeded: false,
error: version.error
};
}

const installFolder = getInstallFolder(tool);
mkdirp.sync(installFolder);

const k3dUrl = `https://github.com/rancher/k3d/releases/download/${version.result.trim()}/k3d-${os}-amd64${binFileExtension}`;
const downloadFile = path.join(installFolder, binFile);

logChannel.appendLine(`[installer] downloading ${k3dUrl} to ${downloadFile}`);
const downloadResult = await download.to(k3dUrl, downloadFile);
if (failed(downloadResult)) {
return {
succeeded: false,
error: [`Failed to download kubectl: ${downloadResult.error[0]}`]
};
}

if (shell.isUnix()) {
fs.chmodSync(downloadFile, '0777');
}

// update the config for pointing to this file
await addPathToConfig(toolPathOSKey(platform, tool), downloadFile);

logChannel.appendLine(`[installer] k3d installed successfully`);

// refresh the views
vscode.commands.executeCommand("extension.vsKubernetesRefreshExplorer");
vscode.commands.executeCommand("extension.vsKubernetesRefreshCloudExplorer");

return {
succeeded: true,
result: downloadFile
};
}

// getStableK3DVersion gets the latest version of K3D from GitHub
async function getStableK3DVersion(): Promise<Errorable<string>> {
const downloadResult = await download.toTempFile('https://api.github.com/repos/rancher/k3d/releases/latest');
if (failed(downloadResult)) {
return {
succeeded: false,
error: [`Failed to find k3d stable version: ${downloadResult.error[0]}`]
};
}

const versionObj = JSON.parse(fs.readFileSync(downloadResult.result, 'utf-8'));
fs.unlinkSync(downloadResult.result);

const v = versionObj['tag_name'];
logChannel.appendLine(`[installer] found latest version ${v} from GitHub relases`);
return {
succeeded: true,
result: v
};
}
Loading

0 comments on commit bee458a

Please sign in to comment.