Skip to content

Commit a4b4d7b

Browse files
committed
Add template add command
1 parent 6da2891 commit a4b4d7b

File tree

3 files changed

+176
-17
lines changed

3 files changed

+176
-17
lines changed

cmd/devcontainer/template.go

Lines changed: 66 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ package main
22

33
import (
44
"fmt"
5-
"io/ioutil"
65
"os"
6+
"sort"
77

88
"github.com/spf13/cobra"
9+
"github.com/stuartleeks/devcontainer-cli/internal/pkg/devcontainers"
10+
ioutil2 "github.com/stuartleeks/devcontainer-cli/internal/pkg/ioutil"
911
)
1012

1113
func createTemplateCommand() *cobra.Command {
@@ -15,38 +17,85 @@ func createTemplateCommand() *cobra.Command {
1517
Long: "Use subcommands to work with devcontainer templates",
1618
}
1719
cmd.AddCommand(createTemplateListCommand())
20+
cmd.AddCommand(createTemplateAddCommand())
1821
return cmd
1922
}
2023

2124
func createTemplateListCommand() *cobra.Command {
22-
isDevcontainerFolder := func(parentPath string, fi os.FileInfo) bool {
23-
if !fi.IsDir() {
24-
return false
25-
}
26-
devcontainerJsonPath := fmt.Sprintf("%s/%s/.devcontainer/devcontainer.json", parentPath, fi.Name())
27-
devContainerJsonInfo, err := os.Stat(devcontainerJsonPath)
28-
return err == nil && !devContainerJsonInfo.IsDir()
29-
}
25+
3026
cmd := &cobra.Command{
3127
Use: "list",
3228
Short: "list templates",
3329
Long: "List devcontainer templates",
3430
Run: func(cmd *cobra.Command, args []string) {
35-
const containerFolder string = "$HOME/source/vscode-dev-containers/containers" // TODO - make configurable!
3631

37-
folder := os.ExpandEnv(containerFolder)
38-
c, err := ioutil.ReadDir(folder)
32+
templates, err := devcontainers.GetTemplates()
33+
if err != nil {
34+
fmt.Println(err)
35+
os.Exit(1)
36+
}
37+
38+
for _, template := range templates {
39+
fmt.Println(template.Name)
40+
}
41+
},
42+
}
43+
return cmd
44+
}
45+
46+
func createTemplateAddCommand() *cobra.Command {
47+
cmd := &cobra.Command{
48+
Use: "add TEMPLATE_NAME",
49+
Short: "add devcontainer from template",
50+
Long: "Add a devcontainer definition to the current folder using the specified template",
51+
Run: func(cmd *cobra.Command, args []string) {
52+
53+
if len(args) != 1 {
54+
cmd.Usage()
55+
os.Exit(1)
56+
}
57+
name := args[0]
58+
59+
template, err := devcontainers.GetTemplateByName(name)
3960
if err != nil {
40-
fmt.Printf("Error reading devcontainer definitions: %s\n", err)
61+
fmt.Println(err)
4162
os.Exit(1)
4263
}
64+
if template == nil {
65+
fmt.Printf("Template '%s' not found\n", name)
66+
}
4367

44-
for _, entry := range c {
45-
if isDevcontainerFolder(folder, entry) {
46-
fmt.Println(entry.Name())
47-
}
68+
info, err := os.Stat("./.devcontainer")
69+
if info != nil && err == nil {
70+
fmt.Println("Current folder already contains a .devcontainer folder - exiting")
71+
os.Exit(1)
4872
}
4973

74+
currentDirectory, err := os.Getwd()
75+
if err != nil {
76+
fmt.Printf("Error reading current directory: %s\n", err)
77+
}
78+
if err = ioutil2.CopyFolder(template.Path, currentDirectory+"/.devcontainer"); err != nil {
79+
fmt.Printf("Error copying folder: %s\n", err)
80+
os.Exit(1)
81+
}
82+
},
83+
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
84+
// only completing the first arg (template name)
85+
if len(args) != 0 {
86+
return nil, cobra.ShellCompDirectiveNoFileComp
87+
}
88+
templates, err := devcontainers.GetTemplates()
89+
if err != nil {
90+
fmt.Printf("Error: %v", err)
91+
os.Exit(1)
92+
}
93+
names := []string{}
94+
for _, template := range templates {
95+
names = append(names, template.Name)
96+
}
97+
sort.Strings(names)
98+
return names, cobra.ShellCompDirectiveNoFileComp
5099
},
51100
}
52101
return cmd
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package devcontainers
2+
3+
import (
4+
"fmt"
5+
"io/ioutil"
6+
"os"
7+
)
8+
9+
// DevcontainerTemplate holds info on templates for list/add etc
10+
type DevcontainerTemplate struct {
11+
Name string
12+
// Path is the path including the .devcontainer folder
13+
Path string
14+
}
15+
16+
// GetTemplateByName returns the template with the specified name or nil if not found
17+
func GetTemplateByName(name string) (*DevcontainerTemplate, error) {
18+
// TODO - could possibly make this quicker by searching using the name rather than listing all and filtering
19+
templates, err := GetTemplates()
20+
if err != nil {
21+
return nil, err
22+
}
23+
for _, template := range templates {
24+
if template.Name == name {
25+
return &template, nil
26+
}
27+
}
28+
return nil, nil
29+
}
30+
31+
// GetTemplates returns a list of discovered templates
32+
func GetTemplates() ([]DevcontainerTemplate, error) {
33+
const containerFolder string = "$HOME/source/vscode-dev-containers/containers" // TODO - make configurable!
34+
35+
isDevcontainerFolder := func(parentPath string, fi os.FileInfo) bool {
36+
if !fi.IsDir() {
37+
return false
38+
}
39+
devcontainerJsonPath := fmt.Sprintf("%s/%s/.devcontainer/devcontainer.json", parentPath, fi.Name())
40+
devContainerJsonInfo, err := os.Stat(devcontainerJsonPath)
41+
return err == nil && !devContainerJsonInfo.IsDir()
42+
}
43+
44+
folder := os.ExpandEnv(containerFolder)
45+
c, err := ioutil.ReadDir(folder)
46+
if err != nil {
47+
return []DevcontainerTemplate{}, fmt.Errorf("Error reading devcontainer definitions: %s\n", err)
48+
}
49+
50+
templates := []DevcontainerTemplate{}
51+
for _, entry := range c {
52+
if isDevcontainerFolder(folder, entry) {
53+
template := DevcontainerTemplate{
54+
Name: entry.Name(),
55+
Path: fmt.Sprintf("%s/%s/.devcontainer", folder, entry.Name()),
56+
}
57+
templates = append(templates, template)
58+
}
59+
}
60+
return templates, nil
61+
}

internal/pkg/ioutil/files.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package ioutil
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"io/ioutil"
7+
"os"
8+
)
9+
10+
// TODO - recursive copy folder (also add recursive symlink)
11+
12+
func CopyFolder(source string, target string) error {
13+
sourceItem, err := os.Stat(source)
14+
if err != nil {
15+
return fmt.Errorf("Error reading source folder: %s\n", err)
16+
}
17+
if err = os.Mkdir(target, sourceItem.Mode()); err != nil {
18+
return fmt.Errorf("Error creating directory '%s': %s", target, err)
19+
}
20+
21+
sourceSubItems, err := ioutil.ReadDir(source)
22+
if err != nil {
23+
return fmt.Errorf("Error reading source folder contents: %s\n", err)
24+
}
25+
26+
for _, sourceSubItem := range sourceSubItems {
27+
if sourceSubItem.IsDir() {
28+
CopyFolder(source+"/"+sourceSubItem.Name(), target+"/"+sourceSubItem.Name())
29+
} else {
30+
CopyFile(source+"/"+sourceSubItem.Name(), target+"/"+sourceSubItem.Name(), sourceSubItem.Mode())
31+
}
32+
}
33+
return nil
34+
}
35+
func CopyFile(source string, target string, perm os.FileMode) error {
36+
sourceFile, err := os.Open(source)
37+
if err != nil {
38+
return err
39+
}
40+
defer sourceFile.Close()
41+
42+
targetFile, err := os.OpenFile(target, os.O_RDWR|os.O_CREATE|os.O_EXCL|os.O_TRUNC, perm)
43+
if err != nil {
44+
return err
45+
}
46+
defer targetFile.Close()
47+
_, err = io.Copy(targetFile, sourceFile)
48+
return err
49+
}

0 commit comments

Comments
 (0)