Skip to content

Commit 68c5c71

Browse files
cmd: New add-package and remove-package commands (#4226)
* adding package command * add-package command name * refactoring duplicate code * fixed by review * fixed by review * remove-package command * commands in different files, common utils * fix add, remove, upgrade packages in 1 file * copyright and downloadPath moved * refactor * downloadPath do no export * adding/removing multiple packages * addPackages/removePackages, comments, command-desc * add-package, process case len(args) == 0 Co-authored-by: Francis Lavoie <lavofr@gmail.com>
1 parent 569ecdb commit 68c5c71

File tree

3 files changed

+330
-197
lines changed

3 files changed

+330
-197
lines changed

cmd/commandfuncs.go

Lines changed: 0 additions & 197 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,8 @@ import (
2525
"log"
2626
"net"
2727
"net/http"
28-
"net/url"
2928
"os"
3029
"os/exec"
31-
"reflect"
3230
"runtime"
3331
"runtime/debug"
3432
"sort"
@@ -570,151 +568,6 @@ func cmdFmt(fl Flags) (int, error) {
570568
return caddy.ExitCodeSuccess, nil
571569
}
572570

573-
func cmdUpgrade(_ Flags) (int, error) {
574-
l := caddy.Log()
575-
576-
thisExecPath, err := os.Executable()
577-
if err != nil {
578-
return caddy.ExitCodeFailedStartup, fmt.Errorf("determining current executable path: %v", err)
579-
}
580-
thisExecStat, err := os.Stat(thisExecPath)
581-
if err != nil {
582-
return caddy.ExitCodeFailedStartup, fmt.Errorf("retrieving current executable permission bits: %v", err)
583-
}
584-
l.Info("this executable will be replaced", zap.String("path", thisExecPath))
585-
586-
// get the list of nonstandard plugins
587-
_, nonstandard, _, err := getModules()
588-
if err != nil {
589-
return caddy.ExitCodeFailedStartup, fmt.Errorf("unable to enumerate installed plugins: %v", err)
590-
}
591-
pluginPkgs := make(map[string]struct{})
592-
for _, mod := range nonstandard {
593-
if mod.goModule.Replace != nil {
594-
return caddy.ExitCodeFailedStartup, fmt.Errorf("cannot auto-upgrade when Go module has been replaced: %s => %s",
595-
mod.goModule.Path, mod.goModule.Replace.Path)
596-
}
597-
l.Info("found non-standard module",
598-
zap.String("id", mod.caddyModuleID),
599-
zap.String("package", mod.goModule.Path))
600-
pluginPkgs[mod.goModule.Path] = struct{}{}
601-
}
602-
603-
// build the request URL to download this custom build
604-
qs := url.Values{
605-
"os": {runtime.GOOS},
606-
"arch": {runtime.GOARCH},
607-
}
608-
for pkg := range pluginPkgs {
609-
qs.Add("p", pkg)
610-
}
611-
urlStr := fmt.Sprintf("https://caddyserver.com/api/download?%s", qs.Encode())
612-
613-
// initiate the build
614-
l.Info("requesting build",
615-
zap.String("os", qs.Get("os")),
616-
zap.String("arch", qs.Get("arch")),
617-
zap.Strings("packages", qs["p"]))
618-
resp, err := http.Get(urlStr)
619-
if err != nil {
620-
return caddy.ExitCodeFailedStartup, fmt.Errorf("secure request failed: %v", err)
621-
}
622-
defer resp.Body.Close()
623-
if resp.StatusCode >= 400 {
624-
var details struct {
625-
StatusCode int `json:"status_code"`
626-
Error struct {
627-
Message string `json:"message"`
628-
ID string `json:"id"`
629-
} `json:"error"`
630-
}
631-
err2 := json.NewDecoder(resp.Body).Decode(&details)
632-
if err2 != nil {
633-
return caddy.ExitCodeFailedStartup, fmt.Errorf("download and error decoding failed: HTTP %d: %v", resp.StatusCode, err2)
634-
}
635-
return caddy.ExitCodeFailedStartup, fmt.Errorf("download failed: HTTP %d: %s (id=%s)", resp.StatusCode, details.Error.Message, details.Error.ID)
636-
}
637-
638-
// back up the current binary, in case something goes wrong we can replace it
639-
backupExecPath := thisExecPath + ".tmp"
640-
l.Info("build acquired; backing up current executable",
641-
zap.String("current_path", thisExecPath),
642-
zap.String("backup_path", backupExecPath))
643-
err = os.Rename(thisExecPath, backupExecPath)
644-
if err != nil {
645-
return caddy.ExitCodeFailedStartup, fmt.Errorf("backing up current binary: %v", err)
646-
}
647-
defer func() {
648-
if err != nil {
649-
err2 := os.Rename(backupExecPath, thisExecPath)
650-
if err2 != nil {
651-
l.Error("restoring original executable failed; will need to be restored manually",
652-
zap.String("backup_path", backupExecPath),
653-
zap.String("original_path", thisExecPath),
654-
zap.Error(err2))
655-
}
656-
}
657-
}()
658-
659-
// download the file; do this in a closure to close reliably before we execute it
660-
writeFile := func() error {
661-
destFile, err := os.OpenFile(thisExecPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, thisExecStat.Mode())
662-
if err != nil {
663-
return fmt.Errorf("unable to open destination file: %v", err)
664-
}
665-
defer destFile.Close()
666-
667-
l.Info("downloading binary", zap.String("source", urlStr), zap.String("destination", thisExecPath))
668-
669-
_, err = io.Copy(destFile, resp.Body)
670-
if err != nil {
671-
return fmt.Errorf("unable to download file: %v", err)
672-
}
673-
674-
err = destFile.Sync()
675-
if err != nil {
676-
return fmt.Errorf("syncing downloaded file to device: %v", err)
677-
}
678-
679-
return nil
680-
}
681-
err = writeFile()
682-
if err != nil {
683-
return caddy.ExitCodeFailedStartup, err
684-
}
685-
686-
l.Info("download successful; displaying new binary details", zap.String("location", thisExecPath))
687-
688-
// use the new binary to print out version and module info
689-
fmt.Print("\nModule versions:\n\n")
690-
cmd := exec.Command(thisExecPath, "list-modules", "--versions")
691-
cmd.Stdout = os.Stdout
692-
cmd.Stderr = os.Stderr
693-
err = cmd.Run()
694-
if err != nil {
695-
return caddy.ExitCodeFailedStartup, fmt.Errorf("download succeeded, but unable to execute: %v", err)
696-
}
697-
fmt.Println("\nVersion:")
698-
cmd = exec.Command(thisExecPath, "version")
699-
cmd.Stdout = os.Stdout
700-
cmd.Stderr = os.Stderr
701-
err = cmd.Run()
702-
if err != nil {
703-
return caddy.ExitCodeFailedStartup, fmt.Errorf("download succeeded, but unable to execute: %v", err)
704-
}
705-
fmt.Println()
706-
707-
// clean up the backup file
708-
err = os.Remove(backupExecPath)
709-
if err != nil {
710-
return caddy.ExitCodeFailedStartup, fmt.Errorf("download succeeded, but unable to clean up backup binary: %v", err)
711-
}
712-
713-
l.Info("upgrade successful; please restart any running Caddy instances", zap.String("executable", thisExecPath))
714-
715-
return caddy.ExitCodeSuccess, nil
716-
}
717-
718571
func cmdHelp(fl Flags) (int, error) {
719572
const fullDocs = `Full documentation is available at:
720573
https://caddyserver.com/docs/command-line`
@@ -779,56 +632,6 @@ commands:
779632
return caddy.ExitCodeSuccess, nil
780633
}
781634

782-
func getModules() (standard, nonstandard, unknown []moduleInfo, err error) {
783-
bi, ok := debug.ReadBuildInfo()
784-
if !ok {
785-
err = fmt.Errorf("no build info")
786-
return
787-
}
788-
789-
for _, modID := range caddy.Modules() {
790-
modInfo, err := caddy.GetModule(modID)
791-
if err != nil {
792-
// that's weird, shouldn't happen
793-
unknown = append(unknown, moduleInfo{caddyModuleID: modID, err: err})
794-
continue
795-
}
796-
797-
// to get the Caddy plugin's version info, we need to know
798-
// the package that the Caddy module's value comes from; we
799-
// can use reflection but we need a non-pointer value (I'm
800-
// not sure why), and since New() should return a pointer
801-
// value, we need to dereference it first
802-
iface := interface{}(modInfo.New())
803-
if rv := reflect.ValueOf(iface); rv.Kind() == reflect.Ptr {
804-
iface = reflect.New(reflect.TypeOf(iface).Elem()).Elem().Interface()
805-
}
806-
modPkgPath := reflect.TypeOf(iface).PkgPath()
807-
808-
// now we find the Go module that the Caddy module's package
809-
// belongs to; we assume the Caddy module package path will
810-
// be prefixed by its Go module path, and we will choose the
811-
// longest matching prefix in case there are nested modules
812-
var matched *debug.Module
813-
for _, dep := range bi.Deps {
814-
if strings.HasPrefix(modPkgPath, dep.Path) {
815-
if matched == nil || len(dep.Path) > len(matched.Path) {
816-
matched = dep
817-
}
818-
}
819-
}
820-
821-
caddyModGoMod := moduleInfo{caddyModuleID: modID, goModule: matched}
822-
823-
if strings.HasPrefix(modPkgPath, caddy.ImportPath) {
824-
standard = append(standard, caddyModGoMod)
825-
} else {
826-
nonstandard = append(nonstandard, caddyModGoMod)
827-
}
828-
}
829-
return
830-
}
831-
832635
// apiRequest makes an API request to the endpoint adminAddr with the
833636
// given HTTP method and request URI. If body is non-nil, it will be
834637
// assumed to be Content-Type application/json.

cmd/commands.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,30 @@ Downloads an updated Caddy binary with the same modules/plugins at the
291291
latest versions. EXPERIMENTAL: May be changed or removed.`,
292292
})
293293

294+
RegisterCommand(Command{
295+
Name: "add-package",
296+
Func: cmdAddPackage,
297+
Usage: "<packages...>",
298+
Short: "Adds Caddy packages (EXPERIMENTAL)",
299+
Long: `
300+
Downloads an updated Caddy binary with the specified packages (module/plugin)
301+
added. Retains existing packages. Returns an error if the any of packages are
302+
already included. EXPERIMENTAL: May be changed or removed.
303+
`,
304+
})
305+
306+
RegisterCommand(Command{
307+
Name: "remove-package",
308+
Func: cmdRemovePackage,
309+
Usage: "<packages...>",
310+
Short: "Removes Caddy packages (EXPERIMENTAL)",
311+
Long: `
312+
Downloads an updated Caddy binaries without the specified packages (module/plugin).
313+
Returns an error if any of the packages are not included.
314+
EXPERIMENTAL: May be changed or removed.
315+
`,
316+
})
317+
294318
}
295319

296320
// RegisterCommand registers the command cmd.

0 commit comments

Comments
 (0)