Skip to content

Commit

Permalink
feat: support creating multiple projects non-interactively
Browse files Browse the repository at this point in the history
Signed-off-by: Toma Puljak <[email protected]>
  • Loading branch information
Tpuljak committed Sep 20, 2024
1 parent 15af1bc commit 1101b0f
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 32 deletions.
4 changes: 2 additions & 2 deletions docs/daytona_create.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
Create a workspace

```
daytona create [REPOSITORY_URL] [flags]
daytona create [REPOSITORY_URL | PROJECT_CONFIG_NAME]... [flags]
```

### Options

```
--blank Create a blank project without using existing configurations
--branch string Specify the Git branch to use in the project
--branch strings Specify the Git branches to use in the projects
--builder BuildChoice Specify the builder (currently auto/devcontainer/none)
-c, --code Open the workspace in the IDE after workspace creation
--custom-image string Create the project with the custom image passed as the flag value; Requires setting --custom-image-user flag as well
Expand Down
5 changes: 3 additions & 2 deletions hack/docs/daytona_create.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
name: daytona create
synopsis: Create a workspace
usage: daytona create [REPOSITORY_URL] [flags]
usage: daytona create [REPOSITORY_URL | PROJECT_CONFIG_NAME]... [flags]
options:
- name: blank
default_value: "false"
usage: Create a blank project without using existing configurations
- name: branch
usage: Specify the Git branch to use in the project
default_value: '[]'
usage: Specify the Git branches to use in the projects
- name: builder
usage: Specify the builder (currently auto/devcontainer/none)
- name: code
Expand Down
106 changes: 80 additions & 26 deletions pkg/cmd/workspace/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,15 @@ import (
)

var CreateCmd = &cobra.Command{
Use: "create [REPOSITORY_URL]",
Use: "create [REPOSITORY_URL | PROJECT_CONFIG_NAME]...",
Short: "Create a workspace",
Args: cobra.RangeArgs(0, 1),
GroupID: util.WORKSPACE_GROUP,
Run: func(cmd *cobra.Command, args []string) {
ctx := context.Background()
var projects []apiclient.CreateProjectDTO
var workspaceName string
var existingWorkspaceNames []string
var existingProjectConfigName *string
var existingProjectConfigNames []string

apiClient, err := apiclient_util.GetApiClient(nil)
if err != nil {
Expand Down Expand Up @@ -90,7 +89,7 @@ var CreateCmd = &cobra.Command{
}
}
} else {
existingProjectConfigName, err = processCmdArgument(args[0], apiClient, &projects, ctx)
existingProjectConfigNames, err = processCmdArguments(args, apiClient, &projects, ctx)
if err != nil {
log.Fatal(err)
}
Expand Down Expand Up @@ -123,11 +122,14 @@ var CreateCmd = &cobra.Command{
Msg: "Request submitted\n",
}, logs_view.STATIC_INDEX)

if existingProjectConfigName != nil {
for i, projectConfigName := range existingProjectConfigNames {
if projectConfigName == "" {
continue
}
logs_view.DisplayLogEntry(logs.LogEntry{
ProjectName: existingProjectConfigName,
Msg: fmt.Sprintf("Using detected project config '%s'\n", *existingProjectConfigName),
}, logs_view.FIRST_PROJECT_INDEX)
ProjectName: &projects[i].Name,
Msg: fmt.Sprintf("Using detected project config '%s'\n", projectConfigName),
}, i)
}

targetList, res, err := apiClient.TargetAPI.ListTargets(ctx).Execute()
Expand Down Expand Up @@ -233,6 +235,7 @@ var projectConfigurationFlags = workspace_util.ProjectConfigurationFlags{
CustomImage: new(string),
CustomImageUser: new(string),
Branch: new(string),
Branches: new([]string),
DevcontainerPath: new(string),
EnvVars: new([]string),
Manual: new(bool),
Expand Down Expand Up @@ -336,47 +339,85 @@ func processPrompting(apiClient *apiclient.APIClient, workspaceName *string, pro
return nil
}

func processCmdArgument(argument string, apiClient *apiclient.APIClient, projects *[]apiclient.CreateProjectDTO, ctx context.Context) (*string, error) {
func processCmdArguments(repoUrls []string, apiClient *apiclient.APIClient, projects *[]apiclient.CreateProjectDTO, ctx context.Context) ([]string, error) {
if len(repoUrls) == 0 {
return nil, fmt.Errorf("no repository URLs provided")
}

if len(repoUrls) > 1 && workspace_util.CheckAnyProjectConfigurationFlagSet(projectConfigurationFlags) {
return nil, fmt.Errorf("can't set custom project configuration properties for multiple projects")
}

if *projectConfigurationFlags.Builder != "" && *projectConfigurationFlags.Builder != views_util.DEVCONTAINER && *projectConfigurationFlags.DevcontainerPath != "" {
return nil, fmt.Errorf("can't set devcontainer file path if builder is not set to %s", views_util.DEVCONTAINER)
}

var projectConfig *apiclient.ProjectConfig

repoUrl, err := util.GetValidatedUrl(argument)
if err == nil {
// The argument is a Git URL
return processGitURL(repoUrl, apiClient, projects, ctx)
}
existingProjectConfigNames := []string{}

// The argument is not a Git URL - try getting the project config
projectConfig, _, err = apiClient.ProjectConfigAPI.GetProjectConfig(ctx, argument).Execute()
if err != nil {
return nil, fmt.Errorf("failed to parse the URL or fetch the project config for '%s'", argument)
for i, repoUrl := range repoUrls {
branch := ""
if len(*projectConfigurationFlags.Branches) > i {
branch = (*projectConfigurationFlags.Branches)[i]
}

validatedUrl, err := util.GetValidatedUrl(repoUrl)
if err == nil {
// The argument is a Git URL
existingProjectConfigName, err := processGitURL(ctx, validatedUrl, apiClient, projects, &branch)
if err != nil {
return nil, err
}
if existingProjectConfigName != nil {
existingProjectConfigNames = append(existingProjectConfigNames, *existingProjectConfigName)
} else {
existingProjectConfigNames = append(existingProjectConfigNames, "")
}

continue
}

// The argument is not a Git URL - try getting the project config
projectConfig, _, err = apiClient.ProjectConfigAPI.GetProjectConfig(ctx, repoUrl).Execute()
if err != nil {
return nil, fmt.Errorf("failed to parse the URL or fetch the project config for '%s'", repoUrl)
}

existingProjectConfigName, err := workspace_util.AddProjectFromConfig(projectConfig, apiClient, projects, branch)
if err != nil {
return nil, err
}
if existingProjectConfigName != nil {
existingProjectConfigNames = append(existingProjectConfigNames, *existingProjectConfigName)
} else {
existingProjectConfigNames = append(existingProjectConfigNames, "")
}
}

return workspace_util.AddProjectFromConfig(projectConfig, apiClient, projects, *projectConfigurationFlags.Branch)
dedupProjectNames(projects)

return existingProjectConfigNames, nil
}

func processGitURL(repoUrl string, apiClient *apiclient.APIClient, projects *[]apiclient.CreateProjectDTO, ctx context.Context) (*string, error) {
func processGitURL(ctx context.Context, repoUrl string, apiClient *apiclient.APIClient, projects *[]apiclient.CreateProjectDTO, branch *string) (*string, error) {
encodedURLParam := url.QueryEscape(repoUrl)

if !blankFlag {
projectConfig, res, err := apiClient.ProjectConfigAPI.GetDefaultProjectConfig(ctx, encodedURLParam).Execute()
if err == nil {
return workspace_util.AddProjectFromConfig(projectConfig, apiClient, projects, *projectConfigurationFlags.Branch)
b := ""
if branch != nil {
b = *branch
}
return workspace_util.AddProjectFromConfig(projectConfig, apiClient, projects, b)
}

if res.StatusCode != http.StatusNotFound {
return nil, apiclient_util.HandleErrorResponse(res, err)
}
}

var branch *string
if *projectConfigurationFlags.Branch != "" {
branch = projectConfigurationFlags.Branch
}

repo, res, err := apiClient.GitProviderAPI.GetGitContext(ctx).Repository(apiclient.GetRepositoryContext{
Url: repoUrl,
Branch: branch,
Expand Down Expand Up @@ -438,3 +479,16 @@ func waitForDial(workspace *apiclient.Workspace, activeProfile *config.Profile,
time.Sleep(time.Second)
}
}

func dedupProjectNames(projects *[]apiclient.CreateProjectDTO) {
projectNames := map[string]int{}

for i, project := range *projects {
if _, ok := projectNames[project.Name]; ok {
(*projects)[i].Name = fmt.Sprintf("%s-%d", project.Name, projectNames[project.Name])
projectNames[project.Name]++
} else {
projectNames[project.Name] = 2
}
}
}
7 changes: 6 additions & 1 deletion pkg/cmd/workspace/util/branch_wizard.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,12 @@ func SetBranchFromWizard(config BranchWizardConfig) (*apiclient.GitRepository, e
}

var branch *apiclient.GitBranch
parentIdentifier := fmt.Sprintf("%s/%s/%s", config.ProviderId, config.Namespace, config.ChosenRepo.Name)
namespace := config.Namespace
if namespace == "" {
namespace = config.ChosenRepo.Owner
}

parentIdentifier := fmt.Sprintf("%s/%s/%s", config.ProviderId, namespace, config.ChosenRepo.Name)
if len(prList) == 0 {
branch = selection.GetBranchFromPrompt(branchList, config.ProjectOrder, parentIdentifier)
if branch == nil {
Expand Down
7 changes: 6 additions & 1 deletion pkg/cmd/workspace/util/workspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type ProjectConfigurationFlags struct {
CustomImage *string
CustomImageUser *string
Branch *string
Branches *[]string
DevcontainerPath *string
EnvVars *[]string
Manual *bool
Expand All @@ -25,7 +26,11 @@ type ProjectConfigurationFlags struct {
func AddProjectConfigurationFlags(cmd *cobra.Command, flags ProjectConfigurationFlags, multiProjectFlagException bool) {
cmd.Flags().StringVar(flags.CustomImage, "custom-image", "", "Create the project with the custom image passed as the flag value; Requires setting --custom-image-user flag as well")
cmd.Flags().StringVar(flags.CustomImageUser, "custom-image-user", "", "Create the project with the custom image user passed as the flag value; Requires setting --custom-image flag as well")
cmd.Flags().StringVar(flags.Branch, "branch", "", "Specify the Git branch to use in the project")
if multiProjectFlagException {
cmd.Flags().StringSliceVar(flags.Branches, "branch", []string{}, "Specify the Git branches to use in the projects")
} else {
cmd.Flags().StringVar(flags.Branch, "branch", "", "Specify the Git branch to use in the project")
}
cmd.Flags().StringVar(flags.DevcontainerPath, "devcontainer-path", "", "Automatically assign the devcontainer builder with the path passed as the flag value")
cmd.Flags().Var(flags.Builder, "builder", fmt.Sprintf("Specify the builder (currently %s/%s/%s)", views_util.AUTOMATIC, views_util.DEVCONTAINER, views_util.NONE))
cmd.Flags().StringArrayVar(flags.EnvVars, "env", []string{}, "Specify environment variables (e.g. --env 'KEY1=VALUE1' --env 'KEY2=VALUE2' ...')")
Expand Down

0 comments on commit 1101b0f

Please sign in to comment.