Skip to content

Commit

Permalink
New predefined template dev command (#216)
Browse files Browse the repository at this point in the history
* Hide commands that are not available

* Add base structure

* Adjust file permissions

* Fix bug with creating the template dir

* Fix typo

* Add dev check

* Extend

* Add test for happy path

* Add more tests
  • Loading branch information
marcauberer committed Nov 8, 2021
1 parent 44f2542 commit 881f692
Show file tree
Hide file tree
Showing 15 changed files with 426 additions and 72 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,7 @@ node_modules/*
test

# Ignore logs
log
log

# Ignore predefined template tests
predefined-service/database/quark-db
80 changes: 80 additions & 0 deletions src/cmd/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package cmd

import (
"github.com/urfave/cli/v2"
)

// CliCommands is a list of all available cli commands for Compose Generator
var CliCommands = []*cli.Command{
{
Name: "generate",
Aliases: []string{"g", "gen"},
Usage: "Generates a docker compose configuration",
Flags: GenerateCliFlags,
Action: Generate,
},
{
Name: "add",
Aliases: []string{"a"},
Usage: "Adds a service to an existing compose file",
Flags: AddCliFlags,
Action: Add,
},
{
Name: "remove",
Aliases: []string{"r", "rm"},
Usage: "Removes a service from an existing compose file",
Flags: RemoveCliFlags,
Action: Remove,
},
{
Name: "template",
Aliases: []string{"t"},
Usage: "Manages snapshots of your compose configuration for later use",
Subcommands: []*cli.Command{
{
Name: "save",
Aliases: []string{"s"},
Usage: "Save a custom template.",
Flags: TemplateSaveCliFlags,
Action: SaveTemplate,
},
{
Name: "load",
Aliases: []string{"l"},
Usage: "Load a custom template.",
Flags: TemplateLoadCliFlags,
Action: LoadTemplate,
},
{
Name: "delete",
Aliases: []string{"d"},
Usage: "Delete a custom template.",
Flags: TemplateDeleteCliFlags,
Action: DeleteTemplate,
},
},
},
{
Name: "install",
Aliases: []string{"i", "in"},
Usage: "Installs Docker and Docker Compose with a single command",
Hidden: isDockerizedEnvironment(),
Action: Install,
},
{
Name: "predefined-template",
Aliases: []string{"p", "pd"},
Usage: "Manages predefined service templates (this command does only exist in dev environments)",
Hidden: !isDevVersion(),
Subcommands: []*cli.Command{
{
Name: "new",
Aliases: []string{"n"},
Usage: "Creates a blank predefined service template",
Flags: PredefinedTemplateNewCliFlags,
Action: NewPredefinedTemplate,
},
},
},
}
2 changes: 1 addition & 1 deletion src/cmd/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ var GenerateCliFlags = []cli.Flag{

// ---------------------------------------------------------------- Public functions ---------------------------------------------------------------

// Generate a docker compose configuration
// Generate generates a docker compose configuration
func Generate(c *cli.Context) error {
infoLogger.Println("Generate command executed")

Expand Down
8 changes: 8 additions & 0 deletions src/cmd/mock_functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
removePass "compose-generator/pass/remove"
"compose-generator/project"
"compose-generator/util"
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
Expand All @@ -32,27 +33,34 @@ var printHeading = util.Heading
var pl = util.Pl
var pel = util.Pel
var yesNoQuestion = util.YesNoQuestion
var textQuestion = util.TextQuestion
var textQuestionWithDefault = util.TextQuestionWithDefault
var menuQuestion = util.MenuQuestion
var menuQuestionIndex = util.MenuQuestionIndex
var clearScreen = util.ClearScreen
var startProcess = util.StartProcess
var stopProcess = util.StopProcess
var marshalIdent = json.MarshalIndent

// File operations
var readDir = ioutil.ReadDir
var mkDir = os.Mkdir
var abs = filepath.Abs
var rel = filepath.Rel
var copyDir = copy.Copy
var fileExists = util.FileExists
var loadProjectMetadata = project.LoadProjectMetadata
var normalizePaths = util.NormalizePaths
var removeAll = os.RemoveAll
var writeFile = ioutil.WriteFile

// Environment
var isDockerizedEnvironment = util.IsDockerizedEnvironment
var isDevVersion = util.IsDevVersion
var commandExists = util.CommandExists
var getDockerVersion = util.GetDockerVersion
var getAvailablePredefinedTemplates = parser.GetAvailablePredefinedTemplates
var getPredefinedServicesPath = util.GetPredefinedServicesPath
var getCustomTemplatesPath = util.GetCustomTemplatesPath

// Passes
Expand Down
143 changes: 143 additions & 0 deletions src/cmd/predefined-template-new.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package cmd

import (
"compose-generator/model"
"encoding/json"
"fmt"
"strings"

"github.com/urfave/cli/v2"
)

// PredefinedTemplateNewCliFlags are the cli flags for the remove command
var PredefinedTemplateNewCliFlags = []cli.Flag{}

// ---------------------------------------------------------------- Public functions ---------------------------------------------------------------

// NewPredefinedTemplate generates a blank predefined service template in the respective directory
func NewPredefinedTemplate(context *cli.Context) error {
infoLogger.Println("NewPredefinedTemplate command executed")

if !isDevVersion() {
errorLogger.Println("Executed NewPredefinedTemplate command without being dev. Aborting.")
logError("Cannot run this command in a production environment.", true)
return nil
}

// Ask user questions and save to disk subsequently
config, service, readme := createNewPredefinedTemplate()
savePredefinedTemplate(config, service, readme)

return nil
}

const serviceTemplate = `image: hello-world:${{%s_VERSION}}
container_name: ${{PROJECT_NAME_CONTAINER}}-%s-%s
%snetworks:
# ToDo: Insert or remove section
ports:
# ToDo: Insert or remove section
volumes:
# ToDo: Insert or remove section
env_file:
# ToDo: Insert or delete section`

// --------------------------------------------------------------- Private functions ---------------------------------------------------------------

func createNewPredefinedTemplate() (*model.PredefinedTemplateConfig, string, string) {
config := model.PredefinedTemplateConfig{}

// Ask for template label
config.Label = textQuestion("Template label:")
config.Name = strings.ReplaceAll(strings.ToLower(config.Label), " ", "-")
snakeUpperName := strings.ToUpper(strings.ReplaceAll(config.Name, "-", "_"))

// Ask for template type
config.Type = menuQuestion("What is the closest match of specifying the type?", []string{
model.TemplateTypeFrontend,
model.TemplateTypeBackend,
model.TemplateTypeDatabase,
model.TemplateTypeDbAdmin,
})

// Set default configuration
config.Preselected = "false"
config.Proxied = config.Type == model.TemplateTypeFrontend || config.Type == model.TemplateTypeDbAdmin
config.Files = []model.File{
{
Path: "service.yml",
Type: "service",
},
{
Path: "README.md",
Type: "docs",
},
}
config.Questions = []model.Question{
{
Text: "Which version of " + config.Label + " do you want to use?",
Type: model.QuestionTypeText,
DefaultValue: "latest",
Variable: snakeUpperName + "_VERSION",
},
}

// Check if dir already exists
config.Dir = getPredefinedServicesPath() + "/" + config.Type + "/" + config.Name
if fileExists(config.Dir) {
errorLogger.Println("Predefined template dir '" + config.Dir + "' already exists. Aborting.")
logError("Template dir already exists. Aborting.", true)
return nil, "", ""
}

// Prepare Readme contents
readme := fmt.Sprintf("## %s\nToDo: Insert software description here.\n\n### Setup\nToDo: Insert setup instructions here.", config.Label)

// Prepare service contents
restartRule := ""
if config.Type != model.TemplateTypeDbAdmin {
restartRule = "restart: always\n"
}
service := fmt.Sprintf(serviceTemplate, snakeUpperName, config.Type, config.Name, restartRule)

return &config, service, readme
}

func savePredefinedTemplate(config *model.PredefinedTemplateConfig, service, readme string) {
pel()
spinner := startProcess("Saving predefined service template ...")
// Create dir
if err := mkDir(config.Dir, 0777); err != nil {
errorLogger.Println("Error creating the template dir '" + config.Dir + "': " + err.Error())
logError("Error creating the template dir", true)
return
}

// Save config.json
file, err := json.MarshalIndent(config, "", " ")
if err != nil {
errorLogger.Println("Error marshaling predefined template config to json: " + err.Error())
logError("Error marshaling config to json", true)
return
}
if err = writeFile(config.Dir+"/config.json", file, 0600); err != nil {
errorLogger.Println("Error writing predefined template config to file: " + err.Error())
logError("Error saving config.json", true)
return
}

// Save service.yml
if err = writeFile(config.Dir+"/service.yml", []byte(service), 0600); err != nil {
errorLogger.Println("Error writing service to file: " + err.Error())
logError("Error saving service.yml", true)
return
}

// Save README.md
if err = writeFile(config.Dir+"/README.md", []byte(readme), 0600); err != nil {
errorLogger.Println("Error writing Readme to file: " + err.Error())
logError("Error saving README.md", true)
return
}
stopProcess(spinner)
}

0 comments on commit 881f692

Please sign in to comment.