mirror of
https://gitea.elkins.co/Networking/ccl.git
synced 2025-03-09 04:31:38 -05: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.`,
|
names or categories. Multiple arguments are supported.`,
|
||||||
Run: func(_ *cobra.Command, args []string) {
|
Run: func(_ *cobra.Command, args []string) {
|
||||||
conts := config.Union(args, contMask)
|
conts := config.Union(args, contMask)
|
||||||
|
container.Reorder(conts, container.Start)
|
||||||
execForEach(conts, func(c *container.Container) command.CommandSet { return c.CreateCommands() })
|
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) {
|
func execForEach(tgts []container.Container, getSet func(*container.Container) command.CommandSet) {
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
for i := range tgts {
|
for i := range tgts {
|
||||||
wg.Add(1)
|
async := !tgts[i].StartOrder.Valid
|
||||||
go func(cont *container.Container) {
|
runSet := func(cont *container.Container) {
|
||||||
|
if async {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
}
|
||||||
set := getSet(cont)
|
set := getSet(cont)
|
||||||
for _, cmd := range set.Commands {
|
for _, cmd := range set.Commands {
|
||||||
if err := cmd.Execute(output, fake, set.ID); err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}(&tgts[i])
|
}
|
||||||
|
if async {
|
||||||
|
wg.Add(1)
|
||||||
|
go runSet(&tgts[i])
|
||||||
|
} else {
|
||||||
|
runSet(&tgts[i])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,7 @@ affected: the old image will still remain, though untagged, and any defined cont
|
|||||||
will still use it.`,
|
will still use it.`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
conts := config.Union(args, contMask)
|
conts := config.Union(args, contMask)
|
||||||
|
container.Reorder(conts, container.Start)
|
||||||
execForEach(conts, func(c *container.Container) command.CommandSet { return c.PullCommands() })
|
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.`,
|
one or more container names or categories. If empty, "all" is assumed.`,
|
||||||
Run: func(_ *cobra.Command, args []string) {
|
Run: func(_ *cobra.Command, args []string) {
|
||||||
conts := config.Union(args, contMask)
|
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.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.`,
|
one or more container names or categories. If empty, "all" is assumed.`,
|
||||||
Run: func(_ *cobra.Command, args []string) {
|
Run: func(_ *cobra.Command, args []string) {
|
||||||
conts := config.Union(args, contMask)
|
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.`,
|
If running, they will first be stopped.`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
conts := config.Union(args, contMask)
|
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() })
|
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.`,
|
one or more container names or categories. If empty, "all" is assumed.`,
|
||||||
Run: func(_ *cobra.Command, args []string) {
|
Run: func(_ *cobra.Command, args []string) {
|
||||||
conts := config.Union(args, contMask)
|
conts := config.Union(args, contMask)
|
||||||
|
container.Reorder(conts, container.Start)
|
||||||
execForEach(conts, func(c *container.Container) command.CommandSet { return c.StartCommands() })
|
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.`,
|
one or more container names or categories. If empty, "all" is assumed.`,
|
||||||
Run: func(_ *cobra.Command, args []string) {
|
Run: func(_ *cobra.Command, args []string) {
|
||||||
conts := config.Union(args, contMask)
|
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.StopCommands() })
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,10 @@ var updateCmd = &cobra.Command{
|
|||||||
one or more container names or categories. If empty, "all" is assumed.`,
|
one or more container names or categories. If empty, "all" is assumed.`,
|
||||||
Run: func(_ *cobra.Command, args []string) {
|
Run: func(_ *cobra.Command, args []string) {
|
||||||
conts := config.Union(args, contMask)
|
conts := config.Union(args, contMask)
|
||||||
|
container.Reorder(conts, container.Stop)
|
||||||
execForEach(conts, func(c *container.Container) command.CommandSet { return c.UpdateCommands() })
|
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
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
THE SOFTWARE.
|
THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package container
|
package container
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@ -56,9 +57,11 @@ type Container struct {
|
|||||||
ExposeUdp []uint16 `toml:"expose_udp,omitempty"`
|
ExposeUdp []uint16 `toml:"expose_udp,omitempty"`
|
||||||
PortsTcp map[uint16]uint16 `toml:"ports,omitempty"`
|
PortsTcp map[uint16]uint16 `toml:"ports,omitempty"`
|
||||||
NetNS string `toml:"netns,omitempty"`
|
NetNS string `toml:"netns,omitempty"`
|
||||||
|
StartOrder null.Int `toml:"start_order,omitempty"`
|
||||||
|
|
||||||
conn context.Context
|
conn context.Context
|
||||||
cdata *define.InspectContainerData
|
cdata *define.InspectContainerData
|
||||||
|
wasRunning null.Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) Init(conn context.Context, nets []network.Network) error {
|
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)
|
return fmt.Errorf("conn is nil: %s", c.Name)
|
||||||
}
|
}
|
||||||
c.conn = conn
|
c.conn = conn
|
||||||
return c.populateCData()
|
err := c.populateCData()
|
||||||
|
c.wasRunning.SetValid(c.IsRunning())
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) LogEntry() *log.Entry {
|
func (c *Container) LogEntry() *log.Entry {
|
||||||
@ -244,32 +249,14 @@ func (c *Container) CreateCommands() cmd.CommandSet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) RecreateCommands() cmd.CommandSet {
|
func (c *Container) RecreateCommands() cmd.CommandSet {
|
||||||
wasRunning := false
|
|
||||||
return c.newCommandSet("RECREATE", cmd.Commands{
|
return c.newCommandSet("RECREATE", cmd.Commands{
|
||||||
cmd.NewFunc("stash_run_state", func() error {
|
cmd.NewSet(c.RemoveCommands()),
|
||||||
wasRunning = c.IsRunning()
|
|
||||||
return nil
|
|
||||||
}),
|
|
||||||
cmd.NewSet(c.StopCommands()),
|
|
||||||
cmd.NewSet(c.removeCommands()),
|
|
||||||
cmd.NewSet(c.CreateCommands()),
|
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.
|
// 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{
|
return c.newCommandSet("remove", cmd.Commands{
|
||||||
cmd.NewFunc("remove_if_exists", func() error {
|
cmd.NewFunc("remove_if_exists", func() error {
|
||||||
if c.cdata.ID == "" {
|
if c.cdata.ID == "" {
|
||||||
@ -293,7 +280,11 @@ func (c *Container) StartCommands() cmd.CommandSet {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
if err != nil {
|
||||||
return err
|
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 {
|
func (c *Container) IsRunning() bool {
|
||||||
if c.cdata != nil && c.cdata.State != nil {
|
if c.cdata != nil && c.cdata.State != nil {
|
||||||
return c.cdata.State.Running
|
return c.cdata.State.Running
|
||||||
@ -334,21 +318,18 @@ func (c *Container) IsCreated() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Container) UpdateCommands() cmd.CommandSet {
|
func (c *Container) UpdateCommands() cmd.CommandSet {
|
||||||
wasRunning := false
|
|
||||||
return c.newCommandSet("UPDATE", cmd.Commands{
|
return c.newCommandSet("UPDATE", cmd.Commands{
|
||||||
cmd.NewFunc("pull_image", func() error {
|
cmd.NewSet(c.PullCommands()),
|
||||||
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.StopCommands()),
|
cmd.NewSet(c.StopCommands()),
|
||||||
cmd.NewSet(c.removeCommands()),
|
cmd.NewSet(c.RemoveCommands()),
|
||||||
cmd.NewSet(c.CreateCommands()),
|
cmd.NewSet(c.CreateCommands()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Container) ConditionalStartCommands() cmd.CommandSet {
|
||||||
|
return c.newCommandSet("CONDSTART", cmd.Commands{
|
||||||
cmd.NewConditional("restart_if_was_running",
|
cmd.NewConditional("restart_if_was_running",
|
||||||
func() bool { return wasRunning },
|
c.wasRunning.ValueOrZero,
|
||||||
cmd.NewSet(c.StartCommands()),
|
cmd.NewSet(c.StartCommands()),
|
||||||
cmd.NewNop(),
|
cmd.NewNop(),
|
||||||
),
|
),
|
||||||
@ -367,7 +348,11 @@ func (c *Container) StopCommands() cmd.CommandSet {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
if err != nil {
|
||||||
return err
|
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