diff --git a/.gitignore b/.gitignore index 767c43f0d..fa534b856 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ workshop/**/Dockerfile .vscode .idea +kind.config.yaml diff --git a/Makefile b/Makefile index f9205b9eb..fa937f993 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ PORTER_HOME = bin CLIENT_PLATFORM = $(shell go env GOOS) CLIENT_ARCH = $(shell go env GOARCH) -CLIENT_GOPATH=$(shell go env GOPATH) +CLIENT_GOPATH = $(shell go env GOPATH) RUNTIME_PLATFORM = linux RUNTIME_ARCH = amd64 BASEURL_FLAG ?= @@ -125,6 +125,14 @@ publish-mixins: publish-images: VERSION=$(VERSION) PERMALINK=$(PERMALINK) ./scripts/publish-images.sh +start-local-docker-registry: + @docker run -d -p 5000:5000 --name registry registry:2 + +stop-local-docker-registry: + @if $$(docker inspect registry > /dev/null 2>&1); then \ + docker kill registry && docker rm registry ; \ + fi + # all-bundles loops through all items under the dir provided by the first argument # and if the item is a sub-directory containing a porter.yaml file, # runs the make target(s) provided by the second argument diff --git a/Makefile.kind b/Makefile.kind new file mode 100644 index 000000000..e1be5460a --- /dev/null +++ b/Makefile.kind @@ -0,0 +1,50 @@ +SHELL = /bin/bash +CLIENT_PLATFORM = $(shell go env GOOS) +CLIENT_ARCH = $(shell go env GOARCH) + +# Determine host ip to populate kind config api server details +# https://kind.sigs.k8s.io/docs/user/configuration/#api-server +ifeq ($(CLIENT_PLATFORM),linux) +export CURRENT_HOST_IP=$(shell hostname -i | awk '{print $1; exit}' | cut -d ' ' -f 1) +else +export CURRENT_HOST_IP=$(shell ifconfig en0 | awk '/inet / {print $2; }' | cut -d ' ' -f 2) +endif + +## Create file definition for the kind cluster +export KIND_CONFIG_FILE_NAME = kind.config.yaml +define get_kind_config_file +# Remove config file +rm -rf ${KIND_CONFIG_FILE_NAME} +# Define config file +cat << EOF >> ${KIND_CONFIG_FILE_NAME} +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +networking: + apiServerAddress: "${CURRENT_HOST_IP}" + apiServerPort: 6443 +EOF +endef +export KIND_CLUSTER_FILE_CREATOR = $(value get_kind_config_file) + +KIND_VERSION ?= v0.8.1 +CLUSTER_NAME = porter + +install-kind: + @curl -Lo /tmp/kind https://github.com/kubernetes-sigs/kind/releases/download/$(KIND_VERSION)/kind-$(CLIENT_PLATFORM)-$(CLIENT_ARCH) + @chmod +x /tmp/kind + @sudo mv /tmp/kind /usr/local/bin/kind + +create-kind-config-file:; @eval "$$KIND_CLUSTER_FILE_CREATOR" + +# TODO: remove need to manually set server value if darwin +# https://github.com/kubernetes-sigs/kind/issues/1732 +create-kind-cluster: create-kind-config-file + @kind create cluster \ + --name ${CLUSTER_NAME} \ + --config ${KIND_CONFIG_FILE_NAME} + @if [[ "$(CLIENT_PLATFORM)" == "darwin" ]]; then \ + kubectl config set clusters.kind-${CLUSTER_NAME}.server https://${CURRENT_HOST_IP}:6443 ; \ + fi + +delete-kind-cluster: + @kind delete cluster --name ${CLUSTER_NAME} diff --git a/build/azure-pipelines.pr-automatic.yml b/build/azure-pipelines.pr-automatic.yml index 3717fc791..b472016f9 100644 --- a/build/azure-pipelines.pr-automatic.yml +++ b/build/azure-pipelines.pr-automatic.yml @@ -75,3 +75,45 @@ jobs: make build-bundle validate-bundle workingDirectory: '$(System.DefaultWorkingDirectory)' displayName: 'Validate Example Bundles' + +- job: integration_test + dependsOn: setup + condition: eq(dependencies.setup.outputs['BUILD.DOCS_ONLY'], 'false') + steps: + - task: GoTool@0 + inputs: + version: '$(GOVERSION)' + - script: build/azure-pipelines.setup-go-workspace.sh + displayName: 'Set up the Go workspace' + + - script: | + make build + workingDirectory: '$(System.DefaultWorkingDirectory)' + displayName: 'Build' + + - script: | + trap 'make -f Makefile.kind delete-kind-cluster' EXIT + make -f Makefile.kind install-kind create-kind-cluster + make start-local-docker-registry test-integration stop-local-docker-registry + workingDirectory: '$(System.DefaultWorkingDirectory)' + displayName: 'Integration Test' + +- job: cli_test + dependsOn: setup + condition: eq(dependencies.setup.outputs['BUILD.DOCS_ONLY'], 'false') + steps: + - task: GoTool@0 + inputs: + version: '$(GOVERSION)' + - script: build/azure-pipelines.setup-go-workspace.sh + displayName: 'Set up the Go workspace' + + - script: | + make build + workingDirectory: '$(System.DefaultWorkingDirectory)' + displayName: 'Build' + + - script: | + make start-local-docker-registry test-cli stop-local-docker-registry + workingDirectory: '$(System.DefaultWorkingDirectory)' + displayName: 'CLI Test' \ No newline at end of file diff --git a/build/azure-pipelines.pr-manual.yml b/build/azure-pipelines.pr-manual.yml deleted file mode 100644 index 62dc7dafb..000000000 --- a/build/azure-pipelines.pr-manual.yml +++ /dev/null @@ -1,87 +0,0 @@ -# Go -# Build your Go project. -# Add steps that test, save build artifacts, deploy, and more: -# https://docs.microsoft.com/azure/devops/pipelines/languages/go - -trigger: none -pr: - - "*" - -pool: - vmImage: 'Ubuntu 16.04' - -variables: - GOVERSION: '1.13.10' - -jobs: -- job: setup - steps: - - script: ./build/doc-only-build.sh - name: BUILD - displayName: 'Check Doc Only Build' - -- job: integration_test - dependsOn: setup - condition: eq(dependencies.setup.outputs['BUILD.DOCS_ONLY'], 'false') - steps: - - task: GoTool@0 - inputs: - version: '$(GOVERSION)' - - script: build/azure-pipelines.setup-go-workspace.sh - displayName: 'Set up the Go workspace' - - - task: Docker@1 - displayName: Docker Login - inputs: - containerRegistryType: Container Registry - dockerRegistryEndpoint: getporter-registry - command: login - - - task: DownloadSecureFile@1 - displayName: Download Kubeconfig - inputs: - secureFile: kubeconfig - - - script: | - make build - workingDirectory: '$(System.DefaultWorkingDirectory)' - displayName: 'Build' - - - script: | - export KUBECONFIG=$DOWNLOADSECUREFILE_SECUREFILEPATH - make test-integration - workingDirectory: '$(System.DefaultWorkingDirectory)' - displayName: 'Integration Test' - -- job: cli_test - dependsOn: setup - condition: eq(dependencies.setup.outputs['BUILD.DOCS_ONLY'], 'false') - steps: - - task: GoTool@0 - inputs: - version: '$(GOVERSION)' - - script: build/azure-pipelines.setup-go-workspace.sh - displayName: 'Set up the Go workspace' - - - task: Docker@1 - displayName: Docker Login - inputs: - containerRegistryType: Container Registry - dockerRegistryEndpoint: getporter-registry - command: login - - - task: DownloadSecureFile@1 - displayName: Download Kubeconfig - inputs: - secureFile: kubeconfig - - - script: | - make build - workingDirectory: '$(System.DefaultWorkingDirectory)' - displayName: 'Build' - - - script: | - export KUBECONFIG=$DOWNLOADSECUREFILE_SECUREFILEPATH - REGISTRY=getporterci make test-cli - workingDirectory: '$(System.DefaultWorkingDirectory)' - displayName: 'CLI Test' diff --git a/build/azure-pipelines.release.yml b/build/azure-pipelines.release.yml index bb3f7692c..eda64b2f4 100644 --- a/build/azure-pipelines.release.yml +++ b/build/azure-pipelines.release.yml @@ -42,21 +42,10 @@ stages: - script: build/azure-pipelines.setup-go-workspace.sh displayName: 'Set up the Go workspace' - - task: Docker@1 - displayName: Docker Login - inputs: - containerRegistryType: Container Registry - dockerRegistryEndpoint: getporter-registry - command: login - - - task: DownloadSecureFile@1 - displayName: Download Kubeconfig - inputs: - secureFile: kubeconfig - - script: | - export KUBECONFIG=$DOWNLOADSECUREFILE_SECUREFILEPATH - make test-integration + trap 'make -f Makefile.kind delete-kind-cluster' EXIT + make -f Makefile.kind install-kind create-kind-cluster + make start-local-docker-registry test-integration stop-local-docker-registry workingDirectory: '$(System.DefaultWorkingDirectory)' displayName: 'Integration Test' @@ -68,26 +57,12 @@ stages: - script: build/azure-pipelines.setup-go-workspace.sh displayName: 'Set up the Go workspace' - - task: Docker@1 - displayName: Docker Login - inputs: - containerRegistryType: Container Registry - dockerRegistryEndpoint: getporter-registry - command: login - - - task: DownloadSecureFile@1 - displayName: Download Kubeconfig - inputs: - secureFile: kubeconfig - - script: make build workingDirectory: '$(System.DefaultWorkingDirectory)' displayName: 'Build' - script: | - export KUBECONFIG=$DOWNLOADSECUREFILE_SECUREFILEPATH - export REGISTRY=getporterci - make test-cli + make start-local-docker-registry test-cli stop-local-docker-registry workingDirectory: '$(System.DefaultWorkingDirectory)' displayName: 'CLI Test' diff --git a/build/brigade.js b/build/brigade.js index cab20fd85..267ea0262 100644 --- a/build/brigade.js +++ b/build/brigade.js @@ -1,4 +1,5 @@ const { events, Job, Group } = require("brigadier"); +const { KindJob } = require("@brigadecore/brigade-utils"); // ********************************************** // Globals @@ -18,7 +19,6 @@ events.on("issue_comment:edited", handleIssueComment); events.on("exec", (e, p) => { return Group.runAll([ - verify(e, p), build(e, p), xbuild(e, p), testUnit(e, p), @@ -28,6 +28,13 @@ events.on("exec", (e, p) => { ]); }); +events.on("test-integration", (e, p) => { + return Group.runAll([ + testIntegration(e, p), + testCLI(e, p) + ]); +}) + // Although a GH App will trigger 'check_suite:requested' on a push to main branch event, // it will not for a tag push, hence the need for this handler events.on("push", (e, p) => { @@ -62,16 +69,6 @@ events.on("publish-examples", (e, p) => { // Important: each Job name below must only consist of lowercase // alphanumeric characters and hyphens, per K8s resource name restrictions -function verify(e, p) { - var goBuild = new GoJob(`${projectName}-verify`); - - goBuild.tasks.push( - "make verify" - ); - - return goBuild; -} - function build(e, p) { var goBuild = new GoJob(`${projectName}-build`); @@ -120,53 +117,29 @@ function testUnit(e, p) { // TODO: we could refactor so that this job shares a mount with the build job above, // to remove the need of re-building before running test-cli function testIntegration(e, p) { - var goTest = new GoJob(`${projectName}-testintegration`); - // Enable Docker-in-Docker - goTest.enableDind(); - - goTest.env.kubeconfig = { - secretKeyRef: { - name: "porter-kubeconfig", - key: "kubeconfig" - } - }; - - // Set up kubeconfig, docker login, run tests - goTest.tasks.push( - "mkdir -p ${HOME}/.kube", - 'echo "${kubeconfig}" > ${HOME}/.kube/config', - `docker login ${p.secrets.dockerhubRegistry} \ - -u ${p.secrets.dockerhubUsername} \ - -p ${p.secrets.dockerhubPassword}`, - `REGISTRY=${p.secrets.dockerhubOrg} make test-integration` + var testInt = new KindJob(`${projectName}-testintegration`, "vdice/go-dind:kind-v0.7.0"); + + testInt.tasks.push( + "mkdir -p /go/bin", + "cd /src", + "trap 'make -f Makefile.kind delete-kind-cluster' EXIT", + `make -f Makefile.kind create-kind-cluster`, + "make start-local-docker-registry test-integration stop-local-docker-registry" ); - return goTest; + return testInt; } function testCLI(e, p) { - var goTest = new GoJob(`${projectName}-testcli`); + var testCLI = new GoJob(`${projectName}-testcli`); // Enable Docker-in-Docker - goTest.enableDind(); + testCLI.enableDind(); - goTest.env.kubeconfig = { - secretKeyRef: { - name: "porter-kubeconfig", - key: "kubeconfig" - } - }; - - // Set up kubeconfig, docker login, run tests - goTest.tasks.push( - "mkdir -p ${HOME}/.kube", - 'echo "${kubeconfig}" > ${HOME}/.kube/config', - `docker login ${p.secrets.dockerhubRegistry} \ - -u ${p.secrets.dockerhubUsername} \ - -p ${p.secrets.dockerhubPassword}`, - `REGISTRY=${p.secrets.dockerhubOrg} make test-cli` + testCLI.tasks.push( + "make start-local-docker-registry test-cli stop-local-docker-registry" ); - return goTest; + return testCLI; } function publishExamples(e, p) { @@ -214,7 +187,6 @@ function publish(e, p) { // using data supplied by a corresponding GitHub webhook, say, on a // check_run:rerequested event (see runCheck below) checks = { - "verify": { runFunc: verify, description: "Verify" }, "build": { runFunc: build, description: "Build" }, "validate": { runFunc: validate, description: "Validate" }, "crossplatformbuild": { runFunc: xbuild, description: "Cross-Platform Build" }, @@ -302,7 +274,7 @@ class GoJob extends Job { const gopath = "/go"; const localPath = gopath + `/src/get.porter.sh/${projectName}`; - this.image = "quay.io/vdice/go-dind:v0.1.0"; + this.image = "quay.io/vdice/go-dind:v0.1.2"; this.tasks = [ // Need to move the source into GOPATH so vendor/ works as desired. diff --git a/build/brigade.json b/build/brigade.json new file mode 100644 index 000000000..59f2f7ce2 --- /dev/null +++ b/build/brigade.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "@brigadecore/brigade-utils": "0.5.0" + } +} \ No newline at end of file diff --git a/build/testdata/bundles/mysql/porter.yaml b/build/testdata/bundles/mysql/porter.yaml index 30fbbb078..025e918fe 100644 --- a/build/testdata/bundles/mysql/porter.yaml +++ b/build/testdata/bundles/mysql/porter.yaml @@ -4,7 +4,7 @@ mixins: name: mysql version: 0.1.2 -tag: getporter/mysql:v0.1.2 +tag: localhost:5000/mysql:v0.1.2 credentials: - name: kubeconfig diff --git a/build/testdata/bundles/wordpress/porter.yaml b/build/testdata/bundles/wordpress/porter.yaml index 6d54b2aa0..1a3c86703 100644 --- a/build/testdata/bundles/wordpress/porter.yaml +++ b/build/testdata/bundles/wordpress/porter.yaml @@ -4,11 +4,11 @@ mixins: name: wordpress version: 0.1.2 -tag: getporter/wordpress:v0.1.2 +tag: localhost:5000/wordpress:v0.1.2 dependencies: mysql: - tag: getporter/mysql:v0.1.2 + tag: localhost:5000/mysql:v0.1.2 parameters: database-name: wordpress mysql-user: wordpress diff --git a/docs/content/best-practices/kind.md b/docs/content/best-practices/kind.md new file mode 100644 index 000000000..c76709202 --- /dev/null +++ b/docs/content/best-practices/kind.md @@ -0,0 +1,83 @@ +--- +title: Connect to KinD +description: How to connect to a KinD cluster inside a Porter bundle. +--- + +[KinD](https://github.com/kubernetes-sigs/kind) clusters run Kubernetes via Docker containers. As long as you have access to a Docker daemon, you can run KinD! + +Check out the [docs](https://github.com/kubernetes-sigs/kind#installation-and-usage) to install and interact with the `kind` CLI, if you haven't already. + +By default, KinD sets up the Kubernetes API server IP to be the local loopback address (`127.0.0.1`). This is fine for interacting with the cluster from the context of your host, but when it comes to talking to the cluster from inside of another Docker image, as is the case when running an action on a Porter bundle using the default Docker driver, further configuration is needed to make this communication possible. + +There are two main options: + +1. Configure the Docker container that Porter runs to use [host networking](https://docs.docker.com/network/host/) +1. Configure the KinD cluster to use an IP address that is resolvable from within Porter's Docker container + +Both routes lead to considerable security implications and neither are advised for clusters hosting actual workloads or sensitive information. + +Here we'll look at the latter option of configuring the KinD cluster to use a [different API Server address](https://kind.sigs.k8s.io/docs/user/configuration/#api-server). + +### KinD Setup + +First, we need to determine our host IP address. I'll take the example of a Mac OSX host: + +```console +$ ifconfig en0 | awk '/inet / {print $2; }' | cut -d ' ' -f 2 +10.0.1.4 +``` + +We can then use this IP in the [KinD config](https://kind.sigs.k8s.io/docs/user/configuration): + +```yaml +kind: Cluster +apiVersion: kind.x-k8s.io/v1alpha4 +networking: + apiServerAddress: 10.0.1.4 + apiServerPort: 6443 +``` + +After saving the config to `kind-config.yaml`, we can create our cluster: + +```console +$ kind create cluster --config kind-config.yaml +``` + +Once the cluster is successfully created, the generated kubeconfig should be merged into the default location (`~/.kube/config` or the location specified by the `KUBECONFIG` env var). We can test API server communications with `kubectl`: + +```console + $ kubectl cluster-info +Kubernetes master is running at https://10.0.1.4:6443 +KubeDNS is running at https://10.0.1.4:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy + +To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'. +``` + +### Porter time! + +Porter bundles that access a Kubernetes cluster when running can now be installed as normal, once the [Credential Set](../credentials) is generated/edited to use the KinD kubeconfig. + +Here we'll generate credentials and install the [MySQL bundle](https://porter.sh/src/build/testdata/bundles/mysql): + +```console + $ porter credentials generate +Generating new credential mysql from bundle mysql +==> 1 credentials required for bundle mysql +? How would you like to set credential "kubeconfig" + file path +? Enter the path that will be used to set credential "kubeconfig" + /Users/vdice/.kubes/kind/kubeconfig + + $ porter install -c mysql +installing mysql... +executing install action from mysql (bundle instance: mysql) +Install MySQL +... +/usr/local/bin/helm helm install --name porter-ci-mysql stable/mysql --version 1.6.2 --replace --set mysqlDatabase=mydb --set mysqlUser=mysql-admin +NAME: porter-ci-mysql +LAST DEPLOYED: Wed Jul 15 15:11:43 2020 +NAMESPACE: default +STATUS: DEPLOYED +... +execution completed successfully! +``` diff --git a/scripts/test/test-hello.sh b/scripts/test/test-hello.sh index 485a02501..60a651514 100755 --- a/scripts/test/test-hello.sh +++ b/scripts/test/test-hello.sh @@ -1,7 +1,6 @@ #!/usr/bin/env bash set -euo pipefail -export REGISTRY=${REGISTRY:-$USER} export REPO_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/../.." && pwd )" export PORTER_HOME=${PORTER_HOME:-$REPO_DIR/bin} # Run tests in a temp directory @@ -13,9 +12,6 @@ trap popd EXIT # Verify our default template bundle ${PORTER_HOME}/porter create -# Substitute REGISTRY in for the bundle tag -sed -i "s/getporter\/porter-hello/${REGISTRY}\/porter-hello/g" porter.yaml - ${PORTER_HOME}/porter build ${PORTER_HOME}/porter install --debug cat ${PORTER_HOME}/claims/HELLO.json @@ -24,4 +20,4 @@ cat ${PORTER_HOME}/claims/HELLO.json ${PORTER_HOME}/porter uninstall --debug # Publish bundle -${PORTER_HOME}/porter publish +${PORTER_HOME}/porter publish --tag localhost:5000/porter-hello:v0.1.0 diff --git a/tests/archive_test.go b/tests/archive_test.go index 0e3a57036..2cc73b872 100644 --- a/tests/archive_test.go +++ b/tests/archive_test.go @@ -23,7 +23,7 @@ func TestArchive(t *testing.T) { // Currently, archive requires the bundle to already be published. // https://github.com/deislabs/porter/issues/697 publishOpts := porter.PublishOptions{} - publishOpts.Tag = "getporterci/mysql:v0.1.2" + publishOpts.Tag = "localhost:5000/mysql:v0.1.2" err := publishOpts.Validate(p.Context) require.NoError(p.T(), err, "validation of publish opts for bundle failed") @@ -46,7 +46,7 @@ func TestArchive(t *testing.T) { publishFromArchiveOpts := porter.PublishOptions{ ArchiveFile: "mybuns.tgz", BundlePullOptions: porter.BundlePullOptions{ - Tag: "getporterci/mysql-from-archive:v0.1.2", + Tag: "localhost:5000/mysql-from-archive:v0.1.2", }, } err = publishFromArchiveOpts.Validate(p.Context)