Convert project structure to cobra; many other changes. WIP.

- Factor out various components to different packages
- Add abstract command package to allow for calling go funcs etc. at
  runtime.
This commit is contained in:
Joel Elkins 2022-07-18 10:10:24 -05:00
parent 598ed0d427
commit 61b7fe55a0
15 changed files with 801 additions and 237 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
ccl

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
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.

View File

@ -1,237 +0,0 @@
package main
import (
"fmt"
"log"
"os"
"strings"
"github.com/emirpasic/gods/sets/hashset"
toml "github.com/pelletier/go-toml"
"golang.org/x/exp/slices"
)
const (
CONFIG_FILE = "/etc/containerdefs.toml"
)
type Network struct {
Name string
DNS []string
IPv6 bool `default:"true"`
IPv4Address string `toml:"ipv4_address"`
IPv6Address string `toml:"ipv6_address"`
}
type Container struct {
Category string
Name string
Image string
Hostname string
Command string
Arguments string
Networks []Network
createCommands []string
upCommands []string
}
type command string
const (
CREATE command = "create"
START command = "start"
)
var (
networks *[]Network
containers *[]Container
categories *[]string
commands = map[command]func(*Container){
CREATE: PrintCreate,
START: PrintStart,
}
)
// A parsing convenience
type parse struct {
Networks []Network
Containers []Container
}
func Categories() []string {
if categories != nil {
return *categories
}
categories = &[]string{"all"}
gs := hashset.New()
for _, c := range *containers {
gs.Add(c.Category)
}
for _, c := range gs.Values() {
*categories = append(*categories, c.(string))
}
return *categories
}
func Union(ids []string) (conts []Container) {
h := hashset.New()
for _, id := range ids {
for _, c := range *containers {
if id == "all" || c.Name == id || c.Category == id {
h.Add(c.Name)
}
}
}
for _, c := range h.Values() {
name := c.(string)
match := slices.IndexFunc(*containers, func(c Container) bool { return c.Name == name })
conts = append(conts, (*containers)[match])
}
return
}
func Init(configFile string) {
f, err := os.ReadFile(configFile)
if err != nil {
return
}
p := parse{}
err = toml.Unmarshal(f, &p)
if err != nil {
return
}
containers, networks = &p.Containers, &p.Networks
for i, c := range p.Containers {
p.Containers[i].createCommands, p.Containers[i].upCommands = c.MakeCommands()
}
}
func netToArgs(c *Network) string {
net := "%s%s%s"
ipv4 := ""
if c.IPv4Address != "" {
ipv4 = fmt.Sprintf(" --ip %s", c.IPv4Address)
}
ipv6 := ""
if c.IPv6Address != "" {
ipv6 = fmt.Sprintf(" --ip6 %s", c.IPv6Address)
}
net = fmt.Sprintf(net, c.Name, ipv4, ipv6)
return net
}
func NetworkDefaults(name string) (net *Network) {
for _, n := range *networks {
if n.Name == name {
net = &n
}
}
return
}
func ClearRACommands(c *Container) *[]string {
cmds := []string{}
for _, n := range c.Networks {
nw := NetworkDefaults(n.Name)
if !nw.IPv6 {
cmds = append(cmds, fmt.Sprintf("ip netns exec %s sysctl -w net.ipv6.conf.default.accept_ra=0", c.Name))
cmds = append(cmds, fmt.Sprintf("ip netns exec %s sysctl -w net.ipv6.conf.all.accept_ra=0", c.Name))
// TODO: iterate through invoices and set the accpet_ra parameter to zero for each
return &cmds
}
}
return &cmds
}
func (c *Container) MakeCommands() ([]string, []string) {
cmd := []string{
"podman create --name %s%s%s%s%s%s",
}
hostname := ""
if c.Hostname != "" {
hostname = fmt.Sprintf(" --hostname %s", c.Hostname)
}
net := ""
dns := ""
if len(c.Networks) > 0 {
net = " --net " + netToArgs(&c.Networks[0])
if len(c.Networks[0].DNS) > 0 {
dns = " --dns " + strings.Join(c.Networks[0].DNS, ",")
} else if len(NetworkDefaults(c.Networks[0].Name).DNS) > 0 {
dns = " --dns " + strings.Join(NetworkDefaults(c.Networks[0].Name).DNS, ",")
}
}
args := ""
if c.Arguments != "" {
args = " " + c.Arguments
}
entry := ""
if c.Command != "" {
entry = " " + c.Command
}
cmd[0] = fmt.Sprintf(cmd[0], c.Name, hostname, net, dns, args, entry)
if len(c.Networks) > 1 {
for i := 1; i < len(c.Networks); i++ {
n := c.Networks[i]
s := fmt.Sprintf("podman network connect %s %s", c.Name, netToArgs(&n))
cmd = append(cmd, s)
}
}
up := []string{}
if len(c.Networks) > 0 {
if !c.Networks[0].IPv6 || !NetworkDefaults(c.Networks[0].Name).IPv6 {
for _, k := range *ClearRACommands(c) {
up = append(up, k)
}
up = append(up, fmt.Sprintf("ip netns exec %s ip -6 address flush scope global", c.Name))
up = append(up, fmt.Sprintf("ip netns exec %s ip -6 route flush proto ra", c.Name))
}
}
return cmd, up
}
func main() {
Init(CONFIG_FILE)
containers := Union([]string{"all"})
if len(os.Args) > 1 {
containers = Union(os.Args[1:])
}
for _, c := range containers {
commands[CREATE](&c)
// commands[START](&c)
}
log.Printf("CATEGORIES: %v", Categories())
}
func PrintCreate(ct *Container) {
for _, c := range ct.createCommands {
fmt.Println(c)
}
}
func (c *Container) Pid() int {
return 0
}
func (c *Container) AssureNetNS() {
// stat, _ := file.Stat("/var/run/netns")
// if !stat.IsDir() {
// os.MkdirAll("/var/run/netns", os.ModePerm)
// }
}
func PrintStart(ct *Container) {
start_commands := []string{
"podman start " + ct.Name,
"sleep 1",
}
for _, c := range ct.upCommands {
start_commands = append(start_commands, c)
}
for _, c := range start_commands {
fmt.Println(c)
}
}

47
cmd/create.go Normal file
View File

@ -0,0 +1,47 @@
/*
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 cmd
import (
"gitea.elkins.co/Networking/ccl/internal/pkg/config"
"github.com/spf13/cobra"
)
// createCmd represents the create command
var createCmd = &cobra.Command{
Use: "create",
Short: "Create containers",
Args: cobra.OnlyValidArgs,
ValidArgsFunction: validNouns,
Long: `Create containers specified by the arguments. Arguments can be either container
names or categories. Multiple arguments are supported.`,
Run: func(cmd *cobra.Command, args []string) {
conts := config.Union(args)
for _, c := range conts {
config.PrintCreate(&c)
}
},
}
func init() {
rootCmd.AddCommand(createCmd)
}

59
cmd/ls.go Normal file
View File

@ -0,0 +1,59 @@
/*
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 cmd
import (
"fmt"
"text/tabwriter"
"gitea.elkins.co/Networking/ccl/internal/pkg/config"
"github.com/spf13/cobra"
)
// lsCmd represents the ls command
var lsCmd = &cobra.Command{
Use: "ls",
Aliases: []string{"list"},
Short: "List configured containers",
Args: cobra.OnlyValidArgs,
ValidArgsFunction: validNouns,
Long: `List configured containers. You can provide one or more names
or categories to limit the list.`,
Example: `ccl ls # same as below
ccl ls all # same as above
ccl ls default sub # multiple ok
ccl ls squid`,
Run: func(cmd *cobra.Command, args []string) {
conts := config.Union(args)
w := tabwriter.NewWriter(cmd.OutOrStdout(), 0, 0, 4, ' ', 0)
defer w.Flush()
fmt.Fprintf(w, "CATEGORY\tNAME\tIMAGE\n")
for _, c := range conts {
fmt.Fprintf(w, "%s\t%s\t%s\n", c.Category, c.Name, c.Image)
}
},
}
func init() {
rootCmd.AddCommand(lsCmd)
}

38
cmd/nouns.go Normal file
View File

@ -0,0 +1,38 @@
/*
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 cmd
import (
"gitea.elkins.co/Networking/ccl/internal/pkg/config"
"github.com/spf13/cobra"
)
func validNouns(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
validArgs := []string{}
for _, c := range config.Union([]string{}) {
validArgs = append(validArgs, c.Name)
}
for _, c := range config.Categories() {
validArgs = append(validArgs, c)
}
return validArgs, cobra.ShellCompDirectiveNoFileComp
}

67
cmd/root.go Normal file
View File

@ -0,0 +1,67 @@
/*
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 cmd
import (
"io"
"os"
"gitea.elkins.co/Networking/ccl/internal/pkg/config"
"github.com/spf13/cobra"
)
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "ccl",
Short: "Manage a set of pre-configured of podman containers",
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
from a toml configuration file, and the utility uses this information to
execute the necessary podman commands.`,
ValidArgsFunction: cobra.NoFileCompletions,
}
var (
output io.Writer
verbose bool
)
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
}
func init() {
cobra.OnInitialize(config.Init)
cobra.OnInitialize(func() {
if verbose {
output = os.Stderr
} else {
output = io.Discard
}
})
rootCmd.PersistentFlags().StringVarP(&config.ConfigFile, "config", "c", config.CONFIG_FILE_DEFAULT, "pathname of config file")
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "show additional info from command execution")
}

45
cmd/start.go Normal file
View File

@ -0,0 +1,45 @@
/*
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 cmd
import (
"gitea.elkins.co/Networking/ccl/internal/pkg/config"
"github.com/spf13/cobra"
)
// startCmd represents the start command
var startCmd = &cobra.Command{
Use: "start",
Short: "Start containers",
Long: `Start configured containers. They must be created first. Arguments can be
one or more container names or categories. If empty, "all" is assumed.`,
Run: func(cmd *cobra.Command, args []string) {
conts := config.Union(args)
for _, c := range conts {
config.PrintStart(&c)
}
},
}
func init() {
rootCmd.AddCommand(startCmd)
}

6
go.mod
View File

@ -5,5 +5,11 @@ go 1.18
require (
github.com/emirpasic/gods v1.18.1
github.com/pelletier/go-toml v1.9.5
github.com/spf13/cobra v1.5.0
golang.org/x/exp v0.0.0-20220713135740-79cabaa25d75
)
require (
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
)

10
go.sum
View File

@ -1,6 +1,16 @@
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
golang.org/x/exp v0.0.0-20220713135740-79cabaa25d75 h1:x03zeu7B2B11ySp+daztnwM5oBJ/8wGUSqrwcw9L0RA=
golang.org/x/exp v0.0.0-20220713135740-79cabaa25d75/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

View File

@ -0,0 +1,132 @@
/*
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 command
import (
"fmt"
"io"
"os/exec"
)
type CommandType int
func (ct CommandType) String() string {
switch ct {
case CT_SH:
return "SHELL"
case CT_REF:
return "FUNC"
case CT_INDIRECT:
return "INDIRECT"
case CT_SET:
return "SET"
case CT_DEBUG:
return "DEBUG"
default:
return "UNKOWN"
}
}
const (
CT_SH CommandType = iota
CT_REF
CT_INDIRECT
CT_SET
CT_DEBUG
)
type Command struct {
Type CommandType
Command interface{}
}
func NewShell(cmd string) Command {
return Command{CT_SH, cmd}
}
func NewFunc(f func() string) Command {
return Command{CT_REF, f}
}
func NewIndirect(c Command) Command {
return Command{CT_INDIRECT, c}
}
func NewSet(cs []Command) Command {
return Command{CT_SET, cs}
}
func NewDebug(msg string) Command {
return Command{CT_DEBUG, msg}
}
func (c Command) GetShell() (string, error) {
s, ok := c.Command.(string)
if ok {
return s, nil
}
return s, fmt.Errorf("Problem extracting shell command: CommandType = %d", c.Type)
}
func (c Command) CallRef() (string, error) {
f, ok := c.Command.(func() string)
if ok {
s := f()
return s, nil
}
return "", fmt.Errorf("Type error. Requested = %d, Type = %d", CT_REF, c.Type)
}
func (c Command) Execute(output io.Writer) error {
switch c.Type {
case CT_SH:
cmd, err := c.GetShell()
if err != nil {
return err
}
out, err := exec.Command("sh", "-c", cmd).CombinedOutput()
fmt.Fprintln(output, out)
return err
case CT_REF:
s, err := c.CallRef()
fmt.Println(s)
return err
case CT_INDIRECT:
ct, ok := c.Command.(Command)
if !ok {
return fmt.Errorf("Type error: Requested = %d, Type = %d", CT_INDIRECT, c.Type)
}
return ct.Execute(output)
case CT_SET:
cs, ok := c.Command.([]Command)
if !ok {
return fmt.Errorf("Type error: Requested = %d, Type = %d", CT_SET, c.Type)
}
for i := range cs {
err := cs[i].Execute(output)
if err != nil {
return err
}
}
}
return nil
}

View File

@ -0,0 +1,103 @@
package config
import (
"fmt"
"os"
"gitea.elkins.co/Networking/ccl/internal/pkg/container"
"gitea.elkins.co/Networking/ccl/internal/pkg/network"
"github.com/emirpasic/gods/sets/hashset"
toml "github.com/pelletier/go-toml"
"golang.org/x/exp/slices"
)
const (
CONFIG_FILE_DEFAULT = "/etc/containerdefs.toml"
)
type command string
var (
ConfigFile string = CONFIG_FILE_DEFAULT
networks *[]network.Network
containers *[]container.Container
categories *[]string
)
// A parsing convenience
type parse struct {
Networks []network.Network
Containers []container.Container
}
func Categories() []string {
if categories != nil {
return *categories
}
categories = &[]string{"all"}
gs := hashset.New()
for _, c := range *containers {
gs.Add(c.Category)
}
for _, c := range gs.Values() {
*categories = append(*categories, c.(string))
}
return *categories
}
func Union(ids []string) (conts []container.Container) {
if len(ids) == 0 {
return *containers
}
h := hashset.New()
for _, id := range ids {
for _, c := range *containers {
if id == "all" || c.Name == id || c.Category == id {
h.Add(c.Name)
}
}
}
for _, c := range h.Values() {
name := c.(string)
match := slices.IndexFunc(*containers, func(c container.Container) bool { return c.Name == name })
conts = append(conts, (*containers)[match])
}
return
}
func Init() {
f, err := os.ReadFile(ConfigFile)
if err != nil {
return
}
p := parse{}
err = toml.Unmarshal(f, &p)
if err != nil {
return
}
containers, networks = &p.Containers, &p.Networks
for i := range p.Containers {
p.Containers[i].Init(networks)
}
}
func NetworkDefaults(name string) (net *network.Network) {
for _, n := range *networks {
if n.Name == name {
net = &n
}
}
return
}
func PrintCreate(ct *container.Container) {
for _, c := range ct.CreateCommands() {
fmt.Println(c)
}
}
func PrintStart(ct *container.Container) {
for _, c := range ct.StartCommands() {
fmt.Println(c)
}
}

View File

@ -0,0 +1,197 @@
/*
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 (
"fmt"
"os"
"os/exec"
"strconv"
"strings"
"gitea.elkins.co/Networking/ccl/internal/pkg/command"
"gitea.elkins.co/Networking/ccl/internal/pkg/network"
)
type Container struct {
Category string
Name string
Image string
Hostname string
Command string
Arguments string
Networks []network.Network
createCommands []command.Command
upCommands []command.Command
pid int
}
func (c *Container) ClearRACommands() []command.Command {
cmds := []command.Command{}
for _, n := range c.Networks {
if n.IPv6 != nil && !*n.IPv6 {
cmds = append(cmds, command.NewShell(fmt.Sprintf("ip netns exec %s sysctl -w net.ipv6.conf.default.accept_ra=0", c.Name)))
cmds = append(cmds, command.NewShell(fmt.Sprintf("ip netns exec %s sysctl -w net.ipv6.conf.all.accept_ra=0", c.Name)))
// TODO: iterate through invoices and set the accpet_ra parameter to zero for each
return cmds
}
}
return cmds
}
func (c *Container) CreateCommands() []command.Command {
if len(c.createCommands) == 0 {
c.initCommands()
}
return c.createCommands
}
func (c *Container) RecreateCommands() []command.Command {
cmds := c.DestroyCommands()
create := c.CreateCommands()
for _, c := range create {
cmds = append(cmds, c)
}
return cmds
}
func (c *Container) StopCommands() []command.Command {
cmds := []command.Command{}
if _, err := c.Pid(); err != nil {
cmds = append(cmds, command.NewShell("podman stop "+c.Name))
}
cmds = append(cmds, command.NewShell("podman rm "+c.Name))
return cmds
}
func (c *Container) DestroyCommands() []command.Command {
cmds := c.StopCommands()
cmds = append(cmds, command.NewShell("podman rm "+c.Name))
return cmds
}
func (c *Container) StartCommands() []command.Command {
_, err := c.Pid()
if err != nil {
if len(c.upCommands) == 0 {
c.initCommands()
}
return c.upCommands
}
return []command.Command{}
}
func (c *Container) Init(nets *[]network.Network) {
for i := range c.Networks {
var n *network.Network
for j := range *nets {
if (*nets)[j].Name == c.Networks[i].Name {
n = &(*nets)[j]
}
}
if n == nil {
continue
}
if len(c.Networks[i].DNS) == 0 {
c.Networks[i].DNS = n.DNS
}
if c.Networks[i].IPv6 == nil {
if n.IPv6 != nil {
c.Networks[i].IPv6 = n.IPv6
} else {
yes := true
c.Networks[i].IPv6 = &yes
}
}
}
}
func (c *Container) Pid() (pid int, err error) {
pid_s, err := exec.Command("podman", "inspect", "-f", "{{.State.Pid}}").CombinedOutput()
if err != nil {
return
}
c.pid, err = strconv.Atoi(string(pid_s))
return c.pid, err
}
func (c *Container) initCommands() {
c.createCommands = []command.Command{
command.NewShell("podman create --name %s%s%s%s%s%s"),
}
hostname := ""
if c.Hostname != "" {
hostname = fmt.Sprintf(" --hostname %s", c.Hostname)
}
net := ""
dns := ""
if len(c.Networks) > 0 {
net = " --net " + c.Networks[0].ToArgs()
if len(c.Networks[0].DNS) > 0 {
dns = " --dns " + strings.Join(c.Networks[0].DNS, ",")
}
}
args := ""
if c.Arguments != "" {
args = " " + c.Arguments
}
entry := ""
if c.Command != "" {
entry = " " + c.Command
}
t, _ := c.createCommands[0].GetShell()
c.createCommands[0] = command.NewShell(fmt.Sprintf(t, c.Name, hostname, net, dns, args, entry))
if len(c.Networks) > 1 {
for i := 1; i < len(c.Networks); i++ {
n := c.Networks[i]
s := fmt.Sprintf("podman network connect %s %s", c.Name, n.ToArgs())
c.createCommands = append(c.createCommands, command.NewShell(s))
}
}
c.upCommands = []command.Command{
command.NewShell("podman start " + c.Name),
command.NewFunc(func() string {
var err error
pid, err := c.Pid()
if err != nil {
return fmt.Sprintf("%s is not running\n", c.Name)
}
commands := command.NewSet([]command.Command{
command.NewShell(fmt.Sprintf("rm -f /var/run/netns/%s", c.Name)),
command.NewShell(fmt.Sprintf("ln -s /proc/%d/ns/net /var/run/netns/%s", pid, c.Name)),
})
err = commands.Execute(os.Stderr)
if err != nil {
return fmt.Sprintln("Error:", err)
}
return fmt.Sprintln("Pid =", pid)
}),
}
if len(c.Networks) > 0 && !*c.Networks[0].IPv6 {
c.upCommands = append(c.upCommands, command.NewShell("sleep 1"))
for _, k := range c.ClearRACommands() {
c.upCommands = append(c.upCommands, k)
}
}
}

View File

@ -0,0 +1,46 @@
/*
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 network
import "fmt"
type Network struct {
Name string
DNS []string
IPv6 *bool `toml:"ipv6"`
IPv4Address string `toml:"ipv4_address"`
IPv6Address string `toml:"ipv6_address"`
}
func (n *Network) ToArgs() string {
net := "%s%s%s"
ipv4 := ""
if n.IPv4Address != "" {
ipv4 = fmt.Sprintf(" --ip %s", n.IPv4Address)
}
ipv6 := ""
if n.IPv6Address != "" {
ipv6 = fmt.Sprintf(" --ip6 %s", n.IPv6Address)
}
net = fmt.Sprintf(net, n.Name, ipv4, ipv6)
return net
}

28
main.go Normal file
View File

@ -0,0 +1,28 @@
/*
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 main
import "gitea.elkins.co/Networking/ccl/cmd"
func main() {
cmd.Execute()
}