diff --git a/Dockerfile b/Dockerfile index 9ca837e..a076ebb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,7 +26,7 @@ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o kantra main.go FROM quay.io/konveyor/analyzer-lsp:latest -RUN mkdir /opt/rulesets /opt/openrewrite /opt/input /opt/input/example +RUN mkdir /opt/rulesets /opt/rulesets/input /opt/openrewrite /opt/input /opt/input/example RUN touch /opt/input/settings.json /opt/input/output.yaml COPY --from=builder /workspace/kantra /usr/local/bin/kantra diff --git a/cmd/analyze.go b/cmd/analyze.go index 3830d49..624c3cc 100644 --- a/cmd/analyze.go +++ b/cmd/analyze.go @@ -5,6 +5,7 @@ import ( "context" "encoding/json" "fmt" + "io" "io/fs" "io/ioutil" "os" @@ -14,10 +15,13 @@ import ( "strings" "github.com/apex/log" + "github.com/konveyor/analyzer-lsp/engine" outputv1 "github.com/konveyor/analyzer-lsp/output/v1/konveyor" "github.com/konveyor/analyzer-lsp/provider" + "gopkg.in/yaml.v2" "github.com/spf13/cobra" + "golang.org/x/exp/maps" "golang.org/x/exp/slices" ) @@ -31,7 +35,7 @@ type analyzeCommand struct { input string output string mode string - rules string + rules []string } // analyzeCmd represents the analyze command @@ -69,7 +73,7 @@ func NewAnalyzeCmd() *cobra.Command { analyzeCommand.Flags().BoolVar(&analyzeCmd.listTargets, "list-targets", false, "List rules for available migration targets") analyzeCommand.Flags().StringArrayVarP(&analyzeCmd.sources, "source", "s", []string{}, "Source technology to consider for analysis") analyzeCommand.Flags().StringArrayVarP(&analyzeCmd.targets, "target", "t", []string{}, "Target technology to consider for analysis") - analyzeCommand.Flags().StringVar(&analyzeCmd.rules, "rules", "", "Rules for analysis") + analyzeCommand.Flags().StringArrayVar(&analyzeCmd.rules, "rules", []string{}, "filename or directory containing rule files") analyzeCommand.Flags().StringVarP(&analyzeCmd.input, "input", "i", "", "Path to application source code or a binary") analyzeCommand.Flags().StringVarP(&analyzeCmd.output, "output", "o", "", "Path to the directory for analysis output") analyzeCommand.Flags().BoolVar(&analyzeCmd.skipStaticReport, "skip-static-report", false, "Do not generate static report") @@ -198,11 +202,7 @@ func listOptionsFromLabels(sl []string, label string) { } func (a *analyzeCommand) createOutputFile() (string, error) { - // if trailing '/' in given output dir, remove it - trimmedOutput := strings.TrimRight(a.output, "/") - trimmedOutput = strings.TrimRight(a.output, "\\") - - fp := filepath.Join(trimmedOutput, "output.yaml") + fp := filepath.Join(a.output, "output.yaml") outputFile, err := os.Create(fp) if err != nil { return "", err @@ -211,53 +211,153 @@ func (a *analyzeCommand) createOutputFile() (string, error) { return fp, nil } -// TODO: *** write all of provider settings here *** -func (a *analyzeCommand) writeProviderSettings(dir string, settingsFilePath string, sourceAppPath string) error { +func (a *analyzeCommand) writeProviderSettings() error { + provConfig := []provider.Config{ + { + Name: "go", + BinaryPath: "/usr/bin/generic-external-provider", + InitConfig: []provider.InitConfig{ + { + Location: "/opt/input/example", + AnalysisMode: provider.FullAnalysisMode, + ProviderSpecificConfig: map[string]interface{}{ + "name": "go", + "dependencyProviderPath": "/usr/bin/golang-dependency-provider", + provider.LspServerPathConfigKey: "/root/go/bin/gopls", + }, + }, + }, + }, + { + Name: "java", + BinaryPath: "/jdtls/bin/jdtls", + InitConfig: []provider.InitConfig{ + { + Location: "/opt/input/example", + AnalysisMode: provider.SourceOnlyAnalysisMode, + ProviderSpecificConfig: map[string]interface{}{ + "bundles": "/jdtls/java-analyzer-bundle/java-analyzer-bundle.core/target/java-analyzer-bundle.core-1.0.0-SNAPSHOT.jar", + provider.LspServerPathConfigKey: "/jdtls/bin/jdtls", + }, + }, + }, + }, + { + Name: "builtin", + InitConfig: []provider.InitConfig{ + { + Location: "/opt/input/example", + AnalysisMode: "", + }, + }, + }, + } - providerConfig := []provider.Config{} - jsonFile, err := os.Open(settingsFilePath) + jsonData, err := json.MarshalIndent(&provConfig, "", " ") if err != nil { return err } - defer jsonFile.Close() - byteValue, err := ioutil.ReadAll(jsonFile) - err = json.Unmarshal(byteValue, &providerConfig) + fileName := "settings.json" + err = ioutil.WriteFile(fileName, jsonData, os.ModePerm) if err != nil { return err } - for i := range providerConfig { - for init := range providerConfig[i].InitConfig { - providerConfig[i].InitConfig[init].Location = sourceAppPath + return nil +} + +func (a *analyzeCommand) getRules(ruleMountedPath string, wd string, dirName string) (map[string]string, error) { + rulesMap := make(map[string]string) + rulesetNeeded := false + err := os.Mkdir(dirName, os.ModePerm) + if err != nil { + return nil, err + } + for i, r := range a.rules { + stat, err := os.Stat(r) + if err != nil { + log.Errorf("failed to stat rules %s", r) + return nil, err + } + // move rules files passed into dir to mount + if !stat.IsDir() { + rulesetNeeded = true + destFile := filepath.Join(dirName, fmt.Sprintf("rules%v.yaml", i)) + err := copyFileContents(r, destFile) + if err != nil { + return nil, err + } + } else { + dirName = r } } - providerLocation, err := json.MarshalIndent(providerConfig, "", " ") + if rulesetNeeded { + createTempRuleSet(wd, dirName) + } + // add to volumes + rulesMap[dirName] = ruleMountedPath + return rulesMap, nil +} + +func copyFileContents(src string, dst string) (err error) { + source, err := os.Open(src) + if err != nil { + return nil + } + defer source.Close() + destination, err := os.Create(dst) if err != nil { return err } - err = ioutil.WriteFile(settingsFilePath, providerLocation, 0644) + defer destination.Close() + _, err = io.Copy(destination, source) if err != nil { return err } return nil } -func (a *analyzeCommand) Run(ctx context.Context) error { - if len(a.rules) == 0 { - a.rules = RulesetPath +func createTempRuleSet(wd string, tempDirName string) error { + tempRuleSet := engine.RuleSet{ + Name: "ruleset", + Description: "temp ruleset", + } + yamlData, err := yaml.Marshal(&tempRuleSet) + if err != nil { + return err } + fileName := "ruleset.yaml" + err = ioutil.WriteFile(fileName, yamlData, os.ModePerm) + if err != nil { + return err + } + // move temp ruleset into temp dir + rulsetDefault := filepath.Join(wd, "ruleset.yaml") + destRuleSet := filepath.Join(tempDirName, "ruleset.yaml") + err = copyFileContents(rulsetDefault, destRuleSet) + if err != nil { + return err + } + defer os.Remove(rulsetDefault) + return nil +} + +func (a *analyzeCommand) Run(ctx context.Context) error { outputFilePath, err := a.createOutputFile() if err != nil { return err } - dir, err := os.Getwd() + wd, err := os.Getwd() if err != nil { return err } - settingsFilePath := filepath.Join(dir, "settings.json") + // TODO: clean this up + settingsFilePath := filepath.Join(wd, "settings.json") settingsMountedPath := filepath.Join(InputPath, "settings.json") outputMountedPath := filepath.Join(InputPath, "output.yaml") sourceAppPath := filepath.Join(InputPath, "example") - err = a.writeProviderSettings(dir, settingsFilePath, sourceAppPath) + rulesetName := filepath.Join(RulesetPath, "input") + tempDirName := filepath.Join(wd, "tempRulesDir") + err = a.writeProviderSettings() if err != nil { return err } @@ -266,9 +366,16 @@ func (a *analyzeCommand) Run(ctx context.Context) error { settingsFilePath: settingsMountedPath, outputFilePath: outputMountedPath, } + if len(a.rules) > 0 { + ruleVols, err := a.getRules(rulesetName, wd, tempDirName) + if err != nil { + return err + } + maps.Copy(volumes, ruleVols) + } args := []string{ fmt.Sprintf("--provider-settings=%v", settingsMountedPath), - fmt.Sprintf("--rules=%v", a.rules), + fmt.Sprintf("--rules=%v", RulesetPath), fmt.Sprintf("--output-file=%v", outputMountedPath), } cmd := NewContainerCommand( @@ -281,5 +388,7 @@ func (a *analyzeCommand) Run(ctx context.Context) error { if err != nil { return err } + defer os.RemoveAll(tempDirName) + defer os.Remove("settings.json") return nil } diff --git a/settings.json b/settings.json deleted file mode 100644 index b269243..0000000 --- a/settings.json +++ /dev/null @@ -1,37 +0,0 @@ -[ - { - "name": "go", - "binaryPath": "/usr/bin/generic-external-provider", - "initConfig": [ - { - "analysisMode": "full", - "providerSpecificConfig": { - "dependencyProviderPath": "/usr/bin/golang-dependency-provider", - "lspServerPath": "/root/go/bin/gopls", - "name": "go" - } - } - ] - }, - { - "name": "java", - "binaryPath": "/jdtls/bin/jdtls", - "initConfig": [ - { - "analysisMode": "source-only", - "providerSpecificConfig": { - "bundles": "/jdtls/java-analyzer-bundle/java-analyzer-bundle.core/target/java-analyzer-bundle.core-1.0.0-SNAPSHOT.jar", - "lspServerPath": "/jdtls/bin/jdtls" - } - } - ] - }, - { - "name": "builtin", - "initConfig": [ - { - "analysisMode": "" - } - ] - } -] \ No newline at end of file