Skip to content

Commit f075216

Browse files
committed
feat: refactor restart helper options & force purge
1 parent f5644e4 commit f075216

25 files changed

Lines changed: 322 additions & 43 deletions

File tree

cmd/reset/pods.go

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package reset
33
import (
44
"context"
55
devspacecontext "github.com/loft-sh/devspace/pkg/devspace/context"
6+
"github.com/loft-sh/devspace/pkg/devspace/deploy"
67
"github.com/loft-sh/devspace/pkg/devspace/kubectl"
78

89
"github.com/loft-sh/devspace/cmd/flags"
@@ -18,6 +19,8 @@ import (
1819
type podsCmd struct {
1920
*flags.GlobalFlags
2021

22+
Force bool
23+
2124
log log.Logger
2225
}
2326

@@ -45,6 +48,7 @@ devspace reset pods
4548
return cmd.RunResetPods(f, cobraCmd, args)
4649
}}
4750

51+
podsCmd.Flags().BoolVar(&cmd.Force, "force", false, "If true will force resetting pods even though they might be still used by other DevSpace projects")
4852
return podsCmd
4953
}
5054

@@ -102,32 +106,32 @@ func (cmd *podsCmd) RunResetPods(f factory.Factory, cobraCmd *cobra.Command, arg
102106
ctx = ctx.WithDependencies(dependencies)
103107

104108
// reset the pods
105-
ResetPods(ctx, true)
109+
ResetPods(ctx, true, cmd.Force)
106110
return nil
107111
}
108112

109113
// ResetPods deletes the pods created by dev.replacePods
110-
func ResetPods(ctx *devspacecontext.Context, dependencies bool) {
111-
resetted := ResetPodsRecursive(ctx, dependencies)
114+
func ResetPods(ctx *devspacecontext.Context, dependencies, force bool) {
115+
resetted := ResetPodsRecursive(ctx, dependencies, force)
112116
if resetted == 0 {
113117
ctx.Log.Info("No dev pods to reset found")
114118
} else {
115119
ctx.Log.Donef("Successfully reset %d pods", resetted)
116120
}
117121
}
118122

119-
func ResetPodsRecursive(ctx *devspacecontext.Context, dependencies bool) int {
123+
func ResetPodsRecursive(ctx *devspacecontext.Context, dependencies, force bool) int {
120124
resetted := 0
121125
if dependencies {
122126
for _, d := range ctx.Dependencies {
123-
resetted += ResetPodsRecursive(ctx.AsDependency(d), dependencies)
127+
resetted += ResetPodsRecursive(ctx.AsDependency(d), dependencies, force)
124128
}
125129
}
126130

127131
// create pod replacer
128132
podReplacer := podreplace.NewPodReplacer()
129133
for _, replacePodCache := range ctx.Config.RemoteCache().ListDevPods() {
130-
deleted, err := podReplacer.RevertReplacePod(ctx, &replacePodCache)
134+
deleted, err := podReplacer.RevertReplacePod(ctx, &replacePodCache, &deploy.PurgeOptions{ForcePurge: force})
131135
if err != nil {
132136
ctx.Log.Warnf("Error resetting replaced pod: %v", err)
133137
} else if deleted {

cmd/run_pipeline.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ type RunPipelineCmd struct {
5858
BuildSequential bool
5959
MaxConcurrentBuilds int
6060

61+
ForcePurge bool
62+
6163
ForceDeploy bool
6264
SkipDeploy bool
6365

@@ -83,6 +85,7 @@ func (cmd *RunPipelineCmd) AddFlags(command *cobra.Command) {
8385
command.Flags().IntVar(&cmd.MaxConcurrentBuilds, "max-concurrent-builds", cmd.MaxConcurrentBuilds, "The maximum number of image builds built in parallel (0 for infinite)")
8486
command.Flags().BoolVar(&cmd.Render, "render", cmd.Render, "If true will render manifests and print them instead of actually deploying them")
8587

88+
command.Flags().BoolVar(&cmd.ForcePurge, "force-purge", cmd.ForcePurge, "Forces to purge every deployment even though it might be in use by another DevSpace project")
8689
command.Flags().BoolVarP(&cmd.ForceDeploy, "force-deploy", "d", cmd.ForceDeploy, "Forces to deploy every deployment")
8790
command.Flags().BoolVar(&cmd.SkipDeploy, "skip-deploy", cmd.SkipDeploy, "If enabled will skip deploying")
8891
command.Flags().StringVar(&cmd.Pipeline, "pipeline", cmd.Pipeline, "The pipeline to execute")
@@ -224,6 +227,9 @@ func (cmd *RunPipelineCmd) BuildOptions(configOptions *loader.ConfigOptions) *Pi
224227
RenderWriter: cmd.RenderWriter,
225228
SkipDeploy: cmd.SkipDeploy,
226229
},
230+
PurgeOptions: deploy.PurgeOptions{
231+
ForcePurge: cmd.ForcePurge,
232+
},
227233
DependencyOptions: types.DependencyOptions{
228234
Exclude: cmd.SkipDependency,
229235
Only: cmd.Dependency,
@@ -300,6 +306,9 @@ func (cmd *RunPipelineCmd) prepare(ctx context.Context, f factory.Factory, confi
300306
return nil, err
301307
}
302308

309+
// add root name to context
310+
ctx = values.WithRootName(ctx, configInterface.Config().Name)
311+
303312
// adjust config
304313
err = cmd.adjustConfig(configInterface)
305314
if err != nil {

e2e/tests/dependencies/dependencies.go

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,109 @@ var _ = DevSpaceDescribe("dependencies", func() {
4141
kubeClient, err = kube.NewKubeHelper()
4242
})
4343

44+
ginkgo.It("should not purge common dependency", func() {
45+
tempDir, err := framework.CopyToTempDir("tests/dependencies/testdata/purge")
46+
framework.ExpectNoError(err)
47+
defer framework.CleanupTempDir(initialDir, tempDir)
48+
49+
ns, err := kubeClient.CreateNamespace("dependencies")
50+
framework.ExpectNoError(err)
51+
defer func() {
52+
err := kubeClient.DeleteNamespace(ns)
53+
framework.ExpectNoError(err)
54+
}()
55+
56+
// create a new dev command and start it
57+
devCmd := &cmd.RunPipelineCmd{
58+
GlobalFlags: &flags.GlobalFlags{
59+
NoWarn: true,
60+
Namespace: ns,
61+
ConfigPath: "project1.yaml",
62+
},
63+
Pipeline: "dev",
64+
}
65+
err = devCmd.RunDefault(f)
66+
framework.ExpectNoError(err)
67+
68+
// make sure the dependencies are correctly deployed
69+
deploy, err := kubeClient.RawClient().AppsV1().Deployments(ns).Get(context.TODO(), "my-deployment", metav1.GetOptions{})
70+
framework.ExpectNoError(err)
71+
framework.ExpectEqual(deploy.Spec.Template.Spec.Containers[0].Image, "alpine")
72+
73+
// check if replica set exists & pod got replaced correctly
74+
list, err := kubeClient.Client().KubeClient().AppsV1().Deployments(ns).List(context.TODO(), metav1.ListOptions{LabelSelector: selector.ReplacedLabel + "=true"})
75+
framework.ExpectNoError(err)
76+
framework.ExpectEqual(len(list.Items), 1)
77+
framework.ExpectEqual(list.Items[0].Spec.Template.Spec.Containers[0].Command, []string{"sleep"})
78+
79+
// run second dev command
80+
devCmd = &cmd.RunPipelineCmd{
81+
GlobalFlags: &flags.GlobalFlags{
82+
NoWarn: true,
83+
Namespace: ns,
84+
ConfigPath: "project2.yaml",
85+
},
86+
Pipeline: "dev",
87+
}
88+
err = devCmd.RunDefault(f)
89+
framework.ExpectNoError(err)
90+
91+
// make sure the dependencies are correctly deployed
92+
deploy, err = kubeClient.RawClient().AppsV1().Deployments(ns).Get(context.TODO(), "my-deployment", metav1.GetOptions{})
93+
framework.ExpectNoError(err)
94+
framework.ExpectEqual(deploy.Spec.Template.Spec.Containers[0].Image, "alpine")
95+
96+
// check if replica set exists & pod got replaced correctly
97+
list, err = kubeClient.Client().KubeClient().AppsV1().Deployments(ns).List(context.TODO(), metav1.ListOptions{LabelSelector: selector.ReplacedLabel + "=true"})
98+
framework.ExpectNoError(err)
99+
framework.ExpectEqual(len(list.Items), 1)
100+
framework.ExpectEqual(list.Items[0].Spec.Template.Spec.Containers[0].Command, []string{"sleep"})
101+
102+
// purge project 1
103+
purgeCmd := &cmd.RunPipelineCmd{
104+
GlobalFlags: &flags.GlobalFlags{
105+
NoWarn: true,
106+
Namespace: ns,
107+
ConfigPath: "project1.yaml",
108+
},
109+
Pipeline: "purge",
110+
}
111+
err = purgeCmd.RunDefault(f)
112+
framework.ExpectNoError(err)
113+
114+
// make sure the dependencies are correctly deployed
115+
deploy, err = kubeClient.RawClient().AppsV1().Deployments(ns).Get(context.TODO(), "my-deployment", metav1.GetOptions{})
116+
framework.ExpectNoError(err)
117+
framework.ExpectEqual(deploy.Spec.Template.Spec.Containers[0].Image, "alpine")
118+
119+
// check if replica set exists & pod got replaced correctly
120+
list, err = kubeClient.Client().KubeClient().AppsV1().Deployments(ns).List(context.TODO(), metav1.ListOptions{LabelSelector: selector.ReplacedLabel + "=true"})
121+
framework.ExpectNoError(err)
122+
framework.ExpectEqual(len(list.Items), 1)
123+
framework.ExpectEqual(list.Items[0].Spec.Template.Spec.Containers[0].Command, []string{"sleep"})
124+
125+
// purge project 2
126+
purgeCmd = &cmd.RunPipelineCmd{
127+
GlobalFlags: &flags.GlobalFlags{
128+
NoWarn: true,
129+
Namespace: ns,
130+
ConfigPath: "project2.yaml",
131+
},
132+
Pipeline: "purge",
133+
}
134+
err = purgeCmd.RunDefault(f)
135+
framework.ExpectNoError(err)
136+
137+
// make sure the dependencies are correctly deployed
138+
deploy, err = kubeClient.RawClient().AppsV1().Deployments(ns).Get(context.TODO(), "my-deployment", metav1.GetOptions{})
139+
framework.ExpectError(err)
140+
141+
// check if replica set exists & pod got replaced correctly
142+
list, err = kubeClient.Client().KubeClient().AppsV1().Deployments(ns).List(context.TODO(), metav1.ListOptions{LabelSelector: selector.ReplacedLabel + "=true"})
143+
framework.ExpectNoError(err)
144+
framework.ExpectEqual(len(list.Items), 0)
145+
})
146+
44147
ginkgo.It("should deploy git dependency", func() {
45148
tempDir, err := framework.CopyToTempDir("tests/dependencies/testdata/git")
46149
framework.ExpectNoError(err)
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
version: v2beta1
2+
name: dependency
3+
4+
deployments:
5+
my-deployment:
6+
helm:
7+
values:
8+
containers:
9+
- image: alpine
10+
11+
dev:
12+
my-dev:
13+
imageSelector: alpine
14+
command: ["sleep"]
15+
args: ["1000000"]
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
version: v2beta1
2+
name: project1
3+
4+
dependencies:
5+
dependency:
6+
path: dependency.yaml
7+
pipeline: dev
8+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
version: v2beta1
2+
name: project2
3+
4+
dependencies:
5+
dependency:
6+
path: dependency.yaml
7+
pipeline: dev
8+

helper/cmd/sync/upstream.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ func ensurePath(args []string) (string, error) {
9191
}
9292

9393
if absolutePath == "/" && path != "/" {
94-
return "", fmt.Errorf("you are trying to sync the complete container root (/). By default this is not allowed, because this usually leads to unwanted behaviour. Please specify the correct container directory via the `--container-path` flag or `.containerPath` option")
94+
return "", fmt.Errorf("you are trying to sync the complete container root (/). By default this is not allowed, because this usually leads to unwanted behaviour. Please specify the correct container directory via the `--path` flag or `.path: localPath:/remotePath` option")
9595
}
9696

9797
return absolutePath, nil

pkg/devspace/config/remotecache/schema.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ type DevPodCache struct {
6565
// Name is the name of the dev pod
6666
Name string `yaml:"name,omitempty"`
6767

68+
// Projects are the top level projects that have started this dev configuration
69+
Projects []string `yaml:"projects,omitempty"`
70+
6871
// Namespace is the namespace where the replace happened
6972
Namespace string `yaml:"namespace,omitempty"`
7073

@@ -82,6 +85,9 @@ type DevPodCache struct {
8285
type DeploymentCache struct {
8386
Name string `yaml:"name,omitempty"`
8487

88+
// Projects are the top level projects that have deployed this deployment
89+
Projects []string `yaml:"projects,omitempty"`
90+
8591
// DeploymentConfigHash is the deployment config hashed
8692
DeploymentConfigHash string `yaml:"deploymentConfigHash,omitempty"`
8793

pkg/devspace/config/versions/latest/schema.go

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -818,6 +818,9 @@ type DevContainer struct {
818818
// Target Container architecture to use for the devspacehelper (currently amd64 or arm64). Defaults to amd64, but
819819
// devspace tries to find out the architecture by itself by looking at the node this container runs on.
820820
Arch ContainerArchitecture `yaml:"arch,omitempty" json:"arch,omitempty"`
821+
// RestartHelper holds restart helper specific configuration. The restart helper is used to delay starting of
822+
// the container and restarting it and is injected via an annotation in the replaced pod.
823+
RestartHelper *RestartHelper `yaml:"restartHelper,omitempty" json:"restartHelper,omitempty"`
821824

822825
// ReversePorts are port mappings to make local ports available inside the container
823826
ReversePorts []*PortMapping `yaml:"reversePorts,omitempty" json:"reversePorts,omitempty"`
@@ -832,11 +835,6 @@ type DevContainer struct {
832835
// Env can be used to add environment variables to the container. DevSpace will
833836
// not replace existing environment variables if an environment variable is defined here.
834837
Env []EnvVar `yaml:"env,omitempty" json:"env,omitempty"`
835-
// RestartHelperPath defines the path to the restart helper that might be used if certain config
836-
// options are enabled
837-
RestartHelperPath string `yaml:"restartHelperPath,omitempty" json:"restartHelperPath,omitempty"`
838-
// DisableRestartHelper signals DevSpace to not inject the restart helper
839-
DisableRestartHelper bool `yaml:"disableRestartHelper,omitempty" json:"disableRestartHelper,omitempty"`
840838

841839
// Terminal allows you to tell DevSpace to open a terminal with screen support to this container
842840
Terminal *Terminal `yaml:"terminal,omitempty" json:"terminal,omitempty"`
@@ -854,6 +852,14 @@ type DevContainer struct {
854852
ProxyCommands []*ProxyCommand `yaml:"proxyCommands,omitempty" json:"proxyCommands,omitempty"`
855853
}
856854

855+
type RestartHelper struct {
856+
// Path defines the path to the restart helper that might be used if certain config
857+
// options are enabled
858+
Path string `yaml:"path,omitempty" json:"path,omitempty"`
859+
// Disable signals DevSpace to not inject the restart helper
860+
Disable bool `yaml:"disable,omitempty" json:"disable,omitempty"`
861+
}
862+
857863
type ProxyCommand struct {
858864
// Command is the name of the command that should be available in the remote container. DevSpace
859865
// will create a small script for that inside the container that redirect command execution to

pkg/devspace/config/versions/v1beta11/upgrade.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -771,7 +771,10 @@ func (c *Config) mergeDevConfig(log log.Logger) (map[string]*next.DevPod, error)
771771
// disable sync replace
772772
for k := range devPods {
773773
for i := range devPods[k].Containers {
774-
devPods[k].Containers[i].DisableRestartHelper = true
774+
if devPods[k].Containers[i].RestartHelper == nil {
775+
devPods[k].Containers[i].RestartHelper = &next.RestartHelper{}
776+
}
777+
devPods[k].Containers[i].RestartHelper.Disable = true
775778
}
776779
}
777780

@@ -792,7 +795,13 @@ func (c *Config) mergeDevConfig(log log.Logger) (map[string]*next.DevPod, error)
792795
func getMatchingDevContainer(devPod *next.DevPod, containerName string) *next.DevContainer {
793796
for key, container := range devPod.Containers {
794797
if container.Container == containerName || container.Container == "" {
795-
devPod.Containers[key].Container = containerName
798+
if container.Container == "" && containerName != "" {
799+
devContainer := container
800+
devContainer.Container = containerName
801+
delete(devPod.Containers, key)
802+
devPod.Containers[containerName] = devContainer
803+
return devPod.Containers[containerName]
804+
}
796805
return devPod.Containers[key]
797806
} else if containerName == "" {
798807
return devPod.Containers[key]

0 commit comments

Comments
 (0)