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:
Tom 2023-07-14 15:21:18 +02:00
parent e2f5c66b1c
commit 17d64826df
5 changed files with 94 additions and 90 deletions

2
go.mod
View file

@ -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
View file

@ -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=

View file

@ -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{

View file

@ -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

View 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
}
}