2020-03-21 20:31:29 +00:00
|
|
|
// 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.
|
|
|
|
|
2020-04-02 15:35:27 +01:00
|
|
|
package xcaddy
|
2020-03-21 20:31:29 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"html/template"
|
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
2020-03-21 22:27:26 +00:00
|
|
|
"strings"
|
2020-03-21 20:31:29 +00:00
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
2020-04-04 16:56:37 +01:00
|
|
|
func (b Builder) newEnvironment() (*environment, error) {
|
|
|
|
// assume Caddy v2 if no semantic version is provided
|
2020-03-21 22:27:26 +00:00
|
|
|
caddyModulePath := defaultCaddyModulePath
|
2020-04-04 16:56:37 +01:00
|
|
|
if !strings.HasPrefix(b.CaddyVersion, "v") || !strings.Contains(b.CaddyVersion, ".") {
|
2020-03-21 22:27:26 +00:00
|
|
|
caddyModulePath += "/v2"
|
|
|
|
}
|
2020-04-04 16:56:37 +01:00
|
|
|
caddyModulePath, err := versionedModulePath(caddyModulePath, b.CaddyVersion)
|
2020-03-21 20:31:29 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// clean up any SIV-incompatible module paths real quick
|
2020-04-04 16:56:37 +01:00
|
|
|
for i, p := range b.Plugins {
|
|
|
|
b.Plugins[i].ModulePath, err = versionedModulePath(p.ModulePath, p.Version)
|
2020-03-21 20:31:29 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// create the context for the main module template
|
|
|
|
ctx := moduleTemplateContext{
|
|
|
|
CaddyModule: caddyModulePath,
|
|
|
|
}
|
2020-04-04 16:56:37 +01:00
|
|
|
for _, p := range b.Plugins {
|
2020-03-21 20:31:29 +00:00
|
|
|
ctx.Plugins = append(ctx.Plugins, p.ModulePath)
|
|
|
|
}
|
|
|
|
|
|
|
|
// evaluate the template for the main module
|
|
|
|
var buf bytes.Buffer
|
|
|
|
tpl, err := template.New("main").Parse(mainModuleTemplate)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
err = tpl.Execute(&buf, ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// create the folder in which the build environment will operate
|
|
|
|
tempFolder, err := newTempFolder()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
if err != nil {
|
|
|
|
err2 := os.RemoveAll(tempFolder)
|
|
|
|
if err2 != nil {
|
|
|
|
err = fmt.Errorf("%w; additionally, cleaning up folder: %v", err, err2)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
log.Printf("[INFO] Temporary folder: %s", tempFolder)
|
|
|
|
|
|
|
|
// write the main module file to temporary folder
|
|
|
|
mainPath := filepath.Join(tempFolder, "main.go")
|
|
|
|
log.Printf("[INFO] Writing main module: %s", mainPath)
|
|
|
|
err = ioutil.WriteFile(mainPath, buf.Bytes(), 0644)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
env := &environment{
|
2020-04-04 16:56:37 +01:00
|
|
|
caddyVersion: b.CaddyVersion,
|
|
|
|
plugins: b.Plugins,
|
2020-03-21 20:31:29 +00:00
|
|
|
caddyModulePath: caddyModulePath,
|
|
|
|
tempFolder: tempFolder,
|
|
|
|
}
|
|
|
|
|
|
|
|
// initialize the go module
|
|
|
|
log.Println("[INFO] Initializing Go module")
|
|
|
|
cmd := env.newCommand("go", "mod", "init", "caddy")
|
|
|
|
err = env.runCommand(cmd, 10*time.Second)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-03-25 06:01:44 +00:00
|
|
|
// specify module replacements before pinning versions
|
2020-04-04 16:56:37 +01:00
|
|
|
for _, p := range b.Plugins {
|
|
|
|
if p.Replace == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
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
|
2020-03-25 06:01:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-21 20:31:29 +00:00
|
|
|
// pin versions by populating go.mod, first for Caddy itself and then plugins
|
|
|
|
log.Println("[INFO] Pinning versions")
|
2020-04-04 16:56:37 +01:00
|
|
|
err = env.execGoGet(caddyModulePath, b.CaddyVersion)
|
2020-03-21 20:31:29 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-04-04 16:56:37 +01:00
|
|
|
for _, p := range b.Plugins {
|
2020-03-25 06:01:44 +00:00
|
|
|
if p.Replace != "" {
|
|
|
|
continue
|
|
|
|
}
|
2020-03-21 20:31:29 +00:00
|
|
|
err = env.execGoGet(p.ModulePath, p.Version)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Println("[INFO] Build environment ready")
|
|
|
|
|
|
|
|
return env, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type environment struct {
|
|
|
|
caddyVersion string
|
2020-03-25 06:01:44 +00:00
|
|
|
plugins []Dependency
|
2020-03-21 20:31:29 +00:00
|
|
|
caddyModulePath string
|
|
|
|
tempFolder string
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close cleans up the build environment, including deleting
|
|
|
|
// the temporary folder from the disk.
|
|
|
|
func (env environment) Close() error {
|
|
|
|
log.Printf("[INFO] Cleaning up temporary folder: %s", env.tempFolder)
|
|
|
|
return os.RemoveAll(env.tempFolder)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (env environment) newCommand(command string, args ...string) *exec.Cmd {
|
|
|
|
cmd := exec.Command(command, args...)
|
|
|
|
cmd.Dir = env.tempFolder
|
|
|
|
cmd.Stdout = os.Stdout
|
|
|
|
cmd.Stderr = os.Stderr
|
|
|
|
return cmd
|
|
|
|
}
|
|
|
|
|
|
|
|
func (env environment) runCommand(cmd *exec.Cmd, timeout time.Duration) error {
|
|
|
|
log.Printf("[INFO] exec (timeout=%s): %+v ", timeout, cmd)
|
|
|
|
|
|
|
|
// no timeout? this is easy; just run it
|
|
|
|
if timeout == 0 {
|
|
|
|
return cmd.Run()
|
|
|
|
}
|
|
|
|
|
|
|
|
// otherwise start it and use a timer
|
|
|
|
err := cmd.Start()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
timer := time.AfterFunc(timeout, func() {
|
|
|
|
err = fmt.Errorf("timed out (builder-enforced)")
|
|
|
|
cmd.Process.Kill()
|
|
|
|
})
|
|
|
|
waitErr := cmd.Wait()
|
|
|
|
timer.Stop()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return waitErr
|
|
|
|
}
|
|
|
|
|
|
|
|
func (env environment) execGoGet(modulePath, moduleVersion string) error {
|
|
|
|
mod := modulePath + "@" + moduleVersion
|
|
|
|
cmd := env.newCommand("go", "get", "-d", "-v", mod)
|
2020-03-21 22:29:10 +00:00
|
|
|
return env.runCommand(cmd, 60*time.Second)
|
2020-03-21 20:31:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type moduleTemplateContext struct {
|
|
|
|
CaddyModule string
|
|
|
|
Plugins []string
|
|
|
|
}
|
|
|
|
|
|
|
|
const mainModuleTemplate = `package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
caddycmd "{{.CaddyModule}}/cmd"
|
|
|
|
|
|
|
|
// plug in Caddy modules here
|
|
|
|
_ "{{.CaddyModule}}/modules/standard"
|
|
|
|
{{- range .Plugins}}
|
|
|
|
_ "{{.}}"
|
|
|
|
{{- end}}
|
|
|
|
)
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
caddycmd.Main()
|
|
|
|
}
|
|
|
|
`
|