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/pquerna/otp v1.4.0
|
||||||
github.com/rs/zerolog v1.29.1
|
github.com/rs/zerolog v1.29.1
|
||||||
github.com/tkw1536/goprogram v0.3.5
|
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 v1.5.4
|
||||||
github.com/yuin/goldmark-meta v1.1.0
|
github.com/yuin/goldmark-meta v1.1.0
|
||||||
golang.org/x/crypto v0.8.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/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 h1:S0axKo3R/vGa4zhYqYDKAZEPhAfwUSSeMtVwnAu4sNY=
|
||||||
github.com/tkw1536/goprogram v0.3.5/go.mod h1:pYr4dMHOSVurbPQ4KTR0ett8XWNISbsRS6zlh9Nsxa8=
|
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-20230714124712-ccf0c3658789 h1:S6pXBfW8SJsN/MZEyhSbziV3F59P4VoddviiE5oPFoU=
|
||||||
github.com/tkw1536/pkglib v0.0.0-20230713130635-2bcbc40ecdd9/go.mod h1:0A1B9Cc5+yJXR3eeB14CqD4dFSbEjjWRo5Pr9M3XYuI=
|
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-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 h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,12 @@
|
||||||
package binder
|
package binder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"embed"
|
||||||
"io"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||||
"github.com/tkw1536/pkglib/yamlx"
|
"github.com/tkw1536/pkglib/yamlx"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
_ "embed"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Binder struct {
|
type Binder struct {
|
||||||
|
|
@ -29,52 +26,25 @@ func (binder *Binder) Context(parent component.InstallationContext) component.In
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed docker-compose.yml
|
//go:embed docker-compose.yml
|
||||||
var composeTemplate []byte
|
var composeTemplate embed.FS
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (binder *Binder) Stack() component.StackWithResources {
|
func (binder *Binder) Stack() component.StackWithResources {
|
||||||
return component.MakeStack(binder, component.StackWithResources{
|
return component.MakeStack(binder, component.StackWithResources{
|
||||||
ReadComposeFile: func() (io.Reader, error) {
|
ContextPath: ".",
|
||||||
data, err := binder.buildYML()
|
Resources: composeTemplate,
|
||||||
if err != nil {
|
|
||||||
|
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 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{
|
EnvContext: map[string]string{
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/tkw1536/pkglib/fsx/umaskfree"
|
"github.com/tkw1536/pkglib/fsx/umaskfree"
|
||||||
"github.com/tkw1536/pkglib/stream"
|
"github.com/tkw1536/pkglib/stream"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Stack represents a 'docker compose' stack living in a specific directory
|
// 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.
|
// These all refer to paths within the Resource filesystem.
|
||||||
Resources fs.FS
|
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
|
ComposerYML func(*yaml.Node) (*yaml.Node, error) // update 'docker-compose.yml', if no 'docker-compose.yml' exists, the passed node is nil.
|
||||||
ReadComposeFile func() (io.Reader, error) // read the 'docker-compose.yml' (if not contained in context)
|
|
||||||
|
|
||||||
// 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
|
EnvContext map[string]string // context when instantiating the '.env' template
|
||||||
|
|
||||||
CopyContextFiles []string // Files to copy from the installation context
|
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
|
// update the docker compose file
|
||||||
if is.ReadComposeFile != nil {
|
if is.ComposerYML != nil {
|
||||||
err := (func() error {
|
dst := filepath.Join(is.Dir, "docker-compose.yml")
|
||||||
// find the file to install!
|
fmt.Fprintf(progress, "[install] %s\n", dst)
|
||||||
dst := filepath.Join(is.Dir, "docker-compose.yml")
|
|
||||||
defer fmt.Fprintf(progress, "[install] %s\n", dst)
|
|
||||||
|
|
||||||
// create the file
|
if err := doComposeFile(dst, is.ComposerYML); err != nil {
|
||||||
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 {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -301,7 +276,7 @@ func (is StackWithResources) Install(ctx context.Context, progress io.Writer, co
|
||||||
dst := filepath.Join(is.Dir, name)
|
dst := filepath.Join(is.Dir, name)
|
||||||
|
|
||||||
fmt.Fprintf(progress, "[touch] %s\n", dst)
|
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
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -318,6 +293,59 @@ func (is StackWithResources) Install(ctx context.Context, progress io.Writer, co
|
||||||
return nil
|
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
|
// writeEnvFile writes an environment file
|
||||||
func writeEnvFile(path string, perm fs.FileMode, variables map[string]string) error {
|
func writeEnvFile(path string, perm fs.FileMode, variables map[string]string) error {
|
||||||
// create the environment file
|
// create the environment file
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
package web
|
package web
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"io"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
_ "embed"
|
_ "embed"
|
||||||
)
|
)
|
||||||
|
|
@ -44,16 +43,23 @@ func (web *Web) Stack() component.StackWithResources {
|
||||||
}
|
}
|
||||||
|
|
||||||
if web.Config.HTTP.HTTPSEnabled() {
|
if web.Config.HTTP.HTTPSEnabled() {
|
||||||
stack.ReadComposeFile = func() (io.Reader, error) {
|
stack.ComposerYML = readYaml(dockerComposeHTTPS)
|
||||||
return bytes.NewReader(dockerComposeHTTPS), nil
|
|
||||||
}
|
|
||||||
stack.TouchFilesPerm = 0600
|
stack.TouchFilesPerm = 0600
|
||||||
stack.TouchFiles = []string{"acme.json"}
|
stack.TouchFiles = []string{"acme.json"}
|
||||||
} else {
|
} else {
|
||||||
stack.ReadComposeFile = func() (io.Reader, error) {
|
stack.ComposerYML = readYaml(dockerComposeHTTP)
|
||||||
return bytes.NewReader(dockerComposeHTTP), nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return component.MakeStack(web, stack)
|
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