Skip to content

Commit

Permalink
Changed name of new flags. Restructured code. Enabled semi-generic bu…
Browse files Browse the repository at this point in the history
…iltin templates.
  • Loading branch information
normanjaeckel committed Jan 11, 2025
1 parent 9084d19 commit 5ecdbff
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 495 deletions.
201 changes: 132 additions & 69 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,25 +50,38 @@ func Cmd() *cobra.Command {
Args: cobra.ExactArgs(1),
}

tech := FlagTech(cmd)
builtinTemplate := FlagBuiltinTemplate(cmd)
tplFileOrDirName := FlagTpl(cmd)
configFileNames := FlagConfig(cmd)

cmd.RunE = func(cmd *cobra.Command, args []string) error {
if *tplFileOrDirName != "" && *builtinTemplate != BuiltinTemplateDefault {
return fmt.Errorf("flag --builtin-template must not be used together with flag --template")
}
dir := args[0]
if err := Config(dir, *tech, *tplFileOrDirName, *configFileNames); err != nil {
if err := Config(dir, *builtinTemplate, *tplFileOrDirName, *configFileNames); err != nil {
return fmt.Errorf("running Config(): %w", err)
}
return nil
}
return cmd
}

const techMsg = " must bei either \"docker-compose\" or \"kubernetes\""
// BuiltinTemplateDefault is the default builtin template which is used if the
// user does neigther provide a custom template nor give a builtin template.
const BuiltinTemplateDefault = "docker-compose"

// FlagTech setups the technology flag to the given cobra command.
func FlagTech(cmd *cobra.Command) *string {
return cmd.Flags().String("technology", "docker-compose", "create files for this deployment technology,"+techMsg)
func getBuiltinTemplateMsg() string {
var allNames []string
for _, obj := range allBuiltinTemplates {
allNames = append(allNames, obj.name)
}
return fmt.Sprintf(" must be one of the following: %s", strings.Join(allNames, ", "))
}

// FlagBuiltinTemplate setups the builtin-template flag to the given cobra command.
func FlagBuiltinTemplate(cmd *cobra.Command) *string {
return cmd.Flags().String("builtin-template", BuiltinTemplateDefault, "create files for this builtin deployment variant,"+getBuiltinTemplateMsg())
}

// FlagTpl setups the template flag to the given cobra command.
Expand Down Expand Up @@ -98,79 +111,129 @@ func Config(baseDir string, tech string, tplFileOrDirName string, configFileName
return nil
}

// CreateDirAndFiles creates the base directory and (re-)creates the deployment files
// according to the given technology and the given template. If tplFileOrDirName
// is empty, the default deployment file or directory is used. Use a truthy
// value for force to override existing files.
func CreateDirAndFiles(baseDir string, force bool, tech string, tplFileOrDirName string, cfg *YmlConfig) error {
switch tech {
case "docker-compose":
// Get template file from command line option or default
var tplFile []byte
var err error
if tplFileOrDirName == "" {
tplFile, err = deploymentTemplates.ReadFile(path.Join("templates", "docker-compose", "docker-compose.yml"))
if err != nil {
return fmt.Errorf("reading template file: %w", err)
}
} else {
tplFile, err = os.ReadFile(tplFileOrDirName)
if err != nil {
return fmt.Errorf("reading file %q: %w", tplFileOrDirName, err)
}
type builtinTemplateFunc struct {
name string
fn func(baseDir string, force bool, cfg *YmlConfig) error
}

var allBuiltinTemplates = []builtinTemplateFunc{
{name: "docker-compose", fn: builtinTemplateDockerCompose},
{name: "kubernetes", fn: builtinTemplateKubernetes},
}

func builtinTemplateByName(name string) (builtinTemplateFunc, bool) {
for _, obj := range allBuiltinTemplates {
if obj.name == name {
return obj, true
}
}
return builtinTemplateFunc{}, false
}

// Create directory
if err := os.MkdirAll(baseDir, os.ModePerm); err != nil {
return fmt.Errorf("creating directory at %q: %w", baseDir, err)
// CreateDirAndFiles creates the base directory and (re-)creates the deployment
// files according to the given template. If tplFileOrDirName is empty, the
// given builtin template file or directory is used. Use a truthy value for
// force to override existing files.
func CreateDirAndFiles(baseDir string, force bool, builtinTemplate string, tplFileOrDirName string, cfg *YmlConfig) error {
if tplFileOrDirName == "" {
obj, found := builtinTemplateByName(builtinTemplate)
if !found {
return fmt.Errorf("unknown builtin template %q,"+getBuiltinTemplateMsg(), builtinTemplate)
}
return obj.fn(baseDir, force, cfg)
}

// Create deployment file for Docker Compose
filename := filepath.Join(baseDir, cfg.Filename)
if err := CreateDeploymentFile(filename, force, tplFile, cfg); err != nil {
return fmt.Errorf("creating deployment file %q: %w", filename, err)
fileInfo, err := os.Stat(tplFileOrDirName)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("template file or directory %q does not exist", tplFileOrDirName)
}
return fmt.Errorf("checking file info of %q: %w", tplFileOrDirName, err)
}

return nil
if !fileInfo.IsDir() {
return customTemplateSingleFile(baseDir, force, tplFileOrDirName, cfg)
}

case "kubernetes":
// Get template directory from command line option or default
var tplDir fs.FS
var err error
if tplFileOrDirName == "" {
tplDir, err = fs.Sub(deploymentTemplates, path.Join("templates", "kubernetes"))
if err != nil {
return fmt.Errorf("retrieving subtree: %w", err)
}
} else {
fileInfo, err := os.Stat(tplFileOrDirName)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return fmt.Errorf("template file or directory %q does not exist", tplFileOrDirName)
}
return fmt.Errorf("checking file info of %q: %w", tplFileOrDirName, err)
}
if !fileInfo.IsDir() {
return fmt.Errorf("%q is not a directory", tplFileOrDirName)
}
tplDir = os.DirFS(tplFileOrDirName)
}
return customTemplateDirectory(baseDir, force, tplFileOrDirName, cfg)
}

// Create directory
if err := os.MkdirAll(baseDir, os.ModePerm); err != nil {
return fmt.Errorf("creating directory at %q: %w", baseDir, err)
}
func builtinTemplateDockerCompose(baseDir string, force bool, cfg *YmlConfig) error {
// Get default template file
tplFile, err := deploymentTemplates.ReadFile(path.Join("templates", "docker-compose.yml"))
if err != nil {
return fmt.Errorf("reading template file: %w", err)
}

// Create the deployment directory for Kubernetes
if err := CreateDeploymentFilesFromTree(baseDir, force, tplDir, cfg); err != nil {
return fmt.Errorf("creating deployment files at %q: %w", baseDir, err)
}
// Create directory
if err := os.MkdirAll(baseDir, os.ModePerm); err != nil {
return fmt.Errorf("creating directory at %q: %w", baseDir, err)
}

return nil
// Create deployment file for Docker Compose
filename := filepath.Join(baseDir, cfg.Filename)
if err := CreateDeploymentFile(filename, force, tplFile, cfg); err != nil {
return fmt.Errorf("creating deployment file %q: %w", filename, err)
}

return nil
}

default:
return fmt.Errorf("unknown technology %q,"+techMsg, tech)
func builtinTemplateKubernetes(baseDir string, force bool, cfg *YmlConfig) error {
// Get default template directory
tplDir, err := fs.Sub(deploymentTemplates, path.Join("templates", "kubernetes"))
if err != nil {
return fmt.Errorf("retrieving subtree: %w", err)
}

// Create directory
if err := os.MkdirAll(baseDir, os.ModePerm); err != nil {
return fmt.Errorf("creating directory at %q: %w", baseDir, err)
}

// Create the deployment directory for Kubernetes
if err := CreateDeploymentFilesFromTree(baseDir, force, tplDir, cfg); err != nil {
return fmt.Errorf("creating deployment files at %q: %w", baseDir, err)
}

return nil
}

func customTemplateSingleFile(baseDir string, force bool, tplFilename string, cfg *YmlConfig) error {
// Get file content
tplFile, err := os.ReadFile(tplFilename)
if err != nil {
return fmt.Errorf("reading file %q: %w", tplFilename, err)
}

// Create directory
if err := os.MkdirAll(baseDir, os.ModePerm); err != nil {
return fmt.Errorf("creating directory at %q: %w", baseDir, err)
}

// Create deployment file
filename := filepath.Join(baseDir, cfg.Filename)
if err := CreateDeploymentFile(filename, force, tplFile, cfg); err != nil {
return fmt.Errorf("creating deployment file %q: %w", filename, err)
}

return nil
}

func customTemplateDirectory(baseDir string, force bool, tplDirname string, cfg *YmlConfig) error {
tplDir := os.DirFS(tplDirname)

// Create directory
if err := os.MkdirAll(baseDir, os.ModePerm); err != nil {
return fmt.Errorf("creating directory at %q: %w", baseDir, err)
}

// Create the deployment directory for Kubernetes
if err := CreateDeploymentFilesFromTree(baseDir, force, tplDir, cfg); err != nil {
return fmt.Errorf("creating deployment files at %q: %w", baseDir, err)
}

return nil
}

// CreateDeploymentFilesFromTree walks through the FS containing templates and
Expand Down Expand Up @@ -208,8 +271,8 @@ func CreateDeploymentFilesFromTree(baseDir string, force bool, tplDir fs.FS, cfg
return nil
}

// CreateDeploymentFile builds a single deployment file to the given path. Use a truthy value for force
// to override an existing file.
// CreateDeploymentFile builds a single deployment file to the given path. Use a
// truthy value for force to override an existing file.
func CreateDeploymentFile(filename string, force bool, tplFile []byte, cfg *YmlConfig) error {
tmpl, err := template.New("Deployment File").Funcs(funcMap).Parse(string(tplFile))
if err != nil {
Expand Down
28 changes: 26 additions & 2 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
)

func TestCmd(t *testing.T) {
t.Run("executing setup.Cmd() with existing directory", func(t *testing.T) {
t.Run("executing config.Cmd() with existing directory", func(t *testing.T) {
testDir, err := os.MkdirTemp("", "openslides-manage-service-")
if err != nil {
t.Fatalf("generating temporary directory failed: %v", err)
Expand All @@ -30,7 +30,31 @@ func TestCmd(t *testing.T) {
}
})

t.Run("executing setup.CmdCreateDefault() with existing directory", func(t *testing.T) {
t.Run("executing config.Cmd() with existing directory with builtin-template flag and template flag", func(t *testing.T) {
testDir, err := os.MkdirTemp("", "openslides-manage-service-")
if err != nil {
t.Fatalf("generating temporary directory failed: %v", err)
}
defer os.RemoveAll(testDir)

templateFilePath := path.Join(testDir, "some-template.yml")
if err := os.WriteFile(templateFilePath, []byte(""), os.ModePerm); err != nil {
t.Fatalf("writing custom template failed: %v", err)
}
cmd := config.Cmd()
cmd.SetArgs([]string{testDir, "--builtin-template", "kubernetes", "--template", templateFilePath})

err = cmd.Execute()
if err == nil {
t.Fatalf("executing config subcommand: expected error but err is nil")
}
errMsg := "flag --builtin-template must not be used together with flag --template"
if err.Error() != errMsg {
t.Fatalf("wrong error message, expected %q, got %q", errMsg, err.Error())
}
})

t.Run("executing config.CmdCreateDefault() with existing directory", func(t *testing.T) {
testDir, err := os.MkdirTemp("", "openslides-manage-service-")
if err != nil {
t.Fatalf("generating temporary directory failed: %v", err)
Expand Down
Loading

0 comments on commit 5ecdbff

Please sign in to comment.