Skip to content

Add migrate from variable support#1162

Open
Supplementing wants to merge 5 commits into
elastic:mainfrom
Supplementing:add-migrate-from-variable-support
Open

Add migrate from variable support#1162
Supplementing wants to merge 5 commits into
elastic:mainfrom
Supplementing:add-migrate-from-variable-support

Conversation

@Supplementing
Copy link
Copy Markdown
Contributor

@Supplementing Supplementing commented May 6, 2026

What does this PR do?

Closes elastic/kibana#266829

This PR adds support for migrate_from at the variable level allowing package maintainers to move variables between scopes (for instance input <-> stream).

As we already has an existing field named migrate_from, it was rolled into an object form. You now must use anyOf name or scope in both places you use migrate_from.

Why is it important?

We needed this in order to stop vars from being dropped when maintainers moved them in scope. See elastic/kibana#264085

Checklist

Related issues

Summary by CodeRabbit

  • New Features

    • Added support for a migrate_from object on variables to indicate migrating values between scopes (input ↔ stream).
  • Tests

    • Expanded validation tests and fixtures to cover valid and invalid migrate_from scenarios (including scope validation).
  • Documentation

    • Added documentation and sample package scaffolds illustrating migrate_from usage and examples.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 6, 2026

📝 Walkthrough

Walkthrough

This PR adds a per-variable migrate_from object to the data stream manifest schema, updates version patches, extends validator tests with good and bad fixtures, and adds three test packages showing valid and invalid migrate_from usages.

Changes

Variable-Level migrate_from Feature

Layer / File(s) Summary
Schema Definition
spec/integration/data_stream/manifest.spec.yml
Added migrate_from object schema to variable definitions with name, scope, and optional stream; validation requires at least one of name or scope. Added a versions patch to exclude this field for packages before 3.7.0.
Validator Tests & Changelog
code/go/pkg/validator/validator_test.go, spec/changelog.yml
Extended TestValidateFile with good_var_migrate_from, bad_var_migrate_from, and bad_var_migrate_from_scope cases. Added 3.7.0-next changelog enhancement entry documenting variable-level migrate_from.
Valid Test Fixture: package & data_stream
test/packages/good_var_migrate_from/manifest.yml, test/packages/good_var_migrate_from/data_stream/logs/manifest.yml
Valid package demonstrating variable-level migrate_from usage (e.g., api_url/shared_credential migration metadata).
Valid Fixture Supporting Files
test/packages/good_var_migrate_from/...
Supporting files for the valid package: license, documentation templates, changelog, field definitions, ingest pipeline, templates, and sample event.
Invalid Fixture: Unsupported format_version
test/packages/bad_var_migrate_from/...
Test package using migrate_from with an unsupported format_version to assert validator rejection, plus supporting assets.
Invalid Fixture: Invalid scope value
test/packages/bad_var_migrate_from_scope/...
Test package where migrate_from.scope: global (invalid enum value) to assert schema validation failure, plus supporting assets.
Misc README & templates
test/packages/*/_dev/build/docs/README.md, test/packages/*/data_stream/*/agent/stream/filestream.yml.hbs
README scaffolds and small Handlebars templates used across test packages to validate packaging structure.

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • jsoriano
  • teresaromero

Poem

🐇 A little var hopped from input to stream,
migrate_from marks its old daydream,
Tests and schema now sing the song,
The rabbit hums — the spec moves along!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add migrate from variable support' accurately and concisely summarizes the main change: adding migrate_from support at the variable level.
Linked Issues check ✅ Passed The PR implements all acceptance criteria from #266829: schema accepts migrate_from object, variable definitions with migrate_from pass validation, unrecognized values fail with clear error, and both level migrate_from work together.
Out of Scope Changes check ✅ Passed All changes are in scope: schema extension, test packages for validation, changelog entry, and test cases covering good/bad migrate_from scenarios at variable level.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

…Subject to change during fleet implementation
@Supplementing Supplementing force-pushed the add-migrate-from-variable-support branch from 60eb085 to 352ee88 Compare May 6, 2026 15:42
@Supplementing Supplementing marked this pull request as ready for review May 11, 2026 22:48
@Supplementing Supplementing requested a review from a team as a code owner May 11, 2026 22:48
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
test/packages/bad_var_migrate_from/_dev/build/docs/README.md (1)

1-102: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Template syntax is likely breaking check-static (merge blocker).

This file introduces many markdownlint violations (MD041/MD022/MD037), which aligns with the recurring Run check-static CI failure. Please make this fixture README lint-safe (either suppress lint for this template file or adjust template comments/heading spacing).

Suggested minimal suppression (if this fixture is intentionally template-heavy)
+<!-- markdownlint-disable MD041 MD022 MD037 -->
 {{- generatedHeader }}
 {{/*
 This template can be used as a starting point for writing documentation for your new integration. For each section, fill in the details
 described in the comments.
@@
 These APIs are used with this integration:
 * ...
+<!-- markdownlint-enable MD041 MD022 MD037 -->
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/packages/bad_var_migrate_from/_dev/build/docs/README.md` around lines 1
- 102, The README.md template is triggering markdownlint errors
(MD041/MD022/MD037) and failing check-static; fix it by adding a lint
suppression at the top of the template (e.g., a markdownlint disable comment for
MD041, MD022, MD037) or by adjusting the template comment/heading spacing so
headings are separated by a blank line and inline HTML/templating comments (like
"{{- generatedHeader }}" and the large "{{/* ... */}}" comment blocks) do not
break markdown rules; locate the template start around "{{- generatedHeader }}"
and the trailing "{{ inputDocs }}" section and either insert a markdownlint
disable directive or reflow the comment blocks/headings to comply with
markdownlint.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@spec/changelog.yml`:
- Around line 14-16: Add a per-entry pending marker directly above the changelog
entry whose description is "Add `migrate_from` object to variable definitions to
declare that a variable moved between input-level and stream-level scope within
the same input type." by inserting a comment line "# Pending on <url>"
immediately above that description line (replace <url> with the upstream Kibana
PR/issue URL cited in the review) so the entry itself is clearly marked pending
as required by the changelog policy.

In
`@test/packages/bad_var_migrate_from_scope/data_stream/logs/elasticsearch/ingest_pipeline/default.yml`:
- Around line 6-8: The data_stream.dataset value in the
bad_var_migrate_from_scope ingest pipeline is pointing to the wrong package
fixture; update the value under the field data_stream.dataset (currently
"good_migrate_from.logs") to the correct dataset for this fixture (e.g.,
"bad_var_migrate_from.logs") so the ingest pipeline and the
bad_var_migrate_from_scope fixture reference the matching dataset name.

In
`@test/packages/bad_var_migrate_from/data_stream/logs/elasticsearch/ingest_pipeline/default.yml`:
- Line 7: The dataset identifier value is incorrect: the ingest pipeline sets
value: "good_migrate_from.logs" inside the bad_var_migrate_from fixture; update
the identifier to the correct package fixture name (e.g.,
"bad_var_migrate_from.logs") so the dataset matches the package (modify the
value field in the ingest_pipeline/default.yml to use the bad_var_migrate_from
dataset identifier instead of good_migrate_from.logs).

In `@test/packages/bad_var_migrate_from/docs/README.md`:
- Line 20: The README has three spelling/grammar typos: replace "recieving" with
"receiving" at the high-level overview line, change "will be replace" to "will
be replaced" around line 87, and change "relevent" to "relevant" around line 99;
update those exact words in the README.md so the text reads correctly and
removes static-check noise.

In `@test/packages/good_var_migrate_from/_dev/build/docs/README.md`:
- Line 99: Replace the typo "relevent" with "relevant" in the source README
template that generates the documentation (the string literal "relevent" appears
in the generated README), update the rendered docs/README.md entry to the
corrected spelling, and re-run the docs build to regenerate the artifact so the
change is propagated to the `_dev/build/docs/` output.

In
`@test/packages/good_var_migrate_from/data_stream/logs/elasticsearch/ingest_pipeline/default.yml`:
- Line 7: Update the data_stream.dataset value in the ingest pipeline YAML to
match the package name from manifest.yml: change the current
"good_migrate_from.logs" to "good_var_migrate_from.logs" so it follows the
"<package>.<data_stream>" convention; locate the key named data_stream.dataset
in default.yml and replace the package segment to match the manifest package
name (good_var_migrate_from).

In `@test/packages/good_var_migrate_from/docs/README.md`:
- Line 99: Replace the misspelled word "relevent" with the correct spelling
"relevant" in the README content string "{{/* For integrations that use APIs to
collect data, document all the APIs that are used, and link to relevent
information */}}"; update that comment text so it reads "...link to relevant
information".

---

Outside diff comments:
In `@test/packages/bad_var_migrate_from/_dev/build/docs/README.md`:
- Around line 1-102: The README.md template is triggering markdownlint errors
(MD041/MD022/MD037) and failing check-static; fix it by adding a lint
suppression at the top of the template (e.g., a markdownlint disable comment for
MD041, MD022, MD037) or by adjusting the template comment/heading spacing so
headings are separated by a blank line and inline HTML/templating comments (like
"{{- generatedHeader }}" and the large "{{/* ... */}}" comment blocks) do not
break markdown rules; locate the template start around "{{- generatedHeader }}"
and the trailing "{{ inputDocs }}" section and either insert a markdownlint
disable directive or reflow the comment blocks/headings to comply with
markdownlint.
🪄 Autofix (Beta)

❌ Autofix failed (check again to retry)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 2bafd3e7-40c3-47c6-be9c-3afbfc22dc42

📥 Commits

Reviewing files that changed from the base of the PR and between 6d1dec0 and 46d57d9.

⛔ Files ignored due to path filters (6)
  • test/packages/bad_var_migrate_from/img/sample-logo.svg is excluded by !**/*.svg
  • test/packages/bad_var_migrate_from/img/sample-screenshot.png is excluded by !**/*.png
  • test/packages/bad_var_migrate_from_scope/img/sample-logo.svg is excluded by !**/*.svg
  • test/packages/bad_var_migrate_from_scope/img/sample-screenshot.png is excluded by !**/*.png
  • test/packages/good_var_migrate_from/img/sample-logo.svg is excluded by !**/*.svg
  • test/packages/good_var_migrate_from/img/sample-screenshot.png is excluded by !**/*.png
📒 Files selected for processing (36)
  • code/go/pkg/validator/validator_test.go
  • spec/changelog.yml
  • spec/integration/data_stream/manifest.spec.yml
  • test/packages/bad_var_migrate_from/LICENSE.txt
  • test/packages/bad_var_migrate_from/_dev/build/docs/README.md
  • test/packages/bad_var_migrate_from/changelog.yml
  • test/packages/bad_var_migrate_from/data_stream/logs/agent/stream/filestream.yml.hbs
  • test/packages/bad_var_migrate_from/data_stream/logs/agent/stream/stream.yml.hbs
  • test/packages/bad_var_migrate_from/data_stream/logs/elasticsearch/ingest_pipeline/default.yml
  • test/packages/bad_var_migrate_from/data_stream/logs/fields/base-fields.yml
  • test/packages/bad_var_migrate_from/data_stream/logs/manifest.yml
  • test/packages/bad_var_migrate_from/docs/README.md
  • test/packages/bad_var_migrate_from/manifest.yml
  • test/packages/bad_var_migrate_from/sample_event.json
  • test/packages/bad_var_migrate_from_scope/LICENSE.txt
  • test/packages/bad_var_migrate_from_scope/_dev/build/docs/README.md
  • test/packages/bad_var_migrate_from_scope/changelog.yml
  • test/packages/bad_var_migrate_from_scope/data_stream/logs/agent/stream/filestream.yml.hbs
  • test/packages/bad_var_migrate_from_scope/data_stream/logs/agent/stream/stream.yml.hbs
  • test/packages/bad_var_migrate_from_scope/data_stream/logs/elasticsearch/ingest_pipeline/default.yml
  • test/packages/bad_var_migrate_from_scope/data_stream/logs/fields/base-fields.yml
  • test/packages/bad_var_migrate_from_scope/data_stream/logs/manifest.yml
  • test/packages/bad_var_migrate_from_scope/docs/README.md
  • test/packages/bad_var_migrate_from_scope/manifest.yml
  • test/packages/bad_var_migrate_from_scope/sample_event.json
  • test/packages/good_var_migrate_from/LICENSE.txt
  • test/packages/good_var_migrate_from/_dev/build/docs/README.md
  • test/packages/good_var_migrate_from/changelog.yml
  • test/packages/good_var_migrate_from/data_stream/logs/agent/stream/filestream.yml.hbs
  • test/packages/good_var_migrate_from/data_stream/logs/agent/stream/stream.yml.hbs
  • test/packages/good_var_migrate_from/data_stream/logs/elasticsearch/ingest_pipeline/default.yml
  • test/packages/good_var_migrate_from/data_stream/logs/fields/base-fields.yml
  • test/packages/good_var_migrate_from/data_stream/logs/manifest.yml
  • test/packages/good_var_migrate_from/docs/README.md
  • test/packages/good_var_migrate_from/manifest.yml
  • test/packages/good_var_migrate_from/sample_event.json

Comment thread spec/changelog.yml
Comment thread test/packages/bad_var_migrate_from/docs/README.md Outdated
Comment thread test/packages/good_var_migrate_from/_dev/build/docs/README.md Outdated
Comment thread test/packages/good_var_migrate_from/docs/README.md Outdated
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 11, 2026

Note

Autofix is a beta feature. Expect some limitations and changes as we gather feedback and continue to improve it.

An unexpected error occurred while generating fixes: Not Found - https://docs.github.com/rest/git/refs#get-a-reference

Copy link
Copy Markdown
Contributor

@teresaromero teresaromero left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hi! regarding this change i have couple questions:

  • will the variables coming from an input package (composables) be in the scope of this change? when an integration uses packages on its data-streams, the variables from the input package are merged into the integration, and whatever variable is declared at the integration overrides the one coming from the input. With this said, how is this change affecting this cases?

  • i understand fleet reads this field from the manifest an uses the migrated value instead of the "original". How does this affect elastic-package? I believe system tests would need to mirror the behavior if, for example, some system test has variables declared?

@Supplementing
Copy link
Copy Markdown
Contributor Author

  • will the variables coming from an input package (composables) be in the scope of this change? when an integration uses packages on its data-streams, the variables from the input package are merged into the integration, and whatever variable is declared at the integration overrides the one coming from the input. With this said, how is this change affecting this cases?

This wasnt in scope for this change. Input packages themselves cant declare migrate_from on their vars, and the existing merge happens upstream of this change. If this is something we want to support, then we should to discuss if its something that is a blocker for this particular change or should be a follow up.

  • i understand fleet reads this field from the manifest an uses the migrated value instead of the "original". How does this affect elastic-package? I believe system tests would need to mirror the behavior if, for example, some system test has variables declared?

By my understanding, no elastic-package changes are needed here, the package-spec release step will resolve any issues. Once the spec release happens, elastic-package will pick up the changes required. System tests install one version, the migration is upgrade-only.

@elasticmachine
Copy link
Copy Markdown

💚 Build Succeeded

History

Comment thread spec/changelog.yml
- description: Add optional `release` field to agentless deployment mode to explicitly declare its release stage.
type: enhancement
link: https://github.com/elastic/package-spec/pull/1130
- description: Add `migrate_from` object to variable definitions to declare that a variable moved between input-level and stream-level scope within the same input type.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will this wait until 3.7 or shall it be 3.6.3 ??? cc @jsoriano if we need a quick release of this maybe we can add it to next elastic-package release?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to go in 3.7, as this requires changes in Kibana.

Copy link
Copy Markdown
Contributor

@teresaromero teresaromero left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its fine for 3.7 if packages from this version forward could include this field. I dont see how this is blocking kibana, as the spec will affect packages from version 3.7 kibana should be able to check if the field is there and act on it 🤔 so until there are no packages with version 3.7 kibana wont be seeing this? is this correct?

the spec goes to elastic-package so integration devs can update their packages with the new spec fields, and bump the version. but i spec is not injected at fleet (as far as i know)

@Supplementing
Copy link
Copy Markdown
Contributor Author

Quick update so theres a trail, this is no longer a blocker for the Kibana side! It can (and likely will) merge before this is ready. It has been verified to pass CI and will work without these changes.

Supplementing added a commit to elastic/kibana that referenced this pull request May 18, 2026
…es (#268789)

## Summary

Closes #266831. Builds on the input/stream-level `migrate_from`
mechanism (#242934) and the var-rename feature (#265810) to handle the
remaining gap: same-input-type upgrades where a variable moves between
input and stream scope.

Previously, when a package author moved a var from input-level to
stream-level (or vice versa) inside the same input type, Fleet silently
dropped the user's configured value during updatePackageInputs() because
removeStaleVars() discarded any var no longer at its original scope. The
existing migrate_from mechanisms only fired on cross-input-type
upgrades.

  ## What this adds

- Var-level migrate_from is now an object (`{ name?, scope?, stream? }`)
— folding the existing string-form rename into the same field. scope:
'input' | 'stream' declares the var moved scope; name declares it was
renamed; both can be combined. Backed by
elastic/package-spec#1162
- New applyVarScopeMigration() helper in
package_policy_migration_helpers.ts — seeds old-scope values into the
new scope's slot before deepMergeVars runs, then the existing
removeStaleVars cycle drops the old scope automatically. Mirrors the
merge-then-prune pattern used elsewhere.
- Wired into the existing-input branch of updatePackageInputs() — fires
only on same-input-type upgrades, so cross-input-type migrations
continue to use applyInputLevelMigration / applyStreamLevelMigration
unchanged.
- Multi-stream safety: migrate_from.stream accepts either the
fully-qualified dataset (mypackage.logs) or the bare folder name (logs).
When omitted on multi-stream inputs, Fleet falls back to the package
default rather than silently picking an arbitrary stream.
- Secret-typed vars preserve their secret references across scope
migration — manual end-to-end test confirmed input ↔ stream movement of
a password-typed var carries the existing {id, isSecretRef: true}
reference without re-creating or orphaning the underlying secret.
- Backwards compatible with the original string-form migrate_from
shorthand (#265810): normalized at read time to { name: <string> } so
any package built outside the validator still migrates cleanly.
- UI tooltip: extends the existing MigrationTooltip pattern with a
sibling VarMigrationTooltip that renders an info icon next to migrated
var labels during the upgrade flow, with messaging that adapts to
whether the var was renamed, moved, or both.
  
 Before (v1.0.0)
```
  {
    "items": [
      {
        "id": "b49a95e5-6a57-485c-836c-1605cb027390",
        "version": "WzI0NCwxXQ==",
        "spaceIds": ["default"],
        "name": "good_var_migrate_from-1",
        "namespace": "",
        "description": "",
        "package": {
          "name": "good_var_migrate_from",
          "title": "Good Var Migrate From",
          "version": "1.0.0"
        },
        "enabled": true,
        "policy_id": "a617c87f-5de4-42f5-846f-667412cd0e7f",
        "policy_ids": ["a617c87f-5de4-42f5-846f-667412cd0e7f"],
        "inputs": [
          {
            "type": "filestream",
            "policy_template": "sample",
            "enabled": true,
            "streams": [
              {
                "enabled": true,
                "data_stream": {
                  "type": "logs",
                  "dataset": "good_var_migrate_from.logs"
                },
                "vars": {
                  "paths": {
                    "value": ["/var/log/*.log"],
                    "type": "text"
                  },
                  "shared_credential": { // <- Notice that this is in the streams array before 
                    "type": "password",  
                    "value": {
                      "id": "QAUUGZ4Bllp68onmINYA",
                      "isSecretRef": true
                    }
                  }
                },
                "id": "filestream-good_var_migrate_from.logs-b49a95e5-6a57-485c-836c-1605cb027390",
                "compiled_stream": {
                  "paths": ["/var/log/*.log"]
                }
              }
            ],
            "vars": {
              "api_url": { // <- Notice that this is in the vars 
                "type": "url",
                "value": "https://www.example.com"
              }
            },
            "id": "filestream-sample-b49a95e5-6a57-485c-836c-1605cb027390"
          }
        ],
        "secret_references": [
          { "id": "QAUUGZ4Bllp68onmINYA" }
        ],
        "revision": 1,
        "created_at": "2026-05-11T22:06:49.897Z",
        "created_by": "system",
        "updated_at": "2026-05-11T22:06:49.897Z",
        "updated_by": "system",
        "package_agent_version_condition": "^8.9.0"
      }
    ],
    "total": 1,
    "page": 1,
    "perPage": 20
  }
```
  After (v1.0.1 — same data, scopes swapped)
```
  {
    "items": [
      {
        "id": "b49a95e5-6a57-485c-836c-1605cb027390",
        "version": "WzI2NSwxXQ==",
        "spaceIds": ["default"],
        "name": "good_var_migrate_from-1",
        "namespace": "",
        "description": "",
        "package": {
          "name": "good_var_migrate_from",
          "title": "Good Var Migrate From",
          "version": "1.0.1"
        },
        "enabled": true,
        "policy_id": "a617c87f-5de4-42f5-846f-667412cd0e7f",
        "policy_ids": ["a617c87f-5de4-42f5-846f-667412cd0e7f"],
        "inputs": [
          {
            "type": "filestream",
            "policy_template": "sample",
            "enabled": true,
            "streams": [
              {
                "enabled": true,
                "data_stream": {
                  "type": "logs",
                  "dataset": "good_var_migrate_from.logs"
                },
                "vars": {
                  "paths": {
                    "value": ["/var/log/*.log"],
                    "type": "text"
                  },
                  "api_url": { // <- Notice that this  has now swapped places and is in the streams as expected
                    "type": "url",
                    "value": "https://www.example.com"
                  }
                },
                "id": "filestream-good_var_migrate_from.logs-b49a95e5-6a57-485c-836c-1605cb027390",
                "compiled_stream": {
                  "paths": ["/var/log/*.log"]
                }
              }
            ],
            "vars": {
              "shared_credential": { // <- Notice that this  has now moved to the vars 
                "type": "password",
                "value": {
                  "id": "QAUUGZ4Bllp68onmINYA",
                  "isSecretRef": true
                }
              }
            },
            "id": "filestream-sample-b49a95e5-6a57-485c-836c-1605cb027390"
          }
        ],
        "secret_references": [
          { "id": "QAUUGZ4Bllp68onmINYA" }
        ],
        "revision": 2,
        "created_at": "2026-05-11T22:06:49.897Z",
        "created_by": "system",
        "updated_at": "2026-05-11T22:07:33.934Z",
        "updated_by": "elastic",
        "package_agent_version_condition": "^8.9.0"
      }
    ],
    "total": 1,
    "page": 1,
    "perPage": 20
  }  
```
## Testing Locally

These steps reproduce the same end-to-end test used to verify the
feature. Assumes you have elastic-package, package-registry, and a local
Kibana checkout.

  1. Create two versions of a test package

Pick any small package (or copy test/packages/good_var_migrate_from from
the package-spec branch). You need:

- v1.0.0 (before): a var at one scope (e.g. api_url at input level,
shared_credential at stream level), no migrate_from.
- v1.0.1 (after): the same vars moved to the opposite scope, each
declaring migrate_from:

  ### Input-level (in top-level manifest.yml)
  - name: shared_credential
    type: password
    secret: true
    migrate_from:
      scope: stream

  #### Stream-level (in data_stream/<ds>/manifest.yml)
  - name: api_url
    type: url
    migrate_from:
      scope: input

Same internal name: in both versions, version-bumped in manifest.yml and
changelog.yml. Use version: 1.0.0 and up so the registry doesn't tag
them release: beta.

  2. Build both versions

#### elastic-package needs to know about format_version 3.7.0, which
adds var-level migrate_from.
  #### Rebuild from the package-spec branch that adds it:
```
  cd /path/to/package-spec  # branch: add-migrate-from-variable-support
```
#### Confirm go.mod has `replace github.com/elastic/package-spec/v3 =>
/path/to/package-spec`
```
  cd /path/to/elastic-package
  go mod tidy && go install ./
```
  #### Build each version
```
  cd /path/to/packages/my_test_package_v1
  elastic-package build --skip-validation
  cd /path/to/packages/my_test_package_v2
  elastic-package build --skip-validation
```
#### zips land in /path/to/package-spec/build/packages/ (or your repo
root's build/packages/)

  3. Serve them locally
```
  mkdir -p /tmp/fleet-test-packages
  cp /path/to/build/packages/my_test_package-1.0.0.zip /tmp/fleet-test-packages/
  cp /path/to/build/packages/my_test_package-1.0.1.zip /tmp/fleet-test-packages/

  cat > /tmp/fleet-test-packages/registry-config.yml <<EOF
  package_paths:
    - /tmp/fleet-test-packages
  EOF

  cd /path/to/package-registry
  go build -o package-registry ./
  ./package-registry \
    --address localhost:8080 \
    --feature-storage-indexer=false \
    --disable-package-validation \
    --config /tmp/fleet-test-packages/registry-config.yml
```
  Sanity-check:
```
  curl -s 'http://localhost:8080/search?package=my_test_package&all=true' \
    | python3 -c "import sys,json; [print(p['version']) for p in json.load(sys.stdin)]"
```
  #### expect: 1.0.0 and 1.0.1

  4. Run Kibana pointed at the local registry
```
  yarn es snapshot                                                     # terminal A
  yarn start --no-base-path --xpack.fleet.registryUrl=http://localhost:8080   # terminal B
```
  5. Exercise the upgrade

1. Integrations → install v1.0.0, attach to an agent policy, set
non-default values for both vars.
  2. Confirm pre-upgrade scopes with:
  `GET kbn:/api/fleet/package_policies?perPage=20`
  3. Install v1.0.1 and upgrade the policy:
  
`POST kbn:/api/fleet/epm/packages/my_test_package/1.0.1 {}`
Then upgrade with
 `POST kbn:/api/fleet/package_policies/upgrade
  { "packagePolicyIds": ["<policy-id>"] }`
  4. Re-fetch the policy. Expected:
    - Values are present at the new scopes.
    - Both old-scope entries are gone.
- secret_references array still tracks any moved secret-typed vars (no
orphan, no re-create).
- Upgrade form shows the info-icon tooltip next to migrated var labels.

  
### Checklist

Check the PR satisfies following conditions. 

Reviewers should verify this PR satisfies this list as well.

- [ ] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] If a plugin configuration key changed, check if it needs to be
allowlisted in the cloud and added to the [docker
list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)
- [ ] This was checked for breaking HTTP API changes, and any breaking
changes have been approved by the breaking-change committee. The
`release_note:breaking` label should be applied in these situations.
- [ ] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [ ] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
- [ ] Review the [backport
guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing)
and apply applicable `backport:*` labels.

### Identify risks

Does this PR introduce any risks? For example, consider risks like hard
to test bugs, performance regression, potential of data loss.

Describe the risk, its severity, and mitigation for each identified
risk. Invite stakeholders and evaluate how to proceed before merging.

- [ ] [See some risk
examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx)
- [ ] ...

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
jcger pushed a commit to elastic/kibana that referenced this pull request May 26, 2026
…es (#268789)

## Summary

Closes #266831. Builds on the input/stream-level `migrate_from`
mechanism (#242934) and the var-rename feature (#265810) to handle the
remaining gap: same-input-type upgrades where a variable moves between
input and stream scope.

Previously, when a package author moved a var from input-level to
stream-level (or vice versa) inside the same input type, Fleet silently
dropped the user's configured value during updatePackageInputs() because
removeStaleVars() discarded any var no longer at its original scope. The
existing migrate_from mechanisms only fired on cross-input-type
upgrades.

  ## What this adds

- Var-level migrate_from is now an object (`{ name?, scope?, stream? }`)
— folding the existing string-form rename into the same field. scope:
'input' | 'stream' declares the var moved scope; name declares it was
renamed; both can be combined. Backed by
elastic/package-spec#1162
- New applyVarScopeMigration() helper in
package_policy_migration_helpers.ts — seeds old-scope values into the
new scope's slot before deepMergeVars runs, then the existing
removeStaleVars cycle drops the old scope automatically. Mirrors the
merge-then-prune pattern used elsewhere.
- Wired into the existing-input branch of updatePackageInputs() — fires
only on same-input-type upgrades, so cross-input-type migrations
continue to use applyInputLevelMigration / applyStreamLevelMigration
unchanged.
- Multi-stream safety: migrate_from.stream accepts either the
fully-qualified dataset (mypackage.logs) or the bare folder name (logs).
When omitted on multi-stream inputs, Fleet falls back to the package
default rather than silently picking an arbitrary stream.
- Secret-typed vars preserve their secret references across scope
migration — manual end-to-end test confirmed input ↔ stream movement of
a password-typed var carries the existing {id, isSecretRef: true}
reference without re-creating or orphaning the underlying secret.
- Backwards compatible with the original string-form migrate_from
shorthand (#265810): normalized at read time to { name: <string> } so
any package built outside the validator still migrates cleanly.
- UI tooltip: extends the existing MigrationTooltip pattern with a
sibling VarMigrationTooltip that renders an info icon next to migrated
var labels during the upgrade flow, with messaging that adapts to
whether the var was renamed, moved, or both.
  
 Before (v1.0.0)
```
  {
    "items": [
      {
        "id": "b49a95e5-6a57-485c-836c-1605cb027390",
        "version": "WzI0NCwxXQ==",
        "spaceIds": ["default"],
        "name": "good_var_migrate_from-1",
        "namespace": "",
        "description": "",
        "package": {
          "name": "good_var_migrate_from",
          "title": "Good Var Migrate From",
          "version": "1.0.0"
        },
        "enabled": true,
        "policy_id": "a617c87f-5de4-42f5-846f-667412cd0e7f",
        "policy_ids": ["a617c87f-5de4-42f5-846f-667412cd0e7f"],
        "inputs": [
          {
            "type": "filestream",
            "policy_template": "sample",
            "enabled": true,
            "streams": [
              {
                "enabled": true,
                "data_stream": {
                  "type": "logs",
                  "dataset": "good_var_migrate_from.logs"
                },
                "vars": {
                  "paths": {
                    "value": ["/var/log/*.log"],
                    "type": "text"
                  },
                  "shared_credential": { // <- Notice that this is in the streams array before 
                    "type": "password",  
                    "value": {
                      "id": "QAUUGZ4Bllp68onmINYA",
                      "isSecretRef": true
                    }
                  }
                },
                "id": "filestream-good_var_migrate_from.logs-b49a95e5-6a57-485c-836c-1605cb027390",
                "compiled_stream": {
                  "paths": ["/var/log/*.log"]
                }
              }
            ],
            "vars": {
              "api_url": { // <- Notice that this is in the vars 
                "type": "url",
                "value": "https://www.example.com"
              }
            },
            "id": "filestream-sample-b49a95e5-6a57-485c-836c-1605cb027390"
          }
        ],
        "secret_references": [
          { "id": "QAUUGZ4Bllp68onmINYA" }
        ],
        "revision": 1,
        "created_at": "2026-05-11T22:06:49.897Z",
        "created_by": "system",
        "updated_at": "2026-05-11T22:06:49.897Z",
        "updated_by": "system",
        "package_agent_version_condition": "^8.9.0"
      }
    ],
    "total": 1,
    "page": 1,
    "perPage": 20
  }
```
  After (v1.0.1 — same data, scopes swapped)
```
  {
    "items": [
      {
        "id": "b49a95e5-6a57-485c-836c-1605cb027390",
        "version": "WzI2NSwxXQ==",
        "spaceIds": ["default"],
        "name": "good_var_migrate_from-1",
        "namespace": "",
        "description": "",
        "package": {
          "name": "good_var_migrate_from",
          "title": "Good Var Migrate From",
          "version": "1.0.1"
        },
        "enabled": true,
        "policy_id": "a617c87f-5de4-42f5-846f-667412cd0e7f",
        "policy_ids": ["a617c87f-5de4-42f5-846f-667412cd0e7f"],
        "inputs": [
          {
            "type": "filestream",
            "policy_template": "sample",
            "enabled": true,
            "streams": [
              {
                "enabled": true,
                "data_stream": {
                  "type": "logs",
                  "dataset": "good_var_migrate_from.logs"
                },
                "vars": {
                  "paths": {
                    "value": ["/var/log/*.log"],
                    "type": "text"
                  },
                  "api_url": { // <- Notice that this  has now swapped places and is in the streams as expected
                    "type": "url",
                    "value": "https://www.example.com"
                  }
                },
                "id": "filestream-good_var_migrate_from.logs-b49a95e5-6a57-485c-836c-1605cb027390",
                "compiled_stream": {
                  "paths": ["/var/log/*.log"]
                }
              }
            ],
            "vars": {
              "shared_credential": { // <- Notice that this  has now moved to the vars 
                "type": "password",
                "value": {
                  "id": "QAUUGZ4Bllp68onmINYA",
                  "isSecretRef": true
                }
              }
            },
            "id": "filestream-sample-b49a95e5-6a57-485c-836c-1605cb027390"
          }
        ],
        "secret_references": [
          { "id": "QAUUGZ4Bllp68onmINYA" }
        ],
        "revision": 2,
        "created_at": "2026-05-11T22:06:49.897Z",
        "created_by": "system",
        "updated_at": "2026-05-11T22:07:33.934Z",
        "updated_by": "elastic",
        "package_agent_version_condition": "^8.9.0"
      }
    ],
    "total": 1,
    "page": 1,
    "perPage": 20
  }  
```
## Testing Locally

These steps reproduce the same end-to-end test used to verify the
feature. Assumes you have elastic-package, package-registry, and a local
Kibana checkout.

  1. Create two versions of a test package

Pick any small package (or copy test/packages/good_var_migrate_from from
the package-spec branch). You need:

- v1.0.0 (before): a var at one scope (e.g. api_url at input level,
shared_credential at stream level), no migrate_from.
- v1.0.1 (after): the same vars moved to the opposite scope, each
declaring migrate_from:

  ### Input-level (in top-level manifest.yml)
  - name: shared_credential
    type: password
    secret: true
    migrate_from:
      scope: stream

  #### Stream-level (in data_stream/<ds>/manifest.yml)
  - name: api_url
    type: url
    migrate_from:
      scope: input

Same internal name: in both versions, version-bumped in manifest.yml and
changelog.yml. Use version: 1.0.0 and up so the registry doesn't tag
them release: beta.

  2. Build both versions

#### elastic-package needs to know about format_version 3.7.0, which
adds var-level migrate_from.
  #### Rebuild from the package-spec branch that adds it:
```
  cd /path/to/package-spec  # branch: add-migrate-from-variable-support
```
#### Confirm go.mod has `replace github.com/elastic/package-spec/v3 =>
/path/to/package-spec`
```
  cd /path/to/elastic-package
  go mod tidy && go install ./
```
  #### Build each version
```
  cd /path/to/packages/my_test_package_v1
  elastic-package build --skip-validation
  cd /path/to/packages/my_test_package_v2
  elastic-package build --skip-validation
```
#### zips land in /path/to/package-spec/build/packages/ (or your repo
root's build/packages/)

  3. Serve them locally
```
  mkdir -p /tmp/fleet-test-packages
  cp /path/to/build/packages/my_test_package-1.0.0.zip /tmp/fleet-test-packages/
  cp /path/to/build/packages/my_test_package-1.0.1.zip /tmp/fleet-test-packages/

  cat > /tmp/fleet-test-packages/registry-config.yml <<EOF
  package_paths:
    - /tmp/fleet-test-packages
  EOF

  cd /path/to/package-registry
  go build -o package-registry ./
  ./package-registry \
    --address localhost:8080 \
    --feature-storage-indexer=false \
    --disable-package-validation \
    --config /tmp/fleet-test-packages/registry-config.yml
```
  Sanity-check:
```
  curl -s 'http://localhost:8080/search?package=my_test_package&all=true' \
    | python3 -c "import sys,json; [print(p['version']) for p in json.load(sys.stdin)]"
```
  #### expect: 1.0.0 and 1.0.1

  4. Run Kibana pointed at the local registry
```
  yarn es snapshot                                                     # terminal A
  yarn start --no-base-path --xpack.fleet.registryUrl=http://localhost:8080   # terminal B
```
  5. Exercise the upgrade

1. Integrations → install v1.0.0, attach to an agent policy, set
non-default values for both vars.
  2. Confirm pre-upgrade scopes with:
  `GET kbn:/api/fleet/package_policies?perPage=20`
  3. Install v1.0.1 and upgrade the policy:
  
`POST kbn:/api/fleet/epm/packages/my_test_package/1.0.1 {}`
Then upgrade with
 `POST kbn:/api/fleet/package_policies/upgrade
  { "packagePolicyIds": ["<policy-id>"] }`
  4. Re-fetch the policy. Expected:
    - Values are present at the new scopes.
    - Both old-scope entries are gone.
- secret_references array still tracks any moved secret-typed vars (no
orphan, no re-create).
- Upgrade form shows the info-icon tooltip next to migrated var labels.

  
### Checklist

Check the PR satisfies following conditions. 

Reviewers should verify this PR satisfies this list as well.

- [ ] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/src/platform/packages/shared/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] If a plugin configuration key changed, check if it needs to be
allowlisted in the cloud and added to the [docker
list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)
- [ ] This was checked for breaking HTTP API changes, and any breaking
changes have been approved by the breaking-change committee. The
`release_note:breaking` label should be applied in these situations.
- [ ] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
- [ ] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
- [ ] Review the [backport
guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing)
and apply applicable `backport:*` labels.

### Identify risks

Does this PR introduce any risks? For example, consider risks like hard
to test bugs, performance regression, potential of data loss.

Describe the risk, its severity, and mitigation for each identified
risk. Invite stakeholders and evaluate how to proceed before merging.

- [ ] [See some risk
examples](https://github.com/elastic/kibana/blob/main/RISK_MATRIX.mdx)
- [ ] ...

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Ecosystem] Add migrate_from to variable schema

4 participants