refactor: more code cleanup and reorganization

This commit is contained in:
David Allen 2024-11-21 14:16:03 -07:00
parent 72dd8416aa
commit 69aac3c929
Signed by: towk
GPG key ID: 793B2924A49B3A3F
4 changed files with 138 additions and 140 deletions

66
pkg/client/client.go Normal file
View file

@ -0,0 +1,66 @@
package client
import (
"crypto/tls"
"crypto/x509"
"net"
"net/http"
"os"
"time"
)
type Option func(*Params)
type Params struct {
Host string `yaml:"host"`
AccessToken string `yaml:"access-token"`
Transport *http.Transport
}
func ToParams(opts ...Option) *Params {
params := &Params{}
for _, opt := range opts {
opt(params)
}
return params
}
func WithHost(host string) Option {
return func(c *Params) {
c.Host = host
}
}
func WithAccessToken(token string) Option {
return func(c *Params) {
c.AccessToken = token
}
}
func WithCertPool(certPool *x509.CertPool) Option {
return func(c *Params) {
c.Transport = &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: certPool,
InsecureSkipVerify: true,
},
DisableKeepAlives: true,
Dial: (&net.Dialer{
Timeout: 120 * time.Second,
KeepAlive: 120 * time.Second,
}).Dial,
TLSHandshakeTimeout: 120 * time.Second,
ResponseHeaderTimeout: 120 * time.Second,
}
}
}
// FIXME: Need to check for errors when reading from a file
func WithCertPoolFile(certPath string) Option {
if certPath == "" {
return func(sc *Params) {}
}
cacert, _ := os.ReadFile(certPath)
certPool := x509.NewCertPool()
certPool.AppendCertsFromPEM(cacert)
return WithCertPool(certPool)
}

View file

@ -1,22 +1,15 @@
package configurator package client
import ( import (
"bytes" "bytes"
"crypto/tls"
"crypto/x509"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"net"
"net/http" "net/http"
"os"
"time"
"github.com/OpenCHAMI/configurator/pkg/util" configurator "github.com/OpenCHAMI/configurator/pkg"
) )
type ClientOption func(*SmdClient)
// An struct that's meant to extend functionality of the base HTTP client by // An struct that's meant to extend functionality of the base HTTP client by
// adding commonly made requests to SMD. The implemented functions are can be // adding commonly made requests to SMD. The implemented functions are can be
// used in generator plugins to fetch data when it is needed to substitute // used in generator plugins to fetch data when it is needed to substitute
@ -28,101 +21,43 @@ type SmdClient struct {
AccessToken string `yaml:"access-token"` AccessToken string `yaml:"access-token"`
} }
// Constructor function that allows supplying ClientOption arguments to set // Constructor function that allows supplying Option arguments to set
// things like the host, port, access token, etc. // things like the host, port, access token, etc.
func NewSmdClient(opts ...ClientOption) SmdClient { func NewSmdClient(opts ...Option) SmdClient {
client := SmdClient{} var (
for _, opt := range opts { params = ToParams(opts...)
opt(&client) client = SmdClient{
Host: params.Host,
AccessToken: params.AccessToken,
} }
)
return client return client
} }
func WithHost(host string) ClientOption {
return func(c *SmdClient) {
c.Host = host
}
}
func WithPort(port int) ClientOption {
return func(c *SmdClient) {
c.Port = port
}
}
func WithAccessToken(token string) ClientOption {
return func(c *SmdClient) {
c.AccessToken = token
}
}
func WithCertPool(certPool *x509.CertPool) ClientOption {
return func(c *SmdClient) {
c.Client.Transport = &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: certPool,
InsecureSkipVerify: true,
},
DisableKeepAlives: true,
Dial: (&net.Dialer{
Timeout: 120 * time.Second,
KeepAlive: 120 * time.Second,
}).Dial,
TLSHandshakeTimeout: 120 * time.Second,
ResponseHeaderTimeout: 120 * time.Second,
}
}
}
// FIXME: Need to check for errors when reading from a file
func WithCertPoolFile(certPath string) ClientOption {
if certPath == "" {
return func(sc *SmdClient) {}
}
cacert, _ := os.ReadFile(certPath)
certPool := x509.NewCertPool()
certPool.AppendCertsFromPEM(cacert)
return WithCertPool(certPool)
}
func WithVerbosity() util.Option {
return func(p util.Params) {
p["verbose"] = true
}
}
// Create a set of params with all default values.
func NewParams() util.Params {
return util.Params{
"verbose": false,
}
}
// Fetch the ethernet interfaces from SMD service using its API. An access token may be required if the SMD // Fetch the ethernet interfaces from SMD service using its API. An access token may be required if the SMD
// service SMD_JWKS_URL envirnoment variable is set. // service SMD_JWKS_URL envirnoment variable is set.
func (client *SmdClient) FetchEthernetInterfaces(opts ...util.Option) ([]EthernetInterface, error) { func (client *SmdClient) FetchEthernetInterfaces(verbose bool) ([]configurator.EthernetInterface, error) {
var ( var (
params = util.ToDict(opts...) eths = []configurator.EthernetInterface{}
verbose = util.Get[bool](params, "verbose") bytes []byte
eths = []EthernetInterface{} err error
) )
// make request to SMD endpoint // make request to SMD endpoint
b, err := client.makeRequest("/Inventory/EthernetInterfaces") bytes, err = client.makeRequest("/Inventory/EthernetInterfaces")
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read HTTP response: %v", err) return nil, fmt.Errorf("failed to read HTTP response: %v", err)
} }
// unmarshal response body JSON and extract in object // unmarshal response body JSON and extract in object
err = json.Unmarshal(b, &eths) err = json.Unmarshal(bytes, &eths)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %v", err) return nil, fmt.Errorf("failed to unmarshal response: %v", err)
} }
// print what we got if verbose is set // print what we got if verbose is set
if verbose != nil { if verbose {
if *verbose { fmt.Printf("Ethernet Interfaces: %v\n", string(bytes))
fmt.Printf("Ethernet Interfaces: %v\n", string(b))
}
} }
return eths, nil return eths, nil
@ -130,68 +65,68 @@ func (client *SmdClient) FetchEthernetInterfaces(opts ...util.Option) ([]Etherne
// Fetch the components from SMD using its API. An access token may be required if the SMD // Fetch the components from SMD using its API. An access token may be required if the SMD
// service SMD_JWKS_URL envirnoment variable is set. // service SMD_JWKS_URL envirnoment variable is set.
func (client *SmdClient) FetchComponents(opts ...util.Option) ([]Component, error) { func (client *SmdClient) FetchComponents(verbose bool) ([]configurator.Component, error) {
var ( var (
params = util.ToDict(opts...) comps = []configurator.Component{}
verbose = util.Get[bool](params, "verbose") bytes []byte
comps = []Component{} err error
) )
// make request to SMD endpoint // make request to SMD endpoint
b, err := client.makeRequest("/State/Components") bytes, err = client.makeRequest("/State/Components")
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to make HTTP request: %v", err) return nil, fmt.Errorf("failed to make HTTP request: %v", err)
} }
// make sure our response is actually JSON // make sure our response is actually JSON
if !json.Valid(b) { if !json.Valid(bytes) {
return nil, fmt.Errorf("expected valid JSON response: %v", string(b)) return nil, fmt.Errorf("expected valid JSON response: %v", string(bytes))
} }
// unmarshal response body JSON and extract in object // unmarshal response body JSON and extract in object
var tmp map[string]any var tmp map[string]any
err = json.Unmarshal(b, &tmp) err = json.Unmarshal(bytes, &tmp)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %v", err) return nil, fmt.Errorf("failed to unmarshal response: %v", err)
} }
b, err = json.Marshal(tmp["RedfishEndpoints"].([]any)) bytes, err = json.Marshal(tmp["RedfishEndpoints"].([]any))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to marshal JSON: %v", err) return nil, fmt.Errorf("failed to marshal JSON: %v", err)
} }
err = json.Unmarshal(b, &comps) err = json.Unmarshal(bytes, &comps)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %v", err) return nil, fmt.Errorf("failed to unmarshal response: %v", err)
} }
// print what we got if verbose is set // print what we got if verbose is set
if verbose != nil { if verbose {
if *verbose { fmt.Printf("Components: %v\n", string(bytes))
fmt.Printf("Components: %v\n", string(b))
}
} }
return comps, nil return comps, nil
} }
func (client *SmdClient) FetchRedfishEndpoints(opts ...util.Option) ([]RedfishEndpoint, error) { // TODO: improve implementation of this function
func (client *SmdClient) FetchRedfishEndpoints(verbose bool) ([]configurator.RedfishEndpoint, error) {
var ( var (
params = util.ToDict(opts...) eps = []configurator.RedfishEndpoint{}
verbose = util.Get[bool](params, "verbose") tmp map[string]any
eps = []RedfishEndpoint{}
) )
// make initial request to get JSON with 'RedfishEndpoints' as property
b, err := client.makeRequest("/Inventory/RedfishEndpoints") b, err := client.makeRequest("/Inventory/RedfishEndpoints")
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to make HTTP resquest: %v", err) return nil, fmt.Errorf("failed to make HTTP resquest: %v", err)
} }
// make sure response is in JSON
if !json.Valid(b) { if !json.Valid(b) {
return nil, fmt.Errorf("expected valid JSON response: %v", string(b)) return nil, fmt.Errorf("expected valid JSON response: %v", string(b))
} }
var tmp map[string]any
err = json.Unmarshal(b, &tmp) err = json.Unmarshal(b, &tmp)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to unmarshal response: %v", err) return nil, fmt.Errorf("failed to unmarshal response: %v", err)
} }
// marshal RedfishEndpoint JSON back to configurator.RedfishEndpoint
b, err = json.Marshal(tmp["RedfishEndpoints"].([]any)) b, err = json.Marshal(tmp["RedfishEndpoints"].([]any))
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to marshal JSON: %v", err) return nil, fmt.Errorf("failed to marshal JSON: %v", err)
@ -201,11 +136,10 @@ func (client *SmdClient) FetchRedfishEndpoints(opts ...util.Option) ([]RedfishEn
return nil, fmt.Errorf("failed to unmarshal response: %v", err) return nil, fmt.Errorf("failed to unmarshal response: %v", err)
} }
if verbose != nil { // show the final result
if *verbose { if verbose {
fmt.Printf("Redfish endpoints: %v\n", string(b)) fmt.Printf("Redfish endpoints: %v\n", string(b))
} }
}
return eps, nil return eps, nil
} }

View file

@ -1,22 +1,15 @@
package configurator package config
import ( import (
"log" "log"
"os" "os"
"path/filepath" "path/filepath"
configurator "github.com/OpenCHAMI/configurator/pkg"
"github.com/OpenCHAMI/configurator/pkg/client"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
type Options struct{}
type Target struct {
PluginPath string `yaml:"plugin,omitempty"`
TemplatePaths []string `yaml:"templates,omitempty"`
FilePaths []string `yaml:"files,omitempty"`
RunTargets []string `yaml:"targets,omitempty"`
}
type Jwks struct { type Jwks struct {
Uri string `yaml:"uri"` Uri string `yaml:"uri"`
Retries int `yaml:"retries,omitempty"` Retries int `yaml:"retries,omitempty"`
@ -31,33 +24,32 @@ type Server struct {
type Config struct { type Config struct {
Version string `yaml:"version,omitempty"` Version string `yaml:"version,omitempty"`
Server Server `yaml:"server,omitempty"` Server Server `yaml:"server,omitempty"`
SmdClient SmdClient `yaml:"smd,omitempty"` SmdClient client.SmdClient `yaml:"smd,omitempty"`
AccessToken string `yaml:"access-token,omitempty"` AccessToken string `yaml:"access-token,omitempty"`
Targets map[string]Target `yaml:"targets,omitempty"` Targets map[string]configurator.Target `yaml:"targets,omitempty"`
PluginDirs []string `yaml:"plugins,omitempty"` PluginDirs []string `yaml:"plugins,omitempty"`
CertPath string `yaml:"cacert,omitempty"` CertPath string `yaml:"cacert,omitempty"`
Options Options `yaml:"options,omitempty"`
} }
// Creates a new config with default parameters. // Creates a new config with default parameters.
func NewConfig() Config { func New() Config {
return Config{ return Config{
Version: "", Version: "",
SmdClient: SmdClient{ SmdClient: client.SmdClient{
Host: "http://127.0.0.1", Host: "http://127.0.0.1",
Port: 27779, Port: 27779,
}, },
Targets: map[string]Target{ Targets: map[string]configurator.Target{
"dnsmasq": Target{ "dnsmasq": configurator.Target{
PluginPath: "", Plugin: "",
TemplatePaths: []string{}, TemplatePaths: []string{},
}, },
"conman": Target{ "conman": configurator.Target{
PluginPath: "", Plugin: "",
TemplatePaths: []string{}, TemplatePaths: []string{},
}, },
"warewulf": Target{ "warewulf": configurator.Target{
PluginPath: "", Plugin: "",
TemplatePaths: []string{ TemplatePaths: []string{
"templates/warewulf/defaults/node.jinja", "templates/warewulf/defaults/node.jinja",
"templates/warewulf/defaults/provision.jinja", "templates/warewulf/defaults/provision.jinja",
@ -74,12 +66,11 @@ func NewConfig() Config {
Retries: 5, Retries: 5,
}, },
}, },
Options: Options{},
} }
} }
func LoadConfig(path string) Config { func Load(path string) Config {
var c Config = NewConfig() var c Config = New()
file, err := os.ReadFile(path) file, err := os.ReadFile(path)
if err != nil { if err != nil {
log.Printf("failed to read config file: %v\n", err) log.Printf("failed to read config file: %v\n", err)
@ -93,7 +84,7 @@ func LoadConfig(path string) Config {
return c return c
} }
func (config *Config) SaveConfig(path string) { func (config *Config) Save(path string) {
path = filepath.Clean(path) path = filepath.Clean(path)
if path == "" || path == "." { if path == "" || path == "." {
path = "config.yaml" path = "config.yaml"
@ -110,12 +101,12 @@ func (config *Config) SaveConfig(path string) {
} }
} }
func SaveDefaultConfig(path string) { func SaveDefault(path string) {
path = filepath.Clean(path) path = filepath.Clean(path)
if path == "" || path == "." { if path == "" || path == "." {
path = "config.yaml" path = "config.yaml"
} }
var c = NewConfig() var c = New()
data, err := yaml.Marshal(c) data, err := yaml.Marshal(c)
if err != nil { if err != nil {
log.Printf("failed to marshal config: %v\n", err) log.Printf("failed to marshal config: %v\n", err)

View file

@ -2,6 +2,13 @@ package configurator
import "encoding/json" import "encoding/json"
type Target struct {
Plugin string `yaml:"plugin,omitempty"` // Set the plugin or it's path
TemplatePaths []string `yaml:"templates,omitempty"` // Set the template paths
FilePaths []string `yaml:"files,omitempty"` // Set the file paths
RunTargets []string `yaml:"targets,omitempty"` // Set additional targets to run
}
type IPAddr struct { type IPAddr struct {
IpAddress string `json:"IPAddress"` IpAddress string `json:"IPAddress"`
Network string `json:"Network"` Network string `json:"Network"`