More errors, some fixes

main
andrew 3 weeks ago
parent 3032626095
commit d7ae9bea8a

@ -4,67 +4,74 @@ package errors
type ErrorCode string
const (
ErrEmptyFile ErrorCode = "EMPTY_FILE" // файл пуст или отсутствует
ErrYamlSyntax ErrorCode = "YAML_SYNTAX_ERROR" // ошибка синтаксического анализа YAML
// General Errors
ErrEmptyFile ErrorCode = "FileIsEmpty" // The file is empty or missing
ErrYamlSyntax ErrorCode = "YAMLSyntaxError" // YAML syntax error
ErrJobNameBlank ErrorCode = "JOB_NAME_BLANK" // имя задачи пустое
ErrJobNameTooLong ErrorCode = "JOB_NAME_TOO_LONG"
ErrMissingScript ErrorCode = "MISSING_SCRIPT" // отсутствует обязательное поле script (или run)
ErrBothScriptAndRun ErrorCode = "BOTH_SCRIPT_RUN" // одновременно присутствуют script и run
ErrJobStageNotExist ErrorCode = "JOB_STAGE_NOT_EXIST" // указан stage, которого нет в списке разрешённых
ErrUndefinedDependency ErrorCode = "UNDEFINED_DEPENDENCY" // зависимость не определена в списке job'ов
ErrInvalidStageOrder ErrorCode = "INVALID_STAGE_ORDER" // зависимость имеет stage, который идёт позже, чем у задачи
ErrDuplicateNeeds ErrorCode = "DUPLICATE_NEEDS" // дублирующиеся записи в needs
ErrUndefinedNeed ErrorCode = "UNDEFINED_NEED" // need ссылается на несуществующую задачу
ErrNeedNameTooLong ErrorCode = "NEED_NAME_TOO_LONG" // имя need превышает допустимую длину
ErrNoVisibleJob ErrorCode = "NO_VISIBLE_JOB" // нет ни одной видимой задачи
ErrMissingJobs ErrorCode = "MISSING_JOBS"
ErrArtifactsPathsBlank ErrorCode = "ARTIFACTS_PATHS_BLANK" // отсутствует или пустой блок paths в artifacts
ErrUnknownRootKey ErrorCode = "UNKNOWN_ROOT_KEY"
ErrInvalidWhen ErrorCode = "INVALID_WHEN"
ErrInvalidOnly ErrorCode = "INVALID_ONLY"
ErrUnknownKey ErrorCode = "UNKNOWN_KEY"
ErrMissingStage ErrorCode = "MISSING_STAGE"
ErrInvalidChanges ErrorCode = "INVALID_CHANGES"
ErrInvalidRulesFormat ErrorCode = "INVALID_RULES_FORMAT"
// Job-related Errors
ErrJobNameBlank ErrorCode = "JobNameBlank" // The job name is blank
ErrJobNameTooLong ErrorCode = "JobNameTooLong" // The job name exceeds the maximum length
ErrMissingScript ErrorCode = "MissingScript" // The required field script (or run) is missing
ErrBothScriptAndRun ErrorCode = "BothScriptAndRun" // Both script and run are specified simultaneously
ErrJobStageNotExist ErrorCode = "JobStageNotExist" // The specified stage does not exist in the allowed list
ErrUndefinedDependency ErrorCode = "UndefinedDependency" // A dependency is not defined among the jobs
ErrInvalidStageOrder ErrorCode = "InvalidStageOrder" // A dependency has a stage that occurs later than the job's stage
ErrDuplicateNeeds ErrorCode = "DuplicateNeeds" // Duplicate entries found in needs
ErrUndefinedNeed ErrorCode = "UndefinedNeed" // A need refers to a non-existent job
ErrNeedNameTooLong ErrorCode = "NeedNameTooLong" // The need name exceeds the allowed length
ErrNoVisibleJob ErrorCode = "NoVisibleJob" // There are no visible jobs
ErrMissingJobs ErrorCode = "MissingJobs" // No jobs defined
ErrArtifactsPathsBlank ErrorCode = "ArtifactsPathsBlank" // The artifacts paths block is missing or empty
ErrUnknownRootKey ErrorCode = "UnknownRootKey" // An unknown key was found at the root level
ErrInvalidWhen ErrorCode = "InvalidWhen" // The value of 'when' is invalid
ErrInvalidOnly ErrorCode = "InvalidOnly" // The value of 'only' is invalid
ErrUnknownKey ErrorCode = "UnknownKey" // An unknown key was found
ErrMissingStage ErrorCode = "MissingStage" // The stage is missing
ErrInvalidChanges ErrorCode = "InvalidChanges" // The changes configuration is invalid
ErrInvalidRulesFormat ErrorCode = "InvalidRulesFormat" // The rules format is invalid
// Changes
ErrChangesNotArrayOfStrings ErrorCode = "CHANGES_NOT_ARRAY_OF_STRINGS"
ErrChangesInvalidType ErrorCode = "CHANGES_INVALID_TYPE"
ErrChangesTooManyEntries ErrorCode = "CHANGES_TOO_MANY_ENTRIES"
ErrChangesMissingPaths ErrorCode = "CHANGES_MISSING_PATHS"
// Changes-related Errors
ErrChangesNotArrayOfStrings ErrorCode = "ChangesNotArrayOfStrings" // Changes config should be an array of strings
ErrChangesInvalidType ErrorCode = "ChangesInvalidType" // Changes config is of an invalid type
ErrChangesTooManyEntries ErrorCode = "ChangesTooManyEntries" // Changes config has too many entries (maximum 50)
ErrChangesMissingPaths ErrorCode = "ChangesMissingPaths" // The changes config hash must contain the key 'paths'
// Paths
ErrPathsNotArrayOfStrings ErrorCode = "PATHS_NOT_ARRAY_OF_STRINGS"
// Paths-related Errors
ErrPathsNotArrayOfStrings ErrorCode = "PathsNotArrayOfStrings" // Paths config should be an array of strings
// Job delayed parameters
ErrStartInMissing ErrorCode = "START_IN_MISSING"
ErrStartInInvalid ErrorCode = "START_IN_INVALID"
ErrStartInTooLong ErrorCode = "START_IN_TOO_LONG"
ErrStartInMustBeBlank ErrorCode = "START_IN_MUST_BE_BLANK"
// Job Delayed Parameters
ErrStartInMissing ErrorCode = "StartInMissing" // For delayed jobs, start_in is missing
ErrStartInInvalid ErrorCode = "StartInInvalid" // start_in is not a valid duration
ErrStartInTooLong ErrorCode = "StartInTooLong" // start_in exceeds the allowed limit
ErrStartInMustBeBlank ErrorCode = "StartInMustBeBlank" // For non-delayed jobs, start_in must be blank
// Dependencies / Needs consistency
ErrDependencyNotInNeeds ErrorCode = "DEPENDENCY_NOT_IN_NEEDS"
// Dependencies / Needs Consistency
ErrDependencyNotInNeeds ErrorCode = "DependencyNotInNeeds" // A dependency is not included in needs
// Rules validation
ErrRulesOnlyExcept ErrorCode = "RULES_ONLY_EXCEPT"
ErrRulesOnly ErrorCode = "RULES_ONLY"
ErrRulesExcept ErrorCode = "RULES_EXCEPT"
ErrInvalidExpressionSyntax ErrorCode = "INVALID_EXPRESSION_SYNTAX"
ErrUnknownRulesKey ErrorCode = "UNKNOWN_RULES_KEY"
// Rules Validation
ErrRulesOnlyExcept ErrorCode = "RulesOnlyExcept" // Only and except cannot be used with rules
ErrRulesOnly ErrorCode = "RulesOnly" // Only cannot be used with rules
ErrRulesExcept ErrorCode = "RulesExcept" // Except cannot be used with rules
ErrInvalidExpressionSyntax ErrorCode = "InvalidExpressionSyntax" // The expression syntax is invalid
ErrUnknownRulesKey ErrorCode = "UnknownRulesKey" // An unknown key was found in rules
ErrBeforeScriptInvalid ErrorCode = "BEFORE_SCRIPT_INVALID"
ErrServiceInvalid ErrorCode = "SERVICE_INVALID"
ErrStageInvalid ErrorCode = "STAGE_INVALID"
ErrVariableInvalid ErrorCode = "VARIABLE_INVALID"
ErrVariableNameTooLong ErrorCode = "VARIABLE_NAME_TOO_LONG"
ErrInvalidStagesOrder ErrorCode = "INVALID_STAGES_ORDER"
ErrVariablesInvalid ErrorCode = "VARIABLES_INVALID"
ErrVariablesInvalidKey ErrorCode = "VARIABLES_INVALID_KEY"
// Other Job-related Errors
ErrBeforeScriptInvalid ErrorCode = "BeforeScriptInvalid" // The before_script configuration is invalid
ErrServiceInvalid ErrorCode = "ServiceInvalid" // The service configuration is invalid
ErrStageInvalid ErrorCode = "StageInvalid" // The stage configuration is invalid
ErrVariableInvalid ErrorCode = "VariableInvalid" // The variable is invalid
ErrVariableNameTooLong ErrorCode = "VariableNameTooLong" // The variable name exceeds the maximum length
ErrInvalidStagesOrder ErrorCode = "InvalidStagesOrder" // The stage order is invalid
ErrVariablesInvalid ErrorCode = "VariablesInvalid" // The variables configuration is invalid
ErrVariablesInvalidKey ErrorCode = "VariablesInvalidKey" // The variable uses invalid keys
ErrCyclicDependency ErrorCode = "CYCLIC_DEPENDENCY" // обнаружена циклическая зависимость
ErrBooleanValue ErrorCode = "BOOLEAN_VALUE" // значение должно быть булевым
ErrIncludeRulesInvalid ErrorCode = "INCLUDE_RULES_INVALID" // значение exists или changes не является строкой или массивом строк
// Additional Errors
ErrCyclicDependency ErrorCode = "CyclicDependency" // A cyclic dependency was detected
ErrBooleanValue ErrorCode = "BooleanValue" // The value must be boolean
ErrIncludeRulesInvalid ErrorCode = "IncludeRulesInvalid" // The 'exists' or 'changes' value is not a string or an array of strings
// Style Note (Informational/Note)
ErrMissingFinalNewline ErrorCode = "MissingFinalNewline" // The file is missing a final empty newline
)
// ErrorSeverity maps each error code to a severity level.
@ -114,5 +121,6 @@ var ErrorSeverity = map[ErrorCode]SeverityLevel{
// Notes (if needed, add informational codes here)
ErrJobNameTooLong: Note,
ErrVariableNameTooLong: Note,
ErrNeedNameTooLong: Critical,
ErrNeedNameTooLong: Note,
ErrMissingFinalNewline: Note,
}

@ -46,4 +46,4 @@ var ErrorMessages = map[ErrorCode]string{
ErrIncludeRulesInvalid: "include rule key '%s' should be a string or an array of strings",
ErrVariableInvalid: "variable '%s' must be a scalar or a map",
ErrInvalidStagesOrder: "job '%s' (Line %d, Col %d) has need '%s' (Line %d, Col %d-%d) with a stage occurring later than '%s'",
}
ErrMissingFinalNewline: "File should end with an empty line for better style"}

@ -1,5 +1,7 @@
package gitlabcivalidator
import "gopkg.in/yaml.v3"
// ValidationOptions описывает настройки валидации.
type ValidationOptions struct {
VerifyProjectSHA bool // нужно ли проверять SHA проекта
@ -8,17 +10,18 @@ type ValidationOptions struct {
// GitLabCIConfig описывает структуру корневого объекта .gitlab-ci.yml
type GitLabCIConfig struct {
Default interface{} `yaml:"default,omitempty"`
Include interface{} `yaml:"include,omitempty"`
BeforeScript interface{} `yaml:"before_script,omitempty"`
Image interface{} `yaml:"image,omitempty"`
Services interface{} `yaml:"services,omitempty"`
AfterScript interface{} `yaml:"after_script,omitempty"`
Variables interface{} `yaml:"variables,omitempty"`
Stages []string `yaml:"stages,omitempty"`
Cache interface{} `yaml:"cache,omitempty"`
Workflow interface{} `yaml:"workflow,omitempty"`
Jobs map[string]*Job `yaml:"jobs,omitempty"`
Default interface{} `yaml:"default,omitempty"`
Include interface{} `yaml:"include,omitempty"`
BeforeScript interface{} `yaml:"before_script,omitempty"`
Image interface{} `yaml:"image,omitempty"`
Services interface{} `yaml:"services,omitempty"`
AfterScript interface{} `yaml:"after_script,omitempty"`
Variables interface{} `yaml:"variables,omitempty"`
VariablesNode *yaml.Node `yaml:"-"`
Stages []string `yaml:"stages,omitempty"`
Cache interface{} `yaml:"cache,omitempty"`
Workflow interface{} `yaml:"workflow,omitempty"`
Jobs map[string]*Job `yaml:"jobs,omitempty"`
Changes interface{} `yaml:"changes,omitempty"`
Paths interface{} `yaml:"paths,omitempty"`
@ -51,6 +54,9 @@ type Job struct {
OnlyColumn int `yaml:"-"`
Except interface{} `yaml:"except,omitempty"`
Variables interface{} `yaml:"variables,omitempty"`
VariablesNode *yaml.Node `yaml:"-"`
Line int `yaml:"-"` // Строка в файле
Column int `yaml:"-"` // Позиция в строке
DependencyLines map[string]struct {

@ -56,6 +56,10 @@ func ParseGitLabCIConfig(data []byte, ve *errors.ValidationError) (*GitLabCIConf
for i := 0; i < len(mainNode.Content)-1; i += 2 {
keyNode := mainNode.Content[i]
valNode := mainNode.Content[i+1]
if keyNode.Value == "variables" {
config.VariablesNode = valNode
}
// If the key is not one of the allowed top-level keys, treat it as a job.
if _, ok := allowedKeys[keyNode.Value]; !ok {
// Create a new Job.
@ -79,31 +83,34 @@ func ParseGitLabCIConfig(data []byte, ve *errors.ValidationError) (*GitLabCIConf
for d := 0; d < len(valNode.Content)-1; d += 2 {
depKey := valNode.Content[d]
depVal := valNode.Content[d+1]
if depKey.Value == "dependencies" && depVal.Kind == yaml.SequenceNode {
for k := 0; k < len(depVal.Content); k++ {
depNode := depVal.Content[k]
job.Dependencies = append(job.Dependencies, depNode.Value)
job.DependencyLines[depNode.Value] = struct {
Line int
Column int
}{depNode.Line, depNode.Column}
switch depKey.Value {
case "dependencies":
if depVal.Kind == yaml.SequenceNode {
for k := 0; k < len(depVal.Content); k++ {
depNode := depVal.Content[k]
job.Dependencies = append(job.Dependencies, depNode.Value)
job.DependencyLines[depNode.Value] = struct {
Line int
Column int
}{depNode.Line, depNode.Column}
}
}
}
if depKey.Value == "needs" && depVal.Kind == yaml.SequenceNode {
job.NeedLines = make(map[string]struct {
Line int
Column int
})
for k := 0; k < len(depVal.Content); k++ {
needNode := depVal.Content[k]
job.Needs = append(job.Needs, needNode.Value)
job.NeedLines[needNode.Value] = struct {
case "needs":
if depVal.Kind == yaml.SequenceNode {
job.NeedLines = make(map[string]struct {
Line int
Column int
}{needNode.Line, needNode.Column}
})
for k := 0; k < len(depVal.Content); k++ {
needNode := depVal.Content[k]
job.Needs = append(job.Needs, needNode.Value)
job.NeedLines[needNode.Value] = struct {
Line int
Column int
}{needNode.Line, needNode.Column}
}
}
}
if depKey.Value == "only" {
case "only":
if depVal.Kind == yaml.ScalarNode {
job.Only = depVal.Value
} else if depVal.Kind == yaml.SequenceNode {
@ -115,8 +122,15 @@ func ParseGitLabCIConfig(data []byte, ve *errors.ValidationError) (*GitLabCIConf
}
job.OnlyLine = depKey.Line
job.OnlyColumn = depKey.Column
case "variables":
// Заполняем поля переменных для job.
job.VariablesNode = depVal
var vars map[string]interface{}
if err := depVal.Decode(&vars); err != nil {
return nil, fmt.Errorf("failed to decode variables for job '%s': %w", job.Name, err)
}
job.Variables = vars
}
// You can add additional manual processing here.
}
} else {
// If the job value is not a mapping, leave job fields as default.

@ -31,19 +31,19 @@ func ValidateGitLabCIFile(content string, opts ValidationOptions) error {
err := yaml.Unmarshal([]byte(content), &root)
if err != nil {
validationErr.AddDetailWithSpan(errors.ErrYamlSyntax,
fmt.Sprintf("ошибка синтаксического анализа YAML: %v", err),
fmt.Sprintf("YAML parsing error: %v", err),
0, 0, 0, 0)
return validationErr
}
if len(root.Content) == 0 {
validationErr.AddDetailWithSpan(errors.ErrYamlSyntax,
"файл .gitlab-ci.yml пуст или не содержит допустимых данных",
".gitflame-ci.yml file is empty or does not contain valid data",
0, 0, 0, 0)
return validationErr
}
if root.Kind != yaml.DocumentNode || len(root.Content) == 0 {
validationErr.AddDetailWithSpan(errors.ErrYamlSyntax,
"некорректный формат YAML: ожидался корневой объект",
"invalid YAML format: a root object was expected",
0, 0, 0, 0)
return validationErr
}
@ -75,6 +75,7 @@ func ValidateGitLabCIFile(content string, opts ValidationOptions) error {
validateStages(cfg, validationErr)
validateVariables(cfg, validationErr)
validateIncludeRules(cfg, validationErr)
checkFinalEmptyLine(content, validationErr)
if !validationErr.IsEmpty() {
return validationErr
@ -153,46 +154,123 @@ func validateStages(cfg *GitLabCIConfig, ve *errors.ValidationError) {
}
}
func validateVariables(cfg *GitLabCIConfig, ve *errors.ValidationError) {
if cfg.Variables == nil {
return
}
varsMap, ok := cfg.Variables.(map[string]interface{})
if !ok {
ve.AddDetailWithSpan(errors.ErrVariablesInvalid, errors.ErrorMessages[errors.ErrVariablesInvalid], 0, 0, 0, 0)
return
}
// validateVariablesMap проверяет карту переменных varsMap, используя данные из YAMLузла varsNode для получения позиций.
func validateVariablesMap(varsMap map[string]interface{}, varsNode *yaml.Node, ve *errors.ValidationError, posGetter func(varName string) (int, int)) {
allowedKeys := map[string]struct{}{
"value": {},
"description": {},
"expand": {},
"options": {},
}
for varName, val := range varsMap {
if isScalar(val) {
if len(varName) > MaxVariableNameLength {
ve.AddDetailWithSpan(errors.ErrVariableNameTooLong, fmt.Sprintf(errors.ErrorMessages[errors.ErrVariableNameTooLong], varName),
0, 0, 0, 0,
// Проходим по всем ключам, определённым в YAMLузле
for i := 0; i < len(varsNode.Content)-1; i += 2 {
keyNode := varsNode.Content[i]
varName := keyNode.Value
// Получаем позицию для этой переменной через posGetter.
line, col := posGetter(varName)
// Проверяем длину имени переменной
if len(varName) > MaxVariableNameLength {
ve.AddDetailWithSpan(
errors.ErrVariableNameTooLong,
fmt.Sprintf(errors.ErrorMessages[errors.ErrVariableNameTooLong], varName),
line, col, line, col+len(varName),
)
}
// Проверяем, что значение переменной присутствует и корректно
if val, exists := varsMap[varName]; exists {
if !isScalar(val) {
ve.AddDetailWithSpan(
errors.ErrVariableInvalid,
fmt.Sprintf(errors.ErrorMessages[errors.ErrVariableInvalid], varName),
line, col, line, col+len(varName),
)
}
} else if nested, ok := val.(map[string]interface{}); ok {
invalidKeys := []string{}
for k := range nested {
if _, allowed := allowedKeys[k]; !allowed {
invalidKeys = append(invalidKeys, k)
if nested, ok := val.(map[string]interface{}); ok {
var invalidKeys []string
for k := range nested {
if _, allowed := allowedKeys[k]; !allowed {
invalidKeys = append(invalidKeys, k)
}
}
if len(invalidKeys) > 0 {
ve.AddDetailWithSpan(
errors.ErrVariablesInvalidKey,
fmt.Sprintf(errors.ErrorMessages[errors.ErrVariablesInvalidKey], varName, strings.Join(invalidKeys, ", ")),
line, col, line, col+len(varName),
)
}
}
if len(invalidKeys) > 0 {
ve.AddDetailWithSpan(errors.ErrVariablesInvalidKey,
fmt.Sprintf(errors.ErrorMessages[errors.ErrVariablesInvalidKey], varName, strings.Join(invalidKeys, ", ")),
0, 0, 0, 0)
}
}
}
func validateJobVariables(job *Job, ve *errors.ValidationError) {
if job.VariablesNode == nil {
return
}
if job.VariablesNode.Kind != yaml.MappingNode {
ve.AddDetailWithSpan(
errors.ErrVariablesInvalid,
"job variables should be a map",
job.Line, job.Column, job.Line, job.Column+1,
)
return
}
varsMap, ok := job.Variables.(map[string]interface{})
if !ok {
ve.AddDetailWithSpan(
errors.ErrVariablesInvalid,
"job variables should be a map",
job.Line, job.Column, job.Line, job.Column+1,
)
return
}
validateVariablesMap(varsMap, job.VariablesNode, ve, func(varName string) (int, int) {
// Ищем переменную в узле job.VariablesNode
for i := 0; i < len(job.VariablesNode.Content)-1; i += 2 {
keyNode := job.VariablesNode.Content[i]
if keyNode.Value == varName {
return keyNode.Line, keyNode.Column
}
} else {
ve.AddDetailWithSpan(errors.ErrVariableInvalid,
fmt.Sprintf(errors.ErrorMessages[errors.ErrVariableInvalid], varName),
0, 0, 0, 0)
}
return job.Line, job.Column // если не найдено, используем позицию job
})
}
func validateVariables(cfg *GitLabCIConfig, ve *errors.ValidationError) {
if cfg.VariablesNode == nil {
return
}
if cfg.VariablesNode.Kind != yaml.MappingNode {
ve.AddDetailWithSpan(
errors.ErrVariablesInvalid,
errors.ErrorMessages[errors.ErrVariablesInvalid],
0, 0, 0, 0,
)
return
}
varsMap, ok := cfg.Variables.(map[string]interface{})
if !ok {
ve.AddDetailWithSpan(
errors.ErrVariablesInvalid,
errors.ErrorMessages[errors.ErrVariablesInvalid],
0, 0, 0, 0,
)
return
}
validateVariablesMap(varsMap, cfg.VariablesNode, ve, func(varName string) (int, int) {
// Ищем в YAML узле переменной с именем varName
for i := 0; i < len(cfg.VariablesNode.Content)-1; i += 2 {
keyNode := cfg.VariablesNode.Content[i]
if keyNode.Value == varName {
return keyNode.Line, keyNode.Column
}
}
return 0, 0
})
}
func isScalar(val interface{}) bool {
@ -335,6 +413,7 @@ func validateJobs(cfg *GitLabCIConfig, ve *errors.ValidationError) {
validateJobRules(job, ve)
validateJobWhen(job, ve)
validateJobOnly(job, ve)
validateJobVariables(job, ve)
}
if visibleCount == 0 {
@ -798,3 +877,20 @@ func validateIncludeRuleMap(m map[string]interface{}, ve *errors.ValidationError
}
}
}
// checkFinalEmptyLine проверяет, что файл заканчивается пустой строкой.
func checkFinalEmptyLine(content string, ve *errors.ValidationError) {
// Если содержимое не заканчивается символом новой строки...
if !strings.HasSuffix(content, "\n") {
lines := strings.Split(content, "\n")
lastLineIndex := len(lines)
lastLineContent := lines[lastLineIndex-1]
lineNum := lastLineIndex
colNum := len(lastLineContent) + 1
ve.AddDetailWithSpan(
errors.ErrMissingFinalNewline,
errors.ErrorMessages[errors.ErrMissingFinalNewline],
lineNum, colNum, lineNum, colNum,
)
}
}

@ -1,71 +1,5 @@
package main
import (
"bufio"
"fmt"
"os"
"tea.gitpark.ru/Azaki/CI_VALIDATOR/gitlabcivalidator"
)
func main() {
// Корректный (упрощённый) YAML
// validYAML := `
//stages:
// - build
// - test
//jobs:
// build_job:
// stage: build
// script:
// - echo "Building..."
// test_job:
// stage: test
// dependencies: ["build_job"]
// script:
// - echo "Testing..."
//`
// Тип, ссылка и текст ошибки, показатели где нахоидтся ошибка...
// Некорректный YAML — отсутствует видимая задача, есть только скрытая
// invalidYAML := `
//stages:
// - build
// - test
//jobs:
// teaaa:
// build_job:
// validateJobMapping(jobValNode, ve):
// script:
// - c
// needs: ["test_job2"]
// test_job2:
// stage: test
//`
scanner := bufio.NewScanner(os.Stdin)
var inputYAML string
for scanner.Scan() {
inputYAML += scanner.Text() + "\n"
}
if err := scanner.Err(); err != nil {
fmt.Println("Ошибка чтения ввода:", err)
return
}
err := gitlabcivalidator.ValidateGitLabCIFile(inputYAML, gitlabcivalidator.ValidationOptions{})
if err != nil {
fmt.Println(err.Error())
} else {
fmt.Println("no errors")
}
//fmt.Println("\n=== Пример 2: Некорректный YAML ===")
//err2 := gitlabcivalidator.ValidateGitLabCIFile(invalidYAML, gitlabcivalidator.ValidationOptions{})
//if err2 != nil {
// fmt.Println("Ошибка валидации (ожидаемо для данного файла):")
// fmt.Println(err2.Error())
//} else {
// log.Fatal("Ожидали ошибку, но её не возникло")
//}
}

Loading…
Cancel
Save