Skip to content

Commit

Permalink
Use urfave/cli package; add verbose flag; print metrics; refactor driver
Browse files Browse the repository at this point in the history
  • Loading branch information
petrgazarov committed Oct 25, 2023
1 parent 7ebd2c8 commit cdd2f5d
Show file tree
Hide file tree
Showing 13 changed files with 315 additions and 165 deletions.
2 changes: 1 addition & 1 deletion backend/llm/openai/gpt4/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -158,5 +158,5 @@ func logMessages(llmMessages []*LlmMessage) {
str += llmMessage.Content + "\n\n"
}
}
logger.Log(str)
logger.Debug(str)
}
2 changes: 1 addition & 1 deletion backend/target/terraform/validate_code.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ func parseTerraformValidateOutput(
return tfErrors[i].Range.Start.Line < tfErrors[j].Range.Start.Line
})

logger.Log("tfErrors: " + fmt.Sprint(tfErrors))
logger.Debug("tfErrors: " + fmt.Sprint(tfErrors))

j := 0
for _, tfError := range tfErrors {
Expand Down
9 changes: 5 additions & 4 deletions cli/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ module salami-cli

go 1.21.2

require github.com/urfave/cli/v2 v2.25.7

require (
github.com/fatih/color v1.15.0
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.17 // indirect
golang.org/x/sys v0.6.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
)
18 changes: 8 additions & 10 deletions cli/go.sum
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
107 changes: 60 additions & 47 deletions cli/main.go
Original file line number Diff line number Diff line change
@@ -1,69 +1,82 @@
package main

import (
"flag"
"fmt"
"log"
"os"
"salami/common/constants"
"salami/common/driver"
"strings"

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

const GENERAL_COMMAND = "salami"

var command_list = map[string]map[string]string{
"version": {
"cmd": "version",
"description": "Print the version of the installed Salami CLI",
},
"compile": {
"cmd": "compile",
"description": "Run the compilation end-to-end",
},
type SalamiMultiError struct {
errors []error
}

func showCommands() {
fmt.Println("Usage: \n \t ", GENERAL_COMMAND, "<command>\n ")
fmt.Println("The commands are:")
for id, command_ := range command_list {
fmt.Println(color.HiBlueString(id), ":", command_["description"])
func (m *SalamiMultiError) Error() string {
msgs := []string{}
for _, err := range m.errors {
msgs = append(msgs, err.Error())
}
fmt.Println()
return strings.Join(msgs, ", ")
}

func runSystem() {
errors := driver.Run()

for _, err := range errors {
color.Red(err.Error())
}
func (m *SalamiMultiError) Errors() []error {
return m.errors
}

func main() {
flag.Parse()
command := flag.Arg(0)
app := &cli.App{
Name: "salami",
HelpName: "Salami",
Version: constants.SalamiVersion,
Usage: "a declarative DSL for cloud infrastructure based on natural language descriptions",
UsageText: "salami [global options] [command] [command options]",
HideVersion: true,
Suggest: true,
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "verbose",
Aliases: []string{"v"},
Usage: "Enable verbose mode",
},
},
Commands: []*cli.Command{
{
Name: "compile",
Usage: "Runs the compilation end-to-end",
UsageText: "salami [global options] compile [command options]",
Action: func(cCtx *cli.Context) error {
verbose := cCtx.Bool("verbose")
errors := driver.Run(verbose)

if len(errors) == 1 {
return errors[0]
} else if len(errors) > 1 {
return &SalamiMultiError{errors: errors}
}

if command == "" {
color.Green("====================================")
color.Green("======= Welcome to Salami CLI ======")
color.Green("====================================\n ")
fmt.Println("Salami is a declarative domain-specific language for cloud infrastructure based on natural language descriptions. " +
"Salami compiler uses GPT4 to convert the natural language to Terraform code. You can think of Salami as writing documentation " +
"for each cloud resource object, and letting the compiler take care of converting that to IaC (Infrastructure as Code).",
)
showCommands()
return
return nil
},
},
{
Name: "version",
Usage: "Prints the version",
Action: func(cCtx *cli.Context) error {
fmt.Println("Salami version " + constants.SalamiVersion)
return nil
},
},
},
Authors: []*cli.Author{{
Name: "Petr Gazarov",
Email: "[email protected]",
}},
}

switch cmd := command; cmd {
case command_list["version"]["cmd"]:
fmt.Println("Salami version " + constants.SalamiVersion)
case command_list["compile"]["cmd"]:
runSystem()
case "help":
showCommands()
default:
msg := "Invalid command passed. Type '" + GENERAL_COMMAND + " help'"
color.Red(msg)
if err := app.Run(os.Args); err != nil {
log.Fatal(err)
}
}
125 changes: 40 additions & 85 deletions common/driver/driver.go → common/driver/backend.go
Original file line number Diff line number Diff line change
@@ -1,71 +1,21 @@
package driver

import (
"path/filepath"
"sort"

"salami/backend/llm"
"salami/backend/target"
"salami/backend/target_file_manager"
"salami/common/change_set"
"salami/common/config"
"salami/common/constants"
"salami/common/lock_file_manager"
"salami/common/metrics"
"salami/common/symbol_table"
commonTypes "salami/common/types"
"salami/common/utils/file_utils"
"salami/frontend/semantic_analyzer"
"salami/common/types"
"sort"
)

func Run() []error {
if errors := runValidations(); len(errors) > 0 {
return errors
}

symbolTable, errors := runFrontend()
if len(errors) > 0 {
return errors
}

newTargetFileMetas, newObjects, errors := runBackend(symbolTable)
if len(errors) > 0 {
return errors
}

if err := lock_file_manager.UpdateLockFile(newTargetFileMetas, newObjects); err != nil {
return []error{err}
}

return nil
}

func runFrontend() (*symbol_table.SymbolTable, []error) {
sourceFilePaths, err := getSourceFilePaths()
if err != nil {
return nil, []error{err}
}

allResources, allVariables, errors := parseFiles(sourceFilePaths, config.GetSourceDir())
if len(errors) > 0 {
return nil, errors
}

symbolTable, err := symbol_table.NewSymbolTable(allResources, allVariables)
if err != nil {
return nil, []error{err}
}

semanticAnalyzer := semantic_analyzer.NewSemanticAnalyzer(symbolTable)
if err = semanticAnalyzer.Analyze(); err != nil {
return nil, []error{err}
}

return symbolTable, nil
}

func runBackend(
symbolTable *symbol_table.SymbolTable,
) ([]commonTypes.TargetFileMeta, []*commonTypes.Object, []error) {
) ([]types.TargetFileMeta, []*types.Object, []error) {
newObjects, newTargetFiles, errors := generateCode(symbolTable)
if len(errors) > 0 {
return nil, nil, errors
Expand All @@ -83,12 +33,14 @@ func runBackend(
return nil, nil, errors
}

metrics.SetMetric(metrics.SourceFilesProcessed, len(newTargetFiles))

return newTargetFileMetas, newObjects, nil
}

func generateCode(
symbolTable *symbol_table.SymbolTable,
) ([]*commonTypes.Object, []*commonTypes.TargetFile, []error) {
) ([]*types.Object, []*types.TargetFile, []error) {
previousObjects := lock_file_manager.GetObjects()
changeSet := change_set.NewChangeSet(previousObjects, symbolTable)
changeSetRepository := change_set.NewChangeSetRepository(changeSet)
Expand All @@ -106,33 +58,35 @@ func generateCode(
}
targetFiles := target.GetFilesFromObjects(newObjects)

updateMetricsFromChangeSet(changeSet)

return newObjects, targetFiles, nil
}

func getSourceFilePaths() ([]string, error) {
sourceFilePaths, err := file_utils.GetFilePaths(config.GetSourceDir(), func(path string) bool {
return filepath.Ext(path) == constants.SalamiFileExtension
})
if err != nil {
return nil, err
func getFilePathsToRemove(
oldTargetFileMetas []types.TargetFileMeta,
newTargetFileMetas []types.TargetFileMeta,
) []string {
newMetaMap := make(map[string]bool)
for _, meta := range newTargetFileMetas {
newMetaMap[meta.FilePath] = true
}

relativeSourceFilePaths, err := file_utils.GetRelativeFilePaths(
config.GetSourceDir(),
sourceFilePaths,
)
if err != nil {
return nil, err
oldFilePaths := make([]string, 0)
for _, meta := range oldTargetFileMetas {
if _, exists := newMetaMap[meta.FilePath]; !exists {
oldFilePaths = append(oldFilePaths, meta.FilePath)
}
}

return relativeSourceFilePaths, nil
return oldFilePaths
}

func computeNewObjects(
previousObjects []*commonTypes.Object,
previousObjects []*types.Object,
changeSetRepository *change_set.ChangeSetRepository,
) []*commonTypes.Object {
objects := make([]*commonTypes.Object, 0)
) []*types.Object {
objects := make([]*types.Object, 0)
for _, object := range previousObjects {
if changeSetRepository.WasObjectChanged(object) {
objects = append(objects, changeSetRepository.GetChangedObject(object))
Expand All @@ -158,21 +112,22 @@ func computeNewObjects(
return objects
}

func getFilePathsToRemove(
oldTargetFileMetas []commonTypes.TargetFileMeta,
newTargetFileMetas []commonTypes.TargetFileMeta,
) []string {
newMetaMap := make(map[string]bool)
for _, meta := range newTargetFileMetas {
newMetaMap[meta.FilePath] = true
}

oldFilePaths := make([]string, 0)
for _, meta := range oldTargetFileMetas {
if _, exists := newMetaMap[meta.FilePath]; !exists {
oldFilePaths = append(oldFilePaths, meta.FilePath)
func updateMetricsFromChangeSet(changeSet *types.ChangeSet) {
objectsAdded := 0
objectsRemoved := 0
objectsChanged := 0

for _, diff := range changeSet.Diffs {
if diff.IsAdd() {
objectsAdded++
} else if diff.IsRemove() {
objectsRemoved++
} else if diff.IsUpdate() || diff.IsMove() {
objectsChanged++
}
}

return oldFilePaths
metrics.SetMetric(metrics.ObjectsAdded, objectsAdded)
metrics.SetMetric(metrics.ObjectsRemoved, objectsRemoved)
metrics.SetMetric(metrics.ObjectsChanged, objectsChanged)
}
Loading

0 comments on commit cdd2f5d

Please sign in to comment.