diff --git a/.circleci/config.yml b/.circleci/config.yml index e13461ab14..23266e6a4c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,7 +4,7 @@ # version: 2 jobs: - build: + unit-tests: docker: # specify the version you desire here - image: circleci/node:9 @@ -29,6 +29,10 @@ jobs: # fallback to using the latest cache if no exact match is found - v2-dependencies- + - run: + command: | + curl -Lo kubectl https://storage.googleapis.com/kubernetes-release/release/v1.10.0/bin/linux/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/ + - run: sudo apt install rsync ruby ruby-dev cmake libicu-dev pkg-config - run: sudo gem install --no-document copyright-header - run: npm install @@ -47,4 +51,54 @@ jobs: # run tests - run: npm test - - run: ./bin/integ + + minikube-tests: + # got this from https://github.com/gavinzhou/ci-minikube/blob/master/.circleci/config.yml + machine: true + environment: + CHANGE_MINIKUBE_NONE_USER: true + BASH_ENV: ~/.bashrc + steps: + - checkout + - run: + command: | + curl -Lo kubectl https://storage.googleapis.com/kubernetes-release/release/v1.10.0/bin/linux/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/ + - run: + command: | + ## only run 0.25.0 , not work after 0.25.0+ + ## because circleci machine run ubuntu 14 + curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.25.0/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/ + - run: sudo minikube start --vm-driver=none + - run: sudo minikube update-context + - run: + name: Install node@8 + # https://discuss.circleci.com/t/how-to-change-node-version-in-circleci-2-0/17455 + command: | + set +e + touch $BASH_ENV + curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.5/install.sh | bash + echo 'export NVM_DIR="$HOME/.nvm"' >> $BASH_ENV + echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' >> $BASH_ENV + echo 'nvm install 8' >> $BASH_ENV + echo 'nvm alias default 8' >> $BASH_ENV + - run: + # for some reason we need to manually source $BASH_ENV (CircleCI machine executer is poorly documented...) + command: | + source $BASH_ENV + npm install + npm run build + - run: + command: | + JSONPATH='{range .items[*]}{@.metadata.name}:{range @.status.conditions[*]}{@.type}={@.status};{end}{end}'; until sudo kubectl get nodes -o jsonpath="$JSONPATH" 2>&1 | grep -q "Ready=True"; do sleep 1; done + - run: sudo kubectl cluster-info + - run: + command: | + source $BASH_ENV + ./bin/integ + +workflows: + version: 2 + all-tests: + jobs: + - unit-tests +# - minikube-tests diff --git a/docs/minikube.md b/docs/minikube.md new file mode 100644 index 0000000000..6c5bb9e5d5 --- /dev/null +++ b/docs/minikube.md @@ -0,0 +1,42 @@ +## Using Garden with Minikube + +Garden can be used with [Minikube](https://github.com/kubernetes/minikube) on supported platforms. + +### Installation + +For installation instructions, please see the [official guide](https://github.com/kubernetes/minikube#installation). +You'll likely also need to install a driver to run the Minikube VM, please follow the +[instructions here](https://github.com/kubernetes/minikube/blob/master/docs/drivers.md#hyperkit-driver) +and note the name of the driver. + +Once Minikube and the appropriate driver for your OS is installed, you can start it by running: + + minikube start --vm-driver= # e.g. hyperkit on macOS + +You'll also need to have Docker (for macOS, we recommend [Docker for Mac](https://docs.docker.com/engine/installation/)) +and [kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/) installed. + +_NOTE: Garden is not yet officially supported on Windows, but we have every intention to support it. +Please file any issues and we will try and respond promptly._ + +### Usage + +The `local-kubernetes` plugin attempts to automatically detect if it is installed and set the appropriate context +for connecting to the local Kubernetes instance. In most cases you should not have to update your `garden.yml` +since it uses the `local-kubernetes` plugin by default, but you can configure it explicitly in your project +`garden.yml` like so: + +```yaml +project: + environments: + - name: local + providers: + - name: local-kubernetes + context: minikube +``` + +_Note: If you happen to have installed both Minikube and the Docker for Mac version with Kubernetes enabled, +`garden` will choose whichever one is configured as the current context in your `kubectl` configuration, and if neither +is set as the current context, Docker for Mac is preferred by default._ + +Once configured, the `local-kubernetes` plugin will automatically configure everything Garden needs to work. diff --git a/examples/hello-world/garden.yml b/examples/hello-world/garden.yml index 1c1fc85628..3a14de0bd1 100644 --- a/examples/hello-world/garden.yml +++ b/examples/hello-world/garden.yml @@ -10,7 +10,6 @@ project: - name: local providers: - name: local-kubernetes - context: docker-for-desktop - name: local-google-cloud-functions - name: dev providers: diff --git a/garden.yml b/garden.yml index 24b4eb7a36..1850cd3e62 100644 --- a/garden.yml +++ b/garden.yml @@ -5,3 +5,5 @@ project: providers: - name: container - name: local-kubernetes + # note: this context is not actually used for anything + context: docker-for-desktop diff --git a/gulpfile.ts b/gulpfile.ts index 2f76120fbf..0508188f75 100644 --- a/gulpfile.ts +++ b/gulpfile.ts @@ -80,10 +80,22 @@ gulp.task("add-version-files", (cb) => { proc.on("error", err => cb(err)) let output = "" - proc.stdout.on("data", d => output += d) + let outputWithError = "" + proc.stdout.on("data", d => { + output += d + outputWithError += d + }) + proc.stderr.on("data", d => outputWithError += d) proc.on("close", () => { - const results = JSON.parse(output) + let results + try { + results = JSON.parse(output) + } catch { + const msg = "Got unexpected output from `garden scan`" + console.error(msg + "\n" + outputWithError) + return cb(msg) + } for (const module of results.result) { const relPath = relative(__dirname, module.path) diff --git a/src/garden.ts b/src/garden.ts index db33534532..29a90c3e0d 100644 --- a/src/garden.ts +++ b/src/garden.ts @@ -380,8 +380,8 @@ export class Garden { try { plugin = factory({ - garden: this, config, + logEntry: this.log, }) } catch (error) { throw new PluginError(`Unexpected error when loading plugin "${pluginName}": ${error}`, { diff --git a/src/plugins/kubernetes/local.ts b/src/plugins/kubernetes/local.ts index f195c9a916..95e4878a97 100644 --- a/src/plugins/kubernetes/local.ts +++ b/src/plugins/kubernetes/local.ts @@ -6,11 +6,15 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { execSync } from "child_process" +import { readFileSync } from "fs" +import { safeLoad } from "js-yaml" import { every, values, } from "lodash" import * as Joi from "joi" +import { join } from "path" import { validate } from "../../types/common" import { ConfigureEnvironmentParams, @@ -32,6 +36,11 @@ import { isSystemGarden, } from "./system" +// note: this is in order of preference, in case neither is set as the current kubectl context +// and none is explicitly configured in the garden.yml +const supportedContexts = ["docker-for-desktop", "minikube"] +const kubeConfigPath = join(process.env.HOME || "~", ".kube", "config") + // extend the environment configuration to also set up an ingress controller and dashboard export async function getLocalEnvironmentStatus( { ctx, provider, env, logEntry }: GetEnvironmentStatusParams, @@ -78,19 +87,72 @@ async function configureLocalEnvironment( } } -export const name = "local-kubernetes" +function getKubeConfig(): any { + try { + return safeLoad(readFileSync(kubeConfigPath).toString()) + } catch { + return {} + } +} + +function setMinikubeDockerEnv() { + const minikubeEnv = execSync("minikube docker-env --shell=bash").toString() + for (const line of minikubeEnv.split("\n")) { + const matched = line.match(/^export (\w+)="(.+)"$/) + if (matched) { + process.env[matched[1]] = matched[2] + } + } +} const configSchema = providerConfigBase.keys({ - context: Joi.string().default("docker-for-desktop"), + context: Joi.string(), _system: Joi.any(), }) -export function gardenPlugin({ config }): GardenPlugin { +export const name = "local-kubernetes" + +export function gardenPlugin({ config, logEntry }): GardenPlugin { config = validate(config, configSchema, { context: "kubernetes provider config" }) + let context = config.context + + if (!context) { + // automatically detect supported kubectl context if not explicitly configured + const kubeConfig = getKubeConfig() + const currentContext = kubeConfig["current-context"] + + if (currentContext && supportedContexts.includes(currentContext)) { + // prefer current context if set and supported + context = currentContext + logEntry.debug({ section: name, msg: `Using current context: ${context}` }) + } else if (kubeConfig.contexts) { + const availableContexts = kubeConfig.contexts.map(c => c.name) + + for (const supportedContext of supportedContexts) { + if (availableContexts.includes(supportedContext)) { + context = supportedContext + logEntry.debug({ section: name, msg: `Using detected context: ${context}` }) + break + } + } + } + } + + if (!context) { + context = supportedContexts[0] + logEntry.debug({ section: name, msg: `No kubectl context auto-deteced, using default: ${context}` }) + } + + if (context === "minikube") { + // automatically set docker environment variables for minikube + // TODO: it would be better to explicitly provide those to docker instead of using process.env + setMinikubeDockerEnv() + } + const k8sConfig: KubernetesConfig = { name: config.name, - context: config.context, + context, ingressHostname: "local.app.garden", ingressClass: "nginx", // TODO: support SSL on local deployments diff --git a/src/types/plugin.ts b/src/types/plugin.ts index e2d9a1dab1..53d676f3b0 100644 --- a/src/types/plugin.ts +++ b/src/types/plugin.ts @@ -318,7 +318,7 @@ export interface GardenPlugin { } export interface PluginFactory { - ({ garden: Garden, config: object }): GardenPlugin + ({ config: object, logEntry: LogEntry }): GardenPlugin pluginName?: string } export type RegisterPluginParam = string | PluginFactory diff --git a/src/types/project.ts b/src/types/project.ts index 1b99c597b1..0679fdd353 100644 --- a/src/types/project.ts +++ b/src/types/project.ts @@ -46,7 +46,6 @@ export const defaultEnvironments: EnvironmentConfig[] = [ providers: [ { name: "local-kubernetes", - context: "docker-for-desktop", }, ], variables: {},