Huge amount of linting

Linting
This commit is contained in:
Joel Elkins 2022-08-17 02:24:48 -05:00
parent 9c27d0e6f5
commit b91eb62c34
No known key found for this signature in database
GPG Key ID: 133589DC38921AE2
14 changed files with 214 additions and 102 deletions

View File

@ -38,7 +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)
execForEach(conts, func(c *container.Container) command.CommandSet { return c.CreateCommands() }, 0) execForEach(conts, func(c *container.Container) command.Set { return c.CreateCommands() }, 0)
}, },
} }

View File

@ -34,12 +34,12 @@ import (
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
) )
func execForEach(tgts []container.Container, getSet func(*container.Container) command.CommandSet, groupScale int) { func execForEach(tgts []*container.Container, getSet func(*container.Container) command.Set, groupScale int) {
runLevel := make(map[int][]*container.Container) runLevel := make(map[int][]*container.Container)
for i := range tgts { for i := range tgts {
rl := tgts[i].StartGroup * groupScale rl := tgts[i].StartGroup * groupScale
runLevel[rl] = append(runLevel[rl], &tgts[i]) runLevel[rl] = append(runLevel[rl], tgts[i])
} }
rls := maps.Keys(runLevel) rls := maps.Keys(runLevel)
@ -58,8 +58,10 @@ func execForEach(tgts []container.Container, getSet func(*container.Container) c
wg := new(sync.WaitGroup) wg := new(sync.WaitGroup)
for i := range cs { for i := range cs {
wg.Add(1) wg.Add(1)
go func(cont *container.Container, set command.CommandSet) { cont := cs[i]
go func() {
defer wg.Done() defer wg.Done()
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 {
cont.LogEntry().WithFields(log.Fields{ cont.LogEntry().WithFields(log.Fields{
@ -69,7 +71,7 @@ func execForEach(tgts []container.Container, getSet func(*container.Container) c
return return
} }
} }
}(cs[i], getSet(cs[i])) }()
} }
wg.Wait() wg.Wait()
if r != 0 { if r != 0 {

View File

@ -39,7 +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)
execForEach(conts, func(c *container.Container) command.CommandSet { return c.PullCommands() }, 0) execForEach(conts, func(c *container.Container) command.Set { return c.PullCommands() }, 0)
}, },
} }

View File

@ -38,9 +38,9 @@ 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)
execForEach(conts, func(c *container.Container) command.CommandSet { return c.StopCommands() }, -1) execForEach(conts, func(c *container.Container) command.Set { return c.StopCommands() }, -1)
execForEach(conts, func(c *container.Container) command.CommandSet { return c.RecreateCommands() }, 0) execForEach(conts, func(c *container.Container) command.Set { return c.RecreateCommands() }, 0)
execForEach(conts, func(c *container.Container) command.CommandSet { return c.ConditionalStartCommands() }, 1) execForEach(conts, func(c *container.Container) command.Set { return c.ConditionalStartCommands() }, 1)
}, },
} }

View File

@ -38,8 +38,8 @@ 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.StopCommands() }, -1) execForEach(conts, func(c *container.Container) command.Set { return c.StopCommands() }, -1)
execForEach(conts, func(c *container.Container) command.CommandSet { return c.StartCommands() }, 1) execForEach(conts, func(c *container.Container) command.Set { return c.StartCommands() }, 1)
}, },
} }

View File

@ -39,8 +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)
execForEach(conts, func(c *container.Container) command.CommandSet { return c.StopCommands() }, -1) execForEach(conts, func(c *container.Container) command.Set { return c.StopCommands() }, -1)
execForEach(conts, func(c *container.Container) command.CommandSet { return c.RemoveCommands() }, 0) execForEach(conts, func(c *container.Container) command.Set { return c.RemoveCommands() }, 0)
}, },
} }

View File

@ -38,7 +38,7 @@ import (
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Use: "ccl", Use: "ccl",
Short: "Manage a set of pre-configured of podman containers", Short: "Manage a set of pre-configured of podman containers",
Version: Version, Version: version,
Long: `ccl is a utility to manage a set of podman containers. Use with various subcommands Long: `ccl is a utility to manage a set of podman containers. Use with various subcommands
to define, start, stop, or update the container images. Configuration is read to define, start, stop, or update the container images. Configuration is read
from a toml configuration file, and the utility uses this information to from a toml configuration file, and the utility uses this information to
@ -52,9 +52,9 @@ execute the necessary podman commands.`,
requireConn := []string{"create", "pull", "recreate", "restart", "rm", "start", "stop", "update"} requireConn := []string{"create", "pull", "recreate", "restart", "rm", "start", "stop", "update"}
if slices.Contains(requireConn, cmd.Name()) { if slices.Contains(requireConn, cmd.Name()) {
// connect to podman // connect to podman
ConnectMust() connectMust()
} else { } else {
if err := Connect(); err != nil { if err := connect(); err != nil {
log.WithField("error", err).Warnln("Connection failed") log.WithField("error", err).Warnln("Connection failed")
} }
} }
@ -84,14 +84,15 @@ func Execute() {
} }
} }
func Connect() error { func connect() error {
var err error var err error
conn, err = bindings.NewConnection(context.WithValue(context.Background(), "output", output), socket) key := struct{ string }{"output"}
conn, err = bindings.NewConnection(context.WithValue(context.Background(), key, output), socket)
return err return err
} }
func ConnectMust() { func connectMust() {
if err := Connect(); err != nil { if err := connect(); err != nil {
log.WithField("error", err).Errorf("Could not connect") log.WithField("error", err).Errorf("Could not connect")
os.Exit(1) os.Exit(1)
} }
@ -105,7 +106,7 @@ func init() {
output = io.Discard output = io.Discard
} }
}) })
rootCmd.PersistentFlags().StringVarP(&config.ConfigFile, "config", "c", config.CONFIG_FILE_DEFAULT, "pathname of config file") rootCmd.PersistentFlags().StringVarP(&config.ConfigFile, "config", "c", config.ConfigFileDefault, "pathname of config file")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "show additional info from command execution") rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "show additional info from command execution")
rootCmd.PersistentFlags().BoolVarP(&fake, "no-action", "n", false, "do not actually execute commands") rootCmd.PersistentFlags().BoolVarP(&fake, "no-action", "n", false, "do not actually execute commands")
rootCmd.PersistentFlags().BoolVarP(&incDisabled, "include-disabled", "a", false, "include category=. containers in actions") rootCmd.PersistentFlags().BoolVarP(&incDisabled, "include-disabled", "a", false, "include category=. containers in actions")

View File

@ -36,9 +36,9 @@ var startCmd = &cobra.Command{
ValidArgsFunction: validNouns, ValidArgsFunction: validNouns,
Long: `Start configured containers. They must be created first. Arguments can be Long: `Start configured containers. They must be created first. Arguments can be
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.StartCommands() }, 1) execForEach(conts, func(c *container.Container) command.Set { return c.StartCommands() }, 1)
}, },
} }

View File

@ -38,7 +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)
execForEach(conts, func(c *container.Container) command.CommandSet { return c.StopCommands() }, -1) execForEach(conts, func(c *container.Container) command.Set { return c.StopCommands() }, -1)
}, },
} }

View File

@ -38,8 +38,8 @@ 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)
execForEach(conts, func(c *container.Container) command.CommandSet { return c.UpdateCommands() }, -1) execForEach(conts, func(c *container.Container) command.Set { return c.UpdateCommands() }, -1)
execForEach(conts, func(c *container.Container) command.CommandSet { return c.ConditionalStartCommands() }, 1) execForEach(conts, func(c *container.Container) command.Set { return c.ConditionalStartCommands() }, 1)
}, },
} }

View File

@ -35,14 +35,14 @@ var versionCmd = &cobra.Command{
Short: "Show version info", Short: "Show version info",
Long: `Output the ccl binary's version`, Long: `Output the ccl binary's version`,
Run: func(cmd *cobra.Command, _ []string) { Run: func(cmd *cobra.Command, _ []string) {
fmt.Println("ccl version", Version) fmt.Println("ccl version", version)
}, },
} }
var ( var (
//go:embed version.txt //go:embed version.txt
version string rawVersion string
Version string = strings.TrimSpace(version) version = strings.TrimSpace(rawVersion)
) )
func init() { func init() {

View File

@ -1,4 +1,7 @@
/* /*
Package command is used to manage the container management instructions required to carry out
tasks desired by the user, such as "create"
Copyright © 2022 Joel D. Elkins <joel@elkins.co> Copyright © 2022 Joel D. Elkins <joel@elkins.co>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
@ -27,46 +30,64 @@ import (
"os/exec" "os/exec"
) )
type CommandType int // Type is one of the Ct... constants, indicating the underlying method
// for executing a command.
type cType int
// Types of commands:
// - CtNop: No operation; an empty placeholder
// - CtShell: A shell command -- string to be fed to /bin/sh -c
// - CtFunc: A go function of type `func() error`
// - CtIndirect: A reference to another command. When executed, the underlying command is executed.
// - CtSet: An ordered list of commands
// - CtDebug: A debug message is printed upon execution, but does no other action
// - CtConditional: A function and two commands are provided. When evaluated,
// the function, having signature `func() bool`, is executed. If the result is
// true, then the first command is executed, and if false the second one.
const ( const (
CT_NOP CommandType = iota ctNop cType = iota
CT_SHELL ctShell
CT_FUNC ctFunc
CT_INDIRECT ctIndirect
CT_SET ctSet
CT_DEBUG ctDebug
CT_CONDITIONAL ctConditional
) )
func (ct CommandType) String() string { func (ct cType) String() string {
switch ct { switch ct {
case CT_NOP: case ctNop:
return "NOP" return "NOP"
case CT_SHELL: case ctShell:
return "SHELL" return "SHELL"
case CT_FUNC: case ctFunc:
return "FUNC" return "FUNC"
case CT_INDIRECT: case ctIndirect:
return "INDIRECT" return "INDIRECT"
case CT_SET: case ctSet:
return "SET" return "SET"
case CT_DEBUG: case ctDebug:
return "DEBUG" return "DEBUG"
case CT_CONDITIONAL: case ctConditional:
return "CONDITIONAL" return "CONDITIONAL"
default: default:
return "UNKOWN" return "UNKOWN"
} }
} }
// Command combines a Type with an `interface{}`, representing a task (or a set
// of tasks, in the case of CtSet) to be executed. Create a Command with one of
// the `NewXxxCommand` functions.
type Command struct { type Command struct {
Type CommandType Type cType
Command interface{} Command interface{}
} }
// Commands reflects an ordered grouping of `Command`s
type Commands []Command type Commands []Command
type CommandSet struct{ // Set bundes a `Commands` object with an arbitrary ID for verbose output
type Set struct {
ID string ID string
Commands Commands
} }
@ -83,32 +104,46 @@ type conditional struct {
ElseCmd Command ElseCmd Command
} }
// NewShell creates a new shell-type command from a string. Upon execution, the
// string will be fed to `/bin/sh -c`
func NewShell(cmd string) Command { func NewShell(cmd string) Command {
return Command{CT_SHELL, cmd} return Command{ctShell, cmd}
} }
// NewFunc creates a command consisting of go function of type `func() error`
func NewFunc(name string, f func() error) Command { func NewFunc(name string, f func() error) Command {
return Command{CT_FUNC, errFunc{name, f}} return Command{ctFunc, errFunc{name, f}}
} }
// NewIndirect creates a reference to another command. When executed, the underlying
// command is executed.
func NewIndirect(c Command) Command { func NewIndirect(c Command) Command {
return Command{CT_INDIRECT, c} return Command{ctIndirect, c}
} }
func NewSet(cs CommandSet) Command { // NewSet creates a single command containing an ordered list of commands. These
return Command{CT_SET, cs.Commands} // commands will be executed in order with this set-type command is executed.
func NewSet(cs Set) Command {
return Command{ctSet, cs.Commands}
} }
// NewDebug returns a command that prints a debug message upon execution, but
// does no other action
func NewDebug(msg string) Command { func NewDebug(msg string) Command {
return Command{CT_DEBUG, msg} return Command{ctDebug, msg}
} }
// NewNop returns a placeholder command. When executed, nothing is done other
// than to note that it was encountered in the output.
func NewNop() Command { func NewNop() Command {
return Command{CT_NOP, nil} return Command{ctNop, nil}
} }
// NewConditional takes a function and two commands as arguments. When evaluated,
// the function, having signature `func() bool`, is executed. If the result is
// true, then the first command is executed, and if false the second one.
func NewConditional(name string, ifPart func() bool, thenPart Command, elsePart Command) Command { func NewConditional(name string, ifPart func() bool, thenPart Command, elsePart Command) Command {
return Command{CT_CONDITIONAL, conditional{ return Command{ctConditional, conditional{
Name: name, Name: name,
Condition: ifPart, Condition: ifPart,
ThenCmd: thenPart, ThenCmd: thenPart,
@ -116,7 +151,9 @@ func NewConditional(name string, ifPart func() bool, thenPart Command, elsePart
}} }}
} }
func (cmds CommandSet) Execute(output io.Writer, fake bool) error { // Execute the Set with the privileges of the user running the process.
// Warning: in the case of a shell command, this could do anything.
func (cmds Set) Execute(output io.Writer, fake bool) error {
for _, c := range cmds.Commands { for _, c := range cmds.Commands {
if err := c.Execute(output, fake, cmds.ID); err != nil { if err := c.Execute(output, fake, cmds.ID); err != nil {
return err return err
@ -125,11 +162,13 @@ func (cmds CommandSet) Execute(output io.Writer, fake bool) error {
return nil return nil
} }
// Execute the Command with the privileges of the user running the process.
// Warning: in the case of a shell command, this could do anything.
func (c Command) Execute(output io.Writer, fake bool, commandSetID string) error { func (c Command) Execute(output io.Writer, fake bool, commandSetID string) error {
switch c.Type { switch c.Type {
case CT_NOP: case ctNop:
fmt.Fprintf(output, "%s: %s\n", commandSetID, c.Type) fmt.Fprintf(output, "%s: %s\n", commandSetID, c.Type)
case CT_SHELL: case ctShell:
cmd := c.Command.(string) cmd := c.Command.(string)
fmt.Fprintf(output, "%s: %s sh -c \"%s\"\n", commandSetID, c.Type, cmd) fmt.Fprintf(output, "%s: %s sh -c \"%s\"\n", commandSetID, c.Type, cmd)
if !fake { if !fake {
@ -137,7 +176,7 @@ func (c Command) Execute(output io.Writer, fake bool, commandSetID string) error
fmt.Fprint(output, string(out)) fmt.Fprint(output, string(out))
return err return err
} }
case CT_FUNC: case ctFunc:
ef := c.Command.(errFunc) ef := c.Command.(errFunc)
fmt.Fprintf(output, "%s: %s %s\n", commandSetID, c.Type, ef.Name) fmt.Fprintf(output, "%s: %s %s\n", commandSetID, c.Type, ef.Name)
if !fake { if !fake {
@ -146,10 +185,10 @@ func (c Command) Execute(output io.Writer, fake bool, commandSetID string) error
} }
return nil return nil
} }
case CT_INDIRECT: case ctIndirect:
ct := c.Command.(Command) ct := c.Command.(Command)
return ct.Execute(output, fake, commandSetID) return ct.Execute(output, fake, commandSetID)
case CT_SET: case ctSet:
cs := c.Command.(Commands) cs := c.Command.(Commands)
for i := range cs { for i := range cs {
err := cs[i].Execute(output, fake, commandSetID) err := cs[i].Execute(output, fake, commandSetID)
@ -157,7 +196,7 @@ func (c Command) Execute(output io.Writer, fake bool, commandSetID string) error
return err return err
} }
} }
case CT_CONDITIONAL: case ctConditional:
cond := c.Command.(conditional) cond := c.Command.(conditional)
if fake { if fake {
// in a fake setting, we don't know which branch will be followed, // in a fake setting, we don't know which branch will be followed,
@ -173,11 +212,10 @@ func (c Command) Execute(output io.Writer, fake bool, commandSetID string) error
fmt.Fprintf(output, "%s: %s %s: %v\n", commandSetID, c.Type, cond.Name, branch) fmt.Fprintf(output, "%s: %s %s: %v\n", commandSetID, c.Type, cond.Name, branch)
if branch { if branch {
return cond.ThenCmd.Execute(output, fake, commandSetID) return cond.ThenCmd.Execute(output, fake, commandSetID)
} else {
return cond.ElseCmd.Execute(output, fake, commandSetID)
} }
return cond.ElseCmd.Execute(output, fake, commandSetID)
} }
case CT_DEBUG: case ctDebug:
msg := c.Command.(string) msg := c.Command.(string)
fmt.Fprintf(output, "%s: %s %s\n", commandSetID, c.Type, msg) fmt.Fprintf(output, "%s: %s %s\n", commandSetID, c.Type, msg)
} }

View File

@ -1,3 +1,27 @@
/*
Package config is used manage initialize and house the main program data
structures, as well as marshalling the configuration to and from toml.
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 config package config
import ( import (
@ -18,15 +42,17 @@ import (
) )
const ( const (
CONFIG_FILE_DEFAULT = "/etc/ccl.toml" ConfigFileDefault = "/etc/ccl.toml" // ConfigFileDefault is default configuration file path
) )
var ( var (
ConfigFile = CONFIG_FILE_DEFAULT ConfigFile = ConfigFileDefault // ConfigFile is the path to the configuration file to use in a particular invocation.
Networks = []network.Network{} Networks = []*network.Network{} // Networks is the program-global set of congiured network.Networks structures
Containers = []container.Container{} Containers = []*container.Container{} // Containers is the program-global set of configured container.Container structures.
) )
// Categories returns a slice of all of the distinct categories found in the
// configured containers.
func Categories() []string { func Categories() []string {
categories := []string{"all"} categories := []string{"all"}
gs := hashset.New() gs := hashset.New()
@ -40,7 +66,12 @@ func Categories() []string {
return categories return categories
} }
func Union(ids []string, catMask []string) (conts []container.Container) { // Union - given a list of identifiers ("all", a category, or a container
// name), as well as a category mask, will yieLd a list of configured
// containers that match the identifiers but not the mask. As envisioned, the
// default mask would be ".", i.e., the tool would normally exclude containers
// "disabled" by setting their category to a singLe period (".")
func Union(ids []string, catMask []string) (conts []*container.Container) {
if len(ids) == 0 { if len(ids) == 0 {
ids = []string{"all"} ids = []string{"all"}
} }
@ -60,7 +91,7 @@ func Union(ids []string, catMask []string) (conts []container.Container) {
} }
for _, c := range h.Values() { for _, c := range h.Values() {
name := c.(string) name := c.(string)
match := slices.IndexFunc(Containers, func(c container.Container) bool { return c.Name == name }) match := slices.IndexFunc(Containers, func(c *container.Container) bool { return c.Name == name })
conts = append(conts, Containers[match]) conts = append(conts, Containers[match])
} }
if len(conts) == 0 { if len(conts) == 0 {
@ -69,7 +100,7 @@ func Union(ids []string, catMask []string) (conts []container.Container) {
"catMask": catMask, "catMask": catMask,
}).Warnln("No matching containers. If disabled, try adding -a") }).Warnln("No matching containers. If disabled, try adding -a")
} }
slices.SortFunc(conts, func(a, b container.Container) bool { slices.SortFunc(conts, func(a, b *container.Container) bool {
if a.Category < b.Category { if a.Category < b.Category {
return true return true
} }
@ -81,8 +112,9 @@ func Union(ids []string, catMask []string) (conts []container.Container) {
return return
} }
func UnionNetworks(ids []string) []network.Network { // UnionNetworks is like Union but for Networks, and also no mask.
nets := make([]network.Network, len(Networks)) func UnionNetworks(ids []string) []*network.Network {
nets := make([]*network.Network, len(Networks))
rejects := []int{} rejects := []int{}
copy(nets, Networks) copy(nets, Networks)
for i := range nets { for i := range nets {
@ -101,11 +133,14 @@ func UnionNetworks(ids []string) []network.Network {
return nets return nets
} }
// Init will parse the configuration file, create and populate the in-memory
// data structures. Will call container.Init() on each container created in
// this way.
func Init(conn context.Context) error { func Init(conn context.Context) error {
// A parsing convenience // A parsing convenience
type parse struct { type parse struct {
Networks []network.Network Networks []*network.Network
Containers []container.Container Containers []*container.Container
} }
f, err := os.ReadFile(ConfigFile) f, err := os.ReadFile(ConfigFile)
@ -121,22 +156,29 @@ func Init(conn context.Context) error {
for i := range Containers { for i := range Containers {
Containers[i].Init(conn, Networks) Containers[i].Init(conn, Networks)
} }
slices.SortFunc(Containers, func(a, b container.Container) bool { slices.SortFunc(Containers, func(a, b *container.Container) bool {
return a.Name < b.Name return a.Name < b.Name
}) })
return nil return nil
} }
// Show will output a toml configuration of the provided identifiers ("all",
// category, or container name). The resulting string would presumably be sent
// to the terminal or a file.
//
// Other than ordering of fields and possible inclusion of some fields with
// their default values, the generated toml should be completely compatible
// with the actual configuration. Dogfood safe.
func Show(ids []string, contMask []string) string { func Show(ids []string, contMask []string) string {
type parse struct { type parse struct {
Containers []container.Container `toml:"containers,omitempty"` Containers []*container.Container `toml:"containers,omitempty"`
Networks []network.Network `toml:"networks,omitempty"` Networks []*network.Network `toml:"networks,omitempty"`
} }
getNet := func(name string) *network.Network { getNet := func(name string) *network.Network {
for i := range Networks { for i := range Networks {
if Networks[i].Name == name { if Networks[i].Name == name {
return &Networks[i] return Networks[i]
} }
} }
return nil return nil

View File

@ -1,4 +1,7 @@
/* /*
Package container encapuslates both the metadata structure and main
operations to be presented to the user in the `cmd` module.
Copyright © 2022 Joel D. Elkins <joel@elkins.co> Copyright © 2022 Joel D. Elkins <joel@elkins.co>
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
@ -19,7 +22,6 @@ 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 (
@ -41,6 +43,9 @@ import (
"gopkg.in/guregu/null.v4" "gopkg.in/guregu/null.v4"
) )
// Container houses the metadata that may be specified by this utility when
// creating a container. A couple of fields (Name and Image) are mandatory to
// specify, but the rest will use the libpod or otherwise sensible defaults.
type Container struct { type Container struct {
Category string `toml:"category"` Category string `toml:"category"`
Name string `toml:"name"` Name string `toml:"name"`
@ -54,9 +59,9 @@ type Container struct {
Restart string `toml:"restart,omitempty"` Restart string `toml:"restart,omitempty"`
Umask null.Int `toml:"umask,omitempty"` Umask null.Int `toml:"umask,omitempty"`
User string `toml:"user,omitempty"` User string `toml:"user,omitempty"`
ExposeTcp []uint16 `toml:"expose_tcp,omitempty"` ExposeTCP []uint16 `toml:"expose_tcp,omitempty"`
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"`
StartGroup int `toml:"group,omitempty"` StartGroup int `toml:"group,omitempty"`
@ -66,13 +71,15 @@ type Container struct {
wasRunning bool wasRunning bool
} }
func (c *Container) Init(conn context.Context, nets []network.Network) error { // Init will initialize a new container structure by filling in network details
// and by querying other metadata from libpod, if possible.
func (c *Container) Init(conn context.Context, nets []*network.Network) error {
// initialize user-provided definitions // initialize user-provided definitions
for i := range c.Networks { for i := range c.Networks {
var n *network.Network var n *network.Network
for j := range nets { for j := range nets {
if nets[j].Name == c.Networks[i].Name { if nets[j].Name == c.Networks[i].Name {
n = &nets[j] n = nets[j]
} }
} }
if n == nil { if n == nil {
@ -115,9 +122,12 @@ func (c *Container) Init(conn context.Context, nets []network.Network) error {
return err return err
} }
// LogEntry will return a *logrus.LogEntry, with some basic fields populated
// for this container. The idea is that the calling code would add other fields
// (optionally) and do something with the error.
func (c *Container) LogEntry() *log.Entry { func (c *Container) LogEntry() *log.Entry {
f := log.Fields{ f := log.Fields{
"container": c.Name, "container": c.Name,
"wasRunning": c.wasRunning, "wasRunning": c.wasRunning,
} }
if c.cdata != nil && c.cdata.ID != "" { if c.cdata != nil && c.cdata.ID != "" {
@ -134,14 +144,16 @@ func (c *Container) pull() error {
return err return err
} }
func (c *Container) newCommandSet(op string, cmds cmd.Commands) cmd.CommandSet { func (c *Container) newCommandSet(op string, cmds cmd.Commands) cmd.Set {
return cmd.CommandSet{ return cmd.Set{
ID: fmt.Sprintf("%s-%s", op, c.Name), ID: fmt.Sprintf("%s-%s", op, c.Name),
Commands: cmds, Commands: cmds,
} }
} }
func (c *Container) PullCommands() cmd.CommandSet { // PullCommands will return a cmd.Set to pull the specified image using the
// libpod API
func (c *Container) PullCommands() cmd.Set {
return c.newCommandSet("PULL", cmd.Commands{ return c.newCommandSet("PULL", cmd.Commands{
cmd.NewFunc("do_pull", func() error { cmd.NewFunc("do_pull", func() error {
return c.pull() return c.pull()
@ -149,7 +161,9 @@ func (c *Container) PullCommands() cmd.CommandSet {
}) })
} }
func (c *Container) CreateCommands() cmd.CommandSet { // CreateCommands returns a cmd.Set that will create a container from the
// configured metadata. The container should not exist.
func (c *Container) CreateCommands() cmd.Set {
if c.Image == "" { if c.Image == "" {
return c.newCommandSet("CREATE", cmd.Commands{ return c.newCommandSet("CREATE", cmd.Commands{
cmd.NewFunc("image_error", func() error { cmd.NewFunc("image_error", func() error {
@ -179,15 +193,15 @@ func (c *Container) CreateCommands() cmd.CommandSet {
} }
expose := map[uint16]string{} expose := map[uint16]string{}
for _, p := range c.ExposeTcp { for _, p := range c.ExposeTCP {
expose[p] = "tcp" expose[p] = "tcp"
} }
for _, p := range c.ExposeUdp { for _, p := range c.ExposeUDP {
expose[p] = "udp" expose[p] = "udp"
} }
portMappings := []types.PortMapping{} portMappings := []types.PortMapping{}
for ph, pc := range c.PortsTcp { for ph, pc := range c.PortsTCP {
portMappings = append(portMappings, types.PortMapping{ portMappings = append(portMappings, types.PortMapping{
HostPort: ph, HostPort: ph,
ContainerPort: pc, ContainerPort: pc,
@ -256,15 +270,17 @@ func (c *Container) CreateCommands() cmd.CommandSet {
}) })
} }
func (c *Container) RecreateCommands() cmd.CommandSet { // RecreateCommands will stop (if running), remove (if exists), (re)create, and restart (if
// it was initially running) a container. The image is not pulled.
func (c *Container) RecreateCommands() cmd.Set {
return c.newCommandSet("RECREATE", cmd.Commands{ return c.newCommandSet("RECREATE", cmd.Commands{
cmd.NewSet(c.RemoveCommands()), cmd.NewSet(c.RemoveCommands()),
cmd.NewSet(c.CreateCommands()), cmd.NewSet(c.CreateCommands()),
}) })
} }
// unexported version just removes the container without attempting a stop. // RemoveCommands removes a container (as if by `podman rm -f`)
func (c *Container) RemoveCommands() cmd.CommandSet { func (c *Container) RemoveCommands() cmd.Set {
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 {
c.cdataLock.Lock() c.cdataLock.Lock()
@ -281,7 +297,8 @@ func (c *Container) RemoveCommands() cmd.CommandSet {
}) })
} }
func (c *Container) StartCommands() cmd.CommandSet { // StartCommands will start a container if it's not already running.
func (c *Container) StartCommands() cmd.Set {
return c.newCommandSet("START", cmd.Commands{ return c.newCommandSet("START", cmd.Commands{
cmd.NewFunc("start_container", func() error { cmd.NewFunc("start_container", func() error {
c.cdataLock.Lock() c.cdataLock.Lock()
@ -318,6 +335,8 @@ func (c *Container) StartCommands() cmd.CommandSet {
}) })
} }
// IsRunning returns true if libpod reports the container status is running, or
// false otherwise. If an error happens, the default value is false.
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
@ -325,6 +344,7 @@ func (c *Container) IsRunning() bool {
return false return false
} }
// IsCreated tests whether libpod sees the container as being created (running or not)
func (c *Container) IsCreated() bool { func (c *Container) IsCreated() bool {
if c.cdata == nil || c.cdata.ID == "" { if c.cdata == nil || c.cdata.ID == "" {
return false return false
@ -332,7 +352,9 @@ func (c *Container) IsCreated() bool {
return true return true
} }
func (c *Container) UpdateCommands() cmd.CommandSet { // UpdateCommands will pull the image (to force updates) and then recreate the
// container. It will be stopped first.
func (c *Container) UpdateCommands() cmd.Set {
return c.newCommandSet("UPDATE", cmd.Commands{ return c.newCommandSet("UPDATE", cmd.Commands{
cmd.NewSet(c.PullCommands()), cmd.NewSet(c.PullCommands()),
cmd.NewSet(c.StopCommands()), cmd.NewSet(c.StopCommands()),
@ -341,7 +363,10 @@ func (c *Container) UpdateCommands() cmd.CommandSet {
}) })
} }
func (c *Container) ConditionalStartCommands() cmd.CommandSet { // ConditionalStartCommands - several of the other command sets would leave the
// container in the stopped state. This set will restart a container if it was
// running when this container was first initialized.
func (c *Container) ConditionalStartCommands() cmd.Set {
if c.wasRunning { if c.wasRunning {
return c.StartCommands() return c.StartCommands()
} }
@ -350,7 +375,9 @@ func (c *Container) ConditionalStartCommands() cmd.CommandSet {
}) })
} }
func (c *Container) StopCommands() cmd.CommandSet { // StopCommands will stop a container if it is running, defining a 10 second
// timeout before the processes are killed by lippod
func (c *Container) StopCommands() cmd.Set {
return c.newCommandSet("STOP", cmd.Commands{ return c.newCommandSet("STOP", cmd.Commands{
cmd.NewFunc("stop_if_running", func() error { cmd.NewFunc("stop_if_running", func() error {
c.cdataLock.Lock() c.cdataLock.Lock()
@ -385,6 +412,8 @@ func (c *Container) populateCData() error {
return err return err
} }
// Pid will return the host process id of the main container process (pid
// 1 inside the container)
func (c *Container) Pid() int { func (c *Container) Pid() int {
if c.cdata != nil && c.cdata.State != nil { if c.cdata != nil && c.cdata.State != nil {
return c.cdata.State.Pid return c.cdata.State.Pid
@ -394,7 +423,7 @@ func (c *Container) Pid() int {
func (c *Container) assureNetNS() error { func (c *Container) assureNetNS() error {
if nil == c.cdata || nil == c.cdata.NetworkSettings { if nil == c.cdata || nil == c.cdata.NetworkSettings {
return fmt.Errorf("Network namespace not available!") return fmt.Errorf("network namespace not available")
} }
netns := c.cdata.NetworkSettings.SandboxKey netns := c.cdata.NetworkSettings.SandboxKey
if err := exec.Command("rm", "-f", "/var/run/netns/"+c.Name).Run(); err != nil { if err := exec.Command("rm", "-f", "/var/run/netns/"+c.Name).Run(); err != nil {