diff --git a/CLAUDE.md b/CLAUDE.md index dc385dc..f9d9e16 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -278,6 +278,7 @@ img/ optional images kibana/ optional tags.yml optional schema:integration/kibana/tags.spec.yml dashboard/*.json optional opaque JSON saved objects + search/*.json optional " (3.6.6+) security_ai_prompt/*.json optional " security_rule/*.json optional " alerting_rule_template/*.json optional " diff --git a/cmd/generate/augment.yml b/cmd/generate/augment.yml index 1b04b47..da7a4a3 100644 --- a/cmd/generate/augment.yml +++ b/cmd/generate/augment.yml @@ -421,6 +421,7 @@ base_types: - deprecated - description - format_version + - group - icons - name - owner diff --git a/pkgspec/manifest.go b/pkgspec/manifest.go index 99c0a1c..413de20 100644 --- a/pkgspec/manifest.go +++ b/pkgspec/manifest.go @@ -367,6 +367,10 @@ type Manifest struct { Description string `json:"description" yaml:"description"` // The version of the package specification format used by this package. FormatVersion string `json:"format_version" yaml:"format_version"` + // Identifier of a marketplace group. Packages that share the same value belong to the same group. + // Kibana owns the group's title, icon, and description; this field only declares membership. Values + // are not validated against Kibana's group list. + Group string `json:"group,omitempty" yaml:"group,omitempty"` // List of icons for by this package. Icons []Icon `json:"icons,omitempty" yaml:"icons,omitempty"` // The name of the package. diff --git a/pkgspec/pkgspec_test.go b/pkgspec/pkgspec_test.go index a9f8ddf..d27dd1b 100644 --- a/pkgspec/pkgspec_test.go +++ b/pkgspec/pkgspec_test.go @@ -159,3 +159,163 @@ description: A test field. t.Errorf("path = %q, want test.yml", field.FilePath()) } } + +func TestUnmarshalManifestGroup(t *testing.T) { + // group is a top-level field on integration, input, and content manifests + // (package-spec 3.6.6). It is lifted into the shared Manifest base type, + // so it must be populated on the promoted embedded struct. + tests := []struct { + name string + yamlData string + manifest func() (*Manifest, any) + }{ + { + name: "integration", + yamlData: `name: nginx +title: Nginx +version: 1.0.0 +description: Nginx integration. +format_version: 3.6.6 +type: integration +group: nginx +owner: + github: elastic/integrations + type: elastic +`, + manifest: func() (*Manifest, any) { + var m IntegrationManifest + return &m.Manifest, &m + }, + }, + { + name: "input", + yamlData: `name: udp +title: Custom UDP Logs +version: 1.0.0 +description: Custom UDP input. +format_version: 3.6.6 +type: input +group: redis +owner: + github: elastic/integrations + type: elastic +`, + manifest: func() (*Manifest, any) { + var m InputManifest + return &m.Manifest, &m + }, + }, + { + name: "content", + yamlData: `name: security_rules +title: Security Rules +version: 1.0.0 +description: Content package. +format_version: 3.6.6 +type: content +group: security_rules +owner: + github: elastic/security + type: elastic +`, + manifest: func() (*Manifest, any) { + var m ContentManifest + return &m.Manifest, &m + }, + }, + } + + want := map[string]string{ + "integration": "nginx", + "input": "redis", + "content": "security_rules", + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + base, target := tt.manifest() + if err := yaml.Unmarshal([]byte(tt.yamlData), target); err != nil { + t.Fatalf("unmarshal: %v", err) + } + if base.Group != want[tt.name] { + t.Errorf("group = %q, want %q", base.Group, want[tt.name]) + } + }) + } +} + +func TestUnmarshalManifestGroupAbsent(t *testing.T) { + yamlData := `name: nginx +title: Nginx +version: 1.0.0 +description: Nginx integration. +format_version: 3.6.6 +type: integration +owner: + github: elastic/integrations + type: elastic +` + var m IntegrationManifest + if err := yaml.Unmarshal([]byte(yamlData), &m); err != nil { + t.Fatalf("unmarshal: %v", err) + } + if m.Group != "" { + t.Errorf("group = %q, want empty", m.Group) + } +} + +func TestUnmarshalTestConfigPolicyIgnoreFields(t *testing.T) { + // Policy tests use a dedicated config schema that adds ignore_fields + // (package-spec 3.6.6). Other test categories do not have the field. + yamlData := `system: + parallel: false +policy: + parallel: true + ignore_fields: + - state.user_agent + - state.cursor.last_timestamp + skip: + reason: flaky + link: https://github.com/elastic/integrations/issues/1 +` + var cfg TestConfig + if err := yaml.Unmarshal([]byte(yamlData), &cfg); err != nil { + t.Fatalf("unmarshal: %v", err) + } + + wantIgnore := []string{"state.user_agent", "state.cursor.last_timestamp"} + if len(cfg.Policy.IgnoreFields) != len(wantIgnore) { + t.Fatalf("ignore_fields = %v, want %v", cfg.Policy.IgnoreFields, wantIgnore) + } + for i, want := range wantIgnore { + if cfg.Policy.IgnoreFields[i] != want { + t.Errorf("ignore_fields[%d] = %q, want %q", i, cfg.Policy.IgnoreFields[i], want) + } + } + + if cfg.Policy.Parallel == nil || !*cfg.Policy.Parallel { + t.Errorf("policy.parallel = %v, want true", cfg.Policy.Parallel) + } + if cfg.Policy.Skip.Reason != "flaky" { + t.Errorf("policy.skip.reason = %q, want flaky", cfg.Policy.Skip.Reason) + } + if cfg.System.Parallel == nil || *cfg.System.Parallel { + t.Errorf("system.parallel = %v, want false", cfg.System.Parallel) + } +} + +func TestUnmarshalInputTestConfigPolicyIgnoreFields(t *testing.T) { + // Input packages share the policy test config schema (package-spec 3.6.6). + yamlData := `policy: + ignore_fields: + - state.user_agent +` + var cfg InputTestConfig + if err := yaml.Unmarshal([]byte(yamlData), &cfg); err != nil { + t.Fatalf("unmarshal: %v", err) + } + + if len(cfg.Policy.IgnoreFields) != 1 || cfg.Policy.IgnoreFields[0] != "state.user_agent" { + t.Errorf("ignore_fields = %v, want [state.user_agent]", cfg.Policy.IgnoreFields) + } +} diff --git a/pkgspec/test.go b/pkgspec/test.go index 83341cf..369c494 100644 --- a/pkgspec/test.go +++ b/pkgspec/test.go @@ -39,7 +39,7 @@ type AgentProvisioningScript struct { type InputTestConfig struct { FileMetadata `json:"-" yaml:"-"` // Configuration for policy tests - Policy TestCategoryConfig `json:"policy,omitempty" yaml:"policy,omitempty"` + Policy PolicyConfigTests `json:"policy,omitempty" yaml:"policy,omitempty"` // Configuration for system tests System TestCategoryConfig `json:"system,omitempty" yaml:"system,omitempty"` } @@ -550,7 +550,7 @@ type TestConfig struct { // Configuration for pipeline tests Pipeline TestCategoryConfig `json:"pipeline,omitempty" yaml:"pipeline,omitempty"` // Configuration for policy tests - Policy TestCategoryConfig `json:"policy,omitempty" yaml:"policy,omitempty"` + Policy PolicyConfigTests `json:"policy,omitempty" yaml:"policy,omitempty"` // Configuration for static tests Static TestCategoryConfig `json:"static,omitempty" yaml:"static,omitempty"` // Configuration for system tests diff --git a/pkgspec/types.go b/pkgspec/types.go index 075102d..a8cd067 100644 --- a/pkgspec/types.go +++ b/pkgspec/types.go @@ -52,6 +52,19 @@ const ( PolicyAPIFormatSimplified PolicyAPIFormat = "simplified" ) +// PolicyConfigTests configuration for policy tests +type PolicyConfigTests struct { + // Stream-level field paths to strip before comparing the actual policy against the expected file. + // Paths use dot notation scoped to the stream level (e.g. "state.user_agent" strips + // inputs[].streams[].state.user_agent). + IgnoreFields []string `json:"ignore_fields,omitempty" yaml:"ignore_fields,omitempty"` + // Tests defined can be run in parallel (default true). + Parallel *bool `json:"parallel,omitempty" yaml:"parallel,omitempty"` + // Package dependencies required for these tests with exact versions. + Requires []map[string]any `json:"requires,omitempty" yaml:"requires,omitempty"` + Skip TestSkip `json:"skip,omitempty" yaml:"skip,omitempty"` +} + type ProviderPermission struct { // Human-readable description of why these permissions are needed. Description string `json:"description,omitempty" yaml:"description,omitempty"` diff --git a/pkgspec/version.go b/pkgspec/version.go index a5ff780..f3b1f5a 100644 --- a/pkgspec/version.go +++ b/pkgspec/version.go @@ -3,4 +3,4 @@ package pkgspec // SpecVersion is the package-spec schema version used to generate this package. -const SpecVersion = "3.6.5" +const SpecVersion = "3.6.6" diff --git a/pkgsql/api_test.go b/pkgsql/api_test.go index f011b81..ca675f8 100644 --- a/pkgsql/api_test.go +++ b/pkgsql/api_test.go @@ -122,6 +122,7 @@ version: 1.0.0 description: A test package. format_version: 3.5.7 type: integration +group: test_group owner: github: elastic/integrations type: elastic @@ -283,6 +284,17 @@ samples: t.Errorf("got name=%s version=%s type=%s", name, version, pkgType) } + // Verify marketplace group. The column name is a SQLite keyword so it + // must be quoted. + var group sql.NullString + err = db.QueryRowContext(ctx, `SELECT "group" FROM packages WHERE name = 'test-package'`).Scan(&group) + if err != nil { + t.Fatalf("querying group: %v", err) + } + if !group.Valid || group.String != "test_group" { + t.Errorf("expected group=test_group, got %v", group) + } + // Verify conditions. var condKibana, condElastic sql.NullString err = db.QueryRowContext(ctx, "SELECT conditions_kibana_version, conditions_elastic_subscription FROM packages WHERE name = 'test-package'"). @@ -595,6 +607,16 @@ policy_templates: t.Fatalf("writing packages: %v", err) } + // The manifest omits group, so the column must be NULL rather than "". + var group sql.NullString + err = db.QueryRowContext(ctx, `SELECT "group" FROM packages`).Scan(&group) + if err != nil { + t.Fatalf("querying group: %v", err) + } + if group.Valid { + t.Errorf("expected group=NULL, got %v", group) + } + // Verify images were inserted. var imgCount int err = db.QueryRowContext(ctx, "SELECT count(*) FROM images").Scan(&imgCount) @@ -660,6 +682,7 @@ version: 1.0.0 description: A test input package. format_version: 3.5.7 type: input +group: input_group categories: - custom conditions: @@ -724,6 +747,16 @@ owner: t.Errorf("expected type=input, got %s", pkgType) } + // Verify marketplace group. + var group sql.NullString + err = db.QueryRowContext(ctx, `SELECT "group" FROM packages WHERE name = 'test-input'`).Scan(&group) + if err != nil { + t.Fatalf("querying group: %v", err) + } + if !group.Valid || group.String != "input_group" { + t.Errorf("expected group=input_group, got %v", group) + } + // Verify policy template was inserted. var ptCount int err = db.QueryRowContext(ctx, "SELECT count(*) FROM policy_templates").Scan(&ptCount) @@ -824,6 +857,7 @@ version: 1.0.0 description: A test content package. format_version: 3.5.7 type: content +group: content_group owner: github: elastic/security type: elastic @@ -844,6 +878,15 @@ discovery: type: enhancement link: https://github.com/test/1 `)}, + "kibana/search/test-content-alerts.json": {Data: []byte(`{ + "id": "test-content-alerts", + "type": "search", + "attributes": { + "title": "Alert Events", + "description": "Saved search over alert events." + }, + "references": [] +}`)}, } pkg, err := pkgreader.Read(".", pkgreader.WithFS(fsys)) @@ -869,6 +912,32 @@ discovery: t.Errorf("expected type=content, got %s", pkgType) } + // Verify marketplace group. + var group sql.NullString + err = db.QueryRowContext(ctx, `SELECT "group" FROM packages WHERE name = 'test-content'`).Scan(&group) + if err != nil { + t.Fatalf("querying group: %v", err) + } + if !group.Valid || group.String != "content_group" { + t.Errorf("expected group=content_group, got %v", group) + } + + // Verify saved search assets, which content packages may ship as of + // package-spec 3.6.6. + var searchAssetType, searchObjectID, searchTitle string + err = db.QueryRowContext(ctx, + "SELECT asset_type, object_id, title FROM kibana_saved_objects WHERE asset_type = 'search'"). + Scan(&searchAssetType, &searchObjectID, &searchTitle) + if err != nil { + t.Fatalf("querying saved search: %v", err) + } + if searchObjectID != "test-content-alerts" { + t.Errorf("expected object_id=test-content-alerts, got %s", searchObjectID) + } + if searchTitle != "Alert Events" { + t.Errorf("expected title=Alert Events, got %s", searchTitle) + } + // Verify conditions. var condKibana, condElastic sql.NullString err = db.QueryRowContext(ctx, "SELECT conditions_kibana_version, conditions_elastic_subscription FROM packages WHERE name = 'test-content'"). diff --git a/pkgsql/insert.go b/pkgsql/insert.go index ec9aa1e..5eb5527 100644 --- a/pkgsql/insert.go +++ b/pkgsql/insert.go @@ -160,6 +160,7 @@ func mapPackagesParams(v *pkgspec.Manifest, agentPrivilegesRoot sql.NullBool, co FileLine: toNullInt64(v.Line()), FilePath: toNullString(v.FilePath()), FormatVersion: v.FormatVersion, + Group: toNullString(v.Group), Name: v.Name, OwnerGithub: v.Owner.Github, OwnerType: string(v.Owner.Type), diff --git a/pkgsql/internal/db/models.go b/pkgsql/internal/db/models.go index df45b43..acbe4c5 100644 --- a/pkgsql/internal/db/models.go +++ b/pkgsql/internal/db/models.go @@ -222,6 +222,7 @@ type Package struct { FileColumn sql.NullInt64 Description string FormatVersion string + Group sql.NullString Name string OwnerGithub string OwnerType string diff --git a/pkgsql/internal/db/query.sql b/pkgsql/internal/db/query.sql index ee78038..c1401c7 100644 --- a/pkgsql/internal/db/query.sql +++ b/pkgsql/internal/db/query.sql @@ -100,6 +100,7 @@ INSERT INTO packages ( file_column, description, format_version, + "group", name, owner_github, owner_type, @@ -127,6 +128,7 @@ INSERT INTO packages ( ?, ?, ?, + ?, ? ) RETURNING id; diff --git a/pkgsql/internal/db/query.sql.go b/pkgsql/internal/db/query.sql.go index 4e88b4c..7810c67 100644 --- a/pkgsql/internal/db/query.sql.go +++ b/pkgsql/internal/db/query.sql.go @@ -974,6 +974,7 @@ INSERT INTO packages ( file_column, description, format_version, + "group", name, owner_github, owner_type, @@ -1001,6 +1002,7 @@ INSERT INTO packages ( ?, ?, ?, + ?, ? ) RETURNING id ` @@ -1019,6 +1021,7 @@ type InsertPackagesParams struct { FileColumn sql.NullInt64 Description string FormatVersion string + Group sql.NullString Name string OwnerGithub string OwnerType string @@ -1043,6 +1046,7 @@ func (q *Queries) InsertPackages(ctx context.Context, arg InsertPackagesParams) arg.FileColumn, arg.Description, arg.FormatVersion, + arg.Group, arg.Name, arg.OwnerGithub, arg.OwnerType, diff --git a/pkgsql/internal/db/schema.sql b/pkgsql/internal/db/schema.sql index d1ccc44..9f0a0c2 100644 --- a/pkgsql/internal/db/schema.sql +++ b/pkgsql/internal/db/schema.sql @@ -60,6 +60,7 @@ CREATE TABLE IF NOT EXISTS packages ( file_column INTEGER, -- source file column number description TEXT NOT NULL, -- A longer description of the package. It should describe, at least all the kinds of data that is collected and with what collectors, following the structure "Collect X from Y with X". format_version TEXT NOT NULL, -- The version of the package specification format used by this package. + "group" TEXT, -- Identifier of a marketplace group. Packages that share the same value belong to the same group. Kibana owns the group's title, icon, and description; this field only declares membership. Values are... name TEXT NOT NULL, -- The name of the package. owner_github TEXT NOT NULL, -- Github team name of the package maintainer. owner_type TEXT NOT NULL, -- Describes who owns the package and the level of support that is provided. The 'elastic' value indicates that the package is built and maintained by Elastic. The 'partner' value indicates that the p... diff --git a/pkgsql/tables.go b/pkgsql/tables.go index 4f53dc8..6928615 100644 --- a/pkgsql/tables.go +++ b/pkgsql/tables.go @@ -5,7 +5,7 @@ package pkgsql // CREATE TABLE statements for each table. const ( fields = "CREATE TABLE IF NOT EXISTS fields (\n -- Elasticsearch field definitions, flattened from nested YAML into dotted-path names.\n id INTEGER PRIMARY KEY AUTOINCREMENT, -- unique identifier\n file_path TEXT, -- source file path\n file_line INTEGER, -- source file line number\n file_column INTEGER, -- source file column number\n analyzer TEXT, -- Name of the analyzer to use for indexing. Unless search_analyzer is specified this analyzer is used for both indexing and searching. Only valid for 'type: text'.\n copy_to TEXT, -- The copy_to parameter allows you to copy the values of multiple fields into a group field, which can then be queried as a single field.\n date_format TEXT, -- The date format(s) that can be parsed. Type date format default to `strict_date_optional_time||epoch_millis`, see the [doc]. In JSON documents, dates are represented as strings. Elasticsearch uses ...\n default_metric JSON, -- JSON-encoded DefaultMetric\n description TEXT, -- Short description of field\n dimension BOOLEAN, -- Declare a field as dimension of time series. This is attached to the field as a `time_series_dimension` mapping parameter.\n doc_values BOOLEAN, -- Controls whether doc values are enabled for a field. All fields which support doc values have them enabled by default. If you are sure that you don’t need to sort or aggregate on a field, or acce...\n dynamic JSON, -- Dynamic controls whether new fields are added dynamically. Accepts true, false, \"strict\", or \"runtime\".\n enabled BOOLEAN, -- The enabled setting, which can be applied only to the top-level mapping definition and to object fields, causes Elasticsearch to skip parsing of the contents of the field entirely. The JSON can sti...\n example JSON, -- Example values for this field.\n expected_values JSON, -- An array of expected values for the field. When defined, these are the only expected values.\n external TEXT, -- External source reference\n ignore_above INTEGER, -- Strings longer than the ignore_above setting will not be indexed or stored. For arrays of strings, ignore_above will be applied for each array element separately and string elements longer than ign...\n ignore_malformed BOOLEAN, -- Trying to index the wrong data type into a field throws an exception by default, and rejects the whole document. The ignore_malformed parameter, if set to true, allows the exception to be ignored. ...\n include_in_parent BOOLEAN, -- For nested field types, this specifies if all fields in the nested object are also added to the parent document as standard (flat) fields.\n include_in_root BOOLEAN, -- For nested field types, this specifies if all fields in the nested object are also added to the root document as standard (flat) fields.\n \"index\" BOOLEAN, -- The index option controls whether field values are indexed. Fields that are not indexed are typically not queryable.\n inference_id TEXT, -- For semantic_text fields, this specifies the id of the inference endpoint associated with the field\n metric_type TEXT, -- The metric type of a numeric field. This is attached to the field as a `time_series_metric` mapping parameter. A gauge is a single-value measurement that can go up or down over time, such as a temp...\n metrics JSON, -- JSON-encoded Metrics\n multi_fields JSON, -- It is often useful to index the same field in different ways for different purposes. This is the purpose of multi-fields. For instance, a string field could be mapped as a text field for full-text ...\n name TEXT NOT NULL, -- Name of field. Names containing dots are automatically split into sub-fields. Names with wildcards generate dynamic mappings.\n normalize JSON, -- Specifies the expected normalizations for a field. `array` normalization implies that the values in the field should always be an array, even if they are single values.\n normalizer TEXT, -- Specifies the name of a normalizer to apply to keyword fields. A simple normalizer called lowercase ships with elasticsearch and can be used. Custom normalizers can be defined as part of analysis i...\n null_value JSON, -- The null_value parameter allows you to replace explicit null values with the specified value so that it can be indexed and searched. A null value cannot be indexed or searched. When a field is set ...\n object_type TEXT, -- Type of the members of the object when `type: object` is used. In these cases a dynamic template is created so direct subobjects of this field have the type indicated. When `object_type_mapping_typ...\n object_type_mapping_type TEXT, -- Type that members of a field of with `type: object` must have in the source document. This type corresponds to the data type detected by the JSON parser, and is translated to the `match_mapping_typ...\n path TEXT, -- For alias type fields this is the path to the target field. Note that this must be the full path, including any parent objects (e.g. object1.object2.field).\n pattern TEXT, -- Regular expression pattern matching the allowed values for the field. This is used for development-time data validation.\n runtime JSON, -- Runtime specifies if this field is evaluated at query time. Can be a boolean or a script string.\n scaling_factor INTEGER, -- The scaling factor to use when encoding values. Values will be multiplied by this factor at index time and rounded to the closest long value. For instance, a scaled_float with a scaling_factor of 1...\n search_analyzer TEXT, -- Name of the analyzer to use for searching. Only valid for 'type: text'.\n store BOOLEAN, -- By default, field values are indexed, but not stored. This means that the field can be queried, but the original field cannot be retrieved. Setting this value to true ensures that the field is also...\n subobjects BOOLEAN, -- Specifies if field names containing dots should be expanded into subobjects. For example, if this is set to `true`, a field named `foo.bar` will be expanded into an object with a field named `bar` ...\n type TEXT, -- Datatype of field. If the type is set to object, a dynamic mapping is created. In this case, if the name doesn't contain any wildcard, the wildcard is added as the last segment of the path.\n unit TEXT, -- Unit type to associate with a numeric field. This is attached to the field as metadata (via `meta`). By default, a field does not have a unit. The convention for percents is to use value 1 to mean ...\n value TEXT, -- The value to associate with a constant_keyword field.\n json_pointer TEXT -- JsonPointer is the RFC 6901 JSON Pointer to this field's location in the original fields file (e.g. /0/fields/1). Set by pkgreader after parsing.\n);\n" - packages = "CREATE TABLE IF NOT EXISTS packages (\n -- Fleet packages (integration, input, or content). Each row is one package version.\n id INTEGER PRIMARY KEY AUTOINCREMENT, -- unique identifier\n agent_privileges_root BOOLEAN, -- whether collection requires root privileges in the agent\n commit_id TEXT, -- git HEAD commit ID (populated when WithGitMetadata is used)\n conditions_agent_version TEXT, -- required Elastic Agent version range\n conditions_elastic_subscription TEXT, -- required Elastic subscription level\n conditions_kibana_version TEXT, -- required Kibana version range\n dir_name TEXT NOT NULL UNIQUE, -- directory name of the package\n elasticsearch_privileges_cluster JSON, -- Elasticsearch cluster privilege requirements (JSON array)\n policy_templates_behavior TEXT, -- behavior when multiple policy templates are defined (all, combined_policy, individual_policies)\n file_path TEXT, -- source file path\n file_line INTEGER, -- source file line number\n file_column INTEGER, -- source file column number\n description TEXT NOT NULL, -- A longer description of the package. It should describe, at least all the kinds of data that is collected and with what collectors, following the structure \"Collect X from Y with X\".\n format_version TEXT NOT NULL, -- The version of the package specification format used by this package.\n name TEXT NOT NULL, -- The name of the package.\n owner_github TEXT NOT NULL, -- Github team name of the package maintainer.\n owner_type TEXT NOT NULL, -- Describes who owns the package and the level of support that is provided. The 'elastic' value indicates that the package is built and maintained by Elastic. The 'partner' value indicates that the p...\n source_license TEXT, -- Identifier of the license of the package, as specified in https://spdx.org/licenses/.\n title TEXT NOT NULL, -- Title of the package. It should be the usual title given to the product, service or kind of source being managed by this package.\n type TEXT NOT NULL, -- The type of package.\n version TEXT NOT NULL -- The version of the package.\n);\n" + packages = "CREATE TABLE IF NOT EXISTS packages (\n -- Fleet packages (integration, input, or content). Each row is one package version.\n id INTEGER PRIMARY KEY AUTOINCREMENT, -- unique identifier\n agent_privileges_root BOOLEAN, -- whether collection requires root privileges in the agent\n commit_id TEXT, -- git HEAD commit ID (populated when WithGitMetadata is used)\n conditions_agent_version TEXT, -- required Elastic Agent version range\n conditions_elastic_subscription TEXT, -- required Elastic subscription level\n conditions_kibana_version TEXT, -- required Kibana version range\n dir_name TEXT NOT NULL UNIQUE, -- directory name of the package\n elasticsearch_privileges_cluster JSON, -- Elasticsearch cluster privilege requirements (JSON array)\n policy_templates_behavior TEXT, -- behavior when multiple policy templates are defined (all, combined_policy, individual_policies)\n file_path TEXT, -- source file path\n file_line INTEGER, -- source file line number\n file_column INTEGER, -- source file column number\n description TEXT NOT NULL, -- A longer description of the package. It should describe, at least all the kinds of data that is collected and with what collectors, following the structure \"Collect X from Y with X\".\n format_version TEXT NOT NULL, -- The version of the package specification format used by this package.\n \"group\" TEXT, -- Identifier of a marketplace group. Packages that share the same value belong to the same group. Kibana owns the group's title, icon, and description; this field only declares membership. Values are...\n name TEXT NOT NULL, -- The name of the package.\n owner_github TEXT NOT NULL, -- Github team name of the package maintainer.\n owner_type TEXT NOT NULL, -- Describes who owns the package and the level of support that is provided. The 'elastic' value indicates that the package is built and maintained by Elastic. The 'partner' value indicates that the p...\n source_license TEXT, -- Identifier of the license of the package, as specified in https://spdx.org/licenses/.\n title TEXT NOT NULL, -- Title of the package. It should be the usual title given to the product, service or kind of source being managed by this package.\n type TEXT NOT NULL, -- The type of package.\n version TEXT NOT NULL -- The version of the package.\n);\n" buildManifests = "CREATE TABLE IF NOT EXISTS build_manifests (\n -- Build configuration for integration packages (_dev/build/build.yml).\n id INTEGER PRIMARY KEY AUTOINCREMENT, -- unique identifier\n packages_id INTEGER NOT NULL REFERENCES packages(id), -- foreign key to packages\n file_path TEXT, -- source file path\n file_line INTEGER, -- source file line number\n file_column INTEGER, -- source file column number\n dependencies_ecs_import_mappings BOOLEAN, -- Whether or not import common used dynamic templates and properties into the package\n dependencies_ecs_reference TEXT NOT NULL -- Reference is the ECS version source reference. Values begin with \"git@\" (e.g. \"git@v8.11.0\").\n);\n" changelogs = "CREATE TABLE IF NOT EXISTS changelogs (\n -- Changelog versions for a package. Each row is one version entry with its release date.\n id INTEGER PRIMARY KEY AUTOINCREMENT, -- unique identifier\n packages_id INTEGER NOT NULL REFERENCES packages(id), -- foreign key to packages\n file_path TEXT, -- source file path\n file_line INTEGER, -- source file line number\n file_column INTEGER, -- source file column number\n version TEXT NOT NULL, -- Package version.\n date TEXT -- Date is the approximate release date, populated via git blame when WithGitMetadata is used.\n);\n" changelogEntries = "CREATE TABLE IF NOT EXISTS changelog_entries (\n -- Individual changelog entries within a changelog version.\n id INTEGER PRIMARY KEY AUTOINCREMENT, -- unique identifier\n changelogs_id INTEGER NOT NULL REFERENCES changelogs(id), -- foreign key to changelogs\n file_path TEXT, -- source file path\n file_line INTEGER, -- source file line number\n file_column INTEGER, -- source file column number\n description TEXT NOT NULL, -- Description of change.\n link TEXT NOT NULL, -- Link to issue or PR describing change in detail.\n type TEXT NOT NULL -- Type of change.\n);\n"