mirror of
https://gitea.elkins.co/Networking/ccl.git
synced 2025-03-06 02:08:49 -06:00
235 lines
6.8 KiB
Go
235 lines
6.8 KiB
Go
/*
|
|
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
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"sort"
|
|
|
|
"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"
|
|
log "github.com/sirupsen/logrus"
|
|
"golang.org/x/exp/slices"
|
|
"gopkg.in/guregu/null.v4"
|
|
)
|
|
|
|
const (
|
|
ConfigFileDefault = "/etc/ccl.toml" // ConfigFileDefault is default configuration file path
|
|
)
|
|
|
|
var (
|
|
ConfigFile = ConfigFileDefault // ConfigFile is the path to the configuration file to use in a particular invocation.
|
|
Networks = []*network.Network{} // Networks is the program-global set of congiured network.Networks structures
|
|
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 {
|
|
categories := []string{"all"}
|
|
gs := hashset.New()
|
|
for _, c := range Containers {
|
|
gs.Add(c.Category)
|
|
}
|
|
for _, c := range gs.Values() {
|
|
categories = append(categories, c.(string))
|
|
}
|
|
slices.Sort(categories)
|
|
return categories
|
|
}
|
|
|
|
// 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 string with a leading period (e.g.
|
|
// ".hidden")
|
|
func Union(ids []string, catMask []string) (conts []*container.Container) {
|
|
if len(ids) == 0 {
|
|
ids = []string{"all"}
|
|
}
|
|
h := hashset.New()
|
|
for _, id := range ids {
|
|
if j := slices.Index(catMask, id); j >= 0 {
|
|
catMask = slices.Delete(catMask, j, j+1)
|
|
}
|
|
for _, c := range Containers {
|
|
if (id == "all" || (id == "running" && c.IsRunning()) || c.Category == id) && !slices.Contains(catMask, c.Category) {
|
|
h.Add(c.Name)
|
|
}
|
|
if c.Name == 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])
|
|
}
|
|
if len(conts) == 0 {
|
|
log.WithFields(log.Fields{
|
|
"ids": ids,
|
|
"catMask": catMask,
|
|
}).Warnln("No matching containers. If disabled, try adding -a")
|
|
}
|
|
slices.SortFunc(conts, func(a, b *container.Container) bool {
|
|
if a.Category < b.Category {
|
|
return true
|
|
}
|
|
if a.Category > b.Category {
|
|
return false
|
|
}
|
|
return a.Name <= b.Name
|
|
})
|
|
return
|
|
}
|
|
|
|
// UnionNetworks is like Union but for Networks, and also no mask.
|
|
func UnionNetworks(ids []string) []*network.Network {
|
|
nets := make([]*network.Network, len(Networks))
|
|
rejects := []int{}
|
|
copy(nets, Networks)
|
|
for i := range nets {
|
|
if !slices.Contains(ids, nets[i].Name) {
|
|
rejects = append(rejects, i)
|
|
}
|
|
}
|
|
// reverse the rejects list, so we can delete from the end backward
|
|
// and thereby not fuck up sequential indicies
|
|
sort.SliceStable(rejects, func(i, j int) bool {
|
|
return true
|
|
})
|
|
for _, j := range rejects {
|
|
nets = slices.Delete(nets, j, j+1)
|
|
}
|
|
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 {
|
|
// A parsing convenience
|
|
type parse struct {
|
|
Networks []*network.Network
|
|
Containers []*container.Container
|
|
}
|
|
|
|
f, err := os.ReadFile(ConfigFile)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
p := parse{}
|
|
err = toml.Unmarshal(f, &p)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
Containers, Networks = p.Containers, p.Networks
|
|
for i := range Containers {
|
|
Containers[i].Init(conn, Networks)
|
|
}
|
|
slices.SortFunc(Containers, func(a, b *container.Container) bool {
|
|
return a.Name < b.Name
|
|
})
|
|
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 {
|
|
type parse struct {
|
|
Containers []*container.Container `toml:"containers,omitempty"`
|
|
Networks []*network.Network `toml:"networks,omitempty"`
|
|
}
|
|
|
|
getNet := func(name string) *network.Network {
|
|
for i := range Networks {
|
|
if Networks[i].Name == name {
|
|
return Networks[i]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
ipSliceEq := func(a, b []net.IP) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
for i := range a {
|
|
if !bytes.Equal(a[i], b[i]) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
conts := Union(ids, contMask)
|
|
// clear out values equal to the defined networks
|
|
for c := range conts {
|
|
for n := range conts[c].Networks {
|
|
d := getNet(conts[c].Networks[n].Name)
|
|
if d == nil {
|
|
conts[c].LogEntry().WithField("network", conts[c].Networks[n].Name).Warnln("Network defaults not defined")
|
|
continue
|
|
}
|
|
if conts[c].Networks[n].IPv6 == d.IPv6 {
|
|
conts[c].Networks[n].IPv6 = null.Bool{}
|
|
}
|
|
if ipSliceEq(conts[c].Networks[n].DNS, d.DNS) {
|
|
conts[c].Networks[n].DNS = nil
|
|
}
|
|
}
|
|
}
|
|
usednets := hashset.New()
|
|
for _, c := range conts {
|
|
for _, n := range c.Networks {
|
|
usednets.Add(n.Name)
|
|
}
|
|
}
|
|
for _, un := range usednets.Values() {
|
|
ids = append(ids, un.(string))
|
|
}
|
|
nets := UnionNetworks(ids)
|
|
|
|
output, err := toml.Marshal(parse{conts, nets})
|
|
if err != nil {
|
|
fmt.Println("could not marshal containers:", err)
|
|
os.Exit(1)
|
|
}
|
|
return string(output)
|
|
}
|