diff --git a/cmd/generate.go b/cmd/generate.go index 8ce2093..c3629c9 100644 --- a/cmd/generate.go +++ b/cmd/generate.go @@ -1,5 +1,5 @@ -//go:build client || all -// +build client all +///go:build client || all +/// +build client all package cmd @@ -20,6 +20,7 @@ var ( tokenFetchRetries int templatePaths []string pluginPath string + pluginArgs map[string]string cacertPath string useCompression bool ) @@ -61,13 +62,14 @@ var generateCmd = &cobra.Command{ RunTargets(&config, args, targets...) } else { if pluginPath == "" { - fmt.Printf("no plugin path specified") + log.Error().Msg("no plugin path specified") return } // run generator.Generate() with just plugin path and templates provided generator.Generate(&config, generator.Params{ PluginPath: pluginPath, + PluginArgs: pluginArgs, TemplatePaths: templatePaths, }) @@ -86,6 +88,8 @@ func RunTargets(config *configurator.Config, args []string, targets ...string) { for _, target := range targets { outputBytes, err := generator.GenerateWithTarget(config, generator.Params{ Args: args, + Host: remoteHost, + Port: remotePort, PluginPath: pluginPath, Target: target, Verbose: verbose, @@ -122,9 +126,10 @@ func RunTargets(config *configurator.Config, args []string, targets ...string) { } log.Info().Msgf("wrote file to '%s'\n", outputPath) } - } else if outputPath != "" && targetCount > 1 && useCompression { + } else if outputPath != "" && len(outputBytes) > 1 && useCompression { // write multiple files to archive, compress, then save to output path - out, err := os.Create(fmt.Sprintf("%s.tar.gz", outputPath)) + outputPath = fmt.Sprintf("%s.tar.gz", outputPath) + out, err := os.Create(outputPath) if err != nil { log.Error().Err(err).Msg("failed to write archive") os.Exit(1) @@ -140,7 +145,7 @@ func RunTargets(config *configurator.Config, args []string, targets ...string) { log.Error().Err(err).Msg("failed to create archive") os.Exit(1) } - + log.Info().Msgf("wrote file to '%s'\n", outputPath) } else if outputPath != "" && targetCount > 1 || templateCount > 1 { // write multiple files in directory using template name err := os.MkdirAll(filepath.Clean(outputPath), 0o755) @@ -174,6 +179,7 @@ func init() { generateCmd.Flags().StringSliceVar(&targets, "target", []string{}, "set the targets to run pre-defined config") generateCmd.Flags().StringSliceVar(&templatePaths, "template", []string{}, "set the paths for the Jinja 2 templates to use") generateCmd.Flags().StringVar(&pluginPath, "plugin", "", "set the generator plugin path") + generateCmd.Flags().StringToStringVar(&pluginArgs, "plugin-args", map[string]string{}, "set the generate plugin args as key-value pairs") generateCmd.Flags().StringVarP(&outputPath, "output", "o", "", "set the output path for config targets") generateCmd.Flags().StringVar(&cacertPath, "cacert", "", "path to CA cert. (defaults to system CAs)") generateCmd.Flags().IntVar(&tokenFetchRetries, "fetch-retries", 5, "set the number of retries to fetch an access token") diff --git a/go.mod b/go.mod index e9ff5f2..a2e3c6f 100644 --- a/go.mod +++ b/go.mod @@ -6,12 +6,12 @@ require ( github.com/OpenCHAMI/jwtauth/v5 v5.0.0-20240321222802-e6cb468a2a18 github.com/go-chi/chi/v5 v5.1.0 github.com/lestrrat-go/jwx/v2 v2.1.1 + github.com/mitchellh/mapstructure v1.5.0 github.com/nikolalohinski/gonja/v2 v2.2.0 github.com/openchami/chi-middleware/auth v0.0.0-20240812224658-b16b83c70700 github.com/openchami/chi-middleware/log v0.0.0-20240812224658-b16b83c70700 github.com/rodaine/table v1.2.0 github.com/rs/zerolog v1.33.0 - github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.8.0 golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 gopkg.in/yaml.v2 v2.4.0 @@ -35,6 +35,7 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/segmentio/asm v1.2.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/pflag v1.0.5 // indirect golang.org/x/crypto v0.25.0 // indirect golang.org/x/sys v0.22.0 // indirect diff --git a/go.sum b/go.sum index 8452dee..d68830c 100644 --- a/go.sum +++ b/go.sum @@ -53,6 +53,8 @@ github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APP github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= diff --git a/pkg/client.go b/pkg/client.go index ddbfab0..854fbe0 100644 --- a/pkg/client.go +++ b/pkg/client.go @@ -38,25 +38,25 @@ func NewSmdClient(opts ...ClientOption) SmdClient { return client } -func WithHost(host string) ClientOption { +func WithClientHost(host string) ClientOption { return func(c *SmdClient) { c.Host = host } } -func WithPort(port int) ClientOption { +func WithClientPort(port int) ClientOption { return func(c *SmdClient) { c.Port = port } } -func WithAccessToken(token string) ClientOption { +func WithClientAccessToken(token string) ClientOption { return func(c *SmdClient) { c.AccessToken = token } } -func WithCertPool(certPool *x509.CertPool) ClientOption { +func WithClientCertPool(certPool *x509.CertPool) ClientOption { return func(c *SmdClient) { c.Client.Transport = &http.Transport{ TLSClientConfig: &tls.Config{ @@ -82,13 +82,7 @@ func WithCertPoolFile(certPath string) ClientOption { 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 - } + return WithClientCertPool(certPool) } // Create a set of params with all default values. @@ -98,16 +92,56 @@ func NewParams() util.Params { } } -// 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 WithNetwork(network string) util.Option { + return func(p util.Params) { + p["network"] = network + } +} + +func WithIPAddress(ipAddr string) util.Option { + return func(p util.Params) { + p["ip"] = ipAddr + } +} + +func GetNetwork(p util.Params) string { + if network, ok := p["network"].(string); ok { + return network + } + + // default value + return "" +} + +func GetIPAddress(p util.Params) string { + if ip, ok := p["ip"].(string); ok { + return ip + } + + // default value + return "" +} + +// 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. +// +// TODO: Change the `Option` type being used here to reduce it's scope. func (client *SmdClient) FetchEthernetInterfaces(opts ...util.Option) ([]EthernetInterface, error) { var ( - params = util.ToDict(opts...) - verbose = util.Get[bool](params, "verbose") - eths = []EthernetInterface{} + params = util.ToDict(opts...) + verbose = util.GetVerbose(params) + network = GetNetwork(params) + eths = []EthernetInterface{} + endpoint = "/Inventory/EthernetInterfaces" ) + + // add network to endpoint fetch filter by network type + if network != "" { + endpoint = fmt.Sprintf("%s?Network=%s", endpoint, network) + } + // make request to SMD endpoint - b, err := client.makeRequest("/Inventory/EthernetInterfaces") + b, err := client.makeRequest(endpoint) if err != nil { return nil, fmt.Errorf("failed to read HTTP response: %v", err) } @@ -119,10 +153,8 @@ func (client *SmdClient) FetchEthernetInterfaces(opts ...util.Option) ([]Etherne } // 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(b)) } return eths, nil diff --git a/pkg/generator/generator.go b/pkg/generator/generator.go index 9a00545..68b5b69 100644 --- a/pkg/generator/generator.go +++ b/pkg/generator/generator.go @@ -1,7 +1,6 @@ package generator import ( - "bytes" "fmt" "io/fs" "os" @@ -10,8 +9,6 @@ import ( configurator "github.com/OpenCHAMI/configurator/pkg" "github.com/OpenCHAMI/configurator/pkg/util" - "github.com/nikolalohinski/gonja/v2" - "github.com/nikolalohinski/gonja/v2/exec" ) type Mappings map[string]any @@ -29,11 +26,16 @@ type Generator interface { } // Params defined and used by the "generate" subcommand. +// TODO: It may make more sense to just pass this to 'Generate()' instead of using +// the functional options pattern. type Params struct { Args []string + Host string + Port int Generators map[string]Generator TemplatePaths []string PluginPath string + PluginArgs map[string]string Target string Verbose bool } @@ -131,7 +133,7 @@ func LoadPlugins(dirpath string, opts ...util.Option) (map[string]Generator, err } // show the plugins found if verbose flag is set - if params.GetVerbose() { + if util.GetVerbose(params) { fmt.Printf("-- found plugin '%s'\n", gen.GetName()) } @@ -144,111 +146,34 @@ func LoadPlugins(dirpath string, opts ...util.Option) (map[string]Generator, err return nil, fmt.Errorf("failed to walk directory: %w", err) } - // items, _ := os.ReadDir(dirpath) - // for _, item := range items { - // if item.IsDir() { - // subitems, _ := os.ReadDir(item.Name()) - // for _, subitem := range subitems { - // if !subitem.IsDir() { - // gen, err := LoadPlugin(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 := LoadPlugin(dirpath + item.Name()) - // if err != nil { - // fmt.Printf("failed to load plugin: %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 generators, nil } -func LoadTemplate(path string) (Template, error) { - // skip loading template if path is a directory with no error - if isDir, err := util.IsDirectory(path); err == nil && isDir { - return nil, nil - } else if err != nil { - return nil, fmt.Errorf("failed to test if template path is directory: %w", err) - } - - // try and read the contents of the file - // NOTE: we don't care if this is actually a Jinja template - // or not...at least for now. - return os.ReadFile(path) -} - -func LoadTemplates(paths []string, opts ...util.Option) (map[string]Template, error) { - var ( - templates = make(map[string]Template) - params = util.ToDict(opts...) - ) - - for _, path := range paths { - err := filepath.Walk(path, func(path string, info fs.FileInfo, err error) error { - // skip trying to load generator plugin if directory or error - if info.IsDir() || err != nil { - return nil - } - - // load the contents of the template - template, err := LoadTemplate(path) - if err != nil { - return fmt.Errorf("failed to load generator in directory '%s': %w", path, err) - } - - // show the templates loaded if verbose flag is set - if params.GetVerbose() { - fmt.Printf("-- loaded tempalte '%s'\n", path) - } - - // map each template by the path it was loaded from - templates[path] = template - return nil - }) - - if err != nil { - return nil, fmt.Errorf("failed to walk directory: %w", err) +func With(key string, value any) util.Option { + return func(p util.Params) { + if p != nil { + p[key] = value } } - - return templates, nil } // Option to specify "target" in parameter map. This is used to set which generator // to use to generate a config file. func WithTarget(target string) util.Option { - return func(p util.Params) { - if p != nil { - p["target"] = target - } - } + return With("target", target) +} + +func WithArgs(args []string) util.Option { + return With("args", args) +} + +func WithPluginArgs(pluginArgs map[string]string) util.Option { + return With("plugin-args", pluginArgs) } // Option to specify "type" in parameter map. This is not currently used. func WithType(_type string) util.Option { - return func(p util.Params) { - if p != nil { - p["type"] = _type - } - } + return With("type", _type) } // Option to the plugin to load @@ -305,61 +230,20 @@ func GetParams(opts ...util.Option) util.Params { return params } -// Wrapper function to slightly abstract away some of the nuances with using gonja -// into a single function call. This function is *mostly* for convenience and -// simplication. If no paths are supplied, then no templates will be applied and -// there will be no output. -// -// The "FileList" returns a slice of byte arrays in the same order as the argument -// list supplied, but with the Jinja templating applied. -func ApplyTemplates(mappings Mappings, contents ...[]byte) (FileList, error) { - var ( - data = exec.NewContext(mappings) - outputs = FileList{} - ) - - for _, b := range contents { - // load jinja template from file - t, err := gonja.FromBytes(b) - if err != nil { - return nil, fmt.Errorf("failed to read template from file: %w", err) - } - - // execute/render jinja template - b := bytes.Buffer{} - if err = t.Execute(&b, data); err != nil { - return nil, fmt.Errorf("failed to execute: %w", err) - } - outputs = append(outputs, b.Bytes()) +func GetArgs(params util.Params) []string { + args := util.Get[[]string](params, "args") + if args != nil { + return *args } - - return outputs, nil + return nil } -// Wrapper function similiar to "ApplyTemplates" but takes file paths as arguments. -// This function will load templates from a file instead of using file contents. -func ApplyTemplateFromFiles(mappings Mappings, paths ...string) (FileMap, error) { - var ( - data = exec.NewContext(mappings) - outputs = FileMap{} - ) - - for _, path := range paths { - // load jinja template from file - t, err := gonja.FromFile(path) - if err != nil { - return nil, fmt.Errorf("failed to read template from file: %w", err) - } - - // execute/render jinja template - b := bytes.Buffer{} - if err = t.Execute(&b, data); err != nil { - return nil, fmt.Errorf("failed to execute: %w", err) - } - outputs[path] = b.Bytes() +func GetPluginArgs(params util.Params) map[string]string { + pluginArgs := util.Get[map[string]string](params, "plugin-args") + if pluginArgs != nil { + return *pluginArgs } - - return outputs, nil + return nil } // Generate() is the main function to generate a collection of files and returns them as a map. @@ -388,13 +272,14 @@ func Generate(config *configurator.Config, params Params) (FileMap, error) { // It is also call when running the configurator as a service with the "/generate" route. // // TODO: Separate loading plugins so we can load them once when running as a service. +// TODO: Handle host/port arguments from CLI flags correctly func GenerateWithTarget(config *configurator.Config, params Params) (FileMap, error) { // load generator plugins to generate configs or to print var ( client = configurator.NewSmdClient( - configurator.WithHost(config.SmdClient.Host), - configurator.WithPort(config.SmdClient.Port), - configurator.WithAccessToken(config.AccessToken), + configurator.WithClientHost(config.SmdClient.Host), + configurator.WithClientPort(config.SmdClient.Port), + configurator.WithClientAccessToken(config.AccessToken), configurator.WithCertPoolFile(config.CertPath), ) ) @@ -426,5 +311,7 @@ func GenerateWithTarget(config *configurator.Config, params Params) (FileMap, er config, WithTarget(generator.GetName()), WithClient(client), + WithPluginArgs(params.PluginArgs), + WithArgs(params.Args), ) } diff --git a/pkg/generator/plugins/nodes/nodes.go b/pkg/generator/plugins/nodes/nodes.go new file mode 100644 index 0000000..b306e1d --- /dev/null +++ b/pkg/generator/plugins/nodes/nodes.go @@ -0,0 +1,101 @@ +package main + +import ( + "fmt" + + configurator "github.com/OpenCHAMI/configurator/pkg" + "github.com/OpenCHAMI/configurator/pkg/generator" + "github.com/OpenCHAMI/configurator/pkg/util" + "github.com/mitchellh/mapstructure" + "github.com/rs/zerolog/log" +) + +type Nodes struct{} + +func (g *Nodes) GetName() string { + return "nodes" +} + +func (g *Nodes) GetVersion() string { + return util.GitCommit() +} + +func (g *Nodes) GetDescription() string { + return "Plugin for generating 'ospfd.conf' and 'zebra.conf' with hostnames." +} + +func (g *Nodes) Generate(config *configurator.Config, opts ...util.Option) (generator.FileMap, error) { + var ( + params = generator.GetParams(opts...) + client = generator.GetClient(params) + target = generator.GetTarget(config, params["target"].(string)) + ethHost string = configurator.GetIPAddress(params) + external []configurator.EthernetInterface = nil + internal []configurator.EthernetInterface = nil + ethExternal []map[string]any = nil + ethInternal []map[string]any = nil + // hostEth *configurator.EthernetInterface = nil + err error = nil + ) + + if ethHost != "" { + client.FetchEthernetInterfaces( + configurator.WithIPAddress(ethHost), + ) + _ = ethHost + } + + // if we have a client, try making the request for the ethernet interfaces + if client != nil { + fmt.Printf("host: %s:%d\n", client.Host, client.Port) + // try and get all external ethernet interfaces + external, err = client.FetchEthernetInterfaces( + configurator.WithNetwork("External"), + ) + if err != nil { + return nil, fmt.Errorf("failed to fetch external ethernet interfaces with client: %v", err) + } + // try and get all internal ethernet interfaces + internal, err = client.FetchEthernetInterfaces( + configurator.WithNetwork("Internal"), + ) + if err != nil { + return nil, fmt.Errorf("failed to fetch internal ethernet interfaces with client: %v", err) + } + // TODO: try and get host ethernet interface using hostname + + } + + // we can *maybe* get the {{ host.*_netmask }} from the IP address + CIDR + // otherwise, assume /24 + + err = mapstructure.Decode(internal, ðExternal) + if err != nil { + log.Error().Err(err).Msg("failed to decode internal interfaces") + } + + err = mapstructure.Decode(external, ðInternal) + if err != nil { + log.Error().Err(err).Msg("failed to decode external interfaces") + } + + // try to decode subnet mask from IP address if in CIDR notation + + // create a shim layer + return generator.ApplyTemplateFromFiles(generator.Mappings{ + "host.name": "", // ?? + "host.internal_interface": "", // get from cloud-init?? + "host.internal_subnet": "", // get from cloud-init?? + "host.internal_netmask": "", // ?? + "host.internal_ip": "", // ?? + "host.external_interface": "", // get from cloud-init?? + "host.external_subnet": "", // get from cloud-init?? + "host.external_netmask": "", // ?? + "host.external_ip": "", // ?? + "host": "", + "ext_ethernet_interfaces": ethExternal, + "int_ethernet_interfaces": ethInternal, + }, target.TemplatePaths...) +} + +var Generator Nodes diff --git a/pkg/generator/templates.go b/pkg/generator/templates.go new file mode 100644 index 0000000..cfbe43f --- /dev/null +++ b/pkg/generator/templates.go @@ -0,0 +1,121 @@ +package generator + +import ( + "bytes" + "fmt" + "io/fs" + "os" + "path/filepath" + + "github.com/OpenCHAMI/configurator/pkg/util" + "github.com/nikolalohinski/gonja/v2" + "github.com/nikolalohinski/gonja/v2/exec" +) + +func LoadTemplate(path string) (Template, error) { + // skip loading template if path is a directory with no error + if isDir, err := util.IsDirectory(path); err == nil && isDir { + return nil, nil + } else if err != nil { + return nil, fmt.Errorf("failed to test if template path is directory: %w", err) + } + + // try and read the contents of the file + // NOTE: we don't care if this is actually a Jinja template + // or not...at least for now. + return os.ReadFile(path) +} + +func LoadTemplates(paths []string, opts ...util.Option) (map[string]Template, error) { + var ( + templates = make(map[string]Template) + params = util.ToDict(opts...) + ) + + for _, path := range paths { + err := filepath.Walk(path, func(path string, info fs.FileInfo, err error) error { + // skip trying to load generator plugin if directory or error + if info.IsDir() || err != nil { + return nil + } + + // load the contents of the template + template, err := LoadTemplate(path) + if err != nil { + return fmt.Errorf("failed to load generator in directory '%s': %w", path, err) + } + + // show the templates loaded if verbose flag is set + if util.GetVerbose(params) { + fmt.Printf("-- loaded tempalte '%s'\n", path) + } + + // map each template by the path it was loaded from + templates[path] = template + return nil + }) + + if err != nil { + return nil, fmt.Errorf("failed to walk directory: %w", err) + } + } + + return templates, nil +} + +// Wrapper function to slightly abstract away some of the nuances with using gonja +// into a single function call. This function is *mostly* for convenience and +// simplication. If no paths are supplied, then no templates will be applied and +// there will be no output. +// +// The "FileList" returns a slice of byte arrays in the same order as the argument +// list supplied, but with the Jinja templating applied. +func ApplyTemplates(mappings Mappings, contents ...[]byte) (FileList, error) { + var ( + data = exec.NewContext(mappings) + outputs = FileList{} + ) + + for _, b := range contents { + // load jinja template from file + t, err := gonja.FromBytes(b) + if err != nil { + return nil, fmt.Errorf("failed to read template from file: %w", err) + } + + // execute/render jinja template + b := bytes.Buffer{} + if err = t.Execute(&b, data); err != nil { + return nil, fmt.Errorf("failed to execute: %w", err) + } + outputs = append(outputs, b.Bytes()) + } + + return outputs, nil +} + +// Wrapper function similiar to "ApplyTemplates" but takes file paths as arguments. +// This function will load templates from a file instead of using file contents. +func ApplyTemplateFromFiles(mappings Mappings, paths ...string) (FileMap, error) { + var ( + data = exec.NewContext(mappings) + outputs = FileMap{} + ) + + for _, path := range paths { + // load jinja template from file + t, err := gonja.FromFile(path) + if err != nil { + return nil, fmt.Errorf("failed to read template from file: %w", err) + } + + // execute/render jinja template + b := bytes.Buffer{} + if err = t.Execute(&b, data); err != nil { + return nil, fmt.Errorf("failed to execute: %w", err) + } + outputs[path] = b.Bytes() + } + + return outputs, nil +} diff --git a/pkg/server/server.go b/pkg/server/server.go index 89e0d71..c6d1af5 100644 --- a/pkg/server/server.go +++ b/pkg/server/server.go @@ -16,7 +16,6 @@ import ( "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/rs/zerolog" - "github.com/sirupsen/logrus" openchami_authenticator "github.com/openchami/chi-middleware/auth" openchami_logger "github.com/openchami/chi-middleware/log" @@ -76,7 +75,7 @@ func (s *Server) Serve() error { var err error tokenAuth, err = configurator.FetchPublicKeyFromURL(s.Config.Server.Jwks.Uri) if err != nil { - logrus.Errorf("failed to fetch JWKS: %w", err) + log.Error().Err(err).Msgf("failed to fetch JWKS from URL '%s'", s.Config.Server.Jwks.Uri) continue } break @@ -132,13 +131,18 @@ func (s *Server) Generate(w http.ResponseWriter, r *http.Request) { s.GeneratorParams.Target = r.URL.Query().Get("target") if s.GeneratorParams.Target == "" { writeErrorResponse(w, "must specify a target") + log.Error().Msg("must specify a target") return } + // get the IP address from the request + s.GeneratorParams.PluginArgs["host"] = r.Host + // generate a new config file from supplied params outputs, err := generator.GenerateWithTarget(s.Config, s.GeneratorParams) if err != nil { - writeErrorResponse(w, "failed to generate file: %w", err) + writeErrorResponse(w, "failed to generate file with target '%s': %w", s.GeneratorParams.Target, err) + log.Error().Err(err).Msgf("failed to generate file with target '%s'", s.GeneratorParams.Target) return } @@ -147,11 +151,13 @@ func (s *Server) Generate(w http.ResponseWriter, r *http.Request) { b, err := json.Marshal(tmp) if err != nil { writeErrorResponse(w, "failed to marshal output: %w", err) + log.Error().Err(err).Msg("failed to marshal output after converting contents to string") return } _, err = w.Write(b) if err != nil { writeErrorResponse(w, "failed to write response: %w", err) + log.Error().Err(err).Msg("failed to write response") return } } diff --git a/pkg/util/params.go b/pkg/util/params.go index 322d852..572ba52 100644 --- a/pkg/util/params.go +++ b/pkg/util/params.go @@ -60,11 +60,17 @@ func GetOpt[T any](opts []Option, key string) *T { return Get[T](ToDict(opts...), "required_claims") } -func (p Params) GetVerbose() bool { +func WithVerbosity() Option { + return func(p Params) { + p["verbose"] = true + } +} + +func GetVerbose(p Params) bool { if verbose, ok := p["verbose"].(bool); ok { return verbose } - // default setting + // default value return false } diff --git a/pkg/util/util.go b/pkg/util/util.go index 6ff13b0..b2adb39 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -65,6 +65,7 @@ func MakeRequest(url string, httpMethod string, body []byte, headers map[string] // Returns the git commit string by executing command. // NOTE: This currently requires git to be installed. +// NOTE: This also requires running within the repository. // TODO: Change how this is done to not require executing a command. func GitCommit() string { c := exec.Command("git", "rev-parse", "--short=8", "HEAD")