diff --git a/bindata/bindata.go b/bindata/bindata.go index 5ea1ef1..669979b 100644 --- a/bindata/bindata.go +++ b/bindata/bindata.go @@ -1,12 +1,14 @@ -// Code generated for package bindata by go-bindata DO NOT EDIT. (@generated) +// Code generated by go-bindata. DO NOT EDIT. // sources: -// the-hook/Dockerfile -// the-hook/deployment-patch.yaml +// the-hook/Dockerfile (167B) +// the-hook/deployment-patch.yaml (755B) + package bindata import ( "bytes" "compress/gzip" + "crypto/sha256" "fmt" "io" "io/ioutil" @@ -19,7 +21,7 @@ import ( func bindataRead(data []byte, name string) ([]byte, error) { gz, err := gzip.NewReader(bytes.NewBuffer(data)) if err != nil { - return nil, fmt.Errorf("Read %q: %v", name, err) + return nil, fmt.Errorf("read %q: %w", name, err) } var buf bytes.Buffer @@ -27,7 +29,7 @@ func bindataRead(data []byte, name string) ([]byte, error) { clErr := gz.Close() if err != nil { - return nil, fmt.Errorf("Read %q: %v", name, err) + return nil, fmt.Errorf("read %q: %w", name, err) } if clErr != nil { return nil, err @@ -37,8 +39,9 @@ func bindataRead(data []byte, name string) ([]byte, error) { } type asset struct { - bytes []byte - info os.FileInfo + bytes []byte + info os.FileInfo + digest [sha256.Size]byte } type bindataFileInfo struct { @@ -48,32 +51,21 @@ type bindataFileInfo struct { modTime time.Time } -// Name return file name func (fi bindataFileInfo) Name() string { return fi.name } - -// Size return file size func (fi bindataFileInfo) Size() int64 { return fi.size } - -// Mode return file mode func (fi bindataFileInfo) Mode() os.FileMode { return fi.mode } - -// Mode return file modify time func (fi bindataFileInfo) ModTime() time.Time { return fi.modTime } - -// IsDir return file whether a directory func (fi bindataFileInfo) IsDir() bool { - return fi.mode&os.ModeDir != 0 + return false } - -// Sys return file is sys mode func (fi bindataFileInfo) Sys() interface{} { return nil } @@ -93,12 +85,12 @@ func theHookDockerfile() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "the-hook/Dockerfile", size: 167, mode: os.FileMode(420), modTime: time.Unix(1593157807, 0)} - a := &asset{bytes: bytes, info: info} + info := bindataFileInfo{name: "the-hook/Dockerfile", size: 167, mode: os.FileMode(0644), modTime: time.Unix(1593158731, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0x18, 0x2, 0xa4, 0x80, 0xee, 0xd7, 0xf2, 0x95, 0x86, 0x3b, 0x54, 0xd8, 0xe4, 0x7a, 0x6b, 0x60, 0xee, 0x69, 0x95, 0x29, 0xe2, 0x7a, 0xed, 0x33, 0x25, 0x75, 0x8, 0xa, 0x62, 0xf5, 0x6, 0xe4}} return a, nil } -var _theHookDeploymentPatchYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x51\xcb\x6e\x83\x30\x10\xbc\xe7\x2b\x46\x28\xc7\x92\x0f\x40\xea\xa9\x97\x56\x6a\x2b\x7e\x61\x83\xb7\x60\xc9\x0f\x64\x9b\x5c\x10\xff\x5e\xd9\x86\x00\x51\xaa\xc2\xc9\x3b\x33\x3b\xbb\xb3\x9c\x7c\xcf\x4d\x75\x02\x02\xeb\x5e\x51\xe0\xf8\x06\x34\x07\x12\x14\x28\x57\x80\xa2\x2b\x2b\xbf\x54\xc0\x38\xe2\x52\x53\x68\x3a\x16\x9f\x91\xfa\x26\xcd\x98\xa6\x0a\x45\x70\x03\x17\x49\xb7\x38\xc7\xaf\xb1\x26\x90\x34\xec\xee\x1e\x25\x0c\x69\xae\x92\xd3\xdb\xc2\xce\x36\xf7\x31\x52\x53\x3b\x6b\x3e\xe2\x73\xcb\x35\x56\x6b\x32\x62\xdd\x89\x5c\xbb\xd9\x50\xc9\x1b\x1b\xf6\xbe\x76\xf6\xca\x2b\xec\x98\x84\x7c\x82\x8f\x23\xe4\x0f\x2e\x5f\x76\x30\xc1\x6f\xc7\xdc\xac\x1a\x34\x67\x7c\x95\xa7\x06\x47\xa6\x65\x9c\xe5\x0b\xce\x3a\xf2\xa8\x5e\x9f\x39\xc4\xac\x89\xaf\x29\x74\x29\x4c\x96\x67\x69\x04\xf7\x6a\xcc\x97\x29\xfa\x78\xe0\x32\x69\xcb\xd8\x25\x31\x4d\xc5\x7e\x05\x36\x62\xdb\xfc\x88\xfc\x91\x2a\x67\xda\xff\xce\x83\x61\xca\xa3\xcb\x75\xd6\xe7\xc0\xbb\x64\xfd\xc3\x09\xde\x67\xd5\x3f\x21\x72\xfd\x1b\x00\x00\xff\xff\x94\x5a\x0c\x9d\xac\x02\x00\x00") +var _theHookDeploymentPatchYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x91\x41\x6a\xf3\x30\x10\x85\xf7\x39\xc5\x60\xbc\xfc\x9d\x03\x18\xfe\x55\xbb\x68\xa1\x81\x5c\x61\x22\x4f\x6c\x81\x46\x12\xb2\x1c\x28\x46\x77\x2f\x92\x62\x5b\x4e\x4b\x1b\xaf\xa4\x37\x33\x4f\xef\x1b\x1f\x46\x4b\xa2\x3d\x00\x78\x62\xab\xd0\x53\x3c\x03\x30\x79\xec\xd0\x63\xbe\x01\x28\xbc\x90\x1a\x97\x1b\xc0\x3c\xc3\xf1\x23\x6a\x10\x42\x0b\x95\x77\x13\x55\xa9\xb8\xd8\xc5\x4f\x18\xed\x51\x6a\x72\xeb\x60\x03\x1a\x99\xda\x34\xfe\xb2\x54\x21\x84\xd5\x57\x32\xf6\xf7\xfa\x7b\x3c\x96\x35\x61\x98\x51\x77\x5b\x08\x74\x7d\x11\x49\xc9\x1b\x69\x1a\xc7\xb3\x33\x17\xda\x64\x47\xd8\xc9\x1f\xf4\x9b\x51\x13\xd3\xc9\x4c\xda\x17\x26\x5b\x44\x8b\x5e\x0c\x8d\x30\xfa\x2a\x7b\x46\x5b\x74\x00\x70\x9c\x3a\xa3\x1f\x56\x92\xab\xec\x4f\x68\x93\x5b\x19\x39\x2d\xca\xa1\xee\x09\x6a\xf9\x0f\xea\x34\x08\xed\x7f\x38\xe6\x87\xf7\xbd\xcb\xd3\x55\x7e\x3b\x35\x37\xf3\x0c\xb5\x84\x10\xaa\x5f\x12\x64\xdf\xec\x19\xc5\x6f\x11\x48\x77\x9b\x96\xc9\x0b\xe8\xbf\x91\xc5\x42\xd8\xee\x52\x6c\x3f\xf3\x95\xac\x32\x9f\x4c\x7b\xfc\xe7\xe1\x9f\x46\x1f\xcc\x98\xb9\x77\x41\xec\xc3\x26\xde\xee\x5d\x0f\x61\xf2\x1a\xbe\x02\x00\x00\xff\xff\x97\x35\x00\xfe\xf3\x02\x00\x00") func theHookDeploymentPatchYamlBytes() ([]byte, error) { return bindataRead( @@ -113,8 +105,8 @@ func theHookDeploymentPatchYaml() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "the-hook/deployment-patch.yaml", size: 684, mode: os.FileMode(420), modTime: time.Unix(1593100633, 0)} - a := &asset{bytes: bytes, info: info} + info := bindataFileInfo{name: "the-hook/deployment-patch.yaml", size: 755, mode: os.FileMode(0644), modTime: time.Unix(1593534251, 0)} + a := &asset{bytes: bytes, info: info, digest: [32]uint8{0xf, 0xe6, 0x33, 0x57, 0xf2, 0x12, 0x93, 0x8d, 0xdb, 0xaf, 0x57, 0xad, 0x4f, 0x8d, 0x5, 0x64, 0xbb, 0xc1, 0x63, 0xb9, 0x8d, 0xee, 0xba, 0xc7, 0x60, 0xd4, 0x53, 0xd, 0x8b, 0xe9, 0x16, 0xc2}} return a, nil } @@ -122,8 +114,8 @@ func theHookDeploymentPatchYaml() (*asset, error) { // It returns an error if the asset could not be found or // could not be loaded. func Asset(name string) ([]byte, error) { - cannonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[cannonicalName]; ok { + canonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[canonicalName]; ok { a, err := f() if err != nil { return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err) @@ -133,6 +125,12 @@ func Asset(name string) ([]byte, error) { return nil, fmt.Errorf("Asset %s not found", name) } +// AssetString returns the asset contents as a string (instead of a []byte). +func AssetString(name string) (string, error) { + data, err := Asset(name) + return string(data), err +} + // MustAsset is like Asset but panics when Asset would return an error. // It simplifies safe initialization of global variables. func MustAsset(name string) []byte { @@ -144,12 +142,18 @@ func MustAsset(name string) []byte { return a } +// MustAssetString is like AssetString but panics when Asset would return an +// error. It simplifies safe initialization of global variables. +func MustAssetString(name string) string { + return string(MustAsset(name)) +} + // AssetInfo loads and returns the asset info for the given name. // It returns an error if the asset could not be found or // could not be loaded. func AssetInfo(name string) (os.FileInfo, error) { - cannonicalName := strings.Replace(name, "\\", "/", -1) - if f, ok := _bindata[cannonicalName]; ok { + canonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[canonicalName]; ok { a, err := f() if err != nil { return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err) @@ -159,6 +163,33 @@ func AssetInfo(name string) (os.FileInfo, error) { return nil, fmt.Errorf("AssetInfo %s not found", name) } +// AssetDigest returns the digest of the file with the given name. It returns an +// error if the asset could not be found or the digest could not be loaded. +func AssetDigest(name string) ([sha256.Size]byte, error) { + canonicalName := strings.Replace(name, "\\", "/", -1) + if f, ok := _bindata[canonicalName]; ok { + a, err := f() + if err != nil { + return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s can't read by error: %v", name, err) + } + return a.digest, nil + } + return [sha256.Size]byte{}, fmt.Errorf("AssetDigest %s not found", name) +} + +// Digests returns a map of all known files and their checksums. +func Digests() (map[string][sha256.Size]byte, error) { + mp := make(map[string][sha256.Size]byte, len(_bindata)) + for name := range _bindata { + a, err := _bindata[name]() + if err != nil { + return nil, err + } + mp[name] = a.digest + } + return mp, nil +} + // AssetNames returns the names of the assets. func AssetNames() []string { names := make([]string, 0, len(_bindata)) @@ -174,6 +205,9 @@ var _bindata = map[string]func() (*asset, error){ "the-hook/deployment-patch.yaml": theHookDeploymentPatchYaml, } +// AssetDebug is true if the assets were built with the debug flag enabled. +const AssetDebug = false + // AssetDir returns the file names below a certain // directory embedded in the file by go-bindata. // For example if you run go-bindata on data/... and data contains the @@ -183,15 +217,15 @@ var _bindata = map[string]func() (*asset, error){ // img/ // a.png // b.png -// then AssetDir("data") would return []string{"foo.txt", "img"} -// AssetDir("data/img") would return []string{"a.png", "b.png"} -// AssetDir("foo.txt") and AssetDir("notexist") would return an error +// then AssetDir("data") would return []string{"foo.txt", "img"}, +// AssetDir("data/img") would return []string{"a.png", "b.png"}, +// AssetDir("foo.txt") and AssetDir("notexist") would return an error, and // AssetDir("") will return []string{"data"}. func AssetDir(name string) ([]string, error) { node := _bintree if len(name) != 0 { - cannonicalName := strings.Replace(name, "\\", "/", -1) - pathList := strings.Split(cannonicalName, "/") + canonicalName := strings.Replace(name, "\\", "/", -1) + pathList := strings.Split(canonicalName, "/") for _, p := range pathList { node = node.Children[p] if node == nil { @@ -221,7 +255,7 @@ var _bintree = &bintree{nil, map[string]*bintree{ }}, }} -// RestoreAsset restores an asset under the given directory +// RestoreAsset restores an asset under the given directory. func RestoreAsset(dir, name string) error { data, err := Asset(name) if err != nil { @@ -239,14 +273,10 @@ func RestoreAsset(dir, name string) error { if err != nil { return err } - err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) - if err != nil { - return err - } - return nil + return os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime()) } -// RestoreAssets restores an asset under the given directory recursively +// RestoreAssets restores an asset under the given directory recursively. func RestoreAssets(dir, name string) error { children, err := AssetDir(name) // File @@ -264,6 +294,6 @@ func RestoreAssets(dir, name string) error { } func _filePath(dir, name string) string { - cannonicalName := strings.Replace(name, "\\", "/", -1) - return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...) + canonicalName := strings.Replace(name, "\\", "/", -1) + return filepath.Join(append([]string{dir}, strings.Split(canonicalName, "/")...)...) } diff --git a/cmd/actions/flags.go b/cmd/actions/flags.go index 643b576..d1ab742 100644 --- a/cmd/actions/flags.go +++ b/cmd/actions/flags.go @@ -92,23 +92,23 @@ func (n *Namespace) Set(value string) error { type Deployment struct { *KubeResource - ns *Namespace - res *appsv1.Deployment + ns Namespace + res appsv1.Deployment } -func newDeployment(ns *Namespace) *Deployment { - return &Deployment{newKubeResource(""), ns, nil} +func newDeployment(ns Namespace) *Deployment { + return &Deployment{KubeResource: newKubeResource(""), ns: ns} } func (d *Deployment) Set(value string) error { return gograpple.ValidateDeployment(d.l, d.ns.name, value) } -func (d *Deployment) Resource() (*appsv1.Deployment, error) { - if d.res == nil { +func (d *Deployment) Resource() (appsv1.Deployment, error) { + if d.res.Name == "" { res, err := gograpple.GetDeployment(d.l, d.ns.name, d.name) if err != nil { - return nil, err + return appsv1.Deployment{}, err } d.res = res } @@ -117,10 +117,10 @@ func (d *Deployment) Resource() (*appsv1.Deployment, error) { type Pod struct { *KubeResource - d *Deployment + d Deployment } -func newPod(d *Deployment) *Pod { +func newPod(d Deployment) *Pod { return &Pod{newKubeResource(""), d} } @@ -135,12 +135,12 @@ func (p *Pod) Set(value string) error { type Container struct { *KubeResource - d *Deployment - obj *corev1.Container + d Deployment + res corev1.Container } -func newContainer(d *Deployment) *Container { - return &Container{newKubeResource(""), d, nil} +func newContainer(d Deployment) *Container { + return &Container{KubeResource: newKubeResource(""), d: d} } func (c *Container) Set(value string) error { @@ -154,7 +154,7 @@ func (c *Container) Set(value string) error { func (c *Container) ValidateImage(image, tag *string) error { if *image == "" { - for _, container := range deployment.Spec.Template.Spec.Containers { + for _, container := range c.d.res.Spec.Template.Spec.Containers { if c.name == container.Name { pieces := strings.Split(container.Image, ":") if len(pieces) != 2 { diff --git a/cmd/actions/root.go b/cmd/actions/root.go index 2068c52..12e6047 100644 --- a/cmd/actions/root.go +++ b/cmd/actions/root.go @@ -50,9 +50,9 @@ var ( ) var ( - log = logrus.New() - l *logrus.Entry - deployment *v1.Deployment + log = logrus.New() + l *logrus.Entry + flagDeployment v1.Deployment rootCmd = &cobra.Command{ Use: "gograpple", @@ -71,19 +71,19 @@ var ( if err != nil { return err } - deployment, err = gograpple.GetDeployment(l, flagNamespace, args[0]) + flagDeployment, err = gograpple.GetDeployment(l, flagNamespace, args[0]) if err != nil { return err } - err = gograpple.ValidatePod(l, deployment, &flagPod) + err = gograpple.ValidatePod(l, flagDeployment, &flagPod) if err != nil { return err } - err = gograpple.ValidateContainer(l, deployment, &flagContainer) + err = gograpple.ValidateContainer(l, flagDeployment, &flagContainer) if err != nil { return err } - err = gograpple.ValidateImage(l, deployment, flagContainer, &flagImage, &flagTag) + err = gograpple.ValidateImage(l, flagDeployment, flagContainer, &flagImage, &flagTag) if err != nil { return err } @@ -96,14 +96,14 @@ var ( Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { if flagRollback { - _, err := rollback(l, flagNamespace, deployment) + _, err := rollback(l, flagNamespace, flagDeployment) return err } mounts, err := gograpple.ValidateMounts(flagDir, flagMounts) if err != nil { return err } - _, err = patch(l, flagNamespace, deployment, flagPod, flagContainer, flagImage, flagTag, mounts) + _, err = patch(l, flagNamespace, flagDeployment, flagPod, flagContainer, flagImage, flagTag, mounts) return err }, } @@ -112,7 +112,7 @@ var ( Short: "shell into the dev patched deployment", Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { - _, err := shell(l, deployment, flagPod) + _, err := shell(l, flagDeployment, flagPod) if err != nil { log.WithError(err).Fatalf("shelling into dev mode failed") } @@ -123,7 +123,7 @@ var ( Short: "start a headless delve debug server for .go input on a patched deployment", Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { - _, err := delve(l, deployment, flagPod, flagContainer, flagInput, flagArgs.items, flagListen.Host, flagListen.Port, flagCleanup, flagContinue, flagVscode) + _, err := delve(l, flagDeployment, flagPod, flagContainer, flagInput, flagArgs.items, flagListen.Host, flagListen.Port, flagCleanup, flagContinue, flagVscode) if err != nil { log.WithError(err).Fatalf("debug in dev mode failed") } @@ -131,43 +131,38 @@ var ( } ) -func patch(l *logrus.Entry, namespace string, deployment *v1.Deployment, pod, container, image, tag string, mounts []gograpple.Mount) (string, error) { - if gograpple.DeploymentIsPatched(l, deployment) { - l.Warnf("deployment already patched, running rollback first") - out, err := gograpple.Rollback(l, deployment.Namespace, deployment.Name) - if err != nil { - return out, err - } - deployment, err = gograpple.GetDeployment(l, deployment.Namespace, deployment.Name) - if err != nil { - return "", err - } +func patch(l *logrus.Entry, namespace string, d v1.Deployment, pod, container, image, tag string, mounts []gograpple.Mount) (string, error) { + if gograpple.DeploymentIsPatched(l, d) { + l.Warn("deployment already patched, rolling back first") } - return gograpple.Patch(l, deployment, container, image, tag, mounts) + if err := gograpple.RollbackRecursive(l, &d); err != nil { + return "", err + } + return gograpple.Patch(l, d, container, image, tag, mounts) } -func rollback(l *logrus.Entry, namespace string, deployment *v1.Deployment) (string, error) { - if !gograpple.DeploymentIsPatched(l, deployment) { +func rollback(l *logrus.Entry, namespace string, d v1.Deployment) (string, error) { + if !gograpple.DeploymentIsPatched(l, d) { return "", fmt.Errorf("deployment not patched, stopping rollback") } - return gograpple.Rollback(l, namespace, deployment.Name) + return "", gograpple.RollbackRecursive(l, &d) } -func shell(l *logrus.Entry, deployment *v1.Deployment, pod string) (string, error) { - if !gograpple.DeploymentIsPatched(l, deployment) { +func shell(l *logrus.Entry, d v1.Deployment, pod string) (string, error) { + if !gograpple.DeploymentIsPatched(l, d) { return "", fmt.Errorf("deployment not patched, stopping shell") } - return gograpple.Shell(l, deployment, pod) + return gograpple.Shell(l, d, pod) } -func delve(l *logrus.Entry, deployment *v1.Deployment, pod, container, input string, args []string, host string, port int, cleanup, dlvContinue, vscode bool) (string, error) { - if !gograpple.DeploymentIsPatched(l, deployment) { +func delve(l *logrus.Entry, d v1.Deployment, pod, container, input string, args []string, host string, port int, cleanup, dlvContinue, vscode bool) (string, error) { + if !gograpple.DeploymentIsPatched(l, d) { return "", fmt.Errorf("deployment not patched, stopping delve") } if cleanup { - return gograpple.DelveCleanup(l, deployment, pod, container) + return gograpple.DelveCleanup(l, d, pod, container) } - return gograpple.Delve(l, deployment, pod, container, input, args, dlvContinue, host, port, vscode) + return gograpple.Delve(l, d, pod, container, input, args, dlvContinue, host, port, vscode) } func Execute() { diff --git a/dev.go b/dev.go index 3630856..44c2388 100644 --- a/dev.go +++ b/dev.go @@ -21,16 +21,17 @@ import ( "github.com/go-delve/delve/service/api" "github.com/go-delve/delve/service/rpc2" "github.com/sirupsen/logrus" - "gopkg.in/yaml.v1" v1 "k8s.io/api/apps/v1" ) const ( - devDeploymentPatchFile = "deployment-patch.yaml" - defaultWaitTimeout = "30s" - conditionContainersReady = "condition=ContainersReady" - defaultPatchedLabel = "dev-mode-patched" - defaultPatchImage = "gograpple-patch:latest" + devDeploymentPatchFile = "deployment-patch.yaml" + defaultWaitTimeout = "30s" + conditionContainersReady = "condition=ContainersReady" + defaultPatchedLabel = "dev-mode-patched" + defaultPatchImage = "gograpple-patch:latest" + defaultConfigMapMount = "/etc/config/mounted" + defaultConfigMapDeploymentKey = "deployment.json" ) type Mount struct { @@ -39,18 +40,22 @@ type Mount struct { } type patchValues struct { - PatchedLabelName string - ContainerName string - Mounts []Mount - Image string + Label string + Deployment string + Container string + ConfigMapMount string + Mounts []Mount + Image string } -func newPatchValues(container string, mounts []Mount) *patchValues { +func newPatchValues(deployment, container string, mounts []Mount) *patchValues { return &patchValues{ - PatchedLabelName: defaultPatchedLabel, - ContainerName: container, - Mounts: mounts, - Image: defaultPatchImage, + Label: defaultPatchedLabel, + Deployment: deployment, + Container: container, + ConfigMapMount: defaultConfigMapMount, + Mounts: mounts, + Image: defaultPatchImage, } } @@ -90,7 +95,7 @@ func (la *launchArgs) toJson() (string, error) { return string(bytes), nil } -func DelveCleanup(l *logrus.Entry, deployment *v1.Deployment, pod, container string) (string, error) { +func DelveCleanup(l *logrus.Entry, deployment v1.Deployment, pod, container string) (string, error) { l.Infof("removing delve service") DeleteService(l, deployment, pod).Run() @@ -101,7 +106,7 @@ func DelveCleanup(l *logrus.Entry, deployment *v1.Deployment, pod, container str return "", nil } -func Delve(l *logrus.Entry, deployment *v1.Deployment, pod, container, input string, args []string, delveContinue bool, host string, port int, vscode bool) (string, error) { +func Delve(l *logrus.Entry, deployment v1.Deployment, pod, container, input string, args []string, delveContinue bool, host string, port int, vscode bool) (string, error) { goModDir, err := findGoProjectRoot(input) if err != nil { return "", fmt.Errorf("couldnt find go.mod dir for input %q", input) @@ -163,7 +168,7 @@ func Delve(l *logrus.Entry, deployment *v1.Deployment, pod, container, input str cmd = append(cmd, "--continue") } if len(args) == 0 { - args, err = getArgsFromPod(l, deployment.Namespace, pod, container) + args, err = getArgsFromConfigMap(l, deployment.Name, deployment.Namespace, container) if err != nil { return "", err } @@ -218,10 +223,20 @@ func Delve(l *logrus.Entry, deployment *v1.Deployment, pod, container, input str return "", nil } -func Patch(l *logrus.Entry, deployment *v1.Deployment, container, image, tag string, mounts []Mount) (string, error) { +func Patch(l *logrus.Entry, deployment v1.Deployment, container, image, tag string, mounts []Mount) (string, error) { + l.Infof("creating a ConfigMap with deployment data") + bs, err := json.Marshal(deployment) + if err != nil { + return "", err + } + data := map[string]string{defaultConfigMapDeploymentKey: string(bs)} + out, err := CreateConfigMap(l, deployment.Name, deployment.Namespace, data) + if err != nil { + return out, err + } l.Infof("waiting for deployment to get ready") - out, err := WaitForRollout(l, deployment.Name, deployment.Namespace, defaultWaitTimeout).Run() + out, err = WaitForRollout(l, deployment.Name, deployment.Namespace, defaultWaitTimeout).Run() if err != nil { return out, err } @@ -242,39 +257,24 @@ func Patch(l *logrus.Entry, deployment *v1.Deployment, container, image, tag str l.Infof("rendering deployment patch template") patch, err := renderTemplate( path.Join(theHookPath, devDeploymentPatchFile), - newPatchValues(container, mounts), + newPatchValues(deployment.Name, container, mounts), ) if err != nil { return "", err } l.Infof("patching deployment for development") - out, err = PatchDeployment(l, patch, deployment.Name, deployment.Namespace).Run() - if err != nil { - return out, err - } - - l.Infof("getting most recent pod with selector from deployment %v", deployment.Name) - pod, err := GetMostRecentPodBySelectors(l, deployment.Spec.Selector.MatchLabels, deployment.Namespace) - if err != nil { - return "", err - } + return PatchDeployment(l, patch, deployment.Name, deployment.Namespace).Run() +} - l.Infof("waiting for pod %v with %q", pod, conditionContainersReady) - out, err = WaitForPodState(l, deployment.Namespace, pod, conditionContainersReady, defaultWaitTimeout).Run() +func Rollback(l *logrus.Entry, namespace, deployment string) (string, error) { + l.Infof("removing configMap %v", deployment) + _, err := DeleteConfigMap(l, deployment, namespace) if err != nil { - return out, err - } - - l.Infof("copying deployment %v args into pod %v", deployment.Name, pod) - if err := copyArgsToPod(l, deployment, pod, container); err != nil { - return "", err + // may not exist + l.Warn(err) } - return "", nil -} - -func Rollback(l *logrus.Entry, namespace, deployment string) (string, error) { l.Infof("waiting for deployment to get ready") out, err := WaitForRollout(l, deployment, namespace, defaultWaitTimeout).Run() if err != nil { @@ -290,7 +290,28 @@ func Rollback(l *logrus.Entry, namespace, deployment string) (string, error) { return "", nil } -func Shell(l *logrus.Entry, deployment *v1.Deployment, pod string) (string, error) { +func RollbackRecursive(l *logrus.Entry, deployment *v1.Deployment) error { + for { + if !DeploymentIsPatched(l, *deployment) { + return nil + } + out, err := Rollback(l, deployment.Namespace, deployment.Name) + if err != nil { + l.Warn(out) + return err + } + *deployment, err = GetDeployment(l, deployment.Namespace, deployment.Name) + if err != nil { + return err + } + err = RollbackRecursive(l, deployment) + if err != nil { + return err + } + } +} + +func Shell(l *logrus.Entry, deployment v1.Deployment, pod string) (string, error) { l.Infof("waiting for pod %v with %q", pod, conditionContainersReady) out, err := WaitForPodState(l, deployment.Namespace, pod, conditionContainersReady, defaultWaitTimeout).Run() if err != nil { @@ -322,7 +343,7 @@ func CheckTCPConnection(host string, port int) (*net.TCPAddr, error) { return l.Addr().(*net.TCPAddr), nil } -func DeploymentIsPatched(l *logrus.Entry, deployment *v1.Deployment) bool { +func DeploymentIsPatched(l *logrus.Entry, deployment v1.Deployment) bool { _, ok := deployment.Spec.Template.ObjectMeta.Labels[defaultPatchedLabel] return ok } @@ -350,7 +371,7 @@ func ValidateDeployment(l *logrus.Entry, namespace, deployment string) error { return validateResource("deployment", deployment, fmt.Sprintf("for namespace %q", namespace), available) } -func ValidatePod(l *logrus.Entry, deployment *v1.Deployment, pod *string) error { +func ValidatePod(l *logrus.Entry, deployment v1.Deployment, pod *string) error { if *pod == "" { var err error *pod, err = GetMostRecentPodBySelectors(l, deployment.Spec.Selector.MatchLabels, deployment.Namespace) @@ -366,7 +387,7 @@ func ValidatePod(l *logrus.Entry, deployment *v1.Deployment, pod *string) error return validateResource("pod", *pod, fmt.Sprintf("for deployment %q", deployment.Name), available) } -func ValidateContainer(l *logrus.Entry, deployment *v1.Deployment, container *string) error { +func ValidateContainer(l *logrus.Entry, deployment v1.Deployment, container *string) error { if *container == "" { *container = deployment.Name } @@ -374,7 +395,7 @@ func ValidateContainer(l *logrus.Entry, deployment *v1.Deployment, container *st return validateResource("container", *container, fmt.Sprintf("for deployment %q", deployment.Name), available) } -func ValidateImage(l *logrus.Entry, deployment *v1.Deployment, container string, image, tag *string) error { +func ValidateImage(l *logrus.Entry, deployment v1.Deployment, container string, image, tag *string) error { if *image == "" { for _, c := range deployment.Spec.Template.Spec.Containers { if container == c.Name { @@ -468,37 +489,21 @@ func debugBuild(l *logrus.Entry, input, goModDir, output string, env []string) ( return squadron.Command(l, cmd...).Cwd(goModDir).Env(env).Run() } -func getArgsFromPod(l *logrus.Entry, namespace, pod, container string) ([]string, error) { - out, err := ExecPod(l, pod, container, namespace, []string{"cat", "/args.yaml"}).Run() +func getArgsFromConfigMap(l *logrus.Entry, configMap, namespace, container string) ([]string, error) { + out, err := GetConfigMapKey(l, configMap, namespace, defaultConfigMapDeploymentKey) if err != nil { return nil, err } - var args []string - if err := yaml.Unmarshal([]byte(out), &args); err != nil { + var d v1.Deployment + if err := json.Unmarshal([]byte(out), &d); err != nil { return nil, err } - return args, nil -} - -func copyArgsToPod(l *logrus.Entry, deployment *v1.Deployment, pod, container string) error { - var args []string - for _, c := range deployment.Spec.Template.Spec.Containers { + for _, c := range d.Spec.Template.Spec.Containers { if c.Name == container { - args = c.Args - break + return c.Args, nil } } - - argsSource := path.Join(os.TempDir(), "args.yaml") - if err := squadron.GenerateYaml(argsSource, args); err != nil { - return err - } - argsDestination := "/args.yaml" - _, err := CopyToPod(l, pod, container, deployment.Namespace, argsSource, argsDestination).Run() - if err != nil { - return err - } - return nil + return nil, fmt.Errorf("no args found for container %q", container) } func signalCapture(l *logrus.Entry) { diff --git a/kube.go b/kube.go index f76c844..f5e8412 100644 --- a/kube.go +++ b/kube.go @@ -119,7 +119,7 @@ func ExposePod(l *logrus.Entry, namespace, pod string, host string, port int) *s return squadron.Command(l, cmd...) } -func DeleteService(l *logrus.Entry, deployment *v1.Deployment, service string) *squadron.Cmd { +func DeleteService(l *logrus.Entry, deployment v1.Deployment, service string) *squadron.Cmd { cmd := []string{ "kubectl", "-n", deployment.Namespace, "delete", "service", service, @@ -127,7 +127,7 @@ func DeleteService(l *logrus.Entry, deployment *v1.Deployment, service string) * return squadron.Command(l, cmd...) } -func GetDeployment(l *logrus.Entry, namespace, deployment string) (*v1.Deployment, error) { +func GetDeployment(l *logrus.Entry, namespace, deployment string) (v1.Deployment, error) { cmd := []string{ "kubectl", "-n", namespace, "get", "deployment", deployment, @@ -135,13 +135,13 @@ func GetDeployment(l *logrus.Entry, namespace, deployment string) (*v1.Deploymen } out, err := squadron.Command(l, cmd...).Run() if err != nil { - return nil, err + return v1.Deployment{}, err } var d v1.Deployment if err := json.Unmarshal([]byte(out), &d); err != nil { - return nil, err + return v1.Deployment{}, err } - return &d, nil + return d, nil } func GetNamespaces(l *logrus.Entry) ([]string, error) { @@ -191,7 +191,7 @@ func GetPods(l *logrus.Entry, namespace string, selectors map[string]string) ([] return parseResources(out, "\n", "pod/") } -func GetContainers(l *logrus.Entry, deployment *v1.Deployment) []string { +func GetContainers(l *logrus.Entry, deployment v1.Deployment) []string { var containers []string for _, c := range deployment.Spec.Template.Spec.Containers { containers = append(containers, c.Name) @@ -239,11 +239,56 @@ func parseResources(out, delimiter, prefix string) ([]string, error) { return res, nil } - func RestartDeployment(l *logrus.Entry, deployment, namespace string) *squadron.Cmd { cmd := []string{ "kubectl", "-n", namespace, "rollout", "restart", fmt.Sprintf("deployment/%v", deployment), } return squadron.Command(l, cmd...) -} \ No newline at end of file +} + +func CreateConfigMapFromFile(l *logrus.Entry, name, namespace, path string) (string, error) { + cmd := []string{ + "kubectl", "-n", namespace, + "create", "configmap", name, + "--from-file", path, + } + return squadron.Command(l, cmd...).Run() +} + +func CreateConfigMap(l *logrus.Entry, name, namespace string, keyMap map[string]string) (string, error) { + cmd := []string{ + "kubectl", "-n", namespace, + "create", "configmap", name, + } + for key, value := range keyMap { + cmd = append(cmd, fmt.Sprintf("--from-literal=%v=%v", key, value)) + } + return squadron.Command(l, cmd...).Run() +} + +func DeleteConfigMap(l *logrus.Entry, name, namespace string) (string, error) { + cmd := []string{ + "kubectl", "-n", namespace, + "delete", "configmap", name, + } + return squadron.Command(l, cmd...).Run() +} + +func GetConfigMapKey(l *logrus.Entry, name, namespace, key string) (string, error) { + key = strings.ReplaceAll(key, ".", "\\.") + // jsonpath map key is not very fond of dots + cmd := []string{ + "kubectl", "-n", namespace, + "get", "configmap", name, + "-o", fmt.Sprintf("jsonpath={.data.%v}", key), + } + out, err := squadron.Command(l, cmd...).Run() + if err != nil { + return out, err + } + if out == "" { + return out, fmt.Errorf("no key %q found in ConfigMap %q", key, name) + } + return out, nil +} diff --git a/the-hook/deployment-patch.yaml b/the-hook/deployment-patch.yaml index a47ad13..932b472 100644 --- a/the-hook/deployment-patch.yaml +++ b/the-hook/deployment-patch.yaml @@ -3,27 +3,28 @@ spec: template: metadata: labels: - {{ .PatchedLabelName }}: "true" + {{ .Label }}: "true" spec: containers: - - name: {{ .ContainerName }} + - name: {{ .Container }} image: {{ .Image }} command: args: livenessProbe: readinessProbe: - {{ if .Mounts }} volumeMounts: + - name: patch-configmap + mountPath: {{ .ConfigMapMount }} {{ range $i, $mount := .Mounts }} - - mountPath: {{ $mount.MountPath }} - name: "patch-mount-{{ $i }}" + - name: "patch-mount-{{ $i }}" + mountPath: {{ $mount.MountPath }} {{ end }} - {{ end }} - {{ if .Mounts }} volumes: + - name: patch-configmap + configMap: + name: {{ .Deployment }} {{ range $i, $mount := .Mounts }} - name: "patch-mount-{{ $i }}" hostPath: path: {{ $mount.HostPath }} - {{ end }} - {{ end }} \ No newline at end of file + {{ end }} \ No newline at end of file