refactor: initial commit for major rewrite
This commit is contained in:
parent
3253cb8bbb
commit
bfd83f35a3
45 changed files with 439 additions and 1733 deletions
|
|
@ -1,32 +0,0 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/OpenCHAMI/configurator/pkg/config"
|
||||
"github.com/OpenCHAMI/configurator/pkg/util"
|
||||
)
|
||||
|
||||
var configCmd = &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "Create a new default config file",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// create a new config at all args (paths)
|
||||
//
|
||||
// TODO: change this to only take a single arg since more
|
||||
// than one arg is *maybe* a mistake
|
||||
for _, path := range args {
|
||||
// check and make sure something doesn't exist first
|
||||
if exists, err := util.PathExists(path); exists || err != nil {
|
||||
log.Error().Err(err).Msg("file or directory exists")
|
||||
continue
|
||||
}
|
||||
config.SaveDefault(path)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(configCmd)
|
||||
}
|
||||
7
cmd/download.go
Normal file
7
cmd/download.go
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
package cmd
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
var downloadCmd = cobra.Command{
|
||||
Use: "download",
|
||||
}
|
||||
77
cmd/fetch.go
77
cmd/fetch.go
|
|
@ -1,77 +0,0 @@
|
|||
//go:build client || all
|
||||
// +build client all
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/OpenCHAMI/configurator/pkg/util"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var fetchCmd = &cobra.Command{
|
||||
Use: "fetch",
|
||||
Short: "Fetch a config file from a remote instance of configurator",
|
||||
Long: "This command is simplified to make a HTTP request to the a configurator service.",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// make sure a host is set
|
||||
if remoteHost == "" {
|
||||
log.Error().Msg("no '--host' argument set")
|
||||
return
|
||||
}
|
||||
|
||||
// check if we actually have any targets to run
|
||||
if len(targets) <= 0 {
|
||||
log.Error().Msg("must specify a target")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// check to see if an access token is available from env
|
||||
if conf.AccessToken == "" {
|
||||
// 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 != "" {
|
||||
conf.AccessToken = accessToken
|
||||
} else {
|
||||
// TODO: try and fetch token first if it is needed
|
||||
if verbose {
|
||||
log.Warn().Msg("No token found. Attempting to generate config without one...")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add the "Authorization" header if an access token is supplied
|
||||
headers := map[string]string{}
|
||||
if accessToken != "" {
|
||||
headers["Authorization"] = "Bearer " + accessToken
|
||||
}
|
||||
|
||||
for _, target := range targets {
|
||||
// make a request for each target
|
||||
url := fmt.Sprintf("%s/generate?target=%s", remoteHost, target)
|
||||
res, body, err := util.MakeRequest(url, http.MethodGet, nil, headers)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to fetch files")
|
||||
return
|
||||
}
|
||||
// handle getting other error codes other than a 200
|
||||
if res != nil {
|
||||
// NOTE: the server responses are already marshaled to JSON
|
||||
fmt.Print(string(body))
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
fetchCmd.Flags().StringVar(&remoteHost, "host", "", "set the remote configurator host and port")
|
||||
fetchCmd.Flags().StringSliceVar(&targets, "target", nil, "set the target configs to make")
|
||||
fetchCmd.Flags().StringVarP(&outputPath, "output", "o", "", "set the output path for config targets")
|
||||
fetchCmd.Flags().StringVar(&accessToken, "access-token", "o", "set the output path for config targets")
|
||||
|
||||
rootCmd.AddCommand(fetchCmd)
|
||||
}
|
||||
212
cmd/generate.go
212
cmd/generate.go
|
|
@ -1,212 +0,0 @@
|
|||
//go:build client || all
|
||||
// +build client all
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/OpenCHAMI/configurator/pkg/client"
|
||||
"github.com/OpenCHAMI/configurator/pkg/config"
|
||||
"github.com/OpenCHAMI/configurator/pkg/generator"
|
||||
"github.com/OpenCHAMI/configurator/pkg/util"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
tokenFetchRetries int
|
||||
templatePaths []string
|
||||
pluginPath string
|
||||
useCompression bool
|
||||
)
|
||||
|
||||
var generateCmd = &cobra.Command{
|
||||
Use: "generate",
|
||||
Short: "Generate a config file from state management",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// make sure that we have a token present before trying to make request
|
||||
if conf.AccessToken == "" {
|
||||
// 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 != "" {
|
||||
conf.AccessToken = accessToken
|
||||
} else {
|
||||
// TODO: try and fetch token first if it is needed
|
||||
if verbose {
|
||||
log.Warn().Msg("No token found. Attempting to generate conf without one...\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// use cert path from cobra if empty
|
||||
if conf.CertPath == "" {
|
||||
conf.CertPath = cacertPath
|
||||
}
|
||||
|
||||
// show conf as JSON and generators if verbose
|
||||
if verbose {
|
||||
b, err := json.MarshalIndent(conf, "", " ")
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to marshal config")
|
||||
}
|
||||
// print the config file as JSON
|
||||
fmt.Printf("%v\n", string(b))
|
||||
}
|
||||
|
||||
// run all of the target recursively until completion if provided
|
||||
if len(targets) > 0 {
|
||||
RunTargets(&conf, args, targets...)
|
||||
} else {
|
||||
if pluginPath == "" {
|
||||
log.Error().Msg("no plugin path specified")
|
||||
return
|
||||
}
|
||||
|
||||
// load the templates to use
|
||||
templates := map[string]generator.Template{}
|
||||
for _, path := range templatePaths {
|
||||
template := generator.Template{}
|
||||
template.LoadFromFile(path)
|
||||
if !template.IsEmpty() {
|
||||
templates[path] = template
|
||||
}
|
||||
}
|
||||
|
||||
params := generator.Params{
|
||||
Templates: templates,
|
||||
}
|
||||
|
||||
// set the client options
|
||||
// params.ClientOpts = append(params.ClientOpts, client.WithHost(remoteHost))
|
||||
if conf.AccessToken != "" {
|
||||
params.ClientOpts = append(params.ClientOpts, client.WithAccessToken(conf.AccessToken))
|
||||
}
|
||||
if conf.CertPath != "" {
|
||||
params.ClientOpts = append(params.ClientOpts, client.WithCertPoolFile(conf.CertPath))
|
||||
}
|
||||
|
||||
// run generator.Generate() with just plugin path and templates provided
|
||||
outputBytes, err := generator.Generate(&conf, pluginPath, params)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to generate files")
|
||||
}
|
||||
|
||||
// if we have more than one target and output is set, create configs in directory
|
||||
outputMap := generator.ConvertContentsToString(outputBytes)
|
||||
writeOutput(outputBytes, len(targets), len(outputMap))
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// Generate files by supplying a list of targets as string values. Currently,
|
||||
// targets are defined statically in a config file. Targets are ran recursively
|
||||
// if more targets are nested in a defined target, but will not run additional
|
||||
// child targets if it is the same as the parent.
|
||||
//
|
||||
// NOTE: This may be changed in the future how this is done.
|
||||
func RunTargets(conf *config.Config, args []string, targets ...string) {
|
||||
// generate config with each supplied target
|
||||
for _, target := range targets {
|
||||
outputBytes, err := generator.GenerateWithTarget(conf, target)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("target", target).Msg("failed to generate config")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// if we have more than one target and output is set, create configs in directory
|
||||
outputMap := generator.ConvertContentsToString(outputBytes)
|
||||
writeOutput(outputBytes, len(targets), len(outputMap))
|
||||
|
||||
// remove any targets that are the same as current to prevent infinite loop
|
||||
nextTargets := util.CopyIf(conf.Targets[target].RunTargets, func(nextTarget string) bool {
|
||||
return nextTarget != target
|
||||
})
|
||||
|
||||
// ...then, run any other targets that the current target has
|
||||
RunTargets(conf, args, nextTargets...)
|
||||
}
|
||||
}
|
||||
|
||||
func writeOutput(outputBytes generator.FileMap, targetCount int, templateCount int) {
|
||||
outputMap := generator.ConvertContentsToString(outputBytes)
|
||||
if outputPath == "" {
|
||||
// write only to stdout by default
|
||||
if len(outputMap) == 1 {
|
||||
for _, contents := range outputMap {
|
||||
fmt.Printf("%s\n", string(contents))
|
||||
}
|
||||
} else {
|
||||
for path, contents := range outputMap {
|
||||
fmt.Printf("-- file: %s, size: %d B\n%s\n", path, len(contents), string(contents))
|
||||
}
|
||||
}
|
||||
} else if outputPath != "" && targetCount == 1 && templateCount == 1 {
|
||||
// write just a single file using provided name
|
||||
for _, contents := range outputBytes {
|
||||
err := os.WriteFile(outputPath, contents, 0o644)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("path", outputPath).Msg("failed to write config file")
|
||||
os.Exit(1)
|
||||
}
|
||||
log.Info().Msgf("wrote file to '%s'\n", outputPath)
|
||||
}
|
||||
} else if outputPath != "" && targetCount > 1 && useCompression {
|
||||
// write multiple files to archive, compress, then save to output path
|
||||
out, err := os.Create(fmt.Sprintf("%s.tar.gz", outputPath))
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("path", outputPath).Msg("failed to write archive")
|
||||
os.Exit(1)
|
||||
}
|
||||
files := make([]string, len(outputBytes))
|
||||
i := 0
|
||||
for path := range outputBytes {
|
||||
files[i] = path
|
||||
i++
|
||||
}
|
||||
err = util.CreateArchive(files, out)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("path", outputPath).Msg("failed to create archive")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
} else if outputPath != "" && targetCount > 1 || templateCount > 1 {
|
||||
// write multiple files in directory using template name
|
||||
err := os.MkdirAll(filepath.Clean(outputPath), 0o755)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("path", filepath.Clean(outputPath)).Msg("failed to make output directory")
|
||||
os.Exit(1)
|
||||
}
|
||||
for path, contents := range outputBytes {
|
||||
filename := filepath.Base(path)
|
||||
cleanPath := fmt.Sprintf("%s/%s", filepath.Clean(outputPath), filename)
|
||||
err := os.WriteFile(cleanPath, contents, 0o755)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("path", path).Msg("failed to write config to file")
|
||||
os.Exit(1)
|
||||
}
|
||||
log.Info().Msgf("wrote file to '%s'\n", cleanPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
generateCmd.Flags().StringSliceVar(&targets, "target", []string{}, "set the targets to run pre-defined conf")
|
||||
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().StringVarP(&outputPath, "output", "o", "", "set the output path for conf targets")
|
||||
generateCmd.Flags().IntVar(&tokenFetchRetries, "fetch-retries", 5, "set the number of retries to fetch an access token")
|
||||
generateCmd.Flags().StringVar(&remoteHost, "host", "http://localhost", "set the remote host")
|
||||
generateCmd.Flags().BoolVar(&useCompression, "compress", false, "set whether to archive and compress multiple file outputs")
|
||||
|
||||
// requires either 'target' by itself or 'plugin' and 'templates' together
|
||||
// generateCmd.MarkFlagsOneRequired("target", "plugin")
|
||||
generateCmd.MarkFlagsMutuallyExclusive("target", "plugin")
|
||||
generateCmd.MarkFlagsMutuallyExclusive("target", "template")
|
||||
generateCmd.MarkFlagsRequiredTogether("plugin", "template")
|
||||
|
||||
rootCmd.AddCommand(generateCmd)
|
||||
}
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/OpenCHAMI/configurator/pkg/generator"
|
||||
"github.com/OpenCHAMI/configurator/pkg/util"
|
||||
"github.com/rodaine/table"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
byTarget bool
|
||||
)
|
||||
|
||||
var inspectCmd = &cobra.Command{
|
||||
Use: "inspect",
|
||||
Short: "Inspect generator plugin information",
|
||||
Long: "The 'inspect' sub-command takes a list of directories and prints all found plugin information.",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// set up table formatter
|
||||
table.DefaultHeaderFormatter = func(format string, vals ...interface{}) string {
|
||||
return strings.ToUpper(fmt.Sprintf(format, vals...))
|
||||
}
|
||||
|
||||
// remove duplicate clean paths from CLI
|
||||
paths := make([]string, len(args))
|
||||
for _, path := range args {
|
||||
paths = append(paths, filepath.Clean(path))
|
||||
}
|
||||
paths = util.RemoveDuplicates(paths)
|
||||
|
||||
// load specific plugins from positional args
|
||||
var generators = make(map[string]generator.Generator)
|
||||
for _, path := range paths {
|
||||
err := filepath.Walk(path, func(path string, info fs.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
gen, err := generator.LoadPlugin(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
generators[gen.GetName()] = gen
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to walk directory")
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// print all generator plugin information found
|
||||
tbl := table.New("Name", "Version", "Description")
|
||||
for _, g := range generators {
|
||||
tbl.AddRow(g.GetName(), g.GetVersion(), g.GetDescription())
|
||||
}
|
||||
if len(generators) > 0 {
|
||||
tbl.Print()
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
inspectCmd.Flags().BoolVar(&byTarget, "by-target", false, "set whether to ")
|
||||
rootCmd.AddCommand(inspectCmd)
|
||||
}
|
||||
0
cmd/list.go
Normal file
0
cmd/list.go
Normal file
0
cmd/profiles.go
Normal file
0
cmd/profiles.go
Normal file
0
cmd/render.go
Normal file
0
cmd/render.go
Normal file
65
cmd/root.go
65
cmd/root.go
|
|
@ -4,35 +4,20 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/OpenCHAMI/configurator/pkg/config"
|
||||
"github.com/OpenCHAMI/configurator/pkg/util"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
conf config.Config
|
||||
configPath string
|
||||
cacertPath string
|
||||
verbose bool
|
||||
targets []string
|
||||
outputPath string
|
||||
accessToken string
|
||||
remoteHost string
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "configurator",
|
||||
Short: "Dynamically generate files defined by generators",
|
||||
var rootCmd = cobra.Command{
|
||||
Use: "configurator",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if len(args) == 0 {
|
||||
cmd.Help()
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
// run initialization code first
|
||||
initEnv()
|
||||
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
|
|
@ -40,39 +25,13 @@ func Execute() {
|
|||
}
|
||||
|
||||
func init() {
|
||||
cobra.OnInitialize(InitConfig)
|
||||
rootCmd.PersistentFlags().StringVarP(&configPath, "config", "c", "", "set the config path")
|
||||
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "set to enable verbose output")
|
||||
rootCmd.PersistentFlags().StringVar(&cacertPath, "cacert", "", "path to CA cert. (defaults to system CAs)")
|
||||
// initialize the config a single time
|
||||
}
|
||||
|
||||
func InitConfig() {
|
||||
// empty from not being set
|
||||
if configPath != "" {
|
||||
exists, err := util.PathExists(configPath)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("path", configPath).Msg("failed to load config")
|
||||
os.Exit(1)
|
||||
} else if exists {
|
||||
conf = config.Load(configPath)
|
||||
} else {
|
||||
// show error and exit since a path was specified
|
||||
log.Error().Str("path", configPath).Msg("config file not found")
|
||||
os.Exit(1)
|
||||
}
|
||||
} else {
|
||||
// set to the default value and create a new one
|
||||
configPath = "./config.yaml"
|
||||
conf = config.New()
|
||||
}
|
||||
func initConfigFromFile(path string) {
|
||||
|
||||
}
|
||||
|
||||
func initEnv() {
|
||||
|
||||
//
|
||||
// set environment variables to override config values
|
||||
//
|
||||
|
||||
// set the JWKS url if we find the CONFIGURATOR_JWKS_URL environment variable
|
||||
jwksUrl := os.Getenv("CONFIGURATOR_JWKS_URL")
|
||||
if jwksUrl != "" {
|
||||
conf.Server.Jwks.Uri = jwksUrl
|
||||
}
|
||||
}
|
||||
|
|
|
|||
67
cmd/serve.go
67
cmd/serve.go
|
|
@ -1,67 +0,0 @@
|
|||
//go:build server || all
|
||||
// +build server all
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/OpenCHAMI/configurator/pkg/server"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var serveCmd = &cobra.Command{
|
||||
Use: "serve",
|
||||
Short: "Start configurator as a server and listen for requests",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
// make sure that we have a token present before trying to make request
|
||||
if conf.AccessToken == "" {
|
||||
// check if ACCESS_TOKEN env var is set if no access token is provided and use that instead
|
||||
accessToken := os.Getenv("ACCESS_TOKEN")
|
||||
if accessToken != "" {
|
||||
conf.AccessToken = accessToken
|
||||
} else {
|
||||
if verbose {
|
||||
log.Warn().Msg("No token found. Continuing without one...\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// show config as JSON and generators if verbose
|
||||
if verbose {
|
||||
b, err := json.MarshalIndent(conf, "", "\t")
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("failed to marshal config")
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("%v\n", string(b))
|
||||
}
|
||||
|
||||
// start listening with the server
|
||||
var (
|
||||
s *server.Server = server.New(&conf)
|
||||
err error = s.Serve()
|
||||
)
|
||||
if errors.Is(err, http.ErrServerClosed) {
|
||||
if verbose {
|
||||
log.Info().Msg("server closed")
|
||||
}
|
||||
} else if err != nil {
|
||||
log.Error().Err(err).Msg("failed to start server")
|
||||
os.Exit(1)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
serveCmd.Flags().StringVar(&conf.Server.Host, "host", conf.Server.Host, "set the server host and port")
|
||||
// serveCmd.Flags().StringVar(&pluginPath, "plugin", "", "set the generator plugins directory path")
|
||||
serveCmd.Flags().StringVar(&conf.Server.Jwks.Uri, "jwks-uri", conf.Server.Jwks.Uri, "set the JWKS url to fetch public key")
|
||||
serveCmd.Flags().IntVar(&conf.Server.Jwks.Retries, "jwks-fetch-retries", conf.Server.Jwks.Retries, "set the JWKS fetch retry count")
|
||||
rootCmd.AddCommand(serveCmd)
|
||||
}
|
||||
14
cmd/upload.go
Normal file
14
cmd/upload.go
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package cmd
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
|
||||
var uploadCmd = &cobra.Command{
|
||||
Use: "upload",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(uploadCmd)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue