Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: categorize QA engine questions #1100

Merged
merged 10 commits into from
Nov 22, 2023
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
Loading