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 ,