diff --git a/cmd/commands.go b/cmd/commands.go index ab4b66d7..ebb57690 100644 --- a/cmd/commands.go +++ b/cmd/commands.go @@ -409,12 +409,13 @@ latest versions. EXPERIMENTAL: May be changed or removed. RegisterCommand(Command{ Name: "add-package", - Usage: "", + Usage: "", Short: "Adds Caddy packages (EXPERIMENTAL)", Long: ` Downloads an updated Caddy binary with the specified packages (module/plugin) -added. Retains existing packages. Returns an error if the any of packages are -already included. EXPERIMENTAL: May be changed or removed. +added, with an optional version specified (e.g., "package@version"). Retains +existing packages. Returns an error if any of the specified packages are already +included. EXPERIMENTAL: May be changed or removed. `, CobraFunc: func(cmd *cobra.Command) { cmd.Flags().BoolP("keep-backup", "k", false, "Keep the backed up binary, instead of deleting it") diff --git a/cmd/packagesfuncs.go b/cmd/packagesfuncs.go index 0784f7ee..69523200 100644 --- a/cmd/packagesfuncs.go +++ b/cmd/packagesfuncs.go @@ -46,6 +46,25 @@ func cmdUpgrade(fl Flags) (int, error) { return upgradeBuild(pluginPkgs, fl) } +func splitModule(arg string) (module, version string, err error) { + const versionSplit = "@" + + // accommodate module paths that have @ in them, but we can only tolerate that if there's also + // a version, otherwise we don't know if it's a version separator or part of the file path + lastVersionSplit := strings.LastIndex(arg, versionSplit) + if lastVersionSplit < 0 { + module = arg + } else { + module, version = arg[:lastVersionSplit], arg[lastVersionSplit+1:] + } + + if module == "" { + err = fmt.Errorf("module name is required") + } + + return +} + func cmdAddPackage(fl Flags) (int, error) { if len(fl.Args()) == 0 { return caddy.ExitCodeFailedStartup, fmt.Errorf("at least one package name must be specified") @@ -60,10 +79,15 @@ func cmdAddPackage(fl Flags) (int, error) { } for _, arg := range fl.Args() { - if _, ok := pluginPkgs[arg]; ok { + module, version, err := splitModule(arg) + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid module name: %v", err) + } + // only allow a version to be specified if it's different from the existing version + if _, ok := pluginPkgs[module]; ok && !(version != "" && pluginPkgs[module].Version != version) { return caddy.ExitCodeFailedStartup, fmt.Errorf("package is already added") } - pluginPkgs[arg] = struct{}{} + pluginPkgs[module] = pluginPackage{Version: version, Path: module} } return upgradeBuild(pluginPkgs, fl) @@ -83,7 +107,11 @@ func cmdRemovePackage(fl Flags) (int, error) { } for _, arg := range fl.Args() { - if _, ok := pluginPkgs[arg]; !ok { + module, _, err := splitModule(arg) + if err != nil { + return caddy.ExitCodeFailedStartup, fmt.Errorf("invalid module name: %v", err) + } + if _, ok := pluginPkgs[module]; !ok { // package does not exist return caddy.ExitCodeFailedStartup, fmt.Errorf("package is not added") } @@ -93,7 +121,7 @@ func cmdRemovePackage(fl Flags) (int, error) { return upgradeBuild(pluginPkgs, fl) } -func upgradeBuild(pluginPkgs map[string]struct{}, fl Flags) (int, error) { +func upgradeBuild(pluginPkgs map[string]pluginPackage, fl Flags) (int, error) { l := caddy.Log() thisExecPath, err := os.Executable() @@ -120,8 +148,8 @@ func upgradeBuild(pluginPkgs map[string]struct{}, fl Flags) (int, error) { "os": {runtime.GOOS}, "arch": {runtime.GOARCH}, } - for pkg := range pluginPkgs { - qs.Add("p", pkg) + for _, pkgInfo := range pluginPkgs { + qs.Add("p", pkgInfo.String()) } // initiate the build @@ -276,14 +304,14 @@ func downloadBuild(qs url.Values) (*http.Response, error) { return resp, nil } -func getPluginPackages(modules []moduleInfo) (map[string]struct{}, error) { - pluginPkgs := make(map[string]struct{}) +func getPluginPackages(modules []moduleInfo) (map[string]pluginPackage, error) { + pluginPkgs := make(map[string]pluginPackage) for _, mod := range modules { if mod.goModule.Replace != nil { return nil, fmt.Errorf("cannot auto-upgrade when Go module has been replaced: %s => %s", mod.goModule.Path, mod.goModule.Replace.Path) } - pluginPkgs[mod.goModule.Path] = struct{}{} + pluginPkgs[mod.goModule.Path] = pluginPackage{Version: mod.goModule.Version, Path: mod.goModule.Path} } return pluginPkgs, nil } @@ -312,3 +340,15 @@ func writeCaddyBinary(path string, body *io.ReadCloser, fileInfo os.FileInfo) er } const downloadPath = "https://caddyserver.com/api/download" + +type pluginPackage struct { + Version string + Path string +} + +func (p pluginPackage) String() string { + if p.Version == "" { + return p.Path + } + return p.Path + "@" + p.Version +}