From 94011a3a030df479d047b6298e31e1d9feb05bf6 Mon Sep 17 00:00:00 2001 From: Alejandro Mery Date: Mon, 4 Sep 2023 19:42:04 +0000 Subject: [PATCH] ceph: add NewConfigFromReader() and initial ceph.conf parser Signed-off-by: Alejandro Mery --- go.mod | 2 + go.sum | 4 ++ pkg/ceph/ceph.go | 2 + pkg/ceph/config.go | 38 +++++++++++++ pkg/ceph/config_parser.go | 110 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 156 insertions(+) create mode 100644 pkg/ceph/ceph.go create mode 100644 pkg/ceph/config.go create mode 100644 pkg/ceph/config_parser.go diff --git a/go.mod b/go.mod index 6e9c068..019ccee 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module git.jpi.io/amery/jpictl go 1.19 require ( + asciigoat.org/ini v0.2.5 darvaza.org/core v0.9.8 darvaza.org/resolver v0.5.4 darvaza.org/sidecar v0.0.2 @@ -18,6 +19,7 @@ require ( ) require ( + asciigoat.org/core v0.3.9 // indirect darvaza.org/slog/handlers/filter v0.4.5 // indirect darvaza.org/slog/handlers/zerolog v0.4.5 // indirect github.com/BurntSushi/toml v1.3.2 // indirect diff --git a/go.sum b/go.sum index 8d58d18..e2f357a 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,7 @@ +asciigoat.org/core v0.3.9 h1:hgDDz4ecm3ZvehX++m8A/IzAt+B5oDPiRtxatzfUHPQ= +asciigoat.org/core v0.3.9/go.mod h1:CAaHwyw8MpAq4a1MYtN2dxJrsK+hmIdW50OndaQZYPI= +asciigoat.org/ini v0.2.5 h1:4gRIp9rU+XQt8+HMqZO5R7GavMv9Yl2+N+je6djDIAE= +asciigoat.org/ini v0.2.5/go.mod h1:gmXzJ9XFqf1NLk5nQkj04USQ4tMtdRJHNQX6vp3DzjU= darvaza.org/core v0.9.8 h1:luLxgfUc2pzuusYPo/Z/dC/qr9XZPKpSQw8/kS7zNUM= darvaza.org/core v0.9.8/go.mod h1:Dbme64naxeshQfxcVJX9ZT7AiGyIY8kldfuELVtf8mw= darvaza.org/resolver v0.5.4 h1:dlSBNV14yYsp7Kg7ipwYOMNsLbrpeXa8Z0HBTa0Ryxs= diff --git a/pkg/ceph/ceph.go b/pkg/ceph/ceph.go new file mode 100644 index 0000000..53e1099 --- /dev/null +++ b/pkg/ceph/ceph.go @@ -0,0 +1,2 @@ +// Package ceph deals with ceph config +package ceph diff --git a/pkg/ceph/config.go b/pkg/ceph/config.go new file mode 100644 index 0000000..64c5bff --- /dev/null +++ b/pkg/ceph/config.go @@ -0,0 +1,38 @@ +package ceph + +import ( + "io" + "net/netip" + + "github.com/gofrs/uuid/v5" + + "asciigoat.org/ini/basic" +) + +// Config represents a ceph.conf file +type Config struct { + Global GlobalConfig `ini:"global"` +} + +// GlobalConfig represents the [global] section of a ceph.conf file +type GlobalConfig struct { + FSID uuid.UUID `ini:"fsid"` + Monitors []string `ini:"mon_initial_members,comma"` + MonitorsAddr []netip.Addr `ini:"mon_host,comma"` + ClusterNetwork netip.Prefix `ini:"cluster_network"` +} + +// NewConfigFromReader parses the ceph.conf file +func NewConfigFromReader(r io.Reader) (*Config, error) { + doc, err := basic.Decode(r) + if err != nil { + return nil, err + } + + cfg, err := newConfigFromDocument(doc) + if err != nil { + return nil, err + } + + return cfg, nil +} diff --git a/pkg/ceph/config_parser.go b/pkg/ceph/config_parser.go new file mode 100644 index 0000000..9428e27 --- /dev/null +++ b/pkg/ceph/config_parser.go @@ -0,0 +1,110 @@ +package ceph + +import ( + "io/fs" + "net/netip" + + "asciigoat.org/ini/basic" + "asciigoat.org/ini/parser" + + "darvaza.org/core" +) + +var sectionMap = map[string]func(*Config, *basic.Section) error{ + "global": loadGlobalConfSection, +} + +func loadConfSection(out *Config, src *basic.Section) error { + h, ok := sectionMap[src.Key] + if !ok { + return core.Wrapf(fs.ErrInvalid, "unknown section %q", src.Key) + } + + return h(out, src) +} + +func loadGlobalConfSection(out *Config, src *basic.Section) error { + var cfg GlobalConfig + + for _, field := range src.Fields { + if err := loadGlobalConfField(&cfg, field); err != nil { + return core.Wrap(err, "global") + } + } + + out.Global = cfg + return nil +} + +// revive:disable:cyclomatic +// revive:disable:cognitive-complexity + +func loadGlobalConfField(cfg *GlobalConfig, field basic.Field) error { + // revive:enable:cyclomatic + // revive:enable:cognitive-complexity + + // TODO: refactor when asciigoat's ini parser learns to do reflection + + switch field.Key { + case "fsid": + if !core.IsZero(cfg.FSID) { + return core.Wrapf(fs.ErrInvalid, "duplicate field %q", field.Key) + } + + err := cfg.FSID.UnmarshalText([]byte(field.Value)) + switch { + case err != nil: + return core.Wrap(err, field.Key) + default: + return nil + } + case "mon_host": + entries, _ := parser.SplitCommaArray(field.Value) + for _, s := range entries { + var addr netip.Addr + + if err := addr.UnmarshalText([]byte(s)); err != nil { + return core.Wrap(err, field.Key) + } + + cfg.MonitorsAddr = append(cfg.MonitorsAddr, addr) + } + return nil + case "mon_initial_members": + entries, _ := parser.SplitCommaArray(field.Value) + cfg.Monitors = append(cfg.Monitors, entries...) + return nil + case "cluster_network": + if !core.IsZero(cfg.ClusterNetwork) { + err := core.Wrap(fs.ErrInvalid, "fields before the first section") + return err + } + + err := cfg.ClusterNetwork.UnmarshalText([]byte(field.Value)) + switch { + case err != nil: + return core.Wrap(err, field.Key) + default: + return nil + } + } + return nil +} + +func newConfigFromDocument(doc *basic.Document) (*Config, error) { + var out Config + + if len(doc.Global) > 0 { + err := core.Wrap(fs.ErrInvalid, "fields before the first section") + return nil, err + } + + for i := range doc.Sections { + src := &doc.Sections[i] + if err := loadConfSection(&out, src); err != nil { + return nil, err + } + } + + return &out, nil +}