4
4
"context"
5
5
"fmt"
6
6
"io"
7
+ "strings"
7
8
"time"
8
9
9
10
"github.com/google/uuid"
@@ -29,7 +30,9 @@ func (r *RootCmd) create() *serpent.Command {
29
30
parameterFlags workspaceParameterFlags
30
31
autoUpdates string
31
32
copyParametersFrom string
32
- orgContext = NewOrganizationContext ()
33
+ // Organization context is only required if more than 1 template
34
+ // shares the same name across multiple organizations.
35
+ orgContext = NewOrganizationContext ()
33
36
)
34
37
client := new (codersdk.Client )
35
38
cmd := & serpent.Command {
@@ -44,11 +47,7 @@ func (r *RootCmd) create() *serpent.Command {
44
47
),
45
48
Middleware : serpent .Chain (r .InitClient (client )),
46
49
Handler : func (inv * serpent.Invocation ) error {
47
- organization , err := orgContext .Selected (inv , client )
48
- if err != nil {
49
- return err
50
- }
51
-
50
+ var err error
52
51
workspaceOwner := codersdk .Me
53
52
if len (inv .Args ) >= 1 {
54
53
workspaceOwner , workspaceName , err = splitNamedWorkspace (inv .Args [0 ])
@@ -99,7 +98,7 @@ func (r *RootCmd) create() *serpent.Command {
99
98
if templateName == "" {
100
99
_ , _ = fmt .Fprintln (inv .Stdout , pretty .Sprint (cliui .DefaultStyles .Wrap , "Select a template below to preview the provisioned infrastructure:" ))
101
100
102
- templates , err := client .TemplatesByOrganization (inv .Context (), organization . ID )
101
+ templates , err := client .Templates (inv .Context (), codersdk. TemplateFilter {} )
103
102
if err != nil {
104
103
return err
105
104
}
@@ -111,13 +110,28 @@ func (r *RootCmd) create() *serpent.Command {
111
110
templateNames := make ([]string , 0 , len (templates ))
112
111
templateByName := make (map [string ]codersdk.Template , len (templates ))
113
112
113
+ // If more than 1 organization exists in the list of templates,
114
+ // then include the organization name in the select options.
115
+ uniqueOrganizations := make (map [uuid.UUID ]bool )
116
+ for _ , template := range templates {
117
+ uniqueOrganizations [template .OrganizationID ] = true
118
+ }
119
+
114
120
for _ , template := range templates {
115
121
templateName := template .Name
122
+ if len (uniqueOrganizations ) > 1 {
123
+ templateName += cliui .Placeholder (
124
+ fmt .Sprintf (
125
+ " (%s)" ,
126
+ template .OrganizationName ,
127
+ ),
128
+ )
129
+ }
116
130
117
131
if template .ActiveUserCount > 0 {
118
132
templateName += cliui .Placeholder (
119
133
fmt .Sprintf (
120
- " ( used by %s) " ,
134
+ " used by %s" ,
121
135
formatActiveDevelopers (template .ActiveUserCount ),
122
136
),
123
137
)
@@ -145,13 +159,65 @@ func (r *RootCmd) create() *serpent.Command {
145
159
}
146
160
templateVersionID = sourceWorkspace .LatestBuild .TemplateVersionID
147
161
} else {
148
- template , err = client .TemplateByName (inv .Context (), organization .ID , templateName )
162
+ templates , err := client .Templates (inv .Context (), codersdk.TemplateFilter {
163
+ ExactName : templateName ,
164
+ })
149
165
if err != nil {
150
166
return xerrors .Errorf ("get template by name: %w" , err )
151
167
}
168
+ if len (templates ) == 0 {
169
+ return xerrors .Errorf ("no template found with the name %q" , templateName )
170
+ }
171
+
172
+ if len (templates ) > 1 {
173
+ templateOrgs := []string {}
174
+ for _ , tpl := range templates {
175
+ templateOrgs = append (templateOrgs , tpl .OrganizationName )
176
+ }
177
+
178
+ selectedOrg , err := orgContext .Selected (inv , client )
179
+ if err != nil {
180
+ return xerrors .Errorf ("multiple templates found with the name %q, use `--org=<organization_name>` to specify which template by that name to use. Organizations available: %s" , templateName , strings .Join (templateOrgs , ", " ))
181
+ }
182
+
183
+ index := slices .IndexFunc (templates , func (i codersdk.Template ) bool {
184
+ return i .OrganizationID == selectedOrg .ID
185
+ })
186
+ if index == - 1 {
187
+ return xerrors .Errorf ("no templates found with the name %q in the organization %q. Templates by that name exist in organizations: %s. Use --org=<organization_name> to select one." , templateName , selectedOrg .Name , strings .Join (templateOrgs , ", " ))
188
+ }
189
+
190
+ // remake the list with the only template selected
191
+ templates = []codersdk.Template {templates [index ]}
192
+ }
193
+
194
+ template = templates [0 ]
152
195
templateVersionID = template .ActiveVersionID
153
196
}
154
197
198
+ // If the user specified an organization via a flag or env var, the template **must**
199
+ // be in that organization. Otherwise, we should throw an error.
200
+ orgValue , orgValueSource := orgContext .ValueSource (inv )
201
+ if orgValue != "" && ! (orgValueSource == serpent .ValueSourceDefault || orgValueSource == serpent .ValueSourceNone ) {
202
+ selectedOrg , err := orgContext .Selected (inv , client )
203
+ if err != nil {
204
+ return err
205
+ }
206
+
207
+ if template .OrganizationID != selectedOrg .ID {
208
+ orgNameFormat := "'--org=%q'"
209
+ if orgValueSource == serpent .ValueSourceEnv {
210
+ orgNameFormat = "CODER_ORGANIZATION=%q"
211
+ }
212
+
213
+ return xerrors .Errorf ("template is in organization %q, but %s was specified. Use %s to use this template" ,
214
+ template .OrganizationName ,
215
+ fmt .Sprintf (orgNameFormat , selectedOrg .Name ),
216
+ fmt .Sprintf (orgNameFormat , template .OrganizationName ),
217
+ )
218
+ }
219
+ }
220
+
155
221
var schedSpec * string
156
222
if startAt != "" {
157
223
sched , err := parseCLISchedule (startAt )
@@ -207,7 +273,7 @@ func (r *RootCmd) create() *serpent.Command {
207
273
ttlMillis = ptr .Ref (stopAfter .Milliseconds ())
208
274
}
209
275
210
- workspace , err := client .CreateWorkspace (inv .Context (), organization . ID , workspaceOwner , codersdk.CreateWorkspaceRequest {
276
+ workspace , err := client .CreateWorkspace (inv .Context (), template . OrganizationID , workspaceOwner , codersdk.CreateWorkspaceRequest {
211
277
TemplateVersionID : templateVersionID ,
212
278
Name : workspaceName ,
213
279
AutostartSchedule : schedSpec ,
0 commit comments