mirror of
https://github.com/fluxcd/flux2.git
synced 2025-11-02 10:48:03 +08:00
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
|
||||
}
|
||||
|
||||
1
go.mod
1
go.mod
@ -4,6 +4,7 @@ go 1.14
|
||||
|
||||
require (
|
||||
github.com/blang/semver v3.5.1+incompatible
|
||||
github.com/manifoldco/promptui v0.7.0
|
||||
github.com/spf13/cobra v0.0.6
|
||||
k8s.io/client-go v0.18.0
|
||||
)
|
||||
|
||||
17
go.sum
17
go.sum
@ -29,6 +29,12 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce
|
||||
github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ=
|
||||
github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/chzyer/logex v1.1.10 h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
@ -109,6 +115,8 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV
|
||||
github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok=
|
||||
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU=
|
||||
github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||
@ -120,8 +128,16 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw=
|
||||
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/manifoldco/promptui v0.7.0 h1:3l11YT8tm9MnwGFQ4kETwkzpAwY2Jt9lCrumCUW4+z4=
|
||||
github.com/manifoldco/promptui v0.7.0/go.mod h1:n4zTdgP0vr0S3w7/O/g98U+e0gwLScEXGwov2nIKuGQ=
|
||||
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
@ -228,6 +244,7 @@ golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
|
||||
Reference in New Issue
Block a user