Skip to content

Commit f607b5e

Browse files
committed
refactor: integrate init command with compose config manager
1 parent deb40ae commit f607b5e

14 files changed

Lines changed: 269 additions & 159 deletions

File tree

cmd/init.go

Lines changed: 177 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"strconv"
1212
"strings"
1313

14+
"github.com/loft-sh/devspace/pkg/devspace/compose"
1415
"github.com/loft-sh/devspace/pkg/devspace/config/localcache"
1516
"github.com/sirupsen/logrus"
1617

@@ -147,45 +148,31 @@ func (cmd *InitCmd) Run(f factory.Factory) error {
147148
// Print DevSpace logo
148149
log.PrintLogo()
149150

150-
/*
151-
generateFromDockerCompose := false
152-
// TODO: Enable again
153-
dockerComposePath := "" // compose.GetDockerComposePath()
154-
if dockerComposePath != "" {
155-
selectedDockerComposeOption, err := cmd.log.Question(&survey.QuestionOptions{
156-
Question: "Docker Compose configuration detected. Do you want to create a DevSpace configuration based on Docker Compose?",
157-
DefaultValue: DockerComposeDevSpaceConfigOption,
158-
Options: []string{
159-
DockerComposeDevSpaceConfigOption,
160-
NewDevSpaceConfigOption,
161-
},
162-
})
163-
if err != nil {
164-
return err
165-
}
151+
// Determine if we're initializing from scratch, or using docker-compose.yaml
152+
dockerComposePath, generateFromDockerCompose, err := cmd.shouldGenerateFromDockerCompose()
153+
if err != nil {
154+
return err
155+
}
166156

167-
generateFromDockerCompose = selectedDockerComposeOption == DockerComposeDevSpaceConfigOption
168-
}
157+
if generateFromDockerCompose {
158+
err = cmd.initDockerCompose(f, dockerComposePath)
159+
} else {
160+
err = cmd.initDevspace(f, configLoader)
161+
}
169162

170-
if generateFromDockerCompose {
171-
composeLoader := compose.NewDockerComposeLoader(dockerComposePath)
172-
if err != nil {
173-
return err
174-
}
163+
if err != nil {
164+
panic(err)
165+
}
175166

176-
// Load config
177-
config, err := composeLoader.Load(cmd.log)
178-
if err != nil {
179-
return err
180-
}
167+
cmd.log.WriteString(logrus.InfoLevel, "\n")
168+
cmd.log.Done("Project successfully initialized")
169+
cmd.log.Info("Configuration saved in devspace.yaml - you can make adjustments as needed")
170+
cmd.log.Infof("\r \nYou can now run:\n1. %s - to pick which Kubernetes namespace to work in\n2. %s - to start developing your project in Kubernetes\n\nRun `%s` or `%s` to see a list of available commands and flags\n", ansi.Color("devspace use namespace", "blue+b"), ansi.Color("devspace dev", "blue+b"), ansi.Color("devspace -h", "blue+b"), ansi.Color("devspace [command] -h", "blue+b"))
181171

182-
// Save config
183-
err = composeLoader.Save(config)
184-
if err != nil {
185-
return err
186-
}
187-
} else {*/
172+
return nil
173+
}
188174

175+
func (cmd *InitCmd) initDevspace(f factory.Factory, configLoader loader.ConfigLoader) error {
189176
// Create new dockerfile generator
190177
languageHandler, err := generator.NewLanguageHandler("", "", cmd.log)
191178
if err != nil {
@@ -476,8 +463,6 @@ create_deployments --all \ # 3. Deploy Helm charts and ma
476463
return err
477464
}
478465

479-
/*}*/
480-
481466
// Save generated
482467
err = localCache.Save()
483468
if err != nil {
@@ -491,6 +476,81 @@ create_deployments --all \ # 3. Deploy Helm charts and ma
491476
}
492477

493478
configPath := loader.ConfigPath("")
479+
err = annotateConfig(configPath)
480+
if err != nil {
481+
return err
482+
}
483+
484+
return nil
485+
}
486+
487+
func (cmd *InitCmd) initDockerCompose(f factory.Factory, composePath string) error {
488+
project, err := compose.LoadDockerComposeProject(composePath)
489+
if err != nil {
490+
return err
491+
}
492+
493+
// Prompt user for entrypoints for each container with sync folders.
494+
for idx, service := range project.Services {
495+
localPaths := compose.GetServiceSyncPaths(project, service)
496+
noEntryPoint := len(service.Entrypoint) == 0
497+
hasSyncEndpoints := len(localPaths) > 0
498+
499+
if noEntryPoint && hasSyncEndpoints {
500+
entrypointStr, err := cmd.log.Question(&survey.QuestionOptions{
501+
Question: "How is this container started? (e.g. npm start, gradle run, go run main.go)",
502+
})
503+
if err != nil {
504+
return err
505+
}
506+
507+
entrypoint := strings.Split(entrypointStr, " ")
508+
project.Services[idx].Entrypoint = entrypoint
509+
}
510+
}
511+
512+
// Generate DevSpace configuration
513+
composeManager := compose.NewComposeManager(project)
514+
err = composeManager.Load(cmd.log)
515+
if err != nil {
516+
return err
517+
}
518+
519+
// Save each configuration file
520+
for path, config := range composeManager.Configs() {
521+
localCache, err := localcache.NewCacheLoader().Load(path)
522+
if err != nil {
523+
return err
524+
}
525+
526+
// Save config
527+
err = loader.Save(path, config)
528+
if err != nil {
529+
return err
530+
}
531+
532+
// Save generated
533+
err = localCache.Save()
534+
if err != nil {
535+
return errors.Errorf("Error saving generated file: %v", err)
536+
}
537+
538+
// Add .devspace/ to .gitignore
539+
err = appendToIgnoreFile(gitIgnoreFile, devspaceFolderGitignore)
540+
if err != nil {
541+
cmd.log.Warn(err)
542+
}
543+
544+
err = annotateConfig(path)
545+
if err != nil {
546+
return err
547+
}
548+
}
549+
550+
return nil
551+
}
552+
553+
func annotateConfig(configPath string) error {
494554
annotatedConfig, err := ioutil.ReadFile(configPath)
495555
if err != nil {
496556
panic(err)
@@ -541,74 +601,9 @@ create_deployments --all \ # 3. Deploy Helm charts and ma
541601
return err
542602
}
543603

544-
cmd.log.WriteString(logrus.InfoLevel, "\n")
545-
cmd.log.Done("Project successfully initialized")
546-
cmd.log.Info("Configuration saved in devspace.yaml - you can make adjustments as needed")
547-
cmd.log.Infof("\r \nYou can now run:\n1. %s - to pick which Kubernetes namespace to work in\n2. %s - to start developing your project in Kubernetes\n\nRun `%s` or `%s` to see a list of available commands and flags\n", ansi.Color("devspace use namespace", "blue+b"), ansi.Color("devspace dev", "blue+b"), ansi.Color("devspace -h", "blue+b"), ansi.Color("devspace [command] -h", "blue+b"))
548-
return nil
549-
}
550-
551-
func appendToIgnoreFile(ignoreFile, content string) error {
552-
// Check if ignoreFile exists
553-
_, err := os.Stat(ignoreFile)
554-
if os.IsNotExist(err) {
555-
_ = fsutil.WriteToFile([]byte(content), ignoreFile)
556-
} else {
557-
fileContent, err := ioutil.ReadFile(ignoreFile)
558-
if err != nil {
559-
return errors.Errorf("Error reading file %s: %v", ignoreFile, err)
560-
}
561-
562-
// append only if not found in file content
563-
if !strings.Contains(string(fileContent), content) {
564-
file, err := os.OpenFile(ignoreFile, os.O_APPEND|os.O_WRONLY, 0600)
565-
if err != nil {
566-
return errors.Errorf("Error writing file %s: %v", ignoreFile, err)
567-
}
568-
569-
defer file.Close()
570-
if _, err = file.WriteString(content); err != nil {
571-
return errors.Errorf("Error writing file %s: %v", ignoreFile, err)
572-
}
573-
}
574-
}
575604
return nil
576605
}
577606

578-
func getProjectName() (string, string, error) {
579-
projectName := ""
580-
projectNamespace := ""
581-
gitRemote, err := command.Output(context.TODO(), "", "git", "config", "--get", "remote.origin.url")
582-
if err == nil {
583-
sep := "/"
584-
projectParts := strings.Split(string(regexp.MustCompile(`^.*?://[^/]+?/([^.]+)(\.git)?`).ReplaceAll(gitRemote, []byte("$1"))), sep)
585-
partsLen := len(projectParts)
586-
if partsLen > 1 {
587-
projectNamespace = strings.Join(projectParts[0:partsLen-1], sep)
588-
projectName = projectParts[partsLen-1]
589-
}
590-
}
591-
592-
if projectName == "" {
593-
absPath, err := filepath.Abs(".")
594-
if err != nil {
595-
return "", "", err
596-
}
597-
projectName = filepath.Base(absPath)
598-
}
599-
600-
projectName = strings.ToLower(projectName)
601-
projectName = regexp.MustCompile("[^a-zA-Z0-9- ]+").ReplaceAllString(projectName, "")
602-
projectName = regexp.MustCompile("[^a-zA-Z0-9-]+").ReplaceAllString(projectName, "-")
603-
projectName = strings.Trim(projectName, "-")
604-
605-
if !SpaceNameValidationRegEx.MatchString(projectName) || len(projectName) > 42 {
606-
projectName = "devspace"
607-
}
608-
609-
return projectName, projectNamespace, nil
610-
}
611-
612607
func (cmd *InitCmd) addDevConfig(config *latest.Config, imageName, image string, port int, languageHandler *generator.LanguageHandler) error {
613608
if config.Dev == nil {
614609
config.Dev = map[string]*latest.DevPod{}
@@ -739,6 +734,87 @@ func (cmd *InitCmd) render(f factory.Factory, config *latest.Config) (string, er
739734
return writer.String(), nil
740735
}
741736

737+
func (cmd *InitCmd) shouldGenerateFromDockerCompose() (string, bool, error) {
738+
dockerComposePath := compose.GetDockerComposePath()
739+
if dockerComposePath != "" {
740+
selectedDockerComposeOption, err := cmd.log.Question(&survey.QuestionOptions{
741+
Question: "Docker Compose configuration detected. Do you want to create a DevSpace configuration based on Docker Compose?",
742+
DefaultValue: DockerComposeDevSpaceConfigOption,
743+
Options: []string{
744+
DockerComposeDevSpaceConfigOption,
745+
NewDevSpaceConfigOption,
746+
},
747+
})
748+
if err != nil {
749+
return "", false, err
750+
}
751+
752+
return dockerComposePath, selectedDockerComposeOption == DockerComposeDevSpaceConfigOption, nil
753+
}
754+
return "", false, nil
755+
}
756+
757+
func appendToIgnoreFile(ignoreFile, content string) error {
758+
// Check if ignoreFile exists
759+
_, err := os.Stat(ignoreFile)
760+
if os.IsNotExist(err) {
761+
_ = fsutil.WriteToFile([]byte(content), ignoreFile)
762+
} else {
763+
fileContent, err := ioutil.ReadFile(ignoreFile)
764+
if err != nil {
765+
return errors.Errorf("Error reading file %s: %v", ignoreFile, err)
766+
}
767+
768+
// append only if not found in file content
769+
if !strings.Contains(string(fileContent), content) {
770+
file, err := os.OpenFile(ignoreFile, os.O_APPEND|os.O_WRONLY, 0600)
771+
if err != nil {
772+
return errors.Errorf("Error writing file %s: %v", ignoreFile, err)
773+
}
774+
775+
defer file.Close()
776+
if _, err = file.WriteString(content); err != nil {
777+
return errors.Errorf("Error writing file %s: %v", ignoreFile, err)
778+
}
779+
}
780+
}
781+
return nil
782+
}
783+
784+
func getProjectName() (string, string, error) {
785+
projectName := ""
786+
projectNamespace := ""
787+
gitRemote, err := command.Output(context.TODO(), "", "git", "config", "--get", "remote.origin.url")
788+
if err == nil {
789+
sep := "/"
790+
projectParts := strings.Split(string(regexp.MustCompile(`^.*?://[^/]+?/([^.]+)(\.git)?`).ReplaceAll(gitRemote, []byte("$1"))), sep)
791+
partsLen := len(projectParts)
792+
if partsLen > 1 {
793+
projectNamespace = strings.Join(projectParts[0:partsLen-1], sep)
794+
projectName = projectParts[partsLen-1]
795+
}
796+
}
797+
798+
if projectName == "" {
799+
absPath, err := filepath.Abs(".")
800+
if err != nil {
801+
return "", "", err
802+
}
803+
projectName = filepath.Base(absPath)
804+
}
805+
806+
projectName = strings.ToLower(projectName)
807+
projectName = regexp.MustCompile("[^a-zA-Z0-9- ]+").ReplaceAllString(projectName, "")
808+
projectName = regexp.MustCompile("[^a-zA-Z0-9-]+").ReplaceAllString(projectName, "-")
809+
projectName = strings.Trim(projectName, "-")
810+
811+
if !SpaceNameValidationRegEx.MatchString(projectName) || len(projectName) > 42 {
812+
projectName = "devspace"
813+
}
814+
815+
return projectName, projectNamespace, nil
816+
}
817+
742818
func parseImages(manifests string) ([]string, error) {
743819
images := []string{}
744820

pkg/devspace/compose/config_builder.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ import (
99
)
1010

1111
type ConfigBuilder interface {
12-
AddDependencies(dependency composetypes.Project, service composetypes.ServiceConfig) error
13-
AddDeployment(dockerCompose composetypes.Project, service composetypes.ServiceConfig) error
12+
AddDependencies(dependency *composetypes.Project, service composetypes.ServiceConfig) error
13+
AddDeployment(dockerCompose *composetypes.Project, service composetypes.ServiceConfig) error
1414
AddDev(service composetypes.ServiceConfig) error
15-
AddImage(dockerCompose composetypes.Project, service composetypes.ServiceConfig) error
16-
AddSecret(dockerCompose composetypes.Project, service composetypes.ServiceConfig) error
15+
AddImage(dockerCompose *composetypes.Project, service composetypes.ServiceConfig) error
16+
AddSecret(dockerCompose *composetypes.Project, service composetypes.ServiceConfig) error
1717
Config() *latest.Config
1818
SetName(name string)
1919
}

pkg/devspace/compose/dependency.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"github.com/loft-sh/devspace/pkg/devspace/config/versions/latest"
88
)
99

10-
func (cb *configBuilder) AddDependencies(dockerCompose composetypes.Project, service composetypes.ServiceConfig) error {
10+
func (cb *configBuilder) AddDependencies(dockerCompose *composetypes.Project, service composetypes.ServiceConfig) error {
1111
for _, dependency := range service.GetDependencies() {
1212
depName := formatName(dependency)
1313

pkg/devspace/compose/deployment.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212
v1 "k8s.io/api/core/v1"
1313
)
1414

15-
func (cb *configBuilder) AddDeployment(dockerCompose composetypes.Project, service composetypes.ServiceConfig) error {
15+
func (cb *configBuilder) AddDeployment(dockerCompose *composetypes.Project, service composetypes.ServiceConfig) error {
1616
values := map[string]interface{}{}
1717

1818
volumes, volumeMounts, _ := volumesConfig(service, dockerCompose.Volumes, cb.log)

pkg/devspace/compose/image.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,15 @@ import (
77
"github.com/loft-sh/devspace/pkg/devspace/config/versions/latest"
88
)
99

10-
func (cb *configBuilder) AddImage(dockerCompose composetypes.Project, service composetypes.ServiceConfig) error {
10+
func (cb *configBuilder) AddImage(dockerCompose *composetypes.Project, service composetypes.ServiceConfig) error {
1111
build := service.Build
1212
if build == nil {
1313
cb.config.Images = nil
1414
return nil
1515
}
1616

17-
currentDir := filepath.Join(dockerCompose.WorkingDir, cb.workingDir)
1817
contextDir := filepath.Join(dockerCompose.WorkingDir, build.Context)
19-
context, err := filepath.Rel(currentDir, contextDir)
18+
context, err := filepath.Rel(cb.workingDir, contextDir)
2019
if err != nil {
2120
return err
2221
}
@@ -26,7 +25,7 @@ func (cb *configBuilder) AddImage(dockerCompose composetypes.Project, service co
2625
context = ""
2726
}
2827

29-
dockerfile, err := filepath.Rel(currentDir, filepath.Join(dockerCompose.WorkingDir, build.Context, build.Dockerfile))
28+
dockerfile, err := filepath.Rel(cb.workingDir, filepath.Join(dockerCompose.WorkingDir, build.Context, build.Dockerfile))
3029
if err != nil {
3130
return err
3231
}

0 commit comments

Comments
 (0)