Skip to content

Commit

Permalink
🎉 Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
gabe565 committed May 22, 2021
0 parents commit b7c6051
Show file tree
Hide file tree
Showing 11 changed files with 1,323 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.idea
dist
out
kubedb
vendor
37 changes: 37 additions & 0 deletions cmd/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package cmd

import (
"github.com/clevyr/kubedb/cmd/dump"
"github.com/clevyr/kubedb/cmd/exec"
"github.com/clevyr/kubedb/cmd/restore"
"github.com/spf13/cobra"
"k8s.io/client-go/util/homedir"
"path/filepath"
)

var Command = &cobra.Command{
Use: "kubedb",
Short: "Interact with a database inside of Kubernetes",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
cmd.SilenceUsage = true
},
}

func Execute() error {
return Command.Execute()
}

func init() {
var kubeconfigDefault string
if home := homedir.HomeDir(); home != "" {
kubeconfigDefault = filepath.Join(home, ".kube", "config")
}
Command.PersistentFlags().String("kubeconfig", kubeconfigDefault, "absolute path to the kubeconfig file")
Command.PersistentFlags().StringP("namespace", "n", "", "the namespace scope for this CLI request")

Command.AddCommand(
exec.Command,
dump.Command,
restore.Command,
)
}
158 changes: 158 additions & 0 deletions cmd/dump/dump.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
package dump

import (
"bufio"
"bytes"
"compress/gzip"
"github.com/clevyr/kubedb/internal/kubernetes"
"github.com/clevyr/kubedb/internal/postgres"
"github.com/spf13/cobra"
"io"
_ "k8s.io/client-go/plugin/pkg/client/auth"
"log"
"os"
"path"
"strings"
"text/template"
"time"
)

var Command = &cobra.Command{
Use: "dump",
Aliases: []string{"d"},
Short: "Dump a database",
RunE: run,
}

var (
database string
username string
password string
directory string
gzipFile bool
ifExists bool
clean bool
noOwner bool
)

func init() {
Command.Flags().StringVarP(&database, "dbname", "d", "db", "Database name to connect to")
Command.Flags().StringVarP(&username, "username", "U", "postgres", "Database username")
Command.Flags().StringVarP(&password, "password", "p", "", "Database password")
Command.Flags().StringVarP(&directory, "directory", "C", ".", "Directory to dump to")

Command.Flags().BoolVar(&gzipFile, "gzip", false, "Gzip on disk")

Command.Flags().BoolVar(&ifExists, "if-exists", true, "use IF EXISTS when dropping objects")
Command.Flags().BoolVar(&clean, "clean", true, "clean (drop) database objects before recreating")
Command.Flags().BoolVar(&noOwner, "no-owner", true, "skip restoration of object ownership in plain-text format")
}

func run(cmd *cobra.Command, args []string) (err error) {
client, err := kubernetes.CreateClientForCmd(cmd)
if err != nil {
return err
}

postgresPod, err := postgres.GetPod(client)
if err != nil {
return err
}

if password == "" {
password, err = postgres.GetSecret(client)
if err != nil {
return err
}
}

filename, err := generateFilename(directory, client.Namespace)
if err != nil {
return err
}

if _, err := os.Stat(path.Dir(filename)); os.IsNotExist(err) {
err = os.Mkdir(path.Dir(filename), os.ModePerm)
if err != nil {
return err
}
}

outfile, err := os.Create(filename)
if err != nil {
return err
}
defer outfile.Close()
fileWriter := bufio.NewWriter(outfile)
defer fileWriter.Flush()

pr, pw := io.Pipe()

log.Println("Dumping \"" + postgresPod.Name + "\" to \"" + filename + "\"")

ch := make(chan error)
go func() {
err := kubernetes.Exec(client, postgresPod, buildCommand(), os.Stdin, pw, false)
pw.Close()
ch <- err
}()

if gzipFile {
_, err = io.Copy(fileWriter, pr)
} else {
var gzr *gzip.Reader
gzr, err = gzip.NewReader(pr)
if err != nil {
return err
}
defer gzr.Close()
_, err = io.Copy(fileWriter, gzr)
}
if err != nil {
return err
}

err = <-ch
if err == nil {
log.Println("Finished")
}
return err
}

func generateFilename(directory, namespace string) (string, error) {
directory = path.Clean(directory)
t, err := template.New("filename").Parse("{{.directory}}/{{.namespace}}-{{.now.Format \"2006-01-02-150405\"}}")
if err != nil {
return "", err
}

var tpl bytes.Buffer
data := map[string]interface{}{
"directory": directory,
"namespace": namespace,
"now": time.Now(),
}
err = t.Execute(&tpl, data)

tpl.WriteString(".sql")
if gzipFile {
tpl.WriteString(".gz")
}

return tpl.String(), err
}

func buildCommand() []string {
cmd := []string{"PGPASSWORD=" + password, "pg_dump", "--username=" + username, database}
if clean {
cmd = append(cmd, "--clean")
}
if noOwner {
cmd = append(cmd, "--no-owner")
}
if ifExists {
cmd = append(cmd, "--if-exists")
}
cmd = append(cmd, "|", "gzip", "--force")
return []string{"sh", "-c", strings.Join(cmd, " ")}
}
70 changes: 70 additions & 0 deletions cmd/exec/exec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package exec

import (
"github.com/clevyr/kubedb/internal/kubernetes"
"github.com/clevyr/kubedb/internal/postgres"
"github.com/docker/cli/cli/streams"
"github.com/spf13/cobra"
_ "k8s.io/client-go/plugin/pkg/client/auth"
"log"
"os"
"strings"
)

var Command = &cobra.Command{
Use: "exec",
Aliases: []string{"e", "shell"},
Short: "Connect to an interactive shell",
RunE: run,
}

var (
database string
username string
password string
)

func init() {
Command.Flags().StringVarP(&database, "dbname", "d", "db", "Database name to connect to")
Command.Flags().StringVarP(&username, "username", "U", "postgres", "Database username")
Command.Flags().StringVarP(&password, "password", "p", "", "Database password")
}

func run(cmd *cobra.Command, args []string) (err error) {
client, err := kubernetes.CreateClientForCmd(cmd)
if err != nil {
return err
}

postgresPod, err := postgres.GetPod(client)
if err != nil {
return err
}

if password == "" {
password, err = postgres.GetSecret(client)
if err != nil {
return err
}
}

log.Println("Execing into \"" + postgresPod.Name + "\"")

stdin := streams.NewIn(os.Stdin)
if err := stdin.SetRawTerminal(); err != nil{
return err
}
defer stdin.RestoreTerminal()

return kubernetes.Exec(client, postgresPod, buildCommand(args), stdin, os.Stdout, stdin.IsTerminal())
}

func buildCommand(args []string) []string {
var cmd []string
if len(args) == 0 {
cmd = []string{"PGPASSWORD=" + password, "psql", "--username=" + username, database}
} else {
cmd = append([]string{"PGPASSWORD=" + password, "exec"}, args...)
}
return []string{"sh", "-c", strings.Join(cmd, " ")}
}
Loading

0 comments on commit b7c6051

Please sign in to comment.