mirror of
https://github.com/davidallendj/configurator.git
synced 2025-12-20 03:27:02 -07:00
Fixed server implementation and refactored
This commit is contained in:
parent
0e3eec733b
commit
22195fa00a
8 changed files with 289 additions and 215 deletions
194
cmd/generate.go
194
cmd/generate.go
|
|
@ -6,13 +6,10 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"maps"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
configurator "github.com/OpenCHAMI/configurator/internal"
|
|
||||||
"github.com/OpenCHAMI/configurator/internal/generator"
|
"github.com/OpenCHAMI/configurator/internal/generator"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -25,28 +22,19 @@ var generateCmd = &cobra.Command{
|
||||||
Use: "generate",
|
Use: "generate",
|
||||||
Short: "Generate a config file from state management",
|
Short: "Generate a config file from state management",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
// load generator plugins to generate configs or to print
|
// if we have more than one target and output is set, create configs in directory
|
||||||
var (
|
targetCount := len(targets)
|
||||||
generators = make(map[string]generator.Generator)
|
if outputPath != "" && targetCount > 1 {
|
||||||
client = configurator.SmdClient{
|
err := os.MkdirAll(outputPath, 0o755)
|
||||||
Host: config.SmdClient.Host,
|
|
||||||
Port: config.SmdClient.Port,
|
|
||||||
AccessToken: config.AccessToken,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
for _, path := range pluginPaths {
|
|
||||||
if verbose {
|
|
||||||
fmt.Printf("loading plugins from '%s'\n", path)
|
|
||||||
}
|
|
||||||
gens, err := generator.LoadPlugins(path)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("failed to load plugins: %v\n", err)
|
fmt.Printf("failed to make output directory: %v", err)
|
||||||
err = nil
|
os.Exit(1)
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// add loaded generator plugins to set
|
// use config plugins if none supplied via CLI
|
||||||
maps.Copy(generators, gens)
|
if len(pluginPaths) <= 0 {
|
||||||
|
pluginPaths = append(pluginPaths, config.PluginDirs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// show config as JSON and generators if verbose
|
// show config as JSON and generators if verbose
|
||||||
|
|
@ -58,149 +46,47 @@ var generateCmd = &cobra.Command{
|
||||||
fmt.Printf("%v\n", string(b))
|
fmt.Printf("%v\n", string(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
// show available targets then exit
|
// generate config with each supplied target
|
||||||
if len(args) == 0 && len(targets) == 0 {
|
for _, target := range targets {
|
||||||
for g := range generators {
|
params := generator.Params{
|
||||||
fmt.Printf("\tplugin: %s, name:\n", g)
|
Args: args,
|
||||||
|
PluginPaths: pluginPaths,
|
||||||
|
Target: target,
|
||||||
|
Verbose: verbose,
|
||||||
}
|
}
|
||||||
os.Exit(0)
|
output, err := generator.Generate(&config, params)
|
||||||
}
|
if err != nil {
|
||||||
|
fmt.Printf("failed to generate config: %v\n", err)
|
||||||
// make sure that we have a token present before trying to make request
|
os.Exit(1)
|
||||||
if config.AccessToken == "" {
|
|
||||||
// TODO: make request to check if request will need token
|
|
||||||
|
|
||||||
// check if OCHAMI_ACCESS_TOKEN env var is set if no access token is provided and use that instead
|
|
||||||
accessToken := os.Getenv("ACCESS_TOKEN")
|
|
||||||
if accessToken != "" {
|
|
||||||
config.AccessToken = accessToken
|
|
||||||
} else {
|
|
||||||
// TODO: try and fetch token first if it is needed
|
|
||||||
if verbose {
|
|
||||||
fmt.Printf("No token found. Attempting to generate config without one...\n")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if targets == nil {
|
// write config output if no specific targetPath is set
|
||||||
logrus.Errorf("no target supplied (--target type:template)")
|
if outputPath == "" {
|
||||||
} else {
|
// write only to stdout
|
||||||
// if we have more than one target and output is set, create configs in directory
|
fmt.Printf("%s\n", string(output))
|
||||||
targetCount := len(targets)
|
} else if outputPath != "" && targetCount == 1 {
|
||||||
if outputPath != "" && targetCount > 1 {
|
// write just a single file using template name
|
||||||
err := os.MkdirAll(outputPath, 0o755)
|
err := os.WriteFile(outputPath, output, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("failed to make output directory: %v", err)
|
fmt.Printf("failed to write config to file: %v", err)
|
||||||
return
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
} else if outputPath != "" && targetCount > 1 {
|
||||||
|
// write multiple files in directory using template name
|
||||||
|
err := os.WriteFile(fmt.Sprintf("%s/%s.%s", filepath.Clean(outputPath), target, ".conf"), output, 0o644)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("failed to write config to file: %v", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, target := range targets {
|
|
||||||
// split the target and type
|
|
||||||
// tmp := strings.Split(target, ":")
|
|
||||||
|
|
||||||
// make sure each target has at least two args
|
|
||||||
// if len(tmp) < 2 {
|
|
||||||
// message := "target"
|
|
||||||
// if len(tmp) == 1 {
|
|
||||||
// message += fmt.Sprintf(" '%s'", tmp[0])
|
|
||||||
// }
|
|
||||||
// message += " does not provide enough arguments (args: \"type:template\")"
|
|
||||||
// logrus.Errorf(message)
|
|
||||||
// continue
|
|
||||||
// }
|
|
||||||
// var (
|
|
||||||
// _type = tmp[0]
|
|
||||||
// _template = tmp[1]
|
|
||||||
// )
|
|
||||||
// g := generator.Generator{
|
|
||||||
// Type: tmp[0],
|
|
||||||
// Template: tmp[1],
|
|
||||||
// }
|
|
||||||
|
|
||||||
// check if another param is specified
|
|
||||||
// targetPath := ""
|
|
||||||
// if len(tmp) > 2 {
|
|
||||||
// targetPath = tmp[2]
|
|
||||||
// }
|
|
||||||
|
|
||||||
// run the generator plugin from target passed
|
|
||||||
gen := generators[target]
|
|
||||||
if gen == nil {
|
|
||||||
fmt.Printf("invalid generator target (%s)\n", target)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
output, err := gen.Generate(
|
|
||||||
&config,
|
|
||||||
generator.WithTemplate(gen.GetName()),
|
|
||||||
generator.WithClient(client),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("failed to generate config: %v\n", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: we probably don't want to hardcode the types, but should do for now
|
|
||||||
// ext := ""
|
|
||||||
// contents := []byte{}
|
|
||||||
// if _type == "dhcp" {
|
|
||||||
// // fetch eths from SMD
|
|
||||||
// eths, err := client.FetchEthernetInterfaces()
|
|
||||||
// if err != nil {
|
|
||||||
// logrus.Errorf("failed to fetch DHCP metadata: %v\n", err)
|
|
||||||
// continue
|
|
||||||
// }
|
|
||||||
// if len(eths) <= 0 {
|
|
||||||
// continue
|
|
||||||
// }
|
|
||||||
// // generate a new config from that data
|
|
||||||
// contents, err = g.GenerateDHCP(&config, eths)
|
|
||||||
// if err != nil {
|
|
||||||
// logrus.Errorf("failed to generate DHCP config file: %v\n", err)
|
|
||||||
// continue
|
|
||||||
// }
|
|
||||||
// ext = "conf"
|
|
||||||
// } else if g.Type == "dns" {
|
|
||||||
// // TODO: fetch from SMD
|
|
||||||
// // TODO: generate config from pulled info
|
|
||||||
|
|
||||||
// } else if g.Type == "syslog" {
|
|
||||||
|
|
||||||
// } else if g.Type == "ansible" {
|
|
||||||
|
|
||||||
// } else if g.Type == "warewulf" {
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
// write config output if no specific targetPath is set
|
|
||||||
// if targetPath == "" {
|
|
||||||
if outputPath == "" {
|
|
||||||
// write only to stdout
|
|
||||||
fmt.Printf("%s\n", string(output))
|
|
||||||
} else if outputPath != "" && targetCount == 1 {
|
|
||||||
// write just a single file using template name
|
|
||||||
err := os.WriteFile(outputPath, output, 0o644)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Errorf("failed to write config to file: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
} else if outputPath != "" && targetCount > 1 {
|
|
||||||
// write multiple files in directory using template name
|
|
||||||
err := os.WriteFile(fmt.Sprintf("%s/%s.%s", filepath.Clean(outputPath), target, ".conf"), output, 0o644)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Errorf("failed to write config to file: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// }
|
|
||||||
} // for targets
|
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
generateCmd.Flags().StringSliceVar(&targets, "target", nil, "set the target configs to make")
|
generateCmd.Flags().StringSliceVar(&targets, "target", []string{}, "set the target configs to make")
|
||||||
generateCmd.Flags().StringSliceVar(&pluginPaths, "plugin", nil, "set the generator plugins directory path to shared libraries")
|
generateCmd.Flags().StringSliceVar(&pluginPaths, "plugins", []string{}, "set the generator plugins directory path")
|
||||||
generateCmd.Flags().StringVarP(&outputPath, "output", "o", "", "set the output path for config targets")
|
generateCmd.Flags().StringVarP(&outputPath, "output", "o", "", "set the output path for config targets")
|
||||||
generateCmd.Flags().IntVar(&tokenFetchRetries, "fetch-retries", 5, "set the number of retries to fetch an access token")
|
generateCmd.Flags().IntVar(&tokenFetchRetries, "fetch-retries", 5, "set the number of retries to fetch an access token")
|
||||||
|
|
||||||
|
|
|
||||||
42
cmd/serve.go
42
cmd/serve.go
|
|
@ -4,13 +4,14 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/OpenCHAMI/configurator/internal/generator"
|
||||||
"github.com/OpenCHAMI/configurator/internal/server"
|
"github.com/OpenCHAMI/configurator/internal/server"
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -18,13 +19,41 @@ var serveCmd = &cobra.Command{
|
||||||
Use: "serve",
|
Use: "serve",
|
||||||
Short: "Start configurator as a server and listen for requests",
|
Short: "Start configurator as a server and listen for requests",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
// use config plugins if none supplied via CLI
|
||||||
|
if len(pluginPaths) <= 0 {
|
||||||
|
pluginPaths = append(pluginPaths, config.PluginDirs...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// show config as JSON and generators if verbose
|
||||||
|
if verbose {
|
||||||
|
b, err := json.MarshalIndent(config, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("failed to marshal config: %v\n", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("%v\n", string(b))
|
||||||
|
}
|
||||||
|
|
||||||
// set up the routes and start the server
|
// set up the routes and start the server
|
||||||
server := server.New()
|
server := server.Server{
|
||||||
err := server.Start(&config)
|
Server: &http.Server{
|
||||||
|
Addr: fmt.Sprintf("%s:%d", config.Server.Host, config.Server.Port),
|
||||||
|
},
|
||||||
|
Jwks: server.Jwks{
|
||||||
|
Uri: config.Server.Jwks.Uri,
|
||||||
|
Retries: config.Server.Jwks.Retries,
|
||||||
|
},
|
||||||
|
GeneratorParams: generator.Params{
|
||||||
|
Args: args,
|
||||||
|
PluginPaths: pluginPaths,
|
||||||
|
// Target: target, // NOTE: targets are set via HTTP requests (ex: curl http://configurator:3334/generate?target=dnsmasq)
|
||||||
|
Verbose: verbose,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err := server.Serve(&config)
|
||||||
if errors.Is(err, http.ErrServerClosed) {
|
if errors.Is(err, http.ErrServerClosed) {
|
||||||
fmt.Printf("Server closed.")
|
fmt.Printf("Server closed.")
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
logrus.Errorf("failed to start server: %v", err)
|
fmt.Errorf("failed to start server: %v", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -33,7 +62,8 @@ var serveCmd = &cobra.Command{
|
||||||
func init() {
|
func init() {
|
||||||
serveCmd.Flags().StringVar(&config.Server.Host, "host", config.Server.Host, "set the server host")
|
serveCmd.Flags().StringVar(&config.Server.Host, "host", config.Server.Host, "set the server host")
|
||||||
serveCmd.Flags().IntVar(&config.Server.Port, "port", config.Server.Port, "set the server port")
|
serveCmd.Flags().IntVar(&config.Server.Port, "port", config.Server.Port, "set the server port")
|
||||||
serveCmd.Flags().StringVar(&config.Options.JwksUri, "jwks-uri", config.Options.JwksUri, "set the JWKS url to fetch public key")
|
serveCmd.Flags().StringSliceVar(&pluginPaths, "plugins", nil, "set the generator plugins directory path")
|
||||||
serveCmd.Flags().IntVar(&config.Options.JwksRetries, "jwks-fetch-retries", config.Options.JwksRetries, "set the JWKS fetch retry count")
|
serveCmd.Flags().StringVar(&config.Server.Jwks.Uri, "jwks-uri", config.Server.Jwks.Uri, "set the JWKS url to fetch public key")
|
||||||
|
serveCmd.Flags().IntVar(&config.Server.Jwks.Retries, "jwks-fetch-retries", config.Server.Jwks.Retries, "set the JWKS fetch retry count")
|
||||||
rootCmd.AddCommand(serveCmd)
|
rootCmd.AddCommand(serveCmd)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type SmdClient struct {
|
type SmdClient struct {
|
||||||
http.Client
|
http.Client `json:"-"`
|
||||||
Host string `yaml:"host"`
|
Host string `yaml:"host"`
|
||||||
Port int `yaml:"port"`
|
Port int `yaml:"port"`
|
||||||
AccessToken string `yaml:"access-token"`
|
AccessToken string `yaml:"access-token"`
|
||||||
|
|
@ -20,7 +20,7 @@ type SmdClient struct {
|
||||||
type Params = map[string]any
|
type Params = map[string]any
|
||||||
type Option func(Params)
|
type Option func(Params)
|
||||||
|
|
||||||
func WithVerbose() Option {
|
func WithVerbosity() Option {
|
||||||
return func(p util.Params) {
|
return func(p util.Params) {
|
||||||
p["verbose"] = true
|
p["verbose"] = true
|
||||||
}
|
}
|
||||||
|
|
@ -35,6 +35,11 @@ func NewParams() Params {
|
||||||
// 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(opts ...util.Option) ([]EthernetInterface, error) {
|
||||||
|
var (
|
||||||
|
params = util.GetParams(opts...)
|
||||||
|
verbose = util.Get[bool](params, "verbose")
|
||||||
|
eths = []EthernetInterface{}
|
||||||
|
)
|
||||||
// make request to SMD endpoint
|
// make request to SMD endpoint
|
||||||
b, err := client.makeRequest("/Inventory/EthernetInterfaces")
|
b, err := client.makeRequest("/Inventory/EthernetInterfaces")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -42,16 +47,14 @@ func (client *SmdClient) FetchEthernetInterfaces(opts ...util.Option) ([]Etherne
|
||||||
}
|
}
|
||||||
|
|
||||||
// unmarshal response body JSON and extract in object
|
// unmarshal response body JSON and extract in object
|
||||||
eths := []EthernetInterface{} // []map[string]any{}
|
|
||||||
err = json.Unmarshal(b, ðs)
|
err = json.Unmarshal(b, ðs)
|
||||||
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
|
||||||
params := util.GetParams(opts...)
|
if verbose != nil {
|
||||||
if verbose, ok := params["verbose"].(bool); ok {
|
if *verbose {
|
||||||
if verbose {
|
|
||||||
fmt.Printf("Ethernet Interfaces: %v\n", string(b))
|
fmt.Printf("Ethernet Interfaces: %v\n", string(b))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -62,23 +65,26 @@ 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(opts ...util.Option) ([]Component, error) {
|
||||||
|
var (
|
||||||
|
params = util.GetParams(opts...)
|
||||||
|
verbose = util.Get[bool](params, "verbose")
|
||||||
|
comps = []Component{}
|
||||||
|
)
|
||||||
// make request to SMD endpoint
|
// make request to SMD endpoint
|
||||||
b, err := client.makeRequest("/State/Components")
|
b, err := client.makeRequest("/State/Components")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read HTTP response: %v", err)
|
return nil, fmt.Errorf("failed to make HTTP request: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// unmarshal response body JSON and extract in object
|
// unmarshal response body JSON and extract in object
|
||||||
comps := []Component{}
|
|
||||||
err = json.Unmarshal(b, &comps)
|
err = json.Unmarshal(b, &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
|
||||||
params := util.GetParams(opts...)
|
if verbose != nil {
|
||||||
if verbose, ok := params["verbose"].(bool); ok {
|
if *verbose {
|
||||||
if verbose {
|
|
||||||
fmt.Printf("Components: %v\n", string(b))
|
fmt.Printf("Components: %v\n", string(b))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -86,6 +92,32 @@ func (client *SmdClient) FetchComponents(opts ...util.Option) ([]Component, erro
|
||||||
return comps, nil
|
return comps, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (client *SmdClient) FetchRedfishEndpoints(opts ...util.Option) ([]RedfishEndpoint, error) {
|
||||||
|
var (
|
||||||
|
params = util.GetParams(opts...)
|
||||||
|
verbose = util.Get[bool](params, "verbose")
|
||||||
|
rfs = []RedfishEndpoint{}
|
||||||
|
)
|
||||||
|
|
||||||
|
b, err := client.makeRequest("/Inventory/RedfishEndpoints")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to make HTTP resquest: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(b, &rfs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to unmarshal response: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if verbose != nil {
|
||||||
|
if *verbose {
|
||||||
|
fmt.Printf("Redfish endpoints: %v\n", string(b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rfs, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (client *SmdClient) makeRequest(endpoint string) ([]byte, error) {
|
func (client *SmdClient) makeRequest(endpoint string) ([]byte, error) {
|
||||||
if client == nil {
|
if client == nil {
|
||||||
return nil, fmt.Errorf("client is nil")
|
return nil, fmt.Errorf("client is nil")
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ type Config struct {
|
||||||
SmdClient SmdClient `yaml:"smd"`
|
SmdClient SmdClient `yaml:"smd"`
|
||||||
AccessToken string `yaml:"access-token"`
|
AccessToken string `yaml:"access-token"`
|
||||||
TemplatePaths map[string]string `yaml:"templates"`
|
TemplatePaths map[string]string `yaml:"templates"`
|
||||||
Plugins []string `yaml:"plugins"`
|
PluginDirs []string `yaml:"plugins"`
|
||||||
Options Options `yaml:"options"`
|
Options Options `yaml:"options"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,7 +45,7 @@ func NewConfig() Config {
|
||||||
"powerman": "templates/powerman.jinja",
|
"powerman": "templates/powerman.jinja",
|
||||||
"conman": "templates/conman.jinja",
|
"conman": "templates/conman.jinja",
|
||||||
},
|
},
|
||||||
Plugins: []string{},
|
PluginDirs: []string{},
|
||||||
Server: Server{
|
Server: Server{
|
||||||
Host: "127.0.0.1",
|
Host: "127.0.0.1",
|
||||||
Port: 3334,
|
Port: 3334,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package configurator
|
package configurator
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
type IPAddr struct {
|
type IPAddr struct {
|
||||||
IpAddress string `json:"IPAddress"`
|
IpAddress string `json:"IPAddress"`
|
||||||
Network string `json:"Network"`
|
Network string `json:"Network"`
|
||||||
|
|
@ -16,6 +18,38 @@ type EthernetInterface struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Component struct {
|
type Component struct {
|
||||||
|
ID string `json:"ID"`
|
||||||
|
Type string `json:"Type"`
|
||||||
|
State string `json:"State,omitempty"`
|
||||||
|
Flag string `json:"Flag,omitempty"`
|
||||||
|
Enabled *bool `json:"Enabled,omitempty"`
|
||||||
|
SwStatus string `json:"SoftwareStatus,omitempty"`
|
||||||
|
Role string `json:"Role,omitempty"`
|
||||||
|
SubRole string `json:"SubRole,omitempty"`
|
||||||
|
NID json.Number `json:"NID,omitempty"`
|
||||||
|
Subtype string `json:"Subtype,omitempty"`
|
||||||
|
NetType string `json:"NetType,omitempty"`
|
||||||
|
Arch string `json:"Arch,omitempty"`
|
||||||
|
Class string `json:"Class,omitempty"`
|
||||||
|
ReservationDisabled bool `json:"ReservationDisabled,omitempty"`
|
||||||
|
Locked bool `json:"Locked,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RedfishEndpoint struct {
|
||||||
|
ID string `json:"ID"`
|
||||||
|
Type string `json:"Type"`
|
||||||
|
Name string `json:"Name,omitempty"` // user supplied descriptive name
|
||||||
|
Hostname string `json:"Hostname"`
|
||||||
|
Domain string `json:"Domain"`
|
||||||
|
FQDN string `json:"FQDN"`
|
||||||
|
Enabled bool `json:"Enabled"`
|
||||||
|
UUID string `json:"UUID,omitempty"`
|
||||||
|
User string `json:"User"`
|
||||||
|
Password string `json:"Password"` // Temporary until more secure method
|
||||||
|
UseSSDP bool `json:"UseSSDP,omitempty"`
|
||||||
|
MACRequired bool `json:"MACRequired,omitempty"`
|
||||||
|
MACAddr string `json:"MACAddr,omitempty"`
|
||||||
|
IPAddr string `json:"IPAddress,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Node struct {
|
type Node struct {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package generator
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"maps"
|
||||||
"os"
|
"os"
|
||||||
"plugin"
|
"plugin"
|
||||||
|
|
||||||
|
|
@ -10,6 +11,7 @@ import (
|
||||||
"github.com/OpenCHAMI/configurator/internal/util"
|
"github.com/OpenCHAMI/configurator/internal/util"
|
||||||
"github.com/nikolalohinski/gonja/v2"
|
"github.com/nikolalohinski/gonja/v2"
|
||||||
"github.com/nikolalohinski/gonja/v2/exec"
|
"github.com/nikolalohinski/gonja/v2/exec"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Mappings = map[string]any
|
type Mappings = map[string]any
|
||||||
|
|
@ -19,6 +21,13 @@ type Generator interface {
|
||||||
Generate(config *configurator.Config, opts ...util.Option) ([]byte, error)
|
Generate(config *configurator.Config, opts ...util.Option) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Params struct {
|
||||||
|
Args []string
|
||||||
|
PluginPaths []string
|
||||||
|
Target string
|
||||||
|
Verbose bool
|
||||||
|
}
|
||||||
|
|
||||||
func LoadPlugin(path string) (Generator, error) {
|
func LoadPlugin(path string) (Generator, error) {
|
||||||
p, err := plugin.Open(path)
|
p, err := plugin.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -45,53 +54,33 @@ func LoadPlugins(dirpath string, opts ...util.Option) (map[string]Generator, err
|
||||||
)
|
)
|
||||||
|
|
||||||
items, _ := os.ReadDir(dirpath)
|
items, _ := os.ReadDir(dirpath)
|
||||||
var LoadGenerator = func(path string) (Generator, error) {
|
|
||||||
// load each generator plugin
|
|
||||||
p, err := plugin.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to load plugin: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// lookup symbol in plugin
|
|
||||||
symbol, err := p.Lookup("Generator")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to look up symbol: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// assert that the loaded symbol is the correct type
|
|
||||||
gen, ok := symbol.(Generator)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("failed to load the correct symbol type")
|
|
||||||
}
|
|
||||||
return gen, nil
|
|
||||||
}
|
|
||||||
for _, item := range items {
|
for _, item := range items {
|
||||||
if item.IsDir() {
|
if item.IsDir() {
|
||||||
subitems, _ := os.ReadDir(item.Name())
|
subitems, _ := os.ReadDir(item.Name())
|
||||||
for _, subitem := range subitems {
|
for _, subitem := range subitems {
|
||||||
if !subitem.IsDir() {
|
if !subitem.IsDir() {
|
||||||
gen, err := LoadGenerator(subitem.Name())
|
gen, err := LoadPlugin(subitem.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("failed to load generator in directory '%s': %v\n", item.Name(), err)
|
fmt.Printf("failed to load generator in directory '%s': %v\n", item.Name(), err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if verbose, ok := params["verbose"].(bool); ok {
|
if verbose, ok := params["verbose"].(bool); ok {
|
||||||
if verbose {
|
if verbose {
|
||||||
fmt.Printf("found plugin '%s'\n", item.Name())
|
fmt.Printf("-- found plugin '%s'\n", item.Name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gens[gen.GetName()] = gen
|
gens[gen.GetName()] = gen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
gen, err := LoadGenerator(dirpath + item.Name())
|
gen, err := LoadPlugin(dirpath + item.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("failed to load generator: %v\n", err)
|
fmt.Printf("failed to load generator: %v\n", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if verbose, ok := params["verbose"].(bool); ok {
|
if verbose, ok := params["verbose"].(bool); ok {
|
||||||
if verbose {
|
if verbose {
|
||||||
fmt.Printf("found plugin '%s'\n", dirpath+item.Name())
|
fmt.Printf("-- found plugin '%s'\n", dirpath+item.Name())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gens[gen.GetName()] = gen
|
gens[gen.GetName()] = gen
|
||||||
|
|
@ -123,17 +112,15 @@ func WithClient(client configurator.SmdClient) util.Option {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Syntactic sugar generic function to get parameter from util.Params.
|
func WithOption(key string, value any) util.Option {
|
||||||
func Get[T any](params util.Params, key string) *T {
|
return func(p util.Params) {
|
||||||
if v, ok := params[key].(T); ok {
|
p[key] = value
|
||||||
return &v
|
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to get client in generator plugins.
|
// Helper function to get client in generator plugins.
|
||||||
func GetClient(params util.Params) *configurator.SmdClient {
|
func GetClient(params util.Params) *configurator.SmdClient {
|
||||||
return Get[configurator.SmdClient](params, "client")
|
return util.Get[configurator.SmdClient](params, "client")
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetParams(opts ...util.Option) util.Params {
|
func GetParams(opts ...util.Option) util.Params {
|
||||||
|
|
@ -144,10 +131,6 @@ func GetParams(opts ...util.Option) util.Params {
|
||||||
return params
|
return params
|
||||||
}
|
}
|
||||||
|
|
||||||
func Generate(g Generator, config *configurator.Config, opts ...util.Option) {
|
|
||||||
g.Generate(config, opts...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ApplyTemplate(path string, mappings map[string]any) ([]byte, error) {
|
func ApplyTemplate(path string, mappings map[string]any) ([]byte, error) {
|
||||||
data := exec.NewContext(mappings)
|
data := exec.NewContext(mappings)
|
||||||
|
|
||||||
|
|
@ -165,3 +148,71 @@ func ApplyTemplate(path string, mappings map[string]any) ([]byte, error) {
|
||||||
|
|
||||||
return b.Bytes(), nil
|
return b.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Generate(config *configurator.Config, params Params) ([]byte, error) {
|
||||||
|
// load generator plugins to generate configs or to print
|
||||||
|
var (
|
||||||
|
generators = make(map[string]Generator)
|
||||||
|
client = configurator.SmdClient{
|
||||||
|
Host: config.SmdClient.Host,
|
||||||
|
Port: config.SmdClient.Port,
|
||||||
|
AccessToken: config.AccessToken,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// make sure that we have a token present before trying to make request
|
||||||
|
if config.AccessToken == "" {
|
||||||
|
// TODO: make request to check if request will need token
|
||||||
|
|
||||||
|
// check if OCHAMI_ACCESS_TOKEN env var is set if no access token is provided and use that instead
|
||||||
|
accessToken := os.Getenv("ACCESS_TOKEN")
|
||||||
|
if accessToken != "" {
|
||||||
|
config.AccessToken = accessToken
|
||||||
|
} else {
|
||||||
|
// TODO: try and fetch token first if it is needed
|
||||||
|
if params.Verbose {
|
||||||
|
fmt.Printf("No token found. Attempting to generate config without one...\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// load all plugins from params
|
||||||
|
for _, path := range params.PluginPaths {
|
||||||
|
if params.Verbose {
|
||||||
|
fmt.Printf("loading plugins from '%s'\n", path)
|
||||||
|
}
|
||||||
|
gens, err := LoadPlugins(path)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("failed to load plugins: %v\n", err)
|
||||||
|
err = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// add loaded generator plugins to set
|
||||||
|
maps.Copy(generators, gens)
|
||||||
|
}
|
||||||
|
|
||||||
|
// show available targets then exit
|
||||||
|
if len(params.Args) == 0 && params.Target == "" {
|
||||||
|
for g := range generators {
|
||||||
|
fmt.Printf("-- found generator plugin \"%s\"\n", g)
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.Target == "" {
|
||||||
|
logrus.Errorf("no target supplied (--target name)")
|
||||||
|
} else {
|
||||||
|
// run the generator plugin from target passed
|
||||||
|
gen := generators[params.Target]
|
||||||
|
if gen == nil {
|
||||||
|
return nil, fmt.Errorf("invalid generator target (%s)", params.Target)
|
||||||
|
}
|
||||||
|
return gen.Generate(
|
||||||
|
config,
|
||||||
|
WithTemplate(gen.GetName()),
|
||||||
|
WithClient(client),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("an unknown error has occurred")
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
configurator "github.com/OpenCHAMI/configurator/internal"
|
configurator "github.com/OpenCHAMI/configurator/internal"
|
||||||
|
"github.com/OpenCHAMI/configurator/internal/generator"
|
||||||
"github.com/OpenCHAMI/jwtauth/v5"
|
"github.com/OpenCHAMI/jwtauth/v5"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/go-chi/chi/v5/middleware"
|
"github.com/go-chi/chi/v5/middleware"
|
||||||
|
|
@ -19,9 +20,15 @@ var (
|
||||||
tokenAuth *jwtauth.JWTAuth = nil
|
tokenAuth *jwtauth.JWTAuth = nil
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Jwks struct {
|
||||||
|
Uri string
|
||||||
|
Retries int
|
||||||
|
}
|
||||||
type Server struct {
|
type Server struct {
|
||||||
*http.Server
|
*http.Server
|
||||||
JwksUri string `yaml:"jwks-uri"`
|
Jwks Jwks `yaml:"jwks"`
|
||||||
|
GeneratorParams generator.Params
|
||||||
|
TokenAuth *jwtauth.JWTAuth
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() *Server {
|
func New() *Server {
|
||||||
|
|
@ -29,11 +36,14 @@ func New() *Server {
|
||||||
Server: &http.Server{
|
Server: &http.Server{
|
||||||
Addr: "localhost:3334",
|
Addr: "localhost:3334",
|
||||||
},
|
},
|
||||||
JwksUri: "",
|
Jwks: Jwks{
|
||||||
|
Uri: "",
|
||||||
|
Retries: 5,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Start(config *configurator.Config) error {
|
func (s *Server) Serve(config *configurator.Config) error {
|
||||||
// create client just for the server to use to fetch data from SMD
|
// create client just for the server to use to fetch data from SMD
|
||||||
_ = &configurator.SmdClient{
|
_ = &configurator.SmdClient{
|
||||||
Host: config.SmdClient.Host,
|
Host: config.SmdClient.Host,
|
||||||
|
|
@ -56,6 +66,12 @@ func (s *Server) Start(config *configurator.Config) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var WriteError = func(w http.ResponseWriter, format string, a ...any) {
|
||||||
|
errmsg := fmt.Sprintf(format, a...)
|
||||||
|
fmt.Printf(errmsg)
|
||||||
|
w.Write([]byte(errmsg))
|
||||||
|
}
|
||||||
|
|
||||||
// create new go-chi router with its routes
|
// create new go-chi router with its routes
|
||||||
router := chi.NewRouter()
|
router := chi.NewRouter()
|
||||||
router.Use(middleware.RedirectSlashes)
|
router.Use(middleware.RedirectSlashes)
|
||||||
|
|
@ -67,11 +83,19 @@ func (s *Server) Start(config *configurator.Config) error {
|
||||||
jwtauth.Authenticator(tokenAuth),
|
jwtauth.Authenticator(tokenAuth),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
r.HandleFunc("/target", func(w http.ResponseWriter, r *http.Request) {
|
r.HandleFunc("/generate", func(w http.ResponseWriter, r *http.Request) {
|
||||||
// g := generator.Generator{
|
s.GeneratorParams.Target = r.URL.Query().Get("target")
|
||||||
// Type: r.URL.Query().Get("type"),
|
output, err := generator.Generate(config, s.GeneratorParams)
|
||||||
// Template: r.URL.Query().Get("template"),
|
if err != nil {
|
||||||
// }
|
WriteError(w, "failed to generate config: %v\n", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = w.Write(output)
|
||||||
|
if err != nil {
|
||||||
|
WriteError(w, "failed to write response: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: we probably don't want to hardcode the types, but should do for now
|
// NOTE: we probably don't want to hardcode the types, but should do for now
|
||||||
// if _type == "dhcp" {
|
// if _type == "dhcp" {
|
||||||
|
|
|
||||||
|
|
@ -35,3 +35,20 @@ func AssertOptionsExist(params Params, opts ...string) []string {
|
||||||
}
|
}
|
||||||
return foundKeys
|
return foundKeys
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func WithDefault[T any](v T) Option {
|
||||||
|
return func(p Params) {
|
||||||
|
p["default"] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Syntactic sugar generic function to get parameter from util.Params.
|
||||||
|
func Get[T any](params Params, key string, opts ...Option) *T {
|
||||||
|
if v, ok := params[key].(T); ok {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
if defaultValue, ok := params["default"].(T); ok {
|
||||||
|
return &defaultValue
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue