Skip to content

GitOps changes for custom org's logo uploads - #44550

Merged
nulmete merged 6 commits into
mainfrom
44333-gitops-ability-to-upload-a
May 5, 2026
Merged

GitOps changes for custom org's logo uploads#44550
nulmete merged 6 commits into
mainfrom
44333-gitops-ability-to-upload-a

Conversation

@nulmete

@nulmete nulmete commented Apr 30, 2026

Copy link
Copy Markdown
Member

Related issue: Resolves #44333

Checklist for submitter

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

Testing

generate-gitops

  • Branched off to main, no URLs set, then ran generate-gitops on this branch. Deprecated keys gone, new keys present.
nourls_new
  • Branched off to main, set external URLs for both light and dark modes, then ran generate-gitops on this branch. Deprecated keys gone, new keys set with the external URLs.
externalurl_main externalurl_new
  • Within this branch, after uploading a custom logo for light mode, ran generate-gitops. The logo was saved in lib/org_logo/light.webp
Screenshot 2026-05-04 at 4 06 59 PM Screenshot 2026-05-04 at 4 07 30 PM

gitops

  • Applied gitops with two external URLs. Verified in the UI that those are still present
Screenshot 2026-05-04 at 7 54 53 AM Screenshot 2026-05-04 at 8 01 04 AM
  • Applied gitops with "" as the URLs to clear them. Verified the default fleet logo is shown.
Screenshot 2026-05-04 at 8 15 11 AM Screenshot 2026-05-04 at 8 15 50 AM
  • Applied gitops with a custom logo for light theme, using org_logo_path_light_mode:
Screenshot 2026-05-04 at 4 10 05 PM Screenshot 2026-05-04 at 4 10 35 PM

New Fleet configuration settings

Summary by CodeRabbit

  • New Features

    • GitOps support for uploading custom org logos (dark/light) via local files.
    • fleetctl generate-gitops exports Fleet-hosted logos as local files and inserts path references.
    • New API endpoints to upload, delete, and fetch org logos.
  • Deprecated

    • Legacy logo keys consolidated into mode-specific URL keys (org_logo_url_dark_mode, org_logo_url_light_mode).
  • Bug Fixes / Validation

    • Validation/error when both a path and URL are provided for the same mode; file size and image-format checks enforced.

@codecov

codecov Bot commented Apr 30, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 57.62712% with 100 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.65%. Comparing base (0448ac1) to head (c35284f).
⚠️ Report is 14 commits behind head on main.

Files with missing lines Patch % Lines
server/service/client_appconfig.go 47.58% 63 Missing and 2 partials ⚠️
cmd/fleetctl/fleetctl/generate_gitops.go 60.78% 13 Missing and 7 partials ⚠️
server/fleet/org_logo.go 65.21% 8 Missing ⚠️
server/service/client.go 50.00% 2 Missing and 2 partials ⚠️
server/fleet/app.go 71.42% 1 Missing and 1 partial ⚠️
server/service/org_logo.go 75.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main   #44550      +/-   ##
==========================================
- Coverage   66.67%   66.65%   -0.02%     
==========================================
  Files        2652     2657       +5     
  Lines      213648   214288     +640     
  Branches     9806     9806              
==========================================
+ Hits       142449   142842     +393     
- Misses      58236    58422     +186     
- Partials    12963    13024      +61     
Flag Coverage Δ
backend 68.53% <57.62%> (-0.03%) ⬇️

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

☔ View full report in Codecov by Sentry.
📢 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.

@nulmete
nulmete marked this pull request as ready for review May 1, 2026 08:35
@nulmete
nulmete requested a review from a team as a code owner May 1, 2026 08:35

@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.

Comment thread server/service/client.go
@@ -1954,6 +1955,19 @@ func (c *Client) DoGitOps(
group.CertificateAuthorities = groupedCAs
delete(incoming.OrgSettings, "certificate_authorities")

// Plan org logo upload/delete actions and strip the gitops-only path

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

because OrgInfo in the service layer is not aware of paths, just URLs

Comment on lines +222 to +224
if err := fleet.ValidateOrgLogoBytes(body); err != nil {
return fmt.Errorf("logo file at %q: %w", path, err)
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

this validation is also done within DecodeRequest in the PUT org_logo endpoint, but needs to be called here so we fail early (and also for --dry-run)

@@ -205,33 +170,12 @@ func parseLogoModeQuery(raw string) (fleet.OrgLogoMode, error) {
return m, nil
}

func validateOrgLogoBytes(b []byte) error {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

moved to fleet pkg in order to be reused within gitops

Base automatically changed from 39016-ability-to-upload-a-custom to main May 5, 2026 12:42
@nulmete
nulmete requested a review from a team as a code owner May 5, 2026 12:42
Copilot AI review requested due to automatic review settings May 5, 2026 12:54
@nulmete
nulmete force-pushed the 44333-gitops-ability-to-upload-a branch from d8a918b to da34b3e Compare May 5, 2026 12:54
@coderabbitai

coderabbitai Bot commented May 5, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 0f007e31-68bb-4efa-b42a-ae83883925d1

📥 Commits

Reviewing files that changed from the base of the PR and between da34b3e and c35284f.

📒 Files selected for processing (4)
  • cmd/fleetctl/fleetctl/generate_gitops.go
  • pkg/spec/gitops.go
  • server/fleet/app.go
  • server/service/org_logo.go

Walkthrough

Adds GitOps support for mode-aware organization logos. fleetctl gitops accepts org_logo_path_dark_mode / org_logo_path_light_mode to upload local logo files; fleetctl generate-gitops exports Fleet-hosted logos into ./lib/org_logo/. and replaces URL entries with org_logo_path_* keys while preserving external URLs as org_logo_url_dark_mode / org_logo_url_light_mode. Validations prevent providing both a path and a URL for the same mode. New server/client endpoints and helpers handle upload, delete, fetch, format detection, and size validation.

Possibly related PRs

  • fleetdm/fleet#44390: Implements the org-logo API endpoint and storage layer used for uploading, fetching, and deleting Fleet-hosted org logos.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% 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 clearly summarizes the main change: GitOps support for custom organization logo uploads. It directly reflects the primary focus of the changeset.
Description check ✅ Passed The PR description covers most required checklist items: a changes file was added, automated and integration tests were included, QA was performed manually with detailed evidence, and new GitOps settings were properly verified.
Linked Issues check ✅ Passed The PR implements all objectives from #44333: accepts org_logo_path_dark_mode/light_mode keys, reads and uploads local files via gitops, rejects conflicting path+URL configs, renames deprecated URL keys with warnings, and handles both gitops apply and generate-gitops export correctly.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing GitOps org logo upload support. No unrelated refactoring or out-of-scope modifications are present in the changeset.

✏️ 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 44333-gitops-ability-to-upload-a

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.

@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

🧹 Nitpick comments (4)
server/service/integration_core_test.go (1)

17044-17047: ⚡ Quick win

Restore s.token via defer to prevent cascading failures.

s.token is a suite-level field. If DoRawWithHeaders at line 17046 calls t.FailNow() (e.g., the auth check is broken and the server returns 200 instead of 403), the restore at line 17047 is never reached. Subsequent suite tests that rely on s.token will then fail with mysterious auth errors rather than pointing to this test.

♻️ Proposed fix
-	s.token = s.getCachedUserToken(maintainerEmail, test.GoodPassword)
+	origToken := s.token
+	s.token = s.getCachedUserToken(maintainerEmail, test.GoodPassword)
+	defer func() { s.token = origToken }()
 	body, headers = buildLogoBody("nope.png", pngBytes)
 	s.DoRawWithHeaders("PUT", "/api/v1/fleet/logo?mode=light", body, http.StatusForbidden, headers)
-	s.token = s.getTestAdminToken()
🤖 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 `@server/service/integration_core_test.go` around lines 17044 - 17047, The test
temporarily sets s.token to a non-admin value via s.getCachedUserToken before
calling DoRawWithHeaders and then resets it with s.getTestAdminToken; change
this to save the original token into a local variable (e.g., origToken)
immediately after modifying s.token and use defer to restore s.token = origToken
so the admin token is always restored even if DoRawWithHeaders triggers a
FailNow; reference s.token, getCachedUserToken, DoRawWithHeaders and
getTestAdminToken when making the change.
pkg/spec/gitops_test.go (1)

1106-1122: ⚡ Quick win

Add a light-mode old+new URL conflict case in this subtest.

This currently validates only org_logo_url + org_logo_url_dark_mode. Add the symmetric org_logo_url_light_background + org_logo_url_light_mode conflict case so both migration paths are covered.

Proposed test extension
 t.Run("old + new URL keys conflict", func(t *testing.T) {
@@
 		_, err := gitOpsFromString(t, config)
 		require.Error(t, err)
 		assert.ErrorContains(t, err, "org_logo_url")
+
+		config = getGlobalConfig([]string{"org_settings"})
+		config += `
+org_settings:
+  server_settings:
+    server_url: https://fleet.example.com
+  org_info:
+    contact_url: https://example.com/contact
+    org_name: Test Org
+    org_logo_url_light_background: https://example.com/old-light.png
+    org_logo_url_light_mode: https://example.com/new-light.png
+  secrets:
+`
+		_, err = gitOpsFromString(t, config)
+		require.Error(t, err)
+		assert.ErrorContains(t, err, "org_logo_url_light_background")
 	})
 }
🤖 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 `@pkg/spec/gitops_test.go` around lines 1106 - 1122, The test t.Run("old + new
URL keys conflict") only covers the dark-mode pair; extend it to also cover the
light-mode pair by adding config data containing both
org_logo_url_light_background and org_logo_url_light_mode (either in the same
config string or as a separate subtest inside this t.Run), call gitOpsFromString
as before, require.Error on the result, and use assert.ErrorContains to check
the error mentions the light-mode keys (org_logo_url_light_background /
org_logo_url_light_mode) in addition to the existing org_logo_url check; ensure
you keep the same pattern of using config := getGlobalConfig(...) and the
existing require/assert calls.
server/service/integration_enterprise_test.go (1)

29598-29610: ⚡ Quick win

Defer logo cleanup to avoid state leakage on early failures.

If the assertion on Line 29606 fails, cleanup on Lines 29608-29609 is skipped and test state can leak into later cases.

Proposed change
 	s.DoRawWithHeaders("PUT", "/api/v1/fleet/logo?mode=dark", body.Bytes(), http.StatusOK, map[string]string{
 		"Content-Type":  w.FormDataContentType(),
 		"Accept":        "application/json",
 		"Authorization": "Bearer " + s.token,
 	})
+	defer func() {
+		s.token = s.getTestAdminToken()
+		s.Do("DELETE", "/api/v1/fleet/logo", nil, http.StatusOK, "mode", "dark")
+	}()
 
 	var acResp appConfigResponse
 	s.DoJSON("GET", "/api/v1/fleet/config", nil, http.StatusOK, &acResp)
 	require.Contains(t, acResp.OrgInfo.OrgLogoURLDarkMode, "/api/latest/fleet/logo")
-
-	s.token = s.getTestAdminToken()
-	s.Do("DELETE", "/api/v1/fleet/logo", nil, http.StatusOK, "mode", "dark")
 }
🤖 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 `@server/service/integration_enterprise_test.go` around lines 29598 - 29610,
After uploading the logo but before any assertions, add a deferred cleanup to
always delete the logo to avoid state leakage: immediately after the
s.DoRawWithHeaders(...) call, insert a defer that captures any needed state and
calls s.token = s.getTestAdminToken() followed by s.Do("DELETE",
"/api/v1/fleet/logo", nil, http.StatusOK, "mode", "dark") (restore s.token if
necessary) so cleanup runs even if require.Contains on
acResp.OrgInfo.OrgLogoURLDarkMode fails; reference s.DoRawWithHeaders, s.DoJSON,
s.getTestAdminToken, and s.Do to locate where to add the defer.
server/service/client_appconfig_test.go (1)

98-102: ⚡ Quick win

Deprecated-key deletion is only tested trivially here.

The initial orgInfo at line 88 does not include "org_logo_url", so the assertion that it is absent after the call is trivially true regardless of whether delete(orgInfo, s.deprecatedURLKey) runs. A slightly stronger variant would seed the key and assert it is gone:

♻️ Suggested improvement
 os := orgSettings(map[string]any{
     "org_logo_path_dark_mode": "logo.png",
     "org_logo_url_dark_mode":  "",
+    "org_logo_url":            "https://old.example.com/logo.png", // deprecated alias present
 })
🤖 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 `@server/service/client_appconfig_test.go` around lines 98 - 102, The test
currently asserts deprecated keys are absent but never seeds "org_logo_url",
making the check trivial; modify the test to pre-populate orgInfo with the
deprecated key(s) (e.g., set orgInfo["org_logo_url"] = "placeholder" or similar)
before invoking the code under test, then run the same loop that checks
orgInfo[k] and assert.False for each key so you validate that the code (which
should call delete on s.deprecatedURLKey) actually removes the seeded key; refer
to the orgInfo variable and the loop over keys (including "org_logo_url") to
locate where to insert the seeding.
🤖 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 `@server/fleet/app.go`:
- Around line 1294-1305: Remove the early strings.HasPrefix check in
IsFleetHostedLogoURL and instead parse the URL first (after the empty-string
guard), then compare the parsed URL's Path exactly to orgLogoServingPathPrefix;
specifically, update the function so it returns false on parse error and only
returns true when u.Path == orgLogoServingPathPrefix (do not use
strings.HasPrefix on rawURL), ensuring relative URLs like
"/api/latest/fleet/logo-proxy" no longer mis-identify as Fleet-hosted.

---

Nitpick comments:
In `@pkg/spec/gitops_test.go`:
- Around line 1106-1122: The test t.Run("old + new URL keys conflict") only
covers the dark-mode pair; extend it to also cover the light-mode pair by adding
config data containing both org_logo_url_light_background and
org_logo_url_light_mode (either in the same config string or as a separate
subtest inside this t.Run), call gitOpsFromString as before, require.Error on
the result, and use assert.ErrorContains to check the error mentions the
light-mode keys (org_logo_url_light_background / org_logo_url_light_mode) in
addition to the existing org_logo_url check; ensure you keep the same pattern of
using config := getGlobalConfig(...) and the existing require/assert calls.

In `@server/service/client_appconfig_test.go`:
- Around line 98-102: The test currently asserts deprecated keys are absent but
never seeds "org_logo_url", making the check trivial; modify the test to
pre-populate orgInfo with the deprecated key(s) (e.g., set
orgInfo["org_logo_url"] = "placeholder" or similar) before invoking the code
under test, then run the same loop that checks orgInfo[k] and assert.False for
each key so you validate that the code (which should call delete on
s.deprecatedURLKey) actually removes the seeded key; refer to the orgInfo
variable and the loop over keys (including "org_logo_url") to locate where to
insert the seeding.

In `@server/service/integration_core_test.go`:
- Around line 17044-17047: The test temporarily sets s.token to a non-admin
value via s.getCachedUserToken before calling DoRawWithHeaders and then resets
it with s.getTestAdminToken; change this to save the original token into a local
variable (e.g., origToken) immediately after modifying s.token and use defer to
restore s.token = origToken so the admin token is always restored even if
DoRawWithHeaders triggers a FailNow; reference s.token, getCachedUserToken,
DoRawWithHeaders and getTestAdminToken when making the change.

In `@server/service/integration_enterprise_test.go`:
- Around line 29598-29610: After uploading the logo but before any assertions,
add a deferred cleanup to always delete the logo to avoid state leakage:
immediately after the s.DoRawWithHeaders(...) call, insert a defer that captures
any needed state and calls s.token = s.getTestAdminToken() followed by
s.Do("DELETE", "/api/v1/fleet/logo", nil, http.StatusOK, "mode", "dark")
(restore s.token if necessary) so cleanup runs even if require.Contains on
acResp.OrgInfo.OrgLogoURLDarkMode fails; reference s.DoRawWithHeaders, s.DoJSON,
s.getTestAdminToken, and s.Do to locate where to add the defer.
🪄 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: 2cacbd85-ec6c-4c6b-8eba-d3291f5612ac

📥 Commits

Reviewing files that changed from the base of the PR and between 0448ac1 and da34b3e.

📒 Files selected for processing (21)
  • changes/44333-gitops-custom-org-logo-upload
  • cmd/fleetctl/fleetctl/generate_gitops.go
  • cmd/fleetctl/fleetctl/generate_gitops_test.go
  • cmd/fleetctl/fleetctl/testdata/generateGitops/expectedOrgSettings-insecure.yaml
  • cmd/fleetctl/fleetctl/testdata/generateGitops/expectedOrgSettings.yaml
  • cmd/fleetctl/fleetctl/testdata/generateGitops/test_dir_free/default.yml
  • cmd/fleetctl/fleetctl/testdata/generateGitops/test_dir_premium/default.yml
  • pkg/spec/gitops.go
  • pkg/spec/gitops_deprecations.go
  • pkg/spec/gitops_test.go
  • pkg/spec/gitops_validate.go
  • server/fleet/app.go
  • server/fleet/org_logo.go
  • server/service/client.go
  • server/service/client_appconfig.go
  • server/service/client_appconfig_test.go
  • server/service/integration_core_test.go
  • server/service/integration_enterprise_test.go
  • server/service/org_logo.go
  • server/service/org_logo_test.go
  • server/service/testing_utils.go
💤 Files with no reviewable changes (4)
  • cmd/fleetctl/fleetctl/testdata/generateGitops/expectedOrgSettings-insecure.yaml
  • cmd/fleetctl/fleetctl/testdata/generateGitops/expectedOrgSettings.yaml
  • cmd/fleetctl/fleetctl/testdata/generateGitops/test_dir_free/default.yml
  • cmd/fleetctl/fleetctl/testdata/generateGitops/test_dir_premium/default.yml

Comment thread server/fleet/app.go

Copilot AI 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.

Pull request overview

This PR adds end-to-end GitOps support for custom organization logo uploads, including new YAML keys for local file paths, GitOps apply behavior to upload/delete Fleet-hosted logo blobs, and generate-gitops behavior to export Fleet-hosted logos back to local files (while preserving true external URLs). It also standardizes org logo URL key names to mode-aware variants and updates tests/fixtures accordingly.

Changes:

  • fleetctl gitops: accept org_logo_path_{light,dark}_mode, upload logos after a successful AppConfig PATCH, and delete stale Fleet-hosted blobs when switching to external URLs or clearing URLs.
  • fleetctl generate-gitops: detect Fleet-hosted logo URLs, download logo bytes, write them under lib/org_logo/, and emit org_logo_path_*_mode instead of URL keys.
  • Refactor org-logo byte validation/content-type sniffing into server/fleet and expand unit/integration test coverage.

Reviewed changes

Copilot reviewed 20 out of 21 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
server/service/testing_utils.go Wires a temp OrgLogoStore into test service setup so org-logo endpoints can operate in tests.
server/service/org_logo.go Switches to shared org-logo validation/content-type helpers and relaxes write auth to AppConfig write (enabling GitOps role).
server/service/org_logo_test.go Updates auth expectations to allow the global GitOps role to upload/delete org logos.
server/service/integration_enterprise_test.go Adds integration coverage that a GitOps user can upload an org logo.
server/service/integration_core_test.go Adds integration coverage for upload/GET content-type, auth rejection, invalid payload, and delete lifecycle.
server/service/client.go Plans and applies org-logo upload/delete actions during fleetctl gitops.
server/service/client_appconfig.go Implements org-logo action planning, local-file validation, and client-side PUT/DELETE/GET helpers.
server/service/client_appconfig_test.go Adds focused tests for org-logo planning/stripping behavior and local file validation.
server/fleet/org_logo.go Centralizes org-logo validation and content-type sniffing utilities in the fleet package.
server/fleet/app.go Adds IsFleetHostedLogoURL to distinguish Fleet-served logo URLs from external URLs.
pkg/spec/gitops.go Adds GitOpsOrgInfo to allow gitops-only logo path keys and validates mutual exclusivity of path vs URL.
pkg/spec/gitops_validate.go Registers org_info as a typed gitops object for validation/unknown-key checking.
pkg/spec/gitops_test.go Adds parsing tests for new path keys, mutual exclusivity, and deprecated URL key renames.
pkg/spec/gitops_deprecations.go Adds deprecated-key mappings from old logo URL keys to new mode-aware keys.
cmd/fleetctl/fleetctl/testdata/generateGitops/test_dir_premium/default.yml Updates fixtures to remove deprecated logo URL keys and use mode-aware keys.
cmd/fleetctl/fleetctl/testdata/generateGitops/test_dir_free/default.yml Updates fixtures to remove deprecated logo URL keys and use mode-aware keys.
cmd/fleetctl/fleetctl/testdata/generateGitops/expectedOrgSettings.yaml Updates expected output to remove deprecated keys.
cmd/fleetctl/fleetctl/testdata/generateGitops/expectedOrgSettings-insecure.yaml Updates expected output to remove deprecated keys.
cmd/fleetctl/fleetctl/generate_gitops.go Exports Fleet-hosted logos to lib/org_logo/* and emits path keys during generate-gitops.
cmd/fleetctl/fleetctl/generate_gitops_test.go Adds tests for exporting Fleet-hosted logos (success, external URL no-op, and 404 warning behavior).
changes/44333-gitops-custom-org-logo-upload Adds release note for GitOps org logo upload/export behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/spec/gitops.go Outdated
Comment on lines +329 to +331
// a custom org logo from a local file. The path keys are extracted and turned
// into PUT /api/v1/fleet/logo calls before the OrgInfo is sent to the AppConfig
// PATCH endpoint.
Comment thread server/service/org_logo.go Outdated
Comment on lines 55 to 61
@@ -91,7 +56,7 @@ func (putOrgLogoRequest) DecodeRequest(_ context.Context, r *http.Request) (any,
if err != nil {
return nil, &fleet.BadRequestError{Message: "failed to read uploaded logo", InternalErr: err}
}
if err := validateOrgLogoBytes(body); err != nil {
if err := fleet.ValidateOrgLogoBytes(body); err != nil {
return nil, err
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

👍 makes sense - moved validation to the service function

Comment on lines +1116 to +1119
ext := orgLogoExtFromContentType(contentType)
fileName := fmt.Sprintf("lib/org_logo/%s%s", mode, ext)
cmd.FilesToWrite[fileName] = string(body)

juan-fdz-hawa
juan-fdz-hawa previously approved these changes May 5, 2026
@nulmete
nulmete merged commit f2b2e23 into main May 5, 2026
63 of 65 checks passed
@nulmete
nulmete deleted the 44333-gitops-ability-to-upload-a branch May 5, 2026 16:18
nulmete added a commit that referenced this pull request May 6, 2026
<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Follow-up to #44390 (BE/FE) and #44550 (GitOps).
Parent story #39016.

## Summary

Accepts `.svg` for organization logo uploads in addition to
PNG/JPEG/WebP, with strict server-side validation since SVGs can carry
scripts.

# 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.

## Testing

- [x] Added/updated automated tests

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



https://github.com/user-attachments/assets/318d320e-ff78-41fe-ad3a-55d6dace8dc0



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

* **New Features**
* Organization logos now accept SVG in addition to PNG, JPEG, and WebP.
  * Stored SVG logos are re-validated when served.

* **Security**
* Server applies strict SVG sanitization to block scripts, unsafe
elements, event handlers, and unsafe URL schemes.
* SVG logo responses include headers to prevent content-type sniffing
and restrict execution.

* **Tests**
* Added tests covering SVG detection, validation, allowed/rejected
cases, and serving 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.

[GitOps] Ability to upload a custom logo to be shown within my Fleet instance served from my Fleet instance

3 participants