@@ -2,15 +2,17 @@ package examples
2
2
3
3
import (
4
4
"archive/tar"
5
+ "bufio"
5
6
"bytes"
6
7
"embed"
8
+ "encoding/json"
7
9
"io"
8
10
"io/fs"
9
11
"path"
12
+ "sort"
10
13
"strings"
11
14
"sync"
12
15
13
- "github.com/gohugoio/hugo/parser/pageparser"
14
16
"golang.org/x/sync/singleflight"
15
17
"golang.org/x/xerrors"
16
18
@@ -19,6 +21,8 @@ import (
19
21
20
22
var (
21
23
// Only some templates are embedded that we want to display inside the UI.
24
+ // The metadata in examples.gen.json is generated via scripts/examplegen.
25
+ //go:embed examples.gen.json
22
26
//go:embed templates/aws-ecs-container
23
27
//go:embed templates/aws-linux
24
28
//go:embed templates/aws-windows
@@ -34,112 +38,76 @@ var (
34
38
files embed.FS
35
39
36
40
exampleBasePath = "https://github.com/coder/coder/tree/main/examples/templates/"
37
- examples = make ([]codersdk.TemplateExample , 0 )
41
+ examplesJSON = "examples.gen.json"
42
+ parsedExamples []codersdk.TemplateExample
38
43
parseExamples sync.Once
39
- archives = singleflight.Group {}
44
+ archives singleflight.Group
40
45
ErrNotFound = xerrors .New ("example not found" )
41
46
)
42
47
43
48
const rootDir = "templates"
44
49
45
50
// List returns all embedded examples.
46
51
func List () ([]codersdk.TemplateExample , error ) {
47
- var returnError error
52
+ var err error
48
53
parseExamples .Do (func () {
49
- files , err := fs .Sub (files , rootDir )
50
- if err != nil {
51
- returnError = xerrors .Errorf ("get example fs: %w" , err )
52
- }
53
-
54
- dirs , err := fs .ReadDir (files , "." )
55
- if err != nil {
56
- returnError = xerrors .Errorf ("read dir: %w" , err )
57
- return
58
- }
59
-
60
- for _ , dir := range dirs {
61
- if ! dir .IsDir () {
62
- continue
63
- }
64
- exampleID := dir .Name ()
65
- exampleURL := exampleBasePath + exampleID
66
- // Each one of these is a example!
67
- readme , err := fs .ReadFile (files , path .Join (dir .Name (), "README.md" ))
68
- if err != nil {
69
- returnError = xerrors .Errorf ("example %q does not contain README.md" , exampleID )
70
- return
71
- }
54
+ parsedExamples , err = parseAndVerifyExamples ()
55
+ })
56
+ return parsedExamples , err
57
+ }
72
58
73
- frontMatter , err := pageparser .ParseFrontMatterAndContent (bytes .NewReader (readme ))
74
- if err != nil {
75
- returnError = xerrors .Errorf ("parse example %q front matter: %w" , exampleID , err )
76
- return
77
- }
59
+ func parseAndVerifyExamples () (examples []codersdk.TemplateExample , err error ) {
60
+ f , err := files .Open (examplesJSON )
61
+ if err != nil {
62
+ return nil , xerrors .Errorf ("open %s: %w" , examplesJSON , err )
63
+ }
64
+ defer f .Close ()
78
65
79
- nameRaw , exists := frontMatter .FrontMatter ["name" ]
80
- if ! exists {
81
- returnError = xerrors .Errorf ("example %q front matter does not contain name" , exampleID )
82
- return
83
- }
66
+ b := bufio .NewReader (f )
84
67
85
- name , valid := nameRaw .( string )
86
- if ! valid {
87
- returnError = xerrors . Errorf ( "example %q name isn't a string" , exampleID )
88
- return
89
- }
68
+ // Discard the first line (code generated by-comment).
69
+ _ , _ , err = b . ReadLine ()
70
+ if err != nil {
71
+ return nil , xerrors . Errorf ( "read %s: %w" , examplesJSON , err )
72
+ }
90
73
91
- descriptionRaw , exists := frontMatter .FrontMatter ["description" ]
92
- if ! exists {
93
- returnError = xerrors .Errorf ("example %q front matter does not contain name" , exampleID )
94
- return
95
- }
74
+ err = json .NewDecoder (b ).Decode (& examples )
75
+ if err != nil {
76
+ return nil , xerrors .Errorf ("decode %s: %w" , examplesJSON , err )
77
+ }
96
78
97
- description , valid := descriptionRaw .(string )
98
- if ! valid {
99
- returnError = xerrors .Errorf ("example %q description isn't a string" , exampleID )
100
- return
101
- }
79
+ // Sanity-check: Verify that the examples in the JSON file match the
80
+ // embedded files.
81
+ var wantEmbedFiles []string
82
+ for i , example := range examples {
83
+ examples [i ].URL = exampleBasePath + example .ID
84
+ wantEmbedFiles = append (wantEmbedFiles , example .ID )
85
+ }
102
86
103
- tags := []string {}
104
- tagsRaw , exists := frontMatter .FrontMatter ["tags" ]
105
- if exists {
106
- tagsI , valid := tagsRaw .([]interface {})
107
- if ! valid {
108
- returnError = xerrors .Errorf ("example %q tags isn't a slice: type %T" , exampleID , tagsRaw )
109
- return
110
- }
111
- for _ , tagI := range tagsI {
112
- tag , valid := tagI .(string )
113
- if ! valid {
114
- returnError = xerrors .Errorf ("example %q tag isn't a string: type %T" , exampleID , tagI )
115
- return
116
- }
117
- tags = append (tags , tag )
118
- }
119
- }
87
+ files , err := fs .Sub (files , rootDir )
88
+ if err != nil {
89
+ return nil , xerrors .Errorf ("get templates fs: %w" , err )
90
+ }
91
+ dirs , err := fs .ReadDir (files , "." )
92
+ if err != nil {
93
+ return nil , xerrors .Errorf ("read templates dir: %w" , err )
94
+ }
95
+ var gotEmbedFiles []string
96
+ for _ , dir := range dirs {
97
+ if dir .IsDir () {
98
+ gotEmbedFiles = append (gotEmbedFiles , dir .Name ())
99
+ }
100
+ }
120
101
121
- var icon string
122
- iconRaw , exists := frontMatter .FrontMatter ["icon" ]
123
- if exists {
124
- icon , valid = iconRaw .(string )
125
- if ! valid {
126
- returnError = xerrors .Errorf ("example %q icon isn't a string" , exampleID )
127
- return
128
- }
129
- }
102
+ sort .Strings (wantEmbedFiles )
103
+ sort .Strings (gotEmbedFiles )
104
+ want := strings .Join (wantEmbedFiles , ", " )
105
+ got := strings .Join (gotEmbedFiles , ", " )
106
+ if want != got {
107
+ return nil , xerrors .Errorf ("mismatch between %s and embedded files: want %q, got %q" , examplesJSON , want , got )
108
+ }
130
109
131
- examples = append (examples , codersdk.TemplateExample {
132
- ID : exampleID ,
133
- URL : exampleURL ,
134
- Name : name ,
135
- Description : description ,
136
- Icon : icon ,
137
- Tags : tags ,
138
- Markdown : string (frontMatter .Content ),
139
- })
140
- }
141
- })
142
- return examples , returnError
110
+ return examples , nil
143
111
}
144
112
145
113
// Archive returns a tar by example ID.
0 commit comments