Rewrote generators to use plugin system with default plugins
This commit is contained in:
parent
8036a5a8c0
commit
d77a31c7fe
15 changed files with 712 additions and 179 deletions
198
cmd/generate.go
198
cmd/generate.go
|
|
@ -4,10 +4,11 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"maps"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
|
|
||||||
configurator "github.com/OpenCHAMI/configurator/internal"
|
configurator "github.com/OpenCHAMI/configurator/internal"
|
||||||
"github.com/OpenCHAMI/configurator/internal/generator"
|
"github.com/OpenCHAMI/configurator/internal/generator"
|
||||||
|
|
@ -17,16 +18,52 @@ import (
|
||||||
|
|
||||||
var (
|
var (
|
||||||
tokenFetchRetries int
|
tokenFetchRetries int
|
||||||
|
pluginPaths []string
|
||||||
)
|
)
|
||||||
|
|
||||||
var generateCmd = &cobra.Command{
|
var generateCmd = &cobra.Command{
|
||||||
Use: "generate",
|
Use: "generate",
|
||||||
Short: "Generate a config file from system state",
|
Short: "Generate a config file from state management",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
client := configurator.SmdClient{
|
// load generator plugins to generate configs or to print
|
||||||
Host: config.SmdHost,
|
var (
|
||||||
Port: config.SmdPort,
|
generators = make(map[string]generator.Generator)
|
||||||
AccessToken: config.AccessToken,
|
client = configurator.SmdClient{
|
||||||
|
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 {
|
||||||
|
fmt.Printf("failed to load plugins: %v\n", err)
|
||||||
|
err = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// add loaded generator plugins to set
|
||||||
|
maps.Copy(generators, gens)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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))
|
||||||
|
}
|
||||||
|
|
||||||
|
// show available targets then exit
|
||||||
|
if len(args) == 0 && len(targets) == 0 {
|
||||||
|
for g := range generators {
|
||||||
|
fmt.Printf("\tplugin: %s, name:\n", g)
|
||||||
|
}
|
||||||
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure that we have a token present before trying to make request
|
// make sure that we have a token present before trying to make request
|
||||||
|
|
@ -34,12 +71,14 @@ var generateCmd = &cobra.Command{
|
||||||
// TODO: make request to check if request will need token
|
// 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
|
// check if OCHAMI_ACCESS_TOKEN env var is set if no access token is provided and use that instead
|
||||||
accessToken := os.Getenv("OCHAMI_ACCESS_TOKEN")
|
accessToken := os.Getenv("ACCESS_TOKEN")
|
||||||
if accessToken != "" {
|
if accessToken != "" {
|
||||||
config.AccessToken = accessToken
|
config.AccessToken = accessToken
|
||||||
} else {
|
} else {
|
||||||
// TODO: try and fetch token first if it is needed
|
// TODO: try and fetch token first if it is needed
|
||||||
fmt.Printf("No token found. Attempting to generate config without one...\n")
|
if verbose {
|
||||||
|
fmt.Printf("No token found. Attempting to generate config without one...\n")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -58,82 +97,102 @@ var generateCmd = &cobra.Command{
|
||||||
|
|
||||||
for _, target := range targets {
|
for _, target := range targets {
|
||||||
// split the target and type
|
// split the target and type
|
||||||
tmp := strings.Split(target, ":")
|
// tmp := strings.Split(target, ":")
|
||||||
|
|
||||||
// make sure each target has at least two args
|
// make sure each target has at least two args
|
||||||
if len(tmp) < 2 {
|
// if len(tmp) < 2 {
|
||||||
message := "target"
|
// message := "target"
|
||||||
if len(tmp) == 1 {
|
// if len(tmp) == 1 {
|
||||||
message += fmt.Sprintf(" '%s'", tmp[1])
|
// message += fmt.Sprintf(" '%s'", tmp[0])
|
||||||
}
|
// }
|
||||||
message += " does not provide enough arguments (args: \"type:template\")"
|
// message += " does not provide enough arguments (args: \"type:template\")"
|
||||||
logrus.Errorf(message)
|
// logrus.Errorf(message)
|
||||||
continue
|
// continue
|
||||||
}
|
// }
|
||||||
g := generator.Generator{
|
// var (
|
||||||
Type: tmp[0],
|
// _type = tmp[0]
|
||||||
Template: tmp[1],
|
// _template = tmp[1]
|
||||||
}
|
// )
|
||||||
|
// g := generator.Generator{
|
||||||
|
// Type: tmp[0],
|
||||||
|
// Template: tmp[1],
|
||||||
|
// }
|
||||||
|
|
||||||
// check if another param is specified
|
// check if another param is specified
|
||||||
targetPath := ""
|
// targetPath := ""
|
||||||
if len(tmp) > 2 {
|
// if len(tmp) > 2 {
|
||||||
targetPath = 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
|
// NOTE: we probably don't want to hardcode the types, but should do for now
|
||||||
ext := ""
|
// ext := ""
|
||||||
contents := []byte{}
|
// contents := []byte{}
|
||||||
if g.Type == "dhcp" {
|
// if _type == "dhcp" {
|
||||||
// fetch eths from SMD
|
// // fetch eths from SMD
|
||||||
eths, err := client.FetchEthernetInterfaces()
|
// eths, err := client.FetchEthernetInterfaces()
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
logrus.Errorf("failed to fetch DHCP metadata: %v\n", err)
|
// logrus.Errorf("failed to fetch DHCP metadata: %v\n", err)
|
||||||
continue
|
// continue
|
||||||
}
|
// }
|
||||||
if len(eths) <= 0 {
|
// if len(eths) <= 0 {
|
||||||
continue
|
// continue
|
||||||
}
|
// }
|
||||||
// generate a new config from that data
|
// // generate a new config from that data
|
||||||
contents, err = g.GenerateDHCP(&config, eths)
|
// contents, err = g.GenerateDHCP(&config, eths)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
logrus.Errorf("failed to generate DHCP config file: %v\n", err)
|
// logrus.Errorf("failed to generate DHCP config file: %v\n", err)
|
||||||
continue
|
// continue
|
||||||
}
|
// }
|
||||||
ext = "conf"
|
// ext = "conf"
|
||||||
} else if g.Type == "dns" {
|
// } else if g.Type == "dns" {
|
||||||
// TODO: fetch from SMD
|
// // TODO: fetch from SMD
|
||||||
// TODO: generate config from pulled info
|
// // TODO: generate config from pulled info
|
||||||
|
|
||||||
} else if g.Type == "syslog" {
|
// } else if g.Type == "syslog" {
|
||||||
|
|
||||||
} else if g.Type == "ansible" {
|
// } else if g.Type == "ansible" {
|
||||||
|
|
||||||
} else if g.Type == "warewulf" {
|
// } else if g.Type == "warewulf" {
|
||||||
|
|
||||||
}
|
// }
|
||||||
|
|
||||||
// write config output if no specific targetPath is set
|
// write config output if no specific targetPath is set
|
||||||
if targetPath == "" {
|
// if targetPath == "" {
|
||||||
if outputPath == "" {
|
if outputPath == "" {
|
||||||
// write only to stdout
|
// write only to stdout
|
||||||
fmt.Printf("%s\n", "")
|
fmt.Printf("%s\n", string(output))
|
||||||
} else if outputPath != "" && targetCount == 1 {
|
} else if outputPath != "" && targetCount == 1 {
|
||||||
// write just a single file using template name
|
// write just a single file using template name
|
||||||
err := os.WriteFile(outputPath, contents, 0o644)
|
err := os.WriteFile(outputPath, output, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("failed to write config to file: %v", err)
|
logrus.Errorf("failed to write config to file: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} else if outputPath != "" && targetCount > 1 {
|
} else if outputPath != "" && targetCount > 1 {
|
||||||
// write multiple files in directory using template name
|
// write multiple files in directory using template name
|
||||||
err := os.WriteFile(fmt.Sprintf("%s/%s.%s", filepath.Clean(outputPath), g.Template, ext), contents, 0o644)
|
err := os.WriteFile(fmt.Sprintf("%s/%s.%s", filepath.Clean(outputPath), target, ".conf"), output, 0o644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("failed to write config to file: %v", err)
|
logrus.Errorf("failed to write config to file: %v", err)
|
||||||
continue
|
continue
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// }
|
||||||
} // for targets
|
} // for targets
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -141,6 +200,7 @@ var generateCmd = &cobra.Command{
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
generateCmd.Flags().StringSliceVar(&targets, "target", nil, "set the target configs to make")
|
generateCmd.Flags().StringSliceVar(&targets, "target", nil, "set the target configs to make")
|
||||||
|
generateCmd.Flags().StringSliceVar(&pluginPaths, "plugin", nil, "set the generator plugins directory path to shared libraries")
|
||||||
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")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import (
|
||||||
var (
|
var (
|
||||||
configPath string
|
configPath string
|
||||||
config configurator.Config
|
config configurator.Config
|
||||||
|
verbose bool
|
||||||
targets []string
|
targets []string
|
||||||
outputPath string
|
outputPath string
|
||||||
)
|
)
|
||||||
|
|
@ -36,7 +37,8 @@ func Execute() {
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
cobra.OnInitialize(initConfig)
|
cobra.OnInitialize(initConfig)
|
||||||
rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", "", "set the config path")
|
rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", "./config.yaml", "set the config path")
|
||||||
|
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "set to enable verbose output")
|
||||||
}
|
}
|
||||||
|
|
||||||
func initConfig() {
|
func initConfig() {
|
||||||
|
|
|
||||||
|
|
@ -6,49 +6,110 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/OpenCHAMI/configurator/internal/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SmdClient struct {
|
type SmdClient struct {
|
||||||
http.Client
|
http.Client
|
||||||
Host string
|
Host string `yaml:"host"`
|
||||||
Port int
|
Port int `yaml:"port"`
|
||||||
AccessToken string
|
AccessToken string `yaml:"access-token"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *SmdClient) FetchDNS(config *Config) error {
|
type Params = map[string]any
|
||||||
// fetch DNS related information from SMD's endpoint:
|
type Option func(Params)
|
||||||
return nil
|
|
||||||
|
func WithVerbose() Option {
|
||||||
|
return func(p util.Params) {
|
||||||
|
p["verbose"] = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *SmdClient) FetchEthernetInterfaces() ([]EthernetInterface, error) {
|
func NewParams() Params {
|
||||||
|
return Params{
|
||||||
|
"verbose": false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
// make request to SMD endpoint
|
||||||
|
b, 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
|
||||||
|
eths := []EthernetInterface{} // []map[string]any{}
|
||||||
|
err = json.Unmarshal(b, ðs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to unmarshal response: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// print what we got if verbose is set
|
||||||
|
params := util.GetParams(opts...)
|
||||||
|
if verbose, ok := params["verbose"].(bool); ok {
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf("Ethernet Interfaces: %v\n", string(b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return eths, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
// make request to SMD endpoint
|
||||||
|
b, err := client.makeRequest("/State/Components")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read HTTP response: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// unmarshal response body JSON and extract in object
|
||||||
|
comps := []Component{}
|
||||||
|
err = json.Unmarshal(b, &comps)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to unmarshal response: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// print what we got if verbose is set
|
||||||
|
params := util.GetParams(opts...)
|
||||||
|
if verbose, ok := params["verbose"].(bool); ok {
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf("Components: %v\n", string(b))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return comps, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
}
|
}
|
||||||
// fetch DHCP related information from SMD's endpoint:
|
|
||||||
url := fmt.Sprintf("%s:%d/hsm/v2/Inventory/EthernetInterfaces", client.Host, client.Port)
|
|
||||||
req, err := http.NewRequest(http.MethodGet, url, bytes.NewBuffer([]byte{}))
|
|
||||||
|
|
||||||
// include access token in authorzation header if found
|
// fetch DHCP related information from SMD's endpoint:
|
||||||
if client.AccessToken != "" {
|
url := fmt.Sprintf("%s:%d/hsm/v2%s", client.Host, client.Port, endpoint)
|
||||||
req.Header.Add("Authorization", "Bearer "+client.AccessToken)
|
req, err := http.NewRequest(http.MethodGet, url, bytes.NewBuffer([]byte{}))
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create new HTTP request: %v", err)
|
return nil, fmt.Errorf("failed to create new HTTP request: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// include access token in authorzation header if found
|
||||||
|
// NOTE: This shouldn't be needed for this endpoint since it's public
|
||||||
|
if client.AccessToken != "" {
|
||||||
|
req.Header.Add("Authorization", "Bearer "+client.AccessToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
// make the request to SMD
|
||||||
res, err := client.Do(req)
|
res, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to make request: %v", err)
|
return nil, fmt.Errorf("failed to make request: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := io.ReadAll(res.Body)
|
// read the contents of the response body
|
||||||
if err != nil {
|
return io.ReadAll(res.Body)
|
||||||
return nil, fmt.Errorf("failed to read HTTP response: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// unmarshal JSON and extract
|
|
||||||
eths := []EthernetInterface{} // []map[string]any{}
|
|
||||||
json.Unmarshal(b, ðs)
|
|
||||||
fmt.Printf("ethernet interfaces: %v\n", string(b))
|
|
||||||
|
|
||||||
return eths, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,45 +8,53 @@ import (
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Options struct {
|
type Options struct{}
|
||||||
JwksUri string `yaml:"jwks-uri"`
|
|
||||||
JwksRetries int `yaml:"jwks-retries"`
|
type Jwks struct {
|
||||||
|
Uri string `yaml:"uri"`
|
||||||
|
Retries int `yaml:"retries"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
Host string `yaml:"host"`
|
Host string `yaml:"host"`
|
||||||
Port int `yaml:"port"`
|
Port int `yaml:"port"`
|
||||||
|
Jwks Jwks `yaml:"jwks"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Version string `yaml:"version"`
|
Version string `yaml:"version"`
|
||||||
SmdHost string `yaml:"smd-host"`
|
Server Server `yaml:"server"`
|
||||||
SmdPort int `yaml:"smd-port"`
|
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"`
|
||||||
Server Server `yaml:"server"`
|
Plugins []string `yaml:"plugins"`
|
||||||
Options Options `yaml:"options"`
|
Options Options `yaml:"options"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConfig() Config {
|
func NewConfig() Config {
|
||||||
return Config{
|
return Config{
|
||||||
Version: "",
|
Version: "",
|
||||||
SmdHost: "http://127.0.0.1",
|
SmdClient: SmdClient{
|
||||||
SmdPort: 27779,
|
Host: "http://127.0.0.1",
|
||||||
TemplatePaths: map[string]string{
|
Port: 27779,
|
||||||
"dnsmasq": "templates/dhcp/dnsmasq.conf",
|
|
||||||
"syslog": "templates/syslog/",
|
|
||||||
"ansible": "templates/ansible",
|
|
||||||
"powerman": "templates/powerman",
|
|
||||||
"conman": "templates/conman",
|
|
||||||
},
|
},
|
||||||
|
TemplatePaths: map[string]string{
|
||||||
|
"dnsmasq": "templates/dnsmasq.jinja",
|
||||||
|
"syslog": "templates/syslog.jinja",
|
||||||
|
"ansible": "templates/ansible.jinja",
|
||||||
|
"powerman": "templates/powerman.jinja",
|
||||||
|
"conman": "templates/conman.jinja",
|
||||||
|
},
|
||||||
|
Plugins: []string{},
|
||||||
Server: Server{
|
Server: Server{
|
||||||
Host: "127.0.0.1",
|
Host: "127.0.0.1",
|
||||||
Port: 3334,
|
Port: 3334,
|
||||||
|
Jwks: Jwks{
|
||||||
|
Uri: "",
|
||||||
|
Retries: 5,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Options: Options{
|
Options: Options{},
|
||||||
JwksUri: "",
|
|
||||||
JwksRetries: 5,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,11 @@ type EthernetInterface struct {
|
||||||
IpAddresses []IPAddr
|
IpAddresses []IPAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
type DHCP struct {
|
type Component struct {
|
||||||
Hostname string
|
}
|
||||||
MacAddress string
|
|
||||||
IpAddress []IPAddr
|
type Node struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type BMC struct {
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,50 +2,162 @@ package generator
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"plugin"
|
||||||
|
|
||||||
configurator "github.com/OpenCHAMI/configurator/internal"
|
configurator "github.com/OpenCHAMI/configurator/internal"
|
||||||
|
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Generator struct {
|
type Mappings = map[string]any
|
||||||
Type string
|
type Generator interface {
|
||||||
Template string
|
GetName() string
|
||||||
|
GetGroups() []string
|
||||||
|
Generate(config *configurator.Config, opts ...util.Option) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() *Generator {
|
func LoadPlugin(path string) (Generator, error) {
|
||||||
return &Generator{}
|
p, err := plugin.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to load plugin: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
symbol, err := p.Lookup("Generator")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to look up symbol: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
gen, ok := symbol.(Generator)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("failed to load the correct symbol type")
|
||||||
|
}
|
||||||
|
return gen, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Generator) GenerateDNS(config *configurator.Config) {
|
func LoadPlugins(dirpath string, opts ...util.Option) (map[string]Generator, error) {
|
||||||
// generate file using jinja template
|
// check if verbose option is supplied
|
||||||
// TODO: load template file for DNS
|
var (
|
||||||
// TODO: substitute DNS data fetched from SMD
|
gens = make(map[string]Generator)
|
||||||
// TODO: print generated config file to STDOUT
|
params = util.GetParams(opts...)
|
||||||
|
)
|
||||||
|
|
||||||
|
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 {
|
||||||
|
if item.IsDir() {
|
||||||
|
subitems, _ := os.ReadDir(item.Name())
|
||||||
|
for _, subitem := range subitems {
|
||||||
|
if !subitem.IsDir() {
|
||||||
|
gen, err := LoadGenerator(subitem.Name())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("failed to load generator in directory '%s': %v\n", item.Name(), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if verbose, ok := params["verbose"].(bool); ok {
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf("found plugin '%s'\n", item.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gens[gen.GetName()] = gen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
gen, err := LoadGenerator(dirpath + item.Name())
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("failed to load generator: %v\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if verbose, ok := params["verbose"].(bool); ok {
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf("found plugin '%s'\n", dirpath+item.Name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gens[gen.GetName()] = gen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return gens, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Generator) GenerateDHCP(config *configurator.Config, eths []configurator.EthernetInterface) ([]byte, error) {
|
func WithTemplate(_template string) util.Option {
|
||||||
// generate file using gonja template
|
return func(p util.Params) {
|
||||||
path := config.TemplatePaths[g.Template]
|
if p != nil {
|
||||||
fmt.Printf("path: %s\neth count: %v\n", path, len(eths))
|
p["template"] = _template
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithType(_type string) util.Option {
|
||||||
|
return func(p util.Params) {
|
||||||
|
if p != nil {
|
||||||
|
p["type"] = _type
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithClient(client configurator.SmdClient) util.Option {
|
||||||
|
return func(p util.Params) {
|
||||||
|
p["client"] = client
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Syntactic sugar generic function to get parameter from util.Params.
|
||||||
|
func Get[T any](params util.Params, key string) *T {
|
||||||
|
if v, ok := params[key].(T); ok {
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper function to get client in generator plugins.
|
||||||
|
func GetClient(params util.Params) *configurator.SmdClient {
|
||||||
|
return Get[configurator.SmdClient](params, "client")
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetParams(opts ...util.Option) util.Params {
|
||||||
|
params := util.Params{}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(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) {
|
||||||
|
data := exec.NewContext(mappings)
|
||||||
|
|
||||||
|
// load jinja template from file
|
||||||
t, err := gonja.FromFile(path)
|
t, err := gonja.FromFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read template from file: %v", err)
|
return nil, fmt.Errorf("failed to read template from file: %v", err)
|
||||||
}
|
}
|
||||||
template := "# ========== GENERATED BY OCHAMI CONFIGURATOR ==========\n"
|
|
||||||
for _, eth := range eths {
|
// execute/render jinja template
|
||||||
if eth.Type == "NodeBMC" {
|
|
||||||
template += "dhcp-host=" + eth.MacAddress + "," + eth.ComponentId + "," + eth.IpAddresses[0].IpAddress + "\n"
|
|
||||||
} else {
|
|
||||||
template += "dhcp-host=" + eth.MacAddress + "," + eth.ComponentId + "," + eth.IpAddresses[0].IpAddress + "\n"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
template += "# ======================================================"
|
|
||||||
data := exec.NewContext(map[string]any{
|
|
||||||
"hosts": template,
|
|
||||||
})
|
|
||||||
b := bytes.Buffer{}
|
b := bytes.Buffer{}
|
||||||
if err = t.Execute(&b, data); err != nil {
|
if err = t.Execute(&b, data); err != nil {
|
||||||
return nil, fmt.Errorf("failed to execute: %v", err)
|
return nil, fmt.Errorf("failed to execute: %v", err)
|
||||||
|
|
|
||||||
46
internal/generator/plugins/conman/conman.go
Normal file
46
internal/generator/plugins/conman/conman.go
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
configurator "github.com/OpenCHAMI/configurator/internal"
|
||||||
|
"github.com/OpenCHAMI/configurator/internal/generator"
|
||||||
|
"github.com/OpenCHAMI/configurator/internal/util"
|
||||||
|
"github.com/nikolalohinski/gonja/v2"
|
||||||
|
"github.com/nikolalohinski/gonja/v2/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Conman struct{}
|
||||||
|
|
||||||
|
func (g *Conman) GetName() string {
|
||||||
|
return "conman"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Conman) GetGroups() []string {
|
||||||
|
return []string{"conman"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Conman) Generate(config *configurator.Config, opts ...util.Option) ([]byte, error) {
|
||||||
|
params := generator.GetParams(opts...)
|
||||||
|
var (
|
||||||
|
template = params["template"].(string)
|
||||||
|
path = config.TemplatePaths[template]
|
||||||
|
)
|
||||||
|
data := exec.NewContext(map[string]any{})
|
||||||
|
|
||||||
|
t, err := gonja.FromFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read template from file: %v", err)
|
||||||
|
}
|
||||||
|
output := "# ========== GENERATED BY OCHAMI CONFIGURATOR ==========\n"
|
||||||
|
output += "# ======================================================"
|
||||||
|
b := bytes.Buffer{}
|
||||||
|
if err = t.Execute(&b, data); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to execute: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var Generator Conman
|
||||||
22
internal/generator/plugins/coredhcp/coredhcp.go
Normal file
22
internal/generator/plugins/coredhcp/coredhcp.go
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
configurator "github.com/OpenCHAMI/configurator/internal"
|
||||||
|
"github.com/OpenCHAMI/configurator/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CoreDhcp struct{}
|
||||||
|
|
||||||
|
func (g *CoreDhcp) GetName() string {
|
||||||
|
return "coredhcp"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *CoreDhcp) GetGroups() []string {
|
||||||
|
return []string{"coredhcp"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *CoreDhcp) Generate(config *configurator.Config, opts ...util.Option) ([]byte, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var Generator CoreDhcp
|
||||||
89
internal/generator/plugins/dnsmasq/dnsmasq.go
Normal file
89
internal/generator/plugins/dnsmasq/dnsmasq.go
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
configurator "github.com/OpenCHAMI/configurator/internal"
|
||||||
|
"github.com/OpenCHAMI/configurator/internal/generator"
|
||||||
|
"github.com/OpenCHAMI/configurator/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DnsMasq struct{}
|
||||||
|
|
||||||
|
func TestGenerateDnsMasq() {
|
||||||
|
var (
|
||||||
|
g = DnsMasq{}
|
||||||
|
config = &configurator.Config{}
|
||||||
|
client = configurator.SmdClient{}
|
||||||
|
)
|
||||||
|
g.Generate(
|
||||||
|
config,
|
||||||
|
generator.WithTemplate("dnsmasq"),
|
||||||
|
generator.WithClient(client),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *DnsMasq) GetName() string {
|
||||||
|
return "dnsmasq"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *DnsMasq) GetGroups() []string {
|
||||||
|
return []string{"dnsmasq"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *DnsMasq) Generate(config *configurator.Config, opts ...util.Option) ([]byte, error) {
|
||||||
|
// make sure we have a valid config first
|
||||||
|
if config == nil {
|
||||||
|
return nil, fmt.Errorf("invalid config (config is nil)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// set all the defaults for variables
|
||||||
|
var (
|
||||||
|
params = generator.GetParams(opts...)
|
||||||
|
template = params["template"].(string) // required param
|
||||||
|
path = config.TemplatePaths[template]
|
||||||
|
eths []configurator.EthernetInterface = nil
|
||||||
|
err error = nil
|
||||||
|
)
|
||||||
|
|
||||||
|
// if we have a client, try making the request for the ethernet interfaces
|
||||||
|
if client, ok := params["client"].(configurator.SmdClient); ok {
|
||||||
|
eths, err = client.FetchEthernetInterfaces(opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to fetch ethernet interfaces with client: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if we have the required params first
|
||||||
|
if eths == nil {
|
||||||
|
return nil, fmt.Errorf("invalid ethernet interfaces (variable is nil)")
|
||||||
|
}
|
||||||
|
if len(eths) <= 0 {
|
||||||
|
return nil, fmt.Errorf("no ethernet interfaces found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// print message if verbose param found
|
||||||
|
if verbose, ok := params["verbose"].(bool); ok {
|
||||||
|
if verbose {
|
||||||
|
fmt.Printf("path: %s\neth count: %v\n", path, len(eths))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// format output to write to config file
|
||||||
|
output := "# ========== GENERATED BY OCHAMI CONFIGURATOR ==========\n"
|
||||||
|
for _, eth := range eths {
|
||||||
|
if eth.Type == "NodeBMC" {
|
||||||
|
output += "dhcp-host=" + eth.MacAddress + "," + eth.ComponentId + "," + eth.IpAddresses[0].IpAddress + "\n"
|
||||||
|
} else {
|
||||||
|
output += "dhcp-host=" + eth.MacAddress + "," + eth.ComponentId + "," + eth.IpAddresses[0].IpAddress + "\n"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
output += "# ======================================================"
|
||||||
|
|
||||||
|
// apply template substitutions and return output as byte array
|
||||||
|
return generator.ApplyTemplate(path, generator.Mappings{
|
||||||
|
"hosts": output,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var Generator DnsMasq
|
||||||
22
internal/generator/plugins/powerman/powerman.go
Normal file
22
internal/generator/plugins/powerman/powerman.go
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
configurator "github.com/OpenCHAMI/configurator/internal"
|
||||||
|
"github.com/OpenCHAMI/configurator/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Powerman struct{}
|
||||||
|
|
||||||
|
func (g *Powerman) GetName() string {
|
||||||
|
return "powerman"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Powerman) GetGroups() []string {
|
||||||
|
return []string{"powerman"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Powerman) Generate(config *configurator.Config, opts ...util.Option) ([]byte, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var Generator Powerman
|
||||||
22
internal/generator/plugins/syslog/syslog.go
Normal file
22
internal/generator/plugins/syslog/syslog.go
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
configurator "github.com/OpenCHAMI/configurator/internal"
|
||||||
|
"github.com/OpenCHAMI/configurator/internal/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Syslog struct{}
|
||||||
|
|
||||||
|
func (g *Syslog) GetName() string {
|
||||||
|
return "syslog"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Syslog) GetGroups() []string {
|
||||||
|
return []string{"log"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Syslog) Generate(config *configurator.Config, opts ...util.Option) ([]byte, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var Generator Syslog
|
||||||
44
internal/generator/plugins/warewulf/warewulf.go
Normal file
44
internal/generator/plugins/warewulf/warewulf.go
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
configurator "github.com/OpenCHAMI/configurator/internal"
|
||||||
|
"github.com/nikolalohinski/gonja/v2"
|
||||||
|
"github.com/nikolalohinski/gonja/v2/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Warewulf struct{}
|
||||||
|
|
||||||
|
func (g *Warewulf) GetName() string {
|
||||||
|
return "warewulf"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Warewulf) GetGroups() []string {
|
||||||
|
return []string{"warewulf"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *Warewulf) Generate(config *configurator.Config, template string) ([]byte, error) {
|
||||||
|
var (
|
||||||
|
path = config.TemplatePaths[template]
|
||||||
|
)
|
||||||
|
|
||||||
|
t, err := gonja.FromFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read template from file: %v", err)
|
||||||
|
}
|
||||||
|
output := "# ========== GENERATED BY OCHAMI CONFIGURATOR ==========\n"
|
||||||
|
|
||||||
|
output += "# ======================================================"
|
||||||
|
data := exec.NewContext(map[string]any{
|
||||||
|
"hosts": output,
|
||||||
|
})
|
||||||
|
b := bytes.Buffer{}
|
||||||
|
if err = t.Execute(&b, data); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to execute: %v", err)
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var Generator Warewulf
|
||||||
2
internal/schema.go
Normal file
2
internal/schema.go
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
// TODO: implement a way to fetch schemas from node orchestrator
|
||||||
|
package configurator
|
||||||
|
|
@ -9,7 +9,6 @@ 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"
|
||||||
|
|
@ -22,29 +21,33 @@ var (
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
*http.Server
|
*http.Server
|
||||||
|
JwksUri string `yaml:"jwks-uri"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func New() *Server {
|
func New() *Server {
|
||||||
return &Server{
|
return &Server{
|
||||||
Server: &http.Server{},
|
Server: &http.Server{
|
||||||
|
Addr: "localhost:3334",
|
||||||
|
},
|
||||||
|
JwksUri: "",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) Start(config *configurator.Config) error {
|
func (s *Server) Start(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
|
||||||
client := &configurator.SmdClient{
|
_ = &configurator.SmdClient{
|
||||||
Host: config.SmdHost,
|
Host: config.SmdClient.Host,
|
||||||
Port: config.SmdPort,
|
Port: config.SmdClient.Port,
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the server address with config values
|
// set the server address with config values
|
||||||
s.Server.Addr = fmt.Sprintf("%s:%d", config.Server.Host, config.Server.Port)
|
s.Server.Addr = fmt.Sprintf("%s:%d", config.Server.Host, config.Server.Port)
|
||||||
|
|
||||||
// fetch JWKS public key from authorization server
|
// fetch JWKS public key from authorization server
|
||||||
if config.Options.JwksUri != "" && tokenAuth == nil {
|
if config.Server.Jwks.Uri != "" && tokenAuth == nil {
|
||||||
for i := 0; i < config.Options.JwksRetries; i++ {
|
for i := 0; i < config.Server.Jwks.Retries; i++ {
|
||||||
var err error
|
var err error
|
||||||
tokenAuth, err = configurator.FetchPublicKeyFromURL(config.Options.JwksUri)
|
tokenAuth, err = configurator.FetchPublicKeyFromURL(config.Server.Jwks.Uri)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Errorf("failed to fetch JWKS: %w", err)
|
logrus.Errorf("failed to fetch JWKS: %w", err)
|
||||||
continue
|
continue
|
||||||
|
|
@ -58,42 +61,42 @@ func (s *Server) Start(config *configurator.Config) error {
|
||||||
router.Use(middleware.RedirectSlashes)
|
router.Use(middleware.RedirectSlashes)
|
||||||
router.Use(middleware.Timeout(60 * time.Second))
|
router.Use(middleware.Timeout(60 * time.Second))
|
||||||
router.Group(func(r chi.Router) {
|
router.Group(func(r chi.Router) {
|
||||||
if config.Options.JwksUri != "" {
|
if config.Server.Jwks.Uri != "" {
|
||||||
r.Use(
|
r.Use(
|
||||||
jwtauth.Verifier(tokenAuth),
|
jwtauth.Verifier(tokenAuth),
|
||||||
jwtauth.Authenticator(tokenAuth),
|
jwtauth.Authenticator(tokenAuth),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
r.HandleFunc("/target", func(w http.ResponseWriter, r *http.Request) {
|
r.HandleFunc("/target", func(w http.ResponseWriter, r *http.Request) {
|
||||||
g := generator.Generator{
|
// g := generator.Generator{
|
||||||
Type: r.URL.Query().Get("type"),
|
// Type: r.URL.Query().Get("type"),
|
||||||
Template: r.URL.Query().Get("template"),
|
// Template: r.URL.Query().Get("template"),
|
||||||
}
|
// }
|
||||||
|
|
||||||
// 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 g.Type == "dhcp" {
|
// if _type == "dhcp" {
|
||||||
// fetch eths from SMD
|
// // fetch eths from SMD
|
||||||
eths, err := client.FetchEthernetInterfaces()
|
// eths, err := client.FetchEthernetInterfaces()
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
logrus.Errorf("failed to fetch DHCP metadata: %v\n", err)
|
// logrus.Errorf("failed to fetch DHCP metadata: %v\n", err)
|
||||||
w.Write([]byte("An error has occurred"))
|
// w.Write([]byte("An error has occurred"))
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
if len(eths) <= 0 {
|
// if len(eths) <= 0 {
|
||||||
logrus.Warnf("no ethernet interfaces found")
|
// logrus.Warnf("no ethernet interfaces found")
|
||||||
w.Write([]byte("no ethernet interfaces found"))
|
// w.Write([]byte("no ethernet interfaces found"))
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
// generate a new config from that data
|
// // generate a new config from that data
|
||||||
|
|
||||||
b, err := g.GenerateDHCP(config, eths)
|
// // b, err := g.GenerateDHCP(config, eths)
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
logrus.Errorf("failed to generate DHCP: %v", err)
|
// logrus.Errorf("failed to generate DHCP: %v", err)
|
||||||
w.Write([]byte("An error has occurred."))
|
// w.Write([]byte("An error has occurred."))
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
w.Write(b)
|
// w.Write(b)
|
||||||
}
|
// }
|
||||||
})
|
})
|
||||||
r.HandleFunc("/templates", func(w http.ResponseWriter, r *http.Request) {
|
r.HandleFunc("/templates", func(w http.ResponseWriter, r *http.Request) {
|
||||||
// TODO: handle GET request
|
// TODO: handle GET request
|
||||||
|
|
|
||||||
37
internal/util/params.go
Normal file
37
internal/util/params.go
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"golang.org/x/exp/maps"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Params = map[string]any
|
||||||
|
type Option func(Params)
|
||||||
|
|
||||||
|
func GetParams(opts ...Option) Params {
|
||||||
|
params := Params{}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(params)
|
||||||
|
}
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
|
func OptionExists(params Params, opt string) bool {
|
||||||
|
var k []string = maps.Keys(params)
|
||||||
|
return slices.Contains(k, opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assert that the options exists within the params map
|
||||||
|
func AssertOptionsExist(params Params, opts ...string) []string {
|
||||||
|
foundKeys := []string{}
|
||||||
|
for k := range params {
|
||||||
|
index := slices.IndexFunc(opts, func(s string) bool {
|
||||||
|
return s == k
|
||||||
|
})
|
||||||
|
if index >= 0 {
|
||||||
|
foundKeys = append(foundKeys, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return foundKeys
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue