Skip to content

Commit

Permalink
fix: categorize QA engine questions (#1100)
Browse files Browse the repository at this point in the history
* fix: categorize QA engine questions
* fix: resolve CI lint errors
* feat: finish categorizing all questions, and add glob support to
question keys
* fix: resolve ci errors and remove unnecessary log
* fix: remove default category from mappings file
* feat: display QA categories in prompts
* fix: resolve ci errors
* feat: add support for categorizing external transformer queries

Signed-off-by: Soumil Paranjpay <soumil.paranjpay@gmail.com>
  • Loading branch information
Soumil-07 committed Nov 22, 2023
1 parent ec8107f commit d05661f
Show file tree
Hide file tree
Showing 12 changed files with 271 additions and 19 deletions.
71 changes: 71 additions & 0 deletions assets/built-in/qa/qamappings.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
categories:
- name: imageregistry
# or use the flags --enable imageregistry/--disable imageregistry
enabled: true
questions:
- move2kube.target.imageregistry.url
- move2kube.target.imageregistry.namespace
- move2kube.target.imageregistry.*.logintype
- move2kube.target.imageregistry.*.password
- name: sshkeys
enabled: true
questions:
- move2kube.repo.keys.*.key
- move2kube.repo.keys.pub.domain.*.pubkey
- move2kube.repo.keys.pub.load
- move2kube.repo.keys.load
- move2kube.repo.keys.paths
- move2kube.repo.keys.priv.*.password
- name: storage
enabled: true
questions:
- move2kube.storage.type.*.options
- name: sourceanalyzer
enabled: true
questions:
- move2kube.services.*.enable
- move2kube.services.*.statefulset
- move2kube.services.*.containerizationoption
- move2kube.services.*.childProjects.*.publishprofile
- move2kube.services.*.apacheconfig
- move2kube.services.*.pythonmainfile
- move2kube.services.*.pythonstartingfile
- move2kube.services.*.dockerfileType
- move2kube.services.*.childModules.*.enable
- move2kube.services.*.childProjects.*.enable
- move2kube.services.*.childModules.*.springBootProfiles
- move2kube.services.*.mavenProfiles
- name: cluster
enabled: true
questions:
- move2kube.target.*.clustertype
- move2kube.minreplicas
- name: network
enabled: true
questions:
- move2kube.services.*.*.servicetype
- move2kube.services.*.*.urlpath
- move2kube.services.*.ports
- move2kube.target.*.ingress.ingressclassname
- move2kube.target.*.ingress.host
- move2kube.target.*.ingress.tls
- name: git
enabled: true
questions:
- move2kube.vcs.git.name
- move2kube.vcs.git.username
- move2kube.vcs.git.email
- move2kube.vcs.git.pass
- name: cicd
enabled: true
questions:
- move2kube.target.cicd.tekton.gitreposshsecret
- move2kube.target.cicd.tekton.gitrepobasicauthsecret
- move2kube.target.cicd.tekton.registrypushsecret
- move2kube.transformers.kubernetes.argocd.namespace
- name: transformers
enabled: true
questions:
- move2kube.transformerselector
- move2kube.spawncontainers
- move2kube.transformers.types
1 change: 1 addition & 0 deletions assets/filepermissions.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"built-in/presets/docker-file-only.yaml" : 0644
"built-in/presets/enable-containerized-transformers.yaml" : 0644
"built-in/presets/use-podman-in-scripts.yaml" : 0644
"built-in/qa/qamappings.yaml" : 0644
"built-in/transformers/cloudfoundry/transformer.yaml" : 0644
"built-in/transformers/cnb/transformer.yaml" : 0644
"built-in/transformers/compose/composeanalyser/transformer.yaml" : 0644
Expand Down
12 changes: 7 additions & 5 deletions cmd/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,13 @@ const (
// maxIterationsFlag is the name of the flag that lets you set the maximum number of iterations to allow
maxIterationsFlag = "max-iterations"
// customizationsFlag is the path to customizations directory
customizationsFlag = "customizations"
qadisablecliFlag = "qa-disable-cli"
qaportFlag = "qa-port"
planProgressPortFlag = "plan-progress-port"
transformerSelectorFlag = "transformer-selector"
customizationsFlag = "customizations"
qadisablecliFlag = "qa-disable-cli"
qaportFlag = "qa-port"
planProgressPortFlag = "plan-progress-port"
transformerSelectorFlag = "transformer-selector"
qaEnabledCategoriesFlag = "qa-enable"
qaDisabledCategoriesFlag = "qa-disable"
)

type qaflags struct {
Expand Down
50 changes: 47 additions & 3 deletions cmd/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,17 @@ import (
"path/filepath"
"runtime/pprof"

"github.com/konveyor/move2kube/assets"
"github.com/konveyor/move2kube/common"
"github.com/konveyor/move2kube/common/download"
"github.com/konveyor/move2kube/common/vcs"
"github.com/konveyor/move2kube/lib"
"github.com/konveyor/move2kube/types/plan"
"github.com/konveyor/move2kube/types/qaengine"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"gopkg.in/yaml.v3"
)

type transformFlags struct {
Expand All @@ -54,8 +57,10 @@ type transformFlags struct {
// maxIterations is the maximum number of iterations to allow before aborting with an error
maxIterations int
// CustomizationsPaths contains the path to the customizations directory
customizationsPath string
transformerSelector string
customizationsPath string
transformerSelector string
qaEnabledCategories []string
qaDisabledCategories []string
}

func transformHandler(cmd *cobra.Command, flags transformFlags) {
Expand Down Expand Up @@ -125,10 +130,45 @@ func transformHandler(cmd *cobra.Command, flags transformFlags) {
}
}

// qa-disable and qa=enable are mutually exclusive
if len(flags.qaEnabledCategories) > 0 && len(flags.qaDisabledCategories) > 0 {
logrus.Fatalf("--qa-enable and --qa-disable cannot be used together.\n")
}

// Read the QA categories from the QA mapping file
var qaMapping qaengine.QAMapping
qaMappingFilepath := filepath.Join("built-in/qa", "qamappings.yaml")
file, err := assets.AssetsDir.ReadFile(qaMappingFilepath)
if err != nil {
logrus.Fatalf("failed to read qa-mapping file at %s. Error: %q\n", qaMappingFilepath, err)
}

if err := yaml.Unmarshal(file, &qaMapping); err != nil {
logrus.Fatalf("failed to decode qa-mapping file. Error: %q\n", err)
}

for _, mapping := range qaMapping.Categories {
common.QACategoryMap[mapping.Name] = mapping.Questions
}
common.QACategoryMap["default"] = []string{}
common.QACategoryMap["external"] = []string{}

// Global settings
common.IgnoreEnvironment = flags.ignoreEnv
common.DisableLocalExecution = flags.disableLocalExecution
// Global settings
// if --qa-enable is passed, all categories are disabled by default. Otherwise, only categories passed to --qa-disable
// are disabled
if len(flags.qaEnabledCategories) > 0 {
for k := range common.QACategoryMap {
if !common.IsStringPresent(flags.qaEnabledCategories, k) {
common.DisabledCategories = append(common.DisabledCategories, k)
}
}
} else {
for _, cat := range flags.qaDisabledCategories {
common.DisabledCategories = append(common.DisabledCategories, cat)
}
}

// Parameter cleaning and curate plan
transformationPlan := plan.Plan{}
Expand Down Expand Up @@ -251,6 +291,10 @@ func GetTransformCommand() *cobra.Command {
transformCmd.Flags().StringVarP(&flags.transformerSelector, transformerSelectorFlag, "t", "", "Specify the transformer selector.")
transformCmd.Flags().BoolVar(&flags.qaskip, qaSkipFlag, false, "Enable/disable the default answers to questions posed in QA Cli sub-system. If disabled, you will have to answer the questions posed by QA during interaction.")

// QA options
transformCmd.Flags().StringSliceVar(&flags.qaEnabledCategories, qaEnabledCategoriesFlag, []string{}, "Specify the QA categories to enable (cannot be used in conjunction with qa-disable)")
transformCmd.Flags().StringSliceVar(&flags.qaDisabledCategories, qaDisabledCategoriesFlag, []string{}, "Specify the QA categories to disable (cannot be used in conjunction with qa-enable)")

// Advanced options
transformCmd.Flags().BoolVar(&flags.ignoreEnv, ignoreEnvFlag, false, "Ignore data from local machine.")
transformCmd.Flags().BoolVar(&flags.disableLocalExecution, common.DisableLocalExecutionFlag, false, "Allow files to be executed locally.")
Expand Down
4 changes: 4 additions & 0 deletions common/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,10 @@ var (
DisableLocalExecution = false
// DefaultIgnoreDirRegexps specifies directory name regexes that would be ignored
DefaultIgnoreDirRegexps = []*regexp.Regexp{regexp.MustCompile("^[.].*")}
// DisabledCategories is a list of QA categories that are disabled
DisabledCategories = []string{}
// QACategoryMap maps category names to problem IDs
QACategoryMap = map[string][]string{}
// disallowedDNSCharactersRegex provides pattern for characters not allowed in a DNS Name
disallowedDNSCharactersRegex = regexp.MustCompile(`[^a-z0-9\-]`)
// disallowedEnvironmentCharactersRegex provides pattern for characters not allowed in a DNS Name
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ require (
github.com/docker/docker v23.0.3+incompatible
github.com/docker/libcompose v0.4.1-0.20171025083809-57bd716502dc
github.com/go-git/go-git/v5 v5.4.2
github.com/gobwas/glob v0.2.3
github.com/google/go-cmp v0.5.9
github.com/gorilla/mux v1.8.0
github.com/hashicorp/go-version v1.6.0
Expand Down Expand Up @@ -117,7 +118,6 @@ require (
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-redis/cache/v8 v8.4.2 // indirect
github.com/go-redis/redis/v8 v8.11.3 // indirect
github.com/gobwas/glob v0.2.3 // indirect
github.com/goccy/go-yaml v1.9.5 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.3.0 // indirect
Expand Down Expand Up @@ -149,6 +149,7 @@ require (
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kevinburke/ssh_config v1.1.0 // indirect
github.com/klauspost/compress v1.16.0 // indirect
github.com/kopoli/go-terminal-size v0.0.0-20170219200355-5c97524c8b54 // indirect
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1281,6 +1281,8 @@ github.com/klauspost/cpuid v1.2.3/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgo
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kopoli/go-terminal-size v0.0.0-20170219200355-5c97524c8b54 h1:0SMHxjkLKNawqUjjnMlCtEdj6uWZjv0+qDZ3F6GOADI=
github.com/kopoli/go-terminal-size v0.0.0-20170219200355-5c97524c8b54/go.mod h1:bm7MVZZvHQBfqHG5X59jrRE/3ak6HvK+/Zb6aZhLR2s=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
Expand Down
22 changes: 20 additions & 2 deletions qaengine/cliengine.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,21 @@ func (c *CliEngine) FetchAnswer(prob qatypes.Problem) (qatypes.Problem, error) {
logrus.Errorf("the QA problem object is invalid. Error: %q", err)
return prob, err
}
// return default if the category is skipped
probCategories := qatypes.GetProblemCategories(prob.ID, prob.Categories)
for _, category := range probCategories {
if common.IsStringPresent(common.DisabledCategories, category) {
// if prob.Default == nil {
// // todo: warn instead of returning error
// return prob, errors.New(fmt.Sprintf("category %s is skipped but default doesn't exist", category)) // TODO:
// }
if err := prob.SetAnswer(prob.Default, true); err != nil {
return prob, fmt.Errorf("failed to set the given solution as the answer. Error: %w", err)
}
return prob, nil
}
}

switch prob.Type {
case qatypes.SelectSolutionFormType:
return c.fetchSelectAnswer(prob)
Expand Down Expand Up @@ -221,8 +236,11 @@ func getQAMessage(prob qatypes.Problem) string {
if prob.Desc == "" {
prob.Desc = "Default description for question with id: " + prob.ID
}
categoryList := qatypes.GetProblemCategories(prob.ID, prob.Categories)
// We add the category list on the same line as the ID, but aligned to the right
idAndCategoryLine := AddRightAlignedString(fmt.Sprintf("ID: %s", prob.ID), fmt.Sprintf("Categories: (%s)", strings.Join(categoryList, ", ")))
if len(prob.Hints) == 0 {
return fmt.Sprintf("%s\nID: %s\n", prob.Desc, prob.ID)
return fmt.Sprintf("%s\n%s\n", prob.Desc, idAndCategoryLine)
}
return fmt.Sprintf("%s\nID: %s\nHints:\n- %s\n\n", prob.Desc, prob.ID, strings.Join(prob.Hints, "\n- "))
return fmt.Sprintf("%s\n%s\nHints:\n- %s", prob.Desc, idAndCategoryLine, strings.Join(prob.Hints, "\n- "))
}
37 changes: 37 additions & 0 deletions qaengine/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright IBM Corporation 2023
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package qaengine

import (
"fmt"

tsize "github.com/kopoli/go-terminal-size"
)

// AddRightAlignedString adds a new string to the right of the original, with max width the size of the current
// terminal window
func AddRightAlignedString(original, addition string) string {
termSize, err := tsize.GetSize()
if err != nil {
termSize = tsize.Size{
Height: 100, // the height here doesn't matter
Width: 100, // TODO: is 100 a good default for terminal width?
}
}
width := termSize.Width - len(original)
return fmt.Sprintf("%s%*s", original, width, addition)
}
4 changes: 4 additions & 0 deletions transformer/external/starlarktransformer.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,10 @@ func (t *Starlark) getStarlarkQuery() *starlark.Builtin {
if prob.Type == "" {
prob.Type = qatypes.InputSolutionFormType
}
// QA categories
if len(prob.Categories) == 0 {
prob.Categories = append(prob.Categories, "external")
}
if validation != "" {
validationFn, ok := t.StarGlobals[validation]
if !ok {
Expand Down
17 changes: 9 additions & 8 deletions types/qaengine/problem.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,15 @@ const (

// Problem defines the QA problem
type Problem struct {
ID string `yaml:"id" json:"id"`
Type SolutionFormType `yaml:"type,omitempty" json:"type,omitempty"`
Desc string `yaml:"description,omitempty" json:"description,omitempty"`
Hints []string `yaml:"hints,omitempty" json:"hints,omitempty"`
Options []string `yaml:"options,omitempty" json:"options,omitempty"`
Default interface{} `yaml:"default,omitempty" json:"default,omitempty"`
Answer interface{} `yaml:"answer,omitempty" json:"answer,omitempty"`
Validator func(interface{}) error `yaml:"-" json:"-"`
ID string `yaml:"id" json:"id"`
Type SolutionFormType `yaml:"type,omitempty" json:"type,omitempty"`
Desc string `yaml:"description,omitempty" json:"description,omitempty"`
Hints []string `yaml:"hints,omitempty" json:"hints,omitempty"`
Options []string `yaml:"options,omitempty" json:"options,omitempty"`
Default interface{} `yaml:"default,omitempty" json:"default,omitempty"`
Answer interface{} `yaml:"answer,omitempty" json:"answer,omitempty"`
Validator func(interface{}) error `yaml:"-" json:"-"`
Categories []string `yaml:"categories,omitempty" json:"categories,omitempty"`
}

// NewProblem creates a new problem object from a GRPC problem
Expand Down
Loading

0 comments on commit d05661f

Please sign in to comment.