mirror of
https://github.com/caddyserver/xcaddy.git
synced 2025-01-22 16:46:55 +01:00
Update CLI; change command to 'xcaddy'; update readme
This commit is contained in:
parent
40c0796cc2
commit
1cc8e08c16
5 changed files with 234 additions and 105 deletions
47
README.md
47
README.md
|
@ -5,6 +5,8 @@ This package and associated CLI tool make it easy to perform custom builds of th
|
|||
|
||||
Supports Caddy 2 and up.
|
||||
|
||||
⚠️ Still in early development. Works; but no guarantees and prone to changes at this point. Stay updated and please submit feedback!
|
||||
|
||||
## Requirements
|
||||
|
||||
- Go installed
|
||||
|
@ -14,9 +16,9 @@ Supports Caddy 2 and up.
|
|||
## Library usage
|
||||
|
||||
```go
|
||||
caddyVersion := "v2.0.0-beta.19"
|
||||
plugins := []builder.CaddyPlugin{
|
||||
builder.CaddyPlugin{
|
||||
caddyVersion := "v2.0.0-beta.20"
|
||||
plugins := []builder.Dependency{
|
||||
builder.Dependency{
|
||||
ModulePath: "github.com/caddyserver/nginx-adapter",
|
||||
Version: "6c484552e630ccac384d2d9c43c9d14c4e8d2e56",
|
||||
},
|
||||
|
@ -31,23 +33,52 @@ Versions can be anything compatible with `go get`.
|
|||
|
||||
## CLI usage
|
||||
|
||||
The CLI can be used both to make custom builds of Caddy, but also as a replacement for `go run` while developing Caddy plugins.
|
||||
|
||||
### For custom builds
|
||||
|
||||
Syntax:
|
||||
|
||||
```
|
||||
builder --version <version> [--output <file>] [<plugins...>]
|
||||
xcaddy build <version>
|
||||
[--output <file>]
|
||||
[--with <module[@version]>...]
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
- `--version` is the core Caddy version to build (required).
|
||||
- `--version` is the core Caddy version to build (required, for now).
|
||||
- `--output` changes the output file.
|
||||
- `<plugins...>` are extra plugins and their versions, in `go get` module syntax: `module@version`
|
||||
- `--with` can be used multiple times to add plugins by specifying the module name and optionally its version, in a way similar to `go get`.
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
$ builder --version v2.0.0-beta.19 \
|
||||
github.com/caddyserver/nginx-adapter@6c484552e630ccac384d2d9c43c9d14c4e8d2e56
|
||||
$ xcaddy build v2.0.0-beta.20 \
|
||||
--with github.com/caddyserver/nginx-adapter@6c484552e630ccac384d2d9c43c9d14c4e8d2e56
|
||||
```
|
||||
|
||||
### For plugin development
|
||||
|
||||
If you run `xcaddy` from within the folder of the Caddy plugin you're working on without the `build` subcommand, it will build Caddy with your current module and run it, similar to if you manually plugged it in and ran `go run`.
|
||||
|
||||
The binary will be built, run from the current directory, then cleaned up.
|
||||
|
||||
Syntax:
|
||||
|
||||
```
|
||||
xcaddy <args...>
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
- `<args...>` are passed through to the `caddy` command.
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
$ xcaddy list-modules
|
||||
$ xcaddy run --config caddy.json
|
||||
```
|
||||
|
||||
|
||||
|
|
15
builder.go
15
builder.go
|
@ -18,6 +18,7 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
@ -29,9 +30,9 @@ import (
|
|||
"github.com/Masterminds/semver/v3"
|
||||
)
|
||||
|
||||
// Build builds Caddy at the given version with the given plugins and plops
|
||||
// binary down at outputFile.
|
||||
func Build(caddyVersion string, plugins []CaddyPlugin, outputFile string) error {
|
||||
// Build builds Caddy at the given version with the given plugins and
|
||||
// plops a binary down at outputFile.
|
||||
func Build(caddyVersion string, deps []Dependency, outputFile string) error {
|
||||
if caddyVersion == "" {
|
||||
return fmt.Errorf("caddy version is required")
|
||||
}
|
||||
|
@ -47,7 +48,7 @@ func Build(caddyVersion string, plugins []CaddyPlugin, outputFile string) error
|
|||
return err
|
||||
}
|
||||
|
||||
env, err := newEnvironment(caddyVersion, plugins)
|
||||
env, err := newEnvironment(caddyVersion, deps)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -60,6 +61,7 @@ func Build(caddyVersion string, plugins []CaddyPlugin, outputFile string) error
|
|||
"-ldflags", "-w -s", // trim debug symbols
|
||||
"-trimpath",
|
||||
)
|
||||
cmd.Env = append(os.Environ(), "CGO_ENABLED=0")
|
||||
err = env.runCommand(cmd, 5*time.Minute)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -70,10 +72,11 @@ func Build(caddyVersion string, plugins []CaddyPlugin, outputFile string) error
|
|||
return nil
|
||||
}
|
||||
|
||||
// CaddyPlugin pairs a Go module path with a version.
|
||||
type CaddyPlugin struct {
|
||||
// Dependency pairs a Go module path with a version.
|
||||
type Dependency struct {
|
||||
ModulePath string
|
||||
Version string
|
||||
Replace string
|
||||
}
|
||||
|
||||
// newTempFolder creates a new folder in a temporary location.
|
||||
|
|
|
@ -1,89 +0,0 @@
|
|||
// Copyright 2020 Matthew Holt
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/caddyserver/builder"
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.StringVar(&caddyVersion, "version", "", "The version of Caddy core")
|
||||
flag.StringVar(&output, "output", "", "Where to save the resulting binary")
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
|
||||
if caddyVersion == "" {
|
||||
log.Fatal("[FATAL] Caddy version is required (use --version)")
|
||||
}
|
||||
|
||||
// assemble list of plugins and their versions
|
||||
var plugins []builder.CaddyPlugin
|
||||
for _, arg := range flag.Args() {
|
||||
parts := strings.SplitN(arg, "@", 2)
|
||||
if len(parts) != 2 {
|
||||
log.Fatalf("[FATAL] %s: Plugins must be defined using 'module@version' format, similar to the `go get` command", arg)
|
||||
}
|
||||
plugins = append(plugins, builder.CaddyPlugin{
|
||||
ModulePath: parts[0],
|
||||
Version: parts[1],
|
||||
})
|
||||
}
|
||||
|
||||
// ensure an output file is always specified
|
||||
if output == "" {
|
||||
if runtime.GOOS == "windows" {
|
||||
output = "caddy.exe"
|
||||
} else {
|
||||
output = "caddy"
|
||||
}
|
||||
}
|
||||
|
||||
// perform the build
|
||||
err := builder.Build(caddyVersion, plugins, output)
|
||||
if err != nil {
|
||||
log.Fatalf("[FATAL] %v", err)
|
||||
}
|
||||
|
||||
// prove the build is working by printing the version
|
||||
if !filepath.IsAbs(output) {
|
||||
output = "." + string(filepath.Separator) + output
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Printf("%s version\n", output)
|
||||
cmd := exec.Command(output, "version")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
log.Fatalf("[FATAL] %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
caddyVersion string
|
||||
plugins []string
|
||||
output string
|
||||
)
|
168
cmd/xcaddy/main.go
Normal file
168
cmd/xcaddy/main.go
Normal file
|
@ -0,0 +1,168 @@
|
|||
// Copyright 2020 Matthew Holt
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/builder"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) > 1 && os.Args[1] == "build" {
|
||||
if err := runBuild(os.Args[2:]); err != nil {
|
||||
log.Fatalf("[ERROR] %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: the caddy version needs to be settable by the user... maybe an env var?
|
||||
if err := runDev("v2.0.0-beta.20", os.Args[1:]); err != nil {
|
||||
log.Fatalf("[ERROR] %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func runBuild(args []string) error {
|
||||
// parse the command line args... rather primitively
|
||||
var caddyVersion, output string
|
||||
var plugins []builder.Dependency
|
||||
for i := 0; i < len(args); i++ {
|
||||
switch args[i] {
|
||||
case "--with":
|
||||
if i == len(args)-1 {
|
||||
return fmt.Errorf("expected value after --with flag")
|
||||
}
|
||||
i++
|
||||
var mod, ver string
|
||||
arg := args[i]
|
||||
parts := strings.SplitN(arg, "@", 2)
|
||||
mod = parts[0]
|
||||
if len(parts) == 2 {
|
||||
ver = parts[1]
|
||||
}
|
||||
plugins = append(plugins, builder.Dependency{
|
||||
ModulePath: mod,
|
||||
Version: ver,
|
||||
})
|
||||
|
||||
case "--output":
|
||||
if i == len(args)-1 {
|
||||
return fmt.Errorf("expected value after --output flag")
|
||||
}
|
||||
i++
|
||||
output = args[i+1]
|
||||
|
||||
default:
|
||||
if caddyVersion != "" {
|
||||
return fmt.Errorf("missing flag; caddy version already set at %s", caddyVersion)
|
||||
}
|
||||
caddyVersion = args[i]
|
||||
}
|
||||
}
|
||||
|
||||
// ensure an output file is always specified
|
||||
if output == "" {
|
||||
if runtime.GOOS == "windows" {
|
||||
output = "caddy.exe"
|
||||
} else {
|
||||
output = "caddy"
|
||||
}
|
||||
}
|
||||
|
||||
// perform the build
|
||||
err := builder.Build(caddyVersion, plugins, output)
|
||||
if err != nil {
|
||||
log.Fatalf("[FATAL] %v", err)
|
||||
}
|
||||
|
||||
// prove the build is working by printing the version
|
||||
if !filepath.IsAbs(output) {
|
||||
output = "." + string(filepath.Separator) + output
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Printf("%s version\n", output)
|
||||
cmd := exec.Command(output, "version")
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
log.Fatalf("[FATAL] %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func runDev(caddyVersion string, args []string) error {
|
||||
const binOutput = "./caddy"
|
||||
|
||||
// get current/main module name
|
||||
cmd := exec.Command("go", "list", "-m")
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentModule := strings.TrimSpace(string(out))
|
||||
|
||||
// get its root directory
|
||||
cmd = exec.Command("go", "list", "-m", "-f={{.Dir}}")
|
||||
out, err = cmd.Output()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
moduleDir := strings.TrimSpace(string(out))
|
||||
|
||||
// build caddy with this module plugged in
|
||||
err = builder.Build(caddyVersion, []builder.Dependency{
|
||||
{
|
||||
ModulePath: currentModule,
|
||||
Replace: moduleDir,
|
||||
},
|
||||
}, binOutput)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Printf("[INFO] Running %v\n\n", append([]string{binOutput}, args...))
|
||||
|
||||
cmd = exec.Command(binOutput, args...)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cleanup := func() {
|
||||
err = os.Remove(binOutput)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
log.Printf("[ERROR] Deleting temporary binary %s: %v", binOutput, err)
|
||||
}
|
||||
}
|
||||
defer cleanup()
|
||||
go func() {
|
||||
time.Sleep(2 * time.Second)
|
||||
cleanup()
|
||||
}()
|
||||
|
||||
return cmd.Wait()
|
||||
}
|
|
@ -27,7 +27,7 @@ import (
|
|||
"time"
|
||||
)
|
||||
|
||||
func newEnvironment(caddyVersion string, plugins []CaddyPlugin) (*environment, error) {
|
||||
func newEnvironment(caddyVersion string, plugins []Dependency) (*environment, error) {
|
||||
// assume v2 if no semantic version is provided
|
||||
caddyModulePath := defaultCaddyModulePath
|
||||
if !strings.HasPrefix(caddyVersion, "v") || !strings.Contains(caddyVersion, ".") {
|
||||
|
@ -103,6 +103,19 @@ func newEnvironment(caddyVersion string, plugins []CaddyPlugin) (*environment, e
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// specify module replacements before pinning versions
|
||||
for _, p := range plugins {
|
||||
if p.Replace != "" {
|
||||
log.Printf("[INFO] Replace %s => %s", p.ModulePath, p.Replace)
|
||||
cmd := env.newCommand("go", "mod", "edit",
|
||||
"-replace", fmt.Sprintf("%s=%s", p.ModulePath, p.Replace))
|
||||
err := env.runCommand(cmd, 10*time.Second)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// pin versions by populating go.mod, first for Caddy itself and then plugins
|
||||
log.Println("[INFO] Pinning versions")
|
||||
err = env.execGoGet(caddyModulePath, caddyVersion)
|
||||
|
@ -110,6 +123,9 @@ func newEnvironment(caddyVersion string, plugins []CaddyPlugin) (*environment, e
|
|||
return nil, err
|
||||
}
|
||||
for _, p := range plugins {
|
||||
if p.Replace != "" {
|
||||
continue
|
||||
}
|
||||
err = env.execGoGet(p.ModulePath, p.Version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -123,7 +139,7 @@ func newEnvironment(caddyVersion string, plugins []CaddyPlugin) (*environment, e
|
|||
|
||||
type environment struct {
|
||||
caddyVersion string
|
||||
plugins []CaddyPlugin
|
||||
plugins []Dependency
|
||||
caddyModulePath string
|
||||
tempFolder string
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue