diff --git a/code/go/internal/validator/semantic/validate_var_groups.go b/code/go/internal/validator/semantic/validate_var_groups.go new file mode 100644 index 000000000..07d28ef36 --- /dev/null +++ b/code/go/internal/validator/semantic/validate_var_groups.go @@ -0,0 +1,200 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package semantic + +import ( + "fmt" + "io/fs" + "path" + + "gopkg.in/yaml.v3" + + "github.com/elastic/package-spec/v3/code/go/internal/fspath" + "github.com/elastic/package-spec/v3/code/go/pkg/specerrors" +) + +// ValidateVarGroups validates var_groups definitions in manifests. +// It checks that: +// - vars referenced in options[].vars exist in the manifest vars array +// - var_group names are unique +// - option names within each var_group are unique +// - vars in a var_group must not have required: true (requirement is controlled by var_group) +func ValidateVarGroups(fsys fspath.FS) specerrors.ValidationErrors { + // Validate main manifest. + d, err := fs.ReadFile(fsys, "manifest.yml") + if err != nil { + return specerrors.ValidationErrors{specerrors.NewStructuredErrorf("failed to read file \"%s\": %w", fsys.Path("manifest.yml"), err)} + } + + var manifest varGroupsManifest + err = yaml.Unmarshal(d, &manifest) + if err != nil { + return specerrors.ValidationErrors{specerrors.NewStructuredErrorf("file \"%s\" is invalid: failed to parse manifest: %w", fsys.Path("manifest.yml"), err)} + } + errs := validateVarGroupsManifest(fsys.Path("manifest.yml"), manifest) + + // Validate data stream manifests. + dataStreams, err := listDataStreams(fsys) + if err != nil { + return specerrors.ValidationErrors{specerrors.NewStructuredErrorf("failed to list data streams: %w", err)} + } + for _, ds := range dataStreams { + errs = append(errs, validateDataStreamVarGroups(fsys, path.Join("data_stream", ds, "manifest.yml"), manifest)...) + } + + return errs +} + +type varGroupsManifestVar struct { + Name string `yaml:"name"` + Required bool `yaml:"required"` +} + +type varGroupOption struct { + Name string `yaml:"name"` + Vars []string `yaml:"vars"` +} + +type varGroup struct { + Name string `yaml:"name"` + Required bool `yaml:"required"` + Options []varGroupOption `yaml:"options"` +} + +type varGroupsManifest struct { + Vars []varGroupsManifestVar `yaml:"vars"` + VarGroups []varGroup `yaml:"var_groups"` + PolicyTemplates []struct { + Vars []varGroupsManifestVar `yaml:"vars"` + Inputs []struct { + Vars []varGroupsManifestVar `yaml:"vars"` + } `yaml:"inputs"` + } `yaml:"policy_templates"` +} + +type varGroupsStream struct { + Title string `yaml:"title"` + Input string `yaml:"input"` + Vars []varGroupsManifestVar `yaml:"vars"` + VarGroups []varGroup `yaml:"var_groups"` +} + +type varGroupsDataStreamManifest struct { + Streams []varGroupsStream `yaml:"streams"` +} + +func validateVarGroupsManifest(filePath string, manifest varGroupsManifest) specerrors.ValidationErrors { + var errs specerrors.ValidationErrors + + // Collect all available vars from package, policy templates, and inputs + var availableVars []varGroupsManifestVar + availableVars = append(availableVars, manifest.Vars...) + for _, template := range manifest.PolicyTemplates { + availableVars = append(availableVars, template.Vars...) + for _, input := range template.Inputs { + availableVars = append(availableVars, input.Vars...) + } + } + + errs = append(errs, validateVarGroups(filePath, manifest.VarGroups, availableVars)...) + + return errs +} + +func validateDataStreamVarGroups(fsys fspath.FS, filePath string, pkgManifest varGroupsManifest) specerrors.ValidationErrors { + d, err := fs.ReadFile(fsys, filePath) + if err != nil { + // File might not exist, which is fine + return nil + } + + var manifest varGroupsDataStreamManifest + err = yaml.Unmarshal(d, &manifest) + if err != nil { + return specerrors.ValidationErrors{specerrors.NewStructuredErrorf("file \"%s\" is invalid: failed to parse manifest: %w", fsys.Path(filePath), err)} + } + + var errs specerrors.ValidationErrors + + // Validate var_groups in each stream + for i, stream := range manifest.Streams { + if len(stream.VarGroups) == 0 { + continue + } + + // Collect available vars from both package manifest and stream-level vars + var availableVars []varGroupsManifestVar + availableVars = append(availableVars, pkgManifest.Vars...) + availableVars = append(availableVars, stream.Vars...) + + streamID := stream.Title + if streamID == "" { + streamID = stream.Input + } + if streamID == "" { + streamID = fmt.Sprintf("stream[%d]", i) + } + + streamErrs := validateVarGroups( + fmt.Sprintf("%s (stream: %s)", fsys.Path(filePath), streamID), + stream.VarGroups, + availableVars, + ) + errs = append(errs, streamErrs...) + } + + return errs +} + +func validateVarGroups(filePath string, varGroups []varGroup, availableVars []varGroupsManifestVar) specerrors.ValidationErrors { + var errs specerrors.ValidationErrors + + // Build a map for quick var lookup + varMap := make(map[string]varGroupsManifestVar) + for _, v := range availableVars { + varMap[v.Name] = v + } + + // Check for duplicate var_group names + seenGroupNames := make(map[string]bool) + for _, vg := range varGroups { + if seenGroupNames[vg.Name] { + errs = append(errs, specerrors.NewStructuredErrorf("file \"%s\" is invalid: duplicate var_group name %q", filePath, vg.Name)) + } + seenGroupNames[vg.Name] = true + + // Check for duplicate option names within each var_group + seenOptionNames := make(map[string]bool) + for _, opt := range vg.Options { + if seenOptionNames[opt.Name] { + errs = append(errs, specerrors.NewStructuredErrorf("file \"%s\" is invalid: duplicate option name %q in var_group %q", filePath, opt.Name, vg.Name)) + } + seenOptionNames[opt.Name] = true + + // Validate that referenced vars exist and check required consistency + for _, varName := range opt.Vars { + varDef, exists := varMap[varName] + if !exists { + errs = append(errs, specerrors.NewStructuredErrorf("file \"%s\" is invalid: var %q referenced in var_group %q option %q is not defined", filePath, varName, vg.Name, opt.Name)) + continue + } + + // Validate that vars in a var_group do not have required: true + // The requirement is controlled entirely by the var_group: + // - If var_group is required, all vars are implicitly required (inferred) + // - If var_group is not required, the entire group is optional + if varDef.Required { + if vg.Required { + errs = append(errs, specerrors.NewStructuredErrorf("file \"%s\" is invalid: var %q in required var_group %q should not have required: true (requirement is inferred from var_group)", filePath, varName, vg.Name)) + } else { + errs = append(errs, specerrors.NewStructuredErrorf("file \"%s\" is invalid: var %q in non-required var_group %q should not have required: true (var_group is optional)", filePath, varName, vg.Name)) + } + } + } + } + } + + return errs +} diff --git a/code/go/internal/validator/semantic/validate_var_groups_test.go b/code/go/internal/validator/semantic/validate_var_groups_test.go new file mode 100644 index 000000000..e8395ebc9 --- /dev/null +++ b/code/go/internal/validator/semantic/validate_var_groups_test.go @@ -0,0 +1,381 @@ +// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +// or more contributor license agreements. Licensed under the Elastic License; +// you may not use this file except in compliance with the Elastic License. + +package semantic + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gopkg.in/yaml.v3" +) + +func TestValidateVarGroupsManifest(t *testing.T) { + cases := []struct { + title string + manifest string + errors []string + }{ + { + title: "valid var_groups", + manifest: ` +vars: + - name: access_key_id + - name: secret_access_key + - name: role_arn +var_groups: + - name: credential_type + options: + - name: direct_access_key + vars: + - access_key_id + - secret_access_key + - name: assume_role + vars: + - role_arn +`, + }, + { + title: "variable in policy template", + manifest: ` +vars: + - name: access_key_id +policy_templates: + - vars: + - name: secret_access_key +var_groups: + - name: credential_type + options: + - name: direct_access_key + vars: + - access_key_id + - secret_access_key +`, + }, + { + title: "variable in input", + manifest: ` +vars: + - name: access_key_id +policy_templates: + - inputs: + - vars: + - name: secret_access_key +var_groups: + - name: credential_type + options: + - name: direct_access_key + vars: + - access_key_id + - secret_access_key +`, + }, + { + title: "missing variable", + manifest: ` +vars: + - name: access_key_id +var_groups: + - name: credential_type + options: + - name: direct_access_key + vars: + - access_key_id + - secret_access_key +`, + errors: []string{ + `file "manifest.yml" is invalid: var "secret_access_key" referenced in var_group "credential_type" option "direct_access_key" is not defined`, + }, + }, + { + title: "duplicate var_group name", + manifest: ` +vars: + - name: access_key_id +var_groups: + - name: credential_type + options: + - name: direct_access_key + vars: + - access_key_id + - name: credential_type + options: + - name: another_option + vars: + - access_key_id +`, + errors: []string{ + `file "manifest.yml" is invalid: duplicate var_group name "credential_type"`, + }, + }, + { + title: "duplicate option name", + manifest: ` +vars: + - name: access_key_id + - name: secret_access_key +var_groups: + - name: credential_type + options: + - name: direct_access_key + vars: + - access_key_id + - name: direct_access_key + vars: + - secret_access_key +`, + errors: []string{ + `file "manifest.yml" is invalid: duplicate option name "direct_access_key" in var_group "credential_type"`, + }, + }, + { + title: "no var_groups is valid", + manifest: ` +vars: + - name: access_key_id +`, + }, + { + title: "required var_group with non-required vars is valid", + manifest: ` +vars: + - name: access_key_id + - name: secret_access_key +var_groups: + - name: credential_type + required: true + options: + - name: direct_access_key + vars: + - access_key_id + - secret_access_key +`, + }, + { + title: "non-required var_group with non-required vars is valid", + manifest: ` +vars: + - name: access_key_id + - name: secret_access_key +var_groups: + - name: credential_type + required: false + options: + - name: direct_access_key + vars: + - access_key_id + - secret_access_key +`, + }, + { + title: "required var_group with required vars is invalid", + manifest: ` +vars: + - name: access_key_id + required: true + - name: secret_access_key +var_groups: + - name: credential_type + required: true + options: + - name: direct_access_key + vars: + - access_key_id + - secret_access_key +`, + errors: []string{ + `file "manifest.yml" is invalid: var "access_key_id" in required var_group "credential_type" should not have required: true (requirement is inferred from var_group)`, + }, + }, + { + title: "non-required var_group with required vars is invalid", + manifest: ` +vars: + - name: access_key_id + required: true + - name: secret_access_key + required: true +var_groups: + - name: credential_type + required: false + options: + - name: direct_access_key + vars: + - access_key_id + - secret_access_key +`, + errors: []string{ + `file "manifest.yml" is invalid: var "access_key_id" in non-required var_group "credential_type" should not have required: true (var_group is optional)`, + `file "manifest.yml" is invalid: var "secret_access_key" in non-required var_group "credential_type" should not have required: true (var_group is optional)`, + }, + }, + { + title: "default (non-required) var_group with non-required vars is valid", + manifest: ` +vars: + - name: access_key_id +var_groups: + - name: credential_type + options: + - name: direct_access_key + vars: + - access_key_id +`, + }, + } + + for _, c := range cases { + t.Run(c.title, func(t *testing.T) { + var manifest varGroupsManifest + err := yaml.Unmarshal([]byte(c.manifest), &manifest) + require.NoError(t, err) + + errors := validateVarGroupsManifest("manifest.yml", manifest) + assert.Len(t, errors, len(c.errors)) + for _, err := range errors { + assert.Contains(t, c.errors, err.Error()) + } + }) + } +} + +func TestValidateVarGroups(t *testing.T) { + cases := []struct { + title string + varGroups []varGroup + availableVars []varGroupsManifestVar + errors []string + }{ + { + title: "valid - all vars exist with required var_group", + varGroups: []varGroup{ + { + Name: "auth_type", + Required: true, + Options: []varGroupOption{ + {Name: "basic", Vars: []string{"username", "password"}}, + {Name: "api_key", Vars: []string{"api_key"}}, + }, + }, + }, + availableVars: []varGroupsManifestVar{ + {Name: "username", Required: false}, + {Name: "password", Required: false}, + {Name: "api_key", Required: false}, + }, + errors: nil, + }, + { + title: "valid - all vars exist with non-required var_group and non-required vars", + varGroups: []varGroup{ + { + Name: "auth_type", + Required: false, + Options: []varGroupOption{ + {Name: "basic", Vars: []string{"username", "password"}}, + }, + }, + }, + availableVars: []varGroupsManifestVar{ + {Name: "username", Required: false}, + {Name: "password", Required: false}, + }, + errors: nil, + }, + { + title: "missing var reference", + varGroups: []varGroup{ + { + Name: "auth_type", + Required: true, + Options: []varGroupOption{ + {Name: "basic", Vars: []string{"username", "password", "missing_var"}}, + }, + }, + }, + availableVars: []varGroupsManifestVar{ + {Name: "username", Required: false}, + {Name: "password", Required: false}, + }, + errors: []string{ + `file "test.yml" is invalid: var "missing_var" referenced in var_group "auth_type" option "basic" is not defined`, + }, + }, + { + title: "duplicate var_group names", + varGroups: []varGroup{ + {Name: "auth_type", Required: true, Options: []varGroupOption{{Name: "opt1", Vars: []string{}}}}, + {Name: "auth_type", Required: true, Options: []varGroupOption{{Name: "opt2", Vars: []string{}}}}, + }, + availableVars: []varGroupsManifestVar{}, + errors: []string{ + `file "test.yml" is invalid: duplicate var_group name "auth_type"`, + }, + }, + { + title: "duplicate option names within var_group", + varGroups: []varGroup{ + { + Name: "auth_type", + Required: true, + Options: []varGroupOption{ + {Name: "basic", Vars: []string{}}, + {Name: "basic", Vars: []string{}}, + }, + }, + }, + availableVars: []varGroupsManifestVar{}, + errors: []string{ + `file "test.yml" is invalid: duplicate option name "basic" in var_group "auth_type"`, + }, + }, + { + title: "required var_group with required var is invalid", + varGroups: []varGroup{ + { + Name: "auth_type", + Required: true, + Options: []varGroupOption{ + {Name: "basic", Vars: []string{"username"}}, + }, + }, + }, + availableVars: []varGroupsManifestVar{ + {Name: "username", Required: true}, + }, + errors: []string{ + `file "test.yml" is invalid: var "username" in required var_group "auth_type" should not have required: true (requirement is inferred from var_group)`, + }, + }, + { + title: "non-required var_group with required var is invalid", + varGroups: []varGroup{ + { + Name: "auth_type", + Required: false, + Options: []varGroupOption{ + {Name: "basic", Vars: []string{"username"}}, + }, + }, + }, + availableVars: []varGroupsManifestVar{ + {Name: "username", Required: true}, + }, + errors: []string{ + `file "test.yml" is invalid: var "username" in non-required var_group "auth_type" should not have required: true (var_group is optional)`, + }, + }, + } + + for _, c := range cases { + t.Run(c.title, func(t *testing.T) { + errors := validateVarGroups("test.yml", c.varGroups, c.availableVars) + assert.Len(t, errors, len(c.errors)) + for _, err := range errors { + assert.Contains(t, c.errors, err.Error()) + } + }) + } +} diff --git a/code/go/internal/validator/spec.go b/code/go/internal/validator/spec.go index 7484e8df3..bbdbf7294 100644 --- a/code/go/internal/validator/spec.go +++ b/code/go/internal/validator/spec.go @@ -218,6 +218,7 @@ func (s Spec) rules(pkgType string, rootSpec spectypes.ItemSpec) validationRules {fn: semantic.ValidateDimensionsPresent, types: []string{"integration"}, since: semver.MustParse("3.0.1")}, {fn: semantic.ValidateCapabilitiesRequired, since: semver.MustParse("2.10.0")}, // capabilities definition was added in spec version 2.10.0 {fn: semantic.ValidateRequiredVarGroups}, + {fn: semantic.ValidateVarGroups, since: semver.MustParse("3.6.0")}, {fn: semantic.ValidateDocsStructure}, {fn: semantic.ValidateDeploymentModes, types: []string{"integration"}}, {fn: semantic.ValidateDurationVariables, since: semver.MustParse("3.5.0")}, diff --git a/code/go/pkg/validator/validator_test.go b/code/go/pkg/validator/validator_test.go index 8407735dc..21efe0391 100644 --- a/code/go/pkg/validator/validator_test.go +++ b/code/go/pkg/validator/validator_test.go @@ -33,6 +33,7 @@ func TestValidateFile(t *testing.T) { "good": {}, "good_v2": {}, "good_v3": {}, + "good_var_groups": {}, "good_input": {}, "good_input_otel": {}, "good_content": {}, @@ -283,6 +284,30 @@ func TestValidateFile(t *testing.T) { `required var "password" in optional group is not defined`, }, }, + "bad_var_groups_missing_var": { + "manifest.yml", + []string{ + `var "non_existent_var" referenced in var_group "credential_type" option "direct_access_key" is not defined`, + }, + }, + "bad_var_groups_duplicate_name": { + "manifest.yml", + []string{ + `duplicate option name "direct_access_key" in var_group "credential_type"`, + }, + }, + "bad_var_groups_required_var_in_required_group": { + "manifest.yml", + []string{ + `var "access_key_id" in required var_group "credential_type" should not have required: true (requirement is inferred from var_group)`, + }, + }, + "bad_var_groups_required_var_in_optional_group": { + "manifest.yml", + []string{ + `var "access_key_id" in non-required var_group "credential_type" should not have required: true (var_group is optional)`, + }, + }, "bad_input_deployment_modes": { "manifest.yml", []string{ diff --git a/spec/changelog.yml b/spec/changelog.yml index 1c484a493..d4f0a1037 100644 --- a/spec/changelog.yml +++ b/spec/changelog.yml @@ -21,6 +21,11 @@ - description: Add support for deprecating packages or individual features (policy_templates, inputs, data_streams or variables). type: enhancement link: https://github.com/elastic/package-spec/pull/1053 + # Pending on https://github.com/elastic/kibana/pull/249449 + # Pending on https://github.com/elastic/integrations/pull/16985 + - description: Add var_groups schema to support conditional variable groups for Cloud Connector integration. + type: enhancement + link: https://github.com/elastic/package-spec/issues/1054 # Pending on https://github.com/elastic/kibana/pull/251205 - description: Allow to set time series index mode in input packages. type: enhancement diff --git a/spec/integration/data_stream/manifest.spec.yml b/spec/integration/data_stream/manifest.spec.yml index 39a3ff638..c16cf8183 100644 --- a/spec/integration/data_stream/manifest.spec.yml +++ b/spec/integration/data_stream/manifest.spec.yml @@ -601,6 +601,8 @@ spec: $ref: "#/definitions/required_vars" vars: $ref: "#/definitions/vars" + var_groups: + $ref: "../../integration/manifest.spec.yml#/definitions/var_groups" enabled: description: Is stream enabled? type: boolean @@ -666,6 +668,8 @@ versions: path: "/properties/deprecated" - op: remove # remove deprecated field for vars path: /definitions/vars/items/properties/deprecated + - op: remove + path: "/properties/streams/items/properties/var_groups" # removes var_groups from streams - before: 3.5.0 patch: # Require >=3.5.0 to use the duration variable type. diff --git a/spec/integration/manifest.spec.yml b/spec/integration/manifest.spec.yml index b334a25c2..ddb51b21e 100644 --- a/spec/integration/manifest.spec.yml +++ b/spec/integration/manifest.spec.yml @@ -524,6 +524,97 @@ spec: required: - since - description + var_groups: + description: > + Defines mutually exclusive groups of variables. When an option is selected, + only the variables in that option's vars array are shown. The selected option + name is stored in the policy. Additional properties on options are allowed + for feature-specific extensions (e.g., Cloud Connector metadata). + type: array + items: + type: object + additionalProperties: false + properties: + name: + description: Unique identifier for this variable group selector. + type: string + pattern: '^[a-z][a-z0-9_]*$' + examples: + - credential_type + title: + description: Section header displayed in the UI (e.g., "Setup Access"). + type: string + examples: + - Setup Access + selector_title: + description: Label for the dropdown selector (e.g., "Preferred method"). + type: string + examples: + - Preferred method + description: + description: Help text explaining what this selector controls. + type: string + examples: + - Select how you want to authenticate with AWS. + required: + description: > + Whether a selection is required for this var_group. When true, Fleet UI + will require the user to select an option, and all variables within the + selected option are treated as required (inferred). When false (default), + the entire var_group is optional. Variables within a var_group must not + have required: true - the requirement is controlled entirely by this field. + type: boolean + default: false + options: + description: Available options. First option is the default. + type: array + minItems: 1 + items: + type: object + additionalProperties: true + properties: + name: + description: Unique identifier (stored in policy when selected). + type: string + pattern: '^[a-z][a-z0-9_]*$' + examples: + - direct_access_key + - cloud_connectors + title: + description: Display title shown in the dropdown. + type: string + examples: + - Direct Access Keys + - Cloud Connector (recommended) + description: + description: Help text for this option. + type: string + examples: + - Use AWS access key ID and secret access key directly. + vars: + description: Variable names to display when this option is selected. + type: array + items: + type: string + examples: + - [access_key_id, secret_access_key] + hide_in_deployment_modes: + description: Deployment modes where this option is hidden. + type: array + items: + type: string + enum: + - default + - agentless + required: + - name + - title + - vars + required: + - name + - title + - selector_title + - options properties: format_version: description: The version of the package specification format used by this package. @@ -665,6 +756,17 @@ spec: $ref: "./data_stream/manifest.spec.yml#/definitions/vars" deprecated: $ref: "#/definitions/deprecated" + hide_in_var_group_options: + description: > + Filter out specific var_group options for this input. + Keys are var_group names, values are arrays of option names to hide. + type: object + additionalProperties: + type: array + items: + type: string + examples: + - credential_type: [cloud_connectors] required: - type - title @@ -689,6 +791,8 @@ spec: $ref: "#/definitions/screenshots" vars: $ref: "./data_stream/manifest.spec.yml#/definitions/vars" + var_groups: + $ref: "#/definitions/var_groups" owner: $ref: "#/definitions/owner" agent: @@ -751,6 +855,12 @@ versions: path: "/properties/policy_templates/items/properties/deprecated" - op: remove # remove deprecated definition path: "/definitions/deprecated" + - op: remove + path: "/definitions/var_groups" # removes var_groups definition + - op: remove + path: "/properties/var_groups" # removes var_groups property + - op: remove + path: "/properties/policy_templates/items/properties/inputs/items/properties/hide_in_var_group_options" # removes hide_in_var_group_options from inputs - before: 3.3.2 patch: - op: remove diff --git a/test/packages/bad_var_groups_duplicate_name/LICENSE.txt b/test/packages/bad_var_groups_duplicate_name/LICENSE.txt new file mode 100644 index 000000000..f2d489e45 --- /dev/null +++ b/test/packages/bad_var_groups_duplicate_name/LICENSE.txt @@ -0,0 +1,7 @@ +Elastic License 2.0 + +URL: https://www.elastic.co/licensing/elastic-license + +## Acceptance + +By using the software, you agree to all of the terms and conditions below. diff --git a/test/packages/bad_var_groups_duplicate_name/changelog.yml b/test/packages/bad_var_groups_duplicate_name/changelog.yml new file mode 100644 index 000000000..e00f88133 --- /dev/null +++ b/test/packages/bad_var_groups_duplicate_name/changelog.yml @@ -0,0 +1,6 @@ +# newer versions go on top +- version: "0.0.1" + changes: + - description: Initial draft of the package + type: enhancement + link: https://github.com/elastic/integrations/pull/1 diff --git a/test/packages/bad_var_groups_duplicate_name/docs/README.md b/test/packages/bad_var_groups_duplicate_name/docs/README.md new file mode 100644 index 000000000..fd73506b7 --- /dev/null +++ b/test/packages/bad_var_groups_duplicate_name/docs/README.md @@ -0,0 +1,4 @@ +# Bad Var Groups - Duplicate Names + +This test package has var_groups with duplicate option names. +Should fail validation with an error about duplicate `direct_access_key` option name. diff --git a/test/packages/bad_var_groups_duplicate_name/img/sample-logo.svg b/test/packages/bad_var_groups_duplicate_name/img/sample-logo.svg new file mode 100644 index 000000000..6268dd88f --- /dev/null +++ b/test/packages/bad_var_groups_duplicate_name/img/sample-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/packages/bad_var_groups_duplicate_name/img/sample-screenshot.png b/test/packages/bad_var_groups_duplicate_name/img/sample-screenshot.png new file mode 100644 index 000000000..d7a56a3ec Binary files /dev/null and b/test/packages/bad_var_groups_duplicate_name/img/sample-screenshot.png differ diff --git a/test/packages/bad_var_groups_duplicate_name/manifest.yml b/test/packages/bad_var_groups_duplicate_name/manifest.yml new file mode 100644 index 000000000..a282dfe8d --- /dev/null +++ b/test/packages/bad_var_groups_duplicate_name/manifest.yml @@ -0,0 +1,64 @@ +format_version: 3.6.0 +name: bad_var_groups_duplicate_name +title: Bad Var Groups - Duplicate Names +description: This package has var_groups with duplicate option names. +version: 0.0.1 +type: integration +source: + license: "Apache-2.0" +conditions: + kibana: + version: '^8.10.0' + elastic: + subscription: 'basic' + agent: + version: '^9.1.0' +vars: + - name: access_key_id + type: text + title: Access Key ID + show_user: true + secret: false + - name: secret_access_key + type: password + title: Secret Access Key + show_user: true + secret: true +var_groups: + - name: credential_type + title: Setup Access + selector_title: Preferred method + description: Select how to authenticate. + options: + - name: direct_access_key + title: Direct Access Keys + vars: + - access_key_id + - secret_access_key + # Duplicate option name - should cause validation error + - name: direct_access_key + title: Another Direct Access Keys + vars: + - access_key_id +policy_templates: + - name: sample + title: Sample + description: Sample policy template + inputs: + - type: httpjson + title: Collect via API + description: Collecting data + multi: false +owner: + github: elastic/ecosystem + type: elastic +screenshots: + - src: /img/sample-screenshot.png + title: Sample screenshot + size: 600x600 + type: image/png +icons: + - src: /img/sample-logo.svg + title: Sample logo + size: 32x32 + type: image/svg+xml diff --git a/test/packages/bad_var_groups_missing_var/LICENSE.txt b/test/packages/bad_var_groups_missing_var/LICENSE.txt new file mode 100644 index 000000000..f2d489e45 --- /dev/null +++ b/test/packages/bad_var_groups_missing_var/LICENSE.txt @@ -0,0 +1,7 @@ +Elastic License 2.0 + +URL: https://www.elastic.co/licensing/elastic-license + +## Acceptance + +By using the software, you agree to all of the terms and conditions below. diff --git a/test/packages/bad_var_groups_missing_var/changelog.yml b/test/packages/bad_var_groups_missing_var/changelog.yml new file mode 100644 index 000000000..e00f88133 --- /dev/null +++ b/test/packages/bad_var_groups_missing_var/changelog.yml @@ -0,0 +1,6 @@ +# newer versions go on top +- version: "0.0.1" + changes: + - description: Initial draft of the package + type: enhancement + link: https://github.com/elastic/integrations/pull/1 diff --git a/test/packages/bad_var_groups_missing_var/docs/README.md b/test/packages/bad_var_groups_missing_var/docs/README.md new file mode 100644 index 000000000..bc92ff2b2 --- /dev/null +++ b/test/packages/bad_var_groups_missing_var/docs/README.md @@ -0,0 +1,4 @@ +# Bad Var Groups - Missing Var Reference + +This test package has var_groups referencing a var that does not exist. +Should fail validation with an error about `non_existent_var` not being defined. diff --git a/test/packages/bad_var_groups_missing_var/img/sample-logo.svg b/test/packages/bad_var_groups_missing_var/img/sample-logo.svg new file mode 100644 index 000000000..6268dd88f --- /dev/null +++ b/test/packages/bad_var_groups_missing_var/img/sample-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/packages/bad_var_groups_missing_var/img/sample-screenshot.png b/test/packages/bad_var_groups_missing_var/img/sample-screenshot.png new file mode 100644 index 000000000..d7a56a3ec Binary files /dev/null and b/test/packages/bad_var_groups_missing_var/img/sample-screenshot.png differ diff --git a/test/packages/bad_var_groups_missing_var/manifest.yml b/test/packages/bad_var_groups_missing_var/manifest.yml new file mode 100644 index 000000000..bc34d64a1 --- /dev/null +++ b/test/packages/bad_var_groups_missing_var/manifest.yml @@ -0,0 +1,61 @@ +format_version: 3.6.0 +name: bad_var_groups_missing_var +title: Bad Var Groups - Missing Var Reference +description: This package has var_groups referencing a var that does not exist. +version: 0.0.1 +type: integration +source: + license: "Apache-2.0" +conditions: + kibana: + version: '^8.10.0' + elastic: + subscription: 'basic' + agent: + version: '^9.1.0' +vars: + - name: access_key_id + type: text + title: Access Key ID + show_user: true + secret: false + - name: secret_access_key + type: password + title: Secret Access Key + show_user: true + secret: true +var_groups: + - name: credential_type + title: Setup Access + selector_title: Preferred method + description: Select how to authenticate. + options: + - name: direct_access_key + title: Direct Access Keys + vars: + - access_key_id + - secret_access_key + # This var does not exist - should cause validation error + - non_existent_var +policy_templates: + - name: sample + title: Sample + description: Sample policy template + inputs: + - type: httpjson + title: Collect via API + description: Collecting data + multi: false +owner: + github: elastic/ecosystem + type: elastic +screenshots: + - src: /img/sample-screenshot.png + title: Sample screenshot + size: 600x600 + type: image/png +icons: + - src: /img/sample-logo.svg + title: Sample logo + size: 32x32 + type: image/svg+xml diff --git a/test/packages/bad_var_groups_required_var_in_optional_group/LICENSE.txt b/test/packages/bad_var_groups_required_var_in_optional_group/LICENSE.txt new file mode 100644 index 000000000..f2d489e45 --- /dev/null +++ b/test/packages/bad_var_groups_required_var_in_optional_group/LICENSE.txt @@ -0,0 +1,7 @@ +Elastic License 2.0 + +URL: https://www.elastic.co/licensing/elastic-license + +## Acceptance + +By using the software, you agree to all of the terms and conditions below. diff --git a/test/packages/bad_var_groups_required_var_in_optional_group/changelog.yml b/test/packages/bad_var_groups_required_var_in_optional_group/changelog.yml new file mode 100644 index 000000000..e00f88133 --- /dev/null +++ b/test/packages/bad_var_groups_required_var_in_optional_group/changelog.yml @@ -0,0 +1,6 @@ +# newer versions go on top +- version: "0.0.1" + changes: + - description: Initial draft of the package + type: enhancement + link: https://github.com/elastic/integrations/pull/1 diff --git a/test/packages/bad_var_groups_required_var_in_optional_group/docs/README.md b/test/packages/bad_var_groups_required_var_in_optional_group/docs/README.md new file mode 100644 index 000000000..5a7f70522 --- /dev/null +++ b/test/packages/bad_var_groups_required_var_in_optional_group/docs/README.md @@ -0,0 +1,4 @@ +# Bad Var Groups - Required Var in Optional Group + +This test package has a var with `required: true` inside an optional var_group (required: false). +Should fail validation because vars in a var_group should not have `required: true` - when the var_group is optional, the entire group is optional. diff --git a/test/packages/bad_var_groups_required_var_in_optional_group/img/sample-logo.svg b/test/packages/bad_var_groups_required_var_in_optional_group/img/sample-logo.svg new file mode 100644 index 000000000..6268dd88f --- /dev/null +++ b/test/packages/bad_var_groups_required_var_in_optional_group/img/sample-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/packages/bad_var_groups_required_var_in_optional_group/img/sample-screenshot.png b/test/packages/bad_var_groups_required_var_in_optional_group/img/sample-screenshot.png new file mode 100644 index 000000000..d7a56a3ec Binary files /dev/null and b/test/packages/bad_var_groups_required_var_in_optional_group/img/sample-screenshot.png differ diff --git a/test/packages/bad_var_groups_required_var_in_optional_group/manifest.yml b/test/packages/bad_var_groups_required_var_in_optional_group/manifest.yml new file mode 100644 index 000000000..05afc98a4 --- /dev/null +++ b/test/packages/bad_var_groups_required_var_in_optional_group/manifest.yml @@ -0,0 +1,63 @@ +format_version: 3.6.0 +name: bad_var_groups_required_var_in_optional_group +title: Bad Var Groups - Required Var in Optional Group +description: This package has a var with required:true inside an optional var_group. +version: 0.0.1 +type: integration +source: + license: "Apache-2.0" +conditions: + kibana: + version: '^8.10.0' + elastic: + subscription: 'basic' + agent: + version: '^9.1.0' +vars: + - name: access_key_id + type: text + title: Access Key ID + show_user: true + secret: false + # This var has required: true which is invalid in any var_group + required: true + - name: secret_access_key + type: password + title: Secret Access Key + show_user: true + secret: true +var_groups: + - name: credential_type + title: Setup Access + selector_title: Preferred method + description: Select how to authenticate. + # var_group is optional (required: false), so vars should NOT have required: true + required: false + options: + - name: direct_access_key + title: Direct Access Keys + vars: + - access_key_id + - secret_access_key +policy_templates: + - name: sample + title: Sample + description: Sample policy template + inputs: + - type: httpjson + title: Collect via API + description: Collecting data + multi: false +owner: + github: elastic/ecosystem + type: elastic +screenshots: + - src: /img/sample-screenshot.png + title: Sample screenshot + size: 600x600 + type: image/png +icons: + - src: /img/sample-logo.svg + title: Sample logo + size: 32x32 + type: image/svg+xml diff --git a/test/packages/bad_var_groups_required_var_in_required_group/LICENSE.txt b/test/packages/bad_var_groups_required_var_in_required_group/LICENSE.txt new file mode 100644 index 000000000..f2d489e45 --- /dev/null +++ b/test/packages/bad_var_groups_required_var_in_required_group/LICENSE.txt @@ -0,0 +1,7 @@ +Elastic License 2.0 + +URL: https://www.elastic.co/licensing/elastic-license + +## Acceptance + +By using the software, you agree to all of the terms and conditions below. diff --git a/test/packages/bad_var_groups_required_var_in_required_group/changelog.yml b/test/packages/bad_var_groups_required_var_in_required_group/changelog.yml new file mode 100644 index 000000000..e00f88133 --- /dev/null +++ b/test/packages/bad_var_groups_required_var_in_required_group/changelog.yml @@ -0,0 +1,6 @@ +# newer versions go on top +- version: "0.0.1" + changes: + - description: Initial draft of the package + type: enhancement + link: https://github.com/elastic/integrations/pull/1 diff --git a/test/packages/bad_var_groups_required_var_in_required_group/docs/README.md b/test/packages/bad_var_groups_required_var_in_required_group/docs/README.md new file mode 100644 index 000000000..eb0131d91 --- /dev/null +++ b/test/packages/bad_var_groups_required_var_in_required_group/docs/README.md @@ -0,0 +1,4 @@ +# Bad Var Groups - Required Var in Required Group + +This test package has a var with `required: true` inside a required var_group. +Should fail validation because vars in a var_group should not have `required: true` - the requirement is inferred from the var_group. diff --git a/test/packages/bad_var_groups_required_var_in_required_group/img/sample-logo.svg b/test/packages/bad_var_groups_required_var_in_required_group/img/sample-logo.svg new file mode 100644 index 000000000..6268dd88f --- /dev/null +++ b/test/packages/bad_var_groups_required_var_in_required_group/img/sample-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/packages/bad_var_groups_required_var_in_required_group/img/sample-screenshot.png b/test/packages/bad_var_groups_required_var_in_required_group/img/sample-screenshot.png new file mode 100644 index 000000000..d7a56a3ec Binary files /dev/null and b/test/packages/bad_var_groups_required_var_in_required_group/img/sample-screenshot.png differ diff --git a/test/packages/bad_var_groups_required_var_in_required_group/manifest.yml b/test/packages/bad_var_groups_required_var_in_required_group/manifest.yml new file mode 100644 index 000000000..b5fb6c7a4 --- /dev/null +++ b/test/packages/bad_var_groups_required_var_in_required_group/manifest.yml @@ -0,0 +1,63 @@ +format_version: 3.6.0 +name: bad_var_groups_required_var_in_required_group +title: Bad Var Groups - Required Var in Required Group +description: This package has a var with required:true inside a required var_group. +version: 0.0.1 +type: integration +source: + license: "Apache-2.0" +conditions: + kibana: + version: '^8.10.0' + elastic: + subscription: 'basic' + agent: + version: '^9.1.0' +vars: + - name: access_key_id + type: text + title: Access Key ID + show_user: true + secret: false + # This var has required: true which is invalid in a required var_group + required: true + - name: secret_access_key + type: password + title: Secret Access Key + show_user: true + secret: true +var_groups: + - name: credential_type + title: Setup Access + selector_title: Preferred method + description: Select how to authenticate. + # var_group is required, so vars should NOT have required: true + required: true + options: + - name: direct_access_key + title: Direct Access Keys + vars: + - access_key_id + - secret_access_key +policy_templates: + - name: sample + title: Sample + description: Sample policy template + inputs: + - type: httpjson + title: Collect via API + description: Collecting data + multi: false +owner: + github: elastic/ecosystem + type: elastic +screenshots: + - src: /img/sample-screenshot.png + title: Sample screenshot + size: 600x600 + type: image/png +icons: + - src: /img/sample-logo.svg + title: Sample logo + size: 32x32 + type: image/svg+xml diff --git a/test/packages/good_var_groups/LICENSE.txt b/test/packages/good_var_groups/LICENSE.txt new file mode 100644 index 000000000..f2d489e45 --- /dev/null +++ b/test/packages/good_var_groups/LICENSE.txt @@ -0,0 +1,7 @@ +Elastic License 2.0 + +URL: https://www.elastic.co/licensing/elastic-license + +## Acceptance + +By using the software, you agree to all of the terms and conditions below. diff --git a/test/packages/good_var_groups/changelog.yml b/test/packages/good_var_groups/changelog.yml new file mode 100644 index 000000000..3202e0e08 --- /dev/null +++ b/test/packages/good_var_groups/changelog.yml @@ -0,0 +1,6 @@ +# newer versions go on top +- version: "1.0.0-next" + changes: + - description: Initial draft of the package + type: enhancement + link: https://github.com/elastic/integrations/pull/1 diff --git a/test/packages/good_var_groups/data_stream/findings/agent/stream/aws-s3.yml.hbs b/test/packages/good_var_groups/data_stream/findings/agent/stream/aws-s3.yml.hbs new file mode 100644 index 000000000..9df9e1f0e --- /dev/null +++ b/test/packages/good_var_groups/data_stream/findings/agent/stream/aws-s3.yml.hbs @@ -0,0 +1,2 @@ +# Handlebars template for aws-s3 input +bucket_arn: {{ bucket_arn }} diff --git a/test/packages/good_var_groups/data_stream/findings/fields/base-fields.yml b/test/packages/good_var_groups/data_stream/findings/fields/base-fields.yml new file mode 100644 index 000000000..20dbfcd1c --- /dev/null +++ b/test/packages/good_var_groups/data_stream/findings/fields/base-fields.yml @@ -0,0 +1,15 @@ +- name: data_stream.type + type: constant_keyword + description: Data stream type. +- name: data_stream.dataset + type: constant_keyword + description: Data stream dataset. +- name: data_stream.namespace + type: constant_keyword + description: Data stream namespace. +- name: "@timestamp" + type: date + description: Event timestamp. +- name: message + type: text + description: Log message. diff --git a/test/packages/good_var_groups/data_stream/findings/manifest.yml b/test/packages/good_var_groups/data_stream/findings/manifest.yml new file mode 100644 index 000000000..303cd6a69 --- /dev/null +++ b/test/packages/good_var_groups/data_stream/findings/manifest.yml @@ -0,0 +1,80 @@ +title: Findings +type: logs +streams: + - input: aws-s3 + template_path: aws-s3.yml.hbs + title: AWS S3 Findings + description: Collect findings from AWS S3 bucket + vars: + - name: bucket_arn + type: text + title: Bucket ARN + description: ARN of the S3 bucket + show_user: true + required: true + - name: access_key_id + type: text + title: Access Key ID + description: AWS access key ID for direct access + show_user: true + secret: false + - name: secret_access_key + type: password + title: Secret Access Key + description: AWS secret access key for direct access + show_user: true + secret: true + - name: session_token + type: password + title: Session Token + description: AWS session token for temporary credentials + show_user: true + secret: true + - name: role_arn + type: text + title: Role ARN + description: ARN of the IAM role to assume + show_user: true + - name: external_id + type: password + title: External ID + description: External ID for cross-account role assumption + show_user: true + secret: true + var_groups: + - name: credential_type + title: Setup Access + selector_title: Preferred method + description: > + Choose how to authenticate with AWS for collecting findings. + options: + - name: direct_access_key + title: Direct Access Keys + description: Use AWS access key ID and secret access key directly. + vars: + - access_key_id + - secret_access_key + - name: temporary_credentials + title: Temporary Credentials + description: Use temporary credentials with session token. + vars: + - access_key_id + - secret_access_key + - session_token + - name: assume_role + title: Assume Role + description: Assume an IAM role using role ARN. + vars: + - role_arn + - external_id + - name: cloud_connectors + title: Cloud Connector (recommended) + description: Use Elastic Cloud Connector for secure, agentless authentication. + vars: + - role_arn + - external_id + hide_in_deployment_modes: + - default + provider: aws + iac_template_url: >- + https://console.aws.amazon.com/cloudformation/home#/stacks/quickcreate?templateURL=https://elastic-cloud-connectors.s3.amazonaws.com/cloudformation-aws-connector.yml&stackName=Elastic-Cloud-Connector diff --git a/test/packages/good_var_groups/docs/README.md b/test/packages/good_var_groups/docs/README.md new file mode 100644 index 000000000..e90f1e883 --- /dev/null +++ b/test/packages/good_var_groups/docs/README.md @@ -0,0 +1,10 @@ +# Good Var Groups Package + +This test package demonstrates valid `var_groups` usage for conditional variable groups. + +## Features + +- Package-level `var_groups` with multiple authentication options +- Cloud Connector option with `provider` and `iac_template_url` extensions +- Input-level `hide_in_var_group_options` to filter options per input +- Deployment mode filtering via `hide_in_deployment_modes` diff --git a/test/packages/good_var_groups/img/sample-logo.svg b/test/packages/good_var_groups/img/sample-logo.svg new file mode 100644 index 000000000..6268dd88f --- /dev/null +++ b/test/packages/good_var_groups/img/sample-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/packages/good_var_groups/img/sample-screenshot.png b/test/packages/good_var_groups/img/sample-screenshot.png new file mode 100644 index 000000000..d7a56a3ec Binary files /dev/null and b/test/packages/good_var_groups/img/sample-screenshot.png differ diff --git a/test/packages/good_var_groups/manifest.yml b/test/packages/good_var_groups/manifest.yml new file mode 100644 index 000000000..cf7fb4e11 --- /dev/null +++ b/test/packages/good_var_groups/manifest.yml @@ -0,0 +1,127 @@ +format_version: 3.6.0 +name: good_var_groups +title: Good Var Groups Package +description: This package demonstrates valid var_groups usage for conditional variable groups. +version: 1.0.0-next +type: integration +source: + license: "Apache-2.0" +conditions: + kibana: + version: '^8.10.0' + elastic: + subscription: 'basic' + agent: + version: '^9.1.0' +vars: + - name: access_key_id + type: text + title: Access Key ID + show_user: true + secret: false + - name: secret_access_key + type: password + title: Secret Access Key + show_user: true + secret: true + - name: session_token + type: password + title: Session Token + show_user: true + secret: true + - name: role_arn + type: text + title: Role ARN + show_user: true + - name: external_id + type: password + title: External ID + show_user: true + secret: true +var_groups: + - name: credential_type + title: Setup Access + selector_title: Preferred method + description: > + Utilize AWS Access Keys or assume role to set up access + for assessing your AWS environment's security posture. + required: true # User MUST select an authentication method + options: + - name: direct_access_key + title: Direct Access Keys + description: Use AWS access key ID and secret access key directly. + vars: + - access_key_id + - secret_access_key + + - name: temporary_access_key + title: Temporary Access Keys + description: Use temporary credentials with session token. + vars: + - access_key_id + - secret_access_key + - session_token + + - name: cloud_connectors + title: Cloud Connector (recommended) + description: Use Elastic Cloud Connector for secure, agentless authentication. + vars: + - role_arn + - external_id + hide_in_deployment_modes: + - default + # Cloud Connector-specific extensions (allowed by additionalProperties: true) + provider: aws + iac_template_url: "https://console.aws.amazon.com/cloudformation/home#/stacks/quickcreate?templateURL=https://elastic-cloud-connectors.s3.amazonaws.com/cloudformation-aws-connector.yml&stackName=Elastic-Cloud-Connector" + + - name: assume_role + title: Assume Role + description: Assume an IAM role using role ARN. + vars: + - role_arn + hide_in_deployment_modes: + - agentless +policy_templates: + - name: sample + title: Sample logs and metrics + description: Collect sample logs and metrics + data_streams: + - findings + inputs: + - type: httpjson + title: Collect via API + description: Collecting data from HTTP JSON API + multi: false + vars: + - name: url + type: url + title: API URL + show_user: true + required: true + - type: aws-s3 + title: Collect from S3 + description: Collecting logs from AWS S3 bucket + multi: false + # Hide cloud_connectors option for this input (aws-s3 doesn't support it) + hide_in_var_group_options: + credential_type: + - cloud_connectors + vars: + - name: bucket_name + type: text + title: S3 Bucket Name + show_user: true + required: true +owner: + github: elastic/ecosystem + type: elastic +screenshots: + - src: /img/sample-screenshot.png + title: Sample screenshot + size: 600x600 + type: image/png +icons: + - src: /img/sample-logo.svg + title: Sample logo + size: 32x32 + type: image/svg+xml