From ec2b30c1e7630c722b632a07b5ae827997e392d7 Mon Sep 17 00:00:00 2001 From: Alejandro Mery Date: Mon, 11 Sep 2023 18:36:46 +0000 Subject: [PATCH 1/3] cluster: add DirFS() using hackpadfs/os Signed-off-by: Alejandro Mery --- pkg/cluster/cluster_fs.go | 25 +++++++++++++++++++++++++ pkg/cluster/cluster_scan_options.go | 3 +-- 2 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 pkg/cluster/cluster_fs.go diff --git a/pkg/cluster/cluster_fs.go b/pkg/cluster/cluster_fs.go new file mode 100644 index 0000000..344eccb --- /dev/null +++ b/pkg/cluster/cluster_fs.go @@ -0,0 +1,25 @@ +package cluster + +import ( + "io/fs" + "path/filepath" + + "github.com/hack-pad/hackpadfs/os" +) + +// DirFS returns a file system (an [fs.FS]) for the tree +// of files rooted at the directory dir. +func DirFS(dir string) (fs.FS, error) { + dir = filepath.Clean(dir) + fullPath, err := filepath.Abs(dir) + if err != nil { + return nil, err + } + + sub, err := os.NewFS().Sub(fullPath[1:]) + if err != nil { + return nil, err + } + + return sub, nil +} diff --git a/pkg/cluster/cluster_scan_options.go b/pkg/cluster/cluster_scan_options.go index 53d2a28..6a4b651 100644 --- a/pkg/cluster/cluster_scan_options.go +++ b/pkg/cluster/cluster_scan_options.go @@ -6,7 +6,6 @@ import ( "darvaza.org/resolver" "darvaza.org/slog" - "github.com/hack-pad/hackpadfs/os" ) // A ScanOption pre-configures the Zones before scanning @@ -101,7 +100,7 @@ func NewFromDirectory(dir, domain string, opts ...ScanOption) (*Cluster, error) return nil, err } - sub, err := os.NewFS().Sub(fullPath[1:]) + sub, err := DirFS(dir) if err != nil { return nil, err } From 70008e0ead14b87cc93790a7e84c1b6065fb2b11 Mon Sep 17 00:00:00 2001 From: Alejandro Mery Date: Sun, 10 Sep 2023 23:38:17 +0000 Subject: [PATCH 2/3] cluster: NewFromConfig() trying JSON and YAML Signed-off-by: Alejandro Mery --- pkg/cluster/cluster_import.go | 137 ++++++++++++++++++++++++++++++++++ 1 file changed, 137 insertions(+) create mode 100644 pkg/cluster/cluster_import.go diff --git a/pkg/cluster/cluster_import.go b/pkg/cluster/cluster_import.go new file mode 100644 index 0000000..ac597da --- /dev/null +++ b/pkg/cluster/cluster_import.go @@ -0,0 +1,137 @@ +package cluster + +import ( + "encoding/json" + "fmt" + "os" + + "gopkg.in/yaml.v3" +) + +func (m *Cluster) init(opts *ScanOptions) error { + for _, fn := range []func(*ScanOptions) error{ + m.initZones, + m.scanZoneIDs, + m.scanSort, + m.scanGateways, + } { + if err := fn(opts); err != nil { + return err + } + } + + return nil +} + +func (m *Cluster) initZones(opts *ScanOptions) error { + var err error + + sub, err := DirFS(m.BaseDir) + if err != nil { + return err + } + + m.dir = sub + + m.ForEachZone(func(z *Zone) bool { + err = m.initZone(z, opts) + return err != nil + }) + + return err +} + +func (m *Cluster) initZone(z *Zone, _ *ScanOptions) error { + var hasMissing bool + var lastMachineID int + + z.zones = m + z.logger = m + + z.ForEachMachine(func(p *Machine) bool { + p.zone = z + p.logger = z + + switch { + case p.ID == 0: + hasMissing = true + case p.ID > lastMachineID: + lastMachineID = z.ID + } + + return false + }) + + if hasMissing { + next := lastMachineID + 1 + + z.ForEachMachine(func(p *Machine) bool { + if p.ID == 0 { + p.ID, next = next, next+1 + } + + return false + }) + } + + z.ForEachMachine(func(p *Machine) bool { + p.Name = fmt.Sprintf("%s-%v", z.Name, p.ID) + return false + }) + + return nil +} + +func decodeConfigData(data []byte) (out *Cluster, err error) { + // try JSON first + out = new(Cluster) + err = json.Unmarshal(data, out) + if err == nil { + // good json + return out, nil + } else if _, ok := err.(*json.SyntaxError); !ok { + // bad json + return nil, err + } + + out = new(Cluster) + err = yaml.Unmarshal(data, out) + if err != nil { + // bad yaml too + return nil, err + } + + // good yaml + return out, nil +} + +// NewFromConfig loads the cluster data from the given file +func NewFromConfig(filename string, opts ...ScanOption) (*Cluster, error) { + var scanOptions ScanOptions + + data, err := os.ReadFile(filename) + if err != nil { + return nil, err + } + + m, err := decodeConfigData(data) + if err != nil { + return nil, err + } + + for _, opt := range opts { + if err = opt(m, &scanOptions); err != nil { + return nil, err + } + } + + if err = m.setScanDefaults(&scanOptions); err != nil { + return nil, err + } + + if err := m.init(&scanOptions); err != nil { + return nil, err + } + + return m, nil +} From a0cc698a3952e6cf2c539a6ad5728dbd2a765bfb Mon Sep 17 00:00:00 2001 From: Alejandro Mery Date: Sun, 10 Sep 2023 23:38:46 +0000 Subject: [PATCH 3/3] jpictl: introduce --config-file/-f as alternative to scanning m/ Signed-off-by: Alejandro Mery --- cmd/jpictl/config.go | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/cmd/jpictl/config.go b/cmd/jpictl/config.go index a7649fc..3f71211 100644 --- a/cmd/jpictl/config.go +++ b/cmd/jpictl/config.go @@ -1,11 +1,25 @@ package main -import "git.jpi.io/amery/jpictl/pkg/cluster" +import ( + "os" + + "darvaza.org/core" + + "git.jpi.io/amery/jpictl/pkg/cluster" +) + +const ( + // DefaultConfigFile is read if -f/--config-file isn't specified. + // If it doesn't exist, m/ will be scanned + DefaultConfigFile = "cloud.yaml" +) // Config describes the repository type Config struct { Base string Domain string + + ConfigFile string } var cfg = &Config{ @@ -14,9 +28,32 @@ var cfg = &Config{ } // LoadZones loads all zones and machines in the config directory +// or file func (cfg *Config) LoadZones(resolve bool) (*cluster.Cluster, error) { + // try config file first + zones, err := cluster.NewFromConfig(cfg.ConfigFile, + cluster.ResolvePublicAddresses(resolve), + cluster.WithLogger(log), + ) + + switch { + case err == nil: + // file was good + return zones, nil + case !os.IsNotExist(err) || cfg.ConfigFile != DefaultConfigFile: + // file was bad + return nil, core.Wrapf(err, "NewFromConfig(%q)", cfg.ConfigFile) + } + + // default file doesn't exist. scan instead. return cluster.NewFromDirectory(cfg.Base, cfg.Domain, cluster.ResolvePublicAddresses(resolve), cluster.WithLogger(log), ) } + +func init() { + rootCmd.PersistentFlags(). + StringVarP(&cfg.ConfigFile, "config-file", "f", + DefaultConfigFile, "config file (JSON or YAML)") +}