1
0
mirror of https://github.com/alexellis/arkade.git synced 2022-05-07 18:22:49 +03:00

Rename to bazaar

Signed-off-by: Alex Ellis (OpenFaaS Ltd) <alexellis2@gmail.com>
This commit is contained in:
Alex Ellis (OpenFaaS Ltd)
2020-02-26 09:16:13 +00:00
parent 9b1c878af7
commit 99dd6b4611
44 changed files with 4697 additions and 4 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
bazaar
bin/**
kubeconfig
.DS_Store
.idea/
mc

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2020 Alex Ellis
Copyright (c) 2019 Alex Ellis
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.

23
Makefile Normal file
View File

@@ -0,0 +1,23 @@
Version := $(shell git describe --tags --dirty)
GitCommit := $(shell git rev-parse HEAD)
LDFLAGS := "-s -w -X github.com/alexellis/bazaar/cmd.Version=$(Version) -X github.com/alexellis/bazaar/cmd.GitCommit=$(GitCommit)"
PLATFORM := $(shell ./hack/platform-tag.sh)
.PHONY: all
.PHONY: build
build:
go build
.PHONY: test
test:
CGO_ENABLED=0 go test $(shell go list ./... | grep -v /vendor/|xargs echo) -cover
.PHONY: dist
dist:
mkdir -p bin
GO111MODULE=on CGO_ENABLED=0 GOOS=linux go build -mod=vendor -ldflags $(LDFLAGS) -a -installsuffix cgo -o bin/bazaar
GO111MODULE=on CGO_ENABLED=0 GOOS=darwin go build -mod=vendor -ldflags $(LDFLAGS) -a -installsuffix cgo -o bin/bazaar-darwin
GO111MODULE=on CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 go build -mod=vendor -ldflags $(LDFLAGS) -a -installsuffix cgo -o bin/bazaar-armhf
GO111MODULE=on CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -mod=vendor -ldflags $(LDFLAGS) -a -installsuffix cgo -o bin/bazaar-arm64
GO111MODULE=on CGO_ENABLED=0 GOOS=windows go build -mod=vendor -ldflags $(LDFLAGS) -a -installsuffix cgo -o bin/bazaar.exe

View File

@@ -1,2 +1,79 @@
# bazaar
Bazaar - a Kubernetes marketplace
# bazaar - get Kubernetes apps, the easy way
With bazaar, you can install around a dozen of the most popular cloud native tools to Kubernetes including: nginx-ingress, Minio, OpenFaaS, Istio, cert-manager and many more. Save yourself the hassle of traversing dozens of README files, bazaar automates everything for you including the downloading of helm.
[![Build
Status](https://travis-ci.com/alexellis/bazaar.svg?branch=master)](https://travis-ci.com/alexellis/bazaar)
[![Go Report Card](https://goreportcard.com/badge/github.com/alexellis/bazaar)](https://goreportcard.com/report/github.com/alexellis/bazaar)
[![GoDoc](https://godoc.org/github.com/alexellis/bazaar?status.svg)](https://godoc.org/github.com/alexellis/bazaar) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
![GitHub All Releases](https://img.shields.io/github/downloads/alexellis/bazaar/total)
## Usage
Here's a few examples of apps you can install, for a complete list run: `[baz]aar install --help`.
```bash
[baz]aar install openfaas --gateways 2 --load-balancer false
[baz]aar install cert-manager
[baz]aar install nginx-ingress
[baz]aar install inlets-operator --access-token $HOME/digitalocean --region lon1
```
An alias of `baz` is created at installation time.
Here's how you can get a self-hosted Docker registry with TLS and authentication in just 5 commands on an empty cluster:
```bash
bazaar install nginx-ingress
bazaar install cert-manager
bazaar install docker-registry
bazaar install docker-registry-ingress \
--email web@example.com \
--domain reg.example.com
```
The same for OpenFaaS would look like this:
```bash
bazaar install nginx-ingress
bazaar install cert-manager
bazaar install openfaas
bazaar install openfaas-ingress \
--email web@example.com \
--domain reg.example.com
```
And if you're running on a private cloud, on-premises or on your laptop, you can simply add the inlets-operator using inlets-pro to get a secure TCP tunnel and a public IP address.
```bash
[baz]aar install inlets-operator \
--access-token $HOME/digitalocean \
--region lon1 \
--license $(cat $HOME/license.txt)
```
## Contributing
### Suggesting a new app
To suggest a new app, please check past issues and [raise an issue for it](https://github.com/alexellis/bazaar).
### Improving the code or fixing an issue
Before contributing code, please see the [CONTRIBUTING guide](https://github.com/alexellis/inlets/blob/master/CONTRIBUTING.md). Note that bazaar uses the same guide as [inlets.dev](https://inlets.dev/).
Both Issues and PRs have their own templates. Please fill out the whole template.
All commits must be signed-off as part of the [Developer Certificate of Origin (DCO)](https://developercertificate.org)
### k3sup vs. bazaar
The codebase in this project is derived from `k3sup`. k3sup (ketchup) was developed to automate building of k3s clusters over SSH, then gained the powerful feature to install apps in a single command. The presence of the word "k3s" in the name of the application confused many people, this is why bazaar has come to exist.
### License
MIT

3
ci/hashgen.sh Executable file
View File

@@ -0,0 +1,3 @@
#!/bin/sh
for f in bin/bazaar*; do shasum -a 256 $f > $f.sha256; done

94
cmd/app.go Normal file
View File

@@ -0,0 +1,94 @@
package cmd
import (
"fmt"
"strings"
"github.com/alexellis/bazaar/cmd/apps"
"github.com/spf13/cobra"
)
func MakeInstall() *cobra.Command {
var command = &cobra.Command{
Use: "install",
Short: "Install Kubernetes apps from helm charts or YAML files",
Long: `Install Kubernetes apps from helm charts or YAML files using the "install"
command. Helm 2 is used by default unless a --helm3 flag exists and is passed.
You can also find the post-install message for each app with the "info"
command.`,
Example: ` bazaar install
bazaar install openfaas --helm3 --gateways=2
bazaar app info inlets-operator`,
SilenceUsage: false,
}
var install = &cobra.Command{
Use: "install",
Short: "Install a Kubernetes app",
Example: ` bazaar install [APP]
bazaar install openfaas --help
bazaar install inlets-operator --token-file $HOME/do
bazaar install --help`,
SilenceUsage: true,
}
install.PersistentFlags().String("kubeconfig", "kubeconfig", "Local path for your kubeconfig file")
install.RunE = func(command *cobra.Command, args []string) error {
if len(args) == 0 {
fmt.Printf("You can install: %s\n%s\n\n", strings.TrimRight("\n - "+strings.Join(getApps(), "\n - "), "\n - "),
`Run bazaar install NAME --help to see configuration options.`)
return nil
}
return nil
}
command.AddCommand(install)
install.AddCommand(apps.MakeInstallOpenFaaS())
install.AddCommand(apps.MakeInstallMetricsServer())
install.AddCommand(apps.MakeInstallInletsOperator())
install.AddCommand(apps.MakeInstallCertManager())
install.AddCommand(apps.MakeInstallOpenFaaSIngress())
install.AddCommand(apps.MakeInstallNginx())
install.AddCommand(apps.MakeInstallChart())
install.AddCommand(apps.MakeInstallLinkerd())
install.AddCommand(apps.MakeInstallCronConnector())
install.AddCommand(apps.MakeInstallKafkaConnector())
install.AddCommand(apps.MakeInstallMinio())
install.AddCommand(apps.MakeInstallPostgresql())
install.AddCommand(apps.MakeInstallKubernetesDashboard())
install.AddCommand(apps.MakeInstallIstio())
install.AddCommand(apps.MakeInstallCrossplane())
install.AddCommand(apps.MakeInstallMongoDB())
install.AddCommand(apps.MakeInstallRegistry())
install.AddCommand(apps.MakeInstallRegistryIngress())
command.AddCommand(MakeInfo())
return command
}
func getApps() []string {
return []string{"openfaas",
"nginx-ingress",
"cert-manager",
"openfaas-ingress",
"inlets-operator",
"metrics-server",
"chart",
"tiller",
"linkerd",
"cron-connector",
"kafka-connector",
"minio",
"postgresql",
"kubernetes-dashboard",
"istio",
"crosspane",
"mongodb",
"docker-registry",
"docker-registry-ingress",
}
}

156
cmd/apps/certmanager_app.go Normal file
View File

@@ -0,0 +1,156 @@
package apps
import (
"fmt"
"log"
"os"
"path"
"github.com/alexellis/bazaar/pkg"
"github.com/alexellis/bazaar/pkg/config"
"github.com/alexellis/bazaar/pkg/env"
"github.com/alexellis/bazaar/pkg/helm"
"github.com/spf13/cobra"
)
func MakeInstallCertManager() *cobra.Command {
var certManager = &cobra.Command{
Use: "cert-manager",
Short: "Install cert-manager",
Long: "Install cert-manager for obtaining TLS certificates from LetsEncrypt",
Example: "bazaar install cert-manager",
SilenceUsage: true,
}
certManager.Flags().StringP("namespace", "n", "cert-manager", "The namespace to install cert-manager")
certManager.Flags().Bool("update-repo", true, "Update the helm repo")
certManager.Flags().Bool("helm3", true, "Use helm3, if set to false uses helm2")
certManager.RunE = func(command *cobra.Command, args []string) error {
kubeConfigPath := getDefaultKubeconfig()
if command.Flags().Changed("kubeconfig") {
kubeConfigPath, _ = command.Flags().GetString("kubeconfig")
}
fmt.Printf("Using kubeconfig: %s\n", kubeConfigPath)
helm3, _ := command.Flags().GetBool("helm3")
if helm3 {
fmt.Println("Using helm3")
}
namespace, _ := command.Flags().GetString("namespace")
if namespace != "cert-manager" {
return fmt.Errorf(`To override the "cert-manager" namespace, install cert-manager via helm manually`)
}
userPath, err := config.InitUserDir()
if err != nil {
return err
}
clientArch, clientOS := env.GetClientArch()
fmt.Printf("Client: %s, %s\n", clientArch, clientOS)
log.Printf("User dir established as: %s\n", userPath)
os.Setenv("HELM_HOME", path.Join(userPath, ".helm"))
_, err = helm.TryDownloadHelm(userPath, clientArch, clientOS, helm3)
if err != nil {
return err
}
err = addHelmRepo("jetstack", "https://charts.jetstack.io", helm3)
if err != nil {
return err
}
updateRepo, _ := certManager.Flags().GetBool("update-repo")
if updateRepo {
err = updateHelmRepos(helm3)
if err != nil {
return err
}
}
nsRes, nsErr := kubectlTask("create", "namespace", namespace)
if nsErr != nil {
return nsErr
}
if nsRes.ExitCode != 0 {
fmt.Printf("[Warning] unable to create namespace %s, may already exist: %s", namespace, nsRes.Stderr)
}
chartPath := path.Join(os.TempDir(), "charts")
err = fetchChart(chartPath, "jetstack/cert-manager", helm3)
if err != nil {
return err
}
log.Printf("Applying CRD\n")
crdsURL := "https://raw.githubusercontent.com/jetstack/cert-manager/release-0.12/deploy/manifests/00-crds.yaml"
res, err := kubectlTask("apply", "--validate=false", "-f",
crdsURL)
if err != nil {
return err
}
if res.ExitCode > 0 {
return fmt.Errorf("error applying CRD from: %s, error: %s", crdsURL, res.Stderr)
}
outputPath := path.Join(chartPath, "cert-manager/rendered")
overrides := map[string]string{}
if helm3 {
outputPath := path.Join(chartPath, "cert-manager")
err := helm3Upgrade(outputPath, "jetstack/cert-manager", namespace,
"values.yaml",
"v0.12.0",
overrides)
if err != nil {
return err
}
} else {
err = templateChart(chartPath, "cert-manager", namespace, outputPath, "values.yaml", "v0.12.0", nil)
if err != nil {
return err
}
applyRes, applyErr := kubectlTask("apply", "-R", "-f", outputPath)
if applyErr != nil {
return applyErr
}
if applyRes.ExitCode > 0 {
return fmt.Errorf("error applying templated YAML files, error: %s", applyRes.Stderr)
}
}
fmt.Println(certManagerInstallMsg)
return nil
}
return certManager
}
const CertManagerInfoMsg = `# Get started with cert-manager here:
# https://docs.cert-manager.io/en/latest/tutorials/acme/http-validation.html
# Check cert-manager's logs with:
kubectl logs -n cert-manager deploy/cert-manager -f`
const certManagerInstallMsg = `=======================================================================
= cert-manager has been installed. =
=======================================================================` +
"\n\n" + CertManagerInfoMsg + "\n\n" + pkg.ThanksForUsing

154
cmd/apps/chart_app.go Normal file
View File

@@ -0,0 +1,154 @@
package apps
import (
"fmt"
"log"
"os"
"path"
"strings"
"github.com/alexellis/bazaar/pkg"
"github.com/alexellis/bazaar/pkg/config"
"github.com/alexellis/bazaar/pkg/env"
"github.com/alexellis/bazaar/pkg/helm"
"github.com/spf13/cobra"
)
func MakeInstallChart() *cobra.Command {
var chartCmd = &cobra.Command{
Use: "chart",
Short: "Install the specified helm chart",
Long: `Install the specified helm chart without using tiller.
Note: You may need to install a CRD or run other additional steps
before using the generic helm chart installer command.`,
Example: ` bazaar install chart --repo-name stable/nginx-ingress \
--set controller.service.type=NodePort
bazaar app install chart --repo-name inlets/inlets-operator \
--repo-url https://inlets.github.io/inlets-operator/`,
SilenceUsage: true,
}
chartCmd.Flags().StringP("namespace", "n", "default", "The namespace to install the chart")
chartCmd.Flags().String("repo", "", "The chart repo to install from")
chartCmd.Flags().String("values-file", "", "Give the values.yaml file to use from the upstream chart repo")
chartCmd.Flags().String("repo-name", "", "Chart name")
chartCmd.Flags().String("repo-url", "", "Chart repo")
chartCmd.Flags().StringArray("set", []string{}, "Set individual values in the helm chart")
chartCmd.RunE = func(command *cobra.Command, args []string) error {
chartRepoName, _ := command.Flags().GetString("repo-name")
chartRepoURL, _ := command.Flags().GetString("repo-url")
chartName := chartRepoName
if index := strings.Index(chartRepoName, "/"); index > -1 {
chartName = chartRepoName[index+1:]
}
chartPrefix := chartRepoName
if index := strings.Index(chartRepoName, "/"); index > -1 {
chartPrefix = chartRepoName[:index]
}
if len(chartRepoName) == 0 {
return fmt.Errorf("--repo-name required")
}
kubeConfigPath := getDefaultKubeconfig()
if command.Flags().Changed("kubeconfig") {
kubeConfigPath, _ = command.Flags().GetString("kubeconfig")
}
fmt.Printf("Using kubeconfig: %s\n", kubeConfigPath)
namespace, _ := command.Flags().GetString("namespace")
userPath, err := config.InitUserDir()
if err != nil {
return err
}
clientArch, clientOS := env.GetClientArch()
fmt.Printf("Client: %s, %s\n", clientArch, clientOS)
log.Printf("User dir established as: %s\n", userPath)
os.Setenv("HELM_HOME", path.Join(userPath, ".helm"))
_, err = helm.TryDownloadHelm(userPath, clientArch, clientOS, false)
if err != nil {
return err
}
if len(chartRepoURL) > 0 {
err = addHelmRepo(chartPrefix, chartRepoURL, false)
if err != nil {
return err
}
}
err = updateHelmRepos(false)
if err != nil {
return err
}
res, kcErr := kubectlTask("get", "namespace", namespace)
if kcErr != nil {
return err
}
if res.ExitCode != 0 {
err = kubectl("create", "namespace", namespace)
if err != nil {
return err
}
}
chartPath := path.Join(os.TempDir(), "charts")
err = fetchChart(chartPath, chartRepoName, false)
if err != nil {
return err
}
outputPath := path.Join(chartPath, "chart/rendered")
setMap := map[string]string{}
setVals, _ := chartCmd.Flags().GetStringArray("set")
for _, setV := range setVals {
var k string
var v string
if index := strings.Index(setV, "="); index > -1 {
k = setV[:index]
v = setV[index+1:]
setMap[k] = v
}
}
err = templateChart(chartPath, chartName, namespace, outputPath, "values.yaml", "", setMap)
if err != nil {
return err
}
err = kubectl("apply", "--namespace", namespace, "-R", "-f", outputPath)
if err != nil {
return err
}
fmt.Println(
`=======================================================================
chart ` + chartRepoName + ` installed.
=======================================================================
` + pkg.ThanksForUsing)
return nil
}
return chartCmd
}

View File

@@ -0,0 +1,146 @@
package apps
import (
"fmt"
"log"
"os"
"path"
"github.com/alexellis/bazaar/pkg"
"github.com/alexellis/bazaar/pkg/config"
"github.com/alexellis/bazaar/pkg/env"
"github.com/alexellis/bazaar/pkg/helm"
"github.com/spf13/cobra"
)
func MakeInstallCronConnector() *cobra.Command {
var command = &cobra.Command{
Use: "cron-connector",
Short: "Install cron-connector for OpenFaaS",
Long: `Install cron-connector for OpenFaaS`,
Example: ` bazaar app install cron-connector`,
SilenceUsage: true,
}
command.Flags().StringP("namespace", "n", "openfaas", "The namespace used for installation")
command.Flags().Bool("update-repo", true, "Update the helm repo")
command.Flags().StringArray("set", []string{},
"Use custom flags or override existing flags \n(example --set key=value)")
command.RunE = func(command *cobra.Command, args []string) error {
kubeConfigPath := getDefaultKubeconfig()
if command.Flags().Changed("kubeconfig") {
kubeConfigPath, _ = command.Flags().GetString("kubeconfig")
}
updateRepo, _ := command.Flags().GetBool("update-repo")
fmt.Printf("Using kubeconfig: %s\n", kubeConfigPath)
userPath, err := config.InitUserDir()
if err != nil {
return err
}
namespace, _ := command.Flags().GetString("namespace")
if namespace != "openfaas" {
return fmt.Errorf(`to override the "openfaas", install via tiller`)
}
clientArch, clientOS := env.GetClientArch()
fmt.Printf("Client: %s, %s\n", clientArch, clientOS)
log.Printf("User dir established as: %s\n", userPath)
os.Setenv("HELM_HOME", path.Join(userPath, ".helm"))
_, err = helm.TryDownloadHelm(userPath, clientArch, clientOS, false)
if err != nil {
return err
}
err = addHelmRepo("openfaas", "https://openfaas.github.io/faas-netes/", false)
if err != nil {
return err
}
if updateRepo {
err = updateHelmRepos(false)
if err != nil {
return err
}
}
chartPath := path.Join(os.TempDir(), "charts")
err = fetchChart(chartPath, "openfaas/cron-connector", false)
if err != nil {
return err
}
overrides := map[string]string{}
customFlags, err := command.Flags().GetStringArray("set")
if err != nil {
return fmt.Errorf("error with --set usage: %s", err)
}
if err := mergeFlags(overrides, customFlags); err != nil {
return err
}
arch := getNodeArchitecture()
fmt.Printf("Node architecture: %q\n", arch)
fmt.Println("Chart path: ", chartPath)
outputPath := path.Join(chartPath, "cron-connector/rendered")
ns := namespace
err = templateChart(chartPath,
"cron-connector",
ns,
outputPath,
"values.yaml",
"",
overrides)
if err != nil {
return err
}
err = kubectl("apply", "-R", "-f", outputPath)
if err != nil {
return err
}
fmt.Println(cronConnectorInstallMsg)
return nil
}
return command
}
const CronConnectorInfoMsg = `# Example usage to trigger nodeinfo every 5 minutes:
faas-cli store deploy nodeinfo \
--annotation schedule="*/5 * * * *" \
--annotation topic=cron-function
# View the connector's logs:
kubectl logs deploy/cron-connector -n openfaas -f
# Find out more on the project homepage:
# https://github.com/openfaas-incubator/cron-connector/`
const cronConnectorInstallMsg = `=======================================================================
= cron-connector has been installed. =
=======================================================================` +
"\n\n" + CronConnectorInfoMsg + "\n\n" + pkg.ThanksForUsing

145
cmd/apps/crossplane_app.go Normal file
View File

@@ -0,0 +1,145 @@
package apps
import (
"fmt"
"log"
"os"
"path"
"strings"
"github.com/alexellis/bazaar/pkg"
"github.com/alexellis/bazaar/pkg/config"
"github.com/alexellis/bazaar/pkg/env"
"github.com/alexellis/bazaar/pkg/helm"
"github.com/spf13/cobra"
)
func MakeInstallCrossplane() *cobra.Command {
var crossplane = &cobra.Command{
Use: "crossplane",
Short: "Install Crossplane",
Long: `Install Crossplane to deploy managed services across cloud providers and
schedule workloads to any Kubernetes cluster`,
Example: ` bazaar app install crossplane`,
SilenceUsage: true,
}
crossplane.Flags().StringP("namespace", "n", "crossplane-system", "The namespace used for installation")
crossplane.Flags().Bool("update-repo", true, "Update the helm repo")
crossplane.Flags().Bool("helm3", true, "Use helm3, if set to false uses helm2")
crossplane.RunE = func(command *cobra.Command, args []string) error {
kubeConfigPath := getDefaultKubeconfig()
if command.Flags().Changed("kubeconfig") {
kubeConfigPath, _ = command.Flags().GetString("kubeconfig")
}
fmt.Printf("Using kubeconfig: %s\n", kubeConfigPath)
helm3, _ := command.Flags().GetBool("helm3")
if helm3 {
fmt.Println("Using helm3")
}
namespace, _ := command.Flags().GetString("namespace")
if namespace != "crossplane-system" {
return fmt.Errorf(`to override the namespace, install crossplane via helm manually`)
}
arch := getNodeArchitecture()
if !strings.Contains(arch, "64") {
return fmt.Errorf(`crossplane is currently only supported on 64-bit architectures`)
}
fmt.Printf("Node architecture: %q\n", arch)
userPath, err := config.InitUserDir()
if err != nil {
return err
}
clientArch, clientOS := env.GetClientArch()
fmt.Printf("Client: %q, %q\n", clientArch, clientOS)
log.Printf("User dir established as: %s\n", userPath)
os.Setenv("HELM_HOME", path.Join(userPath, ".helm"))
_, err = helm.TryDownloadHelm(userPath, clientArch, clientOS, helm3)
if err != nil {
return err
}
err = addHelmRepo("crossplane-alpha", "https://charts.crossplane.io/alpha", helm3)
if err != nil {
return err
}
updateRepo, _ := crossplane.Flags().GetBool("update-repo")
if updateRepo {
err = updateHelmRepos(helm3)
if err != nil {
return err
}
}
chartPath := path.Join(os.TempDir(), "charts")
err = fetchChart(chartPath, "crossplane-alpha/crossplane", helm3)
if err != nil {
return err
}
if helm3 {
outputPath := path.Join(chartPath, "crossplane")
_, nsErr := kubectlTask("create", "namespace", "crossplane-system")
if nsErr != nil && !strings.Contains(nsErr.Error(), "AlreadyExists") {
return nsErr
}
err := helm3Upgrade(outputPath, "crossplane-alpha/crossplane",
namespace, "values.yaml", "", map[string]string{})
if err != nil {
return err
}
} else {
outputPath := path.Join(chartPath, "crossplane-alpha/crossplane")
err = templateChart(chartPath, "crossplane", namespace, outputPath, "values.yaml", "", map[string]string{})
if err != nil {
return err
}
applyRes, applyErr := kubectlTask("apply", "-R", "-f", outputPath)
if applyErr != nil {
return applyErr
}
if applyRes.ExitCode > 0 {
return fmt.Errorf("error applying templated YAML files, error: %s", applyRes.Stderr)
}
}
fmt.Println(crossplaneInstallMsg)
return nil
}
return crossplane
}
const CrossplanInfoMsg = `# Get started by installing a stack for your favorite provider:
* stack-gcp: https://crossplane.io/docs/master/install-crossplane.html#gcp-stack
* stack-aws: https://crossplane.io/docs/master/install-crossplane.html#aws-stack
* stack-azure: https://crossplane.io/docs/master/install-crossplane.html#azure-stack
Learn more about Crossplane: https://crossplaneio.github.io/docs/`
const crossplaneInstallMsg = `=======================================================================
= Crossplane has been installed. =
=======================================================================` +
"\n\n" + CrossplanInfoMsg + "\n\n" + pkg.ThanksForUsing

View File

@@ -0,0 +1,269 @@
package apps
import (
"fmt"
"log"
"os"
"path"
"strings"
"github.com/alexellis/bazaar/pkg"
"github.com/alexellis/bazaar/pkg/config"
"github.com/alexellis/bazaar/pkg/env"
"github.com/alexellis/bazaar/pkg/helm"
"github.com/spf13/cobra"
)
func MakeInstallInletsOperator() *cobra.Command {
var inletsOperator = &cobra.Command{
Use: "inlets-operator",
Short: "Install inlets-operator",
Long: `Install inlets-operator to get public IPs for your cluster`,
Example: ` bazaar app install inlets-operator --namespace default`,
SilenceUsage: true,
}
inletsOperator.Flags().StringP("namespace", "n", "default", "The namespace used for installation")
inletsOperator.Flags().StringP("license", "l", "", "The license key if using inlets-pro")
inletsOperator.Flags().StringP("provider", "p", "digitalocean", "Your infrastructure provider - 'packet', 'digitalocean', 'scaleway', 'gce' or 'ec2'")
inletsOperator.Flags().StringP("zone", "z", "us-central1-a", "The zone to provision the exit node (Used by GCE")
inletsOperator.Flags().String("project-id", "", "Project ID to be used (for GCE and Packet)")
inletsOperator.Flags().StringP("region", "r", "lon1", "The default region to provision the exit node (DigitalOcean, Packet and Scaleway")
inletsOperator.Flags().String("organization-id", "", "The organization id (Scaleway")
inletsOperator.Flags().StringP("token-file", "t", "", "Text file containing token or a service account JSON file")
inletsOperator.Flags().Bool("update-repo", true, "Update the helm repo")
inletsOperator.Flags().String("pro-client-image", "", "Docker image for inlets-pro's client")
inletsOperator.Flags().Bool("helm3", true, "Use helm3, if set to false uses helm2")
inletsOperator.Flags().StringArray("set", []string{}, "Use custom flags or override existing flags \n(example --set=image=org/repo:tag)")
inletsOperator.RunE = func(command *cobra.Command, args []string) error {
kubeConfigPath := getDefaultKubeconfig()
if command.Flags().Changed("kubeconfig") {
kubeConfigPath, _ = command.Flags().GetString("kubeconfig")
}
fmt.Printf("Using kubeconfig: %s\n", kubeConfigPath)
helm3, _ := command.Flags().GetBool("helm3")
if helm3 {
fmt.Println("Using helm3")
}
namespace, _ := command.Flags().GetString("namespace")
if namespace != "default" {
return fmt.Errorf(`to override the namespace, install inlets-operator via helm manually`)
}
arch := getNodeArchitecture()
fmt.Printf("Node architecture: %q\n", arch)
userPath, err := config.InitUserDir()
if err != nil {
return err
}
clientArch, clientOS := env.GetClientArch()
fmt.Printf("Client: %q, %q\n", clientArch, clientOS)
log.Printf("User dir established as: %s\n", userPath)
os.Setenv("HELM_HOME", path.Join(userPath, ".helm"))
_, err = helm.TryDownloadHelm(userPath, clientArch, clientOS, helm3)
if err != nil {
return err
}
err = addHelmRepo("inlets", "https://inlets.github.io/inlets-operator/", helm3)
if err != nil {
return err
}
updateRepo, _ := inletsOperator.Flags().GetBool("update-repo")
if updateRepo {
err = updateHelmRepos(helm3)
if err != nil {
return err
}
}
chartPath := path.Join(os.TempDir(), "charts")
err = fetchChart(chartPath, "inlets/inlets-operator", helm3)
if err != nil {
return err
}
overrides, err := getInletsOperatorOverrides(command)
if err != nil {
return err
}
_, err = kubectlTask("apply", "-f", "https://raw.githubusercontent.com/inlets/inlets-operator/master/artifacts/crd.yaml")
if err != nil {
return err
}
secretFileName, _ := command.Flags().GetString("token-file")
if len(secretFileName) == 0 {
return fmt.Errorf(`--token-file is a required field for your cloud API token or service account JSON file`)
}
res, err := kubectlTask("create", "secret", "generic",
"inlets-access-key",
"--from-file", "inlets-access-key="+secretFileName)
if len(res.Stderr) > 0 && strings.Contains(res.Stderr, "AlreadyExists") {
fmt.Println("[Warning] secret inlets-access-key already exists and will be used.")
} else if len(res.Stderr) > 0 {
return fmt.Errorf("error from kubectl\n%q", res.Stderr)
} else if err != nil {
return err
}
customFlags, _ := command.Flags().GetStringArray("set")
if err := mergeFlags(overrides, customFlags); err != nil {
return err
}
region, _ := command.Flags().GetString("region")
overrides["region"] = region
if val, _ := command.Flags().GetString("license"); len(val) > 0 {
overrides["inletsProLicense"] = val
}
if val, _ := command.Flags().GetString("pro-client-image"); len(val) > 0 {
overrides["proClientImage"] = val
}
if helm3 {
outputPath := path.Join(chartPath, "inlets-operator")
err := helm3Upgrade(outputPath, "inlets/inlets-operator",
namespace, "values.yaml", "", overrides)
if err != nil {
return err
}
} else {
outputPath := path.Join(chartPath, "inlets-operator/rendered")
err = templateChart(chartPath, "inlets-operator", namespace, outputPath, "values.yaml", "", overrides)
if err != nil {
return err
}
applyRes, applyErr := kubectlTask("apply", "-R", "-f", outputPath)
if applyErr != nil {
return applyErr
}
if applyRes.ExitCode > 0 {
return fmt.Errorf("error applying templated YAML files, error: %s", applyRes.Stderr)
}
}
fmt.Println(inletsOperatorPostInstallMsg)
return nil
}
return inletsOperator
}
func getInletsOperatorOverrides(command *cobra.Command) (map[string]string, error) {
overrides := map[string]string{}
provider, _ := command.Flags().GetString("provider")
overrides["provider"] = strings.ToLower(provider)
providers := []string{
"digitalocean", "packet", "ec2", "scaleway", "gce",
}
found := false
for _, p := range providers {
if p == provider {
found = true
}
}
if !found {
return overrides, fmt.Errorf("provider: %s not supported at this time", provider)
}
if provider == "gce" {
gceProjectID, err := command.Flags().GetString("project-id")
if err != nil {
return overrides, err
}
overrides["gceProjectId"] = gceProjectID
zone, err := command.Flags().GetString("zone")
if err != nil {
return overrides, err
}
overrides["zone"] = strings.ToLower(zone)
if len(zone) == 0 {
return overrides, fmt.Errorf("zone is required for provider %s", provider)
}
if len(gceProjectID) == 0 {
return overrides, fmt.Errorf("project-id is required for provider %s", provider)
}
} else if provider == "packet" {
packetProjectID, err := command.Flags().GetString("project-id")
if err != nil {
return overrides, err
}
overrides["packetProjectId"] = packetProjectID
if len(packetProjectID) == 0 {
return overrides, fmt.Errorf("project-id is required for provider %s", provider)
}
} else if provider == "scaleway" {
orgID, err := command.Flags().GetString("organization-id")
if err != nil {
return overrides, err
}
overrides["organization-id"] = orgID
if len(orgID) == 0 {
return overrides, fmt.Errorf("organization-id is required for provider %s", provider)
}
}
return overrides, nil
}
const InletsOperatorInfoMsg = `# The default configuration is for DigitalOcean and your secret is
# stored as "inlets-access-key" in the "default" namespace.
# To get your first Public IP run the following:
kubectl run nginx-1 --image=nginx --port=80 --restart=Always
kubectl expose deployment nginx-1 --port=80 --type=LoadBalancer
# Find your IP in the "EXTERNAL-IP" field, watch for "<pending>" to
# change to an IP
kubectl get svc -w
# When you're done, remove the tunnel by deleting the service
kubectl delete svc/nginx-1
# Check the logs
kubectl logs deploy/inlets-operator -f
# Find out more at:
# https://github.com/inlets/inlets-operator`
const inletsOperatorPostInstallMsg = `=======================================================================
= inlets-operator has been installed. =
=======================================================================` +
"\n\n" + InletsOperatorInfoMsg + "\n\n" + pkg.ThanksForUsing

216
cmd/apps/istio_app.go Normal file
View File

@@ -0,0 +1,216 @@
package apps
import (
"fmt"
"io/ioutil"
"log"
"os"
"path"
"github.com/alexellis/bazaar/pkg"
"github.com/alexellis/bazaar/pkg/config"
"github.com/alexellis/bazaar/pkg/env"
"github.com/alexellis/bazaar/pkg/helm"
"github.com/spf13/cobra"
)
var IntelArch = "amd64"
func MakeInstallIstio() *cobra.Command {
var istio = &cobra.Command{
Use: "istio",
Short: "Install istio",
Long: `Install istio`,
Example: ` bazaar app install istio --loadbalancer`,
SilenceUsage: true,
}
istio.Flags().Bool("update-repo", true, "Update the helm repo")
istio.Flags().String("namespace", "istio-system", "Namespace for the app")
istio.Flags().Bool("init", true, "Run the Istio init to add CRDs etc")
istio.Flags().StringArray("set", []string{},
"Use custom flags or override existing flags \n(example --set=prometheus.enabled=false)")
istio.RunE = func(command *cobra.Command, args []string) error {
kubeConfigPath := getDefaultKubeconfig()
if command.Flags().Changed("kubeconfig") {
kubeConfigPath, _ = command.Flags().GetString("kubeconfig")
}
fmt.Printf("Using kubeconfig: %s\n", kubeConfigPath)
namespace, _ := command.Flags().GetString("namespace")
if namespace != "istio-system" {
return fmt.Errorf(`to override the "istio-system" namespace, install Istio via helm manually`)
}
arch := getNodeArchitecture()
fmt.Printf("Node architecture: %q\n", arch)
if arch != IntelArch {
return fmt.Errorf(`only Intel, i.e. PC architecture is supported for this app`)
}
userPath, err := config.InitUserDir()
if err != nil {
return err
}
clientArch, clientOS := env.GetClientArch()
fmt.Printf("Client: %q, %q\n", clientArch, clientOS)
log.Printf("User dir established as: %s\n", userPath)
os.Setenv("HELM_HOME", path.Join(userPath, ".helm"))
helm3 := true
_, err = helm.TryDownloadHelm(userPath, clientArch, clientOS, helm3)
if err != nil {
return err
}
istioVer := "1.3.3"
err = addHelmRepo("istio", "https://storage.googleapis.com/istio-release/releases/"+istioVer+"/charts", helm3)
if err != nil {
return fmt.Errorf("unable to add repo %s", err)
}
updateRepo, _ := istio.Flags().GetBool("update-repo")
if updateRepo {
err = updateHelmRepos(helm3)
if err != nil {
return fmt.Errorf("unable to update repos %s", err)
}
}
_, err = kubectlTask("create", "ns", "istio-system")
if err != nil {
return fmt.Errorf("unable to create namespace %s", err)
}
chartPath := path.Join(os.TempDir(), "charts")
err = fetchChart(chartPath, "istio/istio", helm3)
if err != nil {
return fmt.Errorf("unable fetch chart %s", err)
}
overrides := map[string]string{}
valuesFile, writeErr := writeIstioValues()
if writeErr != nil {
return writeErr
}
outputPath := path.Join(chartPath, "istio")
if initIstio, _ := command.Flags().GetBool("init"); initIstio {
err = helm3Upgrade(outputPath, "istio/istio-init", namespace, "", "", overrides)
if err != nil {
return fmt.Errorf("unable to istio-init install chart with helm %s", err)
}
}
customFlags, customFlagErr := command.Flags().GetStringArray("set")
if customFlagErr != nil {
return fmt.Errorf("error with --set usage: %s", customFlagErr)
}
if err := mergeFlags(overrides, customFlags); err != nil {
return err
}
err = helm3Upgrade(outputPath, "istio/istio", namespace, valuesFile, "", overrides)
if err != nil {
return fmt.Errorf("unable to istio install chart with helm %s", err)
}
fmt.Println(istioPostInstallMsg)
return nil
}
return istio
}
const IstioInfoMsg = `# Find out more at:
# https://github.com/istio/`
const istioPostInstallMsg = `=======================================================================
= Istio has been installed. =
=======================================================================` +
"\n\n" + IstioInfoMsg + "\n\n" + pkg.ThanksForUsing
func writeIstioValues() (string, error) {
out := `#
# Minimal Istio Configuration taken from https://github.com/weaveworks/flagger
# pilot configuration
pilot:
enabled: true
sidecar: true
resources:
requests:
cpu: 10m
memory: 128Mi
gateways:
enabled: true
istio-ingressgateway:
autoscaleMax: 1
# sidecar-injector webhook configuration
sidecarInjectorWebhook:
enabled: true
# galley configuration
galley:
enabled: false
# mixer configuration
mixer:
policy:
enabled: false
telemetry:
enabled: true
replicaCount: 1
autoscaleEnabled: false
resources:
requests:
cpu: 10m
memory: 128Mi
# addon prometheus configuration
prometheus:
enabled: true
scrapeInterval: 5s
# addon jaeger tracing configuration
tracing:
enabled: false
# Common settings.
global:
proxy:
# Resources for the sidecar.
resources:
requests:
cpu: 10m
memory: 64Mi
limits:
cpu: 1000m
memory: 256Mi
useMCP: false`
writeTo := path.Join(os.TempDir(), "istio-values.yaml")
return writeTo, ioutil.WriteFile(writeTo, []byte(out), 0600)
}

View File

@@ -0,0 +1,157 @@
package apps
import (
"fmt"
"log"
"os"
"path"
"github.com/alexellis/bazaar/pkg"
"github.com/alexellis/bazaar/pkg/config"
"github.com/alexellis/bazaar/pkg/env"
"github.com/alexellis/bazaar/pkg/helm"
"github.com/spf13/cobra"
)
func MakeInstallKafkaConnector() *cobra.Command {
var command = &cobra.Command{
Use: "kafka-connector",
Short: "Install kafka-connector for OpenFaaS",
Long: `Install kafka-connector for OpenFaaS`,
Example: ` bazaar app install kafka-connector`,
SilenceUsage: true,
}
command.Flags().StringP("namespace", "n", "openfaas", "The namespace used for installation")
command.Flags().Bool("update-repo", true, "Update the helm repo")
command.Flags().StringP("topics", "t", "faas-request", "The topics for the connector to bind to")
command.Flags().String("broker-host", "kafka", "The host for the Kafka broker")
command.Flags().StringArray("set", []string{},
"Use custom flags or override existing flags \n(example --set key=value)")
command.RunE = func(command *cobra.Command, args []string) error {
kubeConfigPath := getDefaultKubeconfig()
if command.Flags().Changed("kubeconfig") {
kubeConfigPath, _ = command.Flags().GetString("kubeconfig")
}
updateRepo, _ := command.Flags().GetBool("update-repo")
fmt.Printf("Using kubeconfig: %s\n", kubeConfigPath)
userPath, err := config.InitUserDir()
if err != nil {
return err
}
namespace, _ := command.Flags().GetString("namespace")
if namespace != "openfaas" {
return fmt.Errorf(`to override the "openfaas", install via tiller`)
}
clientArch, clientOS := env.GetClientArch()
fmt.Printf("Client: %s, %s\n", clientArch, clientOS)
log.Printf("User dir established as: %s\n", userPath)
os.Setenv("HELM_HOME", path.Join(userPath, ".helm"))
_, err = helm.TryDownloadHelm(userPath, clientArch, clientOS, false)
if err != nil {
return err
}
err = addHelmRepo("openfaas", "https://openfaas.github.io/faas-netes/", false)
if err != nil {
return err
}
if updateRepo {
err = updateHelmRepos(false)
if err != nil {
return err
}
}
chartPath := path.Join(os.TempDir(), "charts")
err = fetchChart(chartPath, "openfaas/kafka-connector", false)
if err != nil {
return err
}
topicsVal, err := command.Flags().GetString("topics")
if err != nil {
return err
}
brokerHostVal, err := command.Flags().GetString("broker-host")
if err != nil {
return err
}
overrides := map[string]string{
"topics": topicsVal,
"broker_host": brokerHostVal,
}
customFlags, err := command.Flags().GetStringArray("set")
if err != nil {
return fmt.Errorf("error with --set usage: %s", err)
}
if err := mergeFlags(overrides, customFlags); err != nil {
return err
}
arch := getNodeArchitecture()
fmt.Printf("Node architecture: %q\n", arch)
if arch != IntelArch {
return fmt.Errorf(`only Intel, i.e. PC architecture is supported for this app`)
}
fmt.Println("Chart path: ", chartPath)
outputPath := path.Join(chartPath, "kafka-connector/rendered")
ns := namespace
err = templateChart(chartPath,
"kafka-connector",
ns,
outputPath,
"values.yaml",
"",
overrides)
if err != nil {
return err
}
err = kubectl("apply", "-R", "-f", outputPath)
if err != nil {
return err
}
fmt.Println(kafkaConnectorInstallMsg)
return nil
}
return command
}
const KafkaConnectorInfoMsg = `# View the connector's logs:
kubectl logs deploy/kafka-connector -n openfaas -f
# Find out more on the project homepage:
# https://github.com/openfaas-incubator/kafka-connector/`
const kafkaConnectorInstallMsg = `=======================================================================
= kafka-connector has been installed. =
=======================================================================` +
"\n\n" + KafkaConnectorInfoMsg + "\n\n" + pkg.ThanksForUsing

View File

@@ -0,0 +1,84 @@
package apps
import (
"fmt"
"github.com/alexellis/bazaar/pkg"
"github.com/spf13/cobra"
)
func MakeInstallKubernetesDashboard() *cobra.Command {
var kubeDashboard = &cobra.Command{
Use: "kubernetes-dashboard",
Short: "Install kubernetes-dashboard",
Long: `Install kubernetes-dashboard`,
Example: ` bazaar app install kubernetes-dashboard`,
SilenceUsage: true,
}
kubeDashboard.RunE = func(command *cobra.Command, args []string) error {
kubeConfigPath := getDefaultKubeconfig()
if command.Flags().Changed("kubeconfig") {
kubeConfigPath, _ = command.Flags().GetString("kubeconfig")
}
fmt.Printf("Using kubeconfig: %s\n", kubeConfigPath)
arch := getNodeArchitecture()
fmt.Printf("Node architecture: %q\n", arch)
_, err := kubectlTask("apply", "-f",
"https://raw.githubusercontent.com/kubernetes/dashboard/v2.0.0-rc2/aio/deploy/recommended.yaml")
if err != nil {
return err
}
fmt.Println(KubernetesDashboardInfoMsg)
return nil
}
return kubeDashboard
}
const KubernetesDashboardInfoMsg = `# To create the Service Account and the ClusterRoleBinding
# @See https://github.com/kubernetes/dashboard/blob/master/docs/user/access-control/creating-sample-user.md#creating-sample-user
cat <<EOF | kubectl apply -f -
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: admin-user
namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: admin-user
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: admin-user
namespace: kubernetes-dashboard
---
EOF
#To forward the dashboard to your local machine
kubectl proxy
#To get your Token for logging in
kubectl -n kubernetes-dashboard describe secret $(kubectl -n kubernetes-dashboard get secret | grep admin-user-token | awk '{print $1}')
# Once Proxying you can navigate to the below
http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/#/login`
const KubernetesDashboardInstallMsg = `=======================================================================
= Kubernetes Dashboard has been installed. =
=======================================================================` +
"\n\n" + KubernetesDashboardInfoMsg + "\n\n" + pkg.ThanksForUsing

249
cmd/apps/kubernetes_exec.go Normal file
View File

@@ -0,0 +1,249 @@
package apps
import (
"fmt"
"log"
"os"
"path"
"strings"
"github.com/alexellis/bazaar/pkg/env"
execute "github.com/alexellis/go-execute/pkg/v1"
)
func fetchChart(path, chart string, helm3 bool) error {
subdir := ""
if helm3 {
subdir = "helm3"
}
mkErr := os.MkdirAll(path, 0700)
if mkErr != nil {
return mkErr
}
task := execute.ExecTask{
Command: fmt.Sprintf("%s fetch %s --untar --untardir %s", env.LocalBinary("helm", subdir), chart, path),
Env: os.Environ(),
StreamStdio: true,
}
res, err := task.Execute()
if err != nil {
return err
}
if res.ExitCode != 0 {
return fmt.Errorf("exit code %d", res.ExitCode)
}
return nil
}
func getNodeArchitecture() string {
res, _ := kubectlTask("get", "nodes", `--output`, `jsonpath={range $.items[0]}{.status.nodeInfo.architecture}`)
arch := strings.TrimSpace(string(res.Stdout))
return arch
}
func helm3Upgrade(basePath, chart, namespace, values, version string, overrides map[string]string) error {
chartName := chart
if index := strings.Index(chartName, "/"); index > -1 {
chartName = chartName[index+1:]
}
chartRoot := basePath
args := []string{"upgrade", "--install", chartName, chart, "--namespace", namespace}
if len(version) > 0 {
args = append(args, "--version", version)
}
fmt.Println("VALUES", values)
if len(values) > 0 {
args = append(args, "--values")
if !strings.HasPrefix(values, "/") {
args = append(args, path.Join(chartRoot, values))
} else {
args = append(args, values)
}
}
for k, v := range overrides {
args = append(args, "--set")
args = append(args, fmt.Sprintf("%s=%s", k, v))
}
task := execute.ExecTask{
Command: env.LocalBinary("helm", "helm3"),
Args: args,
Env: os.Environ(),
Cwd: basePath,
StreamStdio: true,
}
fmt.Printf("Command: %s %s\n", task.Command, task.Args)
res, err := task.Execute()
if err != nil {
return err
}
if res.ExitCode != 0 {
return fmt.Errorf("exit code %d, stderr: %s", res.ExitCode, res.Stderr)
}
if len(res.Stderr) > 0 {
log.Printf("stderr: %s\n", res.Stderr)
}
return nil
}
func templateChart(basePath, chart, namespace, outputPath, values, version string, overrides map[string]string) error {
rmErr := os.RemoveAll(outputPath)
if rmErr != nil {
log.Printf("Error cleaning up: %s, %s\n", outputPath, rmErr.Error())
}
mkErr := os.MkdirAll(outputPath, 0700)
if mkErr != nil {
return mkErr
}
overridesStr := ""
for k, v := range overrides {
overridesStr += fmt.Sprintf(" --set %s=%s", k, v)
}
chartRoot := path.Join(basePath, chart)
valuesStr := ""
if len(values) > 0 {
valuesStr = "--values " + path.Join(chartRoot, values)
}
versionStr := ""
if len(version) > 0 {
versionStr = "--version " + version
}
task := execute.ExecTask{
Command: fmt.Sprintf("%s template %s --name %s --namespace %s --output-dir %s %s %s %s",
env.LocalBinary("helm", ""), chart, chart, namespace, outputPath, valuesStr, overridesStr, versionStr),
Env: os.Environ(),
Cwd: basePath,
StreamStdio: true,
}
res, err := task.Execute()
if err != nil {
return err
}
if res.ExitCode != 0 {
return fmt.Errorf("exit code %d, stderr: %s", res.ExitCode, res.Stderr)
}
if len(res.Stderr) > 0 {
log.Printf("stderr: %s\n", res.Stderr)
}
return nil
}
func addHelmRepo(name, url string, helm3 bool) error {
subdir := ""
if helm3 {
subdir = "helm3"
}
task := execute.ExecTask{
Command: fmt.Sprintf("%s repo add %s %s", env.LocalBinary("helm", subdir), name, url),
Env: os.Environ(),
StreamStdio: true,
}
res, err := task.Execute()
if err != nil {
return err
}
if res.ExitCode != 0 {
return fmt.Errorf("exit code %d", res.ExitCode)
}
return nil
}
func updateHelmRepos(helm3 bool) error {
subdir := ""
if helm3 {
subdir = "helm3"
}
task := execute.ExecTask{
Command: fmt.Sprintf("%s repo update", env.LocalBinary("helm", subdir)),
Env: os.Environ(),
StreamStdio: true,
}
res, err := task.Execute()
if err != nil {
return err
}
if res.ExitCode != 0 {
return fmt.Errorf("exit code %d", res.ExitCode)
}
return nil
}
func kubectlTask(parts ...string) (execute.ExecResult, error) {
task := execute.ExecTask{
Command: "kubectl",
Args: parts,
StreamStdio: false,
}
res, err := task.Execute()
return res, err
}
func kubectl(parts ...string) error {
task := execute.ExecTask{
Command: "kubectl",
Args: parts,
StreamStdio: true,
}
res, err := task.Execute()
if err != nil {
return err
}
if res.ExitCode != 0 {
return fmt.Errorf("kubectl exit code %d, stderr: %s",
res.ExitCode,
res.Stderr)
}
return nil
}
func getDefaultKubeconfig() string {
kubeConfigPath := path.Join(os.Getenv("HOME"), ".kube/config")
if val, ok := os.LookupEnv("KUBECONFIG"); ok {
kubeConfigPath = val
}
return kubeConfigPath
}

196
cmd/apps/linkerd_app.go Normal file
View File

@@ -0,0 +1,196 @@
package apps
import (
"bufio"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"path"
"strings"
"github.com/alexellis/bazaar/pkg"
"github.com/alexellis/bazaar/pkg/config"
"github.com/alexellis/bazaar/pkg/env"
execute "github.com/alexellis/go-execute/pkg/v1"
"github.com/spf13/cobra"
)
var linkerdVersion = "stable-2.6.1"
func MakeInstallLinkerd() *cobra.Command {
var linkerd = &cobra.Command{
Use: "linkerd",
Short: "Install linkerd",
Long: `Install linkerd`,
Example: ` bazaar app install linkerd`,
SilenceUsage: true,
}
linkerd.RunE = func(command *cobra.Command, args []string) error {
kubeConfigPath := getDefaultKubeconfig()
if command.Flags().Changed("kubeconfig") {
kubeConfigPath, _ = command.Flags().GetString("kubeconfig")
}
fmt.Printf("Using kubeconfig: %s\n", kubeConfigPath)
arch := getNodeArchitecture()
fmt.Printf("Node architecture: %q\n", arch)
if arch != IntelArch {
return fmt.Errorf(`only Intel, i.e. PC architecture is supported for this app`)
}
userPath, err := getUserPath()
if err != nil {
return err
}
_, clientOS := env.GetClientArch()
fmt.Printf("Client: %q\n", clientOS)
log.Printf("User dir established as: %s\n", userPath)
err = downloadLinkerd(userPath, clientOS)
if err != nil {
return err
}
fmt.Println("Running linkerd check, this may take a few moments.")
_, err = linkerdCli("check", "--pre")
if err != nil {
return err
}
res, err := linkerdCli("install")
if err != nil {
return err
}
file, err := ioutil.TempFile("", "linkerd")
w := bufio.NewWriter(file)
_, err = w.WriteString(res.Stdout)
if err != nil {
return err
}
w.Flush()
err = kubectl("apply", "-R", "-f", file.Name())
if err != nil {
return err
}
defer os.Remove(file.Name())
_, err = linkerdCli("check")
if err != nil {
return err
}
fmt.Println(`=======================================================================
= Linkerd has been installed. =
=======================================================================
# Get the linkerd-cli
curl -sL https://run.linkerd.io/install | sh
# Find out more at:
# https://linkerd.io
# To use the Linkerd CLI set this path:
export PATH=$PATH:` + path.Join(userPath, "bin/") + `
linkerd --help
` + pkg.ThanksForUsing)
return nil
}
return linkerd
}
func getLinkerdURL(os, version string) string {
osSuffix := strings.ToLower(os)
return fmt.Sprintf("https://github.com/linkerd/linkerd2/releases/download/%s/linkerd2-cli-%s-%s", version, version, osSuffix)
}
func downloadLinkerd(userPath, clientOS string) error {
filePath := path.Join(path.Join(userPath, "bin"), "linkerd")
if _, statErr := os.Stat(filePath); statErr != nil {
linkerdURL := getLinkerdURL(clientOS, linkerdVersion)
fmt.Println(linkerdURL)
parsedURL, _ := url.Parse(linkerdURL)
res, err := http.DefaultClient.Get(parsedURL.String())
if err != nil {
return err
}
defer res.Body.Close()
out, err := os.Create(filePath)
if err != nil {
return err
}
defer out.Close()
// Write the body to file
_, err = io.Copy(out, res.Body)
err = os.Chmod(filePath, 0755)
if err != nil {
return err
}
}
return nil
}
func linkerdCli(parts ...string) (execute.ExecResult, error) {
task := execute.ExecTask{
Command: fmt.Sprintf("%s", env.LocalBinary("linkerd", "")),
Args: parts,
Env: os.Environ(),
StreamStdio: true,
}
res, err := task.Execute()
if err != nil {
return res, err
}
if res.ExitCode != 0 {
return res, fmt.Errorf("exit code %d, stderr: %s", res.ExitCode, res.Stderr)
}
return res, nil
}
func getUserPath() (string, error) {
userPath, err := config.InitUserDir()
return userPath, err
}
func getExportPath() string {
userPath, _ := getUserPath()
return path.Join(userPath, "bin/")
}
var LinkerdInfoMsg = `# Get the linkerd-cli
curl -sL https://run.linkerd.io/install | sh
# Find out more at:
# https://linkerd.io
# To use the Linkerd CLI set this path:
export PATH=$PATH:` + getExportPath() + `
linkerd --help`
var linkerdInstallMsg = `=======================================================================
= Linkerd has been installed. =
=======================================================================` +
"\n\n" + LinkerdInfoMsg + "\n\n" + pkg.ThanksForUsing

View File

@@ -0,0 +1,144 @@
package apps
import (
"fmt"
"log"
"os"
"path"
"github.com/alexellis/bazaar/pkg"
"github.com/alexellis/bazaar/pkg/config"
"github.com/alexellis/bazaar/pkg/env"
"github.com/alexellis/bazaar/pkg/helm"
"github.com/spf13/cobra"
)
func MakeInstallMetricsServer() *cobra.Command {
var metricsServer = &cobra.Command{
Use: "metrics-server",
Short: "Install metrics-server",
Long: `Install metrics-server to provide metrics on nodes and Pods in your cluster.`,
Example: ` bazaar app install metrics-server --namespace kube-system --helm3`,
SilenceUsage: true,
}
metricsServer.Flags().StringP("namespace", "n", "kube-system", "The namespace used for installation")
metricsServer.Flags().Bool("helm3", true, "Use helm3, if set to false uses helm2")
metricsServer.RunE = func(command *cobra.Command, args []string) error {
kubeConfigPath := getDefaultKubeconfig()
if command.Flags().Changed("kubeconfig") {
kubeConfigPath, _ = command.Flags().GetString("kubeconfig")
}
fmt.Printf("Using kubeconfig: %s\n", kubeConfigPath)
userPath, err := config.InitUserDir()
if err != nil {
return err
}
namespace, _ := command.Flags().GetString("namespace")
if namespace != "kube-system" {
return fmt.Errorf(`to override the "kube-system", install via tiller`)
}
helm3, _ := command.Flags().GetBool("helm3")
if helm3 {
fmt.Println("Using helm3")
}
clientArch, clientOS := env.GetClientArch()
fmt.Printf("Client: %q, %q\n", clientArch, clientOS)
log.Printf("User dir established as: %s\n", userPath)
os.Setenv("HELM_HOME", path.Join(userPath, ".helm"))
_, err = helm.TryDownloadHelm(userPath, clientArch, clientOS, helm3)
if err != nil {
return err
}
err = updateHelmRepos(helm3)
if err != nil {
return err
}
chartPath := path.Join(os.TempDir(), "charts")
err = fetchChart(chartPath, "stable/metrics-server", helm3)
if err != nil {
return err
}
overrides := map[string]string{}
overrides["args"] = `{--kubelet-insecure-tls,--kubelet-preferred-address-types=InternalIP\,ExternalIP\,Hostname}`
fmt.Println("Chart path: ", chartPath)
if helm3 {
outputPath := path.Join(chartPath, "metrics-server")
err := helm3Upgrade(outputPath, "stable/metrics-server", namespace,
"values.yaml",
"",
overrides)
if err != nil {
return err
}
} else {
outputPath := path.Join(chartPath, "metrics-server/rendered")
err = templateChart(chartPath,
"metrics-server",
namespace,
outputPath,
"values.yaml",
"",
overrides)
if err != nil {
return err
}
applyRes, applyErr := kubectlTask("apply", "-n", namespace, "-R", "-f", outputPath)
if applyErr != nil {
return applyErr
}
if applyRes.ExitCode > 0 {
return fmt.Errorf("error applying templated YAML files, error: %s", applyRes.Stderr)
}
}
fmt.Println(`=======================================================================
= metrics-server has been installed. =
=======================================================================
# It can take a few minutes for the metrics-server to collect data
# from the cluster. Try these commands and wait a few moments if
# no data is showing.
` + MetricsInfoMsg + `
` + pkg.ThanksForUsing)
return nil
}
return metricsServer
}
const MetricsInfoMsg = `# Check pod usage
kubectl top pod
# Check node usage
kubectl top node
# Find out more at:
# https://github.com/helm/charts/tree/master/stable/metrics-server`

172
cmd/apps/minio_app.go Normal file
View File

@@ -0,0 +1,172 @@
package apps
import (
"fmt"
"log"
"os"
"path"
"strconv"
"strings"
"github.com/alexellis/bazaar/pkg"
"github.com/alexellis/bazaar/pkg/config"
"github.com/alexellis/bazaar/pkg/env"
"github.com/alexellis/bazaar/pkg/helm"
"github.com/sethvargo/go-password/password"
"github.com/spf13/cobra"
)
func MakeInstallMinio() *cobra.Command {
var minio = &cobra.Command{
Use: "minio",
Short: "Install minio",
Long: `Install minio`,
Example: ` bazaar app install minio`,
SilenceUsage: true,
}
minio.Flags().Bool("update-repo", true, "Update the helm repo")
minio.Flags().String("access-key", "", "Provide an access key to override the pre-generated value")
minio.Flags().String("secret-key", "", "Provide a secret key to override the pre-generated value")
minio.Flags().Bool("distributed", false, "Deploy Minio in Distributed Mode")
minio.Flags().String("namespace", "default", "Kubernetes namespace for the application")
minio.Flags().Bool("persistence", false, "Enable persistence")
minio.Flags().StringArray("set", []string{},
"Use custom flags or override existing flags \n(example --set persistence.enabled=true)")
minio.RunE = func(command *cobra.Command, args []string) error {
kubeConfigPath := getDefaultKubeconfig()
if command.Flags().Changed("kubeconfig") {
kubeConfigPath, _ = command.Flags().GetString("kubeconfig")
}
updateRepo, _ := minio.Flags().GetBool("update-repo")
fmt.Printf("Using kubeconfig: %s\n", kubeConfigPath)
userPath, err := config.InitUserDir()
if err != nil {
return err
}
clientArch, clientOS := env.GetClientArch()
fmt.Printf("Client: %s, %s\n", clientArch, clientOS)
log.Printf("User dir established as: %s\n", userPath)
os.Setenv("HELM_HOME", path.Join(userPath, ".helm"))
ns, _ := minio.Flags().GetString("namespace")
if ns != "default" {
return fmt.Errorf("please use the helm chart if you'd like to change the namespace to %s", ns)
}
_, err = helm.TryDownloadHelm(userPath, clientArch, clientOS, false)
if err != nil {
return err
}
if updateRepo {
err = updateHelmRepos(false)
if err != nil {
return err
}
}
chartPath := path.Join(os.TempDir(), "charts")
err = fetchChart(chartPath, "stable/minio", false)
if err != nil {
return err
}
persistence, _ := minio.Flags().GetBool("persistence")
overrides := map[string]string{}
accessKey, _ := minio.Flags().GetString("access-key")
secretKey, _ := minio.Flags().GetString("secret-key")
if len(accessKey) == 0 {
fmt.Printf("Access Key not provided, one will be generated for you\n")
accessKey, err = password.Generate(20, 10, 0, false, true)
}
if len(secretKey) == 0 {
fmt.Printf("Secret Key not provided, one will be generated for you\n")
secretKey, err = password.Generate(40, 10, 5, false, true)
}
if err != nil {
return err
}
overrides["accessKey"] = accessKey
overrides["secretKey"] = secretKey
overrides["persistence.enabled"] = strings.ToLower(strconv.FormatBool(persistence))
if dist, _ := minio.Flags().GetBool("distributed"); dist {
overrides["mode"] = "distributed"
}
customFlags, err := minio.Flags().GetStringArray("set")
if err != nil {
return fmt.Errorf("error with --set usage: %s", err)
}
if err := mergeFlags(overrides, customFlags); err != nil {
return err
}
outputPath := path.Join(chartPath, "minio/rendered")
err = templateChart(chartPath,
"minio",
ns,
outputPath,
"values.yaml",
"",
overrides)
if err != nil {
return err
}
err = kubectl("apply", "-R", "-f", outputPath)
if err != nil {
return err
}
fmt.Println(minioInstallMsg)
return nil
}
return minio
}
var _, clientOS = env.GetClientArch()
var MinioInfoMsg = `# Forward the minio port to your machine
kubectl port-forward -n default svc/minio 9000:9000 &
# Get the access and secret key to gain access to minio
ACCESSKEY=$(kubectl get secret -n default minio -o jsonpath="{.data.accesskey}" | base64 --decode; echo)
SECRETKEY=$(kubectl get secret -n default minio -o jsonpath="{.data.secretkey}" | base64 --decode; echo)
# Get the Minio Client
curl -SLf https://dl.min.io/client/mc/release/` + strings.ToLower(clientOS) + `-amd64/mc \
&& chmod +x mc
# Add a host
mc config host add minio http://127.0.0.1:9000 $ACCESSKEY $SECRETKEY
# List buckets
mc ls minio
# Find out more at: https://min.io`
var minioInstallMsg = `=======================================================================
= Minio has been installed. =
=======================================================================` +
"\n\n" + MinioInfoMsg + "\n\n" + pkg.ThanksForUsing

136
cmd/apps/mongodb_app.go Normal file
View File

@@ -0,0 +1,136 @@
package apps
import (
"fmt"
"log"
"os"
"path"
"strconv"
"github.com/alexellis/bazaar/pkg"
"github.com/alexellis/bazaar/pkg/config"
"github.com/alexellis/bazaar/pkg/env"
"github.com/alexellis/bazaar/pkg/helm"
"github.com/spf13/cobra"
)
func MakeInstallMongoDB() *cobra.Command {
var command = &cobra.Command{
Use: "mongodb",
Short: "Install mongodb",
Long: `Install mongodb`,
Example: ` bazaar app install mongodb`,
SilenceUsage: true,
}
command.Flags().String("namespace", "default", "Namespace for the app")
command.Flags().StringArray("set", []string{},
"Use custom flags or override existing flags \n(example --set=mongodbUsername=admin)")
command.Flags().Bool("persistence", false, "Create and bind a persistent volume, not recommended for development")
command.RunE = func(command *cobra.Command, args []string) error {
kubeConfigPath := getDefaultKubeconfig()
if command.Flags().Changed("kubeconfig") {
kubeConfigPath, _ = command.Flags().GetString("kubeconfig")
}
fmt.Printf("Using kubeconfig: %s\n", kubeConfigPath)
namespace, _ := command.Flags().GetString("namespace")
userPath, err := config.InitUserDir()
if err != nil {
return err
}
clientArch, clientOS := env.GetClientArch()
fmt.Printf("Client: %q, %q\n", clientArch, clientOS)
log.Printf("User dir established as: %s\n", userPath)
os.Setenv("HELM_HOME", path.Join(userPath, ".helm"))
helm3 := true
persistence, _ := command.Flags().GetBool("persistence")
_, err = helm.TryDownloadHelm(userPath, clientArch, clientOS, helm3)
if err != nil {
return err
}
err = addHelmRepo("stable", "https://kubernetes-charts.storage.googleapis.com/", helm3)
if err != nil {
return fmt.Errorf("unable to add repo %s", err)
}
updateRepo, _ := command.Flags().GetBool("update-repo")
if updateRepo {
err = updateHelmRepos(helm3)
if err != nil {
return fmt.Errorf("unable to update repos %s", err)
}
}
chartPath := path.Join(os.TempDir(), "charts")
err = fetchChart(chartPath, "stable/mongodb", helm3)
if err != nil {
return fmt.Errorf("unable fetch chart %s", err)
}
overrides := map[string]string{}
overrides["persistence.enabled"] = strconv.FormatBool(persistence)
outputPath := path.Join(chartPath, "mongodb")
customFlags, err := command.Flags().GetStringArray("set")
if err != nil {
return fmt.Errorf("error with --set usage: %s", err)
}
if err := mergeFlags(overrides, customFlags); err != nil {
return err
}
err = helm3Upgrade(outputPath, "stable/mongodb",
namespace, "values.yaml", "", overrides)
if err != nil {
return fmt.Errorf("unable to mongodb chart with helm %s", err)
}
fmt.Println(mongoDBPostInstallMsg)
return nil
}
return command
}
const mongoDBPostInstallMsg = `=======================================================================
= MongoDB has been installed. =
=======================================================================` +
"\n\n" + pkg.ThanksForUsing
var MongoDBInfoMsg = `
# MongoDB can be accessed via port 27017 on the following DNS name from within your cluster:
mongodb.{{namespace}}.svc.cluster.local
# To get the root password run:
export MONGODB_ROOT_PASSWORD=$(kubectl get secret --namespace {{namespace}} mongodb -o jsonpath="{.data.mongodb-root-password}" | base64 --decode)
# To connect to your database run the following command:
kubectl run --namespace {{namespace}} mongodb-client --rm --tty -i --restart='Never' --image bitnami/mongodb --command -- mongo admin --host mongodb --authenticationDatabase admin -u root -p $MONGODB_ROOT_PASSWORD
# To connect to your database from outside the cluster execute the following commands:
kubectl port-forward --namespace {{namespace}} svc/mongodb 27017:27017 &
mongo --host 127.0.0.1 --authenticationDatabase admin -p $MONGODB_ROOT_PASSWORD
# More on GitHub : https://github.com/helm/charts/tree/master/stable/mongodb`

173
cmd/apps/nginx_app.go Normal file
View File

@@ -0,0 +1,173 @@
package apps
import (
"fmt"
"log"
"os"
"path"
"github.com/alexellis/bazaar/pkg"
"github.com/alexellis/bazaar/pkg/config"
"github.com/alexellis/bazaar/pkg/env"
"github.com/alexellis/bazaar/pkg/helm"
"github.com/spf13/cobra"
)
func MakeInstallNginx() *cobra.Command {
var nginx = &cobra.Command{
Use: "nginx-ingress",
Short: "Install nginx-ingress",
Long: `Install nginx-ingress. This app can be installed with Host networking for cases where an external LB is not available. please see the --host-mode flag and the nginx-ingress docs for more info`,
Example: ` bazaar app install nginx-ingress --namespace default`,
SilenceUsage: true,
}
nginx.Flags().StringP("namespace", "n", "default", "The namespace used for installation")
nginx.Flags().Bool("update-repo", true, "Update the helm repo")
nginx.Flags().Bool("host-mode", false, "If we should install nginx-ingress in host mode.")
nginx.Flags().Bool("helm3", true, "Use helm3, if set to false uses helm2")
nginx.RunE = func(command *cobra.Command, args []string) error {
kubeConfigPath := getDefaultKubeconfig()
if command.Flags().Changed("kubeconfig") {
kubeConfigPath, _ = command.Flags().GetString("kubeconfig")
}
updateRepo, _ := nginx.Flags().GetBool("update-repo")
fmt.Printf("Using kubeconfig: %s\n", kubeConfigPath)
helm3, _ := command.Flags().GetBool("helm3")
if helm3 {
fmt.Println("Using helm3")
}
userPath, err := config.InitUserDir()
if err != nil {
return err
}
namespace, _ := command.Flags().GetString("namespace")
if namespace != "default" {
return fmt.Errorf(`to override the "default", install via tiller`)
}
clientArch, clientOS := env.GetClientArch()
fmt.Printf("Client: %s, %s\n", clientArch, clientOS)
log.Printf("User dir established as: %s\n", userPath)
os.Setenv("HELM_HOME", path.Join(userPath, ".helm"))
_, err = helm.TryDownloadHelm(userPath, clientArch, clientOS, helm3)
if err != nil {
return err
}
err = addHelmRepo("stable", "https://kubernetes-charts.storage.googleapis.com", helm3)
if err != nil {
return err
}
if updateRepo {
err = updateHelmRepos(helm3)
if err != nil {
return err
}
}
chartPath := path.Join(os.TempDir(), "charts")
err = fetchChart(chartPath, "stable/nginx-ingress", helm3)
if err != nil {
return err
}
overrides := map[string]string{}
overrides["defaultBackend.enabled"] = "false"
arch := getNodeArchitecture()
fmt.Printf("Node architecture: %q\n", arch)
switch arch {
case "amd64":
// use default image
case "arm", "arm64":
overrides["controller.image.repository"] = fmt.Sprintf("quay.io/kubernetes-ingress-controller/nginx-ingress-controller-%v", arch)
default:
return fmt.Errorf("architecture %v is not supported by ingress-nginx", arch)
}
hostMode, flagErr := command.Flags().GetBool("host-mode")
if flagErr != nil {
return flagErr
}
if hostMode {
fmt.Println("Running in host networking mode")
overrides["controller.hostNetwork"] = "true"
overrides["controller.daemonset.useHostPort"] = "true"
overrides["dnsPolicy"] = "ClusterFirstWithHostNet"
overrides["controller.kind"] = "DaemonSet"
}
fmt.Println("Chart path: ", chartPath)
ns := "default"
if helm3 {
outputPath := path.Join(chartPath, "nginx-ingress")
err := helm3Upgrade(outputPath, "stable/nginx-ingress", ns,
"values.yaml",
"",
overrides)
if err != nil {
return err
}
} else {
outputPath := path.Join(chartPath, "nginx-ingress/rendered")
err = templateChart(chartPath,
"nginx-ingress",
ns,
outputPath,
"values.yaml",
"",
overrides)
if err != nil {
return err
}
err = kubectl("apply", "-R", "-f", outputPath)
if err != nil {
return err
}
}
fmt.Println(nginxIngressInstallMsg)
return nil
}
return nginx
}
const NginxIngressInfoMsg = `# If you're using a local environment such as "minikube" or "KinD",
# then try the inlets operator with "bazaar app install inlets-operator"
# If you're using a managed Kubernetes service, then you'll find
# your LoadBalancer's IP under "EXTERNAL-IP" via:
kubectl get svc nginx-ingress-controller
# Find out more at:
# https://github.com/helm/charts/tree/master/stable/nginx-ingress`
const nginxIngressInstallMsg = `=======================================================================
= nginx-ingress has been installed. =
=======================================================================` +
"\n\n" + NginxIngressInfoMsg + "\n\n" + pkg.ThanksForUsing

292
cmd/apps/openfaas_app.go Normal file
View File

@@ -0,0 +1,292 @@
package apps
import (
"fmt"
"log"
"os"
"path"
"strconv"
"strings"
"github.com/alexellis/bazaar/pkg"
"github.com/alexellis/bazaar/pkg/config"
"github.com/alexellis/bazaar/pkg/env"
"github.com/alexellis/bazaar/pkg/helm"
"github.com/sethvargo/go-password/password"
"github.com/spf13/cobra"
)
func MakeInstallOpenFaaS() *cobra.Command {
var openfaas = &cobra.Command{
Use: "openfaas",
Short: "Install openfaas",
Long: `Install openfaas`,
Example: ` bazaar app install openfaas --loadbalancer`,
SilenceUsage: true,
}
openfaas.Flags().BoolP("basic-auth", "a", true, "Enable authentication")
openfaas.Flags().BoolP("load-balancer", "l", false, "Add a loadbalancer")
openfaas.Flags().StringP("namespace", "n", "openfaas", "The namespace for the core services")
openfaas.Flags().Bool("update-repo", true, "Update the helm repo")
openfaas.Flags().String("pull-policy", "IfNotPresent", "Pull policy for OpenFaaS core services")
openfaas.Flags().String("function-pull-policy", "Always", "Pull policy for functions")
openfaas.Flags().Bool("operator", false, "Create OpenFaaS Operator")
openfaas.Flags().Bool("clusterrole", false, "Create a ClusterRole for OpenFaaS instead of a limited scope Role")
openfaas.Flags().Bool("direct-functions", true, "Invoke functions directly from the gateway")
openfaas.Flags().Int("queue-workers", 1, "Replicas of queue-worker")
openfaas.Flags().Int("gateways", 1, "Replicas of gateway")
openfaas.Flags().Bool("helm3", true, "Use helm3, if set to false uses helm2")
openfaas.Flags().StringArray("set", []string{}, "Use custom flags or override existing flags \n(example --set=gateway.replicas=2)")
openfaas.RunE = func(command *cobra.Command, args []string) error {
kubeConfigPath := getDefaultKubeconfig()
if command.Flags().Changed("kubeconfig") {
kubeConfigPath, _ = command.Flags().GetString("kubeconfig")
}
fmt.Printf("Using kubeconfig: %s\n", kubeConfigPath)
helm3, _ := command.Flags().GetBool("helm3")
if helm3 {
fmt.Println("Using helm3")
}
namespace, _ := command.Flags().GetString("namespace")
if namespace != "openfaas" {
return fmt.Errorf(`to override the "openfaas", install OpenFaaS via helm manually`)
}
arch := getNodeArchitecture()
fmt.Printf("Node architecture: %q\n", arch)
valuesSuffix := getValuesSuffix(arch)
userPath, err := config.InitUserDir()
if err != nil {
return err
}
clientArch, clientOS := env.GetClientArch()
fmt.Printf("Client: %q, %q\n", clientArch, clientOS)
log.Printf("User dir established as: %s\n", userPath)
os.Setenv("HELM_HOME", path.Join(userPath, ".helm"))
_, err = helm.TryDownloadHelm(userPath, clientArch, clientOS, helm3)
if err != nil {
return err
}
err = addHelmRepo("openfaas", "https://openfaas.github.io/faas-netes/", helm3)
if err != nil {
return err
}
updateRepo, _ := openfaas.Flags().GetBool("update-repo")
if updateRepo {
err = updateHelmRepos(helm3)
if err != nil {
return err
}
}
if err != nil {
return err
}
_, err = kubectlTask("apply", "-f",
"https://raw.githubusercontent.com/openfaas/faas-netes/master/namespaces.yml")
if err != nil {
return err
}
pass, err := password.Generate(25, 10, 0, false, true)
if err != nil {
return err
}
res, secretErr := kubectlTask("-n", namespace, "create", "secret", "generic",
"basic-auth",
"--from-literal=basic-auth-user=admin",
`--from-literal=basic-auth-password=`+pass)
if secretErr != nil {
return secretErr
}
if res.ExitCode != 0 {
fmt.Printf("[Warning] unable to create secret %s, may already exist: %s", "basic-auth", res.Stderr)
}
chartPath := path.Join(os.TempDir(), "charts")
err = fetchChart(chartPath, "openfaas/openfaas", helm3)
if err != nil {
return err
}
overrides := map[string]string{}
pullPolicy, _ := command.Flags().GetString("pull-policy")
if len(pullPolicy) == 0 {
return fmt.Errorf("you must give a value for pull-policy such as IfNotPresent or Always")
}
functionPullPolicy, _ := command.Flags().GetString("function-pull-policy")
if len(pullPolicy) == 0 {
return fmt.Errorf("you must give a value for function-pull-policy such as IfNotPresent or Always")
}
createOperator, _ := command.Flags().GetBool("operator")
createOperatorVal := "false"
if createOperator {
createOperatorVal = "true"
}
clusterRole, _ := command.Flags().GetBool("clusterrole")
clusterRoleVal := "false"
if clusterRole {
clusterRoleVal = "true"
}
directFunctions, _ := command.Flags().GetBool("direct-functions")
directFunctionsVal := "true"
if !directFunctions {
directFunctionsVal = "false"
}
gateways, _ := command.Flags().GetInt("gateways")
queueWorkers, _ := command.Flags().GetInt("queue-workers")
overrides["clusterRole"] = clusterRoleVal
overrides["gateway.directFunctions"] = directFunctionsVal
overrides["operator.create"] = createOperatorVal
overrides["openfaasImagePullPolicy"] = pullPolicy
overrides["faasnetes.imagePullPolicy"] = functionPullPolicy
overrides["basicAuthPlugin.replicas"] = "1"
overrides["gateway.replicas"] = fmt.Sprintf("%d", gateways)
overrides["queueWorker.replicas"] = fmt.Sprintf("%d", queueWorkers)
basicAuth, _ := command.Flags().GetBool("basic-auth")
// the value in the template is "basic_auth" not the more usual basicAuth
overrides["basic_auth"] = strings.ToLower(strconv.FormatBool(basicAuth))
overrides["serviceType"] = "NodePort"
lb, _ := command.Flags().GetBool("load-balancer")
if lb {
overrides["serviceType"] = "LoadBalancer"
}
customFlags, _ := command.Flags().GetStringArray("set")
if err := mergeFlags(overrides, customFlags); err != nil {
return err
}
if helm3 {
outputPath := path.Join(chartPath, "openfaas")
err := helm3Upgrade(outputPath, "openfaas/openfaas", namespace,
"values"+valuesSuffix+".yaml",
"",
overrides)
if err != nil {
return err
}
} else {
outputPath := path.Join(chartPath, "openfaas/rendered")
err = templateChart(chartPath, "openfaas",
namespace,
outputPath,
"values"+valuesSuffix+".yaml",
"",
overrides)
if err != nil {
return err
}
applyRes, applyErr := kubectlTask("apply", "-R", "-f", outputPath)
if applyErr != nil {
return applyErr
}
if applyRes.ExitCode > 0 {
return fmt.Errorf("error applying templated YAML files, error: %s", applyRes.Stderr)
}
}
fmt.Println(openfaasPostInstallMsg)
return nil
}
return openfaas
}
func getValuesSuffix(arch string) string {
var valuesSuffix string
switch arch {
case "arm":
valuesSuffix = "-armhf"
break
case "arm64", "aarch64":
valuesSuffix = "-arm64"
break
default:
valuesSuffix = ""
}
return valuesSuffix
}
func mergeFlags(existingMap map[string]string, setOverrides []string) error {
for _, setOverride := range setOverrides {
flag := strings.Split(setOverride, "=")
if len(flag) != 2 {
return fmt.Errorf("incorrect format for custom flag `%s`", setOverride)
}
existingMap[flag[0]] = flag[1]
}
return nil
}
const OpenFaaSInfoMsg = `# Get the faas-cli
curl -SLsf https://cli.openfaas.com | sudo sh
# Forward the gateway to your machine
kubectl rollout status -n openfaas deploy/gateway
kubectl port-forward -n openfaas svc/gateway 8080:8080 &
# If basic auth is enabled, you can now log into your gateway:
PASSWORD=$(kubectl get secret -n openfaas basic-auth -o jsonpath="{.data.basic-auth-password}" | base64 --decode; echo)
echo -n $PASSWORD | faas-cli login --username admin --password-stdin
faas-cli store deploy figlet
faas-cli list
# For Raspberry Pi
faas-cli store list \
--platform armhf
faas-cli store deploy figlet \
--platform armhf
# Find out more at:
# https://github.com/openfaas/faas`
const openfaasPostInstallMsg = `=======================================================================
= OpenFaaS has been installed. =
=======================================================================` +
"\n\n" + OpenFaaSInfoMsg + "\n\n" + pkg.ThanksForUsing

View File

@@ -0,0 +1,119 @@
package apps
import (
"fmt"
"reflect"
"strings"
"testing"
)
func Test_getValuesSuffix_arm64(t *testing.T) {
want := "-arm64"
got := getValuesSuffix("arm64")
if want != got {
t.Errorf("suffix, want: %s, got: %s", want, got)
}
}
func Test_getValuesSuffix_aarch64(t *testing.T) {
want := "-arm64"
got := getValuesSuffix("aarch64")
if want != got {
t.Errorf("suffix, want: %s, got: %s", want, got)
}
}
func Test_mergeFlags(t *testing.T) {
var (
unexpectedErr = "failed to merge err: %v, existing flags: %v, set overrides: %v"
unexpectedMergeErr = "error merging flags, want: %v, got: %v"
inconsistentErrString = "inconsistent error return want: %v, got: %v"
)
tests := []struct {
title string
flags map[string]string
overrides []string
resultExpected map[string]string
errExpected error
}{
// positive cases:
{"Single key with numeric value and no flags",
map[string]string{},
[]string{"b=1"},
map[string]string{"b": "1"},
nil,
},
{"Empty set and no flags, should not fail",
map[string]string{},
[]string{},
map[string]string{},
nil,
},
{"No set key and an existing flag",
map[string]string{"a": "1"},
[]string{},
map[string]string{"a": "1"},
nil,
},
{"Single key with numeric value, override the flag with numeric value",
map[string]string{"a": "1"},
[]string{"a=2"},
map[string]string{"a": "2"},
nil,
},
{"Single key with numeric value and single flag key with numeric value",
map[string]string{"a": "1"},
[]string{"b=1"},
map[string]string{"a": "1", "b": "1"},
nil,
},
{"Single key with numeric value, update existing key and a new key",
map[string]string{"a": "1"},
[]string{"a=2", "b=1"},
map[string]string{"a": "2", "b": "1"},
nil,
},
{"Update all existing flags in the map",
map[string]string{"a": "1", "b": "2"},
[]string{"a=2", "b=3"},
map[string]string{"a": "2", "b": "3"},
nil,
},
// check errors
{"Incorrect flag format, providing : as a delimiter",
map[string]string{"a": "1"},
[]string{"a:2"},
nil,
fmt.Errorf("incorrect format for custom flag `a:2`"),
},
{"Incorrect flag format, providing space as a delimiter",
map[string]string{"a": "1"}, []string{"a 2"},
nil,
fmt.Errorf("incorrect format for custom flag `a 2`"),
},
{"Incorrect flag format, providing - as a delimiter",
map[string]string{"a": "1"},
[]string{"a-2"},
nil,
fmt.Errorf("incorrect format for custom flag `a-2`"),
},
}
for _, test := range tests {
t.Run(test.title, func(t *testing.T) {
err := mergeFlags(test.flags, test.overrides)
if err != nil {
if test.errExpected == nil {
t.Errorf(unexpectedErr, err, test.flags, test.overrides)
} else if !strings.EqualFold(err.Error(), test.errExpected.Error()) {
t.Errorf(inconsistentErrString, test.errExpected, err)
}
return
}
if !reflect.DeepEqual(test.resultExpected, test.flags) {
t.Errorf(unexpectedMergeErr, test.resultExpected, test.flags)
}
})
}
}

View File

@@ -0,0 +1,210 @@
package apps
import (
"bytes"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"text/template"
"github.com/alexellis/bazaar/pkg"
"github.com/spf13/cobra"
)
type InputData struct {
IngressDomain string
CertmanagerEmail string
IngressClass string
}
func MakeInstallOpenFaaSIngress() *cobra.Command {
var openfaasIngress = &cobra.Command{
Use: "openfaas-ingress",
Short: "Install openfaas ingress with TLS",
Long: `Install openfaas ingress. Requires cert-manager 0.11.0 or higher installation in the cluster. Please set --domain to your custom domain and set --email to your email - this email is used by letsencrypt for domain expiry etc.`,
Example: ` bazaar app install openfaas-ingress --domain openfaas.example.com --email openfaas@example.com`,
SilenceUsage: true,
}
openfaasIngress.Flags().StringP("domain", "d", "", "Custom Ingress Domain")
openfaasIngress.Flags().StringP("email", "e", "", "Letsencrypt Email")
openfaasIngress.Flags().String("ingress-class", "nginx", "Ingress class to be used such as nginx or traefik")
openfaasIngress.RunE = func(command *cobra.Command, args []string) error {
email, _ := command.Flags().GetString("email")
domain, _ := command.Flags().GetString("domain")
ingressClass, _ := command.Flags().GetString("ingress-class")
if email == "" || domain == "" {
return errors.New("both --email and --domain flags should be set and not empty, please set these values")
}
if ingressClass == "" {
return errors.New("--ingress-class must be set")
}
kubeConfigPath := getDefaultKubeconfig()
if command.Flags().Changed("kubeconfig") {
kubeConfigPath, _ = command.Flags().GetString("kubeconfig")
}
fmt.Printf("Using kubeconfig: %s\n", kubeConfigPath)
yamlBytes, templateErr := buildYAML(domain, email, ingressClass)
if templateErr != nil {
log.Print("Unable to install the application. Could not build the templated yaml file for the resources")
return templateErr
}
tempFile, tempFileErr := writeTempFile(yamlBytes, "temp_openfaas_ingress.yaml")
if tempFileErr != nil {
log.Print("Unable to save generated yaml file into the temporary directory")
return tempFileErr
}
res, err := kubectlTask("apply", "-f", tempFile)
if err != nil {
log.Print(err)
return err
}
if res.ExitCode != 0 {
return fmt.Errorf(`Unable to apply YAML files.
Have you got OpenFaaS running in the openfaas namespace and cert-manager 0.11.0 or higher installed in cert-manager namespace? %s`,
res.Stderr)
}
fmt.Println(openfaasIngressInstallMsg)
return nil
}
return openfaasIngress
}
func createTempDirectory(directory string) (string, error) {
tempDirectory := filepath.Join(os.TempDir(), directory)
if _, err := os.Stat(tempDirectory); os.IsNotExist(err) {
log.Printf(tempDirectory)
errr := os.Mkdir(tempDirectory, 0744)
if errr != nil {
log.Printf("couldnt make dir %s", err)
return "", err
}
}
return tempDirectory, nil
}
func writeTempFile(input []byte, fileLocation string) (string, error) {
var tempDirectory, dirErr = createTempDirectory(".bazaar/")
if dirErr != nil {
return "", dirErr
}
filename := filepath.Join(tempDirectory, fileLocation)
err := ioutil.WriteFile(filename, input, 0744)
if err != nil {
return "", err
}
return filename, nil
}
func buildYAML(domain, email, ingressClass string) ([]byte, error) {
tmpl, err := template.New("yaml").Parse(ingressYamlTemplate)
if err != nil {
return nil, err
}
inputData := InputData{
IngressDomain: domain,
CertmanagerEmail: email,
IngressClass: ingressClass,
}
var tpl bytes.Buffer
err = tmpl.Execute(&tpl, inputData)
if err != nil {
return nil, err
}
return tpl.Bytes(), nil
}
const OpenfaasIngressInfoMsg = `# You will need to ensure that your domain points to your cluster and is
# accessible through ports 80 and 443.
#
# This is used to validate your ownership of this domain by LetsEncrypt
# and then you can use https with your installation.
# Ingress to your domain has been installed for OpenFaaS
# to see the ingress record run
kubectl get -n openfaas ingress openfaas-gateway
# Check the cert-manager logs with:
kubectl logs -n cert-manager deploy/cert-manager
# A cert-manager ClusterIssuer has been installed into the default
# namespace - to see the resource run
kubectl describe ClusterIssuer letsencrypt-prod
# To check the status of your certificate you can run
kubectl describe -n openfaas Certificate openfaas-gateway
# It may take a while to be issued by LetsEncrypt, in the meantime a
# self-signed cert will be installed`
const openfaasIngressInstallMsg = `=======================================================================
= OpenFaaS Ingress and cert-manager ClusterIssuer have been installed =
=======================================================================` +
"\n\n" + OpenfaasIngressInfoMsg + "\n\n" + pkg.ThanksForUsing
var ingressYamlTemplate = `
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: openfaas-gateway
namespace: openfaas
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
kubernetes.io/ingress.class: {{.IngressClass}}
spec:
rules:
- host: {{.IngressDomain}}
http:
paths:
- backend:
serviceName: gateway
servicePort: 8080
path: /
tls:
- hosts:
- {{.IngressDomain}}
secretName: openfaas-gateway
---
apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
email: {{.CertmanagerEmail}}
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: example-issuer-account-key
solvers:
- http01:
ingress:
class: {{.IngressClass}}`

View File

@@ -0,0 +1,75 @@
package apps
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
)
func Test_buildYAML_SubsitutesDomainEmailAndIngress(t *testing.T) {
templBytes, _ := buildYAML("openfaas.subdomain.example.com", "openfaas@subdomain.example.com", "traefik")
got := string(templBytes)
if want != got {
t.Errorf("suffix, want: %q, got: %q", want, got)
}
}
var want = `
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: openfaas-gateway
namespace: openfaas
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
kubernetes.io/ingress.class: traefik
spec:
rules:
- host: openfaas.subdomain.example.com
http:
paths:
- backend:
serviceName: gateway
servicePort: 8080
path: /
tls:
- hosts:
- openfaas.subdomain.example.com
secretName: openfaas-gateway
---
apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
email: openfaas@subdomain.example.com
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: example-issuer-account-key
solvers:
- http01:
ingress:
class: traefik`
func Test_writeTempFile_writes_to_tmp(t *testing.T) {
var want = "some input string"
tmpLocation, _ := writeTempFile([]byte(want), "tmp_file_name.yaml")
got, _ := ioutil.ReadFile(tmpLocation)
if string(got) != want {
t.Errorf("suffix, want: %q, got: %q", want, got)
}
}
func Test_createTempDirectory_creates(t *testing.T) {
var want = filepath.Join(os.TempDir(), ".bazaar")
got, _ := createTempDirectory(".bazaar")
if got != want {
t.Errorf("suffix, want: %q, got: %q", want, got)
}
}

150
cmd/apps/postgres_app.go Normal file
View File

@@ -0,0 +1,150 @@
package apps
import (
"fmt"
"log"
"os"
"path"
"strconv"
"strings"
"github.com/alexellis/bazaar/pkg"
"github.com/alexellis/bazaar/pkg/config"
"github.com/alexellis/bazaar/pkg/env"
"github.com/alexellis/bazaar/pkg/helm"
"github.com/spf13/cobra"
)
func MakeInstallPostgresql() *cobra.Command {
var postgresql = &cobra.Command{
Use: "postgresql",
Short: "Install postgresql",
Long: `Install postgresql`,
Example: ` bazaar app install postgresql`,
SilenceUsage: true,
}
postgresql.Flags().Bool("update-repo", true, "Update the helm repo")
postgresql.Flags().String("namespace", "default", "Kubernetes namespace for the application")
postgresql.Flags().Bool("persistence", false, "Enable persistence")
postgresql.Flags().StringArray("set", []string{},
"Use custom flags or override existing flags \n(example --set persistence.enabled=true)")
postgresql.RunE = func(command *cobra.Command, args []string) error {
kubeConfigPath := getDefaultKubeconfig()
if command.Flags().Changed("kubeconfig") {
kubeConfigPath, _ = command.Flags().GetString("kubeconfig")
}
updateRepo, _ := postgresql.Flags().GetBool("update-repo")
fmt.Printf("Using kubeconfig: %s\n", kubeConfigPath)
userPath, err := config.InitUserDir()
if err != nil {
return err
}
clientArch, clientOS := env.GetClientArch()
fmt.Printf("Client: %s, %s\n", clientArch, clientOS)
log.Printf("User dir established as: %s\n", userPath)
os.Setenv("HELM_HOME", path.Join(userPath, ".helm"))
ns, _ := postgresql.Flags().GetString("namespace")
if ns != "default" {
return fmt.Errorf("please use the helm chart if you'd like to change the namespace to %s", ns)
}
_, err = helm.TryDownloadHelm(userPath, clientArch, clientOS, false)
if err != nil {
return err
}
if updateRepo {
err = updateHelmRepos(false)
if err != nil {
return err
}
}
chartPath := path.Join(os.TempDir(), "charts")
err = fetchChart(chartPath, "stable/postgresql", false)
if err != nil {
return err
}
persistence, _ := postgresql.Flags().GetBool("persistence")
overrides := map[string]string{}
if err != nil {
return err
}
overrides["persistence.enabled"] = strings.ToLower(strconv.FormatBool(persistence))
customFlags, err := command.Flags().GetStringArray("set")
if err != nil {
return fmt.Errorf("error with --set usage: %s", err)
}
if err := mergeFlags(overrides, customFlags); err != nil {
return err
}
outputPath := path.Join(chartPath, "postgresql/rendered")
err = templateChart(chartPath,
"postgresql",
ns,
outputPath,
"values.yaml",
"",
overrides)
if err != nil {
return err
}
err = kubectl("apply", "-R", "-f", outputPath)
if err != nil {
return err
}
fmt.Println(postgresqlInstallMsg)
return nil
}
return postgresql
}
const PostgresqlInfoMsg = `PostgreSQL can be accessed via port 5432 on the following DNS name from within your cluster:
postgresql.default.svc.cluster.local - Read/Write connection
To get the password for "postgres" run:
export POSTGRES_PASSWORD=$(kubectl get secret --namespace default postgresql -o jsonpath="{.data.postgresql-password}" | base64 --decode)
To connect to your database run the following command:
kubectl run postgresql-client --rm --tty -i --restart='Never' --namespace default --image docker.io/bitnami/postgresql:11.6.0-debian-9-r0 --env="PGPASSWORD=$POSTGRES_PASSWORD" --command -- psql --host postgresql -U postgres -d postgres -p 5432
To connect to your database from outside the cluster execute the following commands:
kubectl port-forward --namespace default svc/postgresql 5432:5432 &
PGPASSWORD="$POSTGRES_PASSWORD" psql --host 127.0.0.1 -U postgres -d postgres -p 5432
# Find out more at: https://github.com/helm/charts/tree/master/stable/postgresql`
const postgresqlInstallMsg = `=======================================================================
= PostgreSQL has been installed. =
=======================================================================` +
"\n\n" + PostgresqlInfoMsg + "\n\n" + pkg.ThanksForUsing

178
cmd/apps/registry_app.go Normal file
View File

@@ -0,0 +1,178 @@
package apps
import (
"fmt"
"log"
"os"
"path"
"golang.org/x/crypto/bcrypt"
"github.com/alexellis/bazaar/pkg"
"github.com/alexellis/bazaar/pkg/config"
"github.com/alexellis/bazaar/pkg/env"
"github.com/alexellis/bazaar/pkg/helm"
"github.com/sethvargo/go-password/password"
"github.com/spf13/cobra"
)
func MakeInstallRegistry() *cobra.Command {
var registry = &cobra.Command{
Use: "docker-registry",
Short: "Install a Docker registry",
Long: `Install a Docker registry`,
Example: ` bazaar app install registry --namespace default`,
SilenceUsage: true,
}
registry.Flags().StringP("namespace", "n", "default", "The namespace used for installation")
registry.Flags().Bool("update-repo", true, "Update the helm repo")
registry.Flags().Bool("helm3", true, "Use helm3, if set to false uses helm2")
registry.Flags().StringP("username", "u", "admin", "Username for the registry")
registry.Flags().StringP("password", "p", "", "Password for the registry, leave blank to generate")
registry.RunE = func(command *cobra.Command, args []string) error {
kubeConfigPath := getDefaultKubeconfig()
if command.Flags().Changed("kubeconfig") {
kubeConfigPath, _ = command.Flags().GetString("kubeconfig")
}
updateRepo, _ := registry.Flags().GetBool("update-repo")
fmt.Printf("Using kubeconfig: %s\n", kubeConfigPath)
helm3, _ := command.Flags().GetBool("helm3")
userPath, err := config.InitUserDir()
if err != nil {
return err
}
namespace, _ := command.Flags().GetString("namespace")
if namespace != "default" {
return fmt.Errorf(`to override the "default", install via tiller`)
}
clientArch, clientOS := env.GetClientArch()
fmt.Printf("Client: %s, %s\n", clientArch, clientOS)
log.Printf("User dir established as: %s\n", userPath)
os.Setenv("HELM_HOME", path.Join(userPath, ".helm"))
_, err = helm.TryDownloadHelm(userPath, clientArch, clientOS, helm3)
if err != nil {
return err
}
username, _ := command.Flags().GetString("username")
pass, _ := command.Flags().GetString("password")
if len(pass) == 0 {
key, err := password.Generate(20, 10, 0, false, true)
if err != nil {
return err
}
pass = key
}
val, err := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost)
if err != nil {
return err
}
htPasswd := fmt.Sprintf("%s:%s\n", username, string(val))
err = addHelmRepo("stable", "https://kubernetes-charts.storage.googleapis.com", helm3)
if err != nil {
return err
}
if updateRepo {
err = updateHelmRepos(helm3)
if err != nil {
return err
}
}
chartPath := path.Join(os.TempDir(), "charts")
err = fetchChart(chartPath, "stable/docker-registry", helm3)
if err != nil {
return err
}
overrides := map[string]string{}
overrides["persistence.enabled"] = "false"
overrides["secrets.htpasswd"] = string(htPasswd)
arch := getNodeArchitecture()
fmt.Printf("Node architecture: %q\n", arch)
fmt.Println("Chart path: ", chartPath)
ns := "default"
if helm3 {
outputPath := path.Join(chartPath, "docker-registry")
err := helm3Upgrade(outputPath, "stable/docker-registry", ns,
"values.yaml",
"",
overrides)
if err != nil {
return err
}
} else {
outputPath := path.Join(chartPath, "docker-registry/rendered")
err = templateChart(chartPath,
"docker-registry",
ns,
outputPath,
"values.yaml",
"",
overrides)
if err != nil {
return err
}
err = kubectl("apply", "-R", "-f", outputPath)
if err != nil {
return err
}
}
fmt.Println(registryInstallMsg)
fmt.Printf("Registry credentials: %s %s\nexport PASSWORD=%s\n", username, pass, pass)
return nil
}
return registry
}
const registryInfoMsg = `# Your docker-registry has been configured
kubectl logs deploy/docker-registry
export IP="192.168.0.11" # Set to WiFI/ethernet adapter
export PASSWORD="" # See below
kubectl port-forward svc/docker-registry --address 0.0.0.0 5000 &
docker login $IP:5000 --username admin --password $PASSWORD
docker tag alpine:3.11 $IP:5000/alpine:3.11
docker push $IP:5000/alpine:3.11
# Find out more at:
# https://github.com/helm/charts/tree/master/stable/registry`
const registryInstallMsg = `=======================================================================
= docker-registry has been installed. =
=======================================================================` +
"\n\n" + registryInfoMsg + "\n\n" + pkg.ThanksForUsing

View File

@@ -0,0 +1,192 @@
package apps
import (
"bytes"
"errors"
"fmt"
"log"
"text/template"
"github.com/alexellis/bazaar/pkg"
"github.com/spf13/cobra"
)
type RegInputData struct {
IngressDomain string
CertmanagerEmail string
IngressClass string
Namespace string
NginxMaxBuffer string
}
func MakeInstallRegistryIngress() *cobra.Command {
var registryIngress = &cobra.Command{
Use: "docker-registry-ingress",
Short: "Install registry ingress with TLS",
Long: `Install registry ingress. Requires cert-manager 0.11.0 or higher installation in the cluster. Please set --domain to your custom domain and set --email to your email - this email is used by letsencrypt for domain expiry etc.`,
Example: ` bazaar app install registry-ingress --domain registry.example.com --email openfaas@example.com`,
SilenceUsage: true,
}
registryIngress.Flags().StringP("domain", "d", "", "Custom Ingress Domain")
registryIngress.Flags().StringP("email", "e", "", "Letsencrypt Email")
registryIngress.Flags().String("ingress-class", "nginx", "Ingress class to be used such as nginx or traefik")
registryIngress.Flags().String("max-size", "200m", "the max size for the ingress proxy, default to 200m")
registryIngress.Flags().StringP("namespace", "n", "default", "The namespace where the registry is installed")
registryIngress.RunE = func(command *cobra.Command, args []string) error {
email, _ := command.Flags().GetString("email")
domain, _ := command.Flags().GetString("domain")
ingressClass, _ := command.Flags().GetString("ingress-class")
namespace, _ := command.Flags().GetString("namespace")
maxSize, _ := command.Flags().GetString("max-size")
if email == "" || domain == "" {
return errors.New("both --email and --domain flags should be set and not empty, please set these values")
}
if ingressClass == "" {
return errors.New("--ingress-class must be set")
}
kubeConfigPath := getDefaultKubeconfig()
if command.Flags().Changed("kubeconfig") {
kubeConfigPath, _ = command.Flags().GetString("kubeconfig")
}
fmt.Printf("Using kubeconfig: %s\n", kubeConfigPath)
yamlBytes, templateErr := buildRegistryYAML(domain, email, ingressClass, namespace, maxSize)
if templateErr != nil {
log.Print("Unable to install the application. Could not build the templated yaml file for the resources")
return templateErr
}
tempFile, tempFileErr := writeTempFile(yamlBytes, "temp_registry_ingress.yaml")
if tempFileErr != nil {
log.Print("Unable to save generated yaml file into the temporary directory")
return tempFileErr
}
res, err := kubectlTask("apply", "-f", tempFile)
if err != nil {
log.Print(err)
return err
}
if res.ExitCode != 0 {
return fmt.Errorf(`Unable to apply YAML files.
Have you got the Registry running and cert-manager 0.11.0 or higher installed? %s`,
res.Stderr)
}
fmt.Println(RegistryIngressInstallMsg)
return nil
}
return registryIngress
}
func buildRegistryYAML(domain, email, ingressClass, namespace, maxSize string) ([]byte, error) {
tmpl, err := template.New("yaml").Parse(registryIngressYamlTemplate)
if err != nil {
return nil, err
}
inputData := RegInputData{
IngressDomain: domain,
CertmanagerEmail: email,
IngressClass: ingressClass,
Namespace: namespace,
NginxMaxBuffer: "",
}
if ingressClass == "nginx" {
inputData.NginxMaxBuffer = fmt.Sprintf(" nginx.ingress.kubernetes.io/proxy-body-size: %s", maxSize)
}
var tpl bytes.Buffer
err = tmpl.Execute(&tpl, inputData)
if err != nil {
return nil, err
}
return tpl.Bytes(), nil
}
const RegistryIngressInfoMsg = `# You will need to ensure that your domain points to your cluster and is
# accessible through ports 80 and 443.
#
# This is used to validate your ownership of this domain by LetsEncrypt
# and then you can use https with your installation.
# Ingress to your domain has been installed for the Registry
# to see the ingress record run
kubectl get -n <installed-namespace> ingress docker-registry
# Check the cert-manager logs with:
kubectl logs -n cert-manager deploy/cert-manager
# A cert-manager ClusterIssuer has been installed into the provided
# namespace - to see the resource run
kubectl describe -n <installed-namespace> ClusterIssuer letsencrypt-prod-registry
# To check the status of your certificate you can run
kubectl describe -n <installed-namespace> Certificate docker-registry
# It may take a while to be issued by LetsEncrypt, in the meantime a
# self-signed cert will be installed`
const RegistryIngressInstallMsg = `=======================================================================
= Docker Registry Ingress and cert-manager ClusterIssuer have been installed =
=======================================================================` +
"\n\n" + RegistryIngressInfoMsg + "\n\n" + pkg.ThanksForUsing
var registryIngressYamlTemplate = `
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: docker-registry
namespace: {{.Namespace}}
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod-registry
kubernetes.io/ingress.class: {{.IngressClass}}
{{.NginxMaxBuffer}}
spec:
rules:
- host: {{.IngressDomain}}
http:
paths:
- backend:
serviceName: docker-registry
servicePort: 5000
path: /
tls:
- hosts:
- {{.IngressDomain}}
secretName: docker-registry
---
apiVersion: cert-manager.io/v1alpha2
kind: ClusterIssuer
metadata:
name: letsencrypt-prod-registry
namespace: {{.Namespace}}
spec:
acme:
email: {{.CertmanagerEmail}}
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-prod-registry
solvers:
- http01:
ingress:
class: {{.IngressClass}}`

94
cmd/info.go Normal file
View File

@@ -0,0 +1,94 @@
package cmd
import (
"fmt"
"github.com/alexellis/bazaar/cmd/apps"
"github.com/spf13/cobra"
)
func MakeInfo() *cobra.Command {
info := &cobra.Command{
Use: "info",
Short: "Find info about a Kubernetes app",
Long: "Find info about how to use the installed Kubernetes app",
Example: ` bazaar app info [APP]
bazaar app info openfaas
bazaar app info inlets-operator
bazaar app info mongodb
bazaar app info
bazaar app info --help`,
SilenceUsage: true,
}
info.RunE = func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
fmt.Println("You can get info about: openfaas, inlets-operator, mongodb")
return nil
}
if len(args) != 1 {
return fmt.Errorf("you can only get info about exactly one installed app")
}
appName := args[0]
switch appName {
case "openfaas":
fmt.Printf("Info for app: %s\n", appName)
fmt.Println(apps.OpenFaaSInfoMsg)
case "nginx-ingress":
fmt.Printf("Info for app: %s\n", appName)
fmt.Println(apps.NginxIngressInfoMsg)
case "cert-manager":
fmt.Printf("Info for app: %s\n", appName)
fmt.Println(apps.CertManagerInfoMsg)
case "openfaas-ingress":
fmt.Printf("Info for app: %s\n", appName)
fmt.Println(apps.OpenfaasIngressInfoMsg)
case "inlets-operator":
fmt.Printf("Info for app: %s\n", appName)
fmt.Println(apps.InletsOperatorInfoMsg)
case "mongodb":
fmt.Printf("Info for app: %s\n", appName)
fmt.Println(apps.MongoDBInfoMsg)
case "metrics-server":
fmt.Printf("Info for app: %s\n", appName)
fmt.Println(apps.MetricsInfoMsg)
case "linkerd":
fmt.Printf("Info for app: %s\n", appName)
fmt.Println(apps.LinkerdInfoMsg)
case "cron-connector":
fmt.Printf("Info for app: %s\n", appName)
fmt.Println(apps.CronConnectorInfoMsg)
case "kafka-connector":
fmt.Printf("Info for app: %s\n", appName)
fmt.Println(apps.KafkaConnectorInfoMsg)
case "minio":
fmt.Printf("Info for app: %s\n", appName)
fmt.Println(apps.MinioInfoMsg)
case "postgresql":
fmt.Printf("Info for app: %s\n", appName)
fmt.Println(apps.PostgresqlInfoMsg)
case "kubernetes-dashboard":
fmt.Printf("Info for app: %s\n", appName)
fmt.Println(apps.KubernetesDashboardInfoMsg)
case "istio":
fmt.Printf("Info for app: %s\n", appName)
fmt.Println(apps.IstioInfoMsg)
case "crossplane":
fmt.Printf("Info for app: %s\n", appName)
fmt.Println(apps.CrossplanInfoMsg)
case "docker-registry-ingress":
fmt.Printf("Info for app: %s\n", appName)
fmt.Println(apps.RegistryIngressInfoMsg)
default:
return fmt.Errorf("no info available for app: %s", appName)
}
return nil
}
return info
}

45
cmd/version.go Normal file
View File

@@ -0,0 +1,45 @@
package cmd
import (
"fmt"
"github.com/morikuni/aec"
"github.com/spf13/cobra"
)
var (
Version string
GitCommit string
)
func PrintBazaarASCIIArt() {
bazaarLogo := aec.RedF.Apply(bazaarFigletStr)
fmt.Print(bazaarLogo)
}
func MakeVersion() *cobra.Command {
var command = &cobra.Command{
Use: "version",
Short: "Print the version",
Example: ` bazaar version`,
SilenceUsage: false,
}
command.Run = func(cmd *cobra.Command, args []string) {
PrintBazaarASCIIArt()
if len(Version) == 0 {
fmt.Println("Version: dev")
} else {
fmt.Println("Version:", Version)
}
fmt.Println("Git Commit:", GitCommit)
}
return command
}
const bazaarFigletStr = ` _ _____
| | _|___ / ___ _ _ _ __
| |/ / |_ \/ __| | | | '_ \
| < ___) \__ \ |_| | |_) |
|_|\_\____/|___/\__,_| .__/
|_|
`

BIN
docs/k3sup-app-install.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 746 KiB

BIN
docs/k3sup-cloud.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 KiB

BIN
docs/k3sup-rpi.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 391 KiB

178
get.sh Executable file
View File

@@ -0,0 +1,178 @@
#!/bin/bash
# Copyright OpenFaaS Author(s) 2019
#########################
# Repo specific content #
#########################
export VERIFY_CHECKSUM=0
export ALIAS="baz"
export OWNER=alexellis
export REPO=bazaar
export SUCCESS_CMD="$REPO version"
export BINLOCATION="/usr/local/bin"
###############################
# Content common across repos #
###############################
version=$(curl -sI https://github.com/$OWNER/$REPO/releases/latest | grep Location | awk -F"/" '{ printf "%s", $NF }' | tr -d '\r')
if [ ! $version ]; then
echo "Failed while attempting to install $REPO. Please manually install:"
echo ""
echo "1. Open your web browser and go to https://github.com/$OWNER/$REPO/releases"
echo "2. Download the latest release for your platform. Call it '$REPO'."
echo "3. chmod +x ./$REPO"
echo "4. mv ./$REPO $BINLOCATION"
if [ -n "$ALIAS_NAME" ]; then
echo "5. ln -sf $BINLOCATION/$REPO /usr/local/bin/$ALIAS_NAME"
fi
exit 1
fi
hasCli() {
hasCurl=$(which curl)
if [ "$?" = "1" ]; then
echo "You need curl to use this script."
exit 1
fi
}
checkHash(){
sha_cmd="sha256sum"
if [ ! -x "$(command -v $sha_cmd)" ]; then
sha_cmd="shasum -a 256"
fi
if [ -x "$(command -v $sha_cmd)" ]; then
targetFileDir=${targetFile%/*}
(cd $targetFileDir && curl -sSL $url.sha256|$sha_cmd -c >/dev/null)
if [ "$?" != "0" ]; then
rm $targetFile
echo "Binary checksum didn't match. Exiting"
exit 1
fi
fi
}
getPackage() {
uname=$(uname)
userid=$(id -u)
suffix=""
case $uname in
"Darwin")
suffix="-darwin"
;;
"MINGW"*)
suffix=".exe"
BINLOCATION="$HOME/bin"
mkdir -p $BINLOCATION
;;
"Linux")
arch=$(uname -m)
echo $arch
case $arch in
"aarch64")
suffix="-arm64"
;;
esac
case $arch in
"armv6l" | "armv7l")
suffix="-armhf"
;;
esac
;;
esac
targetFile="/tmp/$REPO$suffix"
if [ "$userid" != "0" ]; then
targetFile="$(pwd)/$REPO$suffix"
fi
if [ -e $targetFile ]; then
rm $targetFile
fi
url=https://github.com/$OWNER/$REPO/releases/download/$version/$REPO$suffix
echo "Downloading package $url as $targetFile"
curl -sSL $url --output $targetFile
if [ "$?" = "0" ]; then
if [ "$VERIFY_CHECKSUM" = "1" ]; then
checkHash
fi
chmod +x $targetFile
echo "Download complete."
if [ ! -w "$BINLOCATION" ]; then
echo
echo "============================================================"
echo " The script was run as a user who is unable to write"
echo " to $BINLOCATION. To complete the installation the"
echo " following commands may need to be run manually."
echo "============================================================"
echo
echo " sudo cp $REPO$suffix $BINLOCATION/$REPO"
if [ -n "$ALIAS_NAME" ]; then
echo " sudo ln -sf $BINLOCATION/$REPO $BINLOCATION/$ALIAS_NAME"
fi
echo
else
echo
echo "Running with sufficient permissions to attempt to move $REPO to $BINLOCATION"
if [ ! -w "$BINLOCATION/$REPO" ] && [ -f "$BINLOCATION/$REPO" ]; then
echo
echo "================================================================"
echo " $BINLOCATION/$REPO already exists and is not writeable"
echo " by the current user. Please adjust the binary ownership"
echo " or run sh/bash with sudo."
echo "================================================================"
echo
exit 1
fi
mv $targetFile $BINLOCATION/$REPO
if [ "$?" = "0" ]; then
echo "New version of $REPO installed to $BINLOCATION"
fi
if [ -e $targetFile ]; then
rm $targetFile
fi
if [ -n "$ALIAS_NAME" ]; then
if [ ! -L $BINLOCATION/$ALIAS_NAME ]; then
ln -s $BINLOCATION/$REPO $BINLOCATION/$ALIAS_NAME
echo "Creating alias '$ALIAS_NAME' for '$REPO'."
fi
fi
${SUCCESS_CMD}
fi
fi
}
hasCli
getPackage

14
go.mod Normal file
View File

@@ -0,0 +1,14 @@
module github.com/alexellis/bazaar
go 1.13
require (
github.com/alexellis/go-execute v0.0.0-20191207085904-961405ea7544
github.com/mitchellh/go-homedir v1.1.0
github.com/morikuni/aec v1.0.0
github.com/pkg/errors v0.8.1
github.com/sethvargo/go-password v0.1.2
github.com/spf13/cobra v0.0.5
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4
)

56
go.sum Normal file
View File

@@ -0,0 +1,56 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/alexellis/go-execute v0.0.0-20191207085904-961405ea7544 h1:KjtKgzxk/0wfp7vZxWUYPMiEJKep7FJ7C7o/vtHyc/Q=
github.com/alexellis/go-execute v0.0.0-20191207085904-961405ea7544/go.mod h1:zfRbgnPVxXCSpiKrg1CE72hNUWInqxExiaz2D9ppTts=
github.com/alexellis/bazaar v0.0.0-20200224182546-971f87dd4e5b h1:Y0Ier7hEuYh7Ic46d9/vgP34sxRCEjKbBs+/9XqF5zM=
github.com/alexellis/bazaar v0.0.0-20200224182546-971f87dd4e5b/go.mod h1:slULyLX94hIIP3/eVeZqQAqOwpLYK9Pljr8SfaR2nWI=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sethvargo/go-password v0.1.2 h1:fhBF4thiPVKEZ7R6+CX46GWJiPyCyXshbeqZ7lqEeYo=
github.com/sethvargo/go-password v0.1.2/go.mod h1:qKHfdSjT26DpHQWHWWR5+X4BI45jT31dg6j4RI2TEb0=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200210222208-86ce3cb69678 h1:wCWoJcFExDgyYx2m2hpHgwz8W3+FPdfldvIgzqDIhyg=
golang.org/x/crypto v0.0.0-20200210222208-86ce3cb69678/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

19
hack/platform-tag.sh Executable file
View File

@@ -0,0 +1,19 @@
#!/bin/sh
getPackage() {
suffix=""
arch=$(uname -m)
case $arch in
"aarch64")
suffix="-arm64"
;;
esac
case $arch in
"armv6l" | "armv7l")
suffix="-armhf"
;;
esac
}
getPackage
echo ${suffix}

33
main.go Normal file
View File

@@ -0,0 +1,33 @@
package main
import (
"os"
"github.com/alexellis/bazaar/cmd"
"github.com/spf13/cobra"
)
func main() {
cmdVersion := cmd.MakeVersion()
cmdInstall := cmd.MakeInstall()
cmdInfo := cmd.MakeInfo()
printbazaarASCIIArt := cmd.PrintBazaarASCIIArt
var rootCmd = &cobra.Command{
Use: "bazaar",
Run: func(cmd *cobra.Command, args []string) {
printbazaarASCIIArt()
cmd.Help()
},
}
rootCmd.AddCommand(cmdInstall)
rootCmd.AddCommand(cmdVersion)
rootCmd.AddCommand(cmdInfo)
if err := rootCmd.Execute(); err != nil {
os.Exit(1)
}
}

33
pkg/config/config.go Normal file
View File

@@ -0,0 +1,33 @@
package config
import (
"fmt"
"os"
"path"
)
// K3sVersion default version
const K3sVersion = "v1.17.2+k3s1"
func InitUserDir() (string, error) {
home := os.Getenv("HOME")
root := fmt.Sprintf("%s/.bazaar/", home)
if len(home) == 0 {
return home, fmt.Errorf("env-var HOME, not set")
}
binPath := path.Join(root, "/bin/")
err := os.MkdirAll(binPath, 0700)
if err != nil {
return binPath, err
}
helmPath := path.Join(root, "/.helm/")
helmErr := os.MkdirAll(helmPath, 0700)
if helmErr != nil {
return helmPath, helmErr
}
return root, nil
}

41
pkg/env/env.go vendored Normal file
View File

@@ -0,0 +1,41 @@
package env
import (
"log"
"os"
"path"
"strings"
execute "github.com/alexellis/go-execute/pkg/v1"
)
// GetClientArch returns a pair of arch and os
func GetClientArch() (string, string) {
task := execute.ExecTask{Command: "uname", Args: []string{"-m"}, StreamStdio: false}
res, err := task.Execute()
if err != nil {
log.Println(err)
}
arch := strings.TrimSpace(res.Stdout)
taskOS := execute.ExecTask{Command: "uname", Args: []string{"-s"}, StreamStdio: false}
resOS, errOS := taskOS.Execute()
if errOS != nil {
log.Println(errOS)
}
os := strings.TrimSpace(resOS.Stdout)
return arch, os
}
func LocalBinary(name, subdir string) string {
home := os.Getenv("HOME")
val := path.Join(home, ".bazaar/bin/")
if len(subdir) > 0 {
val = path.Join(val, subdir)
}
return path.Join(val, name)
}

104
pkg/helm/helm.go Normal file
View File

@@ -0,0 +1,104 @@
package helm
import (
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"path"
"strings"
"github.com/alexellis/bazaar/pkg/env"
execute "github.com/alexellis/go-execute/pkg/v1"
)
const helmVersion = "v2.16.0"
const helm3Version = "v3.0.2"
func TryDownloadHelm(userPath, clientArch, clientOS string, helm3 bool) (string, error) {
helmVal := "helm"
subdir := ""
if helm3 {
helmVal = "helm3"
subdir = "helm3"
}
helmBinaryPath := path.Join(path.Join(userPath, "bin"), helmVal)
if _, statErr := os.Stat(helmBinaryPath); statErr != nil {
DownloadHelm(userPath, clientArch, clientOS, subdir, helm3)
if !helm3 {
err := HelmInit()
if err != nil {
return "", err
}
}
}
return helmBinaryPath, nil
}
func GetHelmURL(arch, os, version string) string {
archSuffix := "amd64"
osSuffix := strings.ToLower(os)
if strings.HasPrefix(arch, "armv7") {
archSuffix = "arm"
} else if strings.HasPrefix(arch, "aarch64") {
archSuffix = "arm64"
}
return fmt.Sprintf("https://get.helm.sh/helm-%s-%s-%s.tar.gz", version, osSuffix, archSuffix)
}
func DownloadHelm(userPath, clientArch, clientOS, subdir string, helm3 bool) error {
useHelmVersion := helmVersion
if helm3 {
useHelmVersion = helm3Version
}
helmURL := GetHelmURL(clientArch, clientOS, useHelmVersion)
fmt.Println(helmURL)
parsedURL, _ := url.Parse(helmURL)
res, err := http.DefaultClient.Get(parsedURL.String())
if err != nil {
return err
}
dest := path.Join(path.Join(userPath, "bin"), subdir)
os.MkdirAll(dest, 0700)
defer res.Body.Close()
r := ioutil.NopCloser(res.Body)
untarErr := Untar(r, dest)
if untarErr != nil {
return untarErr
}
return nil
}
func HelmInit() error {
fmt.Printf("Running helm init.\n")
subdir := ""
task := execute.ExecTask{
Command: fmt.Sprintf("%s", env.LocalBinary("helm", subdir)),
Env: os.Environ(),
Args: []string{"init", "--client-only"},
StreamStdio: true,
}
res, err := task.Execute()
if err != nil {
return err
}
if res.ExitCode != 0 {
return fmt.Errorf("exit code %d", res.ExitCode)
}
return nil
}

132
pkg/helm/untar.go Normal file
View File

@@ -0,0 +1,132 @@
// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Edited on 2019-10-11 to remove support for nested folders when un-taring
// so that all files are placed in the same target directory
package helm
import (
"archive/tar"
"compress/gzip"
"fmt"
"io"
"log"
"os"
"path"
"path/filepath"
"strings"
"time"
)
// TODO(bradfitz): this was copied from x/build/cmd/buildlet/buildlet.go
// but there were some buildlet-specific bits in there, so the code is
// forked for now. Unfork and add some opts arguments here, so the
// buildlet can use this code somehow.
// Untar reads the gzip-compressed tar file from r and writes it into dir.
func Untar(r io.Reader, dir string) error {
return untar(r, dir)
}
func untar(r io.Reader, dir string) (err error) {
t0 := time.Now()
nFiles := 0
madeDir := map[string]bool{}
defer func() {
td := time.Since(t0)
if err == nil {
log.Printf("extracted tarball into %s: %d files, %d dirs (%v)", dir, nFiles, len(madeDir), td)
} else {
log.Printf("error extracting tarball into %s after %d files, %d dirs, %v: %v", dir, nFiles, len(madeDir), td, err)
}
}()
zr, err := gzip.NewReader(r)
if err != nil {
return fmt.Errorf("requires gzip-compressed body: %v", err)
}
tr := tar.NewReader(zr)
loggedChtimesError := false
for {
f, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
log.Printf("tar reading error: %v", err)
return fmt.Errorf("tar error: %v", err)
}
if !validRelPath(f.Name) {
return fmt.Errorf("tar contained invalid name error %q", f.Name)
}
baseFile := filepath.Base(f.Name)
abs := path.Join(dir, baseFile)
fmt.Println(abs, f.Name)
fi := f.FileInfo()
mode := fi.Mode()
switch {
case mode.IsDir():
break
case mode.IsRegular():
wf, err := os.OpenFile(abs, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode.Perm())
if err != nil {
return err
}
n, err := io.Copy(wf, tr)
if closeErr := wf.Close(); closeErr != nil && err == nil {
err = closeErr
}
if err != nil {
return fmt.Errorf("error writing to %s: %v", abs, err)
}
if n != f.Size {
return fmt.Errorf("only wrote %d bytes to %s; expected %d", n, abs, f.Size)
}
modTime := f.ModTime
if modTime.After(t0) {
// Clamp modtimes at system time. See
// golang.org/issue/19062 when clock on
// buildlet was behind the gitmirror server
// doing the git-archive.
modTime = t0
}
if !modTime.IsZero() {
if err := os.Chtimes(abs, modTime, modTime); err != nil && !loggedChtimesError {
// benign error. Gerrit doesn't even set the
// modtime in these, and we don't end up relying
// on it anywhere (the gomote push command relies
// on digests only), so this is a little pointless
// for now.
log.Printf("error changing modtime: %v (further Chtimes errors suppressed)", err)
loggedChtimesError = true // once is enough
}
}
nFiles++
default:
}
}
return nil
}
func validRelativeDir(dir string) bool {
if strings.Contains(dir, `\`) || path.IsAbs(dir) {
return false
}
dir = path.Clean(dir)
if strings.HasPrefix(dir, "../") || strings.HasSuffix(dir, "/..") || dir == ".." {
return false
}
return true
}
func validRelPath(p string) bool {
if p == "" || strings.Contains(p, `\`) || strings.HasPrefix(p, "/") || strings.Contains(p, "../") {
return false
}
return true
}

32
pkg/operator/operator.go Normal file
View File

@@ -0,0 +1,32 @@
package ssh
import (
goexecute "github.com/alexellis/go-execute/pkg/v1"
)
type CommandOperator interface {
Execute(command string) (CommandRes, error)
}
type ExecOperator struct {
}
func (ex ExecOperator) Execute(command string) (CommandRes, error) {
task := goexecute.ExecTask{
Command: command,
Shell: true,
StreamStdio: true,
}
res, err := task.Execute()
if err != nil {
return CommandRes{}, err
}
return CommandRes{
StdErr: []byte(res.Stderr),
StdOut: []byte(res.Stdout),
}, nil
}

93
pkg/operator/ssh.go Normal file
View File

@@ -0,0 +1,93 @@
package ssh
import (
"bytes"
"io"
"os"
"sync"
"golang.org/x/crypto/ssh"
)
type SSHOperator struct {
conn *ssh.Client
}
func (s SSHOperator) Close() error {
return s.conn.Close()
}
func NewSSHOperator(address string, config *ssh.ClientConfig) (*SSHOperator, error) {
conn, err := ssh.Dial("tcp", address, config)
if err != nil {
return nil, err
}
operator := SSHOperator{
conn: conn,
}
return &operator, nil
}
func (s SSHOperator) Execute(command string) (CommandRes, error) {
sess, err := s.conn.NewSession()
if err != nil {
return CommandRes{}, err
}
defer sess.Close()
sessStdOut, err := sess.StdoutPipe()
if err != nil {
return CommandRes{}, err
}
output := bytes.Buffer{}
wg := sync.WaitGroup{}
stdOutWriter := io.MultiWriter(os.Stdout, &output)
wg.Add(1)
go func() {
io.Copy(stdOutWriter, sessStdOut)
wg.Done()
}()
sessStderr, err := sess.StderrPipe()
if err != nil {
return CommandRes{}, err
}
errorOutput := bytes.Buffer{}
stdErrWriter := io.MultiWriter(os.Stderr, &errorOutput)
wg.Add(1)
go func() {
io.Copy(stdErrWriter, sessStderr)
wg.Done()
}()
err = sess.Run(command)
wg.Wait()
if err != nil {
return CommandRes{}, err
}
return CommandRes{
StdErr: errorOutput.Bytes(),
StdOut: output.Bytes(),
}, nil
}
type CommandRes struct {
StdOut []byte
StdErr []byte
}
func executeCommand(cmd string) (CommandRes, error) {
return CommandRes{}, nil
}

3
pkg/thanks.go Normal file
View File

@@ -0,0 +1,3 @@
package pkg
const ThanksForUsing = `Thanks for using bazaar!`