Skip to content

Commit f38de16

Browse files
authored
[CLI] Added http url handling to create template (#1346)
## Summary Added arbitrary git repo url handling to `devbox create` by adding `--repo` and `--subdir` flags. The behavior is as follows: Either `--template` or `--repo` are required flags. When `--template` is provided, we clone from devbox and use the mapping (no change) When `--repo` is provided we clone that repo and if `--subdir` is provided, we copy over only the contents of the `subdir` otherwise, the whole repo is copied over. Update: marked the 2 new flags (`--repo` and `--subdir`) hidden. ## How was it tested? See unit tests
1 parent eba7998 commit f38de16

File tree

3 files changed

+86
-17
lines changed

3 files changed

+86
-17
lines changed

internal/boxcli/create.go

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,16 @@ import (
1010

1111
"github.com/spf13/cobra"
1212

13+
"go.jetpack.io/devbox/internal/boxcli/usererr"
1314
"go.jetpack.io/devbox/internal/templates"
1415
"go.jetpack.io/devbox/internal/ux"
1516
)
1617

1718
type createCmdFlags struct {
1819
showAll bool
1920
template string
21+
repo string
22+
subdir string
2023
}
2124

2225
func createCmd() *cobra.Command {
@@ -26,7 +29,7 @@ func createCmd() *cobra.Command {
2629
Short: "Initialize a directory as a devbox project using a template",
2730
Args: cobra.MaximumNArgs(1),
2831
RunE: func(cmd *cobra.Command, args []string) error {
29-
if flags.template == "" {
32+
if flags.template == "" && flags.repo == "" {
3033
fmt.Fprintf(
3134
cmd.ErrOrStderr(),
3235
"Usage: devbox create [dir] --template <template>\n\n",
@@ -52,18 +55,32 @@ func createCmd() *cobra.Command {
5255
&flags.showAll, "show-all", false,
5356
"show all available templates",
5457
)
58+
command.Flags().StringVarP(
59+
&flags.repo, "repo", "r", "",
60+
"Git repository HTTPS URL to import template files from. Example: https://github.com/jetpack-io/devbox",
61+
)
62+
command.Flags().StringVarP(
63+
&flags.subdir, "subdir", "s", "",
64+
"Subdirectory of the Git repository in which the template files reside. Example: examples/tutorial",
65+
)
66+
// this command marks a flag as hidden. Error handling for it is not necessary.
67+
_ = command.Flags().MarkHidden("repo")
68+
_ = command.Flags().MarkHidden("subdir")
5569

5670
return command
5771
}
5872

5973
func runCreateCmd(cmd *cobra.Command, args []string, flags *createCmdFlags) error {
60-
path := pathArg(args)
61-
if path == "" {
62-
wd, _ := os.Getwd()
63-
path = filepath.Join(wd, flags.template)
64-
}
74+
path := handlePath(args, flags)
6575

66-
err := templates.Init(cmd.ErrOrStderr(), flags.template, path)
76+
var err error
77+
if flags.template != "" {
78+
err = templates.InitFromName(cmd.ErrOrStderr(), flags.template, path)
79+
} else if flags.repo != "" {
80+
err = templates.InitFromRepo(cmd.ErrOrStderr(), flags.repo, flags.subdir, path)
81+
} else {
82+
err = usererr.New("either --template or --repo need to be specified")
83+
}
6784
if err != nil {
6885
return err
6986
}
@@ -76,3 +93,18 @@ func runCreateCmd(cmd *cobra.Command, args []string, flags *createCmdFlags) erro
7693

7794
return nil
7895
}
96+
97+
func handlePath(args []string, flags *createCmdFlags) string {
98+
path := pathArg(args)
99+
wd, _ := os.Getwd()
100+
if path == "" {
101+
if flags.template != "" {
102+
path = filepath.Join(wd, flags.template)
103+
} else if flags.repo != "" && flags.subdir == "" {
104+
path = filepath.Join(wd, filepath.Base(flags.repo))
105+
} else if flags.repo != "" && flags.subdir != "" {
106+
path = filepath.Join(wd, filepath.Base(flags.subdir))
107+
}
108+
}
109+
return path
110+
}

internal/templates/template.go

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@ package templates
66
import (
77
"fmt"
88
"io"
9+
"net/url"
910
"os"
1011
"os/exec"
1112
"path/filepath"
13+
"strings"
1214

1315
"github.com/pkg/errors"
1416
"github.com/samber/lo"
@@ -17,23 +19,28 @@ import (
1719
"go.jetpack.io/devbox/internal/boxcli/usererr"
1820
)
1921

20-
func Init(w io.Writer, template, dir string) error {
21-
if err := createDirAndEnsureEmpty(dir); err != nil {
22-
return err
23-
}
24-
22+
func InitFromName(w io.Writer, template string, target string) error {
2523
templatePath, ok := templates[template]
2624
if !ok {
27-
return usererr.New("unknown template %q", template)
25+
return usererr.New("unknown template name or format %q", template)
26+
}
27+
return InitFromRepo(w, "https://github.com/jetpack-io/devbox", templatePath, target)
28+
}
29+
30+
func InitFromRepo(w io.Writer, repo string, subdir string, target string) error {
31+
if err := createDirAndEnsureEmpty(target); err != nil {
32+
return err
33+
}
34+
parsedRepoURL, err := ParseRepoURL(repo)
35+
if err != nil {
36+
return errors.WithStack(err)
2837
}
2938

3039
tmp, err := os.MkdirTemp("", "devbox-template")
3140
if err != nil {
3241
return errors.WithStack(err)
3342
}
34-
cmd := exec.Command(
35-
"git", "clone", "https://github.com/jetpack-io/devbox.git", tmp,
36-
)
43+
cmd := exec.Command("git", "clone", parsedRepoURL, tmp)
3744
fmt.Fprintf(w, "%s\n", cmd)
3845
cmd.Stderr = os.Stderr
3946
cmd.Stdout = os.Stdout
@@ -43,7 +50,7 @@ func Init(w io.Writer, template, dir string) error {
4350

4451
cmd = exec.Command(
4552
"sh", "-c",
46-
fmt.Sprintf("cp -r %s %s", filepath.Join(tmp, templatePath, "*"), dir),
53+
fmt.Sprintf("cp -r %s %s", filepath.Join(tmp, subdir, "*"), target),
4754
)
4855
fmt.Fprintf(w, "%s\n", cmd)
4956
cmd.Stderr = os.Stderr
@@ -80,3 +87,13 @@ func createDirAndEnsureEmpty(dir string) error {
8087

8188
return nil
8289
}
90+
91+
func ParseRepoURL(repo string) (string, error) {
92+
u, err := url.Parse(repo)
93+
if err != nil || u.Scheme == "" || u.Host == "" {
94+
return "", usererr.New("Invalid URL format for --repo %s", repo)
95+
}
96+
// this is to handle cases where user puts repo url with .git at the end
97+
// like: https://github.com/jetpack-io/devbox.git
98+
return strings.TrimSuffix(repo, ".git"), nil
99+
}

internal/templates/templates_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"testing"
99

1010
"github.com/pkg/errors"
11+
"github.com/stretchr/testify/assert"
1112
)
1213

1314
func TestTemplatesExist(t *testing.T) {
@@ -26,3 +27,22 @@ func TestTemplatesExist(t *testing.T) {
2627
}
2728
}
2829
}
30+
31+
func TestParseRepoURL(t *testing.T) {
32+
// devbox create --repo="http:::/not.valid/a//a??a?b=&&c#hi"
33+
_, err := ParseRepoURL("http:::/not.valid/a//a??a?b=&&c#hi")
34+
assert.Error(t, err)
35+
_, err = ParseRepoURL("http//github.com")
36+
assert.Error(t, err)
37+
_, err = ParseRepoURL("github.com")
38+
assert.Error(t, err)
39+
_, err = ParseRepoURL("/foo/bar")
40+
assert.Error(t, err)
41+
_, err = ParseRepoURL("http://")
42+
assert.Error(t, err)
43+
_, err = ParseRepoURL("[email protected]:jetpack-io/devbox.git")
44+
assert.Error(t, err)
45+
u, err := ParseRepoURL("http://github.com")
46+
assert.NoError(t, err)
47+
assert.Equal(t, "http://github.com", u)
48+
}

0 commit comments

Comments
 (0)