2022-04-06 17:42:40 +00:00
package coderd
import (
"database/sql"
"errors"
"fmt"
"net/http"
2022-08-30 17:27:33 +00:00
"sort"
2022-06-07 12:37:45 +00:00
"time"
2022-04-06 17:42:40 +00:00
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
2022-05-16 19:36:27 +00:00
"golang.org/x/xerrors"
2022-04-06 17:42:40 +00:00
2023-08-18 18:55:43 +00:00
"github.com/coder/coder/v2/coderd/audit"
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/coderd/database/dbauthz"
2023-09-01 16:50:12 +00:00
"github.com/coder/coder/v2/coderd/database/dbtime"
2023-08-18 18:55:43 +00:00
"github.com/coder/coder/v2/coderd/httpapi"
"github.com/coder/coder/v2/coderd/httpmw"
"github.com/coder/coder/v2/coderd/rbac"
"github.com/coder/coder/v2/coderd/schedule"
"github.com/coder/coder/v2/coderd/telemetry"
"github.com/coder/coder/v2/coderd/util/ptr"
"github.com/coder/coder/v2/codersdk"
"github.com/coder/coder/v2/examples"
2022-04-06 17:42:40 +00:00
)
2023-01-13 11:27:21 +00:00
// Returns a single template.
//
2022-12-19 17:43:46 +00:00
// @Summary Get template metadata by ID
// @ID get-template-metadata-by-id
// @Security CoderSessionToken
// @Produce json
// @Tags Templates
2023-01-13 11:27:21 +00:00
// @Param template path string true "Template ID" format(uuid)
2022-12-19 17:43:46 +00:00
// @Success 200 {object} codersdk.Template
2023-01-13 11:27:21 +00:00
// @Router /templates/{template} [get]
2022-05-26 03:14:08 +00:00
func ( api * API ) template ( rw http . ResponseWriter , r * http . Request ) {
2022-09-21 22:07:00 +00:00
ctx := r . Context ( )
2022-04-06 17:42:40 +00:00
template := httpmw . TemplateParam ( r )
2022-05-13 22:54:32 +00:00
2023-07-19 20:07:33 +00:00
httpapi . Write ( ctx , rw , http . StatusOK , api . convertTemplate ( template ) )
2022-04-06 17:42:40 +00:00
}
2022-12-19 17:43:46 +00:00
// @Summary Delete template by ID
// @ID delete-template-by-id
// @Security CoderSessionToken
// @Produce json
// @Tags Templates
2023-01-13 11:27:21 +00:00
// @Param template path string true "Template ID" format(uuid)
2022-12-19 17:43:46 +00:00
// @Success 200 {object} codersdk.Response
2023-01-13 11:27:21 +00:00
// @Router /templates/{template} [delete]
2022-05-26 03:14:08 +00:00
func ( api * API ) deleteTemplate ( rw http . ResponseWriter , r * http . Request ) {
2022-09-09 16:34:23 +00:00
var (
2022-09-21 22:07:00 +00:00
ctx = r . Context ( )
2022-09-09 16:34:23 +00:00
template = httpmw . TemplateParam ( r )
2022-09-20 04:11:01 +00:00
auditor = * api . Auditor . Load ( )
2022-09-09 16:34:23 +00:00
aReq , commitAudit = audit . InitRequest [ database . Template ] ( rw , & audit . RequestParams {
2024-02-26 14:27:33 +00:00
Audit : auditor ,
Log : api . Logger ,
Request : r ,
Action : database . AuditActionDelete ,
OrganizationID : template . OrganizationID ,
2022-09-09 16:34:23 +00:00
} )
)
defer commitAudit ( )
aReq . Old = template
2023-02-28 17:04:39 +00:00
// This is just to get the workspace count, so we use a system context to
// return ALL workspaces. Not just workspaces the user can view.
// nolint:gocritic
workspaces , err := api . Database . GetWorkspaces ( dbauthz . AsSystemRestricted ( ctx ) , database . GetWorkspacesParams {
2023-07-21 18:00:19 +00:00
TemplateIDs : [ ] uuid . UUID { template . ID } ,
2022-04-06 17:42:40 +00:00
} )
2022-09-09 16:34:23 +00:00
if err != nil && ! errors . Is ( err , sql . ErrNoRows ) {
2022-09-21 22:07:00 +00:00
httpapi . Write ( ctx , rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : "Internal error fetching workspaces by template id." ,
2022-06-03 21:48:09 +00:00
Detail : err . Error ( ) ,
2022-04-06 17:42:40 +00:00
} )
return
}
if len ( workspaces ) > 0 {
2023-01-13 14:30:48 +00:00
httpapi . Write ( ctx , rw , http . StatusBadRequest , codersdk . Response {
2022-04-06 17:42:40 +00:00
Message : "All workspaces must be deleted before a template can be removed." ,
} )
return
}
2022-09-21 22:07:00 +00:00
err = api . Database . UpdateTemplateDeletedByID ( ctx , database . UpdateTemplateDeletedByIDParams {
2022-06-30 12:14:51 +00:00
ID : template . ID ,
Deleted : true ,
2023-09-01 16:50:12 +00:00
UpdatedAt : dbtime . Now ( ) ,
2022-04-06 17:42:40 +00:00
} )
if err != nil {
2022-09-21 22:07:00 +00:00
httpapi . Write ( ctx , rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : "Internal error deleting template." ,
2022-06-03 21:48:09 +00:00
Detail : err . Error ( ) ,
2022-04-06 17:42:40 +00:00
} )
return
}
2022-09-21 22:07:00 +00:00
httpapi . Write ( ctx , rw , http . StatusOK , codersdk . Response {
2022-04-06 17:42:40 +00:00
Message : "Template has been deleted!" ,
} )
}
2023-01-13 11:27:21 +00:00
// Create a new template in an organization.
// Returns a single template.
//
2022-12-19 17:43:46 +00:00
// @Summary Create template by organization
// @ID create-template-by-organization
// @Security CoderSessionToken
2022-12-21 14:37:30 +00:00
// @Accept json
2022-12-19 17:43:46 +00:00
// @Produce json
// @Tags Templates
// @Param request body codersdk.CreateTemplateRequest true "Request body"
2023-01-11 11:16:09 +00:00
// @Param organization path string true "Organization ID"
2022-12-19 17:43:46 +00:00
// @Success 200 {object} codersdk.Template
2023-01-11 11:16:09 +00:00
// @Router /organizations/{organization}/templates [post]
2022-05-26 03:14:08 +00:00
func ( api * API ) postTemplateByOrganization ( rw http . ResponseWriter , r * http . Request ) {
2022-09-09 16:34:23 +00:00
var (
2022-09-21 22:07:00 +00:00
ctx = r . Context ( )
2022-09-09 16:34:23 +00:00
createTemplate codersdk . CreateTemplateRequest
organization = httpmw . OrganizationParam ( r )
apiKey = httpmw . APIKey ( r )
2022-09-20 04:11:01 +00:00
auditor = * api . Auditor . Load ( )
2022-09-09 16:34:23 +00:00
templateAudit , commitTemplateAudit = audit . InitRequest [ database . Template ] ( rw , & audit . RequestParams {
2024-02-26 14:27:33 +00:00
Audit : auditor ,
Log : api . Logger ,
Request : r ,
Action : database . AuditActionCreate ,
OrganizationID : organization . ID ,
2022-09-09 16:34:23 +00:00
} )
templateVersionAudit , commitTemplateVersionAudit = audit . InitRequest [ database . TemplateVersion ] ( rw , & audit . RequestParams {
2024-02-26 14:27:33 +00:00
Audit : auditor ,
Log : api . Logger ,
Request : r ,
Action : database . AuditActionWrite ,
OrganizationID : organization . ID ,
2022-09-09 16:34:23 +00:00
} )
)
defer commitTemplateAudit ( )
defer commitTemplateVersionAudit ( )
2022-09-21 22:07:00 +00:00
if ! httpapi . Read ( ctx , rw , r , & createTemplate ) {
2022-05-10 07:44:09 +00:00
return
}
2023-04-27 23:55:34 +00:00
// Make a temporary struct to represent the template. This is used for
// auditing if any of the following checks fail. It will be overwritten when
// the template is inserted into the db.
templateAudit . New = database . Template {
OrganizationID : organization . ID ,
Name : createTemplate . Name ,
Description : createTemplate . Description ,
CreatedBy : apiKey . UserID ,
Icon : createTemplate . Icon ,
DisplayName : createTemplate . DisplayName ,
}
2022-09-21 22:07:00 +00:00
_ , err := api . Database . GetTemplateByOrganizationAndName ( ctx , database . GetTemplateByOrganizationAndNameParams {
2022-05-16 19:36:27 +00:00
OrganizationID : organization . ID ,
Name : createTemplate . Name ,
2022-05-10 07:44:09 +00:00
} )
2022-05-16 19:36:27 +00:00
if err == nil {
2022-09-21 22:07:00 +00:00
httpapi . Write ( ctx , rw , http . StatusConflict , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : fmt . Sprintf ( "Template with name %q already exists." , createTemplate . Name ) ,
2022-07-13 00:15:02 +00:00
Validations : [ ] codersdk . ValidationError { {
2022-05-16 19:36:27 +00:00
Field : "name" ,
2022-05-16 20:56:11 +00:00
Detail : "This value is already in use and should be unique." ,
2022-05-16 19:36:27 +00:00
} } ,
} )
2022-05-10 07:44:09 +00:00
return
2022-04-06 17:42:40 +00:00
}
2022-05-16 19:36:27 +00:00
if ! errors . Is ( err , sql . ErrNoRows ) {
2022-09-21 22:07:00 +00:00
httpapi . Write ( ctx , rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : "Internal error fetching template by name." ,
2022-06-03 21:48:09 +00:00
Detail : err . Error ( ) ,
2022-04-06 17:42:40 +00:00
} )
return
}
2023-04-27 23:55:34 +00:00
2022-09-21 22:07:00 +00:00
templateVersion , err := api . Database . GetTemplateVersionByID ( ctx , createTemplate . VersionID )
2022-05-16 19:36:27 +00:00
if errors . Is ( err , sql . ErrNoRows ) {
2022-09-21 22:07:00 +00:00
httpapi . Write ( ctx , rw , http . StatusNotFound , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : fmt . Sprintf ( "Template version %q does not exist." , createTemplate . VersionID ) ,
2022-07-13 00:15:02 +00:00
Validations : [ ] codersdk . ValidationError {
2022-06-03 21:48:09 +00:00
{ Field : "template_version_id" , Detail : "Template version does not exist" } ,
} ,
2022-05-16 19:36:27 +00:00
} )
return
2022-04-06 17:42:40 +00:00
}
if err != nil {
2022-09-21 22:07:00 +00:00
httpapi . Write ( ctx , rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : "Internal error fetching template version." ,
2022-06-03 21:48:09 +00:00
Detail : err . Error ( ) ,
2022-04-06 17:42:40 +00:00
} )
return
}
2023-10-11 14:26:22 +00:00
if templateVersion . Archived {
httpapi . Write ( ctx , rw , http . StatusBadRequest , codersdk . Response {
Message : fmt . Sprintf ( "Template version %s is archived." , createTemplate . VersionID ) ,
Validations : [ ] codersdk . ValidationError {
{ Field : "template_version_id" , Detail : "Template version is archived" } ,
} ,
} )
return
}
2022-09-09 16:34:23 +00:00
templateVersionAudit . Old = templateVersion
2023-05-23 08:06:33 +00:00
if templateVersion . TemplateID . Valid {
httpapi . Write ( ctx , rw , http . StatusBadRequest , codersdk . Response {
Message : fmt . Sprintf ( "Template version %s is already part of a template" , createTemplate . VersionID ) ,
Validations : [ ] codersdk . ValidationError {
{ Field : "template_version_id" , Detail : "Template version is already part of a template" } ,
} ,
} )
return
}
2022-09-09 16:34:23 +00:00
2022-09-21 22:07:00 +00:00
importJob , err := api . Database . GetProvisionerJobByID ( ctx , templateVersion . JobID )
2022-05-16 19:36:27 +00:00
if err != nil {
2022-09-21 22:07:00 +00:00
httpapi . Write ( ctx , rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : "Internal error fetching provisioner job." ,
2022-06-03 21:48:09 +00:00
Detail : err . Error ( ) ,
2022-05-16 19:36:27 +00:00
} )
return
2022-04-06 17:42:40 +00:00
}
2023-03-07 14:14:58 +00:00
var (
2023-12-15 08:27:56 +00:00
defaultTTL time . Duration
2024-02-13 07:00:35 +00:00
activityBump = time . Hour // default
2023-10-13 16:57:18 +00:00
autostopRequirementDaysOfWeek [ ] string
autostartRequirementDaysOfWeek [ ] string
autostopRequirementWeeks int64
failureTTL time . Duration
dormantTTL time . Duration
dormantAutoDeletionTTL time . Duration
2023-03-07 14:14:58 +00:00
)
2022-11-09 19:36:25 +00:00
if createTemplate . DefaultTTLMillis != nil {
2023-03-07 14:14:58 +00:00
defaultTTL = time . Duration ( * createTemplate . DefaultTTLMillis ) * time . Millisecond
}
2024-02-13 07:00:35 +00:00
if createTemplate . ActivityBumpMillis != nil {
activityBump = time . Duration ( * createTemplate . ActivityBumpMillis ) * time . Millisecond
}
2023-08-29 18:35:05 +00:00
if createTemplate . AutostopRequirement != nil {
autostopRequirementDaysOfWeek = createTemplate . AutostopRequirement . DaysOfWeek
autostopRequirementWeeks = createTemplate . AutostopRequirement . Weeks
2023-03-07 14:14:58 +00:00
}
2023-10-13 16:57:18 +00:00
if createTemplate . AutostartRequirement != nil {
autostartRequirementDaysOfWeek = createTemplate . AutostartRequirement . DaysOfWeek
} else {
// By default, we want to allow all days of the week to be autostarted.
autostartRequirementDaysOfWeek = codersdk . BitmapToWeekdays ( 0b01111111 )
}
2023-05-10 19:57:11 +00:00
if createTemplate . FailureTTLMillis != nil {
failureTTL = time . Duration ( * createTemplate . FailureTTLMillis ) * time . Millisecond
}
2023-08-24 18:25:54 +00:00
if createTemplate . TimeTilDormantMillis != nil {
dormantTTL = time . Duration ( * createTemplate . TimeTilDormantMillis ) * time . Millisecond
2023-05-10 19:57:11 +00:00
}
2023-08-24 18:25:54 +00:00
if createTemplate . TimeTilDormantAutoDeleteMillis != nil {
dormantAutoDeletionTTL = time . Duration ( * createTemplate . TimeTilDormantAutoDeleteMillis ) * time . Millisecond
2023-07-03 02:29:52 +00:00
}
2023-03-07 14:14:58 +00:00
2023-07-20 13:35:41 +00:00
var (
2023-10-13 16:57:18 +00:00
validErrs [ ] codersdk . ValidationError
autostopRequirementDaysOfWeekParsed uint8
autostartRequirementDaysOfWeekParsed uint8
2023-07-20 13:35:41 +00:00
)
2023-03-07 14:14:58 +00:00
if defaultTTL < 0 {
validErrs = append ( validErrs , codersdk . ValidationError { Field : "default_ttl_ms" , Detail : "Must be a positive integer." } )
}
2024-02-13 07:00:35 +00:00
if activityBump < 0 {
validErrs = append ( validErrs , codersdk . ValidationError { Field : "activity_bump_ms" , Detail : "Must be a positive integer." } )
}
2024-03-20 15:37:57 +00:00
2023-08-29 18:35:05 +00:00
if len ( autostopRequirementDaysOfWeek ) > 0 {
autostopRequirementDaysOfWeekParsed , err = codersdk . WeekdaysToBitmap ( autostopRequirementDaysOfWeek )
2023-07-20 13:35:41 +00:00
if err != nil {
2023-08-29 18:35:05 +00:00
validErrs = append ( validErrs , codersdk . ValidationError { Field : "autostop_requirement.days_of_week" , Detail : err . Error ( ) } )
2023-07-20 13:35:41 +00:00
}
}
2023-10-13 16:57:18 +00:00
if len ( autostartRequirementDaysOfWeek ) > 0 {
autostartRequirementDaysOfWeekParsed , err = codersdk . WeekdaysToBitmap ( autostartRequirementDaysOfWeek )
if err != nil {
validErrs = append ( validErrs , codersdk . ValidationError { Field : "autostart_requirement.days_of_week" , Detail : err . Error ( ) } )
}
}
2024-03-20 15:37:57 +00:00
2023-08-29 18:35:05 +00:00
if autostopRequirementWeeks < 0 {
validErrs = append ( validErrs , codersdk . ValidationError { Field : "autostop_requirement.weeks" , Detail : "Must be a positive integer." } )
2023-07-20 13:35:41 +00:00
}
2023-08-29 18:35:05 +00:00
if autostopRequirementWeeks > schedule . MaxTemplateAutostopRequirementWeeks {
validErrs = append ( validErrs , codersdk . ValidationError { Field : "autostop_requirement.weeks" , Detail : fmt . Sprintf ( "Must be less than %d." , schedule . MaxTemplateAutostopRequirementWeeks ) } )
2023-07-20 13:35:41 +00:00
}
2023-05-10 19:57:11 +00:00
if failureTTL < 0 {
validErrs = append ( validErrs , codersdk . ValidationError { Field : "failure_ttl_ms" , Detail : "Must be a positive integer." } )
}
2023-08-24 18:25:54 +00:00
if dormantTTL < 0 {
validErrs = append ( validErrs , codersdk . ValidationError { Field : "time_til_dormant_autodeletion_ms" , Detail : "Must be a positive integer." } )
2023-05-10 19:57:11 +00:00
}
2023-08-24 18:25:54 +00:00
if dormantAutoDeletionTTL < 0 {
validErrs = append ( validErrs , codersdk . ValidationError { Field : "time_til_dormant_autodeletion_ms" , Detail : "Must be a positive integer." } )
2023-07-03 02:29:52 +00:00
}
2023-03-07 14:14:58 +00:00
if len ( validErrs ) > 0 {
2022-09-21 22:07:00 +00:00
httpapi . Write ( ctx , rw , http . StatusBadRequest , codersdk . Response {
2023-03-07 14:14:58 +00:00
Message : "Invalid create template request." ,
Validations : validErrs ,
2022-08-24 14:45:14 +00:00
} )
return
}
2023-04-04 12:48:35 +00:00
var (
2023-04-27 23:55:34 +00:00
dbTemplate database . Template
allowUserCancelWorkspaceJobs = ptr . NilToDefault ( createTemplate . AllowUserCancelWorkspaceJobs , false )
allowUserAutostart = ptr . NilToDefault ( createTemplate . AllowUserAutostart , true )
allowUserAutostop = ptr . NilToDefault ( createTemplate . AllowUserAutostop , true )
2023-04-04 12:48:35 +00:00
)
2022-11-21 10:43:53 +00:00
2023-06-14 16:08:58 +00:00
defaultsGroups := database . TemplateACL { }
if ! createTemplate . DisableEveryoneGroupAccess {
// The organization ID is used as the group ID for the everyone group
// in this organization.
defaultsGroups [ organization . ID . String ( ) ] = [ ] rbac . Action { rbac . ActionRead }
}
2022-10-10 20:37:06 +00:00
err = api . Database . InTx ( func ( tx database . Store ) error {
2023-09-01 16:50:12 +00:00
now := dbtime . Now ( )
2023-07-19 20:07:33 +00:00
id := uuid . New ( )
err = tx . InsertTemplate ( ctx , database . InsertTemplateParams {
ID : id ,
2023-06-14 16:08:58 +00:00
CreatedAt : now ,
UpdatedAt : now ,
OrganizationID : organization . ID ,
Name : createTemplate . Name ,
Provisioner : importJob . Provisioner ,
ActiveVersionID : templateVersion . ID ,
Description : createTemplate . Description ,
CreatedBy : apiKey . UserID ,
UserACL : database . TemplateACL { } ,
GroupACL : defaultsGroups ,
2022-11-21 10:43:53 +00:00
DisplayName : createTemplate . DisplayName ,
Icon : createTemplate . Icon ,
AllowUserCancelWorkspaceJobs : allowUserCancelWorkspaceJobs ,
2024-02-13 14:31:20 +00:00
MaxPortSharingLevel : database . AppSharingLevelOwner ,
2022-05-16 19:36:27 +00:00
} )
if err != nil {
return xerrors . Errorf ( "insert template: %s" , err )
}
2023-10-18 22:07:21 +00:00
if createTemplate . RequireActiveVersion {
err = ( * api . AccessControlStore . Load ( ) ) . SetTemplateAccessControl ( ctx , tx , id , dbauthz . TemplateAccessControl {
RequireActiveVersion : createTemplate . RequireActiveVersion ,
} )
if err != nil {
return xerrors . Errorf ( "set template access control: %w" , err )
}
}
2023-07-19 20:07:33 +00:00
dbTemplate , err = tx . GetTemplateByID ( ctx , id )
if err != nil {
return xerrors . Errorf ( "get template by id: %s" , err )
}
2023-07-20 13:35:41 +00:00
dbTemplate , err = ( * api . TemplateScheduleStore . Load ( ) ) . Set ( ctx , tx , dbTemplate , schedule . TemplateScheduleOptions {
2023-04-04 12:48:35 +00:00
UserAutostartEnabled : allowUserAutostart ,
UserAutostopEnabled : allowUserAutostop ,
DefaultTTL : defaultTTL ,
2024-02-13 07:00:35 +00:00
ActivityBump : activityBump ,
2023-05-10 19:57:11 +00:00
// Some of these values are enterprise-only, but the
// TemplateScheduleStore will handle avoiding setting them if
// unlicensed.
2023-08-29 18:35:05 +00:00
AutostopRequirement : schedule . TemplateAutostopRequirement {
DaysOfWeek : autostopRequirementDaysOfWeekParsed ,
Weeks : autostopRequirementWeeks ,
2023-07-20 13:35:41 +00:00
} ,
2023-10-13 16:57:18 +00:00
AutostartRequirement : schedule . TemplateAutostartRequirement {
DaysOfWeek : autostartRequirementDaysOfWeekParsed ,
} ,
2023-08-24 18:25:54 +00:00
FailureTTL : failureTTL ,
TimeTilDormant : dormantTTL ,
TimeTilDormantAutoDelete : dormantAutoDeletionTTL ,
2023-03-07 14:14:58 +00:00
} )
if err != nil {
return xerrors . Errorf ( "set template schedule options: %s" , err )
}
2022-09-09 16:34:23 +00:00
templateAudit . New = dbTemplate
2023-07-25 13:14:38 +00:00
err = tx . UpdateTemplateVersionByID ( ctx , database . UpdateTemplateVersionByIDParams {
2022-05-16 19:36:27 +00:00
ID : templateVersion . ID ,
TemplateID : uuid . NullUUID {
UUID : dbTemplate . ID ,
Valid : true ,
} ,
2023-09-01 16:50:12 +00:00
UpdatedAt : dbtime . Now ( ) ,
2023-03-23 16:26:50 +00:00
Name : templateVersion . Name ,
2023-07-13 13:36:10 +00:00
Message : templateVersion . Message ,
2022-05-16 19:36:27 +00:00
} )
if err != nil {
return xerrors . Errorf ( "insert template version: %s" , err )
}
2022-09-09 16:34:23 +00:00
newTemplateVersion := templateVersion
newTemplateVersion . TemplateID = uuid . NullUUID {
UUID : dbTemplate . ID ,
Valid : true ,
}
templateVersionAudit . New = newTemplateVersion
2022-05-16 19:36:27 +00:00
return nil
2022-11-14 17:57:33 +00:00
} , nil )
2022-05-16 19:36:27 +00:00
if err != nil {
2022-09-21 22:07:00 +00:00
httpapi . Write ( ctx , rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : "Internal error inserting template." ,
2022-06-03 21:48:09 +00:00
Detail : err . Error ( ) ,
2022-05-16 19:36:27 +00:00
} )
return
2022-04-06 17:42:40 +00:00
}
2022-04-12 15:17:33 +00:00
2022-06-17 05:26:40 +00:00
api . Telemetry . Report ( & telemetry . Snapshot {
Templates : [ ] telemetry . Template { telemetry . ConvertTemplate ( dbTemplate ) } ,
TemplateVersions : [ ] telemetry . TemplateVersion { telemetry . ConvertTemplateVersion ( templateVersion ) } ,
} )
2024-02-13 07:00:35 +00:00
httpapi . Write ( ctx , rw , http . StatusCreated , api . convertTemplate ( dbTemplate ) )
2022-04-06 17:42:40 +00:00
}
2022-12-19 17:43:46 +00:00
// @Summary Get templates by organization
// @ID get-templates-by-organization
// @Security CoderSessionToken
// @Produce json
// @Tags Templates
// @Param organization path string true "Organization ID" format(uuid)
2023-01-11 11:16:09 +00:00
// @Success 200 {array} codersdk.Template
2022-12-19 17:43:46 +00:00
// @Router /organizations/{organization}/templates [get]
2022-05-26 03:14:08 +00:00
func ( api * API ) templatesByOrganization ( rw http . ResponseWriter , r * http . Request ) {
2022-09-21 22:07:00 +00:00
ctx := r . Context ( )
2022-05-16 19:36:27 +00:00
organization := httpmw . OrganizationParam ( r )
2022-11-28 18:12:34 +00:00
2023-11-20 19:16:18 +00:00
p := httpapi . NewQueryParamParser ( )
values := r . URL . Query ( )
deprecated := sql . NullBool { }
if values . Has ( "deprecated" ) {
deprecated = sql . NullBool {
Bool : p . Boolean ( values , false , "deprecated" ) ,
Valid : true ,
}
}
if len ( p . Errors ) > 0 {
httpapi . Write ( ctx , rw , http . StatusBadRequest , codersdk . Response {
Message : "Invalid query params." ,
Validations : p . Errors ,
} )
return
}
2022-11-28 18:12:34 +00:00
prepared , err := api . HTTPAuth . AuthorizeSQLFilter ( r , rbac . ActionRead , rbac . ResourceTemplate . Type )
2022-04-06 17:42:40 +00:00
if err != nil {
2022-09-21 22:07:00 +00:00
httpapi . Write ( ctx , rw , http . StatusInternalServerError , codersdk . Response {
2022-11-28 18:12:34 +00:00
Message : "Internal error preparing sql filter." ,
2022-06-03 21:48:09 +00:00
Detail : err . Error ( ) ,
2022-04-06 17:42:40 +00:00
} )
return
}
2022-05-24 13:43:34 +00:00
// Filter templates based on rbac permissions
2022-11-28 18:12:34 +00:00
templates , err := api . Database . GetAuthorizedTemplates ( ctx , database . GetTemplatesWithFilterParams {
OrganizationID : organization . ID ,
2023-11-20 19:16:18 +00:00
Deprecated : deprecated ,
2022-11-28 18:12:34 +00:00
} , prepared )
if errors . Is ( err , sql . ErrNoRows ) {
err = nil
}
2022-08-11 22:07:48 +00:00
if err != nil {
2022-09-21 22:07:00 +00:00
httpapi . Write ( ctx , rw , http . StatusInternalServerError , codersdk . Response {
2022-11-28 18:12:34 +00:00
Message : "Internal error fetching templates in organization." ,
2022-08-11 22:07:48 +00:00
Detail : err . Error ( ) ,
} )
return
}
2022-05-24 13:43:34 +00:00
2023-07-19 20:07:33 +00:00
httpapi . Write ( ctx , rw , http . StatusOK , api . convertTemplates ( templates ) )
2022-04-06 17:42:40 +00:00
}
2022-12-19 17:43:46 +00:00
// @Summary Get templates by organization and template name
// @ID get-templates-by-organization-and-template-name
// @Security CoderSessionToken
// @Produce json
// @Tags Templates
// @Param organization path string true "Organization ID" format(uuid)
2023-01-11 11:16:09 +00:00
// @Param templatename path string true "Template name"
2022-12-19 17:43:46 +00:00
// @Success 200 {object} codersdk.Template
2023-01-11 11:16:09 +00:00
// @Router /organizations/{organization}/templates/{templatename} [get]
2022-05-26 03:14:08 +00:00
func ( api * API ) templateByOrganizationAndName ( rw http . ResponseWriter , r * http . Request ) {
2022-09-21 22:07:00 +00:00
ctx := r . Context ( )
2022-05-16 19:36:27 +00:00
organization := httpmw . OrganizationParam ( r )
templateName := chi . URLParam ( r , "templatename" )
2022-09-21 22:07:00 +00:00
template , err := api . Database . GetTemplateByOrganizationAndName ( ctx , database . GetTemplateByOrganizationAndNameParams {
2022-05-16 19:36:27 +00:00
OrganizationID : organization . ID ,
Name : templateName ,
} )
2022-04-06 17:42:40 +00:00
if err != nil {
2023-04-13 18:06:16 +00:00
if httpapi . Is404Error ( err ) {
2022-06-14 15:14:05 +00:00
httpapi . ResourceNotFound ( rw )
2022-05-16 19:36:27 +00:00
return
}
2022-09-21 22:07:00 +00:00
httpapi . Write ( ctx , rw , http . StatusInternalServerError , codersdk . Response {
2022-06-07 14:33:06 +00:00
Message : "Internal error fetching template." ,
2022-06-03 21:48:09 +00:00
Detail : err . Error ( ) ,
2022-04-06 17:42:40 +00:00
} )
return
}
2022-05-16 19:36:27 +00:00
2023-07-19 20:07:33 +00:00
httpapi . Write ( ctx , rw , http . StatusOK , api . convertTemplate ( template ) )
2022-04-06 17:42:40 +00:00
}
2022-12-19 17:43:46 +00:00
// @Summary Update template metadata by ID
2023-01-13 11:27:21 +00:00
// @ID update-template-metadata-by-id
2022-12-19 17:43:46 +00:00
// @Security CoderSessionToken
// @Produce json
// @Tags Templates
2023-01-13 11:27:21 +00:00
// @Param template path string true "Template ID" format(uuid)
2022-12-19 17:43:46 +00:00
// @Success 200 {object} codersdk.Template
2023-01-13 11:27:21 +00:00
// @Router /templates/{template} [patch]
2022-06-08 14:14:57 +00:00
func ( api * API ) patchTemplateMeta ( rw http . ResponseWriter , r * http . Request ) {
2022-09-09 16:34:23 +00:00
var (
2022-09-21 22:07:00 +00:00
ctx = r . Context ( )
2022-09-09 16:34:23 +00:00
template = httpmw . TemplateParam ( r )
2022-09-20 04:11:01 +00:00
auditor = * api . Auditor . Load ( )
2024-02-13 14:31:20 +00:00
portSharer = * api . PortSharer . Load ( )
2022-09-09 16:34:23 +00:00
aReq , commitAudit = audit . InitRequest [ database . Template ] ( rw , & audit . RequestParams {
2024-02-26 14:27:33 +00:00
Audit : auditor ,
Log : api . Logger ,
Request : r ,
Action : database . AuditActionWrite ,
OrganizationID : template . OrganizationID ,
2022-09-09 16:34:23 +00:00
} )
)
defer commitAudit ( )
aReq . Old = template
2023-07-20 13:35:41 +00:00
scheduleOpts , err := ( * api . TemplateScheduleStore . Load ( ) ) . Get ( ctx , api . Database , template . ID )
if err != nil {
httpapi . Write ( ctx , rw , http . StatusInternalServerError , codersdk . Response {
Message : "Internal error fetching template schedule options." ,
Detail : err . Error ( ) ,
} )
return
}
2022-06-08 14:14:57 +00:00
var req codersdk . UpdateTemplateMeta
2022-09-21 22:07:00 +00:00
if ! httpapi . Read ( ctx , rw , r , & req ) {
2022-06-08 14:14:57 +00:00
return
}
2023-07-20 13:35:41 +00:00
var (
2023-10-13 16:57:18 +00:00
validErrs [ ] codersdk . ValidationError
autostopRequirementDaysOfWeekParsed uint8
autostartRequirementDaysOfWeekParsed uint8
2023-07-20 13:35:41 +00:00
)
2022-11-09 19:36:25 +00:00
if req . DefaultTTLMillis < 0 {
validErrs = append ( validErrs , codersdk . ValidationError { Field : "default_ttl_ms" , Detail : "Must be a positive integer." } )
2022-08-24 14:45:14 +00:00
}
2024-02-13 07:00:35 +00:00
if req . ActivityBumpMillis < 0 {
validErrs = append ( validErrs , codersdk . ValidationError { Field : "activity_bump_ms" , Detail : "Must be a positive integer." } )
}
2024-03-20 15:37:57 +00:00
2023-08-29 18:35:05 +00:00
if req . AutostopRequirement == nil {
req . AutostopRequirement = & codersdk . TemplateAutostopRequirement {
DaysOfWeek : codersdk . BitmapToWeekdays ( scheduleOpts . AutostopRequirement . DaysOfWeek ) ,
Weeks : scheduleOpts . AutostopRequirement . Weeks ,
2023-07-20 13:35:41 +00:00
}
}
2023-08-29 18:35:05 +00:00
if len ( req . AutostopRequirement . DaysOfWeek ) > 0 {
autostopRequirementDaysOfWeekParsed , err = codersdk . WeekdaysToBitmap ( req . AutostopRequirement . DaysOfWeek )
2023-07-20 13:35:41 +00:00
if err != nil {
2023-08-29 18:35:05 +00:00
validErrs = append ( validErrs , codersdk . ValidationError { Field : "autostop_requirement.days_of_week" , Detail : err . Error ( ) } )
2023-07-20 13:35:41 +00:00
}
}
2023-10-13 16:57:18 +00:00
if req . AutostartRequirement == nil {
req . AutostartRequirement = & codersdk . TemplateAutostartRequirement {
DaysOfWeek : codersdk . BitmapToWeekdays ( scheduleOpts . AutostartRequirement . DaysOfWeek ) ,
}
}
if len ( req . AutostartRequirement . DaysOfWeek ) > 0 {
autostartRequirementDaysOfWeekParsed , err = codersdk . WeekdaysToBitmap ( req . AutostartRequirement . DaysOfWeek )
if err != nil {
validErrs = append ( validErrs , codersdk . ValidationError { Field : "autostart_requirement.days_of_week" , Detail : err . Error ( ) } )
}
}
2023-08-29 18:35:05 +00:00
if req . AutostopRequirement . Weeks < 0 {
validErrs = append ( validErrs , codersdk . ValidationError { Field : "autostop_requirement.weeks" , Detail : "Must be a positive integer." } )
2023-07-20 13:35:41 +00:00
}
2023-08-30 20:41:27 +00:00
if req . AutostopRequirement . Weeks == 0 {
req . AutostopRequirement . Weeks = 1
}
if template . AutostopRequirementWeeks <= 0 {
template . AutostopRequirementWeeks = 1
}
2023-08-29 18:35:05 +00:00
if req . AutostopRequirement . Weeks > schedule . MaxTemplateAutostopRequirementWeeks {
validErrs = append ( validErrs , codersdk . ValidationError { Field : "autostop_requirement.weeks" , Detail : fmt . Sprintf ( "Must be less than %d." , schedule . MaxTemplateAutostopRequirementWeeks ) } )
2023-07-20 13:35:41 +00:00
}
2023-11-20 19:16:18 +00:00
// Defaults to the existing.
deprecationMessage := template . Deprecated
if req . DeprecationMessage != nil {
deprecationMessage = * req . DeprecationMessage
}
2023-10-05 18:41:07 +00:00
// The minimum valid value for a dormant TTL is 1 minute. This is
// to ensure an uninformed user does not send an unintentionally
// small number resulting in potentially catastrophic consequences.
const minTTL = 1000 * 60
if req . FailureTTLMillis < 0 || ( req . FailureTTLMillis > 0 && req . FailureTTLMillis < minTTL ) {
validErrs = append ( validErrs , codersdk . ValidationError { Field : "failure_ttl_ms" , Detail : "Value must be at least one minute." } )
2023-05-05 15:19:26 +00:00
}
2023-10-05 18:41:07 +00:00
if req . TimeTilDormantMillis < 0 || ( req . TimeTilDormantMillis > 0 && req . TimeTilDormantMillis < minTTL ) {
validErrs = append ( validErrs , codersdk . ValidationError { Field : "time_til_dormant_ms" , Detail : "Value must be at least one minute." } )
2023-06-20 02:37:55 +00:00
}
2023-10-05 18:41:07 +00:00
if req . TimeTilDormantAutoDeleteMillis < 0 || ( req . TimeTilDormantAutoDeleteMillis > 0 && req . TimeTilDormantAutoDeleteMillis < minTTL ) {
validErrs = append ( validErrs , codersdk . ValidationError { Field : "time_til_dormant_autodelete_ms" , Detail : "Value must be at least one minute." } )
2023-06-20 02:37:55 +00:00
}
2024-02-13 14:31:20 +00:00
maxPortShareLevel := template . MaxPortSharingLevel
if req . MaxPortShareLevel != nil && * req . MaxPortShareLevel != codersdk . WorkspaceAgentPortShareLevel ( maxPortShareLevel ) {
err := portSharer . ValidateTemplateMaxPortSharingLevel ( * req . MaxPortShareLevel )
if err != nil {
validErrs = append ( validErrs , codersdk . ValidationError { Field : "max_port_sharing_level" , Detail : err . Error ( ) } )
} else {
maxPortShareLevel = database . AppSharingLevel ( * req . MaxPortShareLevel )
}
}
2022-06-08 14:14:57 +00:00
if len ( validErrs ) > 0 {
2022-09-21 22:07:00 +00:00
httpapi . Write ( ctx , rw , http . StatusBadRequest , codersdk . Response {
2022-06-08 14:14:57 +00:00
Message : "Invalid request to update template metadata!" ,
Validations : validErrs ,
} )
return
}
var updated database . Template
2023-07-20 13:35:41 +00:00
err = api . Database . InTx ( func ( tx database . Store ) error {
2022-08-17 19:04:00 +00:00
if req . Name == template . Name &&
req . Description == template . Description &&
2022-11-10 20:51:09 +00:00
req . DisplayName == template . DisplayName &&
2022-08-19 13:17:35 +00:00
req . Icon == template . Icon &&
2023-04-04 12:48:35 +00:00
req . AllowUserAutostart == template . AllowUserAutostart &&
req . AllowUserAutostop == template . AllowUserAutostop &&
2022-11-21 10:43:53 +00:00
req . AllowUserCancelWorkspaceJobs == template . AllowUserCancelWorkspaceJobs &&
2023-03-07 14:14:58 +00:00
req . DefaultTTLMillis == time . Duration ( template . DefaultTTL ) . Milliseconds ( ) &&
2024-02-13 07:00:35 +00:00
req . ActivityBumpMillis == time . Duration ( template . ActivityBump ) . Milliseconds ( ) &&
2023-08-29 18:35:05 +00:00
autostopRequirementDaysOfWeekParsed == scheduleOpts . AutostopRequirement . DaysOfWeek &&
2023-10-16 14:29:42 +00:00
autostartRequirementDaysOfWeekParsed == scheduleOpts . AutostartRequirement . DaysOfWeek &&
2023-08-29 18:35:05 +00:00
req . AutostopRequirement . Weeks == scheduleOpts . AutostopRequirement . Weeks &&
2023-05-05 15:19:26 +00:00
req . FailureTTLMillis == time . Duration ( template . FailureTTL ) . Milliseconds ( ) &&
2023-08-24 18:25:54 +00:00
req . TimeTilDormantMillis == time . Duration ( template . TimeTilDormant ) . Milliseconds ( ) &&
2023-10-18 22:07:21 +00:00
req . TimeTilDormantAutoDeleteMillis == time . Duration ( template . TimeTilDormantAutoDelete ) . Milliseconds ( ) &&
2023-11-20 19:16:18 +00:00
req . RequireActiveVersion == template . RequireActiveVersion &&
2024-02-13 14:31:20 +00:00
( deprecationMessage == template . Deprecated ) &&
maxPortShareLevel == template . MaxPortSharingLevel {
2022-06-08 14:14:57 +00:00
return nil
}
2023-05-05 15:19:26 +00:00
// Users should not be able to clear the template name in the UI
2022-08-17 19:04:00 +00:00
name := req . Name
if name == "" {
name = template . Name
}
2022-06-08 14:14:57 +00:00
2024-01-05 21:04:14 +00:00
groupACL := template . GroupACL
if req . DisableEveryoneGroupAccess {
2024-01-11 22:18:46 +00:00
delete ( groupACL , template . OrganizationID . String ( ) )
2024-01-05 21:04:14 +00:00
}
2024-03-05 18:47:01 +00:00
if template . MaxPortSharingLevel != maxPortShareLevel {
switch maxPortShareLevel {
case database . AppSharingLevelOwner :
err = tx . DeleteWorkspaceAgentPortSharesByTemplate ( ctx , template . ID )
if err != nil {
return xerrors . Errorf ( "delete workspace agent port shares by template: %w" , err )
}
case database . AppSharingLevelAuthenticated :
err = tx . ReduceWorkspaceAgentShareLevelToAuthenticatedByTemplate ( ctx , template . ID )
if err != nil {
return xerrors . Errorf ( "reduce workspace agent share level to authenticated by template: %w" , err )
}
}
}
2023-02-02 17:59:43 +00:00
var err error
2023-07-19 20:07:33 +00:00
err = tx . UpdateTemplateMetaByID ( ctx , database . UpdateTemplateMetaByIDParams {
2022-11-21 10:43:53 +00:00
ID : template . ID ,
2023-09-01 16:50:12 +00:00
UpdatedAt : dbtime . Now ( ) ,
2022-11-21 10:43:53 +00:00
Name : name ,
2023-05-05 15:19:26 +00:00
DisplayName : req . DisplayName ,
Description : req . Description ,
Icon : req . Icon ,
2023-04-04 12:48:35 +00:00
AllowUserCancelWorkspaceJobs : req . AllowUserCancelWorkspaceJobs ,
2024-01-05 21:04:14 +00:00
GroupACL : groupACL ,
2024-02-13 14:31:20 +00:00
MaxPortSharingLevel : maxPortShareLevel ,
2022-09-09 16:34:23 +00:00
} )
2022-06-08 14:14:57 +00:00
if err != nil {
2023-03-07 14:14:58 +00:00
return xerrors . Errorf ( "update template metadata: %w" , err )
}
2023-11-20 19:16:18 +00:00
if template . RequireActiveVersion != req . RequireActiveVersion || deprecationMessage != template . Deprecated {
2023-10-18 22:07:21 +00:00
err = ( * api . AccessControlStore . Load ( ) ) . SetTemplateAccessControl ( ctx , tx , template . ID , dbauthz . TemplateAccessControl {
RequireActiveVersion : req . RequireActiveVersion ,
2023-11-20 19:16:18 +00:00
Deprecated : deprecationMessage ,
2023-10-18 22:07:21 +00:00
} )
if err != nil {
return xerrors . Errorf ( "set template access control: %w" , err )
}
}
2023-07-19 20:07:33 +00:00
updated , err = tx . GetTemplateByID ( ctx , template . ID )
if err != nil {
return xerrors . Errorf ( "fetch updated template metadata: %w" , err )
}
2023-03-07 14:14:58 +00:00
defaultTTL := time . Duration ( req . DefaultTTLMillis ) * time . Millisecond
2024-02-13 07:00:35 +00:00
activityBump := time . Duration ( req . ActivityBumpMillis ) * time . Millisecond
2023-05-05 15:19:26 +00:00
failureTTL := time . Duration ( req . FailureTTLMillis ) * time . Millisecond
2023-08-24 18:25:54 +00:00
inactivityTTL := time . Duration ( req . TimeTilDormantMillis ) * time . Millisecond
timeTilDormantAutoDelete := time . Duration ( req . TimeTilDormantAutoDeleteMillis ) * time . Millisecond
2023-05-05 15:19:26 +00:00
2023-04-04 12:48:35 +00:00
if defaultTTL != time . Duration ( template . DefaultTTL ) ||
2024-02-13 07:00:35 +00:00
activityBump != time . Duration ( template . ActivityBump ) ||
2023-08-29 18:35:05 +00:00
autostopRequirementDaysOfWeekParsed != scheduleOpts . AutostopRequirement . DaysOfWeek ||
2023-10-13 16:57:18 +00:00
autostartRequirementDaysOfWeekParsed != scheduleOpts . AutostartRequirement . DaysOfWeek ||
2023-08-29 18:35:05 +00:00
req . AutostopRequirement . Weeks != scheduleOpts . AutostopRequirement . Weeks ||
2023-05-05 15:19:26 +00:00
failureTTL != time . Duration ( template . FailureTTL ) ||
2023-08-24 18:25:54 +00:00
inactivityTTL != time . Duration ( template . TimeTilDormant ) ||
timeTilDormantAutoDelete != time . Duration ( template . TimeTilDormantAutoDelete ) ||
2023-04-04 12:48:35 +00:00
req . AllowUserAutostart != template . AllowUserAutostart ||
req . AllowUserAutostop != template . AllowUserAutostop {
2023-07-20 13:35:41 +00:00
updated , err = ( * api . TemplateScheduleStore . Load ( ) ) . Set ( ctx , tx , updated , schedule . TemplateScheduleOptions {
2023-04-04 12:48:35 +00:00
// Some of these values are enterprise-only, but the
// TemplateScheduleStore will handle avoiding setting them if
// unlicensed.
UserAutostartEnabled : req . AllowUserAutostart ,
UserAutostopEnabled : req . AllowUserAutostop ,
DefaultTTL : defaultTTL ,
2024-02-13 07:00:35 +00:00
ActivityBump : activityBump ,
2023-08-29 18:35:05 +00:00
AutostopRequirement : schedule . TemplateAutostopRequirement {
DaysOfWeek : autostopRequirementDaysOfWeekParsed ,
Weeks : req . AutostopRequirement . Weeks ,
2023-07-20 13:35:41 +00:00
} ,
2023-10-13 16:57:18 +00:00
AutostartRequirement : schedule . TemplateAutostartRequirement {
DaysOfWeek : autostartRequirementDaysOfWeekParsed ,
} ,
2023-08-22 20:15:13 +00:00
FailureTTL : failureTTL ,
2023-08-24 18:25:54 +00:00
TimeTilDormant : inactivityTTL ,
TimeTilDormantAutoDelete : timeTilDormantAutoDelete ,
2023-08-22 20:15:13 +00:00
UpdateWorkspaceLastUsedAt : req . UpdateWorkspaceLastUsedAt ,
2023-08-24 18:25:54 +00:00
UpdateWorkspaceDormantAt : req . UpdateWorkspaceDormantAt ,
2023-03-07 14:14:58 +00:00
} )
if err != nil {
return xerrors . Errorf ( "set template schedule options: %w" , err )
}
2022-06-08 14:14:57 +00:00
}
2022-09-09 16:34:23 +00:00
2022-06-08 14:14:57 +00:00
return nil
2022-11-14 17:57:33 +00:00
} , nil )
2022-06-08 14:14:57 +00:00
if err != nil {
2022-09-09 16:34:23 +00:00
httpapi . InternalServerError ( rw , err )
2022-06-08 14:14:57 +00:00
return
}
if updated . UpdatedAt . IsZero ( ) {
2022-09-09 16:34:23 +00:00
aReq . New = template
2022-09-21 22:07:00 +00:00
httpapi . Write ( ctx , rw , http . StatusNotModified , nil )
2022-06-08 14:14:57 +00:00
return
}
2022-09-09 16:34:23 +00:00
aReq . New = updated
2022-06-08 14:14:57 +00:00
2023-07-19 20:07:33 +00:00
httpapi . Write ( ctx , rw , http . StatusOK , api . convertTemplate ( updated ) )
2022-06-10 19:24:21 +00:00
}
2023-01-11 11:16:09 +00:00
// @Summary Get template DAUs by ID
// @ID get-template-daus-by-id
// @Security CoderSessionToken
// @Produce json
// @Tags Templates
2023-01-13 11:27:21 +00:00
// @Param template path string true "Template ID" format(uuid)
2023-05-30 17:18:27 +00:00
// @Success 200 {object} codersdk.DAUsResponse
2023-01-13 11:27:21 +00:00
// @Router /templates/{template}/daus [get]
2022-09-01 19:58:23 +00:00
func ( api * API ) templateDAUs ( rw http . ResponseWriter , r * http . Request ) {
template := httpmw . TemplateParam ( r )
2024-03-27 16:10:14 +00:00
api . returnDAUsInternal ( rw , r , [ ] uuid . UUID { template . ID } )
2022-09-01 19:58:23 +00:00
}
2023-01-11 11:16:09 +00:00
// @Summary Get template examples by organization
// @ID get-template-examples-by-organization
// @Security CoderSessionToken
// @Produce json
// @Tags Templates
// @Param organization path string true "Organization ID" format(uuid)
// @Success 200 {array} codersdk.TemplateExample
// @Router /organizations/{organization}/templates/examples [get]
2022-12-09 19:29:50 +00:00
func ( api * API ) templateExamples ( rw http . ResponseWriter , r * http . Request ) {
var (
ctx = r . Context ( )
organization = httpmw . OrganizationParam ( r )
)
if ! api . Authorize ( r , rbac . ActionRead , rbac . ResourceTemplate . InOrg ( organization . ID ) ) {
httpapi . ResourceNotFound ( rw )
return
}
ex , err := examples . List ( )
if err != nil {
httpapi . Write ( ctx , rw , http . StatusInternalServerError , codersdk . Response {
Message : "Internal error fetching examples." ,
Detail : err . Error ( ) ,
} )
return
}
httpapi . Write ( ctx , rw , http . StatusOK , ex )
}
2023-07-19 20:07:33 +00:00
func ( api * API ) convertTemplates ( templates [ ] database . Template ) [ ] codersdk . Template {
2022-04-06 17:42:40 +00:00
apiTemplates := make ( [ ] codersdk . Template , 0 , len ( templates ) )
2022-08-30 17:27:33 +00:00
2022-04-06 17:42:40 +00:00
for _ , template := range templates {
2023-07-19 20:07:33 +00:00
apiTemplates = append ( apiTemplates , api . convertTemplate ( template ) )
2022-04-06 17:42:40 +00:00
}
2022-08-30 17:27:33 +00:00
2022-09-09 19:30:31 +00:00
// Sort templates by ActiveUserCount DESC
2022-08-30 17:27:33 +00:00
sort . SliceStable ( apiTemplates , func ( i , j int ) bool {
2022-09-09 19:30:31 +00:00
return apiTemplates [ i ] . ActiveUserCount > apiTemplates [ j ] . ActiveUserCount
2022-08-30 17:27:33 +00:00
} )
2022-04-06 17:42:40 +00:00
return apiTemplates
}
2022-09-09 19:30:31 +00:00
func ( api * API ) convertTemplate (
2023-07-19 20:07:33 +00:00
template database . Template ,
2022-09-09 19:30:31 +00:00
) codersdk . Template {
2023-11-20 19:16:18 +00:00
templateAccessControl := ( * ( api . Options . AccessControlStore . Load ( ) ) ) . GetTemplateAccessControl ( template )
2023-12-07 15:53:15 +00:00
owners := 0
o , ok := api . metricsCache . TemplateWorkspaceOwners ( template . ID )
if ok {
owners = o
}
2022-10-15 20:36:50 +00:00
2022-10-17 04:34:03 +00:00
buildTimeStats := api . metricsCache . TemplateBuildTimeStats ( template . ID )
2022-10-15 20:36:50 +00:00
2023-08-30 20:41:27 +00:00
autostopRequirementWeeks := template . AutostopRequirementWeeks
if autostopRequirementWeeks < 1 {
autostopRequirementWeeks = 1
}
2022-04-06 17:42:40 +00:00
return codersdk . Template {
2023-08-24 18:25:54 +00:00
ID : template . ID ,
CreatedAt : template . CreatedAt ,
UpdatedAt : template . UpdatedAt ,
OrganizationID : template . OrganizationID ,
Name : template . Name ,
DisplayName : template . DisplayName ,
Provisioner : codersdk . ProvisionerType ( template . Provisioner ) ,
ActiveVersionID : template . ActiveVersionID ,
2023-12-07 15:53:15 +00:00
ActiveUserCount : owners ,
2023-08-24 18:25:54 +00:00
BuildTimeStats : buildTimeStats ,
Description : template . Description ,
Icon : template . Icon ,
DefaultTTLMillis : time . Duration ( template . DefaultTTL ) . Milliseconds ( ) ,
2024-02-13 07:00:35 +00:00
ActivityBumpMillis : time . Duration ( template . ActivityBump ) . Milliseconds ( ) ,
2023-08-24 18:25:54 +00:00
CreatedByID : template . CreatedBy ,
CreatedByName : template . CreatedByUsername ,
AllowUserAutostart : template . AllowUserAutostart ,
AllowUserAutostop : template . AllowUserAutostop ,
AllowUserCancelWorkspaceJobs : template . AllowUserCancelWorkspaceJobs ,
FailureTTLMillis : time . Duration ( template . FailureTTL ) . Milliseconds ( ) ,
TimeTilDormantMillis : time . Duration ( template . TimeTilDormant ) . Milliseconds ( ) ,
TimeTilDormantAutoDeleteMillis : time . Duration ( template . TimeTilDormantAutoDelete ) . Milliseconds ( ) ,
2023-08-29 18:35:05 +00:00
AutostopRequirement : codersdk . TemplateAutostopRequirement {
DaysOfWeek : codersdk . BitmapToWeekdays ( uint8 ( template . AutostopRequirementDaysOfWeek ) ) ,
2023-08-30 20:41:27 +00:00
Weeks : autostopRequirementWeeks ,
2023-07-20 13:35:41 +00:00
} ,
2023-10-13 16:57:18 +00:00
AutostartRequirement : codersdk . TemplateAutostartRequirement {
DaysOfWeek : codersdk . BitmapToWeekdays ( template . AutostartAllowedDays ( ) ) ,
} ,
2023-11-20 19:16:18 +00:00
// These values depend on entitlements and come from the templateAccessControl
RequireActiveVersion : templateAccessControl . RequireActiveVersion ,
Deprecated : templateAccessControl . IsDeprecated ( ) ,
DeprecationMessage : templateAccessControl . Deprecated ,
2024-02-13 14:31:20 +00:00
MaxPortShareLevel : codersdk . WorkspaceAgentPortShareLevel ( template . MaxPortSharingLevel ) ,
2022-04-06 17:42:40 +00:00
}
}