mirror of
https://gitea.elkins.co/Networking/ccl.git
synced 2025-03-06 18:28:48 -06:00
Implement rudimentary dependency model with start_order
Any container definitions in ccl.toml can be given a `start_order` tag (integer). It is recommended to not put a start_order unless a container depends on another one (e.g. synapse needs postgres), in which case all dependents and dependees should be given a `start_order`, with dependees having a lower number than their dependents. It is guaranteed that the dependees will be started first, although the container startup procedure is outside of our control. Containers without a `start_order` will have their operations applied asynchronously, but those with a start order are started, well, in order from lowest to highest. "Stop" operations are applied in the reverse order.
This commit is contained in:
parent
12b7438b25
commit
74bbc9dad8
@ -38,6 +38,7 @@ var createCmd = &cobra.Command{
|
||||
names or categories. Multiple arguments are supported.`,
|
||||
Run: func(_ *cobra.Command, args []string) {
|
||||
conts := config.Union(args, contMask)
|
||||
container.Reorder(conts, container.Start)
|
||||
execForEach(conts, func(c *container.Container) command.CommandSet { return c.CreateCommands() })
|
||||
},
|
||||
}
|
||||
|
@ -32,9 +32,11 @@ import (
|
||||
func execForEach(tgts []container.Container, getSet func(*container.Container) command.CommandSet) {
|
||||
var wg sync.WaitGroup
|
||||
for i := range tgts {
|
||||
wg.Add(1)
|
||||
go func(cont *container.Container) {
|
||||
defer wg.Done()
|
||||
async := !tgts[i].StartOrder.Valid
|
||||
runSet := func(cont *container.Container) {
|
||||
if async {
|
||||
defer wg.Done()
|
||||
}
|
||||
set := getSet(cont)
|
||||
for _, cmd := range set.Commands {
|
||||
if err := cmd.Execute(output, fake, set.ID); err != nil {
|
||||
@ -45,7 +47,13 @@ func execForEach(tgts []container.Container, getSet func(*container.Container) c
|
||||
return
|
||||
}
|
||||
}
|
||||
}(&tgts[i])
|
||||
}
|
||||
if async {
|
||||
wg.Add(1)
|
||||
go runSet(&tgts[i])
|
||||
} else {
|
||||
runSet(&tgts[i])
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ affected: the old image will still remain, though untagged, and any defined cont
|
||||
will still use it.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
conts := config.Union(args, contMask)
|
||||
container.Reorder(conts, container.Start)
|
||||
execForEach(conts, func(c *container.Container) command.CommandSet { return c.PullCommands() })
|
||||
},
|
||||
}
|
||||
|
@ -38,7 +38,11 @@ var recreateCmd = &cobra.Command{
|
||||
one or more container names or categories. If empty, "all" is assumed.`,
|
||||
Run: func(_ *cobra.Command, args []string) {
|
||||
conts := config.Union(args, contMask)
|
||||
container.Reorder(conts, container.Stop)
|
||||
execForEach(conts, func(c *container.Container) command.CommandSet { return c.StopCommands() })
|
||||
container.Reorder(conts, container.Start)
|
||||
execForEach(conts, func(c *container.Container) command.CommandSet { return c.RecreateCommands() })
|
||||
execForEach(conts, func(c *container.Container) command.CommandSet { return c.ConditionalStartCommands() })
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -38,7 +38,10 @@ var restartCmd = &cobra.Command{
|
||||
one or more container names or categories. If empty, "all" is assumed.`,
|
||||
Run: func(_ *cobra.Command, args []string) {
|
||||
conts := config.Union(args, contMask)
|
||||
execForEach(conts, func(c *container.Container) command.CommandSet { return c.RestartCommands() })
|
||||
container.Reorder(conts, container.Stop)
|
||||
execForEach(conts, func(c *container.Container) command.CommandSet { return c.StopCommands() })
|
||||
container.Reorder(conts, container.Start)
|
||||
execForEach(conts, func(c *container.Container) command.CommandSet { return c.StartCommands() })
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -39,6 +39,8 @@ var rmCmd = &cobra.Command{
|
||||
If running, they will first be stopped.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
conts := config.Union(args, contMask)
|
||||
container.Reorder(conts, container.Stop)
|
||||
execForEach(conts, func(c *container.Container) command.CommandSet { return c.StopCommands() })
|
||||
execForEach(conts, func(c *container.Container) command.CommandSet { return c.RemoveCommands() })
|
||||
},
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ var startCmd = &cobra.Command{
|
||||
one or more container names or categories. If empty, "all" is assumed.`,
|
||||
Run: func(_ *cobra.Command, args []string) {
|
||||
conts := config.Union(args, contMask)
|
||||
container.Reorder(conts, container.Start)
|
||||
execForEach(conts, func(c *container.Container) command.CommandSet { return c.StartCommands() })
|
||||
},
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ var stopCmd = &cobra.Command{
|
||||
one or more container names or categories. If empty, "all" is assumed.`,
|
||||
Run: func(_ *cobra.Command, args []string) {
|
||||
conts := config.Union(args, contMask)
|
||||
container.Reorder(conts, container.Stop)
|
||||
execForEach(conts, func(c *container.Container) command.CommandSet { return c.StopCommands() })
|
||||
},
|
||||
}
|
||||
|
@ -38,7 +38,10 @@ var updateCmd = &cobra.Command{
|
||||
one or more container names or categories. If empty, "all" is assumed.`,
|
||||
Run: func(_ *cobra.Command, args []string) {
|
||||
conts := config.Union(args, contMask)
|
||||
container.Reorder(conts, container.Stop)
|
||||
execForEach(conts, func(c *container.Container) command.CommandSet { return c.UpdateCommands() })
|
||||
container.Reorder(conts, container.Start)
|
||||
execForEach(conts, func(c *container.Container) command.CommandSet { return c.ConditionalStartCommands() })
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,7 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package container
|
||||
|
||||
import (
|
||||
@ -40,25 +41,27 @@ import (
|
||||
)
|
||||
|
||||
type Container struct {
|
||||
Category string `toml:"category"`
|
||||
Name string `toml:"name"`
|
||||
Image string `toml:"image"`
|
||||
Hostname string `toml:"hostname,omitempty"`
|
||||
Command []string `toml:"cmd,omitempty"`
|
||||
Arguments string `toml:"arguments,omitempty"`
|
||||
Networks []network.Network `toml:"networks,omitempty"`
|
||||
Env map[string]string `toml:"env,omitempty"`
|
||||
Mounts []spec.Mount `toml:"mounts,omitempty"`
|
||||
Restart string `toml:"restart,omitempty"`
|
||||
Umask null.Int `toml:"umask,omitempty"`
|
||||
User string `toml:"user,omitempty"`
|
||||
ExposeTcp []uint16 `toml:"expose_tcp,omitempty"`
|
||||
ExposeUdp []uint16 `toml:"expose_udp,omitempty"`
|
||||
PortsTcp map[uint16]uint16 `toml:"ports,omitempty"`
|
||||
NetNS string `toml:"netns,omitempty"`
|
||||
Category string `toml:"category"`
|
||||
Name string `toml:"name"`
|
||||
Image string `toml:"image"`
|
||||
Hostname string `toml:"hostname,omitempty"`
|
||||
Command []string `toml:"cmd,omitempty"`
|
||||
Arguments string `toml:"arguments,omitempty"`
|
||||
Networks []network.Network `toml:"networks,omitempty"`
|
||||
Env map[string]string `toml:"env,omitempty"`
|
||||
Mounts []spec.Mount `toml:"mounts,omitempty"`
|
||||
Restart string `toml:"restart,omitempty"`
|
||||
Umask null.Int `toml:"umask,omitempty"`
|
||||
User string `toml:"user,omitempty"`
|
||||
ExposeTcp []uint16 `toml:"expose_tcp,omitempty"`
|
||||
ExposeUdp []uint16 `toml:"expose_udp,omitempty"`
|
||||
PortsTcp map[uint16]uint16 `toml:"ports,omitempty"`
|
||||
NetNS string `toml:"netns,omitempty"`
|
||||
StartOrder null.Int `toml:"start_order,omitempty"`
|
||||
|
||||
conn context.Context
|
||||
cdata *define.InspectContainerData
|
||||
wasRunning null.Bool
|
||||
}
|
||||
|
||||
func (c *Container) Init(conn context.Context, nets []network.Network) error {
|
||||
@ -102,7 +105,9 @@ func (c *Container) Init(conn context.Context, nets []network.Network) error {
|
||||
return fmt.Errorf("conn is nil: %s", c.Name)
|
||||
}
|
||||
c.conn = conn
|
||||
return c.populateCData()
|
||||
err := c.populateCData()
|
||||
c.wasRunning.SetValid(c.IsRunning())
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Container) LogEntry() *log.Entry {
|
||||
@ -244,32 +249,14 @@ func (c *Container) CreateCommands() cmd.CommandSet {
|
||||
}
|
||||
|
||||
func (c *Container) RecreateCommands() cmd.CommandSet {
|
||||
wasRunning := false
|
||||
return c.newCommandSet("RECREATE", cmd.Commands{
|
||||
cmd.NewFunc("stash_run_state", func() error {
|
||||
wasRunning = c.IsRunning()
|
||||
return nil
|
||||
}),
|
||||
cmd.NewSet(c.StopCommands()),
|
||||
cmd.NewSet(c.removeCommands()),
|
||||
cmd.NewSet(c.RemoveCommands()),
|
||||
cmd.NewSet(c.CreateCommands()),
|
||||
cmd.NewConditional("start_if_was_running",
|
||||
func() bool { return wasRunning },
|
||||
cmd.NewSet(c.StartCommands()),
|
||||
cmd.NewNop(),
|
||||
),
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Container) RemoveCommands() cmd.CommandSet {
|
||||
return c.newCommandSet("REMOVE", cmd.Commands{
|
||||
cmd.NewSet(c.StopCommands()),
|
||||
cmd.NewSet(c.removeCommands()),
|
||||
})
|
||||
}
|
||||
|
||||
// unexported version just removes the container without attempting a stop.
|
||||
func (c *Container) removeCommands() cmd.CommandSet {
|
||||
func (c *Container) RemoveCommands() cmd.CommandSet {
|
||||
return c.newCommandSet("remove", cmd.Commands{
|
||||
cmd.NewFunc("remove_if_exists", func() error {
|
||||
if c.cdata.ID == "" {
|
||||
@ -293,7 +280,11 @@ func (c *Container) StartCommands() cmd.CommandSet {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = containers.Wait(c.conn, c.cdata.ID, &containers.WaitOptions{Condition: []define.ContainerStatus{define.ContainerStateRunning}})
|
||||
_, err = containers.Wait(
|
||||
c.conn,
|
||||
c.cdata.ID,
|
||||
&containers.WaitOptions{Condition: []define.ContainerStatus{define.ContainerStateRunning}},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -312,13 +303,6 @@ func (c *Container) StartCommands() cmd.CommandSet {
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Container) RestartCommands() cmd.CommandSet {
|
||||
return c.newCommandSet("RESTART", cmd.Commands{
|
||||
cmd.NewSet(c.StopCommands()),
|
||||
cmd.NewSet(c.StartCommands()),
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Container) IsRunning() bool {
|
||||
if c.cdata != nil && c.cdata.State != nil {
|
||||
return c.cdata.State.Running
|
||||
@ -334,21 +318,18 @@ func (c *Container) IsCreated() bool {
|
||||
}
|
||||
|
||||
func (c *Container) UpdateCommands() cmd.CommandSet {
|
||||
wasRunning := false
|
||||
return c.newCommandSet("UPDATE", cmd.Commands{
|
||||
cmd.NewFunc("pull_image", func() error {
|
||||
err := c.pull()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wasRunning = c.cdata != nil && c.cdata.State != nil && c.cdata.State.Running
|
||||
return nil
|
||||
}),
|
||||
cmd.NewSet(c.PullCommands()),
|
||||
cmd.NewSet(c.StopCommands()),
|
||||
cmd.NewSet(c.removeCommands()),
|
||||
cmd.NewSet(c.RemoveCommands()),
|
||||
cmd.NewSet(c.CreateCommands()),
|
||||
})
|
||||
}
|
||||
|
||||
func (c *Container) ConditionalStartCommands() cmd.CommandSet {
|
||||
return c.newCommandSet("CONDSTART", cmd.Commands{
|
||||
cmd.NewConditional("restart_if_was_running",
|
||||
func() bool { return wasRunning },
|
||||
c.wasRunning.ValueOrZero,
|
||||
cmd.NewSet(c.StartCommands()),
|
||||
cmd.NewNop(),
|
||||
),
|
||||
@ -367,7 +348,11 @@ func (c *Container) StopCommands() cmd.CommandSet {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = containers.Wait(c.conn, c.cdata.ID, &containers.WaitOptions{Condition: []define.ContainerStatus{define.ContainerStateExited}})
|
||||
_, err = containers.Wait(
|
||||
c.conn,
|
||||
c.cdata.ID,
|
||||
&containers.WaitOptions{Condition: []define.ContainerStatus{define.ContainerStateExited}},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
61
internal/pkg/container/ordering.go
Normal file
61
internal/pkg/container/ordering.go
Normal file
@ -0,0 +1,61 @@
|
||||
/*
|
||||
Copyright © 2022 Joel D. Elkins <joel@elkins.co>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
*/
|
||||
|
||||
package container
|
||||
|
||||
import "golang.org/x/exp/slices"
|
||||
|
||||
type operation bool
|
||||
|
||||
const (
|
||||
Start operation = true
|
||||
Stop operation = false
|
||||
)
|
||||
|
||||
func Reorder(conts []Container, op operation) {
|
||||
// null orderings go first for either start or stop, as they are executed asynchronously
|
||||
norm := func(a, b Container) bool {
|
||||
if !a.StartOrder.Valid {
|
||||
return true
|
||||
}
|
||||
if !b.StartOrder.Valid {
|
||||
return false
|
||||
}
|
||||
return a.StartOrder.ValueOrZero() < b.StartOrder.ValueOrZero()
|
||||
}
|
||||
rev := func(a, b Container) bool {
|
||||
if !a.StartOrder.Valid {
|
||||
return true
|
||||
}
|
||||
if !b.StartOrder.Valid {
|
||||
return false
|
||||
}
|
||||
return b.StartOrder.ValueOrZero() > a.StartOrder.ValueOrZero()
|
||||
}
|
||||
var sorter func(a, b Container) bool
|
||||
if op == Start {
|
||||
sorter = norm
|
||||
} else {
|
||||
sorter = rev
|
||||
}
|
||||
slices.SortFunc(conts, sorter)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user