StackWithResources: Update ComposeYML behaviour
Previously, there was a function to manually read bytes for a docker-compose.yml. But this proved to be akward at runtime. Instead, this code automatically reads an existing docker-compose.yml, and takes care of marshalling and unmarshalling.
This commit is contained in:
parent
e2f5c66b1c
commit
17d64826df
5 changed files with 94 additions and 90 deletions
2
go.mod
2
go.mod
|
|
@ -16,7 +16,7 @@ require (
|
|||
github.com/pquerna/otp v1.4.0
|
||||
github.com/rs/zerolog v1.29.1
|
||||
github.com/tkw1536/goprogram v0.3.5
|
||||
github.com/tkw1536/pkglib v0.0.0-20230713130635-2bcbc40ecdd9
|
||||
github.com/tkw1536/pkglib v0.0.0-20230714124712-ccf0c3658789
|
||||
github.com/yuin/goldmark v1.5.4
|
||||
github.com/yuin/goldmark-meta v1.1.0
|
||||
golang.org/x/crypto v0.8.0
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -114,8 +114,8 @@ github.com/tdewolff/parse v2.3.4+incompatible/go.mod h1:8oBwCsVmUkgHO8M5iCzSIDtp
|
|||
github.com/tdewolff/test v1.0.7 h1:8Vs0142DmPFW/bQeHRP3MV19m1gvndjUb1sn8yy74LM=
|
||||
github.com/tkw1536/goprogram v0.3.5 h1:S0axKo3R/vGa4zhYqYDKAZEPhAfwUSSeMtVwnAu4sNY=
|
||||
github.com/tkw1536/goprogram v0.3.5/go.mod h1:pYr4dMHOSVurbPQ4KTR0ett8XWNISbsRS6zlh9Nsxa8=
|
||||
github.com/tkw1536/pkglib v0.0.0-20230713130635-2bcbc40ecdd9 h1:l+xh6dn9PW+HNYrIXgwsOileuIhkdxZocOicQfBdB9M=
|
||||
github.com/tkw1536/pkglib v0.0.0-20230713130635-2bcbc40ecdd9/go.mod h1:0A1B9Cc5+yJXR3eeB14CqD4dFSbEjjWRo5Pr9M3XYuI=
|
||||
github.com/tkw1536/pkglib v0.0.0-20230714124712-ccf0c3658789 h1:S6pXBfW8SJsN/MZEyhSbziV3F59P4VoddviiE5oPFoU=
|
||||
github.com/tkw1536/pkglib v0.0.0-20230714124712-ccf0c3658789/go.mod h1:0A1B9Cc5+yJXR3eeB14CqD4dFSbEjjWRo5Pr9M3XYuI=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||
|
|
|
|||
|
|
@ -1,15 +1,12 @@
|
|||
package binder
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"embed"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||
"github.com/tkw1536/pkglib/yamlx"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
type Binder struct {
|
||||
|
|
@ -29,52 +26,25 @@ func (binder *Binder) Context(parent component.InstallationContext) component.In
|
|||
}
|
||||
|
||||
//go:embed docker-compose.yml
|
||||
var composeTemplate []byte
|
||||
|
||||
func (binder *Binder) buildYML() ([]byte, error) {
|
||||
var dockerCompose yaml.Node
|
||||
if err := yaml.Unmarshal(composeTemplate, &dockerCompose); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for dockerCompose.Kind == yaml.DocumentNode {
|
||||
dockerCompose = *dockerCompose.Content[0]
|
||||
}
|
||||
|
||||
{
|
||||
ports := binder.Config.Listen.ComposePorts("8000")
|
||||
portsNode, err := yamlx.Marshal(ports)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := yamlx.Replace(&dockerCompose, *portsNode, "services", "binder", "ports"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
command := binder.Config.HTTP.TCPMuxCommand("0.0.0.0:8000", "http:80", "http:443", "ssh:2222")
|
||||
commandNode, err := yamlx.Marshal(command)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := yamlx.Replace(&dockerCompose, *commandNode, "services", "binder", "command"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// do the final marshal
|
||||
return yaml.Marshal(dockerCompose)
|
||||
}
|
||||
var composeTemplate embed.FS
|
||||
|
||||
func (binder *Binder) Stack() component.StackWithResources {
|
||||
return component.MakeStack(binder, component.StackWithResources{
|
||||
ReadComposeFile: func() (io.Reader, error) {
|
||||
data, err := binder.buildYML()
|
||||
if err != nil {
|
||||
ContextPath: ".",
|
||||
Resources: composeTemplate,
|
||||
|
||||
ComposerYML: func(root *yaml.Node) (*yaml.Node, error) {
|
||||
ports := binder.Config.Listen.ComposePorts("8000")
|
||||
if err := yamlx.ReplaceWith(root, ports, "services", "binder", "ports"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return bytes.NewReader(data), nil
|
||||
|
||||
command := binder.Config.HTTP.TCPMuxCommand("0.0.0.0:8000", "http:80", "http:443", "ssh:2222")
|
||||
if err := yamlx.ReplaceWith(root, command, "services", "binder", "command"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return root, nil
|
||||
},
|
||||
|
||||
EnvContext: map[string]string{
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
"github.com/tkw1536/pkglib/fsx/umaskfree"
|
||||
"github.com/tkw1536/pkglib/stream"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Stack represents a 'docker compose' stack living in a specific directory
|
||||
|
|
@ -176,12 +177,10 @@ type StackWithResources struct {
|
|||
// These all refer to paths within the Resource filesystem.
|
||||
Resources fs.FS
|
||||
|
||||
ContextPath string // the 'docker compose' stack context. Can, but does not have to, contain 'docker-compose.yml'
|
||||
ContextPath string // the 'docker compose' stack context. May or may not contain 'docker-compose.yml'
|
||||
|
||||
// TODO: Make this nicer to replace variables
|
||||
ReadComposeFile func() (io.Reader, error) // read the 'docker-compose.yml' (if not contained in context)
|
||||
ComposerYML func(*yaml.Node) (*yaml.Node, error) // update 'docker-compose.yml', if no 'docker-compose.yml' exists, the passed node is nil.
|
||||
|
||||
// EnvPath string // the '.env' template, will be installed using [unpack.InstallTemplate]. If empty, use new syntax
|
||||
EnvContext map[string]string // context when instantiating the '.env' template
|
||||
|
||||
CopyContextFiles []string // Files to copy from the installation context
|
||||
|
|
@ -219,36 +218,12 @@ func (is StackWithResources) Install(ctx context.Context, progress io.Writer, co
|
|||
}
|
||||
}
|
||||
|
||||
// write the docker-compose.yml file
|
||||
if is.ReadComposeFile != nil {
|
||||
err := (func() error {
|
||||
// find the file to install!
|
||||
dst := filepath.Join(is.Dir, "docker-compose.yml")
|
||||
defer fmt.Fprintf(progress, "[install] %s\n", dst)
|
||||
// update the docker compose file
|
||||
if is.ComposerYML != nil {
|
||||
dst := filepath.Join(is.Dir, "docker-compose.yml")
|
||||
fmt.Fprintf(progress, "[install] %s\n", dst)
|
||||
|
||||
// create the file
|
||||
yml, err := umaskfree.Create(dst, umaskfree.DefaultFilePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer yml.Close()
|
||||
|
||||
// open the source file from the context
|
||||
src, err := is.ReadComposeFile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if srcc, ok := src.(io.Closer); ok {
|
||||
defer srcc.Close()
|
||||
}
|
||||
|
||||
// copy it over!
|
||||
{
|
||||
_, err := io.Copy(yml, src)
|
||||
return err
|
||||
}
|
||||
})()
|
||||
if err != nil {
|
||||
if err := doComposeFile(dst, is.ComposerYML); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
@ -301,7 +276,7 @@ func (is StackWithResources) Install(ctx context.Context, progress io.Writer, co
|
|||
dst := filepath.Join(is.Dir, name)
|
||||
|
||||
fmt.Fprintf(progress, "[touch] %s\n", dst)
|
||||
if err := umaskfree.Touch(dst, is.TouchFilesPerm); err != nil {
|
||||
if err := umaskfree.Touch(dst, umaskfree.DefaultFilePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
@ -318,6 +293,59 @@ func (is StackWithResources) Install(ctx context.Context, progress io.Writer, co
|
|||
return nil
|
||||
}
|
||||
|
||||
// doComposeFile updates the compose file using the update function.
|
||||
//
|
||||
// If the file at path already exists, calls update with the existing data.
|
||||
// if the file at path does not exist, calls update(nil).
|
||||
func doComposeFile(path string, update func(node *yaml.Node) (*yaml.Node, error)) error {
|
||||
var mode fs.FileMode
|
||||
var node *yaml.Node
|
||||
|
||||
{
|
||||
stat, err := os.Stat(path)
|
||||
switch {
|
||||
case err == nil:
|
||||
// file exists => use the previous file mode
|
||||
mode = stat.Mode()
|
||||
|
||||
// read the yaml bytes
|
||||
bytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to read existing file")
|
||||
}
|
||||
|
||||
// unmarshal it into a node, or bail out!
|
||||
node = new(yaml.Node)
|
||||
if err := yaml.Unmarshal(bytes, node); err != nil {
|
||||
return errors.Wrap(err, "unable to unmarshal existing file")
|
||||
}
|
||||
case errors.Is(err, fs.ErrNotExist):
|
||||
// file does not exist => use default mode
|
||||
mode = umaskfree.DefaultFilePerm
|
||||
|
||||
// use a nil existing node
|
||||
node = nil
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// update the node
|
||||
node, err := update(node)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "update function failed")
|
||||
}
|
||||
|
||||
// re-encode the bytes
|
||||
result, err := yaml.Marshal(node)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to re-marshal")
|
||||
}
|
||||
|
||||
// write the bytes back!
|
||||
return umaskfree.WriteFile(path, result, mode)
|
||||
}
|
||||
|
||||
// writeEnvFile writes an environment file
|
||||
func writeEnvFile(path string, perm fs.FileMode, variables map[string]string) error {
|
||||
// create the environment file
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
package web
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
_ "embed"
|
||||
)
|
||||
|
|
@ -44,16 +43,23 @@ func (web *Web) Stack() component.StackWithResources {
|
|||
}
|
||||
|
||||
if web.Config.HTTP.HTTPSEnabled() {
|
||||
stack.ReadComposeFile = func() (io.Reader, error) {
|
||||
return bytes.NewReader(dockerComposeHTTPS), nil
|
||||
}
|
||||
stack.ComposerYML = readYaml(dockerComposeHTTPS)
|
||||
stack.TouchFilesPerm = 0600
|
||||
stack.TouchFiles = []string{"acme.json"}
|
||||
} else {
|
||||
stack.ReadComposeFile = func() (io.Reader, error) {
|
||||
return bytes.NewReader(dockerComposeHTTP), nil
|
||||
}
|
||||
stack.ComposerYML = readYaml(dockerComposeHTTP)
|
||||
}
|
||||
|
||||
return component.MakeStack(web, stack)
|
||||
}
|
||||
|
||||
func readYaml(bytes []byte) func(*yaml.Node) (*yaml.Node, error) {
|
||||
return func(n *yaml.Node) (*yaml.Node, error) {
|
||||
var node yaml.Node
|
||||
err := yaml.Unmarshal(bytes, &node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &node, nil
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue