Skip to content

Add pinned_version to edited_software activity - #48098

Merged
jkatz01 merged 8 commits into
feat/38504-auto-update-pin-rollback-fmafrom
47679-pinned-version-activity
Jun 23, 2026
Merged

Add pinned_version to edited_software activity#48098
jkatz01 merged 8 commits into
feat/38504-auto-update-pin-rollback-fmafrom
47679-pinned-version-activity

Conversation

@jkatz01

@jkatz01 jkatz01 commented Jun 23, 2026

Copy link
Copy Markdown
Member

Related issue: Resolves #47679
Adds a few things:

  • exports pinned version in generate-gitops enclosed in double quotes
  • adds pinned_version to the edited software activity. When set to a full or major version it shows up in details, when set to latest or unchanged it shows up as pinned_version: null (some other fields like display_name also dont show up when unchanged)
  • fixes a bug where some FMA's like google chrome couldn't be pinned to major version because they couldn't be converted to semver (by just splitting the version on periods instead of converting to semver)

Checklist for submitter

If some of the following don't apply, delete the relevant line.

  • Changes file added for user-visible changes in changes/, orbit/changes/ or ee/fleetd-chrome/changes.
    See Changes files for more information.

  • Input data is properly validated, SELECT * is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters.

  • Timeouts are implemented and retries are limited to avoid infinite loops

  • If paths of existing endpoints are modified without backwards compatibility, checked the frontend/CLI for any necessary changes

Testing

New Fleet configuration settings

  • Setting(s) is/are explicitly excluded from GitOps

If you didn't check the box above, follow this checklist for GitOps-enabled settings:

  • Verified that the setting is exported via fleetctl generate-gitops
  • Verified the setting is documented in a separate PR to the GitOps documentation
  • Verified that the setting is cleared on the server if it is not supplied in a YAML file (or that it is documented as being optional)
  • Verified that any relevant UI is disabled when GitOps mode is enabled

Summary by CodeRabbit

  • New Features
    • Software titles now support version pinning in GitOps exports for fleet-managed applications, with pinned version values properly formatted as quoted strings in the exported YAML configuration
    • Activity logs now record when pinned version information is modified during software editing operations

jkatz01 added 5 commits June 23, 2026 12:43
Add PinnedVersion *string to ActivityTypeEditedSoftware (no omitempty, so clearing to Latest renders pinned_version: null, matching the team_id convention). UpdateSoftwareInstaller populates it on a version PATCH; TestFleetMaintainedAppVersionPin asserts it after each pin/clear. For #47679.
generateSoftware emits version: for an FMA from SoftwarePackage.PinnedVersion so a UI-set pin round-trips through GitOps; Latest/unpinned omits it. Test covers literal, caret, and Latest.
ghodss/yaml only auto-quotes number-like strings, so a pin like 1.2.3 or ^N was emitted bare. Per docs/Configuration/yaml-files.md the version must be quoted so YAML keeps it a string (an unquoted 10.0 would round-trip as the float 10). Force it with a post-marshal regex in the file-write step.
The PinnedVersion field (added in the prior commit) has no omitempty, so every edited_software activity now renders pinned_version: null. Update the existing TestSoftwareInstallerUploadDownloadAndDelete assertions to match. The uploaded_at struct/query edits this commit originally carried are already in the feature base.
versionMatchesMajor ran each cached version through Masterminds semver, which rejects 4-component strings (Chrome/Edge, e.g. 149.0.7827.115). A caret pin like ^149 on such an app failed with "Invalid Semantic Version" on both the PATCH and GitOps slug paths. Compare the leading dot-segment as a string instead.

Add unit tests for versionMatchesMajor/parsePinnedVersion over the non-semver versions in ee/maintained-apps/outputs, and cover the end-to-end Google Chrome caret pin in TestFleetMaintainedAppVersionPin.
@codecov

codecov Bot commented Jun 23, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (feat/38504-auto-update-pin-rollback-fma@5c3eaca). Learn more about missing BASE report.

Additional details and impacted files
@@                            Coverage Diff                             @@
##             feat/38504-auto-update-pin-rollback-fma   #48098   +/-   ##
==========================================================================
  Coverage                                           ?   67.23%           
==========================================================================
  Files                                              ?     3635           
  Lines                                              ?   229906           
  Branches                                           ?    11956           
==========================================================================
  Hits                                               ?   154586           
  Misses                                             ?    61428           
  Partials                                           ?    13892           
Flag Coverage Δ
backend 68.86% <100.00%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

The existing FMA pin fixtures used only non-number-like values (1.2.3/^123), which are strings regardless of quoting, so nothing exercised the case the version quoting actually protects against. Pin fma2 to a number-like 10.0 (must stay quoted, else it parses as the float 10) and leave fma1 unpinned to keep the no-version-line case.
@jkatz01
jkatz01 marked this pull request as ready for review June 23, 2026 19:07
@jkatz01
jkatz01 requested a review from a team as a code owner June 23, 2026 19:07

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@jkatz01

jkatz01 commented Jun 23, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

This PR tracks pinned version changes in the edited_software activity log. A new optional PinnedVersion *string field is added to ActivityTypeEditedSoftware and populated in UpdateSoftwareInstaller when a non-empty pinned version is present. The versionMatchesMajor helper is refactored from a semver-based (bool, error) return to a simple string-split comparison returning only bool, removing error-handling branches at its two call sites. The generate-gitops command gains logic to emit a version field from SoftwarePackage.PinnedVersion for Fleet-maintained app entries and applies a regex post-processing step to quote version: YAML values as strings. Tests are updated throughout: unit tests for the refactored helpers, GitOps mock and fixture YAML updates, and integration test expansions including a Chrome non-semver version fixture and a requireLastPinActivity helper.

Possibly related PRs

  • fleetdm/fleet#47808: Implements FMA version pinning in ee/server/service/software_installers.go, including the original versionMatchesMajor and caret-pin selection logic that this PR refactors.
🚥 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 accurately summarizes the main change: adding pinned_version field to the edited_software activity log structure.
Description check ✅ Passed The description covers key changes: pinned_version field addition, GitOps export with quoted strings, and version handling bug fix for non-semver apps. Most critical items from the checklist are addressed or noted as N/A.
Linked Issues check ✅ Passed All requirements from issue #47679 are met: PinnedVersion field added to ActivityTypeEditedSoftware, pinned_version populated on PATCH requests with literal or caret-based values, clearing emits pinned_version: null, and test coverage added.
Out of Scope Changes check ✅ Passed All changes directly support the linked issue requirements: activity field tracking, GitOps export functionality, version matching logic refactor for non-semver apps, and comprehensive test coverage updates.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 47679-pinned-version-activity

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.44.0)
server/service/integration_enterprise_test.go

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.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
ee/server/service/software_installers.go (1)

3980-3990: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reject non-numeric caret pins before applying/storing them.

Line 3986 only rejects dotted caret values, so inputs like ^abc currently pass parsing. They then fail matching, fall back to latest installer, and can still be recorded as a pinned value, which creates an invalid audit/state combination.

Proposed fix
 func parsePinnedVersion(ctx context.Context, version string) (majorVersion string, usesCaret bool, err error) {
 	majorVersion, usesCaret = strings.CutPrefix(version, "^")
 	if usesCaret {
 		if len(majorVersion) == 0 {
 			return "", false, fleet.NewUserMessageError(errEmptyCaretVersion, http.StatusBadRequest)
 		}
-		if parts := strings.Split(version, "."); len(parts) > 1 {
+		if strings.Contains(majorVersion, ".") {
 			return "", false, fleet.NewUserMessageError(errNonMajorVersion, http.StatusBadRequest)
 		}
+		for _, r := range majorVersion {
+			if r < '0' || r > '9' {
+				return "", false, fleet.NewUserMessageError(errNonMajorVersion, http.StatusBadRequest)
+			}
+		}
 	}
 	return majorVersion, usesCaret, nil
 }

Also applies to: 3993-3995

🤖 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 `@ee/server/service/software_installers.go` around lines 3980 - 3990, The
parsePinnedVersion function does not validate that the extracted majorVersion is
numeric, allowing invalid values like "^abc" to pass parsing and subsequently
fail during matching. After the CutPrefix call removes the caret prefix, add a
validation check to ensure the majorVersion contains only numeric characters
before returning success. This validation should reject non-numeric caret pins
and prevent them from being recorded as pinned values in audit logs.
🤖 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 `@cmd/fleetctl/fleetctl/generate_gitops.go`:
- Around line 674-675: The softwareVersion quoting normalization applied at line
675 only affects the file-write path, but the --key output path returns earlier
(around line 645) after yaml.Marshal without applying the same quoting fix. To
ensure consistent version serialization behavior across both modes and prevent
type coercion of number-like versions in key output, apply the same
softwareVersion.ReplaceAll normalization that quotes versions to the --key
output path before it returns, matching the pattern used in the file-write code
path.

---

Outside diff comments:
In `@ee/server/service/software_installers.go`:
- Around line 3980-3990: The parsePinnedVersion function does not validate that
the extracted majorVersion is numeric, allowing invalid values like "^abc" to
pass parsing and subsequently fail during matching. After the CutPrefix call
removes the caret prefix, add a validation check to ensure the majorVersion
contains only numeric characters before returning success. This validation
should reject non-numeric caret pins and prevent them from being recorded as
pinned values in audit logs.
🪄 Autofix (Beta)

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: CHILL

Plan: Pro

Run ID: 341e9343-fb94-4e78-9341-b289f2fe72f5

📥 Commits

Reviewing files that changed from the base of the PR and between 09e2912 and bee2d04.

📒 Files selected for processing (10)
  • cmd/fleetctl/fleetctl/generate_gitops.go
  • cmd/fleetctl/fleetctl/generate_gitops_test.go
  • cmd/fleetctl/fleetctl/testdata/generateGitops/expectedTeamSoftware.yaml
  • cmd/fleetctl/fleetctl/testdata/generateGitops/test_dir_premium/fleets/team-a-thumbsup.yml
  • ee/server/service/software_installers.go
  • ee/server/service/software_installers_test.go
  • server/fleet/activities.go
  • server/service/integration_enterprise_test.go
  • server/service/integration_software_titles_test.go
  • server/service/integration_vpp_install_test.go

Comment on lines +674 to +675
// Keep software versions quoted so YAML treats them as strings (e.g. "10.0" must not become a float).
b = softwareVersion.ReplaceAll(b, []byte(`${1}"${2}"`))

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Apply version-quoting in --key output too.

Line 675 fixes quoting only in the file-write path, but the --key path returns at Line 645 after yaml.Marshal and skips this normalization entirely. That makes pinned-version serialization behavior inconsistent across modes and can reintroduce type coercion risk for number-like versions in key output.

Suggested fix
@@
-		b, err = yaml.Marshal(value)
+		b, err = yaml.Marshal(value)
 		if err != nil {
 			fmt.Fprintf(cmd.CLI.App.ErrWriter, "Error marshaling value: %s\n", err)
 			return ErrGeneric
 		}
+		// Keep software versions quoted so YAML treats them as strings consistently
+		// with full-file output normalization.
+		b = softwareVersion.ReplaceAll(b, []byte(`${1}"${2}"`))
 		fmt.Fprintf(cmd.CLI.App.Writer, "%s", string(b))
 		return nil
 	}
🤖 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 `@cmd/fleetctl/fleetctl/generate_gitops.go` around lines 674 - 675, The
softwareVersion quoting normalization applied at line 675 only affects the
file-write path, but the --key output path returns earlier (around line 645)
after yaml.Marshal without applying the same quoting fix. To ensure consistent
version serialization behavior across both modes and prevent type coercion of
number-like versions in key output, apply the same softwareVersion.ReplaceAll
normalization that quotes versions to the --key output path before it returns,
matching the pattern used in the file-write code path.

@jkatz01

jkatz01 commented Jun 23, 2026

Copy link
Copy Markdown
Member Author

Addressed:
Finding 1 (^abc not rejected) — fixed.** Added strconv.ParseUint validation of the caret major in parsePinnedVersion; now ^abc/^1.2 → 400. Renamed return majorVersiontrimmedVersion. Tests pass.

Finding 2 (--key quoting) — skipped, not a bug. ghodss/yaml already quotes number-like versions there; no coercion.

@jkatz01
jkatz01 merged commit 5864788 into feat/38504-auto-update-pin-rollback-fma Jun 23, 2026
52 of 54 checks passed
@jkatz01
jkatz01 deleted the 47679-pinned-version-activity branch June 23, 2026 20:15
@jkatz01 jkatz01 linked an issue Jun 24, 2026 that may be closed by this pull request
3 tasks
georgekarrv added a commit that referenced this pull request Jun 26, 2026
#48293)

**Related issue:** Resolves #38504

  **Constituent PRs (merged into this feature branch):**

- #47682 — Fleet UI: APRF Software title details page Library/Inventory
layout
- #47808 — Extend update software installer API to support FMA version
pinning
  - #47944 — Fleet UI: APRF library item accordion component
  - #48081 — Versions modal, multi-row Library, pinned state
  - #48098 — Add `pinned_version` to `edited_software` activity
  - #48123 — Auto-update FMA cron
  - #48144 — Download a newly-published FMA version when pinned to it

  # Checklist for submitter

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or
`ee/fleetd-chrome/changes`. See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.

- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements), JS
inline code is prevented especially for url redirects, and untrusted
data interpolated into shell scripts/commands is validated against shell
metacharacters.
- [x] Timeouts are implemented and retries are limited to avoid infinite
loops
- [x] If paths of existing endpoints are modified without backwards
compatibility, checked the frontend/CLI for any necessary changes

  ## Testing

  - [x] Added/updated automated tests
- [x] Where appropriate, [automated tests simulate multiple hosts and
test for host
isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing)
(updates
  to one hosts's records do not affect another)

  - [x] QA'd all new/changed functionality manually


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Added Fleet-maintained app version pinning (Latest, exact, and major)
via a new Versions modal.
* Introduced premium auto-updates for maintained apps with pin-aware
promotion and rollback-safe caching.
  * Added expandable library version rows and a Policies modal.
* **Bug Fixes**
* Improved pin handling, cache/manifest hydration, and safer update
behavior on per-app failures and deduplication.
* **UI/UX**
* Refreshed the Software title details experience with new
accordion/list patterns, redesigned details widget/tooltips, and updated
installer presentation.
* **Documentation**
* Expanded Storybook component/page coverage and adjusted Storybook
canvas padding.
* **Tests**
* Added/updated unit and integration tests for pinning, auto-update
flows, and new modal/UI behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
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.

APRF: Add pinned_version to edited_software activity

2 participants