From 80be44ebf049540064dd7cdd5f0f41b350a92e77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Roucheton?= Date: Wed, 3 Oct 2018 10:42:14 +0200 Subject: [PATCH] gokube is born again! --- .gitignore | 2 + .travis.yml | 41 +++++++ Gopkg.lock | 111 +++++++++++++++++ Gopkg.toml | 38 ++++++ README.md | 219 +++++++++++++++++++++++++++++++++- cmd/gokube/cmd/init.go | 110 +++++++++++++++++ cmd/gokube/cmd/root.go | 38 ++++++ cmd/gokube/cmd/version.go | 48 ++++++++ cmd/gokube/main.go | 23 ++++ docs/developer-guide.md | 20 ++++ logo/gokube_150x150.png | Bin 0 -> 4965 bytes logo/gokube_1600x1600.png | Bin 0 -> 70001 bytes pkg/docker/docker.go | 112 ++++++++++++++++++ pkg/download/download.go | 89 ++++++++++++++ pkg/gokube/gokube.go | 51 ++++++++ pkg/helm/helm.go | 142 ++++++++++++++++++++++ pkg/kubectl/kubectl.go | 102 ++++++++++++++++ pkg/minikube/minikube.go | 163 +++++++++++++++++++++++++ pkg/utils/utils.go | 243 ++++++++++++++++++++++++++++++++++++++ 19 files changed, 1551 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 Gopkg.lock create mode 100644 Gopkg.toml create mode 100644 cmd/gokube/cmd/init.go create mode 100644 cmd/gokube/cmd/root.go create mode 100644 cmd/gokube/cmd/version.go create mode 100644 cmd/gokube/main.go create mode 100644 docs/developer-guide.md create mode 100644 logo/gokube_150x150.png create mode 100644 logo/gokube_1600x1600.png create mode 100644 pkg/docker/docker.go create mode 100644 pkg/download/download.go create mode 100644 pkg/gokube/gokube.go create mode 100644 pkg/helm/helm.go create mode 100644 pkg/kubectl/kubectl.go create mode 100644 pkg/minikube/minikube.go create mode 100644 pkg/utils/utils.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8ef0e14 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +vendor/ +.idea/ diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..8161c77 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,41 @@ +os: linux + +language: go + +env: + global: + - RELEASE_NUMBER=v1.0.0 + - ARTIFACT_NAME=gokube-$RELEASE_NUMBER+build.$TRAVIS_BUILD_NUMBER.windows.amd64.exe + +go: +- 1.10.x + +go_import_path: github.com/gemalto/gokube + +# Don't email me the results of the test runs. +notifications: + email: true + +before_install: + +install: + +script: +- cd cmd/gokube +- GOOS=windows GOARCH=amd64 go get -t -v ./... +- GOOS=windows GOARCH=amd64 go build -o bin/$ARTIFACT_NAME +- ls -al bin + +before_deploy: +- git config --local user.name "Travis CI" +- git config --local user.email "travis@travis-ci.org" +- git tag $RELEASE_NUMBER + +deploy: + provider: releases + skip_cleanup: true # Important, otherwise the build output would be purged. + api_key: $GITHUB_API_KEY + file: bin/$ARTIFACT_NAME + on: + repo: gemalto/gokube + tags: true # The deployment happens only if the commit has a tag. diff --git a/Gopkg.lock b/Gopkg.lock new file mode 100644 index 0000000..7d58d68 --- /dev/null +++ b/Gopkg.lock @@ -0,0 +1,111 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be" + name = "github.com/inconshreveable/mousetrap" + packages = ["."] + pruneopts = "UT" + revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75" + version = "v1.0" + +[[projects]] + digest = "1:c658e84ad3916da105a761660dcaeb01e63416c8ec7bc62256a9b411a05fcd67" + name = "github.com/mattn/go-colorable" + packages = ["."] + pruneopts = "UT" + revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072" + version = "v0.0.9" + +[[projects]] + digest = "1:0981502f9816113c9c8c4ac301583841855c8cf4da8c72f696b3ebedf6d0e4e5" + name = "github.com/mattn/go-isatty" + packages = ["."] + pruneopts = "UT" + revision = "6ca4dbf54d38eea1a992b3c722a76a5d1c4cb25c" + version = "v0.0.4" + +[[projects]] + digest = "1:645cabccbb4fa8aab25a956cbcbdf6a6845ca736b2c64e197ca7cbb9d210b939" + name = "github.com/spf13/cobra" + packages = ["."] + pruneopts = "UT" + revision = "ef82de70bb3f60c65fb8eebacbb2d122ef517385" + version = "v0.0.3" + +[[projects]] + digest = "1:dab83a1bbc7ad3d7a6ba1a1cc1760f25ac38cdf7d96a5cdd55cd915a4f5ceaf9" + name = "github.com/spf13/pflag" + packages = ["."] + pruneopts = "UT" + revision = "9a97c102cda95a86cec2345a6f09f55a939babf5" + version = "v1.0.2" + +[[projects]] + branch = "master" + digest = "1:6eb2645d74b43d9c87b51947df39f7c668a4f422cd512053f7f6f75bfaad0197" + name = "golang.org/x/sys" + packages = ["unix"] + pruneopts = "UT" + revision = "d0be0721c37eeb5299f245a996a483160fc36940" + +[[projects]] + digest = "1:e626376fab8608a972d47e91b3c1bbbddaecaf1d42b82be6dcc52d10a7557893" + name = "gopkg.in/VividCortex/ewma.v1" + packages = ["."] + pruneopts = "UT" + revision = "b24eb346a94c3ba12c1da1e564dbac1b498a77ce" + version = "v1.1.1" + +[[projects]] + digest = "1:495f9b42208e426f00d10b5bdaf4f7af2d0f56c20483136caca6ef5e2531e801" + name = "gopkg.in/cheggaaa/pb.v2" + packages = [ + ".", + "termutil", + ] + pruneopts = "UT" + revision = "c112833d014c77e8bde723fd0158e3156951639f" + version = "v2.0.6" + +[[projects]] + digest = "1:865079840386857c809b72ce300be7580cb50d3d3129ce11bf9aa6ca2bc1934a" + name = "gopkg.in/fatih/color.v1" + packages = ["."] + pruneopts = "UT" + revision = "5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4" + version = "v1.7.0" + +[[projects]] + digest = "1:c658e84ad3916da105a761660dcaeb01e63416c8ec7bc62256a9b411a05fcd67" + name = "gopkg.in/mattn/go-colorable.v0" + packages = ["."] + pruneopts = "UT" + revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072" + version = "v0.0.9" + +[[projects]] + digest = "1:0981502f9816113c9c8c4ac301583841855c8cf4da8c72f696b3ebedf6d0e4e5" + name = "gopkg.in/mattn/go-isatty.v0" + packages = ["."] + pruneopts = "UT" + revision = "6ca4dbf54d38eea1a992b3c722a76a5d1c4cb25c" + version = "v0.0.4" + +[[projects]] + digest = "1:cdb899c199f907ac9fb50495ec71212c95cb5b0e0a8ee0800da0238036091033" + name = "gopkg.in/mattn/go-runewidth.v0" + packages = ["."] + pruneopts = "UT" + revision = "ce7b0b5c7b45a81508558cd1dba6bb1e4ddb51bb" + version = "v0.0.3" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + input-imports = [ + "github.com/spf13/cobra", + "gopkg.in/cheggaaa/pb.v2", + ] + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml new file mode 100644 index 0000000..57d1507 --- /dev/null +++ b/Gopkg.toml @@ -0,0 +1,38 @@ +# Gopkg.toml example +# +# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html +# for detailed Gopkg.toml documentation. +# +# required = ["github.com/user/thing/cmd/thing"] +# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] +# +# [[constraint]] +# name = "github.com/user/project" +# version = "1.0.0" +# +# [[constraint]] +# name = "github.com/user/project2" +# branch = "dev" +# source = "github.com/myfork/project2" +# +# [[override]] +# name = "github.com/x/y" +# version = "2.4.0" +# +# [prune] +# non-go = false +# go-tests = true +# unused-packages = true + + +[[constraint]] + name = "github.com/spf13/cobra" + version = "0.0.3" + +[[constraint]] + name = "gopkg.in/cheggaaa/pb.v2" + version = "2.0.6" + +[prune] + go-tests = true + unused-packages = true diff --git a/README.md b/README.md index 4b44dd6..3085949 100644 --- a/README.md +++ b/README.md @@ -1 +1,218 @@ -# gokube \ No newline at end of file +# GoKube + +[![Build Status](https://api.travis-ci.com/gemalto/gokube.svg?branch=master)](https://travis-ci.com/gemalto/gokube) + +## What is GoKube? + +![gokube-logo](https://github.com/gemalto/gokube/blob/master/logo/gokube_150x150.png) + +GoKube is a tool that makes it easy developping day-to-day with [Kubernetes](https://github.com/kubernetes/kubernetes) on your laptop under Windows. + +GoKube downloads and installs many dependencies such as: +* [Minikube](https://github.com/kubernetes/minikube) +* [Docker](https://www.docker.com) +* [Helm](https://github.com/helm/helm) +* [Kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl) +* [Monocular](https://github.com/helm/monocular) + +GoKube deploys and configures Monocular for a better user experience! +You will be able to deploy in one click useful helm charts for developing in your kubernetes cluster. + +GoKube is configured with a dedicated helm repository named [miniapps](https://github.com/gokube/miniapps) which contains the following charts: +* [Cassandra](https://github.com/gemalto/miniapps/tree/master/charts/cassandra) +* [Heapster](https://github.com/gemalto/miniapps/tree/master/charts/heapster) +* [Pact](https://github.com/gemalto/miniapps/tree/master/charts/pact) +* [Kibana](https://github.com/gemalto/miniapps/tree/master/charts/kibana) +* [Grafana](https://github.com/gemalto/miniapps/tree/master/charts/grafana) +* [Livedoc](https://github.com/gemalto/miniapps/tree/master/charts/livedoc) + +These charts are optimized in term of memory and cpu for minikube and very useful for developers. + +## Requirements +* Windows + * [VirtualBox](https://www.virtualbox.org/wiki/Downloads) or [Hyper-V](https://github.com/kubernetes/minikube/blob/master/docs/drivers.md#hyperV-driver) +* VT-x/AMD-v virtualization must be enabled in BIOS +* Internet connection on first run + +## How to install GoKube? + +### Windows + + +#### Assumptions + +You will use C:\gokube\bin to store executable files. + +#### Set up Your Directory + +You’ll need a place to store the gokube executable: +* Open Windows Explorer. +* Create a new folder: C:\gokube, assuming you want gokube on your C drive, although this can go anywhere. +* Create a subfolder in the gokube folder: C:\gokube\bin + +#### Download binary + +* The latest release for gokube can be download on the [Releases page](https://github.com/gemalto/gokube/releases/latest). +* Copy executable file to: C:\gokube\bin +* The gokube executable will be named as gokube-version-type+platform.arch.exe. Rename the executable to gokube.exe for ease of use. + +#### Verify the Executable + +In your preferred CLI, at the prompt, type gokube and press the Enter key. You should see output that starts with: + +```shell +$ gokube + +gokube is a nice installer to provide an environment for developing day-to-day +with kubernetes & helm on your laptop. + +Usage: + gokube [command] + +Available Commands: + help Help about any command + init Initializes gokube. This command downloads dependencies: + minikube + helm + kubectl + docker + monocular and creates the virtual machine (minikube) + version Shows version for gokube + +Flags: + -h, --help help for gokube +``` +If you do, then the installation is complete. + +If you don’t, double-check the path that you placed the gokube.exe file in and that you typed that path correctly when you added it to your PATH variable. + +## Quickstart +Here's a brief demo of GoKube usage. + +```shell +$ gokube init + +minikube v0.28.0: 40.83 MiB / 40.83 MiB [-------------------------------------] 100.00% 2.21 MiB p/s +helm v2.9.1: 8.78 MiB / 8.78 MiB [--------------------------------------------] 100.00% 2.20 MiB p/s +docker v17.09.0: 16.17 MiB / 16.17 MiB [--------------------------------------] 100.00% 2.11 MiB p/s +kubectl v1.10.0: 52.16 MiB / 52.16 MiB [--------------------------------------] 100.00% 1.61 MiB p/s + +Installing goKube! + +Starting local Kubernetes v1.10.0 cluster... +Starting VM... +Downloading Minikube ISO + 153.08 MB / 153.08 MB 100.00% 0ssss +Getting VM IP address... +Waiting for image caching to complete... +Moving files into cluster... +Downloading kubelet v1.10.0 +Downloading kubeadm v1.10.0 +Finished Downloading kubeadm v1.10.0 +Finished Downloading kubelet v1.10.0 +Setting up certs... +Connecting to cluster... +Setting up kubeconfig... +Starting cluster components... +Kubectl is now configured to use the cluster. +Loading cached images from config file. +Switched to context "minikube". +Creating C:\Users\user\.helm +Creating C:\Users\user\.helm\repository +Creating C:\Users\user\.helm\repository\cache +Creating C:\Users\user\.helm\repository\local +Creating C:\Users\user\.helm\plugins +Creating C:\Users\user\.helm\starters +Creating C:\Users\user\.helm\cache\archive +Creating C:\Users\user\.helm\repository\repositories.yaml +Adding stable repo with URL: https://kubernetes-charts.storage.googleapis.com +Adding local repo with URL: http://127.0.0.1:8879/charts +$HELM_HOME has been configured at C:\Users\user\.helm. + +Tiller (the Helm server-side component) has been installed into your Kubernetes Cluster. + +Please note: by default, Tiller is deployed with an insecure 'allow unauthenticated users' policy. +Happy Helming! +"monocular" has been added to your repositories +Hang tight while we grab the latest from your chart repositories... +...Skip local chart repository +...Successfully got an update from the "monocular" chart repository +...Successfully got an update from the "stable" chart repository +Update Complete. ⎈ Happy Helming!⎈ +Starting stable/nginx-ingress components... +Starting monocular/monocular components... + +goKube! has been installed. + +To verify that goKube! has started, run: +> kubectl get pods --all-namespaces +``` + +We can see that pods are still being created from the ContainerCreating status: + +```shell +$ kubectl get pod --all-namespaces +NAMESPACE NAME READY STATUS RESTARTS AGE +kube-system etcd-minikube 1/1 Running 0 1m +kube-system gokube-mongodb-7c86445c7-zx6cv 0/1 ContainerCreating 0 1m +kube-system gokube-monocular-api-5798749fdb-6xwsn 0/1 ContainerCreating 0 1m +kube-system gokube-monocular-prerender-c9f57f6c8-nbjl7 0/1 ContainerCreating 0 1m +kube-system gokube-monocular-ui-7d79f486-w98px 0/1 ContainerCreating 0 1m +kube-system kube-addon-manager-minikube 1/1 Running 0 1m +kube-system kube-apiserver-minikube 1/1 Running 0 1m +kube-system kube-controller-manager-minikube 1/1 Running 0 1m +kube-system kube-dns-86f4d74b45-4swsw 3/3 Running 0 2m +kube-system kube-proxy-dltxx 1/1 Running 0 2m +kube-system kube-scheduler-minikube 1/1 Running 0 1m +kube-system kubernetes-dashboard-5498ccf677-5p82h 1/1 Running 0 2m +kube-system nginx-nginx-ingress-controller-859558948c-5rr2c 1/1 Running 0 1m +kube-system nginx-nginx-ingress-default-backend-7bb66746b9-tfmm2 1/1 Running 0 1m +kube-system storage-provisioner 1/1 Running 0 2m +kube-system tiller-deploy-f9b8476d-rk5ps 1/1 Running 0 2m +``` + +We can see that pods are now running and we will now be able to access to gokube: + +```shell +$ kubectl get pod --all-namespaces +NAMESPACE NAME READY STATUS RESTARTS AGE +kube-system etcd-minikube 1/1 Running 0 5m +kube-system gokube-mongodb-7c86445c7-zx6cv 1/1 Running 0 6m +kube-system gokube-monocular-api-5798749fdb-6xwsn 1/1 Running 4 6m +kube-system gokube-monocular-prerender-c9f57f6c8-nbjl7 1/1 Running 0 6m +kube-system gokube-monocular-ui-7d79f486-w98px 1/1 Running 0 6m +kube-system kube-addon-manager-minikube 1/1 Running 0 5m +kube-system kube-apiserver-minikube 1/1 Running 0 5m +kube-system kube-controller-manager-minikube 1/1 Running 0 5m +kube-system kube-dns-86f4d74b45-4swsw 3/3 Running 0 6m +kube-system kube-proxy-dltxx 1/1 Running 0 6m +kube-system kube-scheduler-minikube 1/1 Running 0 5m +kube-system kubernetes-dashboard-5498ccf677-5p82h 1/1 Running 0 6m +kube-system nginx-nginx-ingress-controller-859558948c-5rr2c 1/1 Running 0 6m +kube-system nginx-nginx-ingress-default-backend-7bb66746b9-tfmm2 1/1 Running 0 6m +kube-system storage-provisioner 1/1 Running 0 6m +kube-system tiller-deploy-f9b8476d-rk5ps 1/1 Running 0 6m +``` + +We can stop gokube running the following command: +```shell +$ minikube stop +Stopping local Kubernetes cluster... +Machine stopped. +``` + +We can start gokube running the following command: +```shell +$ minikube start +Starting local Kubernetes v1.10.0 cluster... +Starting VM... +Getting VM IP address... +Moving files into cluster... +Setting up certs... +Connecting to cluster... +Setting up kubeconfig... +Starting cluster components... +Kubectl is now configured to use the cluster. +Loading cached images from config file. +``` + +## Developer Guide + +If you want to contribute to this project you are encouraged to send issue request, or provide pull-requests. +Please read the [developer guide](./docs/developer-guide.md) to learn more on how you can contribute. diff --git a/cmd/gokube/cmd/init.go b/cmd/gokube/cmd/init.go new file mode 100644 index 0000000..af901b4 --- /dev/null +++ b/cmd/gokube/cmd/init.go @@ -0,0 +1,110 @@ +/* +(c) Copyright 2018, Gemalto. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmd + +import ( + "fmt" + "os" + + "github.com/gemalto/gokube/pkg/docker" + "github.com/gemalto/gokube/pkg/gokube" + "github.com/gemalto/gokube/pkg/helm" + "github.com/gemalto/gokube/pkg/kubectl" + "github.com/gemalto/gokube/pkg/minikube" + + "github.com/spf13/cobra" +) + +var memory int16 +var nCPUs int16 +var diskSize string +var httpProxy string +var httpsProxy string +var noProxy string +var upgrade bool +var insecureRegistry string + +// initCmd represents the start command +var initCmd = &cobra.Command{ + Use: "init", + Short: "Initializes gokube. This command downloads dependencies: minikube + helm + kubectl + docker + monocular and creates the virtual machine (minikube)", + Long: "Initializes gokube. This command downloads dependencies: minikube + helm + kubectl + docker + monocular and creates the virtual machine (minikube)", + Run: initRun, +} + +func init() { + initCmd.Flags().Int16VarP(&memory, "memory", "m", int16(8192), "Amount of RAM allocated to the minikube VM in MB") + initCmd.Flags().Int16VarP(&nCPUs, "nCPUs", "c", int16(4), "Number of CPUs allocated to the minikube VM") + initCmd.Flags().StringVarP(&diskSize, "disk-size", "d", "20g", "Disk size allocated to the minikube VM. Format: [], where unit = b, k, m or g") + initCmd.Flags().StringVarP(&httpProxy, "http-proxy", "", "", "HTTP proxy for minikube VM") + initCmd.Flags().StringVarP(&httpsProxy, "https-proxy", "", "", "HTTPS proxy for minikube VM") + initCmd.Flags().StringVarP(&noProxy, "no-proxy", "", "", "No proxy for minikube VM") + initCmd.Flags().BoolVarP(&upgrade, "upgrade", "u", false, "Upgrade if Go Kube! is already installed") + initCmd.Flags().StringVarP(&insecureRegistry, "insecure-registry", "", "", "Insecure Docker registries to pass to the Docker daemon. The default service CIDR range will automatically be added.") + RootCmd.AddCommand(initCmd) +} + +func initRun(cmd *cobra.Command, args []string) { + + if len(args) > 0 { + fmt.Fprintln(os.Stderr, "usage: gokube init") + os.Exit(1) + } + + if upgrade { + minikube.Delete() + minikube.Purge() + helm.Purge() + kubectl.Purge() + docker.Purge() + } + + // Download dependencies + minikube.Download(gokube.GetBinDir()) + helm.Download(gokube.GetBinDir()) + docker.Download(gokube.GetBinDir()) + kubectl.Download(gokube.GetBinDir()) + + // Create virtual machine (minikube) + fmt.Println("\nInstalling goKube...") + minikube.ConfigSet("WantUpdateNotification", "false") + minikube.Start(memory, nCPUs, diskSize, httpProxy, httpsProxy, noProxy, insecureRegistry) + + // Switch context to minikube for kubectl and helm + kubectl.ConfigUseContext("minikube") + + // Install helm + helm.Init() + + // Add Helm repository + helm.RepoAdd("monocular", "https://helm.github.io/monocular") + helm.RepoAdd("miniapps", "https://gokube.github.io/miniapps") + helm.RepoUpdate() + + // Deploy Monocular + helm.UpgradeWithConfiguration("nginx", "kube-system", "controller.hostNetwork=true", "stable/nginx-ingress", "0.25.1") + helm.UpgradeWithConfiguration("gokube", "kube-system", "api.config.repos[0].name=miniapps,api.config.repos[0].url=https://gokube.github.io/miniapps,api.config.repos[0].source=https://github.com/gokube/miniapps/tree/master/charts,api.replicaCount=1,api.image.pullPolicy=IfNotPresent,api.config.cacheRefreshInterval=60,ui.replicaCount=1,ui.image.pullPolicy=IfNotPresent,ui.appName=goKube,prerender.replicaCount=1,prerender.image.pullPolicy=IfNotPresent", "monocular/monocular", "0.6.3") + + //kubectl.Patch("kube-system", "deployment", "gokube-monocular-api", "{\"spec\":{\"template\":{\"spec\":{\"containers\":[{\"env\":[{\"name\":\"HTTP_PROXY\",\"value\":\""+httpProxy+"\"},{\"name\":\"HTTPS_PROXY\",\"value\":\""+httpsProxy+"\"},{\"name\":\"NO_PROXY\",\"value\":\""+noProxy+"\"}] }] }}}}") + kubectl.Patch("kube-system", "deployment", "gokube-monocular-api", "{\"spec\":{\"template\":{\"spec\":{\"containers\":[{\"name\":\"monocular\",\"env\":[{\"name\":\"HTTP_PROXY\",\"value\":\""+httpsProxy+"\"}]}]}}}}") + kubectl.Patch("kube-system", "deployment", "gokube-monocular-api", "{\"spec\":{\"template\":{\"spec\":{\"containers\":[{\"name\":\"monocular\",\"env\":[{\"name\":\"HTTPS_PROXY\",\"value\":\""+httpProxy+"\"}]}]}}}}") + kubectl.Patch("kube-system", "deployment", "gokube-monocular-api", "{\"spec\":{\"template\":{\"spec\":{\"containers\":[{\"name\":\"monocular\",\"env\":[{\"name\":\"NO_PROXY\",\"value\":\""+noProxy+"\"}]}]}}}}") + + fmt.Println("\ngoKube has been installed.") + fmt.Println("\nTo verify that goKube! has started, run:") + fmt.Println("> kubectl get pods --all-namespaces") + fmt.Println("") + +} diff --git a/cmd/gokube/cmd/root.go b/cmd/gokube/cmd/root.go new file mode 100644 index 0000000..103892a --- /dev/null +++ b/cmd/gokube/cmd/root.go @@ -0,0 +1,38 @@ +/* +(c) Copyright 2018, Gemalto. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmd + +import ( + "os" + + "github.com/spf13/cobra" +) + +// RootCmd represents the base command when called without any subcommands +var RootCmd = &cobra.Command{ + Use: "gokube", + Short: `gokube is a nice installer to provide an environment for developing day-to-day with kubernetes & helm on your laptop.`, + Long: `gokube is a nice installer to provide an environment for developing day-to-day with kubernetes & helm on your laptop.`, + PersistentPreRun: func(cmd *cobra.Command, args []string) { + }, +} + +// Execute adds all child commands to the root command sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + if err := RootCmd.Execute(); err != nil { + os.Exit(1) + } +} diff --git a/cmd/gokube/cmd/version.go b/cmd/gokube/cmd/version.go new file mode 100644 index 0000000..5997e0c --- /dev/null +++ b/cmd/gokube/cmd/version.go @@ -0,0 +1,48 @@ +/* +(c) Copyright 2018, Gemalto. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package cmd + +import ( + "fmt" + "os" + + "github.com/gemalto/gokube/pkg/docker" + "github.com/gemalto/gokube/pkg/helm" + "github.com/gemalto/gokube/pkg/kubectl" + "github.com/gemalto/gokube/pkg/minikube" + "github.com/spf13/cobra" +) + +// stopCmd represents the stop command +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Shows version for gokube", + Long: `Shows version for gokube`, + Run: func(cmd *cobra.Command, args []string) { + if len(args) > 0 { + fmt.Fprintln(os.Stderr, "usage: gokube version") + os.Exit(1) + } + fmt.Println("go-kube version: v1.0.0") + minikube.Version() + helm.Version() + docker.Version() + kubectl.Version() + }, +} + +func init() { + RootCmd.AddCommand(versionCmd) +} diff --git a/cmd/gokube/main.go b/cmd/gokube/main.go new file mode 100644 index 0000000..0587994 --- /dev/null +++ b/cmd/gokube/main.go @@ -0,0 +1,23 @@ +/* +(c) Copyright 2018, Gemalto. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package main + +import ( + "github.com/gemalto/gokube/cmd/gokube/cmd" +) + +func main() { + cmd.Execute() +} diff --git a/docs/developer-guide.md b/docs/developer-guide.md new file mode 100644 index 0000000..0a3da25 --- /dev/null +++ b/docs/developer-guide.md @@ -0,0 +1,20 @@ +# Developer Guide + +## Golang + +To develop and build GoKube you need to have Golang (v1.10.x or newer) locally. + + +## Commands for building + +```shell +// Go to build directory +cd cmd/gokube + +// Get dependencies +go get -t -v ./... + +// Start server in development +go build + +``` diff --git a/logo/gokube_150x150.png b/logo/gokube_150x150.png new file mode 100644 index 0000000000000000000000000000000000000000..22b8c0bff261a76e5402d3a68f4af3ea1c33508e GIT binary patch literal 4965 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 p`f~E-elC%H- literal 0 HcmV?d00001 diff --git a/logo/gokube_1600x1600.png b/logo/gokube_1600x1600.png new file mode 100644 index 0000000000000000000000000000000000000000..38192cfbaf32f0f901964c5a6353be855c0ba00e GIT binary patch literal 70001 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" "_" "]" | green }} {{percent . }} {{speed . }}` + tmpl = `{{ green "` + name + `: " }}{{counters . }} {{bar . | green }} {{percent . }} {{speed . }}` + + utils.CreateDir(gokube.GetTempDir()) + output, err := os.Create(gokube.GetTempDir() + "/" + fileName) + if err != nil { + panic(err) + } + defer output.Close() + + response, err := http.Get(url) + if err != nil { + panic(err) + } + defer response.Body.Close() + + filesize := response.ContentLength + count := int(filesize) + bar := pb.ProgressBarTemplate(tmpl).Start(count) + //bar := pb.StartNew(count) + bar.SetWidth(100) + defer bar.Finish() + + // create proxy reader + reader := bar.NewProxyReader(response.Body) + n, err := io.Copy(output, reader) + + defer reader.Close() + if err != nil { + panic(err) + } + + var fi os.FileInfo + for fi == nil || int(fi.Size()) < count { + fi, _ = output.Stat() + bar.Increment() + time.Sleep(time.Millisecond) + } + + tokens = strings.Split(fileName, ".") + fileType := tokens[len(tokens)-1] + switch fileType { + case "zip": + utils.Unzip(gokube.GetTempDir()+"/"+fileName, gokube.GetTempDir()) + case "gz": + utils.Untar(gokube.GetTempDir()+"/"+fileName, gokube.GetTempDir()) + } + + return n +} diff --git a/pkg/gokube/gokube.go b/pkg/gokube/gokube.go new file mode 100644 index 0000000..db1e01b --- /dev/null +++ b/pkg/gokube/gokube.go @@ -0,0 +1,51 @@ +/* +(c) Copyright 2018, Gemalto. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package gokube + +import ( + "log" + "os" + "os/exec" + "strings" +) + +// GetBinDir ... +func GetBinDir() string { + path, err := exec.LookPath("gokube") + if err != nil { + panic(err) + } + if path == "gokube.exe" { + path = whereAmI() + } else { + path = strings.TrimSuffix(path, "\\gokube.exe") + } + return path +} + +// GetTempDir ... +func GetTempDir() string { + return GetBinDir() + "/../tmp" +} + +// WhereAmI returns a string containing the file name, function name +// and the line number of a specified entry on the call stack +func whereAmI() string { + dir, err := os.Getwd() + if err != nil { + log.Fatal(err) + } + return dir +} diff --git a/pkg/helm/helm.go b/pkg/helm/helm.go new file mode 100644 index 0000000..cd84a21 --- /dev/null +++ b/pkg/helm/helm.go @@ -0,0 +1,142 @@ +/* +(c) Copyright 2018, Gemalto. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package helm + +import ( + "fmt" + "os" + "os/exec" + + "github.com/gemalto/gokube/pkg/download" + "github.com/gemalto/gokube/pkg/gokube" + "github.com/gemalto/gokube/pkg/utils" +) + +const ( + URL = "https://storage.googleapis.com/kubernetes-helm/helm-v%s-windows-amd64.tar.gz" + VERSION = "2.9.1" +) + +// Upgrade ... +func Upgrade(chart string, release string) { + fmt.Println("Starting " + chart + " components...") + cmd := exec.Command("helm", "upgrade", "--install", "--devel", release, chart) + cmd.Stderr = os.Stderr + cmd.Run() +} + +// Delete ... +func Delete(release string) { + fmt.Println("Deleting " + release + " components...") + cmd := exec.Command("helm", "delete", release, "--purge") + cmd.Stderr = os.Stderr + cmd.Run() +} + +// UpgradeWithNamespaceVersionAndConfiguration ... +func UpgradeWithNamespaceVersionAndConfiguration(name string, namespace string, version string, configuration string, chart string) { + fmt.Println("Starting " + chart + " components...") + cmd := exec.Command("helm", "upgrade", "--install", "--devel", name, "--namespace", namespace, "--version", version, "--set", configuration, chart) + cmd.Stderr = os.Stderr + cmd.Run() +} + +// UpgradeWithConfiguration ... +func UpgradeWithConfiguration(name string, namespace string, configuration string, chart string, version string) { + fmt.Println("Starting " + chart + " components...") + cmd := exec.Command("helm", "install", chart, "--name", name, "--namespace", namespace, "--set", configuration, "--version", version) + cmd.Stderr = os.Stderr + cmd.Run() +} + +// UpgradeWithValues ... +func UpgradeWithValues(namespace string, values string, chart string) { + fmt.Println("Starting " + chart + " components...") + cmd := exec.Command("helm", "install", chart, "--namespace", namespace, "-f", values) + cmd.Stderr = os.Stderr + cmd.Run() +} + +// UpgradeWithNamespaceAndVersion ... +func UpgradeWithNamespaceAndVersion(name string, namespace string, version string, chart string) { + fmt.Println("Starting " + chart + " components...") + cmd := exec.Command("helm", "upgrade", "--install", "--devel", name, "--namespace", namespace, "--version", version, chart) + cmd.Stderr = os.Stderr + cmd.Run() +} + +// Init ... +func Init() { + cmd := exec.Command("helm", "init", "--upgrade", "--wait", "--tiller-connection-timeout", "600") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Run() +} + +// List ... +func List() { + cmd := exec.Command("helm", "list") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Run() +} + +// RepoAdd ... +func RepoAdd(name string, repo string) { + cmd := exec.Command("helm", "repo", "add", name, repo) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Run() +} + +//RepoUpdate ... +func RepoUpdate() { + cmd := exec.Command("helm", "repo", "update") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Run() +} + +// RepoRemove ... +func RepoRemove(name string) { + cmd := exec.Command("helm", "repo", "remove", name) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Run() +} + +//Version ... +func Version() { + fmt.Print("helm version: ") + cmd := exec.Command("helm", "version", "--client", "--short") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Run() +} + +// Download ... +func Download(dst string) { + if _, err := os.Stat(gokube.GetBinDir() + "/helm.exe"); os.IsNotExist(err) { + download.DownloadFromUrl("helm v"+VERSION, URL, VERSION) + utils.MoveFile(gokube.GetTempDir()+"/windows-amd64/helm.exe", dst+"/helm.exe") + utils.RemoveDir(gokube.GetTempDir()) + } +} + +// Purge ... +func Purge() { + utils.RemoveFile(gokube.GetBinDir() + "/helm.exe") + utils.RemoveDir(utils.GetUserHome() + "/.helm") +} diff --git a/pkg/kubectl/kubectl.go b/pkg/kubectl/kubectl.go new file mode 100644 index 0000000..bac8574 --- /dev/null +++ b/pkg/kubectl/kubectl.go @@ -0,0 +1,102 @@ +/* +(c) Copyright 2018, Gemalto. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kubectl + +import ( + "fmt" + "os" + "os/exec" + + "github.com/gemalto/gokube/pkg/download" + "github.com/gemalto/gokube/pkg/gokube" + "github.com/gemalto/gokube/pkg/utils" +) + +const ( + URL = "https://storage.googleapis.com/kubernetes-release/release/v%s/bin/windows/amd64/kubectl.exe" + VERSION = "1.10.0" +) + +// ConfigUseContext ... +func ConfigUseContext(context string) { + cmd := exec.Command("kubectl", "config", "use-context", context) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Run() +} + +// Patch ... +func Patch(namespace string, resourceType string, resourceName string, patch string) { + cmd := exec.Command("kubectl", "--namespace", namespace, "patch", resourceType, resourceName, "-p", patch) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Run() +} + +// Apply ... +func Apply(file string, namespace string) { + cmd := exec.Command("kubectl", "create", "-f", file, "--namespace", namespace) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Run() +} + +// Version ... +func Version() { + fmt.Println("kubectl version: ") + cmd := exec.Command("kubectl", "version") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Run() +} + +// DisabledNetworkPolicy ... +func DisabledNetworkPolicy() { + cmd := exec.Command("kubectl", "-n", "kube-system", "exec", "$(kubectl -n kube-system get pods -l k8s-app='cilium' -o jsonpath='{.items[0].metadata.name}')", "cilium", "config", "PolicyEnforcement=never") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Run() + fmt.Println("Network Policy disabled.") +} + +// CreateDockerRegistrySecret ... +func CreateDockerRegistrySecret(name string, dockerServer string, dockerUsername string, dockerPassword string, dockerEmail string) { + cmd := exec.Command("kubectl", "create", "secret", "docker-registry", name, dockerServer, dockerUsername, dockerPassword, dockerEmail) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Run() +} + +//Download ... +func Download(dst string) { + if _, err := os.Stat(gokube.GetBinDir() + "/kubectl.exe"); os.IsNotExist(err) { + download.DownloadFromUrl("kubectl v"+VERSION, URL, VERSION) + utils.MoveFile(gokube.GetTempDir()+"/kubectl.exe", dst+"/kubectl.exe") + utils.RemoveDir(gokube.GetTempDir()) + } +} + +// DeleteSecret ... +func DeleteSecret(name string) { + cmd := exec.Command("kubectl", "delete", "secret", name) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Run() +} + +// Purge ... +func Purge() { + utils.RemoveFile(gokube.GetBinDir() + "/kubectl.exe") +} diff --git a/pkg/minikube/minikube.go b/pkg/minikube/minikube.go new file mode 100644 index 0000000..af66e20 --- /dev/null +++ b/pkg/minikube/minikube.go @@ -0,0 +1,163 @@ +/* +(c) Copyright 2018, Gemalto. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package minikube + +import ( + "bufio" + "log" + "os" + "os/exec" + "strconv" + "strings" + + "github.com/gemalto/gokube/pkg/download" + "github.com/gemalto/gokube/pkg/gokube" + "github.com/gemalto/gokube/pkg/utils" +) + +const ( + URL = "https://storage.googleapis.com/minikube/releases/v%s/minikube-windows-amd64.exe" + VERSION = "0.28.0" +) + +// Start ... +func Start(memory int16, nCPUs int16, diskSize string, httpProxy string, httpsProxy string, npProxy string, insecureRegistry string) { + cmd := exec.Command("minikube", "start", "--insecure-registry", insecureRegistry, "--cache-images", "--docker-env", "HTTP_PROXY="+httpProxy, "--docker-env", "HTTPS_PROXY="+httpsProxy, "--docker-env", "NO_PROXY="+npProxy, "--memory", strconv.FormatInt(int64(memory), 10), "--cpus", strconv.FormatInt(int64(nCPUs), 10), "--disk-size", diskSize, "--network-plugin=cni", "--extra-config=kubelet.network-plugin=cni") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Run() +} + +// AddonsEnable ... +func AddonsEnable(addon string) { + cmd := exec.Command("minikube", "addons", "enable", addon) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Run() +} + +// QuickStart ... +func QuickStart() { + cmd := exec.Command("minikube", "start") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Run() +} + +// Stop ... +func Stop() { + cmd := exec.Command("minikube", "stop") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Run() +} + +// Status ... +func Status() { + cmd := exec.Command("minikube", "status") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Run() +} + +// Delete ... +func Delete() { + cmd := exec.Command("minikube", "delete") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Run() +} + +// CopyCerts ... +func CopyCerts() { + path := toLinuxPath("cp -r /home/docker/* etc/docker/certs.d") + cmd := exec.Command("minikube", "ssh", "sudo", path) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Run() +} + +// ConfigSet ... +func ConfigSet(key string, value string) { + cmd := exec.Command("minikube", "config", "set", key, value) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Run() +} + +// Version ... +func Version() { + cmd := exec.Command("minikube", "version") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Run() +} + +// DockerEnv ... +func DockerEnv() []utils.EnvVar { + out, err := exec.Command("minikube", "docker-env").Output() + if err != nil { + log.Fatal(err) + } + scanner := bufio.NewScanner(strings.NewReader(string(out))) + var envVar []utils.EnvVar + for scanner.Scan() { + line := scanner.Text() + if strings.HasPrefix(line, "export ") { + tokens := strings.Split(strings.TrimPrefix(line, "export "), "=") + envVar = append(envVar, utils.EnvVar{ + Name: tokens[0], + Value: trimQuotes(tokens[1]), + }) + } + if strings.HasPrefix(line, "SET ") { + tokens := strings.Split(strings.TrimPrefix(line, "SET "), "=") + envVar = append(envVar, utils.EnvVar{ + Name: tokens[0], + Value: trimQuotes(tokens[1]), + }) + } + } + return envVar +} + +// Download ... +func Download(dst string) { + if _, err := os.Stat(dst + "/minikube.exe"); os.IsNotExist(err) { + download.DownloadFromUrl("minikube v"+VERSION, URL, VERSION) + utils.MoveFile(gokube.GetTempDir()+"/minikube-windows-amd64.exe", dst+"/minikube.exe") + utils.RemoveDir(gokube.GetTempDir()) + } +} + +// Purge ... +func Purge() { + utils.RemoveFile(gokube.GetBinDir() + "/minikube.exe") + utils.RemoveDir(utils.GetUserHome() + "/.minikube") +} + +func toLinuxPath(path string) string { + path = strings.Replace(path, "\\", "/", -1) + return strings.Replace(path, "C:", "/c", -1) +} + +func trimQuotes(s string) string { + if len(s) >= 2 { + if s[0] == '"' && s[len(s)-1] == '"' { + return s[1 : len(s)-1] + } + } + return s +} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go new file mode 100644 index 0000000..0001298 --- /dev/null +++ b/pkg/utils/utils.go @@ -0,0 +1,243 @@ +/* +(c) Copyright 2018, Gemalto. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package utils + +import ( + "archive/tar" + "archive/zip" + "bufio" + "compress/gzip" + "io" + "io/ioutil" + "os" + "os/user" + "path/filepath" +) + +// EnvVar ... +type EnvVar struct { + Name string + Value string +} + +// CreateFile ... +func CreateFile(filePath string) { + var _, err = os.Stat(filePath) + if os.IsNotExist(err) { + var file, err = os.Create(filePath) + if err != nil { + panic(err) + } + defer file.Close() + } +} + +// CreateDir ... +func CreateDir(dirPath string) { + // Check if file already exist + if _, err := os.Stat(dirPath); os.IsNotExist(err) { + err := os.Mkdir(dirPath, 0001) + if err != nil { + panic(err) + } + } +} + +// MoveFile ... +func MoveFile(oldPath string, newPath string) { + err := os.Rename(oldPath, newPath) + if err != nil { + panic(err) + } +} + +func CopyFile(src, dst string) error { + in, err := os.Open(src) + if err != nil { + return err + } + defer in.Close() + + out, err := os.Create(dst) + if err != nil { + return err + } + defer out.Close() + + _, err = io.Copy(out, in) + if err != nil { + return err + } + return out.Close() +} + +// RemoveDir ... +func RemoveDir(dirPath string) { + err := os.RemoveAll(dirPath) + if err != nil { + panic(err) + } +} + +// RemoveFile ... +func RemoveFile(filePath string) { + err := os.RemoveAll(filePath) + if err != nil { + panic(err) + } +} + +// Untar takes a destination path and a reader; a tar reader loops over the tarfile +// creating the file structure at 'dst' along the way, and writing any files +func Untar(src string, dst string) error { + + file, err := os.Open(src) + defer file.Close() + if err != nil { + panic(err) + } + bufio.NewReader(file) + + gzr, err := gzip.NewReader(bufio.NewReader(file)) + defer gzr.Close() + if err != nil { + return err + } + + tr := tar.NewReader(gzr) + + for { + header, err := tr.Next() + + switch { + + // if no more files are found return + case err == io.EOF: + return nil + + // return any other error + case err != nil: + return err + + // if the header is nil, just skip it (not sure how this happens) + case header == nil: + continue + } + + // the target location where the dir/file should be created + target := filepath.Join(dst, header.Name) + + // the following switch could also be done using fi.Mode(), not sure if there + // a benefit of using one vs. the other. + // fi := header.FileInfo() + + // check the file type + switch header.Typeflag { + + // if its a dir and it doesn't exist create it + case tar.TypeDir: + if _, err := os.Stat(target); err != nil { + if err := os.MkdirAll(target, 0755); err != nil { + return err + } + } + + // if it's a file create it + case tar.TypeReg: + f, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR, os.FileMode(header.Mode)) + if err != nil { + return err + } + defer f.Close() + + // copy over contents + if _, err := io.Copy(f, tr); err != nil { + return err + } + } + } +} + +// Unzip ... +func Unzip(src string, dest string) error { + + var filenames []string + + r, err := zip.OpenReader(src) + if err != nil { + return err + } + defer r.Close() + + for _, f := range r.File { + + rc, err := f.Open() + if err != nil { + return err + } + defer rc.Close() + + // Store filename/path for returning and using later on + fpath := filepath.Join(dest, f.Name) + filenames = append(filenames, fpath) + + if f.FileInfo().IsDir() { + + // Make Folder + os.MkdirAll(fpath, os.ModePerm) + + } else { + + // Make File + if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil { + return err + } + + outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + return err + } + + _, err = io.Copy(outFile, rc) + + // Close the file without defer to close before next iteration of loop + outFile.Close() + + if err != nil { + return err + } + + } + } + return nil +} + +// GetUserHome ... +func GetUserHome() string { + user, err := user.Current() + if err != nil { + panic(err) + } + return user.HomeDir +} + +// WriteFile ... +func WriteFile(content string, path string) { + d1 := []byte(content) + err := ioutil.WriteFile(path, d1, 0644) + if err != nil { + panic(err) + } +}