Merge pull request #4 from fluxcd/install-version
Allow multiple installs based on namespace and version
This commit is contained in:
12
.github/workflows/e2e.yaml
vendored
12
.github/workflows/e2e.yaml
vendored
@@ -37,10 +37,20 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
- name: Build
|
- name: Build
|
||||||
run: sudo go build -o ./bin/tk ./cmd/tk
|
run: sudo go build -o ./bin/tk ./cmd/tk
|
||||||
- name: Run integration tests
|
- name: Run check e2e tests
|
||||||
run: |
|
run: |
|
||||||
./bin/tk check
|
./bin/tk check
|
||||||
|
- name: Run install version e2e tests
|
||||||
|
run: |
|
||||||
|
./bin/tk install --version=master --namespace=test --verbose
|
||||||
|
- name: Run uninstall e2e tests
|
||||||
|
run: |
|
||||||
|
./bin/tk uninstall --namespace=test --crds --silent
|
||||||
|
- name: Run dev install e2e tests
|
||||||
|
run: |
|
||||||
./bin/tk install --manifests ./manifests/install/
|
./bin/tk install --manifests ./manifests/install/
|
||||||
|
- name: Run create source e2e tests
|
||||||
|
run: |
|
||||||
./bin/tk create source podinfo --git-url https://github.com/stefanprodan/podinfo-deploy --git-semver=">=0.0.1-rc.1 <0.1.0"
|
./bin/tk create source podinfo --git-url https://github.com/stefanprodan/podinfo-deploy --git-semver=">=0.0.1-rc.1 <0.1.0"
|
||||||
- name: Debug failure
|
- name: Debug failure
|
||||||
if: failure()
|
if: failure()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -31,17 +32,20 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func runCheckCmd(cmd *cobra.Command, args []string) error {
|
func runCheckCmd(cmd *cobra.Command, args []string) error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
logAction("starting verification")
|
logAction("starting verification")
|
||||||
checkFailed := false
|
checkFailed := false
|
||||||
if !sshCheck() {
|
if !sshCheck() {
|
||||||
checkFailed = true
|
checkFailed = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if !kubectlCheck(">=1.18.0") {
|
if !kubectlCheck(ctx, ">=1.18.0") {
|
||||||
checkFailed = true
|
checkFailed = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if !kustomizeCheck(">=3.5.0") {
|
if !kustomizeCheck(ctx, ">=3.5.0") {
|
||||||
checkFailed = true
|
checkFailed = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,14 +83,15 @@ func sshCheck() bool {
|
|||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func kubectlCheck(version string) bool {
|
func kubectlCheck(ctx context.Context, version string) bool {
|
||||||
_, err := exec.LookPath("kubectl")
|
_, err := exec.LookPath("kubectl")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logFailure("kubectl not found")
|
logFailure("kubectl not found")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
output, err := execCommand("kubectl version --client --short | awk '{ print $3 }'")
|
command := "kubectl version --client --short | awk '{ print $3 }'"
|
||||||
|
output, err := utils.execCommand(ctx, ModeCapture, command)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logFailure("kubectl version can't be determined")
|
logFailure("kubectl version can't be determined")
|
||||||
return false
|
return false
|
||||||
@@ -108,21 +113,23 @@ func kubectlCheck(version string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func kustomizeCheck(version string) bool {
|
func kustomizeCheck(ctx context.Context, version string) bool {
|
||||||
_, err := exec.LookPath("kustomize")
|
_, err := exec.LookPath("kustomize")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logFailure("kustomize not found")
|
logFailure("kustomize not found")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
output, err := execCommand("kustomize version --short | awk '{ print $1 }' | cut -c2-")
|
command := "kustomize version --short | awk '{ print $1 }' | cut -c2-"
|
||||||
|
output, err := utils.execCommand(ctx, ModeCapture, command)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logFailure("kustomize version can't be determined")
|
logFailure("kustomize version can't be determined")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(output, "kustomize/") {
|
if strings.Contains(output, "kustomize/") {
|
||||||
output, err = execCommand("kustomize version --short | awk '{ print $1 }' | cut -c12-")
|
command = "kustomize version --short | awk '{ print $1 }' | cut -c12-"
|
||||||
|
output, err = utils.execCommand(ctx, ModeCapture, command)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logFailure("kustomize version can't be determined")
|
logFailure("kustomize version can't be determined")
|
||||||
return false
|
return false
|
||||||
@@ -146,7 +153,7 @@ func kustomizeCheck(version string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func kubernetesCheck(version string) bool {
|
func kubernetesCheck(version string) bool {
|
||||||
client, err := kubernetesClient()
|
client, err := utils.kubeClient(kubeconfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logFailure("kubernetes client initialization failed: %s", err.Error())
|
logFailure("kubernetes client initialization failed: %s", err.Error())
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -5,11 +5,9 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
@@ -44,7 +42,6 @@ var (
|
|||||||
sourceGitSemver string
|
sourceGitSemver string
|
||||||
sourceUsername string
|
sourceUsername string
|
||||||
sourcePassword string
|
sourcePassword string
|
||||||
sourceVerbose bool
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -53,7 +50,6 @@ func init() {
|
|||||||
createSourceCmd.Flags().StringVar(&sourceGitSemver, "git-semver", "", "git tag semver range")
|
createSourceCmd.Flags().StringVar(&sourceGitSemver, "git-semver", "", "git tag semver range")
|
||||||
createSourceCmd.Flags().StringVarP(&sourceUsername, "username", "u", "", "basic authentication username")
|
createSourceCmd.Flags().StringVarP(&sourceUsername, "username", "u", "", "basic authentication username")
|
||||||
createSourceCmd.Flags().StringVarP(&sourcePassword, "password", "p", "", "basic authentication password")
|
createSourceCmd.Flags().StringVarP(&sourcePassword, "password", "p", "", "basic authentication password")
|
||||||
createSourceCmd.Flags().BoolVarP(&sourceVerbose, "verbose", "", false, "print generated source object")
|
|
||||||
|
|
||||||
createCmd.AddCommand(createSourceCmd)
|
createCmd.AddCommand(createSourceCmd)
|
||||||
}
|
}
|
||||||
@@ -84,12 +80,12 @@ func createSourceCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
withAuth := false
|
withAuth := false
|
||||||
if strings.HasPrefix(sourceGitURL, "ssh") {
|
if strings.HasPrefix(sourceGitURL, "ssh") {
|
||||||
if err := generateSSH(name, u.Host, tmpDir); err != nil {
|
if err := generateSSH(ctx, name, u.Host, tmpDir); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
withAuth = true
|
withAuth = true
|
||||||
} else if sourceUsername != "" && sourcePassword != "" {
|
} else if sourceUsername != "" && sourcePassword != "" {
|
||||||
if err := generateBasicAuth(name); err != nil {
|
if err := generateBasicAuth(ctx, name); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
withAuth = true
|
withAuth = true
|
||||||
@@ -129,69 +125,59 @@ func createSourceCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
return fmt.Errorf("source flush failed: %w", err)
|
return fmt.Errorf("source flush failed: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if sourceVerbose {
|
if verbose {
|
||||||
fmt.Print(data.String())
|
fmt.Print(data.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
command := fmt.Sprintf("echo '%s' | kubectl apply -f-", data.String())
|
command := fmt.Sprintf("echo '%s' | kubectl apply -f-", data.String())
|
||||||
c := exec.CommandContext(ctx, "/bin/sh", "-c", command)
|
if _, err := utils.execCommand(ctx, ModeStderrOS, command); err != nil {
|
||||||
|
|
||||||
var stdoutBuf, stderrBuf bytes.Buffer
|
|
||||||
c.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf)
|
|
||||||
c.Stderr = io.MultiWriter(os.Stderr, &stderrBuf)
|
|
||||||
|
|
||||||
err = c.Run()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("source apply failed")
|
return fmt.Errorf("source apply failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
logAction("waiting for source sync")
|
logAction("waiting for source sync")
|
||||||
if output, err := execCommand(fmt.Sprintf(
|
command = fmt.Sprintf("kubectl -n %s wait gitrepository/%s --for=condition=ready --timeout=1m",
|
||||||
"kubectl -n %s wait gitrepository/%s --for=condition=ready --timeout=1m",
|
namespace, name)
|
||||||
namespace, name)); err != nil {
|
if _, err := utils.execCommand(ctx, ModeStderrOS, command); err != nil {
|
||||||
return fmt.Errorf("source sync failed: %s", output)
|
return fmt.Errorf("source sync failed")
|
||||||
} else {
|
|
||||||
fmt.Print(output)
|
|
||||||
}
|
}
|
||||||
|
logSuccess("source %s is ready", name)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateBasicAuth(name string) error {
|
func generateBasicAuth(ctx context.Context, name string) error {
|
||||||
logAction("saving credentials")
|
logAction("saving credentials")
|
||||||
credentials := fmt.Sprintf("--from-literal=username='%s' --from-literal=password='%s'",
|
credentials := fmt.Sprintf("--from-literal=username='%s' --from-literal=password='%s'",
|
||||||
sourceUsername, sourcePassword)
|
sourceUsername, sourcePassword)
|
||||||
secret := fmt.Sprintf("kubectl -n %s create secret generic %s %s --dry-run=client -oyaml | kubectl apply -f-",
|
secret := fmt.Sprintf("kubectl -n %s create secret generic %s %s --dry-run=client -oyaml | kubectl apply -f-",
|
||||||
namespace, name, credentials)
|
namespace, name, credentials)
|
||||||
if output, err := execCommand(secret); err != nil {
|
if _, err := utils.execCommand(ctx, ModeOS, secret); err != nil {
|
||||||
return fmt.Errorf("kubectl create secret failed: %s", output)
|
return fmt.Errorf("kubectl create secret failed")
|
||||||
} else {
|
|
||||||
fmt.Print(output)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateSSH(name, host, tmpDir string) error {
|
func generateSSH(ctx context.Context, name, host, tmpDir string) error {
|
||||||
logAction("generating host key for %s", host)
|
logAction("generating host key for %s", host)
|
||||||
|
|
||||||
keyscan := fmt.Sprintf("ssh-keyscan %s > %s/known_hosts", host, tmpDir)
|
command := fmt.Sprintf("ssh-keyscan %s > %s/known_hosts", host, tmpDir)
|
||||||
if output, err := execCommand(keyscan); err != nil {
|
if _, err := utils.execCommand(ctx, ModeStderrOS, command); err != nil {
|
||||||
return fmt.Errorf("ssh-keyscan failed: %s", output)
|
return fmt.Errorf("ssh-keyscan failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
logAction("generating deploy key")
|
logAction("generating deploy key")
|
||||||
|
|
||||||
keygen := fmt.Sprintf("ssh-keygen -b 2048 -t rsa -f %s/identity -q -N \"\"", tmpDir)
|
command = fmt.Sprintf("ssh-keygen -b 2048 -t rsa -f %s/identity -q -N \"\"", tmpDir)
|
||||||
if output, err := execCommand(keygen); err != nil {
|
if _, err := utils.execCommand(ctx, ModeStderrOS, command); err != nil {
|
||||||
return fmt.Errorf("ssh-keygen failed: %s", output)
|
return fmt.Errorf("ssh-keygen failed")
|
||||||
}
|
}
|
||||||
|
|
||||||
deployKey, err := execCommand(fmt.Sprintf("cat %s/identity.pub", tmpDir))
|
command = fmt.Sprintf("cat %s/identity.pub", tmpDir)
|
||||||
if err != nil {
|
if deployKey, err := utils.execCommand(ctx, ModeCapture, command); err != nil {
|
||||||
return fmt.Errorf("unable to read identity.pub: %w", err)
|
return fmt.Errorf("unable to read identity.pub: %w", err)
|
||||||
|
} else {
|
||||||
|
fmt.Print(deployKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Print(deployKey)
|
|
||||||
prompt := promptui.Prompt{
|
prompt := promptui.Prompt{
|
||||||
Label: "Have you added the deploy key to your repository",
|
Label: "Have you added the deploy key to your repository",
|
||||||
IsConfirm: true,
|
IsConfirm: true,
|
||||||
@@ -206,10 +192,8 @@ func generateSSH(name, host, tmpDir string) error {
|
|||||||
tmpDir, tmpDir, tmpDir)
|
tmpDir, tmpDir, tmpDir)
|
||||||
secret := fmt.Sprintf("kubectl -n %s create secret generic %s %s --dry-run=client -oyaml | kubectl apply -f-",
|
secret := fmt.Sprintf("kubectl -n %s create secret generic %s %s --dry-run=client -oyaml | kubectl apply -f-",
|
||||||
namespace, name, files)
|
namespace, name, files)
|
||||||
if output, err := execCommand(secret); err != nil {
|
if _, err := utils.execCommand(ctx, ModeOS, secret); err != nil {
|
||||||
return fmt.Errorf("kubectl create secret failed: %s", output)
|
return fmt.Errorf("create secret failed")
|
||||||
} else {
|
|
||||||
fmt.Print(output)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -16,78 +15,178 @@ var installCmd = &cobra.Command{
|
|||||||
Use: "install",
|
Use: "install",
|
||||||
Short: "Install the toolkit components",
|
Short: "Install the toolkit components",
|
||||||
Long: `
|
Long: `
|
||||||
The Install command deploys the toolkit components
|
The install command deploys the toolkit components
|
||||||
on the configured Kubernetes cluster in ~/.kube/config`,
|
on the configured Kubernetes cluster in ~/.kube/config`,
|
||||||
Example: ` install --manifests github.com/fluxcd/toolkit//manifests/install --dry-run`,
|
Example: ` install --version=master --namespace=gitops-systems`,
|
||||||
RunE: installCmdRun,
|
RunE: installCmdRun,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
installDryRun bool
|
installDryRun bool
|
||||||
installManifestsPath string
|
installManifestsPath string
|
||||||
|
installVersion string
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
installCmd.Flags().BoolVarP(&installDryRun, "dry-run", "", false,
|
installCmd.Flags().BoolVarP(&installDryRun, "dry-run", "", false,
|
||||||
"only print the object that would be applied")
|
"only print the object that would be applied")
|
||||||
|
installCmd.Flags().StringVarP(&installVersion, "version", "v", "master",
|
||||||
|
"toolkit tag or branch")
|
||||||
installCmd.Flags().StringVarP(&installManifestsPath, "manifests", "", "",
|
installCmd.Flags().StringVarP(&installManifestsPath, "manifests", "", "",
|
||||||
"path to the manifest directory")
|
"path to the manifest directory, dev only")
|
||||||
|
|
||||||
rootCmd.AddCommand(installCmd)
|
rootCmd.AddCommand(installCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
func installCmdRun(cmd *cobra.Command, args []string) error {
|
func installCmdRun(cmd *cobra.Command, args []string) error {
|
||||||
if installManifestsPath == "" {
|
|
||||||
return fmt.Errorf("no manifests specified")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !strings.HasPrefix(installManifestsPath, "github.com/") {
|
|
||||||
if _, err := os.Stat(installManifestsPath); err != nil {
|
|
||||||
return fmt.Errorf("manifests not found: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
|
var kustomizePath string
|
||||||
|
if installVersion == "" && !strings.HasPrefix(installManifestsPath, "github.com/") {
|
||||||
|
if _, err := os.Stat(installManifestsPath); err != nil {
|
||||||
|
return fmt.Errorf("manifests not found: %w", err)
|
||||||
|
}
|
||||||
|
kustomizePath = installManifestsPath
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpDir, err := ioutil.TempDir("", namespace)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
logAction("generating install manifests")
|
||||||
|
if kustomizePath == "" {
|
||||||
|
err = genInstallManifests(installVersion, namespace, tmpDir)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("install failed: %w", err)
|
||||||
|
}
|
||||||
|
kustomizePath = tmpDir
|
||||||
|
}
|
||||||
|
|
||||||
|
manifest := path.Join(tmpDir, fmt.Sprintf("%s.yaml", namespace))
|
||||||
|
command := fmt.Sprintf("kustomize build %s > %s", kustomizePath, manifest)
|
||||||
|
if _, err := utils.execCommand(ctx, ModeStderrOS, command); err != nil {
|
||||||
|
return fmt.Errorf("install failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
command = fmt.Sprintf("cat %s", manifest)
|
||||||
|
if yaml, err := utils.execCommand(ctx, ModeCapture, command); err != nil {
|
||||||
|
return fmt.Errorf("install failed: %w", err)
|
||||||
|
} else {
|
||||||
|
if verbose {
|
||||||
|
fmt.Print(yaml)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
logSuccess("build completed")
|
||||||
|
|
||||||
|
logAction("installing components in %s namespace", namespace)
|
||||||
|
applyOutput := ModeStderrOS
|
||||||
|
if verbose {
|
||||||
|
applyOutput = ModeOS
|
||||||
|
}
|
||||||
dryRun := ""
|
dryRun := ""
|
||||||
if installDryRun {
|
if installDryRun {
|
||||||
dryRun = "--dry-run=client"
|
dryRun = "--dry-run=client"
|
||||||
|
applyOutput = ModeOS
|
||||||
}
|
}
|
||||||
command := fmt.Sprintf("kustomize build %s | kubectl apply -f- %s",
|
command = fmt.Sprintf("cat %s | kubectl apply -f- %s", manifest, dryRun)
|
||||||
installManifestsPath, dryRun)
|
if _, err := utils.execCommand(ctx, applyOutput, command); err != nil {
|
||||||
c := exec.CommandContext(ctx, "/bin/sh", "-c", command)
|
return fmt.Errorf("install failed")
|
||||||
|
|
||||||
var stdoutBuf, stderrBuf bytes.Buffer
|
|
||||||
c.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf)
|
|
||||||
c.Stderr = io.MultiWriter(os.Stderr, &stderrBuf)
|
|
||||||
|
|
||||||
logAction("installing components in %s namespace", namespace)
|
|
||||||
err := c.Run()
|
|
||||||
if err != nil {
|
|
||||||
logFailure("install failed")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if installDryRun {
|
if installDryRun {
|
||||||
logSuccess("install dry-run finished")
|
logSuccess("install dry-run finished")
|
||||||
return nil
|
return nil
|
||||||
|
} else {
|
||||||
|
logSuccess("install completed")
|
||||||
}
|
}
|
||||||
|
|
||||||
logAction("verifying installation")
|
logAction("verifying installation")
|
||||||
for _, deployment := range []string{"source-controller", "kustomize-controller"} {
|
for _, deployment := range []string{"source-controller", "kustomize-controller"} {
|
||||||
command = fmt.Sprintf("kubectl -n %s rollout status deployment %s --timeout=%s",
|
command = fmt.Sprintf("kubectl -n %s rollout status deployment %s --timeout=%s",
|
||||||
namespace, deployment, timeout.String())
|
namespace, deployment, timeout.String())
|
||||||
c = exec.CommandContext(ctx, "/bin/sh", "-c", command)
|
if _, err := utils.execCommand(ctx, applyOutput, command); err != nil {
|
||||||
c.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf)
|
return fmt.Errorf("install failed")
|
||||||
c.Stderr = io.MultiWriter(os.Stderr, &stderrBuf)
|
} else {
|
||||||
err := c.Run()
|
logSuccess("%s ready", deployment)
|
||||||
if err != nil {
|
|
||||||
logFailure("install failed")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logSuccess("install finished")
|
logSuccess("install finished")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var namespaceTmpl = `---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: {{.Namespace}}
|
||||||
|
`
|
||||||
|
|
||||||
|
var labelsTmpl = `---
|
||||||
|
apiVersion: builtin
|
||||||
|
kind: LabelTransformer
|
||||||
|
metadata:
|
||||||
|
name: labels
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/instance: {{.Namespace}}
|
||||||
|
app.kubernetes.io/version: "{{.Version}}"
|
||||||
|
fieldSpecs:
|
||||||
|
- path: metadata/labels
|
||||||
|
create: true
|
||||||
|
`
|
||||||
|
|
||||||
|
var kustomizationTmpl = `---
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
namespace: {{.Namespace}}
|
||||||
|
transformers:
|
||||||
|
- labels.yaml
|
||||||
|
resources:
|
||||||
|
- namespace.yaml
|
||||||
|
- roles
|
||||||
|
- github.com/fluxcd/toolkit/manifests/bases/source-controller?ref={{.Version}}
|
||||||
|
- github.com/fluxcd/toolkit/manifests/bases/kustomize-controller?ref={{.Version}}
|
||||||
|
- github.com/fluxcd/toolkit/manifests/policies?ref={{.Version}}
|
||||||
|
`
|
||||||
|
|
||||||
|
var kustomizationRolesTmpl = `---
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- github.com/fluxcd/toolkit/manifests/rbac?ref={{.Version}}
|
||||||
|
nameSuffix: -{{.Namespace}}
|
||||||
|
`
|
||||||
|
|
||||||
|
func genInstallManifests(ver, ns, tmpDir string) error {
|
||||||
|
model := struct {
|
||||||
|
Version string
|
||||||
|
Namespace string
|
||||||
|
}{
|
||||||
|
Version: ver,
|
||||||
|
Namespace: ns,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := utils.execTemplate(model, namespaceTmpl, path.Join(tmpDir, "namespace.yaml")); err != nil {
|
||||||
|
return fmt.Errorf("generate namespace failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := utils.execTemplate(model, labelsTmpl, path.Join(tmpDir, "labels.yaml")); err != nil {
|
||||||
|
return fmt.Errorf("generate labels failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := utils.execTemplate(model, kustomizationTmpl, path.Join(tmpDir, "kustomization.yaml")); err != nil {
|
||||||
|
return fmt.Errorf("generate kustomization failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(path.Join(tmpDir, "roles"), os.ModePerm); err != nil {
|
||||||
|
return fmt.Errorf("generate roles failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := utils.execTemplate(model, kustomizationRolesTmpl, path.Join(tmpDir, "roles/kustomization.yaml")); err != nil {
|
||||||
|
return fmt.Errorf("generate roles failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,15 +4,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/cobra/doc"
|
"github.com/spf13/cobra/doc"
|
||||||
"k8s.io/client-go/kubernetes"
|
|
||||||
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
_ "k8s.io/client-go/plugin/pkg/client/auth"
|
||||||
"k8s.io/client-go/tools/clientcmd"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var VERSION = "0.0.1"
|
var VERSION = "0.0.1"
|
||||||
@@ -30,6 +27,8 @@ var (
|
|||||||
kubeconfig string
|
kubeconfig string
|
||||||
namespace string
|
namespace string
|
||||||
timeout time.Duration
|
timeout time.Duration
|
||||||
|
verbose bool
|
||||||
|
utils Utils
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -37,6 +36,8 @@ func init() {
|
|||||||
"the namespace scope for this operation")
|
"the namespace scope for this operation")
|
||||||
rootCmd.PersistentFlags().DurationVarP(&timeout, "timeout", "", 5*time.Minute,
|
rootCmd.PersistentFlags().DurationVarP(&timeout, "timeout", "", 5*time.Minute,
|
||||||
"timeout for this operation")
|
"timeout for this operation")
|
||||||
|
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "", false,
|
||||||
|
"print generated objects")
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -56,29 +57,6 @@ func homeDir() string {
|
|||||||
return os.Getenv("USERPROFILE") // windows
|
return os.Getenv("USERPROFILE") // windows
|
||||||
}
|
}
|
||||||
|
|
||||||
func kubernetesClient() (*kubernetes.Clientset, error) {
|
|
||||||
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := kubernetes.NewForConfig(config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func execCommand(command string) (string, error) {
|
|
||||||
c := exec.Command("/bin/sh", "-c", command)
|
|
||||||
output, err := c.CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return string(output), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func logAction(format string, a ...interface{}) {
|
func logAction(format string, a ...interface{}) {
|
||||||
fmt.Println(`✚`, fmt.Sprintf(format, a...))
|
fmt.Println(`✚`, fmt.Sprintf(format, a...))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
"github.com/manifoldco/promptui"
|
"github.com/manifoldco/promptui"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -25,6 +21,7 @@ cluster role bindings and CRDs`,
|
|||||||
var (
|
var (
|
||||||
uninstallCRDs bool
|
uninstallCRDs bool
|
||||||
uninstallDryRun bool
|
uninstallDryRun bool
|
||||||
|
uninstallSilent bool
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -32,6 +29,8 @@ func init() {
|
|||||||
"removes all CRDs previously installed")
|
"removes all CRDs previously installed")
|
||||||
uninstallCmd.Flags().BoolVarP(&uninstallDryRun, "dry-run", "", false,
|
uninstallCmd.Flags().BoolVarP(&uninstallDryRun, "dry-run", "", false,
|
||||||
"only print the object that would be deleted")
|
"only print the object that would be deleted")
|
||||||
|
uninstallCmd.Flags().BoolVarP(&uninstallSilent, "silent", "", false,
|
||||||
|
"delete components without asking for confirmation")
|
||||||
|
|
||||||
rootCmd.AddCommand(uninstallCmd)
|
rootCmd.AddCommand(uninstallCmd)
|
||||||
}
|
}
|
||||||
@@ -43,14 +42,13 @@ func uninstallCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
dryRun := ""
|
dryRun := ""
|
||||||
if uninstallDryRun {
|
if uninstallDryRun {
|
||||||
dryRun = "--dry-run=client"
|
dryRun = "--dry-run=client"
|
||||||
} else {
|
} else if !uninstallSilent {
|
||||||
prompt := promptui.Prompt{
|
prompt := promptui.Prompt{
|
||||||
Label: fmt.Sprintf("Are you sure you want to delete the %s namespace", namespace),
|
Label: fmt.Sprintf("Are you sure you want to delete the %s namespace", namespace),
|
||||||
IsConfirm: true,
|
IsConfirm: true,
|
||||||
}
|
}
|
||||||
if _, err := prompt.Run(); err != nil {
|
if _, err := prompt.Run(); err != nil {
|
||||||
logFailure("aborting")
|
return fmt.Errorf("aborting")
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,19 +57,11 @@ func uninstallCmdRun(cmd *cobra.Command, args []string) error {
|
|||||||
kinds += ",crds"
|
kinds += ",crds"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logAction("uninstalling components")
|
||||||
command := fmt.Sprintf("kubectl delete %s -l app.kubernetes.io/instance=%s --timeout=%s %s",
|
command := fmt.Sprintf("kubectl delete %s -l app.kubernetes.io/instance=%s --timeout=%s %s",
|
||||||
kinds, namespace, timeout.String(), dryRun)
|
kinds, namespace, timeout.String(), dryRun)
|
||||||
c := exec.CommandContext(ctx, "/bin/sh", "-c", command)
|
if _, err := utils.execCommand(ctx, ModeOS, command); err != nil {
|
||||||
|
return fmt.Errorf("uninstall failed")
|
||||||
var stdoutBuf, stderrBuf bytes.Buffer
|
|
||||||
c.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf)
|
|
||||||
c.Stderr = io.MultiWriter(os.Stderr, &stderrBuf)
|
|
||||||
|
|
||||||
logAction("uninstalling components")
|
|
||||||
err := c.Run()
|
|
||||||
if err != nil {
|
|
||||||
logFailure("uninstall failed")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logSuccess("uninstall finished")
|
logSuccess("uninstall finished")
|
||||||
|
|||||||
100
cmd/tk/utils.go
Normal file
100
cmd/tk/utils.go
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/client-go/tools/clientcmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Utils struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type ExecMode string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ModeOS ExecMode = "os.stderr|stdout"
|
||||||
|
ModeStderrOS ExecMode = "os.stderr"
|
||||||
|
ModeCapture ExecMode = "capture.stderr|stdout"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (*Utils) execCommand(ctx context.Context, mode ExecMode, command string) (string, error) {
|
||||||
|
var stdoutBuf, stderrBuf bytes.Buffer
|
||||||
|
c := exec.CommandContext(ctx, "/bin/sh", "-c", command)
|
||||||
|
|
||||||
|
if mode == ModeStderrOS {
|
||||||
|
c.Stderr = io.MultiWriter(os.Stderr, &stderrBuf)
|
||||||
|
}
|
||||||
|
if mode == ModeOS {
|
||||||
|
c.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf)
|
||||||
|
c.Stderr = io.MultiWriter(os.Stderr, &stderrBuf)
|
||||||
|
}
|
||||||
|
|
||||||
|
if mode == ModeStderrOS || mode == ModeOS {
|
||||||
|
if err := c.Run(); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if mode == ModeCapture {
|
||||||
|
if output, err := c.CombinedOutput(); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else {
|
||||||
|
return string(output), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Utils) execTemplate(obj interface{}, tmpl, filename string) error {
|
||||||
|
t, err := template.New("tmpl").Parse(tmpl)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var data bytes.Buffer
|
||||||
|
writer := bufio.NewWriter(&data)
|
||||||
|
if err := t.Execute(writer, obj); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := writer.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Create(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
_, err = io.WriteString(file, data.String())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return file.Sync()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Utils) kubeClient(config string) (*kubernetes.Clientset, error) {
|
||||||
|
cfg, err := clientcmd.BuildConfigFromFlags("", config)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := kubernetes.NewForConfig(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
2
go.mod
2
go.mod
@@ -6,5 +6,5 @@ require (
|
|||||||
github.com/blang/semver v3.5.1+incompatible
|
github.com/blang/semver v3.5.1+incompatible
|
||||||
github.com/manifoldco/promptui v0.7.0
|
github.com/manifoldco/promptui v0.7.0
|
||||||
github.com/spf13/cobra v0.0.6
|
github.com/spf13/cobra v0.0.6
|
||||||
k8s.io/client-go v0.18.0
|
k8s.io/client-go v0.18.2
|
||||||
)
|
)
|
||||||
|
|||||||
12
go.sum
12
go.sum
@@ -298,12 +298,12 @@ gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
|||||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
k8s.io/api v0.18.0 h1:lwYk8Vt7rsVTwjRU6pzEsa9YNhThbmbocQlKvNBB4EQ=
|
k8s.io/api v0.18.2 h1:wG5g5ZmSVgm5B+eHMIbI9EGATS2L8Z72rda19RIEgY8=
|
||||||
k8s.io/api v0.18.0/go.mod h1:q2HRQkfDzHMBZL9l/y9rH63PkQl4vae0xRT+8prbrK8=
|
k8s.io/api v0.18.2/go.mod h1:SJCWI7OLzhZSvbY7U8zwNl9UA4o1fizoug34OV/2r78=
|
||||||
k8s.io/apimachinery v0.18.0 h1:fuPfYpk3cs1Okp/515pAf0dNhL66+8zk8RLbSX+EgAE=
|
k8s.io/apimachinery v0.18.2 h1:44CmtbmkzVDAhCpRVSiP2R5PPrC2RtlIv/MoB8xpdRA=
|
||||||
k8s.io/apimachinery v0.18.0/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA=
|
k8s.io/apimachinery v0.18.2/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA=
|
||||||
k8s.io/client-go v0.18.0 h1:yqKw4cTUQraZK3fcVCMeSa+lqKwcjZ5wtcOIPnxQno4=
|
k8s.io/client-go v0.18.2 h1:aLB0iaD4nmwh7arT2wIn+lMnAq7OswjaejkQ8p9bBYE=
|
||||||
k8s.io/client-go v0.18.0/go.mod h1:uQSYDYs4WhVZ9i6AIoEZuwUggLVEF64HOD37boKAtF8=
|
k8s.io/client-go v0.18.2/go.mod h1:Xcm5wVGXX9HAA2JJ2sSBUn3tCJ+4SVlCbl2MNNv+CIU=
|
||||||
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
|
||||||
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||||
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
|
||||||
|
|||||||
Reference in New Issue
Block a user