Implement create source for ssh git repos
- generate host keys and SSH keys - prompt for deploy key setup - generate gitrepo source - wait for source to sync
This commit is contained in:
@@ -139,7 +139,7 @@ func kustomizeCheck(version string) bool {
|
||||
}
|
||||
|
||||
func kubernetesCheck(version string) bool {
|
||||
client, err := NewKubernetesClient()
|
||||
client, err := kubernetesClient()
|
||||
if err != nil {
|
||||
fmt.Println(`✗`, "kubernetes client initialization failed", err.Error())
|
||||
return false
|
||||
@@ -166,12 +166,3 @@ func kubernetesCheck(version string) bool {
|
||||
fmt.Println(`✔`, "kubernetes", v.String())
|
||||
return true
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
19
cmd/tk/create.go
Normal file
19
cmd/tk/create.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var createCmd = &cobra.Command{
|
||||
Use: "create",
|
||||
Short: "Create commands",
|
||||
}
|
||||
|
||||
var (
|
||||
interval string
|
||||
)
|
||||
|
||||
func init() {
|
||||
createCmd.PersistentFlags().StringVar(&interval, "interval", "1m", "source sync interval")
|
||||
rootCmd.AddCommand(createCmd)
|
||||
}
|
||||
156
cmd/tk/create_source.go
Normal file
156
cmd/tk/create_source.go
Normal file
@@ -0,0 +1,156 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"text/template"
|
||||
|
||||
"github.com/manifoldco/promptui"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var createSourceCmd = &cobra.Command{
|
||||
Use: "source [name]",
|
||||
Short: "Create source resource",
|
||||
Long: `
|
||||
The create source command generates a source.fluxcd.io resource and waits for it to sync.
|
||||
If a Git repository is specified, it will create a SSH deploy key.`,
|
||||
Example: ` create source podinfo --git-url ssh://git@github.com/stefanprodan/podinfo-deploy`,
|
||||
RunE: createSourceCmdRun,
|
||||
}
|
||||
|
||||
var (
|
||||
sourceGitURL string
|
||||
sourceGitBranch string
|
||||
)
|
||||
|
||||
func init() {
|
||||
createSourceCmd.Flags().StringVar(&sourceGitURL, "git-url", "", "git SSH address, in the format ssh://git@host/org/repository")
|
||||
createSourceCmd.Flags().StringVar(&sourceGitBranch, "git-branch", "master", "git branch")
|
||||
|
||||
createCmd.AddCommand(createSourceCmd)
|
||||
}
|
||||
|
||||
func createSourceCmdRun(cmd *cobra.Command, args []string) error {
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("source name is required")
|
||||
}
|
||||
name := args[0]
|
||||
|
||||
if sourceGitURL == "" {
|
||||
return fmt.Errorf("git-url is required")
|
||||
}
|
||||
|
||||
tmpDir, err := ioutil.TempDir("", name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
u, err := url.Parse(sourceGitURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("git URL parse failed: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println(`✚`, "generating host key for", u.Host)
|
||||
|
||||
keyscan := fmt.Sprintf("ssh-keyscan %s > %s/known_hosts", u.Host, tmpDir)
|
||||
if output, err := execCommand(keyscan); err != nil {
|
||||
return fmt.Errorf("ssh-keyscan failed: %s", output)
|
||||
}
|
||||
|
||||
fmt.Println(`✚`, "generating deploy key")
|
||||
|
||||
keygen := fmt.Sprintf("ssh-keygen -b 2048 -t rsa -f %s/identity -q -N \"\"", tmpDir)
|
||||
if output, err := execCommand(keygen); err != nil {
|
||||
return fmt.Errorf("ssh-keygen failed: %s", output)
|
||||
}
|
||||
|
||||
deployKey, err := execCommand(fmt.Sprintf("cat %s/identity.pub", tmpDir))
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read identity.pub: %w", err)
|
||||
}
|
||||
|
||||
fmt.Print(deployKey)
|
||||
prompt := promptui.Prompt{
|
||||
Label: "Have you added the deploy key to your repository",
|
||||
IsConfirm: true,
|
||||
}
|
||||
if _, err := prompt.Run(); err != nil {
|
||||
fmt.Println(`✗`, "aborting")
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Println(`✚`, "saving deploy key")
|
||||
files := fmt.Sprintf("--from-file=%s/identity --from-file=%s/identity.pub --from-file=%s/known_hosts",
|
||||
tmpDir, tmpDir, tmpDir)
|
||||
secret := fmt.Sprintf("kubectl -n %s create secret generic %s %s --dry-run=client -oyaml | kubectl apply -f-",
|
||||
namespace, name, files)
|
||||
if output, err := execCommand(secret); err != nil {
|
||||
return fmt.Errorf("kubectl create secret failed: %s", output)
|
||||
} else {
|
||||
fmt.Print(output)
|
||||
}
|
||||
|
||||
fmt.Println(`✚`, "generating source resource")
|
||||
|
||||
t, err := template.New("tmpl").Parse(gitSource)
|
||||
if err != nil {
|
||||
return fmt.Errorf("template parse error: %w", err)
|
||||
}
|
||||
|
||||
source := struct {
|
||||
Name string
|
||||
Namespace string
|
||||
GitURL string
|
||||
Interval string
|
||||
}{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
GitURL: sourceGitURL,
|
||||
Interval: interval,
|
||||
}
|
||||
|
||||
var data bytes.Buffer
|
||||
writer := bufio.NewWriter(&data)
|
||||
if err := t.Execute(writer, source); err != nil {
|
||||
return fmt.Errorf("template execution failed: %w", err)
|
||||
}
|
||||
if err := writer.Flush(); err != nil {
|
||||
return fmt.Errorf("source flush failed: %w", err)
|
||||
}
|
||||
|
||||
if output, err := execCommand(fmt.Sprintf("echo '%s' | kubectl apply -f-", data.String())); err != nil {
|
||||
return fmt.Errorf("kubectl create source failed: %s", output)
|
||||
} else {
|
||||
fmt.Print(output)
|
||||
}
|
||||
|
||||
fmt.Println(`✚`, "waiting for source sync")
|
||||
if output, err := execCommand(fmt.Sprintf(
|
||||
"kubectl -n %s wait gitrepository/%s --for=condition=ready --timeout=1m",
|
||||
namespace, name)); err != nil {
|
||||
return fmt.Errorf("source sync failed: %s", output)
|
||||
} else {
|
||||
fmt.Print(output)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var gitSource = `---
|
||||
apiVersion: source.fluxcd.io/v1alpha1
|
||||
kind: GitRepository
|
||||
metadata:
|
||||
name: {{.Name}}
|
||||
namespace: {{.Namespace}}
|
||||
spec:
|
||||
interval: {{.Interval}}
|
||||
url: {{.GitURL}}
|
||||
secretRef:
|
||||
name: {{.Name}}
|
||||
`
|
||||
@@ -25,7 +25,6 @@ on the configured Kubernetes cluster in ~/.kube/config`,
|
||||
var (
|
||||
installDryRun bool
|
||||
installManifestsPath string
|
||||
installNamespace string
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -33,8 +32,6 @@ func init() {
|
||||
"only print the object that would be applied")
|
||||
installCmd.Flags().StringVarP(&installManifestsPath, "manifests", "", "",
|
||||
"path to the manifest directory")
|
||||
installCmd.Flags().StringVarP(&installNamespace, "namespace", "", "gitops-system",
|
||||
"the namespace scope for this installation")
|
||||
|
||||
rootCmd.AddCommand(installCmd)
|
||||
}
|
||||
@@ -81,7 +78,7 @@ func installCmdRun(cmd *cobra.Command, args []string) error {
|
||||
fmt.Println(`✚`, "verifying installation...")
|
||||
for _, deployment := range []string{"source-controller", "kustomize-controller"} {
|
||||
command = fmt.Sprintf("kubectl -n %s rollout status deployment %s --timeout=2m",
|
||||
installNamespace, deployment)
|
||||
namespace, deployment)
|
||||
c = exec.CommandContext(ctx, "/bin/sh", "-c", command)
|
||||
c.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf)
|
||||
c.Stderr = io.MultiWriter(os.Stderr, &stderrBuf)
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -23,6 +24,7 @@ var rootCmd = &cobra.Command{
|
||||
|
||||
var (
|
||||
kubeconfig string
|
||||
namespace string
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -30,9 +32,11 @@ func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&kubeconfig, "kubeconfig", "", filepath.Join(home, ".kube", "config"),
|
||||
"path to the kubeconfig file")
|
||||
} else {
|
||||
checkCmd.PersistentFlags().StringVarP(&kubeconfig, "kubeconfig", "", "",
|
||||
rootCmd.PersistentFlags().StringVarP(&kubeconfig, "kubeconfig", "", "",
|
||||
"absolute path to the kubeconfig file")
|
||||
}
|
||||
rootCmd.PersistentFlags().StringVarP(&namespace, "namespace", "", "gitops-system",
|
||||
"the namespace scope for this operation")
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -53,7 +57,7 @@ func homeDir() string {
|
||||
return os.Getenv("USERPROFILE") // windows
|
||||
}
|
||||
|
||||
func NewKubernetesClient() (*kubernetes.Clientset, error) {
|
||||
func kubernetesClient() (*kubernetes.Clientset, error) {
|
||||
config, err := clientcmd.BuildConfigFromFlags("", kubeconfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -66,3 +70,12 @@ func NewKubernetesClient() (*kubernetes.Clientset, error) {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user