2022-06-15 17:42:43 +00:00
package cli
import (
2023-02-22 19:29:51 +00:00
"bytes"
2022-06-15 17:42:43 +00:00
"fmt"
"os"
2024-03-14 12:41:23 +00:00
"path/filepath"
2022-06-15 17:42:43 +00:00
"sort"
"golang.org/x/xerrors"
2023-08-18 18:55:43 +00:00
"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/coder/v2/codersdk"
2024-04-02 15:19:54 +00:00
"github.com/coder/coder/v2/provisionersdk"
2024-03-15 16:24:38 +00:00
"github.com/coder/serpent"
2022-06-15 17:42:43 +00:00
)
2024-03-17 14:45:26 +00:00
func ( r * RootCmd ) templatePull ( ) * serpent . Command {
2023-10-10 15:20:31 +00:00
var (
tarMode bool
2024-02-06 18:17:29 +00:00
zipMode bool
2023-10-10 15:20:31 +00:00
versionName string
)
2023-03-23 22:42:20 +00:00
client := new ( codersdk . Client )
2024-03-17 14:45:26 +00:00
cmd := & serpent . Command {
2022-06-15 17:42:43 +00:00
Use : "pull <name> [destination]" ,
2023-10-10 15:20:31 +00:00
Short : "Download the active, latest, or specified version of a template to a path." ,
2024-03-15 16:24:38 +00:00
Middleware : serpent . Chain (
serpent . RequireRangeArgs ( 1 , 2 ) ,
2023-03-23 22:42:20 +00:00
r . InitClient ( client ) ,
) ,
2024-03-15 16:24:38 +00:00
Handler : func ( inv * serpent . Invocation ) error {
2022-06-15 17:42:43 +00:00
var (
2023-03-23 22:42:20 +00:00
ctx = inv . Context ( )
templateName = inv . Args [ 0 ]
2022-06-15 17:42:43 +00:00
dest string
)
2023-03-23 22:42:20 +00:00
if len ( inv . Args ) > 1 {
dest = inv . Args [ 1 ]
2022-06-15 17:42:43 +00:00
}
2024-02-06 18:17:29 +00:00
if tarMode && zipMode {
return xerrors . Errorf ( "either tar or zip can be selected" )
}
2024-02-26 16:03:49 +00:00
organization , err := CurrentOrganization ( r , inv , client )
2022-06-15 17:42:43 +00:00
if err != nil {
2023-10-10 15:20:31 +00:00
return xerrors . Errorf ( "get current organization: %w" , err )
2022-06-15 17:42:43 +00:00
}
template , err := client . TemplateByName ( ctx , organization . ID , templateName )
if err != nil {
2023-10-10 15:20:31 +00:00
return xerrors . Errorf ( "get template by name: %w" , err )
2022-06-15 17:42:43 +00:00
}
2023-10-10 15:20:31 +00:00
var latestVersion codersdk . TemplateVersion
{
// Determine the latest template version and compare with the
// active version. If they aren't the same, warn the user.
versions , err := client . TemplateVersionsByTemplate ( ctx , codersdk . TemplateVersionsByTemplateRequest {
TemplateID : template . ID ,
} )
if err != nil {
return xerrors . Errorf ( "template versions by template: %w" , err )
}
if len ( versions ) == 0 {
return xerrors . Errorf ( "no template versions for template %q" , templateName )
}
// Sort the slice from newest to oldest template.
sort . SliceStable ( versions , func ( i , j int ) bool {
return versions [ i ] . CreatedAt . After ( versions [ j ] . CreatedAt )
} )
2022-06-15 17:42:43 +00:00
2023-10-10 15:20:31 +00:00
latestVersion = versions [ 0 ]
2022-06-15 17:42:43 +00:00
}
2023-10-10 15:20:31 +00:00
var templateVersion codersdk . TemplateVersion
switch versionName {
case "" , "active" :
activeVersion , err := client . TemplateVersion ( ctx , template . ActiveVersionID )
if err != nil {
return xerrors . Errorf ( "get active template version: %w" , err )
}
if versionName == "" && activeVersion . ID != latestVersion . ID {
cliui . Warn ( inv . Stderr ,
"A newer template version than the active version exists. Pulling the active version instead." ,
2024-01-10 14:36:26 +00:00
"Use " + cliui . Code ( "--version latest" ) + " to pull the latest version." ,
2023-10-10 15:20:31 +00:00
)
}
templateVersion = activeVersion
case "latest" :
templateVersion = latestVersion
default :
version , err := client . TemplateVersionByName ( ctx , template . ID , versionName )
if err != nil {
return xerrors . Errorf ( "get template version: %w" , err )
}
templateVersion = version
}
2022-06-15 17:42:43 +00:00
2023-10-10 15:20:31 +00:00
cliui . Info ( inv . Stderr , "Pulling template version " + cliui . Bold ( templateVersion . Name ) + "..." )
2022-06-15 17:42:43 +00:00
2024-02-06 18:17:29 +00:00
var fileFormat string // empty = default, so .tar
if zipMode {
fileFormat = codersdk . FormatZip
}
2022-06-15 17:42:43 +00:00
// Download the tar archive.
2024-02-06 18:17:29 +00:00
raw , ctype , err := client . DownloadWithFormat ( ctx , templateVersion . Job . FileID , fileFormat )
2022-06-15 17:42:43 +00:00
if err != nil {
return xerrors . Errorf ( "download template: %w" , err )
}
2024-02-06 18:17:29 +00:00
if fileFormat == "" && ctype != codersdk . ContentTypeTar {
2022-06-15 17:42:43 +00:00
return xerrors . Errorf ( "unexpected Content-Type %q, expecting %q" , ctype , codersdk . ContentTypeTar )
}
2024-02-06 18:17:29 +00:00
if fileFormat == codersdk . FormatZip && ctype != codersdk . ContentTypeZip {
return xerrors . Errorf ( "unexpected Content-Type %q, expecting %q" , ctype , codersdk . ContentTypeZip )
}
2022-06-15 17:42:43 +00:00
2024-02-06 18:17:29 +00:00
if tarMode || zipMode {
2023-03-23 22:42:20 +00:00
_ , err = inv . Stdout . Write ( raw )
2023-02-22 19:29:51 +00:00
return err
2022-06-15 17:42:43 +00:00
}
2023-02-22 19:29:51 +00:00
if dest == "" {
2023-08-25 10:58:13 +00:00
dest = templateName
2022-06-15 17:42:43 +00:00
}
2024-03-14 12:41:23 +00:00
clean , err := filepath . Abs ( filepath . Clean ( dest ) )
if err != nil {
return xerrors . Errorf ( "cleaning destination path %s failed: %w" , dest , err )
}
dest = clean
2023-02-22 19:29:51 +00:00
err = os . MkdirAll ( dest , 0 o750 )
if err != nil {
return xerrors . Errorf ( "mkdirall %q: %w" , dest , err )
2022-06-15 17:42:43 +00:00
}
2023-02-22 19:29:51 +00:00
ents , err := os . ReadDir ( dest )
if err != nil {
return xerrors . Errorf ( "read dir %q: %w" , dest , err )
}
if len ( ents ) > 0 {
2023-03-23 22:42:20 +00:00
_ , err = cliui . Prompt ( inv , cliui . PromptOptions {
2023-02-22 19:29:51 +00:00
Text : fmt . Sprintf ( "Directory %q is not empty, existing files may be overwritten.\nContinue extracting?" , dest ) ,
Default : "No" ,
Secret : false ,
2022-06-15 17:42:43 +00:00
IsConfirm : true ,
} )
if err != nil {
2023-02-22 19:29:51 +00:00
return err
2022-06-15 17:42:43 +00:00
}
}
2023-03-23 22:42:20 +00:00
_ , _ = fmt . Fprintf ( inv . Stderr , "Extracting template to %q\n" , dest )
2024-04-02 15:19:54 +00:00
err = provisionersdk . Untar ( dest , bytes . NewReader ( raw ) )
2023-02-22 19:29:51 +00:00
return err
2022-06-15 17:42:43 +00:00
} ,
}
2024-03-15 16:24:38 +00:00
cmd . Options = serpent . OptionSet {
2023-03-23 22:42:20 +00:00
{
Description : "Output the template as a tar archive to stdout." ,
Flag : "tar" ,
2024-03-15 16:24:38 +00:00
Value : serpent . BoolOf ( & tarMode ) ,
2023-03-23 22:42:20 +00:00
} ,
2024-02-06 18:17:29 +00:00
{
Description : "Output the template as a zip archive to stdout." ,
Flag : "zip" ,
2024-03-15 16:24:38 +00:00
Value : serpent . BoolOf ( & zipMode ) ,
2024-02-06 18:17:29 +00:00
} ,
2023-10-10 15:20:31 +00:00
{
Description : "The name of the template version to pull. Use 'active' to pull the active version, 'latest' to pull the latest version, or the name of the template version to pull." ,
Flag : "version" ,
2024-03-15 16:24:38 +00:00
Value : serpent . StringOf ( & versionName ) ,
2023-10-10 15:20:31 +00:00
} ,
2023-03-23 22:42:20 +00:00
cliui . SkipPromptOption ( ) ,
}
2022-06-15 17:42:43 +00:00
return cmd
}