Skip to content

Commit 218436a

Browse files
authored
Merge pull request #2123 from lizardruss/master
Improvements to devspace init
2 parents c8a7310 + 1ccfe23 commit 218436a

4 files changed

Lines changed: 259 additions & 50 deletions

File tree

cmd/init.go

Lines changed: 106 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -334,64 +334,125 @@ func (cmd *InitCmd) initDevspace(f factory.Factory, configLoader loader.ConfigLo
334334
break
335335
}
336336

337+
developProject := "I want to develop this project and my current working dir contains the source code"
338+
deployProject := "I just want to deploy this project"
339+
defaultProjectAction := deployProject
340+
if !configureManager.IsRemoteDeployment(imageName) {
341+
defaultProjectAction = developProject
342+
}
343+
developOrDeployProject, err := cmd.log.Question(&survey.QuestionOptions{
344+
Question: "Do you want to develop this project with DevSpace or just deploy it? [Use arrows to move, type to filter]",
345+
Options: []string{developProject, deployProject},
346+
DefaultValue: defaultProjectAction,
347+
})
348+
if err != nil {
349+
return err
350+
}
351+
337352
image := ""
338-
for {
339-
if !mustAddComponentChart {
340-
manifests, err := cmd.render(f, config)
341-
if err != nil {
342-
return errors.Wrap(err, "error rendering deployment")
343-
}
353+
if developOrDeployProject == developProject {
354+
for {
355+
if !mustAddComponentChart {
356+
manifests, err := cmd.render(f, config)
357+
if err != nil {
358+
return errors.Wrap(err, "error rendering deployment")
359+
}
344360

345-
images, err := parseImages(manifests)
346-
if err != nil {
347-
return errors.Wrap(err, "error parsing images")
348-
}
361+
images, err := parseImages(manifests)
362+
if err != nil {
363+
return errors.Wrap(err, "error parsing images")
364+
}
349365

350-
if len(images) == 0 {
351-
return fmt.Errorf("no images found for the selected deployments")
352-
}
366+
imageManual := "Manually enter the image I want to work on"
367+
imageSkip := "Skip (do not add dev configuration for any images)"
368+
imageAnswer := ""
353369

354-
image, err = cmd.log.Question(&survey.QuestionOptions{
355-
Question: "Which image do you want to develop with DevSpace?",
356-
DefaultValue: images[0],
357-
Options: images,
358-
})
359-
if err != nil {
360-
return err
370+
if len(images) > 0 {
371+
imageAnswer, err = cmd.log.Question(&survey.QuestionOptions{
372+
Question: "Which image do you want to develop with DevSpace?",
373+
DefaultValue: images[0],
374+
Options: append(images, []string{imageManual, imageSkip}...),
375+
})
376+
if err != nil {
377+
return err
378+
}
379+
} else {
380+
imageAnswer, err = cmd.log.Question(&survey.QuestionOptions{
381+
Question: "Couldn’t find any images in your manifests/helm charts. Do you want to skip this step?",
382+
Options: []string{imageManual, imageSkip},
383+
})
384+
if err != nil {
385+
return err
386+
}
387+
}
388+
389+
if imageAnswer == imageSkip {
390+
break
391+
} else if imageAnswer == imageManual {
392+
imageQuestion := "What is the main container image of this project?"
393+
394+
if selectedDeploymentOption == DeployOptionHelm {
395+
imageQuestion = "What is the main container image of this project which is deployed by this Helm chart? (e.g. ecr.io/project/image)"
396+
}
397+
398+
if selectedDeploymentOption == DeployOptionKubectl {
399+
imageQuestion = "What is the main container image of this project which is deployed by these manifests? (e.g. ecr.io/project/image)"
400+
}
401+
402+
if selectedDeploymentOption == DeployOptionKustomize {
403+
imageQuestion = "What is the main container image of this project which is deployed by this Kustomization? (e.g. ecr.io/project/image)"
404+
}
405+
406+
image, err = cmd.log.Question(&survey.QuestionOptions{
407+
Question: imageQuestion,
408+
ValidationMessage: "Please enter a valid container image from a Kubernetes pod (e.g. myregistry.tld/project/image)",
409+
ValidationFunc: func(name string) error {
410+
_, _, err := dockerfile.GetStrippedDockerImageName(strings.ToLower(name))
411+
return err
412+
},
413+
})
414+
if err != nil {
415+
return err
416+
}
417+
} else {
418+
image = imageAnswer
419+
}
361420
}
362-
}
363421

364-
err = configureManager.AddImage(imageName, image, projectNamespace+"/"+projectName, cmd.Dockerfile)
365-
if err != nil {
366-
if err.Error() != "" {
367-
cmd.log.Errorf("Error: %s", err.Error())
422+
err = configureManager.AddImage(imageName, image, projectNamespace+"/"+projectName, cmd.Dockerfile)
423+
if err != nil {
424+
if err.Error() != "" {
425+
cmd.log.Errorf("Error: %s", err.Error())
426+
}
427+
} else {
428+
break
368429
}
369-
} else {
370-
break
371430
}
372431
}
373432

374-
image = config.Images[imageName].Image
375-
376433
// Determine app port
377434
portString := ""
378435

379-
// Try to get ports from dockerfile
380-
ports, err := dockerfile.GetPorts(config.Images[imageName].Dockerfile)
381-
if err == nil {
382-
if len(ports) == 1 {
383-
portString = strconv.Itoa(ports[0])
384-
} else if len(ports) > 1 {
385-
portString, err = cmd.log.Question(&survey.QuestionOptions{
386-
Question: "Which port is your application listening on?",
387-
DefaultValue: strconv.Itoa(ports[0]),
388-
})
389-
if err != nil {
390-
return err
391-
}
436+
if len(config.Images) > 0 {
437+
image = config.Images[imageName].Image
392438

393-
if portString == "" {
439+
// Try to get ports from dockerfile
440+
ports, err := dockerfile.GetPorts(config.Images[imageName].Dockerfile)
441+
if err == nil {
442+
if len(ports) == 1 {
394443
portString = strconv.Itoa(ports[0])
444+
} else if len(ports) > 1 {
445+
portString, err = cmd.log.Question(&survey.QuestionOptions{
446+
Question: "Which port is your application listening on?",
447+
DefaultValue: strconv.Itoa(ports[0]),
448+
})
449+
if err != nil {
450+
return err
451+
}
452+
453+
if portString == "" {
454+
portString = strconv.Itoa(ports[0])
455+
}
395456
}
396457
}
397458
}
@@ -722,7 +783,7 @@ func (cmd *InitCmd) render(f factory.Factory, config *latest.Config) (string, er
722783
err := loader.Save(renderPath, config)
723784
defer os.Remove(renderPath)
724785
if err != nil {
725-
return "", err
786+
return "", errors.Wrap(err, "temp render.yaml")
726787
}
727788

728789
// Use the render command to render it.
@@ -741,8 +802,7 @@ func (cmd *InitCmd) render(f factory.Factory, config *latest.Config) (string, er
741802
}
742803
err = renderCmd.RunDefault(f)
743804
if err != nil {
744-
f.GetLog().Debugf("error rendering chart: %v", err)
745-
return "", nil
805+
return "", errors.Wrap(err, "devspace render")
746806
}
747807

748808
return writer.String(), nil

cmd/init_test.go

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package cmd
2+
3+
import (
4+
"testing"
5+
6+
"gotest.tools/assert"
7+
"gotest.tools/assert/cmp"
8+
)
9+
10+
type parseImagesTestCase struct {
11+
name string
12+
manifests string
13+
expected []string
14+
}
15+
16+
func TestParseImages(t *testing.T) {
17+
testCases := []parseImagesTestCase{
18+
{
19+
name: `Single`,
20+
manifests: `
21+
apiVersion: apps/v1
22+
kind: Deployment
23+
metadata:
24+
name: "new"
25+
labels:
26+
"app.kubernetes.io/name": "devspace-app"
27+
"app.kubernetes.io/component": "test"
28+
"app.kubernetes.io/managed-by": "Helm"
29+
spec:
30+
replicas: 1
31+
strategy:
32+
type: Recreate
33+
selector:
34+
matchLabels:
35+
"app.kubernetes.io/name": "devspace-app"
36+
"app.kubernetes.io/component": "test"
37+
"app.kubernetes.io/managed-by": "Helm"
38+
template:
39+
metadata:
40+
labels:
41+
"app.kubernetes.io/name": "devspace-app"
42+
"app.kubernetes.io/component": "test"
43+
"app.kubernetes.io/managed-by": "Helm"
44+
spec:
45+
containers:
46+
- image: "username/app"
47+
name: "container-0"
48+
`,
49+
expected: []string{
50+
"username/app",
51+
},
52+
},
53+
{
54+
name: `Multiple`,
55+
manifests: `
56+
---
57+
# Source: my-app/templates/service.yaml
58+
apiVersion: v1
59+
kind: Service
60+
metadata:
61+
name: php
62+
labels:
63+
release: "test-helm"
64+
spec:
65+
ports:
66+
- port: 80
67+
protocol: TCP
68+
selector:
69+
release: "test-helm"
70+
---
71+
# Source: my-app/templates/deployment.yaml
72+
apiVersion: apps/v1
73+
kind: Deployment
74+
metadata:
75+
name: test-helm
76+
labels:
77+
release: "test-helm"
78+
spec:
79+
replicas: 1
80+
selector:
81+
matchLabels:
82+
release: "test-helm"
83+
template:
84+
metadata:
85+
annotations:
86+
revision: "1"
87+
labels:
88+
release: "test-helm"
89+
spec:
90+
containers:
91+
- name: default
92+
image: "php"
93+
`,
94+
expected: []string{
95+
"php",
96+
},
97+
},
98+
}
99+
100+
for _, testCase := range testCases {
101+
manifests := testCase.manifests
102+
103+
actual, err := parseImages(manifests)
104+
assert.NilError(
105+
t,
106+
err,
107+
"Unexpected error in test case %s",
108+
testCase.name,
109+
)
110+
111+
expected := testCase.expected
112+
assert.Assert(
113+
t,
114+
cmp.DeepEqual(expected, actual),
115+
"Unexpected values in test case %s",
116+
testCase.name,
117+
)
118+
}
119+
}

pkg/devspace/configure/deployment.go

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"fmt"
77
"net/http"
8+
"net/url"
89
"os"
910
"path"
1011
"path/filepath"
@@ -80,6 +81,7 @@ func (m *manager) AddKubectlDeployment(deploymentName string, isKustomization bo
8081
if isKustomization {
8182
m.config.Deployments[deploymentName].Kubectl.Kustomize = ptr.Bool(isKustomization)
8283
}
84+
m.isRemote[deploymentName] = false
8385

8486
return nil
8587
}
@@ -146,21 +148,30 @@ func (m *manager) AddHelmDeployment(deploymentName string) error {
146148
}
147149

148150
helmConfig.Chart.Name = localChartPathRel
151+
m.isRemote[deploymentName] = false
149152
} else if chartLocation == chartRepo || chartLocation == archiveURL {
150153
ChartRepoLoop:
151154
for {
152155
requestURL := ""
153156

154157
if chartLocation == chartRepo {
155-
helmConfig.Chart.RepoURL, err = m.log.Question(&survey.QuestionOptions{
156-
Question: "Please specify the full URL of the chart repo (e.g. https://charts.org.tld/)",
157-
ValidationRegexPattern: "^http(s)?://.*",
158+
tempChartRepoURL, err := m.log.Question(&survey.QuestionOptions{
159+
Question: "Please specify the full URL of the chart repo (e.g. https://charts.org.tld/)",
160+
ValidationFunc: func(value string) error {
161+
_, err := url.ParseRequestURI(chartRepoURL(value))
162+
if err != nil {
163+
return err
164+
}
165+
return nil
166+
},
158167
})
159168
if err != nil {
160169
return err
161170
}
162171

163-
requestURL = helmConfig.Chart.RepoURL + "/index.yaml"
172+
helmConfig.Chart.RepoURL = chartRepoURL(tempChartRepoURL)
173+
174+
requestURL = strings.TrimRight(helmConfig.Chart.RepoURL, "/") + "/index.yaml"
164175

165176
helmConfig.Chart.Name, err = m.log.Question(&survey.QuestionOptions{
166177
Question: "Please specify the name of the chart within your chart repository (e.g. payment-service)",
@@ -240,6 +251,7 @@ func (m *manager) AddHelmDeployment(deploymentName string) error {
240251
m.localCache.SetVar(passwordVar, password)
241252
}
242253

254+
m.isRemote[deploymentName] = true
243255
break ChartRepoLoop
244256
}
245257
}
@@ -296,6 +308,7 @@ func (m *manager) AddHelmDeployment(deploymentName string) error {
296308
Events: []string{"before:deploy"},
297309
})
298310

311+
m.isRemote[deploymentName] = true
299312
break
300313
}
301314
}
@@ -351,6 +364,19 @@ func (m *manager) AddComponentDeployment(deploymentName, image string, servicePo
351364
Values: chartValues,
352365
},
353366
}
367+
m.isRemote[deploymentName] = true
354368

355369
return nil
356370
}
371+
372+
func (m *manager) IsRemoteDeployment(deploymentName string) bool {
373+
return m.isRemote[deploymentName]
374+
}
375+
376+
func chartRepoURL(url string) string {
377+
repoURL := url
378+
if !(strings.HasPrefix(url, "https://") || strings.HasPrefix(url, "http://")) {
379+
repoURL = "https://" + url
380+
}
381+
return repoURL
382+
}

0 commit comments

Comments
 (0)