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:
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
bazaar
|
||||
bin/**
|
||||
kubeconfig
|
||||
.DS_Store
|
||||
.idea/
|
||||
mc
|
||||
4
LICENSE
4
LICENSE
@@ -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
23
Makefile
Normal 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
|
||||
81
README.md
81
README.md
@@ -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.
|
||||
|
||||
[](https://travis-ci.com/alexellis/bazaar)
|
||||
[](https://goreportcard.com/report/github.com/alexellis/bazaar)
|
||||
[](https://godoc.org/github.com/alexellis/bazaar) [](https://opensource.org/licenses/MIT)
|
||||

|
||||
|
||||
## 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
3
ci/hashgen.sh
Executable 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
94
cmd/app.go
Normal 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
156
cmd/apps/certmanager_app.go
Normal 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
154
cmd/apps/chart_app.go
Normal 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
|
||||
}
|
||||
146
cmd/apps/cronconnector_app.go
Normal file
146
cmd/apps/cronconnector_app.go
Normal 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
145
cmd/apps/crossplane_app.go
Normal 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
|
||||
269
cmd/apps/inletsoperator_app.go
Normal file
269
cmd/apps/inletsoperator_app.go
Normal 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
216
cmd/apps/istio_app.go
Normal 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)
|
||||
}
|
||||
157
cmd/apps/kafkaconnector_app.go
Normal file
157
cmd/apps/kafkaconnector_app.go
Normal 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
|
||||
84
cmd/apps/kubernetes_dashboard_app.go
Normal file
84
cmd/apps/kubernetes_dashboard_app.go
Normal 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
249
cmd/apps/kubernetes_exec.go
Normal 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
196
cmd/apps/linkerd_app.go
Normal 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
|
||||
144
cmd/apps/metricsserver_app.go
Normal file
144
cmd/apps/metricsserver_app.go
Normal 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
172
cmd/apps/minio_app.go
Normal 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
136
cmd/apps/mongodb_app.go
Normal 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
173
cmd/apps/nginx_app.go
Normal 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
292
cmd/apps/openfaas_app.go
Normal 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
|
||||
119
cmd/apps/openfaas_app_test.go
Normal file
119
cmd/apps/openfaas_app_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
210
cmd/apps/openfaas_ingress_app.go
Normal file
210
cmd/apps/openfaas_ingress_app.go
Normal 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}}`
|
||||
75
cmd/apps/openfaas_ingress_app_test.go
Normal file
75
cmd/apps/openfaas_ingress_app_test.go
Normal 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
150
cmd/apps/postgres_app.go
Normal 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
178
cmd/apps/registry_app.go
Normal 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
|
||||
192
cmd/apps/registry_ingress_app.go
Normal file
192
cmd/apps/registry_ingress_app.go
Normal 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
94
cmd/info.go
Normal 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
45
cmd/version.go
Normal 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
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
BIN
docs/k3sup-cloud.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 325 KiB |
BIN
docs/k3sup-rpi.png
Normal file
BIN
docs/k3sup-rpi.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 391 KiB |
178
get.sh
Executable file
178
get.sh
Executable 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
14
go.mod
Normal 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
56
go.sum
Normal 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
19
hack/platform-tag.sh
Executable 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
33
main.go
Normal 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
33
pkg/config/config.go
Normal 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
41
pkg/env/env.go
vendored
Normal 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
104
pkg/helm/helm.go
Normal 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
132
pkg/helm/untar.go
Normal 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
32
pkg/operator/operator.go
Normal 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
93
pkg/operator/ssh.go
Normal 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
3
pkg/thanks.go
Normal file
@@ -0,0 +1,3 @@
|
||||
package pkg
|
||||
|
||||
const ThanksForUsing = `Thanks for using bazaar!`
|
||||
Reference in New Issue
Block a user