Bugfix: gitops policy linked to software package with env var fails to apply (#40944)

This commit is contained in:
Martin Angers 2026-03-04 14:03:34 -05:00 committed by GitHub
parent 4cbc4fdd5e
commit 1fa339298b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 146 additions and 0 deletions

View file

@ -0,0 +1 @@
- Fixed a bug where a policy that links a software to install fails to apply when that software package uses an environment variable in its yaml definition.

View file

@ -155,6 +155,11 @@ func (s *enterpriseIntegrationGitopsTestSuite) TearDownTest() {
require.NoError(t, err)
}
// Delete policies in "No team" (the others are deleted in ts.DS.DeleteTeam above).
mysql.ExecAdhocSQL(t, s.DS, func(q sqlx.ExtContext) error {
_, err := q.ExecContext(ctx, `DELETE FROM policies WHERE team_id = 0;`)
return err
})
// Clean software installers in "No team" (the others are deleted in ts.DS.DeleteTeam above).
mysql.ExecAdhocSQL(t, s.DS, func(q sqlx.ExtContext) error {
_, err := q.ExecContext(ctx, `DELETE FROM software_installers WHERE global_or_team_id = 0;`)
@ -3613,3 +3618,138 @@ reports:
assert.Equal(t, secretPasswordValue, secrets[0].Value,
"secret should be stored as the raw value (not XML-escaped)")
}
// TestGitOpsSoftwareWithEnvVarInstalledByPolicy tests that a software package
// with an environment variable in the URL can be referenced by a policy to be
// installed automatically.
func (s *enterpriseIntegrationGitopsTestSuite) TestGitOpsSoftwareWithEnvVarInstalledByPolicy() {
t := s.T()
ctx := t.Context()
user := s.createGitOpsUser(t)
fleetctlConfig := s.createFleetctlConfig(t, user)
const (
globalTemplate = `
agent_options:
controls:
org_settings:
server_settings:
server_url: $FLEET_URL
org_info:
org_name: Fleet
secrets:
policies:
reports:
`
noTeamTemplate = `name: No team
controls:
policies:
- description: Test policy.
install_software:
package_path: ./lib/ruby.yml
name: Install ruby
platform: linux
query: SELECT 1 FROM file WHERE path = "/usr/local/bin/ruby";
resolution: Install ruby.
software:
packages:
- path: ./lib/ruby.yml
`
packageTemplate = `- url: ${CUSTOM_SOFTWARE_INSTALLER_URL}/ruby.deb`
fleetTemplate = `
controls:
software:
packages:
- path: ./lib/ruby.yml
reports:
policies:
- description: Test policy.
install_software:
package_path: ./lib/ruby.yml
name: Install team ruby
platform: linux
query: SELECT 1 FROM file WHERE path = "/usr/local/bin/ruby";
resolution: Install ruby.
agent_options:
name: %s
settings:
secrets: [{"secret":"enroll_secret"}]
`
)
tempDir := t.TempDir()
globalFile := filepath.Join(tempDir, "global.yml")
err := os.WriteFile(globalFile, []byte(globalTemplate), 0o644) //nolint:gosec
require.NoError(t, err)
noTeamFile := filepath.Join(tempDir, "no-team.yml")
err = os.WriteFile(noTeamFile, []byte(noTeamTemplate), 0o644)
require.NoError(t, err)
fleetName := uuid.NewString()
fleetFile := filepath.Join(tempDir, "fleet.yml")
err = os.WriteFile(fleetFile, fmt.Appendf(nil, fleetTemplate, fleetName), 0o644)
require.NoError(t, err)
pkgFile := filepath.Join(tempDir, "lib", "ruby.yml")
err = os.MkdirAll(filepath.Dir(pkgFile), 0o755)
require.NoError(t, err)
err = os.WriteFile(pkgFile, []byte(packageTemplate), 0o644)
require.NoError(t, err)
// Set the required environment variables
t.Setenv("FLEET_URL", s.Server.URL)
testing_utils.StartSoftwareInstallerServer(t)
// Apply configs, installer URL env var is not defined yet
_, err = fleetctl.RunAppNoChecks([]string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile, "-f", noTeamFile, "-f", fleetFile, "--dry-run"})
require.ErrorContains(t, err, `environment variable "CUSTOM_SOFTWARE_INSTALLER_URL" not set`)
_, err = fleetctl.RunAppNoChecks([]string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile, "-f", noTeamFile, "-f", fleetFile})
require.ErrorContains(t, err, `environment variable "CUSTOM_SOFTWARE_INSTALLER_URL" not set`)
// define the URL env var and apply again, should succeed
t.Setenv("CUSTOM_SOFTWARE_INSTALLER_URL", os.Getenv("SOFTWARE_INSTALLER_URL"))
s.assertDryRunOutput(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile, "-f", noTeamFile, "-f", fleetFile, "--dry-run"}))
s.assertRealRunOutput(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile, "-f", noTeamFile, "-f", fleetFile}))
// no-team has a ruby custom installer
titles, _, _, err := s.DS.ListSoftwareTitles(ctx, fleet.SoftwareTitleListOptions{AvailableForInstall: true, TeamID: ptr.Uint(0)}, fleet.TeamFilter{User: test.UserAdmin})
require.NoError(t, err)
require.Len(t, titles, 1)
require.Equal(t, "ruby", titles[0].Name)
require.NotNil(t, titles[0].SoftwarePackage)
installer, err := s.DS.GetSoftwareInstallerMetadataByTeamAndTitleID(ctx, nil, titles[0].ID, false)
require.NoError(t, err)
tmPols, err := s.DS.ListMergedTeamPolicies(ctx, 0, fleet.ListOptions{})
require.NoError(t, err)
require.Len(t, tmPols, 1)
require.Equal(t, "Install ruby", tmPols[0].Name)
require.NotNil(t, tmPols[0].SoftwareInstallerID)
require.Equal(t, installer.InstallerID, *tmPols[0].SoftwareInstallerID)
// Get the team ID
tm, err := s.DS.TeamByName(ctx, fleetName)
require.NoError(t, err)
// team has a ruby custom installer
titles, _, _, err = s.DS.ListSoftwareTitles(ctx, fleet.SoftwareTitleListOptions{AvailableForInstall: true, TeamID: &tm.ID}, fleet.TeamFilter{User: test.UserAdmin})
require.NoError(t, err)
require.Len(t, titles, 1)
require.Equal(t, "ruby", titles[0].Name)
require.NotNil(t, titles[0].SoftwarePackage)
installer, err = s.DS.GetSoftwareInstallerMetadataByTeamAndTitleID(ctx, &tm.ID, titles[0].ID, false)
require.NoError(t, err)
tmPols, err = s.DS.ListMergedTeamPolicies(ctx, tm.ID, fleet.ListOptions{})
require.NoError(t, err)
require.Len(t, tmPols, 1)
require.Equal(t, "Install team ruby", tmPols[0].Name)
require.NotNil(t, tmPols[0].SoftwareInstallerID)
require.Equal(t, installer.InstallerID, *tmPols[0].SoftwareInstallerID)
}

View file

@ -1329,6 +1329,11 @@ func parsePolicyInstallSoftware(baseDir string, teamName *string, policy *Policy
if err != nil {
return fmt.Errorf("failed to read install_software.package_path file %q: %v", policy.InstallSoftware.PackagePath, err)
}
// Replace $var and ${var} with env values.
fileBytes, err = ExpandEnvBytes(fileBytes)
if err != nil {
return fmt.Errorf("failed to expand environment in file %q: %v", policy.InstallSoftware.PackagePath, err)
}
var policyInstallSoftwareSpec fleet.SoftwarePackageSpec
if err := YamlUnmarshal(fileBytes, &policyInstallSoftwareSpec); err != nil {
// see if the issue is that a package path was passed in that references multiple packages