diff --git a/pkg/client/client.go b/pkg/client/client.go new file mode 100644 index 0000000..a4b7454 --- /dev/null +++ b/pkg/client/client.go @@ -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) +} diff --git a/pkg/client.go b/pkg/client/smd.go similarity index 54% rename from pkg/client.go rename to pkg/client/smd.go index ddbfab0..9a01401 100644 --- a/pkg/client.go +++ b/pkg/client/smd.go @@ -1,22 +1,15 @@ -package configurator +package client import ( "bytes" - "crypto/tls" - "crypto/x509" "encoding/json" "fmt" "io" - "net" "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 // 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 @@ -28,101 +21,43 @@ type SmdClient struct { 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. -func NewSmdClient(opts ...ClientOption) SmdClient { - client := SmdClient{} - for _, opt := range opts { - opt(&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, +func NewSmdClient(opts ...Option) SmdClient { + var ( + params = ToParams(opts...) + client = SmdClient{ + Host: params.Host, + AccessToken: params.AccessToken, } - } -} + ) -// 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, - } + return client } // 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. -func (client *SmdClient) FetchEthernetInterfaces(opts ...util.Option) ([]EthernetInterface, error) { +func (client *SmdClient) FetchEthernetInterfaces(verbose bool) ([]configurator.EthernetInterface, error) { var ( - params = util.ToDict(opts...) - verbose = util.Get[bool](params, "verbose") - eths = []EthernetInterface{} + eths = []configurator.EthernetInterface{} + bytes []byte + err error ) // make request to SMD endpoint - b, err := client.makeRequest("/Inventory/EthernetInterfaces") + bytes, err = client.makeRequest("/Inventory/EthernetInterfaces") if err != nil { return nil, fmt.Errorf("failed to read HTTP response: %v", err) } // unmarshal response body JSON and extract in object - err = json.Unmarshal(b, ðs) + err = json.Unmarshal(bytes, ðs) if err != nil { return nil, fmt.Errorf("failed to unmarshal response: %v", err) } // print what we got if verbose is set - if verbose != nil { - if *verbose { - fmt.Printf("Ethernet Interfaces: %v\n", string(b)) - } + if verbose { + fmt.Printf("Ethernet Interfaces: %v\n", string(bytes)) } 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 // 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 ( - params = util.ToDict(opts...) - verbose = util.Get[bool](params, "verbose") - comps = []Component{} + comps = []configurator.Component{} + bytes []byte + err error ) // make request to SMD endpoint - b, err := client.makeRequest("/State/Components") + bytes, err = client.makeRequest("/State/Components") if err != nil { return nil, fmt.Errorf("failed to make HTTP request: %v", err) } // make sure our response is actually JSON - if !json.Valid(b) { - return nil, fmt.Errorf("expected valid JSON response: %v", string(b)) + if !json.Valid(bytes) { + return nil, fmt.Errorf("expected valid JSON response: %v", string(bytes)) } // unmarshal response body JSON and extract in object var tmp map[string]any - err = json.Unmarshal(b, &tmp) + err = json.Unmarshal(bytes, &tmp) if err != nil { 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 { return nil, fmt.Errorf("failed to marshal JSON: %v", err) } - err = json.Unmarshal(b, &comps) + err = json.Unmarshal(bytes, &comps) if err != nil { return nil, fmt.Errorf("failed to unmarshal response: %v", err) } // print what we got if verbose is set - if verbose != nil { - if *verbose { - fmt.Printf("Components: %v\n", string(b)) - } + if verbose { + fmt.Printf("Components: %v\n", string(bytes)) } 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 ( - params = util.ToDict(opts...) - verbose = util.Get[bool](params, "verbose") - eps = []RedfishEndpoint{} + eps = []configurator.RedfishEndpoint{} + tmp map[string]any ) + // make initial request to get JSON with 'RedfishEndpoints' as property b, err := client.makeRequest("/Inventory/RedfishEndpoints") if err != nil { return nil, fmt.Errorf("failed to make HTTP resquest: %v", err) } + // make sure response is in JSON if !json.Valid(b) { return nil, fmt.Errorf("expected valid JSON response: %v", string(b)) } - var tmp map[string]any err = json.Unmarshal(b, &tmp) if err != nil { return nil, fmt.Errorf("failed to unmarshal response: %v", err) } + // marshal RedfishEndpoint JSON back to configurator.RedfishEndpoint b, err = json.Marshal(tmp["RedfishEndpoints"].([]any)) if err != nil { return nil, fmt.Errorf("failed to marshal JSON: %v", err) @@ -201,10 +136,9 @@ func (client *SmdClient) FetchRedfishEndpoints(opts ...util.Option) ([]RedfishEn return nil, fmt.Errorf("failed to unmarshal response: %v", err) } - if verbose != nil { - if *verbose { - fmt.Printf("Redfish endpoints: %v\n", string(b)) - } + // show the final result + if verbose { + fmt.Printf("Redfish endpoints: %v\n", string(b)) } return eps, nil diff --git a/pkg/config.go b/pkg/config/config.go similarity index 60% rename from pkg/config.go rename to pkg/config/config.go index 89db7ab..2a6ef79 100644 --- a/pkg/config.go +++ b/pkg/config/config.go @@ -1,22 +1,15 @@ -package configurator +package config import ( "log" "os" "path/filepath" + configurator "github.com/OpenCHAMI/configurator/pkg" + "github.com/OpenCHAMI/configurator/pkg/client" "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 { Uri string `yaml:"uri"` Retries int `yaml:"retries,omitempty"` @@ -29,35 +22,34 @@ type Server struct { } type Config struct { - Version string `yaml:"version,omitempty"` - Server Server `yaml:"server,omitempty"` - SmdClient SmdClient `yaml:"smd,omitempty"` - AccessToken string `yaml:"access-token,omitempty"` - Targets map[string]Target `yaml:"targets,omitempty"` - PluginDirs []string `yaml:"plugins,omitempty"` - CertPath string `yaml:"cacert,omitempty"` - Options Options `yaml:"options,omitempty"` + Version string `yaml:"version,omitempty"` + Server Server `yaml:"server,omitempty"` + SmdClient client.SmdClient `yaml:"smd,omitempty"` + AccessToken string `yaml:"access-token,omitempty"` + Targets map[string]configurator.Target `yaml:"targets,omitempty"` + PluginDirs []string `yaml:"plugins,omitempty"` + CertPath string `yaml:"cacert,omitempty"` } // Creates a new config with default parameters. -func NewConfig() Config { +func New() Config { return Config{ Version: "", - SmdClient: SmdClient{ + SmdClient: client.SmdClient{ Host: "http://127.0.0.1", Port: 27779, }, - Targets: map[string]Target{ - "dnsmasq": Target{ - PluginPath: "", + Targets: map[string]configurator.Target{ + "dnsmasq": configurator.Target{ + Plugin: "", TemplatePaths: []string{}, }, - "conman": Target{ - PluginPath: "", + "conman": configurator.Target{ + Plugin: "", TemplatePaths: []string{}, }, - "warewulf": Target{ - PluginPath: "", + "warewulf": configurator.Target{ + Plugin: "", TemplatePaths: []string{ "templates/warewulf/defaults/node.jinja", "templates/warewulf/defaults/provision.jinja", @@ -74,12 +66,11 @@ func NewConfig() Config { Retries: 5, }, }, - Options: Options{}, } } -func LoadConfig(path string) Config { - var c Config = NewConfig() +func Load(path string) Config { + var c Config = New() file, err := os.ReadFile(path) if err != nil { log.Printf("failed to read config file: %v\n", err) @@ -93,7 +84,7 @@ func LoadConfig(path string) Config { return c } -func (config *Config) SaveConfig(path string) { +func (config *Config) Save(path string) { path = filepath.Clean(path) if path == "" || path == "." { 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) if path == "" || path == "." { path = "config.yaml" } - var c = NewConfig() + var c = New() data, err := yaml.Marshal(c) if err != nil { log.Printf("failed to marshal config: %v\n", err) diff --git a/pkg/configurator.go b/pkg/configurator.go index 68dae99..7a19a36 100644 --- a/pkg/configurator.go +++ b/pkg/configurator.go @@ -2,6 +2,13 @@ package configurator 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 { IpAddress string `json:"IPAddress"` Network string `json:"Network"`