mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
Backend: Support labels_include_all for installers/apps (#41324)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #40721 # Checklist for submitter If some of the following don't apply, delete the relevant line. - [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 ## 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) - [ ] QA'd all new/changed functionality manually I (Martin) did test `labels_include_all` for FMA, custom installer, IPA and VPP apps, and it seemed to all work great for gitops apply and gitops generate, **except for VPP apps** which seem to have 2 important pre-existing bugs, see https://github.com/fleetdm/fleet/issues/40723#issuecomment-4041780707 ## New Fleet configuration settings - [ ] Verified that the setting is exported via `fleetctl generate-gitops` - [ ] Verified the setting is documented in a separate PR to [the GitOps documentation](https://github.com/fleetdm/fleet/blob/main/docs/Configuration/yaml-files.md#L485) - [ ] 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 --------- Co-authored-by: Jahziel Villasana-Espinoza <jahziel@fleetdm.com>
This commit is contained in:
parent
f8fa379732
commit
ba04887100
48 changed files with 1628 additions and 239 deletions
1
changes/41324-support-labels-include-all-for-installers
Normal file
1
changes/41324-support-labels-include-all-for-installers
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Added support for `labels_include_all` conditional scoping for software installers and apps.
|
||||
|
|
@ -1831,6 +1831,10 @@ func (cmd *GenerateGitopsCommand) generateSoftware(filePath string, teamID uint,
|
|||
labels = softwareTitle.SoftwarePackage.LabelsExcludeAny
|
||||
labelKey = "labels_exclude_any"
|
||||
}
|
||||
if len(softwareTitle.SoftwarePackage.LabelsIncludeAll) > 0 {
|
||||
labels = softwareTitle.SoftwarePackage.LabelsIncludeAll
|
||||
labelKey = "labels_include_all"
|
||||
}
|
||||
if _, exists := setupSoftwareBySoftwareTitle[softwareTitle.ID]; exists {
|
||||
softwareSpec["setup_experience"] = true
|
||||
}
|
||||
|
|
@ -1845,6 +1849,10 @@ func (cmd *GenerateGitopsCommand) generateSoftware(filePath string, teamID uint,
|
|||
labels = softwareTitle.AppStoreApp.LabelsExcludeAny
|
||||
labelKey = "labels_exclude_any"
|
||||
}
|
||||
if len(softwareTitle.AppStoreApp.LabelsIncludeAll) > 0 {
|
||||
labels = softwareTitle.AppStoreApp.LabelsIncludeAll
|
||||
labelKey = "labels_include_all"
|
||||
}
|
||||
if _, exists := setupSoftwareByPlatformAndAppID[platformAndAppID]; exists {
|
||||
softwareSpec["setup_experience"] = true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -451,9 +451,15 @@ func (MockClient) GetSoftwareTitleByID(ID uint, teamID *uint) (*fleet.SoftwareTi
|
|||
return &fleet.SoftwareTitle{
|
||||
ID: 6,
|
||||
AppStoreApp: &fleet.VPPAppStoreApp{
|
||||
VPPAppID: fleet.VPPAppID{AdamID: "com.example.setup-experience-software", Platform: fleet.AndroidPlatform},
|
||||
LabelsExcludeAny: []fleet.SoftwareScopeLabel{},
|
||||
SelfService: true,
|
||||
VPPAppID: fleet.VPPAppID{AdamID: "com.example.setup-experience-software", Platform: fleet.AndroidPlatform},
|
||||
LabelsIncludeAll: []fleet.SoftwareScopeLabel{
|
||||
{
|
||||
LabelName: "Label C",
|
||||
}, {
|
||||
LabelName: "Label D",
|
||||
},
|
||||
},
|
||||
SelfService: true,
|
||||
},
|
||||
IconUrl: ptr.String("/api/icon3.png"),
|
||||
}, nil
|
||||
|
|
@ -466,6 +472,13 @@ func (MockClient) GetSoftwareTitleByID(ID uint, teamID *uint) (*fleet.SoftwareTi
|
|||
AppStoreApp: &fleet.VPPAppStoreApp{
|
||||
VPPAppID: fleet.VPPAppID{AdamID: "com.example.ios-auto-update", Platform: fleet.IOSPlatform},
|
||||
SelfService: false,
|
||||
LabelsIncludeAll: []fleet.SoftwareScopeLabel{
|
||||
{
|
||||
LabelName: "Label C",
|
||||
}, {
|
||||
LabelName: "Label D",
|
||||
},
|
||||
},
|
||||
},
|
||||
IconUrl: ptr.String("/api/icon4.png"),
|
||||
SoftwareAutoUpdateConfig: fleet.SoftwareAutoUpdateConfig{
|
||||
|
|
@ -502,6 +515,14 @@ func (MockClient) GetSoftwareTitleByID(ID uint, teamID *uint) (*fleet.SoftwareTi
|
|||
ID: 9,
|
||||
Name: "My Windows FMA",
|
||||
SoftwarePackage: &fleet.SoftwareInstaller{
|
||||
LabelsIncludeAll: []fleet.SoftwareScopeLabel{
|
||||
{
|
||||
LabelName: "Label A",
|
||||
},
|
||||
{
|
||||
LabelName: "Label B",
|
||||
},
|
||||
},
|
||||
InstallScript: "install",
|
||||
UninstallScript: "uninstall",
|
||||
SelfService: true,
|
||||
|
|
|
|||
|
|
@ -763,10 +763,16 @@ func getLabelUsage(config *spec.GitOps) (map[string][]LabelUsage, error) {
|
|||
}
|
||||
if len(softwarePackage.LabelsExcludeAny) > 0 {
|
||||
if len(labels) > 0 {
|
||||
return nil, fmt.Errorf("Software package '%s' has multiple label keys; please choose one of `labels_include_any`, `labels_exclude_any`.", softwarePackage.URL)
|
||||
return nil, fmt.Errorf("Software package '%s' has multiple label keys; please choose one of `labels_include_all`, `labels_include_any`, `labels_exclude_any`.", softwarePackage.URL)
|
||||
}
|
||||
labels = softwarePackage.LabelsExcludeAny
|
||||
}
|
||||
if len(softwarePackage.LabelsIncludeAll) > 0 {
|
||||
if len(labels) > 0 {
|
||||
return nil, fmt.Errorf("Software package '%s' has multiple label keys; please choose one of `labels_include_all`, `labels_include_any`, `labels_exclude_any`.", softwarePackage.URL)
|
||||
}
|
||||
labels = softwarePackage.LabelsIncludeAll
|
||||
}
|
||||
updateLabelUsage(labels, softwarePackage.URL, "Software Package", result)
|
||||
}
|
||||
|
||||
|
|
@ -778,10 +784,16 @@ func getLabelUsage(config *spec.GitOps) (map[string][]LabelUsage, error) {
|
|||
}
|
||||
if len(vppApp.LabelsExcludeAny) > 0 {
|
||||
if len(labels) > 0 {
|
||||
return nil, fmt.Errorf("App Store App '%s' has multiple label keys; please choose one of `labels_include_any`, `labels_exclude_any`.", vppApp.AppStoreID)
|
||||
return nil, fmt.Errorf("App Store App '%s' has multiple label keys; please choose one of `labels_include_all`, `labels_include_any`, `labels_exclude_any`.", vppApp.AppStoreID)
|
||||
}
|
||||
labels = vppApp.LabelsExcludeAny
|
||||
}
|
||||
if len(vppApp.LabelsIncludeAll) > 0 {
|
||||
if len(labels) > 0 {
|
||||
return nil, fmt.Errorf("App Store App '%s' has multiple label keys; please choose one of `labels_include_all`, `labels_include_any`, `labels_exclude_any`.", vppApp.AppStoreID)
|
||||
}
|
||||
labels = vppApp.LabelsIncludeAll
|
||||
}
|
||||
updateLabelUsage(labels, vppApp.AppStoreID, "App Store App", result)
|
||||
}
|
||||
|
||||
|
|
@ -792,10 +804,16 @@ func getLabelUsage(config *spec.GitOps) (map[string][]LabelUsage, error) {
|
|||
}
|
||||
if len(maintainedApp.LabelsExcludeAny) > 0 {
|
||||
if len(labels) > 0 {
|
||||
return nil, fmt.Errorf("Fleet Maintained App '%s' has multiple label keys; please choose one of `labels_include_any`, `labels_exclude_any`.", maintainedApp.Slug)
|
||||
return nil, fmt.Errorf("Fleet Maintained App '%s' has multiple label keys; please choose one of `labels_include_all`, `labels_include_any`, `labels_exclude_any`.", maintainedApp.Slug)
|
||||
}
|
||||
labels = maintainedApp.LabelsExcludeAny
|
||||
}
|
||||
if len(maintainedApp.LabelsIncludeAll) > 0 {
|
||||
if len(labels) > 0 {
|
||||
return nil, fmt.Errorf("Fleet Maintained App '%s' has multiple label keys; please choose one of `labels_include_all`, `labels_include_any`, `labels_exclude_any`.", maintainedApp.Slug)
|
||||
}
|
||||
labels = maintainedApp.LabelsIncludeAll
|
||||
}
|
||||
updateLabelUsage(labels, maintainedApp.Slug, "Fleet Maintained App", result)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,9 @@ fleet_maintained_apps:
|
|||
path: ../lib/some-team/queries/my-fma-darwin-preinstallquery.yml
|
||||
self_service: true
|
||||
- slug: fma2/windows
|
||||
labels_include_all:
|
||||
- Label A
|
||||
- Label B
|
||||
self_service: true
|
||||
app_store_apps:
|
||||
- app_store_id: com.example.team-software
|
||||
|
|
@ -42,10 +45,16 @@ app_store_apps:
|
|||
self_service: true
|
||||
platform: darwin
|
||||
- app_store_id: com.example.setup-experience-software
|
||||
labels_include_all:
|
||||
- Label C
|
||||
- Label D
|
||||
platform: android
|
||||
self_service: true
|
||||
setup_experience: true
|
||||
- app_store_id: com.example.ios-auto-update
|
||||
labels_include_all:
|
||||
- Label C
|
||||
- Label D
|
||||
auto_update_enabled: true
|
||||
auto_update_window_start: "01:00"
|
||||
auto_update_window_end: "03:00"
|
||||
|
|
|
|||
|
|
@ -113,6 +113,9 @@ software:
|
|||
- app_store_id: com.example.setup-experience-software
|
||||
icon:
|
||||
path: "../lib/team-a-👍/icons/my-setup-experience-app-android-icon.png"
|
||||
labels_include_all:
|
||||
- Label C
|
||||
- Label D
|
||||
platform: android
|
||||
self_service: true
|
||||
setup_experience: true
|
||||
|
|
@ -122,6 +125,9 @@ software:
|
|||
auto_update_window_start: "01:00"
|
||||
icon:
|
||||
path: "../lib/team-a-👍/icons/my-ios-auto-update-app-ios-icon.png"
|
||||
labels_include_all:
|
||||
- Label C
|
||||
- Label D
|
||||
platform: ios
|
||||
fleet_maintained_apps:
|
||||
- categories:
|
||||
|
|
@ -140,6 +146,9 @@ software:
|
|||
slug: fma1/darwin
|
||||
- icon:
|
||||
path: "../lib/team-a-👍/icons/my-windows-fma-windows-icon.png"
|
||||
labels_include_all:
|
||||
- Label A
|
||||
- Label B
|
||||
self_service: true
|
||||
slug: fma2/windows
|
||||
packages:
|
||||
|
|
|
|||
19
cmd/fleetctl/fleetctl/testdata/gitops/no_team_software_installer_valid_include_all.yml
vendored
Normal file
19
cmd/fleetctl/fleetctl/testdata/gitops/no_team_software_installer_valid_include_all.yml
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
name: No team
|
||||
controls:
|
||||
policies:
|
||||
software:
|
||||
packages:
|
||||
- url: ${SOFTWARE_INSTALLER_URL}/ruby.deb
|
||||
install_script:
|
||||
path: lib/install_ruby.sh
|
||||
pre_install_query:
|
||||
path: lib/query_ruby.yml
|
||||
post_install_script:
|
||||
path: lib/post_install_ruby.sh
|
||||
uninstall_script:
|
||||
path: lib/uninstall_ruby.sh
|
||||
labels_include_all:
|
||||
- a
|
||||
- b
|
||||
- url: ${SOFTWARE_INSTALLER_URL}/other.deb
|
||||
self_service: true
|
||||
28
cmd/fleetctl/fleetctl/testdata/gitops/team_software_installer_valid_include_all.yml
vendored
Normal file
28
cmd/fleetctl/fleetctl/testdata/gitops/team_software_installer_valid_include_all.yml
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
name: "${TEST_TEAM_NAME}"
|
||||
team_settings:
|
||||
secrets:
|
||||
- secret: "ABC"
|
||||
features:
|
||||
enable_host_users: true
|
||||
enable_software_inventory: true
|
||||
host_expiry_settings:
|
||||
host_expiry_enabled: true
|
||||
host_expiry_window: 30
|
||||
agent_options:
|
||||
controls:
|
||||
policies:
|
||||
queries:
|
||||
software:
|
||||
packages:
|
||||
- url: ${SOFTWARE_INSTALLER_URL}/ruby.deb
|
||||
install_script:
|
||||
path: lib/install_ruby.sh
|
||||
pre_install_query:
|
||||
path: lib/query_ruby_apply.yml
|
||||
post_install_script:
|
||||
path: lib/post_install_ruby.sh
|
||||
labels_include_all:
|
||||
- a
|
||||
- b
|
||||
- url: ${SOFTWARE_INSTALLER_URL}/other.deb
|
||||
self_service: true
|
||||
20
cmd/fleetctl/fleetctl/testdata/gitops/team_vpp_valid_app_labels_include_all.yml
vendored
Normal file
20
cmd/fleetctl/fleetctl/testdata/gitops/team_vpp_valid_app_labels_include_all.yml
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
name: "${TEST_TEAM_NAME}"
|
||||
team_settings:
|
||||
secrets:
|
||||
- secret: "ABC"
|
||||
features:
|
||||
enable_host_users: true
|
||||
enable_software_inventory: true
|
||||
host_expiry_settings:
|
||||
host_expiry_enabled: true
|
||||
host_expiry_window: 30
|
||||
agent_options:
|
||||
controls:
|
||||
policies:
|
||||
queries:
|
||||
software:
|
||||
app_store_apps:
|
||||
- app_store_id: "1"
|
||||
labels_include_all:
|
||||
- "label 1"
|
||||
- "label 2"
|
||||
|
|
@ -329,6 +329,10 @@ team_settings:
|
|||
withLabelsExcludeAny = `
|
||||
labels_exclude_any:
|
||||
- Label1
|
||||
`
|
||||
withLabelsIncludeAll = `
|
||||
labels_include_all:
|
||||
- Label1
|
||||
`
|
||||
)
|
||||
|
||||
|
|
@ -392,6 +396,34 @@ team_settings:
|
|||
require.Len(t, meta.LabelsExcludeAny, 1)
|
||||
require.Equal(t, "Label1", meta.LabelsExcludeAny[0].LabelName)
|
||||
|
||||
// switch both to labels_include_all
|
||||
err = os.WriteFile(noTeamFilePath, fmt.Appendf(nil, noTeamTemplate, withLabelsIncludeAll), 0o644)
|
||||
require.NoError(t, err)
|
||||
err = os.WriteFile(teamFile.Name(), fmt.Appendf(nil, teamTemplate, withLabelsIncludeAll, teamName), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Apply configs
|
||||
s.assertDryRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t,
|
||||
[]string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", noTeamFilePath, "-f", teamFile.Name(), "--dry-run"}), true)
|
||||
s.assertRealRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t,
|
||||
[]string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", noTeamFilePath, "-f", teamFile.Name()}), true)
|
||||
|
||||
// the installer is now scoped by labels_include_all for no team
|
||||
meta, err = s.DS.GetSoftwareInstallerMetadataByTeamAndTitleID(ctx, nil, noTeamTitleID, false)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, meta.LabelsIncludeAny)
|
||||
require.Empty(t, meta.LabelsExcludeAny)
|
||||
require.Len(t, meta.LabelsIncludeAll, 1)
|
||||
require.Equal(t, "Label1", meta.LabelsIncludeAll[0].LabelName)
|
||||
|
||||
// the installer is now scoped by labels_include_all for team
|
||||
meta, err = s.DS.GetSoftwareInstallerMetadataByTeamAndTitleID(ctx, &team.ID, teamTitleID, false)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, meta.LabelsIncludeAny)
|
||||
require.Empty(t, meta.LabelsExcludeAny)
|
||||
require.Len(t, meta.LabelsIncludeAll, 1)
|
||||
require.Equal(t, "Label1", meta.LabelsIncludeAll[0].LabelName)
|
||||
|
||||
// remove the label conditions
|
||||
err = os.WriteFile(noTeamFilePath, fmt.Appendf(nil, noTeamTemplate, emptyLabelsIncludeAny), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
|
@ -411,6 +443,7 @@ team_settings:
|
|||
require.Equal(t, noTeamTitleID, *meta.TitleID)
|
||||
require.Len(t, meta.LabelsExcludeAny, 0)
|
||||
require.Len(t, meta.LabelsIncludeAny, 0)
|
||||
require.Len(t, meta.LabelsIncludeAll, 0)
|
||||
|
||||
meta, err = s.DS.GetSoftwareInstallerMetadataByTeamAndTitleID(ctx, &team.ID, teamTitleID, false)
|
||||
require.NoError(t, err)
|
||||
|
|
@ -418,6 +451,7 @@ team_settings:
|
|||
require.Equal(t, teamTitleID, *meta.TitleID)
|
||||
require.Len(t, meta.LabelsExcludeAny, 0)
|
||||
require.Len(t, meta.LabelsIncludeAny, 0)
|
||||
require.Len(t, meta.LabelsIncludeAll, 0)
|
||||
}
|
||||
|
||||
func (s *enterpriseIntegrationGitopsTestSuite) TestNoTeamWebhookSettingsDeprecated() {
|
||||
|
|
@ -944,6 +978,7 @@ team_settings:
|
|||
require.True(t, meta.SelfService)
|
||||
require.Empty(t, meta.LabelsExcludeAny)
|
||||
require.Empty(t, meta.LabelsIncludeAny)
|
||||
require.Empty(t, meta.LabelsIncludeAll)
|
||||
}
|
||||
require.ElementsMatch(t, []string{"ios_apps", "ipados_apps"}, sources)
|
||||
require.ElementsMatch(t, []string{"ios", "ipados"}, platforms)
|
||||
|
|
@ -1000,6 +1035,7 @@ team_settings:
|
|||
require.NoError(t, err)
|
||||
require.False(t, meta.SelfService)
|
||||
require.Empty(t, meta.LabelsExcludeAny)
|
||||
require.Empty(t, meta.LabelsIncludeAll)
|
||||
require.Len(t, meta.LabelsIncludeAny, 1)
|
||||
require.Equal(t, lbl.ID, meta.LabelsIncludeAny[0].LabelID)
|
||||
require.Empty(t, meta.InstallScript) // install script should be ignored for ipa apps
|
||||
|
|
@ -1039,6 +1075,7 @@ team_settings:
|
|||
require.False(t, meta.SelfService)
|
||||
require.Empty(t, meta.LabelsExcludeAny)
|
||||
require.Empty(t, meta.LabelsIncludeAny)
|
||||
require.Empty(t, meta.LabelsIncludeAll)
|
||||
}
|
||||
require.ElementsMatch(t, []string{"ios_apps", "ipados_apps"}, sources)
|
||||
require.ElementsMatch(t, []string{"ios", "ipados"}, platforms)
|
||||
|
|
|
|||
|
|
@ -916,6 +916,10 @@ software:
|
|||
`
|
||||
emptyLabelsIncludeAny = `
|
||||
labels_include_any:
|
||||
`
|
||||
withLabelsIncludeAll = `
|
||||
labels_include_all:
|
||||
- Label1
|
||||
`
|
||||
teamTemplate = `
|
||||
controls:
|
||||
|
|
@ -996,6 +1000,34 @@ settings:
|
|||
require.Len(t, meta.LabelsExcludeAny, 1)
|
||||
require.Equal(t, "Label1", meta.LabelsExcludeAny[0].LabelName)
|
||||
|
||||
// switch both to labels_include_all
|
||||
err = os.WriteFile(noTeamFilePath, fmt.Appendf(nil, noTeamTemplate, withLabelsIncludeAll), 0o644)
|
||||
require.NoError(t, err)
|
||||
err = os.WriteFile(teamFile.Name(), fmt.Appendf(nil, teamTemplate, withLabelsIncludeAll, teamName), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Apply configs
|
||||
s.assertDryRunOutput(t, fleetctl.RunAppForTest(t,
|
||||
[]string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", noTeamFilePath, "-f", teamFile.Name(), "--dry-run"}))
|
||||
s.assertRealRunOutput(t, fleetctl.RunAppForTest(t,
|
||||
[]string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", noTeamFilePath, "-f", teamFile.Name()}))
|
||||
|
||||
// the installer is now scoped by labels_include_all for no team
|
||||
meta, err = s.DS.GetSoftwareInstallerMetadataByTeamAndTitleID(ctx, nil, noTeamTitleID, false)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, meta.LabelsIncludeAny)
|
||||
require.Empty(t, meta.LabelsExcludeAny)
|
||||
require.Len(t, meta.LabelsIncludeAll, 1)
|
||||
require.Equal(t, "Label1", meta.LabelsIncludeAll[0].LabelName)
|
||||
|
||||
// the installer is now scoped by labels_include_all for team
|
||||
meta, err = s.DS.GetSoftwareInstallerMetadataByTeamAndTitleID(ctx, &team.ID, teamTitleID, false)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, meta.LabelsIncludeAny)
|
||||
require.Empty(t, meta.LabelsExcludeAny)
|
||||
require.Len(t, meta.LabelsIncludeAll, 1)
|
||||
require.Equal(t, "Label1", meta.LabelsIncludeAll[0].LabelName)
|
||||
|
||||
// remove the label conditions
|
||||
err = os.WriteFile(noTeamFilePath, []byte(fmt.Sprintf(noTeamTemplate, emptyLabelsIncludeAny)), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
|
@ -1015,6 +1047,7 @@ settings:
|
|||
require.Equal(t, noTeamTitleID, *meta.TitleID)
|
||||
require.Len(t, meta.LabelsExcludeAny, 0)
|
||||
require.Len(t, meta.LabelsIncludeAny, 0)
|
||||
require.Len(t, meta.LabelsIncludeAll, 0)
|
||||
|
||||
meta, err = s.DS.GetSoftwareInstallerMetadataByTeamAndTitleID(ctx, &team.ID, teamTitleID, false)
|
||||
require.NoError(t, err)
|
||||
|
|
@ -1022,6 +1055,7 @@ settings:
|
|||
require.Equal(t, teamTitleID, *meta.TitleID)
|
||||
require.Len(t, meta.LabelsExcludeAny, 0)
|
||||
require.Len(t, meta.LabelsIncludeAny, 0)
|
||||
require.Len(t, meta.LabelsIncludeAll, 0)
|
||||
}
|
||||
|
||||
func (s *enterpriseIntegrationGitopsTestSuite) TestDeletingNoTeamYAML() {
|
||||
|
|
@ -2191,6 +2225,7 @@ settings:
|
|||
require.True(t, meta.SelfService)
|
||||
require.Empty(t, meta.LabelsExcludeAny)
|
||||
require.Empty(t, meta.LabelsIncludeAny)
|
||||
require.Empty(t, meta.LabelsIncludeAll)
|
||||
}
|
||||
require.ElementsMatch(t, []string{"ios_apps", "ipados_apps"}, sources)
|
||||
require.ElementsMatch(t, []string{"ios", "ipados"}, platforms)
|
||||
|
|
@ -2247,6 +2282,7 @@ settings:
|
|||
require.NoError(t, err)
|
||||
require.False(t, meta.SelfService)
|
||||
require.Empty(t, meta.LabelsExcludeAny)
|
||||
require.Empty(t, meta.LabelsIncludeAll)
|
||||
require.Len(t, meta.LabelsIncludeAny, 1)
|
||||
require.Equal(t, lbl.ID, meta.LabelsIncludeAny[0].LabelID)
|
||||
require.Empty(t, meta.InstallScript) // install script should be ignored for ipa apps
|
||||
|
|
@ -2286,6 +2322,7 @@ settings:
|
|||
require.False(t, meta.SelfService)
|
||||
require.Empty(t, meta.LabelsExcludeAny)
|
||||
require.Empty(t, meta.LabelsIncludeAny)
|
||||
require.Empty(t, meta.LabelsIncludeAll)
|
||||
}
|
||||
require.ElementsMatch(t, []string{"ios_apps", "ipados_apps"}, sources)
|
||||
require.ElementsMatch(t, []string{"ios", "ipados"}, platforms)
|
||||
|
|
@ -3999,3 +4036,204 @@ name: %s
|
|||
require.NoError(t, err)
|
||||
require.Len(t, titles, 0)
|
||||
}
|
||||
|
||||
// TestFMALabelsIncludeAll tests that labels_include_all is correctly applied and
|
||||
// cleared for Fleet Maintained Apps via gitops, for both no-team and a specific team.
|
||||
func (s *enterpriseIntegrationGitopsTestSuite) TestFMALabelsIncludeAll() {
|
||||
t := s.T()
|
||||
ctx := context.Background()
|
||||
|
||||
user := s.createGitOpsUser(t)
|
||||
fleetctlConfig := s.createFleetctlConfig(t, user)
|
||||
|
||||
lbl, err := s.DS.NewLabel(ctx, &fleet.Label{Name: "Label1" + t.Name(), Query: "SELECT 1"})
|
||||
require.NoError(t, err)
|
||||
require.NotZero(t, lbl.ID)
|
||||
|
||||
slug := fmt.Sprintf("foo%s/darwin", t.Name())
|
||||
|
||||
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:
|
||||
software:
|
||||
fleet_maintained_apps:
|
||||
- slug: %s
|
||||
%s
|
||||
`
|
||||
teamTemplate = `
|
||||
controls:
|
||||
software:
|
||||
fleet_maintained_apps:
|
||||
- slug: %s
|
||||
%s
|
||||
reports:
|
||||
policies:
|
||||
agent_options:
|
||||
name: %s
|
||||
settings:
|
||||
secrets: [{"secret":"enroll_secret"}]
|
||||
`
|
||||
)
|
||||
const noLabels = ""
|
||||
|
||||
withLabelsIncludeAll := fmt.Sprintf(`
|
||||
labels_include_all:
|
||||
- %s
|
||||
`, lbl.Name)
|
||||
|
||||
globalFile, err := os.CreateTemp(t.TempDir(), "*.yml")
|
||||
require.NoError(t, err)
|
||||
_, err = globalFile.WriteString(globalTemplate)
|
||||
require.NoError(t, err)
|
||||
err = globalFile.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
noTeamFile, err := os.CreateTemp(t.TempDir(), "*.yml")
|
||||
require.NoError(t, err)
|
||||
_, err = fmt.Fprintf(noTeamFile, noTeamTemplate, slug, withLabelsIncludeAll)
|
||||
require.NoError(t, err)
|
||||
err = noTeamFile.Close()
|
||||
require.NoError(t, err)
|
||||
noTeamFilePath := filepath.Join(filepath.Dir(noTeamFile.Name()), "no-team.yml")
|
||||
err = os.Rename(noTeamFile.Name(), noTeamFilePath)
|
||||
require.NoError(t, err)
|
||||
|
||||
teamName := uuid.NewString()
|
||||
teamFile, err := os.CreateTemp(t.TempDir(), "*.yml")
|
||||
require.NoError(t, err)
|
||||
_, err = fmt.Fprintf(teamFile, teamTemplate, slug, withLabelsIncludeAll, teamName)
|
||||
require.NoError(t, err)
|
||||
err = teamFile.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Set the required environment variables
|
||||
t.Setenv("FLEET_URL", s.Server.URL)
|
||||
testing_utils.StartSoftwareInstallerServer(t)
|
||||
|
||||
// Mock server to serve FMA installer bytes
|
||||
installerServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write([]byte("foo"))
|
||||
}))
|
||||
defer installerServer.Close()
|
||||
|
||||
// Mock server to serve the FMA manifest
|
||||
manifestServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
versions := []*ma.FMAManifestApp{
|
||||
{
|
||||
Version: "1.0",
|
||||
Queries: ma.FMAQueries{
|
||||
Exists: "SELECT 1 FROM osquery_info;",
|
||||
},
|
||||
InstallerURL: installerServer.URL + "/foo.pkg",
|
||||
InstallScriptRef: "fooscript",
|
||||
UninstallScriptRef: "fooscript",
|
||||
SHA256: "no_check",
|
||||
},
|
||||
}
|
||||
manifest := ma.FMAManifestFile{
|
||||
Versions: versions,
|
||||
Refs: map[string]string{"fooscript": "echo hello"},
|
||||
}
|
||||
err := json.NewEncoder(w).Encode(manifest)
|
||||
require.NoError(t, err)
|
||||
}))
|
||||
defer manifestServer.Close()
|
||||
|
||||
dev_mode.SetOverride("FLEET_DEV_MAINTAINED_APPS_BASE_URL", manifestServer.URL, t)
|
||||
|
||||
// Insert the FMA record so gitops can resolve the slug
|
||||
mysql.ExecAdhocSQL(t, s.DS, func(q sqlx.ExtContext) error {
|
||||
_, err := q.ExecContext(ctx,
|
||||
`INSERT INTO fleet_maintained_apps (name, slug, platform, unique_identifier)
|
||||
VALUES (?, ?, 'darwin', ?)`, "foo"+t.Name(), slug, `com.example.foo`+t.Name())
|
||||
return err
|
||||
})
|
||||
|
||||
// Apply configs — dry-run first, then real run
|
||||
s.assertDryRunOutput(t, fleetctl.RunAppForTest(t, []string{
|
||||
"gitops", "--config", fleetctlConfig.Name(),
|
||||
"-f", globalFile.Name(), "-f", noTeamFilePath, "-f", teamFile.Name(),
|
||||
"--dry-run",
|
||||
}))
|
||||
s.assertRealRunOutput(t, fleetctl.RunAppForTest(t, []string{
|
||||
"gitops", "--config", fleetctlConfig.Name(),
|
||||
"-f", globalFile.Name(), "-f", noTeamFilePath, "-f", teamFile.Name(),
|
||||
}))
|
||||
|
||||
// Retrieve the team so we have its ID
|
||||
team, err := s.DS.TeamByName(ctx, teamName)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Locate the FMA installer for no-team and assert labels_include_all is set
|
||||
noTeamTitles, _, _, 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, noTeamTitles, 1)
|
||||
noTeamTitleID := noTeamTitles[0].ID
|
||||
|
||||
noTeamMeta, err := s.DS.GetSoftwareInstallerMetadataByTeamAndTitleID(ctx, nil, noTeamTitleID, false)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, noTeamMeta.LabelsIncludeAny)
|
||||
require.Empty(t, noTeamMeta.LabelsExcludeAny)
|
||||
require.Len(t, noTeamMeta.LabelsIncludeAll, 1)
|
||||
require.Equal(t, lbl.Name, noTeamMeta.LabelsIncludeAll[0].LabelName)
|
||||
|
||||
// Locate the FMA installer for the team and assert labels_include_all is set
|
||||
teamTitles, _, _, err := s.DS.ListSoftwareTitles(ctx,
|
||||
fleet.SoftwareTitleListOptions{TeamID: &team.ID},
|
||||
fleet.TeamFilter{User: test.UserAdmin})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, teamTitles, 1)
|
||||
teamTitleID := teamTitles[0].ID
|
||||
|
||||
teamMeta, err := s.DS.GetSoftwareInstallerMetadataByTeamAndTitleID(ctx, &team.ID, teamTitleID, false)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, teamMeta.LabelsIncludeAny)
|
||||
require.Empty(t, teamMeta.LabelsExcludeAny)
|
||||
require.Len(t, teamMeta.LabelsIncludeAll, 1)
|
||||
require.Equal(t, lbl.Name, teamMeta.LabelsIncludeAll[0].LabelName)
|
||||
|
||||
// Now re-apply without labels_include_all and confirm they are cleared
|
||||
err = os.WriteFile(noTeamFilePath, fmt.Appendf(nil, noTeamTemplate, slug, noLabels), 0o644)
|
||||
require.NoError(t, err)
|
||||
err = os.WriteFile(teamFile.Name(), fmt.Appendf(nil, teamTemplate, slug, noLabels, teamName), 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
s.assertDryRunOutput(t, fleetctl.RunAppForTest(t, []string{
|
||||
"gitops", "--config", fleetctlConfig.Name(),
|
||||
"-f", globalFile.Name(), "-f", noTeamFilePath, "-f", teamFile.Name(),
|
||||
"--dry-run",
|
||||
}))
|
||||
s.assertRealRunOutput(t, fleetctl.RunAppForTest(t, []string{
|
||||
"gitops", "--config", fleetctlConfig.Name(),
|
||||
"-f", globalFile.Name(), "-f", noTeamFilePath, "-f", teamFile.Name(),
|
||||
}))
|
||||
|
||||
// Labels should now be empty for no-team
|
||||
noTeamMeta, err = s.DS.GetSoftwareInstallerMetadataByTeamAndTitleID(ctx, nil, noTeamTitleID, false)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, noTeamMeta.LabelsIncludeAny)
|
||||
require.Empty(t, noTeamMeta.LabelsExcludeAny)
|
||||
require.Empty(t, noTeamMeta.LabelsIncludeAll)
|
||||
|
||||
// Labels should now be empty for the team
|
||||
teamMeta, err = s.DS.GetSoftwareInstallerMetadataByTeamAndTitleID(ctx, &team.ID, teamTitleID, false)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, teamMeta.LabelsIncludeAny)
|
||||
require.Empty(t, teamMeta.LabelsExcludeAny)
|
||||
require.Empty(t, teamMeta.LabelsIncludeAll)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,10 +54,11 @@ func TestGitOpsTeamSoftwareInstallers(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"testdata/gitops/team_software_installer_invalid_both_include_exclude.yml",
|
||||
`only one of "labels_exclude_any" or "labels_include_any" can be specified`,
|
||||
`only one of "labels_include_all", "labels_exclude_any" or "labels_include_any" can be specified`,
|
||||
},
|
||||
{"testdata/gitops/team_software_installer_valid_include.yml", ""},
|
||||
{"testdata/gitops/team_software_installer_valid_exclude.yml", ""},
|
||||
{"testdata/gitops/team_software_installer_valid_include_all.yml", ""},
|
||||
{
|
||||
"testdata/gitops/team_software_installer_invalid_unknown_label.yml",
|
||||
"Please create the missing labels, or update your settings to not refer to these labels.",
|
||||
|
|
@ -360,10 +361,11 @@ func TestGitOpsNoTeamSoftwareInstallers(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"testdata/gitops/no_team_software_installer_invalid_both_include_exclude.yml",
|
||||
`only one of "labels_exclude_any" or "labels_include_any" can be specified`,
|
||||
`only one of "labels_include_all", "labels_exclude_any" or "labels_include_any" can be specified`,
|
||||
},
|
||||
{"testdata/gitops/no_team_software_installer_valid_include.yml", ""},
|
||||
{"testdata/gitops/no_team_software_installer_valid_exclude.yml", ""},
|
||||
{"testdata/gitops/no_team_software_installer_valid_include_all.yml", ""},
|
||||
{
|
||||
"testdata/gitops/no_team_software_installer_invalid_unknown_label.yml",
|
||||
"Please create the missing labels, or update your settings to not refer to these labels.",
|
||||
|
|
@ -505,6 +507,10 @@ func TestGitOpsTeamVPPApps(t *testing.T) {
|
|||
"testdata/gitops/team_vpp_valid_app_labels_include_any.yml", "", time.Now().Add(24 * time.Hour),
|
||||
map[string]uint{"label 1": 1, "label 2": 2},
|
||||
},
|
||||
{
|
||||
"testdata/gitops/team_vpp_valid_app_labels_include_all.yml", "", time.Now().Add(24 * time.Hour),
|
||||
map[string]uint{"label 1": 1, "label 2": 2},
|
||||
},
|
||||
{
|
||||
"testdata/gitops/team_vpp_invalid_app_labels_exclude_any.yml",
|
||||
"Please create the missing labels, or update your settings to not refer to these labels.", time.Now().Add(24 * time.Hour),
|
||||
|
|
@ -517,7 +523,7 @@ func TestGitOpsTeamVPPApps(t *testing.T) {
|
|||
},
|
||||
{
|
||||
"testdata/gitops/team_vpp_invalid_app_labels_both.yml",
|
||||
`only one of "labels_exclude_any" or "labels_include_any" can be specified for app store app`, time.Now().Add(24 * time.Hour),
|
||||
`only one of "labels_include_all", "labels_exclude_any" or "labels_include_any" can be specified for app store app`, time.Now().Add(24 * time.Hour),
|
||||
map[string]uint{},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ func (svc *Service) updateInHouseAppInstaller(ctx context.Context, payload *flee
|
|||
|
||||
payload.InstallerID = existingInstaller.InstallerID
|
||||
|
||||
_, validatedLabels, err := ValidateSoftwareLabelsForUpdate(ctx, svc, existingInstaller, payload.LabelsIncludeAny, payload.LabelsExcludeAny)
|
||||
_, validatedLabels, err := ValidateSoftwareLabelsForUpdate(ctx, svc, existingInstaller, payload.LabelsIncludeAny, payload.LabelsExcludeAny, payload.LabelsIncludeAll)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "validating software labels for update")
|
||||
}
|
||||
|
|
@ -127,13 +127,14 @@ func (svc *Service) updateInHouseAppInstaller(ctx context.Context, payload *flee
|
|||
|
||||
// now that the payload has been updated with any patches, we can set the
|
||||
// final fields of the activity
|
||||
actLabelsIncl, actLabelsExcl := activitySoftwareLabelsFromSoftwareScopeLabels(
|
||||
existingInstaller.LabelsIncludeAny, existingInstaller.LabelsExcludeAny)
|
||||
actLabelsInclAny, actLabelsExclAny, actLabelsInclAll := activitySoftwareLabelsFromSoftwareScopeLabels(
|
||||
existingInstaller.LabelsIncludeAny, existingInstaller.LabelsExcludeAny, existingInstaller.LabelsIncludeAll)
|
||||
if payload.ValidatedLabels != nil {
|
||||
actLabelsIncl, actLabelsExcl = activitySoftwareLabelsFromValidatedLabels(payload.ValidatedLabels)
|
||||
actLabelsInclAny, actLabelsExclAny, actLabelsInclAll = activitySoftwareLabelsFromValidatedLabels(payload.ValidatedLabels)
|
||||
}
|
||||
activity.LabelsIncludeAny = actLabelsIncl
|
||||
activity.LabelsExcludeAny = actLabelsExcl
|
||||
activity.LabelsIncludeAny = actLabelsInclAny
|
||||
activity.LabelsExcludeAny = actLabelsExclAny
|
||||
activity.LabelsIncludeAll = actLabelsInclAll
|
||||
if err := svc.NewActivity(ctx, vc.User, activity); err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "creating activity for edited in house app")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ func (svc *Service) AddFleetMaintainedApp(
|
|||
appID uint,
|
||||
installScript, preInstallQuery, postInstallScript, uninstallScript string,
|
||||
selfService bool, automaticInstall bool,
|
||||
labelsIncludeAny, labelsExcludeAny []string,
|
||||
labelsIncludeAny, labelsExcludeAny, labelsIncludeAll []string,
|
||||
) (titleID uint, err error) {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.SoftwareInstaller{TeamID: teamID}, fleet.ActionWrite); err != nil {
|
||||
return 0, err
|
||||
|
|
@ -38,7 +38,7 @@ func (svc *Service) AddFleetMaintainedApp(
|
|||
}
|
||||
|
||||
// validate labels before we do anything else
|
||||
validatedLabels, err := ValidateSoftwareLabels(ctx, svc, teamID, labelsIncludeAny, labelsExcludeAny)
|
||||
validatedLabels, err := ValidateSoftwareLabels(ctx, svc, teamID, labelsIncludeAny, labelsExcludeAny, labelsIncludeAll)
|
||||
if err != nil {
|
||||
return 0, ctxerr.Wrap(ctx, err, "validating software labels")
|
||||
}
|
||||
|
|
@ -205,7 +205,7 @@ func (svc *Service) AddFleetMaintainedApp(
|
|||
teamName = &t.Name
|
||||
}
|
||||
|
||||
actLabelsIncl, actLabelsExcl := activitySoftwareLabelsFromValidatedLabels(payload.ValidatedLabels)
|
||||
actLabelsInclAny, actLabelsExclAny, actLabelsInclAll := activitySoftwareLabelsFromValidatedLabels(payload.ValidatedLabels)
|
||||
if err := svc.NewActivity(ctx, vc.User, fleet.ActivityTypeAddedSoftware{
|
||||
SoftwareTitle: payload.Title,
|
||||
SoftwarePackage: payload.Filename,
|
||||
|
|
@ -213,8 +213,9 @@ func (svc *Service) AddFleetMaintainedApp(
|
|||
TeamID: payload.TeamID,
|
||||
SelfService: payload.SelfService,
|
||||
SoftwareTitleID: titleID,
|
||||
LabelsIncludeAny: actLabelsIncl,
|
||||
LabelsExcludeAny: actLabelsExcl,
|
||||
LabelsIncludeAny: actLabelsInclAny,
|
||||
LabelsExcludeAny: actLabelsExclAny,
|
||||
LabelsIncludeAll: actLabelsInclAll,
|
||||
}); err != nil {
|
||||
return 0, ctxerr.Wrap(ctx, err, "creating activity for added software")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -337,7 +337,7 @@ func TestAddFleetMaintainedApp(t *testing.T) {
|
|||
authCtx := authz_ctx.AuthorizationContext{}
|
||||
ctx := authz_ctx.NewContext(context.Background(), &authCtx)
|
||||
ctx = viewer.NewContext(ctx, viewer.Viewer{User: &fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)}})
|
||||
_, err = svc.AddFleetMaintainedApp(ctx, nil, 1, "", "", "", "", false, false, nil, nil)
|
||||
_, err = svc.AddFleetMaintainedApp(ctx, nil, 1, "", "", "", "", false, false, nil, nil, nil)
|
||||
require.ErrorContains(t, err, "forced error to short-circuit storage and activity creation")
|
||||
|
||||
require.True(t, ds.MatchOrCreateSoftwareInstallerFuncInvoked)
|
||||
|
|
@ -417,7 +417,7 @@ func TestExtractMaintainedAppVersionWhenLatest(t *testing.T) {
|
|||
authCtx := authz_ctx.AuthorizationContext{}
|
||||
ctx := authz_ctx.NewContext(context.Background(), &authCtx)
|
||||
ctx = viewer.NewContext(ctx, viewer.Viewer{User: &fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)}})
|
||||
_, err = svc.AddFleetMaintainedApp(ctx, nil, 1, "", "", "", "", false, false, nil, nil)
|
||||
_, err = svc.AddFleetMaintainedApp(ctx, nil, 1, "", "", "", "", false, false, nil, nil, nil)
|
||||
require.ErrorContains(t, err, "forced error to short-circuit storage and activity creation")
|
||||
|
||||
require.True(t, ds.MatchOrCreateSoftwareInstallerFuncInvoked)
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ func (svc *Service) UploadSoftwareInstaller(ctx context.Context, payload *fleet.
|
|||
}
|
||||
|
||||
// validate labels before we do anything else
|
||||
validatedLabels, err := ValidateSoftwareLabels(ctx, svc, payload.TeamID, payload.LabelsIncludeAny, payload.LabelsExcludeAny)
|
||||
validatedLabels, err := ValidateSoftwareLabels(ctx, svc, payload.TeamID, payload.LabelsIncludeAny, payload.LabelsExcludeAny, payload.LabelsIncludeAll)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "validating software labels")
|
||||
}
|
||||
|
|
@ -148,7 +148,7 @@ func (svc *Service) UploadSoftwareInstaller(ctx context.Context, payload *fleet.
|
|||
teamName = &t.Name
|
||||
}
|
||||
|
||||
actLabelsIncl, actLabelsExcl := activitySoftwareLabelsFromValidatedLabels(payload.ValidatedLabels)
|
||||
actLabelsInclAny, actLabelsExclAny, actLabelsInclAll := activitySoftwareLabelsFromValidatedLabels(payload.ValidatedLabels)
|
||||
if err := svc.NewActivity(ctx, vc.User, fleet.ActivityTypeAddedSoftware{
|
||||
SoftwareTitle: payload.Title,
|
||||
SoftwarePackage: payload.Filename,
|
||||
|
|
@ -156,8 +156,9 @@ func (svc *Service) UploadSoftwareInstaller(ctx context.Context, payload *fleet.
|
|||
TeamID: payload.TeamID,
|
||||
SelfService: payload.SelfService,
|
||||
SoftwareTitleID: titleID,
|
||||
LabelsIncludeAny: actLabelsIncl,
|
||||
LabelsExcludeAny: actLabelsExcl,
|
||||
LabelsIncludeAny: actLabelsInclAny,
|
||||
LabelsExcludeAny: actLabelsExclAny,
|
||||
LabelsIncludeAll: actLabelsInclAll,
|
||||
}); err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "creating activity for added software")
|
||||
}
|
||||
|
|
@ -195,24 +196,35 @@ func (svc *Service) UploadSoftwareInstaller(ctx context.Context, payload *fleet.
|
|||
return addedInstaller, nil
|
||||
}
|
||||
|
||||
func ValidateSoftwareLabels(ctx context.Context, svc fleet.Service, teamID *uint, labelsIncludeAny, labelsExcludeAny []string) (*fleet.LabelIdentsWithScope, error) {
|
||||
func ValidateSoftwareLabels(ctx context.Context, svc fleet.Service, teamID *uint, labelsIncludeAny, labelsExcludeAny, labelsIncludeAll []string) (*fleet.LabelIdentsWithScope, error) {
|
||||
if authctx, ok := authz_ctx.FromContext(ctx); !ok {
|
||||
return nil, fleet.NewAuthRequiredError("validate software labels: missing authorization context")
|
||||
} else if !authctx.Checked() {
|
||||
return nil, fleet.NewAuthRequiredError("validate software labels: method requires previous authorization")
|
||||
}
|
||||
|
||||
var count int
|
||||
for _, set := range [][]string{labelsIncludeAny, labelsExcludeAny, labelsIncludeAll} {
|
||||
if len(set) > 0 {
|
||||
count++
|
||||
}
|
||||
}
|
||||
if count > 1 {
|
||||
return nil, &fleet.BadRequestError{Message: `Only one of "labels_include_all", "labels_include_any" or "labels_exclude_any" can be included.`}
|
||||
}
|
||||
|
||||
var names []string
|
||||
var scope fleet.LabelScope
|
||||
switch {
|
||||
case len(labelsIncludeAny) > 0 && len(labelsExcludeAny) > 0:
|
||||
return nil, &fleet.BadRequestError{Message: `Only one of "labels_include_any" or "labels_exclude_any" can be included.`}
|
||||
case len(labelsIncludeAny) > 0:
|
||||
names = labelsIncludeAny
|
||||
scope = fleet.LabelScopeIncludeAny
|
||||
case len(labelsExcludeAny) > 0:
|
||||
names = labelsExcludeAny
|
||||
scope = fleet.LabelScopeExcludeAny
|
||||
case len(labelsIncludeAll) > 0:
|
||||
names = labelsIncludeAll
|
||||
scope = fleet.LabelScopeIncludeAll
|
||||
}
|
||||
|
||||
if len(names) == 0 {
|
||||
|
|
@ -404,7 +416,7 @@ func (svc *Service) UpdateSoftwareInstaller(ctx context.Context, payload *fleet.
|
|||
dirty["SelfService"] = true
|
||||
}
|
||||
|
||||
shouldUpdateLabels, validatedLabels, err := ValidateSoftwareLabelsForUpdate(ctx, svc, existingInstaller, payload.LabelsIncludeAny, payload.LabelsExcludeAny)
|
||||
shouldUpdateLabels, validatedLabels, err := ValidateSoftwareLabelsForUpdate(ctx, svc, existingInstaller, payload.LabelsIncludeAny, payload.LabelsExcludeAny, payload.LabelsIncludeAll)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "validating software labels for update")
|
||||
}
|
||||
|
|
@ -664,13 +676,14 @@ func (svc *Service) UpdateSoftwareInstaller(ctx context.Context, payload *fleet.
|
|||
|
||||
// now that the payload has been updated with any patches, we can set the
|
||||
// final fields of the activity
|
||||
actLabelsIncl, actLabelsExcl := activitySoftwareLabelsFromSoftwareScopeLabels(
|
||||
existingInstaller.LabelsIncludeAny, existingInstaller.LabelsExcludeAny)
|
||||
actLabelsInclAny, actLabelsExclAny, actLabelsInclAll := activitySoftwareLabelsFromSoftwareScopeLabels(
|
||||
existingInstaller.LabelsIncludeAny, existingInstaller.LabelsExcludeAny, existingInstaller.LabelsIncludeAll)
|
||||
if payload.ValidatedLabels != nil {
|
||||
actLabelsIncl, actLabelsExcl = activitySoftwareLabelsFromValidatedLabels(payload.ValidatedLabels)
|
||||
actLabelsInclAny, actLabelsExclAny, actLabelsInclAll = activitySoftwareLabelsFromValidatedLabels(payload.ValidatedLabels)
|
||||
}
|
||||
activity.LabelsIncludeAny = actLabelsIncl
|
||||
activity.LabelsExcludeAny = actLabelsExcl
|
||||
activity.LabelsIncludeAny = actLabelsInclAny
|
||||
activity.LabelsExcludeAny = actLabelsExclAny
|
||||
activity.LabelsIncludeAll = actLabelsInclAll
|
||||
if payload.SelfService != nil {
|
||||
activity.SelfService = *payload.SelfService
|
||||
}
|
||||
|
|
@ -712,7 +725,7 @@ func (svc *Service) validateEmbeddedSecretsOnScript(ctx context.Context, scriptN
|
|||
return argErr
|
||||
}
|
||||
|
||||
func ValidateSoftwareLabelsForUpdate(ctx context.Context, svc fleet.Service, existingInstaller *fleet.SoftwareInstaller, includeAny, excludeAny []string) (shouldUpdate bool, validatedLabels *fleet.LabelIdentsWithScope, err error) {
|
||||
func ValidateSoftwareLabelsForUpdate(ctx context.Context, svc fleet.Service, existingInstaller *fleet.SoftwareInstaller, includeAny, excludeAny, includeAll []string) (shouldUpdate bool, validatedLabels *fleet.LabelIdentsWithScope, err error) {
|
||||
if authctx, ok := authz_ctx.FromContext(ctx); !ok {
|
||||
return false, nil, fleet.NewAuthRequiredError("batch validate labels: missing authorization context")
|
||||
} else if !authctx.Checked() {
|
||||
|
|
@ -723,16 +736,12 @@ func ValidateSoftwareLabelsForUpdate(ctx context.Context, svc fleet.Service, exi
|
|||
return false, nil, errors.New("existing installer must be provided")
|
||||
}
|
||||
|
||||
if len(existingInstaller.LabelsIncludeAny) > 0 && len(existingInstaller.LabelsExcludeAny) > 0 {
|
||||
return false, nil, errors.New("existing installer must have only one label scope")
|
||||
}
|
||||
|
||||
if includeAny == nil && excludeAny == nil {
|
||||
if includeAny == nil && excludeAny == nil && includeAll == nil {
|
||||
// nothing to do
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
incoming, err := ValidateSoftwareLabels(ctx, svc, existingInstaller.TeamID, includeAny, excludeAny)
|
||||
incoming, err := ValidateSoftwareLabels(ctx, svc, existingInstaller.TeamID, includeAny, excludeAny, includeAll)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
|
|
@ -746,6 +755,9 @@ func ValidateSoftwareLabelsForUpdate(ctx context.Context, svc fleet.Service, exi
|
|||
case len(existingInstaller.LabelsExcludeAny) > 0:
|
||||
prevScope = fleet.LabelScopeExcludeAny
|
||||
prevLabels = existingInstaller.LabelsExcludeAny
|
||||
case len(existingInstaller.LabelsIncludeAll) > 0:
|
||||
prevScope = fleet.LabelScopeIncludeAll
|
||||
prevLabels = existingInstaller.LabelsIncludeAll
|
||||
}
|
||||
|
||||
prevByName := make(map[string]fleet.LabelIdent, len(prevLabels))
|
||||
|
|
@ -855,7 +867,7 @@ func (svc *Service) deleteVPPApp(ctx context.Context, teamID *uint, meta *fleet.
|
|||
teamName = &t.Name
|
||||
}
|
||||
|
||||
actLabelsIncl, actLabelsExcl := activitySoftwareLabelsFromSoftwareScopeLabels(meta.LabelsIncludeAny, meta.LabelsExcludeAny)
|
||||
actLabelsInclAny, actLabelsExclAny, actLabelsInclAll := activitySoftwareLabelsFromSoftwareScopeLabels(meta.LabelsIncludeAny, meta.LabelsExcludeAny, meta.LabelsIncludeAll)
|
||||
|
||||
if err := svc.NewActivity(ctx, vc.User, fleet.ActivityDeletedAppStoreApp{
|
||||
AppStoreID: meta.AdamID,
|
||||
|
|
@ -863,8 +875,9 @@ func (svc *Service) deleteVPPApp(ctx context.Context, teamID *uint, meta *fleet.
|
|||
TeamName: teamName,
|
||||
TeamID: teamID,
|
||||
Platform: meta.Platform,
|
||||
LabelsIncludeAny: actLabelsIncl,
|
||||
LabelsExcludeAny: actLabelsExcl,
|
||||
LabelsIncludeAny: actLabelsInclAny,
|
||||
LabelsExcludeAny: actLabelsExclAny,
|
||||
LabelsIncludeAll: actLabelsInclAll,
|
||||
SoftwareIconURL: meta.IconURL,
|
||||
}); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "creating activity for deleted VPP app")
|
||||
|
|
@ -928,15 +941,16 @@ func (svc *Service) deleteSoftwareInstaller(ctx context.Context, meta *fleet.Sof
|
|||
teamName = &t.Name
|
||||
}
|
||||
|
||||
actLabelsIncl, actLabelsExcl := activitySoftwareLabelsFromSoftwareScopeLabels(meta.LabelsIncludeAny, meta.LabelsExcludeAny)
|
||||
actLabelsInclAny, actLabelsExclAny, actLabelsInclAll := activitySoftwareLabelsFromSoftwareScopeLabels(meta.LabelsIncludeAny, meta.LabelsExcludeAny, meta.LabelsIncludeAll)
|
||||
if err := svc.NewActivity(ctx, vc.User, fleet.ActivityTypeDeletedSoftware{
|
||||
SoftwareTitle: meta.SoftwareTitle,
|
||||
SoftwarePackage: meta.Name,
|
||||
TeamName: teamName,
|
||||
TeamID: meta.TeamID,
|
||||
SelfService: meta.SelfService,
|
||||
LabelsIncludeAny: actLabelsIncl,
|
||||
LabelsExcludeAny: actLabelsExcl,
|
||||
LabelsIncludeAny: actLabelsInclAny,
|
||||
LabelsExcludeAny: actLabelsExclAny,
|
||||
LabelsIncludeAll: actLabelsInclAll,
|
||||
SoftwareIconURL: meta.IconUrl,
|
||||
}); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "creating activity for deleted software")
|
||||
|
|
@ -2030,7 +2044,7 @@ func (svc *Service) BatchSetSoftwareInstallers(
|
|||
}
|
||||
}
|
||||
if !dryRun {
|
||||
validatedLabels, err := ValidateSoftwareLabels(ctx, svc, teamID, payload.LabelsIncludeAny, payload.LabelsExcludeAny)
|
||||
validatedLabels, err := ValidateSoftwareLabels(ctx, svc, teamID, payload.LabelsIncludeAny, payload.LabelsExcludeAny, payload.LabelsIncludeAll)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
@ -2288,6 +2302,7 @@ func (svc *Service) softwareBatchUpload(
|
|||
InstallDuringSetup: p.InstallDuringSetup,
|
||||
LabelsIncludeAny: p.LabelsIncludeAny,
|
||||
LabelsExcludeAny: p.LabelsExcludeAny,
|
||||
LabelsIncludeAll: p.LabelsIncludeAll,
|
||||
ValidatedLabels: p.ValidatedLabels,
|
||||
Categories: p.Categories,
|
||||
DisplayName: p.DisplayName,
|
||||
|
|
@ -3074,12 +3089,11 @@ func UninstallSoftwareMigration(
|
|||
return nil
|
||||
}
|
||||
|
||||
func activitySoftwareLabelsFromValidatedLabels(validatedLabels *fleet.LabelIdentsWithScope) (include, exclude []fleet.ActivitySoftwareLabel) {
|
||||
func activitySoftwareLabelsFromValidatedLabels(validatedLabels *fleet.LabelIdentsWithScope) (includeAny, excludeAny, includeAll []fleet.ActivitySoftwareLabel) {
|
||||
if validatedLabels == nil || len(validatedLabels.ByName) == 0 {
|
||||
return nil, nil
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
excludeAny := validatedLabels.LabelScope == fleet.LabelScopeExcludeAny
|
||||
labels := make([]fleet.ActivitySoftwareLabel, 0, len(validatedLabels.ByName))
|
||||
for _, lbl := range validatedLabels.ByName {
|
||||
labels = append(labels, fleet.ActivitySoftwareLabel{
|
||||
|
|
@ -3087,26 +3101,35 @@ func activitySoftwareLabelsFromValidatedLabels(validatedLabels *fleet.LabelIdent
|
|||
Name: lbl.LabelName,
|
||||
})
|
||||
}
|
||||
if excludeAny {
|
||||
exclude = labels
|
||||
} else {
|
||||
include = labels
|
||||
switch validatedLabels.LabelScope {
|
||||
case fleet.LabelScopeIncludeAny:
|
||||
includeAny = labels
|
||||
case fleet.LabelScopeExcludeAny:
|
||||
excludeAny = labels
|
||||
case fleet.LabelScopeIncludeAll:
|
||||
includeAll = labels
|
||||
}
|
||||
return include, exclude
|
||||
return includeAny, excludeAny, includeAll
|
||||
}
|
||||
|
||||
func activitySoftwareLabelsFromSoftwareScopeLabels(includeScopeLabels, excludeScopeLabels []fleet.SoftwareScopeLabel) (include, exclude []fleet.ActivitySoftwareLabel) {
|
||||
for _, label := range includeScopeLabels {
|
||||
include = append(include, fleet.ActivitySoftwareLabel{
|
||||
func activitySoftwareLabelsFromSoftwareScopeLabels(includeAnyScopeLabels, excludeAnyScopeLabels, includeAllScopeLabels []fleet.SoftwareScopeLabel) (includeAny, excludeAny, includeAll []fleet.ActivitySoftwareLabel) {
|
||||
for _, label := range includeAnyScopeLabels {
|
||||
includeAny = append(includeAny, fleet.ActivitySoftwareLabel{
|
||||
ID: label.LabelID,
|
||||
Name: label.LabelName,
|
||||
})
|
||||
}
|
||||
for _, label := range excludeScopeLabels {
|
||||
exclude = append(exclude, fleet.ActivitySoftwareLabel{
|
||||
for _, label := range excludeAnyScopeLabels {
|
||||
excludeAny = append(excludeAny, fleet.ActivitySoftwareLabel{
|
||||
ID: label.LabelID,
|
||||
Name: label.LabelName,
|
||||
})
|
||||
}
|
||||
return include, exclude
|
||||
for _, label := range includeAllScopeLabels {
|
||||
includeAll = append(includeAll, fleet.ActivitySoftwareLabel{
|
||||
ID: label.LabelID,
|
||||
Name: label.LabelName,
|
||||
})
|
||||
}
|
||||
return includeAny, excludeAny, includeAll
|
||||
}
|
||||
|
|
|
|||
|
|
@ -211,6 +211,7 @@ func generateEditActivityForSoftwareTitleIcon(ctx context.Context, svc *Service,
|
|||
SoftwareIconURL: &iconUrl,
|
||||
LabelsIncludeAny: activityDetailsForSoftwareTitleIcon.LabelsIncludeAny,
|
||||
LabelsExcludeAny: activityDetailsForSoftwareTitleIcon.LabelsExcludeAny,
|
||||
LabelsIncludeAll: activityDetailsForSoftwareTitleIcon.LabelsIncludeAll,
|
||||
}); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "creating activity for software title icon")
|
||||
}
|
||||
|
|
@ -228,6 +229,7 @@ func generateEditActivityForSoftwareTitleIcon(ctx context.Context, svc *Service,
|
|||
SoftwareIconURL: &iconUrl,
|
||||
LabelsIncludeAny: activityDetailsForSoftwareTitleIcon.LabelsIncludeAny,
|
||||
LabelsExcludeAny: activityDetailsForSoftwareTitleIcon.LabelsExcludeAny,
|
||||
LabelsIncludeAll: activityDetailsForSoftwareTitleIcon.LabelsIncludeAll,
|
||||
SoftwareTitleID: activityDetailsForSoftwareTitleIcon.SoftwareTitleID,
|
||||
}); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "creating activity for software title icon")
|
||||
|
|
@ -246,6 +248,7 @@ func generateEditActivityForSoftwareTitleIcon(ctx context.Context, svc *Service,
|
|||
SoftwareIconURL: &iconUrl,
|
||||
LabelsIncludeAny: activityDetailsForSoftwareTitleIcon.LabelsIncludeAny,
|
||||
LabelsExcludeAny: activityDetailsForSoftwareTitleIcon.LabelsExcludeAny,
|
||||
LabelsIncludeAll: activityDetailsForSoftwareTitleIcon.LabelsIncludeAll,
|
||||
SoftwareTitleID: activityDetailsForSoftwareTitleIcon.SoftwareTitleID,
|
||||
}); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "creating activity for software title icon")
|
||||
|
|
|
|||
|
|
@ -99,6 +99,7 @@ func (svc *Service) BatchAssociateVPPApps(ctx context.Context, teamName string,
|
|||
Platform: fleet.MacOSPlatform,
|
||||
LabelsExcludeAny: payload.LabelsExcludeAny,
|
||||
LabelsIncludeAny: payload.LabelsIncludeAny,
|
||||
LabelsIncludeAll: payload.LabelsIncludeAll,
|
||||
Categories: payload.Categories,
|
||||
DisplayName: payload.DisplayName,
|
||||
AutoUpdateEnabled: payload.AutoUpdateEnabled,
|
||||
|
|
@ -112,6 +113,7 @@ func (svc *Service) BatchAssociateVPPApps(ctx context.Context, teamName string,
|
|||
Platform: fleet.IOSPlatform,
|
||||
LabelsExcludeAny: payload.LabelsExcludeAny,
|
||||
LabelsIncludeAny: payload.LabelsIncludeAny,
|
||||
LabelsIncludeAll: payload.LabelsIncludeAll,
|
||||
Categories: payload.Categories,
|
||||
DisplayName: payload.DisplayName,
|
||||
AutoUpdateEnabled: payload.AutoUpdateEnabled,
|
||||
|
|
@ -125,6 +127,7 @@ func (svc *Service) BatchAssociateVPPApps(ctx context.Context, teamName string,
|
|||
Platform: fleet.IPadOSPlatform,
|
||||
LabelsExcludeAny: payload.LabelsExcludeAny,
|
||||
LabelsIncludeAny: payload.LabelsIncludeAny,
|
||||
LabelsIncludeAll: payload.LabelsIncludeAll,
|
||||
Categories: payload.Categories,
|
||||
DisplayName: payload.DisplayName,
|
||||
AutoUpdateEnabled: payload.AutoUpdateEnabled,
|
||||
|
|
@ -141,6 +144,7 @@ func (svc *Service) BatchAssociateVPPApps(ctx context.Context, teamName string,
|
|||
Platform: payload.Platform,
|
||||
LabelsExcludeAny: payload.LabelsExcludeAny,
|
||||
LabelsIncludeAny: payload.LabelsIncludeAny,
|
||||
LabelsIncludeAll: payload.LabelsIncludeAll,
|
||||
Categories: payload.Categories,
|
||||
DisplayName: payload.DisplayName,
|
||||
Configuration: payload.Configuration,
|
||||
|
|
@ -184,7 +188,7 @@ func (svc *Service) BatchAssociateVPPApps(ctx context.Context, teamName string,
|
|||
}
|
||||
}
|
||||
|
||||
validatedLabels, err := ValidateSoftwareLabels(ctx, svc, teamID, payload.LabelsIncludeAny, payload.LabelsExcludeAny)
|
||||
validatedLabels, err := ValidateSoftwareLabels(ctx, svc, teamID, payload.LabelsIncludeAny, payload.LabelsExcludeAny, payload.LabelsIncludeAll)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "validating software labels for batch adding vpp app")
|
||||
}
|
||||
|
|
@ -578,7 +582,7 @@ func (svc *Service) AddAppStoreApp(ctx context.Context, teamID *uint, appID flee
|
|||
fmt.Sprintf("platform must be one of '%s', '%s', '%s', or '%s'", fleet.IOSPlatform, fleet.IPadOSPlatform, fleet.MacOSPlatform, fleet.AndroidPlatform))
|
||||
}
|
||||
|
||||
validatedLabels, err := ValidateSoftwareLabels(ctx, svc, teamID, appID.LabelsIncludeAny, appID.LabelsExcludeAny)
|
||||
validatedLabels, err := ValidateSoftwareLabels(ctx, svc, teamID, appID.LabelsIncludeAny, appID.LabelsExcludeAny, appID.LabelsIncludeAll)
|
||||
if err != nil {
|
||||
return 0, ctxerr.Wrap(ctx, err, "validating software labels for adding vpp app")
|
||||
}
|
||||
|
|
@ -757,7 +761,7 @@ func (svc *Service) AddAppStoreApp(ctx context.Context, teamID *uint, appID flee
|
|||
}
|
||||
}
|
||||
|
||||
actLabelsIncl, actLabelsExcl := activitySoftwareLabelsFromValidatedLabels(addedApp.ValidatedLabels)
|
||||
actLabelsInclAny, actLabelsExclAny, actLabelsInclAll := activitySoftwareLabelsFromValidatedLabels(addedApp.ValidatedLabels)
|
||||
|
||||
act := fleet.ActivityAddedAppStoreApp{
|
||||
AppStoreID: app.AdamID,
|
||||
|
|
@ -767,8 +771,9 @@ func (svc *Service) AddAppStoreApp(ctx context.Context, teamID *uint, appID flee
|
|||
SoftwareTitleId: addedApp.TitleID,
|
||||
TeamID: teamID,
|
||||
SelfService: app.SelfService,
|
||||
LabelsIncludeAny: actLabelsIncl,
|
||||
LabelsExcludeAny: actLabelsExcl,
|
||||
LabelsIncludeAny: actLabelsInclAny,
|
||||
LabelsExcludeAny: actLabelsExclAny,
|
||||
LabelsIncludeAll: actLabelsInclAll,
|
||||
Configuration: app.Configuration,
|
||||
}
|
||||
|
||||
|
|
@ -912,9 +917,9 @@ func (svc *Service) UpdateAppStoreApp(ctx context.Context, titleID uint, teamID
|
|||
}
|
||||
|
||||
var validatedLabels *fleet.LabelIdentsWithScope
|
||||
if payload.LabelsExcludeAny != nil || payload.LabelsIncludeAny != nil {
|
||||
if payload.LabelsExcludeAny != nil || payload.LabelsIncludeAny != nil || payload.LabelsIncludeAll != nil {
|
||||
var err error
|
||||
validatedLabels, err = ValidateSoftwareLabels(ctx, svc, teamID, payload.LabelsIncludeAny, payload.LabelsExcludeAny)
|
||||
validatedLabels, err = ValidateSoftwareLabels(ctx, svc, teamID, payload.LabelsIncludeAny, payload.LabelsExcludeAny, payload.LabelsIncludeAll)
|
||||
if err != nil {
|
||||
return nil, nil, ctxerr.Wrap(ctx, err, "UpdateAppStoreApp: validating software labels")
|
||||
}
|
||||
|
|
@ -995,6 +1000,13 @@ func (svc *Service) UpdateAppStoreApp(ctx context.Context, titleID uint, teamID
|
|||
for _, l := range meta.LabelsIncludeAny {
|
||||
existingLabels.ByName[l.LabelName] = fleet.LabelIdent{LabelName: l.LabelName, LabelID: l.LabelID}
|
||||
}
|
||||
|
||||
case len(meta.LabelsIncludeAll) > 0:
|
||||
existingLabels.LabelScope = fleet.LabelScopeIncludeAll
|
||||
existingLabels.ByName = make(map[string]fleet.LabelIdent, len(meta.LabelsIncludeAll))
|
||||
for _, l := range meta.LabelsIncludeAll {
|
||||
existingLabels.ByName[l.LabelName] = fleet.LabelIdent{LabelName: l.LabelName, LabelID: l.LabelID}
|
||||
}
|
||||
}
|
||||
var labelsChanged bool
|
||||
if validatedLabels != nil {
|
||||
|
|
@ -1066,7 +1078,7 @@ func (svc *Service) UpdateAppStoreApp(ctx context.Context, titleID uint, teamID
|
|||
}
|
||||
}
|
||||
|
||||
actLabelsIncl, actLabelsExcl := activitySoftwareLabelsFromValidatedLabels(validatedLabels)
|
||||
actLabelsInclAny, actLabelsExclAny, actLabelsInclAll := activitySoftwareLabelsFromValidatedLabels(validatedLabels)
|
||||
|
||||
displayNameVal := ptr.ValOrZero(payload.DisplayName)
|
||||
|
||||
|
|
@ -1078,8 +1090,9 @@ func (svc *Service) UpdateAppStoreApp(ctx context.Context, titleID uint, teamID
|
|||
SoftwareTitle: meta.Name,
|
||||
AppStoreID: meta.AdamID,
|
||||
Platform: meta.Platform,
|
||||
LabelsIncludeAny: actLabelsIncl,
|
||||
LabelsExcludeAny: actLabelsExcl,
|
||||
LabelsIncludeAny: actLabelsInclAny,
|
||||
LabelsExcludeAny: actLabelsExclAny,
|
||||
LabelsIncludeAll: actLabelsInclAll,
|
||||
SoftwareIconURL: meta.IconURL,
|
||||
SoftwareDisplayName: displayNameVal,
|
||||
Configuration: appToWrite.Configuration,
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ func TestBatchAssociateVPPApps(t *testing.T) {
|
|||
AppStoreID: "my-fake-app",
|
||||
LabelsExcludeAny: []string{},
|
||||
LabelsIncludeAny: []string{},
|
||||
LabelsIncludeAll: []string{},
|
||||
Categories: []string{},
|
||||
Platform: fleet.MacOSPlatform,
|
||||
},
|
||||
|
|
@ -44,6 +45,7 @@ func TestBatchAssociateVPPApps(t *testing.T) {
|
|||
AppStoreID: "my-fake-app",
|
||||
LabelsExcludeAny: []string{},
|
||||
LabelsIncludeAny: []string{},
|
||||
LabelsIncludeAll: []string{},
|
||||
Categories: []string{},
|
||||
Platform: fleet.MacOSPlatform,
|
||||
},
|
||||
|
|
@ -70,6 +72,7 @@ func TestBatchAssociateVPPApps(t *testing.T) {
|
|||
AppStoreID: pkg,
|
||||
LabelsExcludeAny: []string{},
|
||||
LabelsIncludeAny: []string{},
|
||||
LabelsIncludeAll: []string{},
|
||||
Categories: []string{},
|
||||
Platform: fleet.AndroidPlatform,
|
||||
},
|
||||
|
|
@ -82,6 +85,7 @@ func TestBatchAssociateVPPApps(t *testing.T) {
|
|||
AppStoreID: pkg,
|
||||
LabelsExcludeAny: []string{},
|
||||
LabelsIncludeAny: []string{},
|
||||
LabelsIncludeAll: []string{},
|
||||
Categories: []string{},
|
||||
Platform: fleet.AndroidPlatform,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -251,6 +251,7 @@ func (spec SoftwarePackage) HydrateToPackageLevel(packageLevel fleet.SoftwarePac
|
|||
packageLevel.Categories = spec.Categories
|
||||
packageLevel.LabelsIncludeAny = spec.LabelsIncludeAny
|
||||
packageLevel.LabelsExcludeAny = spec.LabelsExcludeAny
|
||||
packageLevel.LabelsIncludeAll = spec.LabelsIncludeAll
|
||||
packageLevel.InstallDuringSetup = spec.InstallDuringSetup
|
||||
packageLevel.SelfService = spec.SelfService
|
||||
|
||||
|
|
@ -1605,8 +1606,14 @@ func parseSoftware(top map[string]json.RawMessage, result *GitOps, baseDir strin
|
|||
continue
|
||||
}
|
||||
|
||||
if len(item.LabelsExcludeAny) > 0 && len(item.LabelsIncludeAny) > 0 {
|
||||
multiError = multierror.Append(multiError, fmt.Errorf(`only one of "labels_exclude_any" or "labels_include_any" can be specified for app store app %q`, item.AppStoreID))
|
||||
var count int
|
||||
for _, set := range [][]string{item.LabelsExcludeAny, item.LabelsIncludeAny, item.LabelsIncludeAll} {
|
||||
if len(set) > 0 {
|
||||
count++
|
||||
}
|
||||
}
|
||||
if count > 1 {
|
||||
multiError = multierror.Append(multiError, fmt.Errorf(`only one of "labels_include_all", "labels_exclude_any" or "labels_include_any" can be specified for app store app %q`, item.AppStoreID))
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -1626,8 +1633,14 @@ func parseSoftware(top map[string]json.RawMessage, result *GitOps, baseDir strin
|
|||
continue
|
||||
}
|
||||
|
||||
if len(maintainedAppSpec.LabelsExcludeAny) > 0 && len(maintainedAppSpec.LabelsIncludeAny) > 0 {
|
||||
multiError = multierror.Append(multiError, fmt.Errorf(`only one of "labels_exclude_any" or "labels_include_any" can be specified for fleet maintained app %q`, maintainedAppSpec.Slug))
|
||||
var count int
|
||||
for _, set := range [][]string{maintainedAppSpec.LabelsExcludeAny, maintainedAppSpec.LabelsIncludeAny, maintainedAppSpec.LabelsIncludeAll} {
|
||||
if len(set) > 0 {
|
||||
count++
|
||||
}
|
||||
}
|
||||
if count > 1 {
|
||||
multiError = multierror.Append(multiError, fmt.Errorf(`only one of "labels_include_all", "labels_exclude_any" or "labels_include_any" can be specified for fleet maintained app %q`, maintainedAppSpec.Slug))
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -1752,10 +1765,18 @@ func parseSoftware(top map[string]json.RawMessage, result *GitOps, baseDir strin
|
|||
continue
|
||||
}
|
||||
}
|
||||
if len(softwarePackageSpec.LabelsExcludeAny) > 0 && len(softwarePackageSpec.LabelsIncludeAny) > 0 {
|
||||
multiError = multierror.Append(multiError, fmt.Errorf(`only one of "labels_exclude_any" or "labels_include_any" can be specified for software URL %q`, softwarePackageSpec.URL))
|
||||
|
||||
var count int
|
||||
for _, set := range [][]string{softwarePackageSpec.LabelsExcludeAny, softwarePackageSpec.LabelsIncludeAny, softwarePackageSpec.LabelsIncludeAll} {
|
||||
if len(set) > 0 {
|
||||
count++
|
||||
}
|
||||
}
|
||||
if count > 1 {
|
||||
multiError = multierror.Append(multiError, fmt.Errorf(`only one of "labels_include_all", "labels_exclude_any" or "labels_include_any" can be specified for software URL %q`, softwarePackageSpec.URL))
|
||||
continue
|
||||
}
|
||||
|
||||
if softwarePackageSpec.SHA256 != "" && !validSHA256Value.MatchString(softwarePackageSpec.SHA256) {
|
||||
multiError = multierror.Append(multiError, fmt.Errorf("hash_sha256 value %q must be a valid lower-case hex-encoded (64-character) SHA-256 hash value", softwarePackageSpec.SHA256))
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -211,10 +211,12 @@ func TestValidGitOpsYaml(t *testing.T) {
|
|||
assert.Contains(t, pkg.LabelsIncludeAny, "a")
|
||||
assert.Contains(t, pkg.Categories, "Communication")
|
||||
assert.Empty(t, pkg.LabelsExcludeAny)
|
||||
assert.Empty(t, pkg.LabelsIncludeAll)
|
||||
} else {
|
||||
assert.Empty(t, pkg.UninstallScript.Path)
|
||||
assert.Contains(t, pkg.LabelsExcludeAny, "a")
|
||||
assert.Empty(t, pkg.LabelsIncludeAny)
|
||||
assert.Empty(t, pkg.LabelsIncludeAll)
|
||||
}
|
||||
}
|
||||
require.Len(t, gitops.Software.FleetMaintainedApps, 2)
|
||||
|
|
@ -2417,7 +2419,7 @@ agent_options:
|
|||
org_settings:
|
||||
server_settings:
|
||||
org_info:
|
||||
secrets:
|
||||
secrets:
|
||||
controls:
|
||||
apple_settings:
|
||||
configuration_profiles:
|
||||
|
|
@ -2445,7 +2447,7 @@ agent_options:
|
|||
org_settings:
|
||||
server_settings:
|
||||
org_info:
|
||||
secrets:
|
||||
secrets:
|
||||
controls:
|
||||
apple_settings:
|
||||
configuration_profiles:
|
||||
|
|
@ -2474,7 +2476,7 @@ agent_options:
|
|||
org_settings:
|
||||
server_settings:
|
||||
org_info:
|
||||
secrets:
|
||||
secrets:
|
||||
controls:
|
||||
windows_settings:
|
||||
configuration_profiles:
|
||||
|
|
@ -2503,7 +2505,7 @@ agent_options:
|
|||
org_settings:
|
||||
server_settings:
|
||||
org_info:
|
||||
secrets:
|
||||
secrets:
|
||||
controls:
|
||||
android_settings:
|
||||
configuration_profiles:
|
||||
|
|
@ -2532,10 +2534,10 @@ agent_options:
|
|||
org_settings:
|
||||
server_settings:
|
||||
org_info:
|
||||
secrets:
|
||||
secrets:
|
||||
controls:
|
||||
setup_experience:
|
||||
macos_setup:
|
||||
macos_setup:
|
||||
`
|
||||
yamlPath := filepath.Join(dir, "gitops.yml")
|
||||
require.NoError(t, os.WriteFile(yamlPath, []byte(config), 0o644))
|
||||
|
|
|
|||
|
|
@ -211,20 +211,32 @@ WHERE
|
|||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "get in house app labels")
|
||||
}
|
||||
var exclAny, inclAny []fleet.SoftwareScopeLabel
|
||||
var exclAny, inclAny, inclAll []fleet.SoftwareScopeLabel
|
||||
for _, l := range labels {
|
||||
if l.Exclude {
|
||||
switch {
|
||||
case l.Exclude && !l.RequireAll:
|
||||
exclAny = append(exclAny, l)
|
||||
} else {
|
||||
case !l.Exclude && l.RequireAll:
|
||||
inclAll = append(inclAll, l)
|
||||
case !l.Exclude && !l.RequireAll:
|
||||
inclAny = append(inclAny, l)
|
||||
default:
|
||||
ds.logger.WarnContext(ctx, "in house app has an unsupported label scope", "installer_id", dest.InstallerID, "invalid_label", fmt.Sprintf("%#v", l))
|
||||
}
|
||||
}
|
||||
|
||||
if len(inclAny) > 0 && len(exclAny) > 0 {
|
||||
ds.logger.WarnContext(ctx, "in house app has both include and exclude labels", "installer_id", dest.InstallerID, "include", fmt.Sprintf("%v", inclAny), "exclude", fmt.Sprintf("%v", exclAny))
|
||||
var count int
|
||||
for _, set := range [][]fleet.SoftwareScopeLabel{exclAny, inclAny, inclAll} {
|
||||
if len(set) > 0 {
|
||||
count++
|
||||
}
|
||||
}
|
||||
if count > 1 {
|
||||
ds.logger.WarnContext(ctx, "in house app has more than one scope of labels", "installer_id", dest.InstallerID, "include_any", fmt.Sprintf("%v", inclAny), "exclude_any", fmt.Sprintf("%v", exclAny), "include_all", fmt.Sprintf("%v", inclAll))
|
||||
}
|
||||
dest.LabelsExcludeAny = exclAny
|
||||
dest.LabelsIncludeAny = inclAny
|
||||
dest.LabelsIncludeAll = inclAll
|
||||
|
||||
categoryMap, err := ds.GetCategoriesForSoftwareTitles(ctx, []uint{titleID}, teamID)
|
||||
if err != nil {
|
||||
|
|
@ -961,18 +973,21 @@ INSERT INTO
|
|||
in_house_app_labels (
|
||||
in_house_app_id,
|
||||
label_id,
|
||||
exclude
|
||||
exclude,
|
||||
require_all
|
||||
)
|
||||
VALUES
|
||||
%s
|
||||
ON DUPLICATE KEY UPDATE
|
||||
exclude = VALUES(exclude)
|
||||
exclude = VALUES(exclude),
|
||||
require_all = VALUES(require_all)
|
||||
`
|
||||
|
||||
const loadExistingInHouseLabels = `
|
||||
SELECT
|
||||
label_id,
|
||||
exclude
|
||||
exclude,
|
||||
require_all
|
||||
FROM
|
||||
in_house_app_labels
|
||||
WHERE
|
||||
|
|
@ -1322,13 +1337,15 @@ WHERE
|
|||
}
|
||||
|
||||
excludeLabels := installer.ValidatedLabels.LabelScope == fleet.LabelScopeExcludeAny
|
||||
requireAllLabels := installer.ValidatedLabels.LabelScope == fleet.LabelScopeIncludeAll
|
||||
if len(existing) > 0 && !existing[0].IsMetadataModified {
|
||||
// load the remaining labels for that installer, so that we can detect
|
||||
// if any label changed (if the counts differ, then labels did change,
|
||||
// otherwise if the exclude bool changed, the target did change).
|
||||
// otherwise if the exclude/require all bool changed, the target did change).
|
||||
var existingLabels []struct {
|
||||
LabelID uint `db:"label_id"`
|
||||
Exclude bool `db:"exclude"`
|
||||
LabelID uint `db:"label_id"`
|
||||
Exclude bool `db:"exclude"`
|
||||
RequireAll bool `db:"require_all"`
|
||||
}
|
||||
if err := sqlx.SelectContext(ctx, tx, &existingLabels, loadExistingInHouseLabels, installerID); err != nil {
|
||||
return ctxerr.Wrapf(ctx, err, "load existing labels for in-house with name %q", installer.Filename)
|
||||
|
|
@ -1337,8 +1354,8 @@ WHERE
|
|||
if len(existingLabels) != len(labelIDs) {
|
||||
existing[0].IsMetadataModified = true
|
||||
}
|
||||
if len(existingLabels) > 0 && existingLabels[0].Exclude != excludeLabels {
|
||||
// same labels are provided, but the include <-> exclude changed
|
||||
if len(existingLabels) > 0 && (existingLabels[0].Exclude != excludeLabels || existingLabels[0].RequireAll != requireAllLabels) {
|
||||
// same labels are provided, but the include <-> exclude or require all changed
|
||||
existing[0].IsMetadataModified = true
|
||||
}
|
||||
}
|
||||
|
|
@ -1346,9 +1363,9 @@ WHERE
|
|||
// upsert the new labels now that obsolete ones have been deleted
|
||||
var upsertLabelArgs []any
|
||||
for _, lblID := range labelIDs {
|
||||
upsertLabelArgs = append(upsertLabelArgs, installerID, lblID, excludeLabels)
|
||||
upsertLabelArgs = append(upsertLabelArgs, installerID, lblID, excludeLabels, requireAllLabels)
|
||||
}
|
||||
upsertLabelValues := strings.TrimSuffix(strings.Repeat("(?,?,?),", len(installer.ValidatedLabels.ByName)), ",")
|
||||
upsertLabelValues := strings.TrimSuffix(strings.Repeat("(?,?,?,?),", len(installer.ValidatedLabels.ByName)), ",")
|
||||
|
||||
_, err = tx.ExecContext(ctx, fmt.Sprintf(upsertInHouseLabels, upsertLabelValues), upsertLabelArgs...)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -3479,14 +3479,16 @@ func filterSoftwareInstallersByLabel(
|
|||
FROM
|
||||
software_installers
|
||||
INNER JOIN software_installer_labels
|
||||
ON software_installer_labels.software_installer_id = software_installers.id AND software_installer_labels.exclude = 0
|
||||
ON software_installer_labels.software_installer_id = software_installers.id
|
||||
AND software_installer_labels.exclude = 0
|
||||
AND software_installer_labels.require_all = 0
|
||||
LEFT JOIN label_membership
|
||||
ON label_membership.label_id = software_installer_labels.label_id
|
||||
AND label_membership.host_id = :host_id
|
||||
GROUP BY
|
||||
software_installers.id
|
||||
HAVING
|
||||
COUNT(*) > 0 AND COUNT(label_membership.label_id) > 0
|
||||
count_installer_labels > 0 AND count_host_labels > 0
|
||||
),
|
||||
exclude_any AS (
|
||||
SELECT
|
||||
|
|
@ -3505,7 +3507,9 @@ func filterSoftwareInstallersByLabel(
|
|||
FROM
|
||||
software_installers
|
||||
INNER JOIN software_installer_labels
|
||||
ON software_installer_labels.software_installer_id = software_installers.id AND software_installer_labels.exclude = 1
|
||||
ON software_installer_labels.software_installer_id = software_installers.id
|
||||
AND software_installer_labels.exclude = 1
|
||||
AND software_installer_labels.require_all = 0
|
||||
INNER JOIN labels
|
||||
ON labels.id = software_installer_labels.label_id
|
||||
LEFT JOIN label_membership
|
||||
|
|
@ -3514,17 +3518,30 @@ func filterSoftwareInstallersByLabel(
|
|||
GROUP BY
|
||||
software_installers.id
|
||||
HAVING
|
||||
COUNT(*) > 0
|
||||
AND COUNT(*) = SUM(
|
||||
CASE
|
||||
WHEN labels.created_at IS NOT NULL AND (
|
||||
labels.label_membership_type = 1 OR
|
||||
(labels.label_membership_type = 0 AND :host_label_updated_at >= labels.created_at)
|
||||
) THEN 1
|
||||
ELSE 0
|
||||
END
|
||||
)
|
||||
AND COUNT(label_membership.label_id) = 0
|
||||
count_installer_labels > 0
|
||||
AND count_installer_labels = count_host_updated_after_labels
|
||||
AND count_host_labels = 0
|
||||
),
|
||||
include_all AS (
|
||||
SELECT
|
||||
software_installers.id AS installer_id,
|
||||
COUNT(*) AS count_installer_labels,
|
||||
COUNT(label_membership.label_id) AS count_host_labels,
|
||||
0 AS count_host_updated_after_labels
|
||||
FROM
|
||||
software_installers
|
||||
INNER JOIN software_installer_labels
|
||||
ON software_installer_labels.software_installer_id = software_installers.id
|
||||
AND software_installer_labels.exclude = 0
|
||||
AND software_installer_labels.require_all = 1
|
||||
LEFT JOIN label_membership
|
||||
ON label_membership.label_id = software_installer_labels.label_id
|
||||
AND label_membership.host_id = :host_id
|
||||
GROUP BY
|
||||
software_installers.id
|
||||
HAVING
|
||||
count_installer_labels > 0
|
||||
AND count_host_labels = count_installer_labels
|
||||
)
|
||||
SELECT
|
||||
software_installers.id AS id,
|
||||
|
|
@ -3537,6 +3554,8 @@ func filterSoftwareInstallersByLabel(
|
|||
ON include_any.installer_id = software_installers.id
|
||||
LEFT JOIN exclude_any
|
||||
ON exclude_any.installer_id = software_installers.id
|
||||
LEFT JOIN include_all
|
||||
ON include_all.installer_id = software_installers.id
|
||||
WHERE
|
||||
software_installers.global_or_team_id = :global_or_team_id
|
||||
AND software_installers.id IN (:software_installer_ids)
|
||||
|
|
@ -3544,6 +3563,7 @@ func filterSoftwareInstallersByLabel(
|
|||
no_labels.installer_id IS NOT NULL
|
||||
OR include_any.installer_id IS NOT NULL
|
||||
OR exclude_any.installer_id IS NOT NULL
|
||||
OR include_all.installer_id IS NOT NULL
|
||||
)
|
||||
`
|
||||
labelSqlFilter, args, err := sqlx.Named(labelSqlFilter, map[string]any{
|
||||
|
|
@ -3633,7 +3653,9 @@ func filterVPPAppsByLabel(
|
|||
FROM
|
||||
vpp_apps_teams
|
||||
INNER JOIN vpp_app_team_labels
|
||||
ON vpp_app_team_labels.vpp_app_team_id = vpp_apps_teams.id AND vpp_app_team_labels.exclude = 0
|
||||
ON vpp_app_team_labels.vpp_app_team_id = vpp_apps_teams.id
|
||||
AND vpp_app_team_labels.exclude = 0
|
||||
AND vpp_app_team_labels.require_all = 0
|
||||
LEFT JOIN label_membership
|
||||
ON label_membership.label_id = vpp_app_team_labels.label_id
|
||||
AND label_membership.host_id = :host_id
|
||||
|
|
@ -3657,7 +3679,9 @@ func filterVPPAppsByLabel(
|
|||
FROM
|
||||
vpp_apps_teams
|
||||
INNER JOIN vpp_app_team_labels
|
||||
ON vpp_app_team_labels.vpp_app_team_id = vpp_apps_teams.id AND vpp_app_team_labels.exclude = 1
|
||||
ON vpp_app_team_labels.vpp_app_team_id = vpp_apps_teams.id
|
||||
AND vpp_app_team_labels.exclude = 1
|
||||
AND vpp_app_team_labels.require_all = 0
|
||||
INNER JOIN labels
|
||||
ON labels.id = vpp_app_team_labels.label_id
|
||||
LEFT OUTER JOIN label_membership
|
||||
|
|
@ -3668,6 +3692,26 @@ func filterVPPAppsByLabel(
|
|||
count_installer_labels > 0
|
||||
AND count_installer_labels = count_host_updated_after_labels
|
||||
AND count_host_labels = 0
|
||||
),
|
||||
include_all AS (
|
||||
SELECT
|
||||
vpp_apps_teams.id AS team_id,
|
||||
COUNT(vpp_app_team_labels.label_id) AS count_installer_labels,
|
||||
COUNT(label_membership.label_id) AS count_host_labels,
|
||||
0 as count_host_updated_after_labels
|
||||
FROM
|
||||
vpp_apps_teams
|
||||
INNER JOIN vpp_app_team_labels
|
||||
ON vpp_app_team_labels.vpp_app_team_id = vpp_apps_teams.id
|
||||
AND vpp_app_team_labels.exclude = 0
|
||||
AND vpp_app_team_labels.require_all = 1
|
||||
LEFT JOIN label_membership
|
||||
ON label_membership.label_id = vpp_app_team_labels.label_id
|
||||
AND label_membership.host_id = :host_id
|
||||
GROUP BY
|
||||
vpp_apps_teams.id
|
||||
HAVING
|
||||
count_installer_labels > 0 AND count_host_labels = count_installer_labels
|
||||
)
|
||||
SELECT
|
||||
vpp_apps.adam_id AS adam_id,
|
||||
|
|
@ -3675,19 +3719,24 @@ func filterVPPAppsByLabel(
|
|||
FROM
|
||||
vpp_apps
|
||||
INNER JOIN
|
||||
vpp_apps_teams ON vpp_apps.adam_id = vpp_apps_teams.adam_id AND vpp_apps.platform = vpp_apps_teams.platform AND vpp_apps_teams.global_or_team_id = :global_or_team_id
|
||||
vpp_apps_teams ON vpp_apps.adam_id = vpp_apps_teams.adam_id
|
||||
AND vpp_apps.platform = vpp_apps_teams.platform
|
||||
AND vpp_apps_teams.global_or_team_id = :global_or_team_id
|
||||
LEFT JOIN no_labels
|
||||
ON no_labels.team_id = vpp_apps_teams.id
|
||||
LEFT JOIN include_any
|
||||
ON include_any.team_id = vpp_apps_teams.id
|
||||
LEFT JOIN exclude_any
|
||||
ON exclude_any.team_id = vpp_apps_teams.id
|
||||
LEFT JOIN include_all
|
||||
ON include_all.team_id = vpp_apps_teams.id
|
||||
WHERE
|
||||
vpp_apps.adam_id IN (:vpp_app_adam_ids)
|
||||
AND (
|
||||
no_labels.team_id IS NOT NULL
|
||||
OR include_any.team_id IS NOT NULL
|
||||
OR exclude_any.team_id IS NOT NULL
|
||||
OR include_all.team_id IS NOT NULL
|
||||
)
|
||||
`
|
||||
|
||||
|
|
@ -3786,8 +3835,10 @@ func filterInHouseAppsByLabel(
|
|||
0 as count_host_updated_after_labels
|
||||
FROM
|
||||
in_house_apps iha
|
||||
INNER JOIN in_house_app_labels ihl ON
|
||||
ihl.in_house_app_id = iha.id AND ihl.exclude = 0
|
||||
INNER JOIN in_house_app_labels ihl
|
||||
ON ihl.in_house_app_id = iha.id
|
||||
AND ihl.exclude = 0
|
||||
AND ihl.require_all = 0
|
||||
LEFT JOIN label_membership lm ON
|
||||
lm.label_id = ihl.label_id AND lm.host_id = :host_id
|
||||
GROUP BY
|
||||
|
|
@ -3809,8 +3860,10 @@ func filterInHouseAppsByLabel(
|
|||
) AS count_host_updated_after_labels
|
||||
FROM
|
||||
in_house_apps iha
|
||||
INNER JOIN in_house_app_labels ihl ON
|
||||
ihl.in_house_app_id = iha.id AND ihl.exclude = 1
|
||||
INNER JOIN in_house_app_labels ihl
|
||||
ON ihl.in_house_app_id = iha.id
|
||||
AND ihl.exclude = 1
|
||||
AND ihl.require_all = 0
|
||||
INNER JOIN labels lbl ON
|
||||
lbl.id = ihl.label_id
|
||||
LEFT OUTER JOIN label_membership lm ON
|
||||
|
|
@ -3821,6 +3874,25 @@ func filterInHouseAppsByLabel(
|
|||
count_installer_labels > 0 AND
|
||||
count_installer_labels = count_host_updated_after_labels AND
|
||||
count_host_labels = 0
|
||||
),
|
||||
include_all AS (
|
||||
SELECT
|
||||
iha.id AS in_house_app_id,
|
||||
COUNT(ihl.label_id) AS count_installer_labels,
|
||||
COUNT(lm.label_id) AS count_host_labels,
|
||||
0 as count_host_updated_after_labels
|
||||
FROM
|
||||
in_house_apps iha
|
||||
INNER JOIN in_house_app_labels ihl
|
||||
ON ihl.in_house_app_id = iha.id
|
||||
AND ihl.exclude = 0
|
||||
AND ihl.require_all = 1
|
||||
LEFT JOIN label_membership lm ON
|
||||
lm.label_id = ihl.label_id AND lm.host_id = :host_id
|
||||
GROUP BY
|
||||
iha.id
|
||||
HAVING
|
||||
count_installer_labels > 0 AND count_host_labels = count_installer_labels
|
||||
)
|
||||
SELECT
|
||||
iha.id AS in_house_id,
|
||||
|
|
@ -3833,12 +3905,15 @@ func filterInHouseAppsByLabel(
|
|||
ON include_any.in_house_app_id = iha.id
|
||||
LEFT JOIN exclude_any
|
||||
ON exclude_any.in_house_app_id = iha.id
|
||||
LEFT JOIN include_all
|
||||
ON include_all.in_house_app_id = iha.id
|
||||
WHERE
|
||||
iha.global_or_team_id = :global_or_team_id AND
|
||||
iha.id IN (:in_house_ids) AND (
|
||||
no_labels.in_house_app_id IS NOT NULL OR
|
||||
include_any.in_house_app_id IS NOT NULL OR
|
||||
exclude_any.in_house_app_id IS NOT NULL
|
||||
exclude_any.in_house_app_id IS NOT NULL OR
|
||||
include_all.in_house_app_id IS NOT NULL
|
||||
)
|
||||
`
|
||||
|
||||
|
|
@ -4720,7 +4795,7 @@ func (ds *Datastore) ListHostSoftware(ctx context.Context, host *fleet.Host, opt
|
|||
|
||||
SELECT 1 FROM (
|
||||
|
||||
-- no labels
|
||||
-- no labels for any type of installer
|
||||
SELECT 0 AS count_installer_labels, 0 AS count_host_labels, 0 as count_host_updated_after_labels
|
||||
WHERE
|
||||
NOT EXISTS (SELECT 1 FROM software_installer_labels sil WHERE sil.software_installer_id = si.id) AND
|
||||
|
|
@ -4741,6 +4816,7 @@ func (ds *Datastore) ListHostSoftware(ctx context.Context, host *fleet.Host, opt
|
|||
WHERE
|
||||
sil.software_installer_id = si.id
|
||||
AND sil.exclude = 0
|
||||
AND sil.require_all = 0
|
||||
HAVING
|
||||
count_installer_labels > 0 AND count_host_labels > 0
|
||||
|
||||
|
|
@ -4766,11 +4842,30 @@ func (ds *Datastore) ListHostSoftware(ctx context.Context, host *fleet.Host, opt
|
|||
WHERE
|
||||
sil.software_installer_id = si.id
|
||||
AND sil.exclude = 1
|
||||
AND sil.require_all = 0
|
||||
HAVING
|
||||
count_installer_labels > 0 AND count_installer_labels = count_host_updated_after_labels AND count_host_labels = 0
|
||||
|
||||
UNION
|
||||
|
||||
-- include all for software installers
|
||||
SELECT
|
||||
COUNT(*) AS count_installer_labels,
|
||||
COUNT(lm.label_id) AS count_host_labels,
|
||||
0 as count_host_updated_after_labels
|
||||
FROM
|
||||
software_installer_labels sil
|
||||
LEFT OUTER JOIN label_membership lm ON lm.label_id = sil.label_id
|
||||
AND lm.host_id = :host_id
|
||||
WHERE
|
||||
sil.software_installer_id = si.id
|
||||
AND sil.exclude = 0
|
||||
AND sil.require_all = 1
|
||||
HAVING
|
||||
count_installer_labels > 0 AND count_host_labels = count_installer_labels
|
||||
|
||||
UNION
|
||||
|
||||
-- include any for VPP apps
|
||||
SELECT
|
||||
COUNT(*) AS count_installer_labels,
|
||||
|
|
@ -4783,6 +4878,7 @@ func (ds *Datastore) ListHostSoftware(ctx context.Context, host *fleet.Host, opt
|
|||
WHERE
|
||||
vatl.vpp_app_team_id = vat.id
|
||||
AND vatl.exclude = 0
|
||||
AND vatl.require_all = 0
|
||||
HAVING
|
||||
count_installer_labels > 0 AND count_host_labels > 0
|
||||
|
||||
|
|
@ -4805,11 +4901,30 @@ func (ds *Datastore) ListHostSoftware(ctx context.Context, host *fleet.Host, opt
|
|||
WHERE
|
||||
vatl.vpp_app_team_id = vat.id
|
||||
AND vatl.exclude = 1
|
||||
AND vatl.require_all = 0
|
||||
HAVING
|
||||
count_installer_labels > 0 AND count_installer_labels = count_host_updated_after_labels AND count_host_labels = 0
|
||||
|
||||
UNION
|
||||
|
||||
-- include all for VPP apps
|
||||
SELECT
|
||||
COUNT(*) AS count_installer_labels,
|
||||
COUNT(lm.label_id) AS count_host_labels,
|
||||
0 as count_host_updated_after_labels
|
||||
FROM
|
||||
vpp_app_team_labels vatl
|
||||
LEFT OUTER JOIN label_membership lm ON lm.label_id = vatl.label_id
|
||||
AND lm.host_id = :host_id
|
||||
WHERE
|
||||
vatl.vpp_app_team_id = vat.id
|
||||
AND vatl.exclude = 0
|
||||
AND vatl.require_all = 1
|
||||
HAVING
|
||||
count_installer_labels > 0 AND count_host_labels = count_installer_labels
|
||||
|
||||
UNION
|
||||
|
||||
-- include any for in-house apps
|
||||
SELECT
|
||||
COUNT(*) AS count_installer_labels,
|
||||
|
|
@ -4821,6 +4936,7 @@ func (ds *Datastore) ListHostSoftware(ctx context.Context, host *fleet.Host, opt
|
|||
WHERE
|
||||
ihl.in_house_app_id = iha.id
|
||||
AND ihl.exclude = 0
|
||||
AND ihl.require_all = 0
|
||||
HAVING
|
||||
count_installer_labels > 0 AND count_host_labels > 0
|
||||
|
||||
|
|
@ -4839,10 +4955,28 @@ func (ds *Datastore) ListHostSoftware(ctx context.Context, host *fleet.Host, opt
|
|||
LEFT OUTER JOIN labels lbl ON lbl.id = ihl.label_id
|
||||
LEFT OUTER JOIN label_membership lm ON lm.label_id = ihl.label_id AND lm.host_id = :host_id
|
||||
WHERE
|
||||
ihl.in_house_app_id = iha.id AND
|
||||
ihl.exclude = 1
|
||||
ihl.in_house_app_id = iha.id
|
||||
AND ihl.exclude = 1
|
||||
AND ihl.require_all = 0
|
||||
HAVING
|
||||
count_installer_labels > 0 AND count_installer_labels = count_host_updated_after_labels AND count_host_labels = 0
|
||||
|
||||
UNION
|
||||
|
||||
-- include all for in-house apps
|
||||
SELECT
|
||||
COUNT(*) AS count_installer_labels,
|
||||
COUNT(lm.label_id) AS count_host_labels,
|
||||
0 as count_host_updated_after_labels
|
||||
FROM
|
||||
in_house_app_labels ihl
|
||||
LEFT OUTER JOIN label_membership lm ON lm.label_id = ihl.label_id AND lm.host_id = :host_id
|
||||
WHERE
|
||||
ihl.in_house_app_id = iha.id
|
||||
AND ihl.exclude = 0
|
||||
AND ihl.require_all = 1
|
||||
HAVING
|
||||
count_installer_labels > 0 AND count_host_labels = count_installer_labels
|
||||
) t
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -617,23 +617,29 @@ func setOrUpdateSoftwareInstallerLabelsDB(ctx context.Context, tx sqlx.ExtContex
|
|||
|
||||
// insert new labels
|
||||
if len(labelIds) > 0 {
|
||||
var exclude bool
|
||||
var exclude, requireAll bool
|
||||
switch labels.LabelScope {
|
||||
case fleet.LabelScopeIncludeAny:
|
||||
exclude = false
|
||||
requireAll = false
|
||||
case fleet.LabelScopeExcludeAny:
|
||||
exclude = true
|
||||
requireAll = false
|
||||
case fleet.LabelScopeIncludeAll:
|
||||
exclude = false
|
||||
requireAll = true
|
||||
default:
|
||||
// this should never happen
|
||||
return ctxerr.New(ctx, "invalid label scope")
|
||||
}
|
||||
|
||||
stmt := `INSERT INTO %[1]s_labels (%[1]s_id, label_id, exclude) VALUES %s ON DUPLICATE KEY UPDATE exclude = VALUES(exclude)`
|
||||
stmt := `INSERT INTO %[1]s_labels (%[1]s_id, label_id, exclude, require_all) VALUES %s
|
||||
ON DUPLICATE KEY UPDATE exclude = VALUES(exclude), require_all = VALUES(require_all)`
|
||||
var placeholders string
|
||||
var insertArgs []interface{}
|
||||
for _, lid := range labelIds {
|
||||
placeholders += "(?, ?, ?),"
|
||||
insertArgs = append(insertArgs, installerID, lid, exclude)
|
||||
placeholders += "(?, ?, ?, ?),"
|
||||
insertArgs = append(insertArgs, installerID, lid, exclude, requireAll)
|
||||
}
|
||||
placeholders = strings.TrimSuffix(placeholders, ",")
|
||||
|
||||
|
|
@ -1036,21 +1042,32 @@ LIMIT 1`,
|
|||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "get software installer labels")
|
||||
}
|
||||
var exclAny, inclAny []fleet.SoftwareScopeLabel
|
||||
var exclAny, inclAny, inclAll []fleet.SoftwareScopeLabel
|
||||
for _, l := range labels {
|
||||
if l.Exclude {
|
||||
switch {
|
||||
case l.Exclude && !l.RequireAll:
|
||||
exclAny = append(exclAny, l)
|
||||
} else {
|
||||
case !l.Exclude && l.RequireAll:
|
||||
inclAll = append(inclAll, l)
|
||||
case !l.Exclude && !l.RequireAll:
|
||||
inclAny = append(inclAny, l)
|
||||
default:
|
||||
ds.logger.WarnContext(ctx, "software installer has an unsupported label scope", "installer_id", dest.InstallerID, "invalid_label", fmt.Sprintf("%#v", l))
|
||||
}
|
||||
}
|
||||
|
||||
if len(inclAny) > 0 && len(exclAny) > 0 {
|
||||
// there's a bug somewhere
|
||||
ds.logger.WarnContext(ctx, "software installer has both include and exclude labels", "installer_id", dest.InstallerID, "include", fmt.Sprintf("%v", inclAny), "exclude", fmt.Sprintf("%v", exclAny))
|
||||
var count int
|
||||
for _, set := range [][]fleet.SoftwareScopeLabel{exclAny, inclAny, inclAll} {
|
||||
if len(set) > 0 {
|
||||
count++
|
||||
}
|
||||
}
|
||||
if count > 1 {
|
||||
ds.logger.WarnContext(ctx, "software installer has more than one scope of labels", "installer_id", dest.InstallerID, "include_any", fmt.Sprintf("%v", inclAny), "exclude_any", fmt.Sprintf("%v", exclAny), "include_all", fmt.Sprintf("%v", inclAll))
|
||||
}
|
||||
dest.LabelsExcludeAny = exclAny
|
||||
dest.LabelsIncludeAny = inclAny
|
||||
dest.LabelsIncludeAll = inclAll
|
||||
|
||||
categoryMap, err := ds.GetCategoriesForSoftwareTitles(ctx, []uint{titleID}, teamID)
|
||||
if err != nil {
|
||||
|
|
@ -1093,7 +1110,8 @@ SELECT
|
|||
label_id,
|
||||
exclude,
|
||||
l.name as label_name,
|
||||
si.title_id
|
||||
si.title_id,
|
||||
require_all
|
||||
FROM
|
||||
%[1]s_labels sil
|
||||
JOIN %[1]ss si ON si.id = sil.%[1]s_id
|
||||
|
|
@ -2339,18 +2357,21 @@ INSERT INTO
|
|||
software_installer_labels (
|
||||
software_installer_id,
|
||||
label_id,
|
||||
exclude
|
||||
exclude,
|
||||
require_all
|
||||
)
|
||||
VALUES
|
||||
%s
|
||||
ON DUPLICATE KEY UPDATE
|
||||
exclude = VALUES(exclude)
|
||||
exclude = VALUES(exclude),
|
||||
require_all = VALUES(require_all)
|
||||
`
|
||||
|
||||
const loadExistingInstallerLabels = `
|
||||
SELECT
|
||||
label_id,
|
||||
exclude
|
||||
exclude,
|
||||
require_all
|
||||
FROM
|
||||
software_installer_labels
|
||||
WHERE
|
||||
|
|
@ -2893,13 +2914,15 @@ WHERE
|
|||
}
|
||||
|
||||
excludeLabels := installer.ValidatedLabels.LabelScope == fleet.LabelScopeExcludeAny
|
||||
requireAllLabels := installer.ValidatedLabels.LabelScope == fleet.LabelScopeIncludeAll
|
||||
if len(existing) > 0 && !existing[0].IsMetadataModified {
|
||||
// load the remaining labels for that installer, so that we can detect
|
||||
// if any label changed (if the counts differ, then labels did change,
|
||||
// otherwise if the exclude bool changed, the target did change).
|
||||
// otherwise if the exclude/require all bool changed, the target did change).
|
||||
var existingLabels []struct {
|
||||
LabelID uint `db:"label_id"`
|
||||
Exclude bool `db:"exclude"`
|
||||
LabelID uint `db:"label_id"`
|
||||
Exclude bool `db:"exclude"`
|
||||
RequireAll bool `db:"require_all"`
|
||||
}
|
||||
if err := sqlx.SelectContext(ctx, tx, &existingLabels, loadExistingInstallerLabels, installerID); err != nil {
|
||||
return ctxerr.Wrapf(ctx, err, "load existing labels for installer with name %q", installer.Filename)
|
||||
|
|
@ -2908,8 +2931,8 @@ WHERE
|
|||
if len(existingLabels) != len(labelIDs) {
|
||||
existing[0].IsMetadataModified = true
|
||||
}
|
||||
if len(existingLabels) > 0 && existingLabels[0].Exclude != excludeLabels {
|
||||
// same labels are provided, but the include <-> exclude changed
|
||||
if len(existingLabels) > 0 && (existingLabels[0].Exclude != excludeLabels || existingLabels[0].RequireAll != requireAllLabels) {
|
||||
// same labels are provided, but the include <-> exclude or require all changed
|
||||
existing[0].IsMetadataModified = true
|
||||
}
|
||||
}
|
||||
|
|
@ -2917,9 +2940,9 @@ WHERE
|
|||
// upsert the new labels now that obsolete ones have been deleted
|
||||
var upsertLabelArgs []any
|
||||
for _, lblID := range labelIDs {
|
||||
upsertLabelArgs = append(upsertLabelArgs, installerID, lblID, excludeLabels)
|
||||
upsertLabelArgs = append(upsertLabelArgs, installerID, lblID, excludeLabels, requireAllLabels)
|
||||
}
|
||||
upsertLabelValues := strings.TrimSuffix(strings.Repeat("(?,?,?),", len(installer.ValidatedLabels.ByName)), ",")
|
||||
upsertLabelValues := strings.TrimSuffix(strings.Repeat("(?,?,?,?),", len(installer.ValidatedLabels.ByName)), ",")
|
||||
|
||||
_, err = tx.ExecContext(ctx, fmt.Sprintf(upsertInstallerLabels, upsertLabelValues), upsertLabelArgs...)
|
||||
if err != nil {
|
||||
|
|
@ -3203,6 +3226,7 @@ func (ds *Datastore) isSoftwareLabelScoped(ctx context.Context, softwareID, host
|
|||
WHERE
|
||||
sil.%[1]s_id = :software_id
|
||||
AND sil.exclude = 0
|
||||
AND sil.require_all = 0
|
||||
HAVING
|
||||
count_installer_labels > 0 AND count_host_labels > 0
|
||||
|
||||
|
|
@ -3232,8 +3256,27 @@ func (ds *Datastore) isSoftwareLabelScoped(ctx context.Context, softwareID, host
|
|||
WHERE
|
||||
sil.%[1]s_id = :software_id
|
||||
AND sil.exclude = 1
|
||||
AND sil.require_all = 0
|
||||
HAVING
|
||||
count_installer_labels > 0 AND count_installer_labels = count_host_updated_after_labels AND count_host_labels = 0
|
||||
|
||||
UNION
|
||||
|
||||
-- include all
|
||||
SELECT
|
||||
COUNT(*) AS count_installer_labels,
|
||||
COUNT(lm.label_id) AS count_host_labels,
|
||||
0 as count_host_updated_after_labels
|
||||
FROM
|
||||
%[1]s_labels sil
|
||||
LEFT OUTER JOIN label_membership lm ON lm.label_id = sil.label_id
|
||||
AND lm.host_id = :host_id
|
||||
WHERE
|
||||
sil.%[1]s_id = :software_id
|
||||
AND sil.exclude = 0
|
||||
AND sil.require_all = 1
|
||||
HAVING
|
||||
count_installer_labels > 0 AND count_host_labels = count_installer_labels
|
||||
) t
|
||||
`
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ import (
|
|||
"encoding/hex"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"maps"
|
||||
"math/rand"
|
||||
std_slices "slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -7630,7 +7632,8 @@ func testListHostSoftwareWithLabelScoping(t *testing.T, ds *Datastore) {
|
|||
require.False(t, ok)
|
||||
continue
|
||||
}
|
||||
require.True(t, ok)
|
||||
names := std_slices.Collect(maps.Keys(expectedInstallers))
|
||||
require.Truef(t, ok, "didn't find installer for %s in expectedInstallers (%s)", got.SoftwarePackage.Name, strings.Join(names, ", "))
|
||||
require.Equal(t, want, got.SoftwarePackage)
|
||||
}
|
||||
}
|
||||
|
|
@ -7971,6 +7974,65 @@ func testListHostSoftwareWithLabelScoping(t *testing.T, ds *Datastore) {
|
|||
software, _, err = ds.ListHostSoftware(ctx, host, opts)
|
||||
require.NoError(t, err)
|
||||
checkSoftware(software, installer2.Filename, installer3.Filename, installer4.Filename)
|
||||
|
||||
t.Run("include_all", func(t *testing.T) {
|
||||
|
||||
hostIncludeAll := test.NewHost(t, ds, "host_include_all", "", "host1key_include_all", "host1uuid_include_all", time.Now(), test.WithPlatform("darwin"))
|
||||
nanoEnroll(t, ds, hostIncludeAll, false)
|
||||
|
||||
label4, err := ds.NewLabel(ctx, &fleet.Label{Name: "label4" + t.Name()})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Scope installer1 to include_all: [label1, label4].
|
||||
// hostIncludeAll has neither label yet, so installer1 should be out of scope.
|
||||
err = setOrUpdateSoftwareInstallerLabelsDB(ctx, ds.writer(ctx), installerID1, fleet.LabelIdentsWithScope{
|
||||
LabelScope: fleet.LabelScopeIncludeAll,
|
||||
ByName: map[string]fleet.LabelIdent{label1.Name: {LabelName: label1.Name, LabelID: label1.ID}, label4.Name: {LabelName: label4.Name, LabelID: label4.ID}},
|
||||
}, softwareTypeInstaller)
|
||||
require.NoError(t, err)
|
||||
|
||||
// host has no labels yet — installer1 is out of scope
|
||||
scoped, err := ds.IsSoftwareInstallerLabelScoped(ctx, installerID1, hostIncludeAll.ID)
|
||||
require.NoError(t, err)
|
||||
require.False(t, scoped)
|
||||
|
||||
software, _, err = ds.ListHostSoftware(ctx, hostIncludeAll, opts)
|
||||
require.NoError(t, err)
|
||||
// installer1 should be absent (out of scope), installer4 absent (no labels on host)
|
||||
checkSoftware(software, installer1.Filename, installer4.Filename)
|
||||
|
||||
// add only label1: host still missing label4, so still out of scope
|
||||
require.NoError(t, ds.AddLabelsToHost(ctx, hostIncludeAll.ID, []uint{label1.ID}))
|
||||
hostIncludeAll.LabelUpdatedAt = time.Now()
|
||||
err = ds.UpdateHost(ctx, hostIncludeAll)
|
||||
require.NoError(t, err)
|
||||
time.Sleep(time.Second)
|
||||
|
||||
scoped, err = ds.IsSoftwareInstallerLabelScoped(ctx, installerID1, hostIncludeAll.ID)
|
||||
require.NoError(t, err)
|
||||
require.False(t, scoped)
|
||||
|
||||
software, _, err = ds.ListHostSoftware(ctx, hostIncludeAll, opts)
|
||||
require.NoError(t, err)
|
||||
checkSoftware(software, installer1.Filename, installer4.Filename)
|
||||
|
||||
// add label4 — host now has both required labels, so installer1 is in scope
|
||||
require.NoError(t, ds.AddLabelsToHost(ctx, hostIncludeAll.ID, []uint{label4.ID}))
|
||||
hostIncludeAll.LabelUpdatedAt = time.Now()
|
||||
err = ds.UpdateHost(ctx, hostIncludeAll)
|
||||
require.NoError(t, err)
|
||||
time.Sleep(time.Second)
|
||||
|
||||
scoped, err = ds.IsSoftwareInstallerLabelScoped(ctx, installerID1, hostIncludeAll.ID)
|
||||
require.NoError(t, err)
|
||||
require.True(t, scoped)
|
||||
|
||||
software, _, err = ds.ListHostSoftware(ctx, hostIncludeAll, opts)
|
||||
require.NoError(t, err)
|
||||
// installer1 is now in scope; installer4 still absent (no labels on host match it)
|
||||
checkSoftware(software, installer4.Filename)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func testListHostSoftwareVulnerableAndVPP(t *testing.T, ds *Datastore) {
|
||||
|
|
@ -9094,6 +9156,63 @@ func testListHostSoftwareWithLabelScopingVPP(t *testing.T, ds *Datastore) {
|
|||
scoped, err = ds.IsVPPAppLabelScoped(ctx, vppApp.VPPAppTeam.AppTeamID, host.ID)
|
||||
require.NoError(t, err)
|
||||
require.True(t, scoped)
|
||||
|
||||
// --- include_all tests for VPP ---
|
||||
// Create two fresh labels for the include_all scope tests.
|
||||
label5, err := ds.NewLabel(ctx, &fleet.Label{Name: "label5" + t.Name()})
|
||||
require.NoError(t, err)
|
||||
label6, err := ds.NewLabel(ctx, &fleet.Label{Name: "label6" + t.Name()})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Scope the VPP app to include_all: [label5, label6].
|
||||
// host currently has label1 but not label5 or label6.
|
||||
err = setOrUpdateSoftwareInstallerLabelsDB(ctx, ds.writer(ctx), vppAppTeamID, fleet.LabelIdentsWithScope{
|
||||
LabelScope: fleet.LabelScopeIncludeAll,
|
||||
ByName: map[string]fleet.LabelIdent{
|
||||
label5.Name: {LabelName: label5.Name, LabelID: label5.ID},
|
||||
label6.Name: {LabelName: label6.Name, LabelID: label6.ID},
|
||||
},
|
||||
}, softwareTypeVPP)
|
||||
require.NoError(t, err)
|
||||
|
||||
// host has neither required label — out of scope
|
||||
scoped, err = ds.IsVPPAppLabelScoped(ctx, vppApp.VPPAppTeam.AppTeamID, host.ID)
|
||||
require.NoError(t, err)
|
||||
require.False(t, scoped)
|
||||
|
||||
software, _, err = ds.ListHostSoftware(ctx, host, opts)
|
||||
require.NoError(t, err)
|
||||
checkSoftware(software, vppApp.Name)
|
||||
|
||||
// add label5 only — still missing label6, so still out of scope
|
||||
require.NoError(t, ds.AddLabelsToHost(ctx, host.ID, []uint{label5.ID}))
|
||||
host.LabelUpdatedAt = time.Now()
|
||||
err = ds.UpdateHost(ctx, host)
|
||||
require.NoError(t, err)
|
||||
time.Sleep(time.Second)
|
||||
|
||||
scoped, err = ds.IsVPPAppLabelScoped(ctx, vppApp.VPPAppTeam.AppTeamID, host.ID)
|
||||
require.NoError(t, err)
|
||||
require.False(t, scoped)
|
||||
|
||||
software, _, err = ds.ListHostSoftware(ctx, host, opts)
|
||||
require.NoError(t, err)
|
||||
checkSoftware(software, vppApp.Name)
|
||||
|
||||
// add label6 — host now has both required labels, so VPP app is in scope
|
||||
require.NoError(t, ds.AddLabelsToHost(ctx, host.ID, []uint{label6.ID}))
|
||||
host.LabelUpdatedAt = time.Now()
|
||||
err = ds.UpdateHost(ctx, host)
|
||||
require.NoError(t, err)
|
||||
time.Sleep(time.Second)
|
||||
|
||||
scoped, err = ds.IsVPPAppLabelScoped(ctx, vppApp.VPPAppTeam.AppTeamID, host.ID)
|
||||
require.NoError(t, err)
|
||||
require.True(t, scoped)
|
||||
|
||||
software, _, err = ds.ListHostSoftware(ctx, host, opts)
|
||||
require.NoError(t, err)
|
||||
checkSoftware(software)
|
||||
}
|
||||
|
||||
func testListHostSoftwareLastOpenedAt(t *testing.T, ds *Datastore) {
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ func (ds *Datastore) DeleteSoftwareTitleIcon(ctx context.Context, teamID, titleI
|
|||
|
||||
func (ds *Datastore) DeleteIconsAssociatedWithTitlesWithoutInstallers(ctx context.Context, teamID uint) error {
|
||||
_, err := ds.writer(ctx).ExecContext(ctx, `DELETE FROM software_title_icons WHERE team_id = ?
|
||||
AND software_title_id NOT IN (SELECT title_id FROM vpp_apps va JOIN vpp_apps_teams vat
|
||||
AND software_title_id NOT IN (SELECT title_id FROM vpp_apps va JOIN vpp_apps_teams vat
|
||||
ON vat.adam_id = va.adam_id AND vat.platform = va.platform WHERE global_or_team_id = ?)
|
||||
AND software_title_id NOT IN (SELECT title_id FROM software_installers WHERE global_or_team_id = ?)
|
||||
AND software_title_id NOT IN (SELECT title_id FROM in_house_apps WHERE global_or_team_id = ?)`,
|
||||
|
|
@ -172,9 +172,10 @@ func (ds *Datastore) ActivityDetailsForSoftwareTitleIcon(ctx context.Context, te
|
|||
}
|
||||
|
||||
type ActivitySoftwareLabel struct {
|
||||
ID uint `db:"id"`
|
||||
Name string `db:"name"`
|
||||
Exclude bool `db:"exclude"`
|
||||
ID uint `db:"id"`
|
||||
Name string `db:"name"`
|
||||
Exclude bool `db:"exclude"`
|
||||
RequireAll bool `db:"require_all"`
|
||||
}
|
||||
var labels []ActivitySoftwareLabel
|
||||
if details.SoftwareInstallerID != nil {
|
||||
|
|
@ -182,7 +183,8 @@ func (ds *Datastore) ActivityDetailsForSoftwareTitleIcon(ctx context.Context, te
|
|||
SELECT
|
||||
labels.id AS id,
|
||||
labels.name AS name,
|
||||
software_installer_labels.exclude AS exclude
|
||||
software_installer_labels.exclude AS exclude,
|
||||
software_installer_labels.require_all AS require_all
|
||||
FROM software_installer_labels
|
||||
INNER JOIN labels ON software_installer_labels.label_id = labels.id
|
||||
WHERE software_installer_id = ?
|
||||
|
|
@ -196,7 +198,8 @@ func (ds *Datastore) ActivityDetailsForSoftwareTitleIcon(ctx context.Context, te
|
|||
SELECT
|
||||
labels.id AS id,
|
||||
labels.name AS name,
|
||||
vpp_app_team_labels.exclude AS exclude
|
||||
vpp_app_team_labels.exclude AS exclude,
|
||||
vpp_app_team_labels.require_all AS require_all
|
||||
FROM vpp_app_team_labels
|
||||
INNER JOIN labels ON vpp_app_team_labels.label_id = labels.id
|
||||
WHERE vpp_app_team_id = ?
|
||||
|
|
@ -210,7 +213,8 @@ func (ds *Datastore) ActivityDetailsForSoftwareTitleIcon(ctx context.Context, te
|
|||
SELECT
|
||||
labels.id AS id,
|
||||
labels.name AS name,
|
||||
in_house_app_labels.exclude AS exclude
|
||||
in_house_app_labels.exclude AS exclude,
|
||||
in_house_app_labels.require_all AS require_all
|
||||
FROM in_house_app_labels
|
||||
INNER JOIN labels ON in_house_app_labels.label_id = labels.id
|
||||
WHERE in_house_app_id = ?
|
||||
|
|
@ -221,16 +225,28 @@ func (ds *Datastore) ActivityDetailsForSoftwareTitleIcon(ctx context.Context, te
|
|||
}
|
||||
|
||||
for _, l := range labels {
|
||||
if l.Exclude {
|
||||
switch {
|
||||
case l.Exclude && !l.RequireAll:
|
||||
details.LabelsExcludeAny = append(details.LabelsExcludeAny, fleet.ActivitySoftwareLabel{
|
||||
ID: l.ID,
|
||||
Name: l.Name,
|
||||
})
|
||||
} else {
|
||||
|
||||
case !l.Exclude && l.RequireAll:
|
||||
details.LabelsIncludeAll = append(details.LabelsIncludeAll, fleet.ActivitySoftwareLabel{
|
||||
ID: l.ID,
|
||||
Name: l.Name,
|
||||
})
|
||||
|
||||
case !l.Exclude && !l.RequireAll:
|
||||
details.LabelsIncludeAny = append(details.LabelsIncludeAny, fleet.ActivitySoftwareLabel{
|
||||
ID: l.ID,
|
||||
Name: l.Name,
|
||||
})
|
||||
|
||||
default:
|
||||
// should never happen, we don't support ExcludeAll currently
|
||||
ds.logger.ErrorContext(ctx, "unsupported label condition 'exclude-all' encountered for software", "title_id", titleID, "label_id", l.ID)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -303,11 +303,18 @@ func testActivityDetailsForSoftwareTitleIcon(t *testing.T, ds *Datastore) {
|
|||
"INSERT INTO software_installer_labels (software_installer_id, label_id, exclude) VALUES (?, ?, ?)",
|
||||
installerID, label1.ID, true)
|
||||
require.NoError(t, err)
|
||||
// Insert include label
|
||||
// Insert include any label
|
||||
_, err = ds.writer(ctx).ExecContext(ctx,
|
||||
"INSERT INTO software_installer_labels (software_installer_id, label_id, exclude) VALUES (?, ?, ?)",
|
||||
installerID, label2.ID, false)
|
||||
require.NoError(t, err)
|
||||
// Insert include all label
|
||||
label3, err := ds.NewLabel(ctx, &fleet.Label{Name: "label3"})
|
||||
require.NoError(t, err)
|
||||
_, err = ds.writer(ctx).ExecContext(ctx,
|
||||
"INSERT INTO software_installer_labels (software_installer_id, label_id, exclude, require_all) VALUES (?, ?, ?, ?)",
|
||||
installerID, label3.ID, false, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = ds.CreateOrUpdateSoftwareTitleIcon(ctx, &fleet.UploadSoftwareTitleIconPayload{
|
||||
TeamID: teamID,
|
||||
|
|
@ -335,6 +342,8 @@ func testActivityDetailsForSoftwareTitleIcon(t *testing.T, ds *Datastore) {
|
|||
require.Equal(t, "label1", activity.LabelsExcludeAny[0].Name)
|
||||
require.Len(t, activity.LabelsIncludeAny, 1)
|
||||
require.Equal(t, "label2", activity.LabelsIncludeAny[0].Name)
|
||||
require.Len(t, activity.LabelsIncludeAll, 1)
|
||||
require.Equal(t, "label3", activity.LabelsIncludeAll[0].Name)
|
||||
}},
|
||||
{"vpp app", func(ds *Datastore) {
|
||||
teamID, titleID, err = createTeamAndSoftwareTitle(t, ctx, ds)
|
||||
|
|
@ -381,11 +390,18 @@ func testActivityDetailsForSoftwareTitleIcon(t *testing.T, ds *Datastore) {
|
|||
"INSERT INTO vpp_app_team_labels (vpp_app_team_id, label_id, exclude) VALUES (?, ?, ?)",
|
||||
vppApp.VPPAppTeam.AppTeamID, label1.ID, true)
|
||||
require.NoError(t, err)
|
||||
// Insert include label
|
||||
// Insert include any label
|
||||
_, err = ds.writer(ctx).ExecContext(ctx,
|
||||
"INSERT INTO vpp_app_team_labels (vpp_app_team_id, label_id, exclude) VALUES (?, ?, ?)",
|
||||
vppApp.VPPAppTeam.AppTeamID, label2.ID, false)
|
||||
require.NoError(t, err)
|
||||
// Insert include all label
|
||||
label3, err := ds.NewLabel(ctx, &fleet.Label{Name: "label3"})
|
||||
require.NoError(t, err)
|
||||
_, err = ds.writer(ctx).ExecContext(ctx,
|
||||
"INSERT INTO vpp_app_team_labels (vpp_app_team_id, label_id, exclude, require_all) VALUES (?, ?, ?, ?)",
|
||||
vppApp.VPPAppTeam.AppTeamID, label3.ID, false, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = ds.CreateOrUpdateSoftwareTitleIcon(ctx, &fleet.UploadSoftwareTitleIconPayload{
|
||||
TeamID: teamID,
|
||||
|
|
@ -413,6 +429,8 @@ func testActivityDetailsForSoftwareTitleIcon(t *testing.T, ds *Datastore) {
|
|||
require.Equal(t, "label1", activity.LabelsExcludeAny[0].Name)
|
||||
require.Len(t, activity.LabelsIncludeAny, 1)
|
||||
require.Equal(t, "label2", activity.LabelsIncludeAny[0].Name)
|
||||
require.Len(t, activity.LabelsIncludeAll, 1)
|
||||
require.Equal(t, "label3", activity.LabelsIncludeAll[0].Name)
|
||||
}},
|
||||
{"team id 0", func(ds *Datastore) {
|
||||
user := test.NewUser(t, ds, "user1", "user1@example.com", false)
|
||||
|
|
@ -480,6 +498,7 @@ func testActivityDetailsForSoftwareTitleIcon(t *testing.T, ds *Datastore) {
|
|||
require.Nil(t, activity.Platform)
|
||||
require.Nil(t, activity.LabelsExcludeAny)
|
||||
require.Nil(t, activity.LabelsIncludeAny)
|
||||
require.Nil(t, activity.LabelsIncludeAll)
|
||||
}},
|
||||
{"in house app", func(ds *Datastore) {
|
||||
user := test.NewUser(t, ds, "user1", "user1@example.com", false)
|
||||
|
|
@ -519,11 +538,18 @@ func testActivityDetailsForSoftwareTitleIcon(t *testing.T, ds *Datastore) {
|
|||
"INSERT INTO in_house_app_labels (in_house_app_id, label_id, exclude) VALUES (?, ?, ?)",
|
||||
installerID, label1.ID, true)
|
||||
require.NoError(t, err)
|
||||
// Insert include label
|
||||
// Insert include any label
|
||||
_, err = ds.writer(ctx).ExecContext(ctx,
|
||||
"INSERT INTO in_house_app_labels (in_house_app_id, label_id, exclude) VALUES (?, ?, ?)",
|
||||
installerID, label2.ID, false)
|
||||
require.NoError(t, err)
|
||||
// Insert include all label
|
||||
label3, err := ds.NewLabel(ctx, &fleet.Label{Name: "label3"})
|
||||
require.NoError(t, err)
|
||||
_, err = ds.writer(ctx).ExecContext(ctx,
|
||||
"INSERT INTO in_house_app_labels (in_house_app_id, label_id, exclude, require_all) VALUES (?, ?, ?, ?)",
|
||||
installerID, label3.ID, false, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = ds.CreateOrUpdateSoftwareTitleIcon(ctx, &fleet.UploadSoftwareTitleIconPayload{
|
||||
TeamID: teamID,
|
||||
|
|
@ -551,6 +577,8 @@ func testActivityDetailsForSoftwareTitleIcon(t *testing.T, ds *Datastore) {
|
|||
require.Equal(t, "label1", activity.LabelsExcludeAny[0].Name)
|
||||
require.Len(t, activity.LabelsIncludeAny, 1)
|
||||
require.Equal(t, "label2", activity.LabelsIncludeAny[0].Name)
|
||||
require.Len(t, activity.LabelsIncludeAll, 1)
|
||||
require.Equal(t, "label3", activity.LabelsIncludeAll[0].Name)
|
||||
}},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -62,21 +62,32 @@ WHERE
|
|||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "get vpp app labels")
|
||||
}
|
||||
var exclAny, inclAny []fleet.SoftwareScopeLabel
|
||||
var exclAny, inclAny, inclAll []fleet.SoftwareScopeLabel
|
||||
for _, l := range labels {
|
||||
if l.Exclude {
|
||||
switch {
|
||||
case l.Exclude && !l.RequireAll:
|
||||
exclAny = append(exclAny, l)
|
||||
} else {
|
||||
case !l.Exclude && l.RequireAll:
|
||||
inclAll = append(inclAll, l)
|
||||
case !l.Exclude && !l.RequireAll:
|
||||
inclAny = append(inclAny, l)
|
||||
default:
|
||||
ds.logger.WarnContext(ctx, "vpp app has an unsupported label scope", "vpp_apps_teams_id", app.VPPAppsTeamsID, "invalid_label", fmt.Sprintf("%#v", l))
|
||||
}
|
||||
}
|
||||
|
||||
if len(inclAny) > 0 && len(exclAny) > 0 {
|
||||
// there's a bug somewhere
|
||||
ds.logger.WarnContext(ctx, "vpp app has both include and exclude labels", "vpp_apps_teams_id", app.VPPAppsTeamsID, "include", fmt.Sprintf("%v", inclAny), "exclude", fmt.Sprintf("%v", exclAny))
|
||||
var count int
|
||||
for _, set := range [][]fleet.SoftwareScopeLabel{exclAny, inclAny, inclAll} {
|
||||
if len(set) > 0 {
|
||||
count++
|
||||
}
|
||||
}
|
||||
if count > 1 {
|
||||
ds.logger.WarnContext(ctx, "vpp app has more than one scope of labels", "vpp_apps_teams_id", app.VPPAppsTeamsID, "include_any", fmt.Sprintf("%v", inclAny), "exclude_any", fmt.Sprintf("%v", exclAny), "include_all", fmt.Sprintf("%v", inclAll))
|
||||
}
|
||||
app.LabelsExcludeAny = exclAny
|
||||
app.LabelsIncludeAny = inclAny
|
||||
app.LabelsIncludeAll = inclAll
|
||||
|
||||
categories, err := ds.getCategoriesForVPPApp(ctx, app.VPPAppsTeamsID)
|
||||
if err != nil {
|
||||
|
|
@ -146,7 +157,8 @@ SELECT
|
|||
label_id,
|
||||
exclude,
|
||||
l.name AS label_name,
|
||||
va.title_id AS title_id
|
||||
va.title_id AS title_id,
|
||||
require_all
|
||||
FROM
|
||||
vpp_app_team_labels vatl
|
||||
JOIN vpp_apps_teams vat ON vat.id = vatl.vpp_app_team_id
|
||||
|
|
@ -344,18 +356,29 @@ func (ds *Datastore) getExistingLabels(ctx context.Context, vppAppTeamID uint) (
|
|||
}
|
||||
|
||||
var labels fleet.LabelIdentsWithScope
|
||||
var exclAny, inclAny []fleet.SoftwareScopeLabel
|
||||
var exclAny, inclAny, inclAll []fleet.SoftwareScopeLabel
|
||||
for _, l := range existingLabels {
|
||||
if l.Exclude {
|
||||
switch {
|
||||
case l.Exclude && !l.RequireAll:
|
||||
exclAny = append(exclAny, l)
|
||||
} else {
|
||||
case !l.Exclude && l.RequireAll:
|
||||
inclAll = append(inclAll, l)
|
||||
case !l.Exclude && !l.RequireAll:
|
||||
inclAny = append(inclAny, l)
|
||||
default:
|
||||
ds.logger.WarnContext(ctx, "vpp app has an unsupported existing label scope", "vpp_apps_teams_id", vppAppTeamID, "invalid_label", fmt.Sprintf("%#v", l))
|
||||
}
|
||||
}
|
||||
|
||||
if len(inclAny) > 0 && len(exclAny) > 0 {
|
||||
var count int
|
||||
for _, set := range [][]fleet.SoftwareScopeLabel{exclAny, inclAny, inclAll} {
|
||||
if len(set) > 0 {
|
||||
count++
|
||||
}
|
||||
}
|
||||
if count > 1 {
|
||||
// there's a bug somewhere
|
||||
return nil, ctxerr.New(ctx, "found both include and exclude labels on a vpp app")
|
||||
return nil, ctxerr.New(ctx, "found labels for more than one scope on a vpp app")
|
||||
}
|
||||
|
||||
switch {
|
||||
|
|
@ -374,6 +397,15 @@ func (ds *Datastore) getExistingLabels(ctx context.Context, vppAppTeamID uint) (
|
|||
labels.ByName[l.LabelName] = fleet.LabelIdent{LabelName: l.LabelName, LabelID: l.LabelID}
|
||||
}
|
||||
return &labels, nil
|
||||
|
||||
case len(inclAll) > 0:
|
||||
labels.LabelScope = fleet.LabelScopeIncludeAll
|
||||
labels.ByName = make(map[string]fleet.LabelIdent, len(inclAll))
|
||||
for _, l := range inclAll {
|
||||
labels.ByName[l.LabelName] = fleet.LabelIdent{LabelName: l.LabelName, LabelID: l.LabelID}
|
||||
}
|
||||
return &labels, nil
|
||||
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
|
|
@ -2329,9 +2361,8 @@ FROM (
|
|||
vpp_app_team_labels vatl
|
||||
LEFT JOIN vpp_apps_teams ON vpp_apps_teams.id = vatl.vpp_app_team_id
|
||||
JOIN hosts ON hosts.id = ? AND hosts.team_id <=> vpp_apps_teams.team_id
|
||||
LEFT OUTER JOIN label_membership lm ON lm.label_id = vatl.label_id
|
||||
AND lm.host_id = ?
|
||||
WHERE vatl.exclude = 0 AND vpp_apps_teams.platform = 'android'
|
||||
LEFT OUTER JOIN label_membership lm ON lm.label_id = vatl.label_id AND lm.host_id = ?
|
||||
WHERE vatl.exclude = 0 AND vatl.require_all = 0 AND vpp_apps_teams.platform = 'android'
|
||||
GROUP BY installable_id
|
||||
HAVING
|
||||
count_installer_labels > 0
|
||||
|
|
@ -2364,20 +2395,39 @@ FROM (
|
|||
vpp_apps_teams.adam_id AS installable_id
|
||||
FROM
|
||||
vpp_app_team_labels vatl
|
||||
LEFT JOIN vpp_apps_teams ON vpp_apps_teams.id = vatl.vpp_app_team_id
|
||||
JOIN hosts ON hosts.id = ? AND hosts.team_id <=> vpp_apps_teams.team_id
|
||||
LEFT OUTER JOIN labels lbl ON lbl.id = vatl.label_id
|
||||
LEFT OUTER JOIN label_membership lm ON lm.label_id = vatl.label_id
|
||||
AND lm.host_id = ?
|
||||
WHERE vatl.exclude = 1 AND vpp_apps_teams.platform = 'android'
|
||||
LEFT JOIN vpp_apps_teams ON vpp_apps_teams.id = vatl.vpp_app_team_id
|
||||
JOIN hosts ON hosts.id = ? AND hosts.team_id <=> vpp_apps_teams.team_id
|
||||
LEFT OUTER JOIN labels lbl ON lbl.id = vatl.label_id
|
||||
LEFT OUTER JOIN label_membership lm ON lm.label_id = vatl.label_id AND lm.host_id = ?
|
||||
WHERE vatl.exclude = 1 AND vatl.require_all = 0 AND vpp_apps_teams.platform = 'android'
|
||||
GROUP BY installable_id
|
||||
HAVING
|
||||
count_installer_labels > 0
|
||||
AND count_installer_labels = count_host_updated_after_labels
|
||||
AND count_host_labels = 0) t;
|
||||
AND count_host_labels = 0
|
||||
|
||||
UNION
|
||||
|
||||
-- include all
|
||||
SELECT
|
||||
COUNT(*) AS count_installer_labels,
|
||||
COUNT(lm.label_id) AS count_host_labels,
|
||||
0 AS count_host_updated_after_labels,
|
||||
vpp_apps_teams.adam_id AS installable_id
|
||||
FROM
|
||||
vpp_app_team_labels vatl
|
||||
LEFT JOIN vpp_apps_teams ON vpp_apps_teams.id = vatl.vpp_app_team_id
|
||||
JOIN hosts ON hosts.id = ? AND hosts.team_id <=> vpp_apps_teams.team_id
|
||||
LEFT OUTER JOIN label_membership lm ON lm.label_id = vatl.label_id AND lm.host_id = ?
|
||||
WHERE vatl.exclude = 0 AND vatl.require_all = 1 AND vpp_apps_teams.platform = 'android'
|
||||
GROUP BY installable_id
|
||||
HAVING
|
||||
count_installer_labels > 0
|
||||
AND count_host_labels = count_installer_labels
|
||||
) t
|
||||
`
|
||||
|
||||
err = sqlx.SelectContext(ctx, ds.reader(ctx), &applicationIDs, stmt, hostID, hostID, hostID, hostID, hostID, hostID)
|
||||
err = sqlx.SelectContext(ctx, ds.reader(ctx), &applicationIDs, stmt, hostID, hostID, hostID, hostID, hostID, hostID, hostID, hostID)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "get in android apps in scope for host")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -575,6 +575,7 @@ func testVPPApps(t *testing.T, ds *Datastore) {
|
|||
|
||||
require.Len(t, meta.LabelsIncludeAny, 2)
|
||||
require.Len(t, meta.LabelsExcludeAny, 0)
|
||||
require.Len(t, meta.LabelsIncludeAll, 0)
|
||||
|
||||
// insert a VPP app with exclude_any labels
|
||||
labeledApp = &fleet.VPPApp{
|
||||
|
|
@ -599,6 +600,7 @@ func testVPPApps(t *testing.T, ds *Datastore) {
|
|||
|
||||
require.Len(t, meta.LabelsIncludeAny, 0)
|
||||
require.Len(t, meta.LabelsExcludeAny, 2)
|
||||
require.Len(t, meta.LabelsIncludeAll, 0)
|
||||
})
|
||||
|
||||
// create a host with some non-VPP software
|
||||
|
|
@ -1939,6 +1941,7 @@ func testSetTeamVPPAppsWithLabels(t *testing.T, ds *Datastore) {
|
|||
|
||||
require.Len(t, app1Meta.LabelsIncludeAny, 2)
|
||||
require.Len(t, app1Meta.LabelsExcludeAny, 0)
|
||||
require.Len(t, app1Meta.LabelsIncludeAll, 0)
|
||||
for _, l := range app1Meta.LabelsIncludeAny {
|
||||
_, ok := app1.VPPAppTeam.ValidatedLabels.ByName[l.LabelName]
|
||||
require.True(t, ok)
|
||||
|
|
@ -1946,6 +1949,7 @@ func testSetTeamVPPAppsWithLabels(t *testing.T, ds *Datastore) {
|
|||
|
||||
require.Len(t, app2Meta.LabelsExcludeAny, 2)
|
||||
require.Len(t, app2Meta.LabelsIncludeAny, 0)
|
||||
require.Len(t, app2Meta.LabelsIncludeAll, 0)
|
||||
for _, l := range app2Meta.LabelsExcludeAny {
|
||||
_, ok := app2.VPPAppTeam.ValidatedLabels.ByName[l.LabelName]
|
||||
require.True(t, ok)
|
||||
|
|
@ -1998,6 +2002,7 @@ func testSetTeamVPPAppsWithLabels(t *testing.T, ds *Datastore) {
|
|||
|
||||
require.Len(t, app1Meta.LabelsIncludeAny, 0)
|
||||
require.Len(t, app1Meta.LabelsExcludeAny, 2)
|
||||
require.Len(t, app1Meta.LabelsIncludeAll, 0)
|
||||
for _, l := range app1Meta.LabelsExcludeAny {
|
||||
_, ok := app1.VPPAppTeam.ValidatedLabels.ByName[l.LabelName]
|
||||
require.True(t, ok)
|
||||
|
|
@ -2005,6 +2010,7 @@ func testSetTeamVPPAppsWithLabels(t *testing.T, ds *Datastore) {
|
|||
|
||||
require.Len(t, app2Meta.LabelsExcludeAny, 0)
|
||||
require.Len(t, app2Meta.LabelsIncludeAny, 2)
|
||||
require.Len(t, app2Meta.LabelsIncludeAll, 0)
|
||||
for _, l := range app2Meta.LabelsIncludeAny {
|
||||
_, ok := app2.VPPAppTeam.ValidatedLabels.ByName[l.LabelName]
|
||||
require.True(t, ok)
|
||||
|
|
|
|||
|
|
@ -1091,6 +1091,7 @@ type ActivityTypeAddedSoftware struct {
|
|||
SoftwareTitleID uint `json:"software_title_id"`
|
||||
LabelsIncludeAny []ActivitySoftwareLabel `json:"labels_include_any,omitempty"`
|
||||
LabelsExcludeAny []ActivitySoftwareLabel `json:"labels_exclude_any,omitempty"`
|
||||
LabelsIncludeAll []ActivitySoftwareLabel `json:"labels_include_all,omitempty"`
|
||||
}
|
||||
|
||||
func (a ActivityTypeAddedSoftware) ActivityName() string {
|
||||
|
|
@ -1106,6 +1107,7 @@ type ActivityTypeEditedSoftware struct {
|
|||
SoftwareIconURL *string `json:"software_icon_url"`
|
||||
LabelsIncludeAny []ActivitySoftwareLabel `json:"labels_include_any,omitempty"`
|
||||
LabelsExcludeAny []ActivitySoftwareLabel `json:"labels_exclude_any,omitempty"`
|
||||
LabelsIncludeAll []ActivitySoftwareLabel `json:"labels_include_all,omitempty"`
|
||||
SoftwareTitleID uint `json:"software_title_id"`
|
||||
SoftwareDisplayName string `json:"software_display_name"`
|
||||
}
|
||||
|
|
@ -1123,6 +1125,7 @@ type ActivityTypeDeletedSoftware struct {
|
|||
SoftwareIconURL *string `json:"software_icon_url"`
|
||||
LabelsIncludeAny []ActivitySoftwareLabel `json:"labels_include_any,omitempty"`
|
||||
LabelsExcludeAny []ActivitySoftwareLabel `json:"labels_exclude_any,omitempty"`
|
||||
LabelsIncludeAll []ActivitySoftwareLabel `json:"labels_include_all,omitempty"`
|
||||
}
|
||||
|
||||
func (a ActivityTypeDeletedSoftware) ActivityName() string {
|
||||
|
|
@ -1236,6 +1239,7 @@ type ActivityAddedAppStoreApp struct {
|
|||
SelfService bool `json:"self_service"`
|
||||
LabelsIncludeAny []ActivitySoftwareLabel `json:"labels_include_any,omitempty"`
|
||||
LabelsExcludeAny []ActivitySoftwareLabel `json:"labels_exclude_any,omitempty"`
|
||||
LabelsIncludeAll []ActivitySoftwareLabel `json:"labels_include_all,omitempty"`
|
||||
Configuration json.RawMessage `json:"configuration,omitempty"`
|
||||
}
|
||||
|
||||
|
|
@ -1252,6 +1256,7 @@ type ActivityDeletedAppStoreApp struct {
|
|||
SoftwareIconURL *string `json:"software_icon_url"`
|
||||
LabelsIncludeAny []ActivitySoftwareLabel `json:"labels_include_any,omitempty"`
|
||||
LabelsExcludeAny []ActivitySoftwareLabel `json:"labels_exclude_any,omitempty"`
|
||||
LabelsIncludeAll []ActivitySoftwareLabel `json:"labels_include_all,omitempty"`
|
||||
}
|
||||
|
||||
func (a ActivityDeletedAppStoreApp) ActivityName() string {
|
||||
|
|
@ -1308,6 +1313,7 @@ type ActivityEditedAppStoreApp struct {
|
|||
SoftwareIconURL *string `json:"software_icon_url"`
|
||||
LabelsIncludeAny []ActivitySoftwareLabel `json:"labels_include_any,omitempty"`
|
||||
LabelsExcludeAny []ActivitySoftwareLabel `json:"labels_exclude_any,omitempty"`
|
||||
LabelsIncludeAll []ActivitySoftwareLabel `json:"labels_include_all,omitempty"`
|
||||
SoftwareDisplayName string `json:"software_display_name"`
|
||||
Configuration json.RawMessage `json:"configuration,omitempty"`
|
||||
AutoUpdateEnabled *bool `json:"auto_update_enabled,omitempty"`
|
||||
|
|
|
|||
|
|
@ -439,6 +439,7 @@ type SoftwareInstallerPayload struct {
|
|||
InstallDuringSetup *bool `json:"install_during_setup"` // if nil, do not change saved value, otherwise set it
|
||||
LabelsIncludeAny []string `json:"labels_include_any"`
|
||||
LabelsExcludeAny []string `json:"labels_exclude_any"`
|
||||
LabelsIncludeAll []string `json:"labels_include_all"`
|
||||
// ValidatedLabels is a struct that contains the validated labels for the
|
||||
// software installer. It is nil if the labels have not been validated.
|
||||
ValidatedLabels *LabelIdentsWithScope
|
||||
|
|
|
|||
|
|
@ -1362,7 +1362,7 @@ type Service interface {
|
|||
// Fleet-maintained apps
|
||||
|
||||
// AddFleetMaintainedApp adds a Fleet-maintained app to the given team.
|
||||
AddFleetMaintainedApp(ctx context.Context, teamID *uint, appID uint, installScript, preInstallQuery, postInstallScript, uninstallScript string, selfService bool, automaticInstall bool, labelsIncludeAny, labelsExcludeAny []string) (uint, error)
|
||||
AddFleetMaintainedApp(ctx context.Context, teamID *uint, appID uint, installScript, preInstallQuery, postInstallScript, uninstallScript string, selfService bool, automaticInstall bool, labelsIncludeAny, labelsExcludeAny, labelsIncludeAll []string) (uint, error)
|
||||
// ListFleetMaintainedApps lists Fleet-maintained apps, including associated software title for supplied team ID (if any)
|
||||
ListFleetMaintainedApps(ctx context.Context, teamID *uint, opts ListOptions) ([]MaintainedApp, *PaginationMetadata, error)
|
||||
// GetFleetMaintainedApp returns a Fleet-maintained app by ID, including associated software title for supplied team ID (if any)
|
||||
|
|
|
|||
|
|
@ -807,6 +807,7 @@ type VPPBatchPayload struct {
|
|||
InstallDuringSetup *bool `json:"install_during_setup"` // keep saved value if nil, otherwise set as indicated
|
||||
LabelsExcludeAny []string `json:"labels_exclude_any"`
|
||||
LabelsIncludeAny []string `json:"labels_include_any"`
|
||||
LabelsIncludeAll []string `json:"labels_include_all"`
|
||||
// Categories is the list of names of software categories associated with this VPP app.
|
||||
Categories []string `json:"categories"`
|
||||
DisplayName string `json:"display_name"`
|
||||
|
|
@ -834,6 +835,7 @@ type VPPBatchPayloadWithPlatform struct {
|
|||
InstallDuringSetup *bool `json:"install_during_setup"` // keep saved value if nil, otherwise set as indicated
|
||||
LabelsExcludeAny []string `json:"labels_exclude_any"`
|
||||
LabelsIncludeAny []string `json:"labels_include_any"`
|
||||
LabelsIncludeAll []string `json:"labels_include_all"`
|
||||
// Categories is the list of names of software categories associated with this VPP app.
|
||||
Categories []string `json:"categories"`
|
||||
// CategoryIDs is the list of IDs of software categories associated with this VPP app.
|
||||
|
|
|
|||
|
|
@ -123,6 +123,8 @@ type SoftwareInstaller struct {
|
|||
LabelsIncludeAny []SoftwareScopeLabel `json:"labels_include_any" db:"labels_include_any"`
|
||||
// LabelsExcludeAny is the list of "exclude any" labels for this software installer (if not nil).
|
||||
LabelsExcludeAny []SoftwareScopeLabel `json:"labels_exclude_any" db:"labels_exclude_any"`
|
||||
// LabelsIncludeAll is the list of "include all" labels for this software installer (if not nil).
|
||||
LabelsIncludeAll []SoftwareScopeLabel `json:"labels_include_all" db:"labels_include_all"`
|
||||
// Source is the osquery source for this software.
|
||||
Source string `json:"-" db:"source"`
|
||||
// Categories is the list of categories to which this software belongs: e.g. "Productivity",
|
||||
|
|
@ -522,6 +524,7 @@ type UploadSoftwareInstallerPayload struct {
|
|||
InstallDuringSetup *bool // keep saved value if nil, otherwise set as indicated
|
||||
LabelsIncludeAny []string // names of "include any" labels
|
||||
LabelsExcludeAny []string // names of "exclude any" labels
|
||||
LabelsIncludeAll []string // names of "include all" labels
|
||||
// ValidatedLabels is a struct that contains the validated labels for the software installer. It
|
||||
// is nil if the labels have not been validated.
|
||||
ValidatedLabels *LabelIdentsWithScope
|
||||
|
|
@ -605,6 +608,7 @@ type UpdateSoftwareInstallerPayload struct {
|
|||
UpgradeCode string
|
||||
LabelsIncludeAny []string // names of "include any" labels
|
||||
LabelsExcludeAny []string // names of "exclude any" labels
|
||||
LabelsIncludeAll []string // names of "include all" labels
|
||||
// ValidatedLabels is a struct that contains the validated labels for the software installer. It
|
||||
// can be nil if the labels have not been validated or if the labels are not being updated.
|
||||
ValidatedLabels *LabelIdentsWithScope
|
||||
|
|
@ -617,8 +621,8 @@ type UpdateSoftwareInstallerPayload struct {
|
|||
func (u *UpdateSoftwareInstallerPayload) IsNoopPayload(existing *SoftwareTitle) bool {
|
||||
return u.SelfService == nil && u.InstallerFile == nil && u.PreInstallQuery == nil &&
|
||||
u.InstallScript == nil && u.PostInstallScript == nil && u.UninstallScript == nil &&
|
||||
u.LabelsIncludeAny == nil && u.LabelsExcludeAny == nil && u.DisplayName == nil &&
|
||||
u.CategoryIDs == nil
|
||||
u.LabelsIncludeAny == nil && u.LabelsExcludeAny == nil && u.LabelsIncludeAll == nil &&
|
||||
u.DisplayName == nil && u.CategoryIDs == nil
|
||||
}
|
||||
|
||||
// DownloadSoftwareInstallerPayload is the payload for downloading a software installer.
|
||||
|
|
@ -781,6 +785,7 @@ type SoftwarePackageSpec struct {
|
|||
UninstallScript TeamSpecSoftwareAsset `json:"uninstall_script"`
|
||||
LabelsIncludeAny []string `json:"labels_include_any"`
|
||||
LabelsExcludeAny []string `json:"labels_exclude_any"`
|
||||
LabelsIncludeAll []string `json:"labels_include_all"`
|
||||
InstallDuringSetup optjson.Bool `json:"setup_experience"`
|
||||
Icon TeamSpecSoftwareAsset `json:"icon"`
|
||||
|
||||
|
|
@ -813,8 +818,8 @@ func (spec SoftwarePackageSpec) ResolveSoftwarePackagePaths(baseDir string) Soft
|
|||
}
|
||||
|
||||
func (spec SoftwarePackageSpec) IncludesFieldsDisallowedInPackageFile() bool {
|
||||
return len(spec.LabelsExcludeAny) > 0 || len(spec.LabelsIncludeAny) > 0 || len(spec.Categories) > 0 ||
|
||||
spec.SelfService || spec.InstallDuringSetup.Valid
|
||||
return len(spec.LabelsExcludeAny) > 0 || len(spec.LabelsIncludeAny) > 0 || len(spec.LabelsIncludeAll) > 0 ||
|
||||
len(spec.Categories) > 0 || spec.SelfService || spec.InstallDuringSetup.Valid
|
||||
}
|
||||
|
||||
func resolveApplyRelativePath(baseDir string, path string) string {
|
||||
|
|
@ -835,6 +840,7 @@ type MaintainedAppSpec struct {
|
|||
UninstallScript TeamSpecSoftwareAsset `json:"uninstall_script"`
|
||||
LabelsIncludeAny []string `json:"labels_include_any"`
|
||||
LabelsExcludeAny []string `json:"labels_exclude_any"`
|
||||
LabelsIncludeAll []string `json:"labels_include_all"`
|
||||
Categories []string `json:"categories"`
|
||||
InstallDuringSetup optjson.Bool `json:"setup_experience"`
|
||||
Icon TeamSpecSoftwareAsset `json:"icon"`
|
||||
|
|
@ -851,6 +857,7 @@ func (spec MaintainedAppSpec) ToSoftwarePackageSpec() SoftwarePackageSpec {
|
|||
SelfService: spec.SelfService,
|
||||
LabelsIncludeAny: spec.LabelsIncludeAny,
|
||||
LabelsExcludeAny: spec.LabelsExcludeAny,
|
||||
LabelsIncludeAll: spec.LabelsIncludeAll,
|
||||
InstallDuringSetup: spec.InstallDuringSetup,
|
||||
Icon: spec.Icon,
|
||||
Categories: spec.Categories,
|
||||
|
|
@ -1076,10 +1083,11 @@ func NewTempFileReader(from io.Reader, tempDirFn func() string) (*TempFileReader
|
|||
// NOTE: depending on how/where this struct is used, fields MAY BE
|
||||
// UNRELIABLE insofar as they represent default, empty values.
|
||||
type SoftwareScopeLabel struct {
|
||||
LabelName string `db:"label_name" json:"name"`
|
||||
LabelID uint `db:"label_id" json:"id"` // label id in database, which may be the empty value in some cases where id is not known in advance (e.g., if labels are created during gitops processing)
|
||||
Exclude bool `db:"exclude" json:"-"` // not rendered in JSON, used when processing LabelsIncludeAny and LabelsExcludeAny on parent title (may be the empty value in some cases)
|
||||
TitleID uint `db:"title_id" json:"-"` // not rendered in JSON, used to store the associated title ID (may be the empty value in some cases)
|
||||
LabelName string `db:"label_name" json:"name"`
|
||||
LabelID uint `db:"label_id" json:"id"` // label id in database, which may be the empty value in some cases where id is not known in advance (e.g., if labels are created during gitops processing)
|
||||
Exclude bool `db:"exclude" json:"-"` // not rendered in JSON, used when processing LabelsIncludeAll, LabelsIncludeAny and LabelsExcludeAny on parent title (may be the empty value in some cases)
|
||||
TitleID uint `db:"title_id" json:"-"` // not rendered in JSON, used to store the associated title ID (may be the empty value in some cases)
|
||||
RequireAll bool `db:"require_all" json:"-"` // not rendered in JSON, used when processing LabelsIncludeAll, LabelsIncludeAny and LabelsExcludeAny on parent title (may be the empty value in some cases)
|
||||
}
|
||||
|
||||
// Max total attempts (including initial) for a non-policy software install.
|
||||
|
|
|
|||
|
|
@ -67,4 +67,5 @@ type DetailsForSoftwareIconActivity struct {
|
|||
Platform *InstallableDevicePlatform `json:"platform"`
|
||||
LabelsIncludeAny []ActivitySoftwareLabel `db:"-"`
|
||||
LabelsExcludeAny []ActivitySoftwareLabel `db:"-"`
|
||||
LabelsIncludeAll []ActivitySoftwareLabel `db:"-"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -274,6 +274,7 @@ type TeamSpecAppStoreApp struct {
|
|||
SelfService bool `json:"self_service"`
|
||||
LabelsIncludeAny []string `json:"labels_include_any"`
|
||||
LabelsExcludeAny []string `json:"labels_exclude_any"`
|
||||
LabelsIncludeAll []string `json:"labels_include_all"`
|
||||
// Categories is the list of names of software categories associated with this VPP app.
|
||||
Categories []string `json:"categories"`
|
||||
// InstallDuringSetup indicates whether a package should be incorporated into setup experience;
|
||||
|
|
@ -332,7 +333,7 @@ func (t *TeamMDM) Copy() *TeamMDM {
|
|||
|
||||
clone := *t
|
||||
|
||||
// EnableDiskEncryption, MacOSUpdates and MacOSSetup don't have fields that
|
||||
// EnableDiskEncryption, MacOS/IOS/IPadOS/WindowsUpdates don't have fields that
|
||||
// require cloning (all fields are basic value types, no
|
||||
// pointers/slices/maps).
|
||||
|
||||
|
|
|
|||
|
|
@ -30,14 +30,18 @@ type VPPAppTeam struct {
|
|||
// to false), while if not nil, it will update the flag's value in the DB.
|
||||
InstallDuringSetup *bool `db:"install_during_setup" json:"-"`
|
||||
// LabelsIncludeAny are the names of labels associated with this app. If a host has any of
|
||||
// these labels, the app is in scope for that host. If this field is set, LabelsExcludeAny
|
||||
// these labels, the app is in scope for that host. If this field is set, other label fields
|
||||
// cannot be set.
|
||||
LabelsIncludeAny []string `json:"labels_include_any"`
|
||||
// LabelsExcludeAny are the names of labels associated with this app. If a host has any of
|
||||
// these labels, the app is out of scope for that host. If this field is set, LabelsIncludeAny
|
||||
// these labels, the app is out of scope for that host. If this field is set, other label fields
|
||||
// cannot be set.
|
||||
LabelsExcludeAny []string `json:"labels_exclude_any"`
|
||||
// ValidatedLabels are the labels (either include or exclude any) that have been validated by
|
||||
// LabelsIncludeAll are the names of labels associated with this app. If a host has all of
|
||||
// these labels, the app is in scope for that host. If this field is set, other label fields
|
||||
// cannot be set.
|
||||
LabelsIncludeAll []string `json:"labels_include_all"`
|
||||
// ValidatedLabels are the labels (either include any/all or exclude any) that have been validated by
|
||||
// Fleet as being valid labels. This field is only used internally.
|
||||
ValidatedLabels *LabelIdentsWithScope `json:"-"`
|
||||
// AddAutoInstallPolicy indicates whether or not we should create an auto-install policy for
|
||||
|
|
@ -115,6 +119,8 @@ type VPPAppStoreApp struct {
|
|||
LabelsIncludeAny []SoftwareScopeLabel `json:"labels_include_any" db:"labels_include_any"`
|
||||
// LabelsExcludeAny is the list of "exclude any" labels for this app store app (if not nil).
|
||||
LabelsExcludeAny []SoftwareScopeLabel `json:"labels_exclude_any" db:"labels_exclude_any"`
|
||||
// LabelsIncludeAll is the list of "include all" labels for this app store app (if not nil).
|
||||
LabelsIncludeAll []SoftwareScopeLabel `json:"labels_include_all" db:"labels_include_all"`
|
||||
// BundleIdentifier is the bundle identifier for this app.
|
||||
BundleIdentifier string `json:"-" db:"bundle_identifier"`
|
||||
// AddedAt is when the VPP app was added to the team
|
||||
|
|
@ -188,6 +194,7 @@ type AppStoreAppUpdatePayload struct {
|
|||
SelfService *bool
|
||||
LabelsIncludeAny []string
|
||||
LabelsExcludeAny []string
|
||||
LabelsIncludeAll []string
|
||||
Categories []string
|
||||
DisplayName *string
|
||||
Configuration json.RawMessage
|
||||
|
|
|
|||
|
|
@ -839,7 +839,7 @@ type MaybeCancelPendingSetupExperienceStepsFunc func(ctx context.Context, host *
|
|||
|
||||
type IsAllSetupExperienceSoftwareRequiredFunc func(ctx context.Context, host *fleet.Host) (bool, error)
|
||||
|
||||
type AddFleetMaintainedAppFunc func(ctx context.Context, teamID *uint, appID uint, installScript string, preInstallQuery string, postInstallScript string, uninstallScript string, selfService bool, automaticInstall bool, labelsIncludeAny []string, labelsExcludeAny []string) (uint, error)
|
||||
type AddFleetMaintainedAppFunc func(ctx context.Context, teamID *uint, appID uint, installScript string, preInstallQuery string, postInstallScript string, uninstallScript string, selfService bool, automaticInstall bool, labelsIncludeAny []string, labelsExcludeAny []string, labelsIncludeAll []string) (uint, error)
|
||||
|
||||
type ListFleetMaintainedAppsFunc func(ctx context.Context, teamID *uint, opts fleet.ListOptions) ([]fleet.MaintainedApp, *fleet.PaginationMetadata, error)
|
||||
|
||||
|
|
@ -5063,11 +5063,11 @@ func (s *Service) IsAllSetupExperienceSoftwareRequired(ctx context.Context, host
|
|||
return s.IsAllSetupExperienceSoftwareRequiredFunc(ctx, host)
|
||||
}
|
||||
|
||||
func (s *Service) AddFleetMaintainedApp(ctx context.Context, teamID *uint, appID uint, installScript string, preInstallQuery string, postInstallScript string, uninstallScript string, selfService bool, automaticInstall bool, labelsIncludeAny []string, labelsExcludeAny []string) (uint, error) {
|
||||
func (s *Service) AddFleetMaintainedApp(ctx context.Context, teamID *uint, appID uint, installScript string, preInstallQuery string, postInstallScript string, uninstallScript string, selfService bool, automaticInstall bool, labelsIncludeAny []string, labelsExcludeAny []string, labelsIncludeAll []string) (uint, error) {
|
||||
s.mu.Lock()
|
||||
s.AddFleetMaintainedAppFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.AddFleetMaintainedAppFunc(ctx, teamID, appID, installScript, preInstallQuery, postInstallScript, uninstallScript, selfService, automaticInstall, labelsIncludeAny, labelsExcludeAny)
|
||||
return s.AddFleetMaintainedAppFunc(ctx, teamID, appID, installScript, preInstallQuery, postInstallScript, uninstallScript, selfService, automaticInstall, labelsIncludeAny, labelsExcludeAny, labelsIncludeAll)
|
||||
}
|
||||
|
||||
func (s *Service) ListFleetMaintainedApps(ctx context.Context, teamID *uint, opts fleet.ListOptions) ([]fleet.MaintainedApp, *fleet.PaginationMetadata, error) {
|
||||
|
|
|
|||
|
|
@ -898,6 +898,7 @@ func (c *Client) ApplyGroup(
|
|||
InstallDuringSetup: installDuringSetup,
|
||||
LabelsExcludeAny: app.LabelsExcludeAny,
|
||||
LabelsIncludeAny: app.LabelsIncludeAny,
|
||||
LabelsIncludeAll: app.LabelsIncludeAll,
|
||||
Categories: app.Categories,
|
||||
DisplayName: app.DisplayName,
|
||||
IconPath: app.Icon.Path,
|
||||
|
|
@ -1279,6 +1280,7 @@ func buildSoftwarePackagesPayload(specs []fleet.SoftwarePackageSpec, installDuri
|
|||
InstallDuringSetup: installDuringSetup,
|
||||
LabelsIncludeAny: si.LabelsIncludeAny,
|
||||
LabelsExcludeAny: si.LabelsExcludeAny,
|
||||
LabelsIncludeAll: si.LabelsIncludeAll,
|
||||
SHA256: sha256Value,
|
||||
Categories: si.Categories,
|
||||
DisplayName: si.DisplayName,
|
||||
|
|
|
|||
|
|
@ -12060,6 +12060,7 @@ func checkSoftwareInstaller(t *testing.T, ds *mysql.Datastore, payload *fleet.Up
|
|||
byName[l.LabelName] = struct{}{}
|
||||
require.Equal(t, *meta2.TitleID, l.TitleID)
|
||||
require.False(t, l.Exclude)
|
||||
require.False(t, l.RequireAll)
|
||||
}
|
||||
require.Len(t, byName, len(payload.LabelsIncludeAny))
|
||||
for _, l := range payload.LabelsIncludeAny {
|
||||
|
|
@ -12074,6 +12075,7 @@ func checkSoftwareInstaller(t *testing.T, ds *mysql.Datastore, payload *fleet.Up
|
|||
byName[l.LabelName] = struct{}{}
|
||||
require.Equal(t, *meta2.TitleID, l.TitleID)
|
||||
require.True(t, l.Exclude)
|
||||
require.False(t, l.RequireAll)
|
||||
}
|
||||
require.Len(t, byName, len(payload.LabelsExcludeAny))
|
||||
for _, l := range payload.LabelsExcludeAny {
|
||||
|
|
@ -12081,6 +12083,21 @@ func checkSoftwareInstaller(t *testing.T, ds *mysql.Datastore, payload *fleet.Up
|
|||
require.True(t, ok)
|
||||
}
|
||||
|
||||
// check labels include all
|
||||
require.Len(t, meta2.LabelsIncludeAll, len(payload.LabelsIncludeAll))
|
||||
byName = make(map[string]struct{}, len(meta2.LabelsIncludeAll))
|
||||
for _, l := range meta2.LabelsIncludeAll {
|
||||
byName[l.LabelName] = struct{}{}
|
||||
require.Equal(t, *meta2.TitleID, l.TitleID)
|
||||
require.False(t, l.Exclude)
|
||||
require.True(t, l.RequireAll)
|
||||
}
|
||||
require.Len(t, byName, len(payload.LabelsIncludeAll))
|
||||
for _, l := range payload.LabelsIncludeAll {
|
||||
_, ok := byName[l]
|
||||
require.True(t, ok)
|
||||
}
|
||||
|
||||
return meta.InstallerID, *meta.TitleID
|
||||
}
|
||||
|
||||
|
|
@ -12126,6 +12143,21 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerUploadDownloadAndD
|
|||
Query: "select 1",
|
||||
}}, http.StatusOK, &labelResp)
|
||||
require.NotZero(t, labelResp.Label.ID)
|
||||
lblA := labelResp.Label
|
||||
|
||||
s.DoJSON("POST", "/api/latest/fleet/labels", &createLabelRequest{fleet.LabelPayload{
|
||||
Name: "label_b" + t.Name(),
|
||||
Query: "select 1",
|
||||
}}, http.StatusOK, &labelResp)
|
||||
require.NotZero(t, labelResp.Label.ID)
|
||||
lblB := labelResp.Label
|
||||
|
||||
s.DoJSON("POST", "/api/latest/fleet/labels", &createLabelRequest{fleet.LabelPayload{
|
||||
Name: "label_c" + t.Name(),
|
||||
Query: "select 1",
|
||||
}}, http.StatusOK, &labelResp)
|
||||
require.NotZero(t, labelResp.Label.ID)
|
||||
lblC := labelResp.Label
|
||||
|
||||
payload := &fleet.UploadSoftwareInstallerPayload{
|
||||
InstallScript: "some install script",
|
||||
|
|
@ -12141,6 +12173,60 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerUploadDownloadAndD
|
|||
LabelsIncludeAny: []string{t.Name()},
|
||||
}
|
||||
|
||||
// validate that providing more than 1 type of label
|
||||
// results in an error
|
||||
testCases := []struct {
|
||||
desc string
|
||||
incAny []string
|
||||
exclAny []string
|
||||
incAll []string
|
||||
}{
|
||||
{
|
||||
desc: "include_any_exclude_any",
|
||||
incAny: []string{lblA.Name},
|
||||
exclAny: []string{lblB.Name},
|
||||
},
|
||||
{
|
||||
desc: "include_any_include_all",
|
||||
incAny: []string{lblA.Name},
|
||||
incAll: []string{lblB.Name},
|
||||
},
|
||||
{
|
||||
desc: "exclude_any_include_all",
|
||||
exclAny: []string{lblA.Name},
|
||||
incAll: []string{lblB.Name},
|
||||
},
|
||||
{
|
||||
desc: "all_types",
|
||||
incAny: []string{lblA.Name},
|
||||
exclAny: []string{lblB.Name},
|
||||
incAll: []string{lblC.Name},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
|
||||
payload := &fleet.UploadSoftwareInstallerPayload{
|
||||
InstallScript: "some install script",
|
||||
PreInstallQuery: "some pre install query",
|
||||
PostInstallScript: "some post install script",
|
||||
Filename: "ruby.deb",
|
||||
// additional fields below are pre-populated so we can re-use the payload later for the test assertions
|
||||
Title: "ruby",
|
||||
Version: "1:2.5.1",
|
||||
Source: "deb_packages",
|
||||
StorageID: "df06d9ce9e2090d9cb2e8cd1f4d7754a803dc452bf93e3204e3acd3b95508628",
|
||||
Platform: "linux",
|
||||
LabelsIncludeAny: tc.incAny,
|
||||
LabelsIncludeAll: tc.incAll,
|
||||
LabelsExcludeAny: tc.exclAny,
|
||||
}
|
||||
|
||||
s.uploadSoftwareInstaller(t, payload, http.StatusBadRequest, `Only one of "labels_include_all", "labels_include_any" or "labels_exclude_any" can be included.`)
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
s.uploadSoftwareInstaller(t, payload, http.StatusOK, "")
|
||||
|
||||
// check the software installer
|
||||
|
|
@ -12149,7 +12235,7 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerUploadDownloadAndD
|
|||
// check activity
|
||||
activityData := fmt.Sprintf(`{"software_title": "ruby", "software_package": "ruby.deb", "team_name": null,
|
||||
"team_id": null, "fleet_name": null, "fleet_id": null, "self_service": false, "software_title_id": %d, "labels_include_any": [{"id": %d, "name": %q}]}`,
|
||||
titleID, labelResp.Label.ID, t.Name())
|
||||
titleID, lblA.ID, lblA.Name)
|
||||
s.lastActivityMatches(fleet.ActivityTypeAddedSoftware{}.ActivityName(), activityData, 0)
|
||||
|
||||
// upload again fails
|
||||
|
|
@ -12167,7 +12253,7 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerUploadDownloadAndD
|
|||
}, http.StatusOK, "")
|
||||
activityData = fmt.Sprintf(`{"software_title": "ruby", "software_package": "ruby.deb", "software_icon_url": null, "team_name": null,
|
||||
"team_id": null, "fleet_name": null, "fleet_id": null, "self_service": true, "software_title_id": %d, "labels_include_any": [{"id": %d, "name": %q}], "software_display_name": ""}`,
|
||||
titleID, labelResp.Label.ID, t.Name())
|
||||
titleID, lblA.ID, lblA.Name)
|
||||
s.lastActivityMatches(fleet.ActivityTypeEditedSoftware{}.ActivityName(), activityData, 0)
|
||||
// patch the software installer to change the labels
|
||||
body, headers := generateMultipartRequest(t, "", "", nil, s.token, map[string][]string{
|
||||
|
|
@ -12177,12 +12263,12 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerUploadDownloadAndD
|
|||
s.DoRawWithHeaders("PATCH", fmt.Sprintf("/api/latest/fleet/software/titles/%d/package", titleID), body.Bytes(), http.StatusOK, headers)
|
||||
expectedPayload := *payload
|
||||
expectedPayload.LabelsIncludeAny = nil
|
||||
expectedPayload.LabelsExcludeAny = []string{labelResp.Label.Name}
|
||||
expectedPayload.LabelsExcludeAny = []string{lblA.Name}
|
||||
checkSoftwareInstaller(t, s.ds, &expectedPayload)
|
||||
|
||||
// Create a host and assign the label to it
|
||||
host := createOrbitEnrolledHost(t, "linux", "label_host", s.ds)
|
||||
err = s.ds.RecordLabelQueryExecutions(context.Background(), host, map[uint]*bool{labelResp.Label.ID: ptr.Bool(true)}, time.Now(), false)
|
||||
err = s.ds.RecordLabelQueryExecutions(context.Background(), host, map[uint]*bool{lblA.ID: ptr.Bool(true)}, time.Now(), false)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Attempt to install. Should fail because label is "exclude any"
|
||||
|
|
@ -12196,8 +12282,8 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerUploadDownloadAndD
|
|||
})
|
||||
s.DoRawWithHeaders("PATCH", fmt.Sprintf("/api/latest/fleet/software/titles/%d/package", titleID), body.Bytes(), http.StatusOK, headers)
|
||||
expectedPayload.PreInstallQuery = "some other pre install query"
|
||||
expectedPayload.LabelsIncludeAny = nil // no change
|
||||
expectedPayload.LabelsExcludeAny = []string{labelResp.Label.Name} // no change
|
||||
expectedPayload.LabelsIncludeAny = nil // no change
|
||||
expectedPayload.LabelsExcludeAny = []string{lblA.Name} // no change
|
||||
checkSoftwareInstaller(t, s.ds, &expectedPayload)
|
||||
|
||||
// update the label to be "include any". This should allow for the installation to happen.
|
||||
|
|
@ -12205,7 +12291,7 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerUploadDownloadAndD
|
|||
w3 := multipart.NewWriter(&b3)
|
||||
require.NoError(t, w3.WriteField("team_id", "0"))
|
||||
require.NoError(t, w3.WriteField("pre_install_query", "some other pre install query"))
|
||||
require.NoError(t, w3.WriteField("labels_include_any", labelResp.Label.Name))
|
||||
require.NoError(t, w3.WriteField("labels_include_any", lblA.Name))
|
||||
w3.Close()
|
||||
headers = map[string]string{
|
||||
"Content-Type": w3.FormDataContentType(),
|
||||
|
|
@ -12214,7 +12300,7 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerUploadDownloadAndD
|
|||
}
|
||||
s.DoRawWithHeaders("PATCH", fmt.Sprintf("/api/latest/fleet/software/titles/%d/package", titleID), b3.Bytes(), http.StatusOK, headers)
|
||||
expectedPayload.PreInstallQuery = "some other pre install query"
|
||||
expectedPayload.LabelsIncludeAny = []string{labelResp.Label.Name}
|
||||
expectedPayload.LabelsIncludeAny = []string{lblA.Name}
|
||||
expectedPayload.LabelsExcludeAny = nil
|
||||
checkSoftwareInstaller(t, s.ds, &expectedPayload)
|
||||
|
||||
|
|
@ -12227,7 +12313,7 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerUploadDownloadAndD
|
|||
|
||||
activityData = fmt.Sprintf(`{"software_title": "ruby", "software_package": "ruby.deb", "software_icon_url": null, "team_name": null,
|
||||
"team_id": null, "fleet_name": null, "fleet_id": null, "self_service": true, "labels_include_any": [{"id": %d, "name": %q}], "software_title_id": %d, "software_display_name": ""}`,
|
||||
labelResp.Label.ID, labelResp.Label.Name, titleID)
|
||||
lblA.ID, lblA.Name, titleID)
|
||||
s.lastActivityMatches(fleet.ActivityTypeEditedSoftware{}.ActivityName(), activityData, 0)
|
||||
|
||||
// orbit-downloading fails with invalid orbit node key
|
||||
|
|
@ -12246,7 +12332,69 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerUploadDownloadAndD
|
|||
s.Do("DELETE", fmt.Sprintf("/api/latest/fleet/software/titles/%d/available_for_install", titleID), nil, http.StatusNoContent, "team_id", "0")
|
||||
activityData = fmt.Sprintf(`{"software_title": "ruby", "software_package": "ruby.deb", "software_icon_url": null, "team_name": null,
|
||||
"team_id": null, "fleet_name": null, "fleet_id": null, "self_service": true, "labels_include_any": [{"id": %d, "name": %q}]}`,
|
||||
labelResp.Label.ID, labelResp.Label.Name)
|
||||
lblA.ID, lblA.Name)
|
||||
s.lastActivityMatches(fleet.ActivityTypeDeletedSoftware{}.ActivityName(), activityData, 0)
|
||||
})
|
||||
|
||||
t.Run("upload no team software installer with labels_include_all", func(t *testing.T) {
|
||||
// create a label to use for include_all scoping
|
||||
var labelResp createLabelResponse
|
||||
s.DoJSON("POST", "/api/latest/fleet/labels", &createLabelRequest{fleet.LabelPayload{
|
||||
Name: t.Name(),
|
||||
Query: "select 1",
|
||||
}}, http.StatusOK, &labelResp)
|
||||
require.NotZero(t, labelResp.Label.ID)
|
||||
|
||||
payload := &fleet.UploadSoftwareInstallerPayload{
|
||||
InstallScript: "some install script",
|
||||
PreInstallQuery: "some pre install query",
|
||||
Filename: "ruby.deb",
|
||||
Title: "ruby",
|
||||
Version: "1:2.5.1",
|
||||
Source: "deb_packages",
|
||||
StorageID: "df06d9ce9e2090d9cb2e8cd1f4d7754a803dc452bf93e3204e3acd3b95508628",
|
||||
Platform: "linux",
|
||||
LabelsIncludeAll: []string{labelResp.Label.Name},
|
||||
}
|
||||
|
||||
s.uploadSoftwareInstaller(t, payload, http.StatusOK, "")
|
||||
|
||||
// check the software installer metadata: LabelsIncludeAll should be persisted
|
||||
_, titleID := checkSoftwareInstaller(t, s.ds, payload)
|
||||
|
||||
// check that the added-software activity carries labels_include_all
|
||||
activityData := fmt.Sprintf(`{"software_title": "ruby", "software_package": "ruby.deb", "team_name": null,
|
||||
"team_id": null, "fleet_name": null, "fleet_id": null, "self_service": false, "software_title_id": %d, "labels_include_all": [{"id": %d, "name": %q}]}`,
|
||||
titleID, labelResp.Label.ID, t.Name())
|
||||
s.lastActivityMatches(fleet.ActivityTypeAddedSoftware{}.ActivityName(), activityData, 0)
|
||||
|
||||
// patch the installer to update an unrelated field; labels_include_all should be preserved
|
||||
s.updateSoftwareInstaller(t, &fleet.UpdateSoftwareInstallerPayload{
|
||||
SelfService: ptr.Bool(true),
|
||||
InstallScript: ptr.String("some install script"),
|
||||
PreInstallQuery: ptr.String("some pre install query"),
|
||||
Filename: "ruby.deb",
|
||||
TitleID: titleID,
|
||||
TeamID: nil,
|
||||
}, http.StatusOK, "")
|
||||
|
||||
// the edited-software activity should still carry labels_include_all
|
||||
activityData = fmt.Sprintf(`{"software_title": "ruby", "software_package": "ruby.deb", "software_icon_url": null, "team_name": null,
|
||||
"team_id": null, "fleet_name": null, "fleet_id": null, "self_service": true, "software_title_id": %d, "labels_include_all": [{"id": %d, "name": %q}], "software_display_name": ""}`,
|
||||
titleID, labelResp.Label.ID, t.Name())
|
||||
s.lastActivityMatches(fleet.ActivityTypeEditedSoftware{}.ActivityName(), activityData, 0)
|
||||
|
||||
// create a host and assign the label — install should succeed since host has all required labels
|
||||
host := createOrbitEnrolledHost(t, "linux", "include_all_label_host", s.ds)
|
||||
err = s.ds.RecordLabelQueryExecutions(context.Background(), host, map[uint]*bool{labelResp.Label.ID: ptr.Bool(true)}, time.Now(), false)
|
||||
require.NoError(t, err)
|
||||
s.Do("POST", fmt.Sprintf("/api/latest/fleet/hosts/%d/software/%d/install", host.ID, titleID), nil, http.StatusAccepted)
|
||||
|
||||
// delete the installer; the deleted-software activity should carry labels_include_all
|
||||
s.Do("DELETE", fmt.Sprintf("/api/latest/fleet/software/titles/%d/available_for_install", titleID), nil, http.StatusNoContent, "team_id", "0")
|
||||
activityData = fmt.Sprintf(`{"software_title": "ruby", "software_package": "ruby.deb", "software_icon_url": null, "team_name": null,
|
||||
"team_id": null, "fleet_name": null, "fleet_id": null, "self_service": true, "labels_include_all": [{"id": %d, "name": %q}]}`,
|
||||
labelResp.Label.ID, t.Name())
|
||||
s.lastActivityMatches(fleet.ActivityTypeDeletedSoftware{}.ActivityName(), activityData, 0)
|
||||
})
|
||||
|
||||
|
|
@ -13223,8 +13371,6 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallers() {
|
|||
t := s.T()
|
||||
ctx := context.Background()
|
||||
|
||||
fmt.Printf("dev_mode.Env(\"FLEET_DEV_BATCH_RETRY_INTERVAL\"): %v\n", dev_mode.Env("FLEET_DEV_BATCH_RETRY_INTERVAL"))
|
||||
|
||||
// non-existent team
|
||||
s.Do("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{}, http.StatusNotFound, "team_name", "foo")
|
||||
|
||||
|
|
@ -13432,24 +13578,65 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallers() {
|
|||
titlesResp.SoftwareTitles[0].SoftwarePackage.SelfService = ptr.Bool(true)
|
||||
require.Equal(t, titlesResp, newTitlesResp)
|
||||
|
||||
// create some labels A and B
|
||||
// create some labels A, B and C
|
||||
lblA, err := s.ds.NewLabel(ctx, &fleet.Label{Name: "A"})
|
||||
require.NoError(t, err)
|
||||
lblB, err := s.ds.NewLabel(ctx, &fleet.Label{Name: "B"})
|
||||
require.NoError(t, err)
|
||||
lblC, err := s.ds.NewLabel(ctx, &fleet.Label{Name: "C"})
|
||||
require.NoError(t, err)
|
||||
|
||||
// providing both labels include/exclude results in an error
|
||||
softwareToInstall = []*fleet.SoftwareInstallerPayload{
|
||||
{URL: rubyURL, LabelsIncludeAny: []string{lblA.Name}, LabelsExcludeAny: []string{lblB.Name}},
|
||||
// validate that providing more than 1 type of label
|
||||
// results in an error
|
||||
testCases := []struct {
|
||||
desc string
|
||||
incAny []string
|
||||
exclAny []string
|
||||
incAll []string
|
||||
}{
|
||||
{
|
||||
desc: "include_any_exclude_any",
|
||||
incAny: []string{lblA.Name},
|
||||
exclAny: []string{lblB.Name},
|
||||
},
|
||||
{
|
||||
desc: "include_any_include_all",
|
||||
incAny: []string{lblA.Name},
|
||||
incAll: []string{lblB.Name},
|
||||
},
|
||||
{
|
||||
desc: "exclude_any_include_all",
|
||||
exclAny: []string{lblA.Name},
|
||||
incAll: []string{lblB.Name},
|
||||
},
|
||||
{
|
||||
desc: "all_types",
|
||||
incAny: []string{lblA.Name},
|
||||
exclAny: []string{lblB.Name},
|
||||
incAll: []string{lblC.Name},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
softwareToInstall = []*fleet.SoftwareInstallerPayload{
|
||||
{
|
||||
URL: rubyURL,
|
||||
LabelsIncludeAny: tc.incAny,
|
||||
LabelsExcludeAny: tc.exclAny,
|
||||
LabelsIncludeAll: tc.incAll,
|
||||
},
|
||||
}
|
||||
res := s.Do("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusBadRequest)
|
||||
assert.Contains(t, extractServerErrorText(res.Body), `Only one of "labels_include_all", "labels_include_any" or "labels_exclude_any" can be included.`)
|
||||
|
||||
})
|
||||
}
|
||||
res := s.Do("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusBadRequest)
|
||||
require.Contains(t, extractServerErrorText(res.Body), `Only one of "labels_include_any" or "labels_exclude_any" can be included.`)
|
||||
|
||||
// providing a non-existing label results in an error
|
||||
softwareToInstall = []*fleet.SoftwareInstallerPayload{
|
||||
{URL: rubyURL, LabelsIncludeAny: []string{"no-such-label"}},
|
||||
}
|
||||
res = s.Do("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusBadRequest)
|
||||
res := s.Do("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusBadRequest)
|
||||
require.Contains(t, extractServerErrorText(res.Body), `Couldn't update. Label "no-such-label" doesn't exist. Please remove the label from the software.`)
|
||||
|
||||
// valid installer scoped by label
|
||||
|
|
@ -13465,6 +13652,7 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallers() {
|
|||
meta, err := s.ds.GetSoftwareInstallerMetadataByTeamAndTitleID(ctx, nil, *packages[0].TitleID, false)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, meta.LabelsExcludeAny)
|
||||
require.Empty(t, meta.LabelsIncludeAll)
|
||||
require.Len(t, meta.LabelsIncludeAny, 1)
|
||||
require.Equal(t, lblA.ID, meta.LabelsIncludeAny[0].LabelID)
|
||||
require.Equal(t, lblA.Name, meta.LabelsIncludeAny[0].LabelName)
|
||||
|
|
@ -13533,7 +13721,7 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallers() {
|
|||
|
||||
// with a label
|
||||
softwareToInstall = []*fleet.SoftwareInstallerPayload{
|
||||
{Slug: &maintained1.Slug, LabelsIncludeAny: []string{lblA.Name}},
|
||||
{Slug: &maintained1.Slug, LabelsIncludeAll: []string{lblA.Name}},
|
||||
}
|
||||
s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusAccepted, &batchResponse)
|
||||
packages = waitBatchSetSoftwareInstallersCompleted(t, &s.withServer, "", batchResponse.RequestUUID)
|
||||
|
|
@ -13544,9 +13732,10 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallers() {
|
|||
meta, err = s.ds.GetSoftwareInstallerMetadataByTeamAndTitleID(ctx, nil, *packages[0].TitleID, false)
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, meta.LabelsExcludeAny)
|
||||
require.Len(t, meta.LabelsIncludeAny, 1)
|
||||
require.Equal(t, lblA.ID, meta.LabelsIncludeAny[0].LabelID)
|
||||
require.Equal(t, lblA.Name, meta.LabelsIncludeAny[0].LabelName)
|
||||
require.Empty(t, meta.LabelsIncludeAny)
|
||||
require.Len(t, meta.LabelsIncludeAll, 1)
|
||||
require.Equal(t, lblA.ID, meta.LabelsIncludeAll[0].LabelID)
|
||||
require.Equal(t, lblA.Name, meta.LabelsIncludeAll[0].LabelName)
|
||||
|
||||
// maintained app with no_check for sha, latest for version
|
||||
maintained2, err := s.ds.UpsertMaintainedApp(ctx, &fleet.MaintainedApp{
|
||||
|
|
@ -13611,7 +13800,7 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallers() {
|
|||
http.DefaultTransport = mockTransport
|
||||
|
||||
softwareToInstall = []*fleet.SoftwareInstallerPayload{
|
||||
{Slug: &maintained2.Slug},
|
||||
{Slug: &maintained2.Slug, LabelsIncludeAll: []string{lblA.Name}},
|
||||
}
|
||||
s.DoJSON("POST", "/api/latest/fleet/software/batch", batchSetSoftwareInstallersRequest{Software: softwareToInstall}, http.StatusAccepted, &batchResponse)
|
||||
packages = waitBatchSetSoftwareInstallersCompleted(t, &s.withServer, "", batchResponse.RequestUUID)
|
||||
|
|
@ -13627,6 +13816,16 @@ func (s *integrationEnterpriseTestSuite) TestBatchSetSoftwareInstallers() {
|
|||
s.DoJSON("GET", fmt.Sprintf("/api/v1/fleet/software/titles/%d", *packages[0].TitleID), nil, http.StatusOK, &titleResponse, "team_id", "0")
|
||||
require.Equal(t, "1.0.0", titleResponse.SoftwareTitle.SoftwarePackage.Version)
|
||||
|
||||
meta, err = s.ds.GetSoftwareInstallerMetadataByTeamAndTitleID(ctx, nil, *packages[0].TitleID, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Validate labels
|
||||
require.Empty(t, meta.LabelsExcludeAny)
|
||||
require.Empty(t, meta.LabelsIncludeAny)
|
||||
require.Len(t, meta.LabelsIncludeAll, 1)
|
||||
require.Equal(t, lblA.ID, meta.LabelsIncludeAll[0].LabelID)
|
||||
require.Equal(t, lblA.Name, meta.LabelsIncludeAll[0].LabelName)
|
||||
|
||||
http.DefaultTransport = oldTransport
|
||||
}
|
||||
|
||||
|
|
@ -18214,6 +18413,179 @@ func (s *integrationEnterpriseTestSuite) TestPolicyAutomationsSoftwareInstallers
|
|||
host1LastInstall, err = s.ds.GetHostLastInstallData(ctx, host.ID, vimInstallerID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, host1LastInstall)
|
||||
|
||||
// --- include_all tests ---
|
||||
//
|
||||
// Use a dedicated team so we can reuse the ruby.deb and vim.deb testdata
|
||||
// files without conflicting with the no-team installers already uploaded above.
|
||||
var inclAllTeamResp teamResponse
|
||||
s.DoJSON("POST", "/api/latest/fleet/teams", fleet.Team{Name: t.Name() + "-include-all"}, http.StatusOK, &inclAllTeamResp)
|
||||
inclAllTeam := inclAllTeamResp.Team
|
||||
err = s.ds.AddHostsToTeam(ctx, fleet.NewAddHostsToTeamParams(&inclAllTeam.ID, []uint{host.ID}))
|
||||
require.NoError(t, err)
|
||||
|
||||
// Host has lbl1 and lbl2. lbl3 is not on the host (lbl3 was never added
|
||||
// via RecordLabelQueryExecutions in this test above).
|
||||
|
||||
// Upload ruby.deb with labels_include_all: [lbl1, lbl2].
|
||||
// Host has both labels, so it IS in scope and install should be attempted.
|
||||
rubyIncludeAllPayload := &fleet.UploadSoftwareInstallerPayload{
|
||||
InstallScript: "some deb install script",
|
||||
Filename: "ruby.deb",
|
||||
TeamID: &inclAllTeam.ID,
|
||||
LabelsIncludeAll: []string{lbl1.Name, lbl2.Name},
|
||||
Platform: "linux",
|
||||
}
|
||||
s.uploadSoftwareInstaller(t, rubyIncludeAllPayload, http.StatusOK, "")
|
||||
|
||||
resp = listSoftwareTitlesResponse{}
|
||||
s.DoJSON(
|
||||
"GET", "/api/latest/fleet/software/titles",
|
||||
listSoftwareTitlesRequest{},
|
||||
http.StatusOK, &resp,
|
||||
"query", "ruby",
|
||||
"team_id", fmt.Sprint(inclAllTeam.ID),
|
||||
)
|
||||
require.Len(t, resp.SoftwareTitles, 1)
|
||||
require.NotNil(t, resp.SoftwareTitles[0].SoftwarePackage)
|
||||
rubyInclAllTitleID := resp.SoftwareTitles[0].ID
|
||||
|
||||
var rubyInclAllDetail getSoftwareTitleResponse
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/software/titles/%d", rubyInclAllTitleID), nil, http.StatusOK, &rubyInclAllDetail, "team_id", fmt.Sprint(inclAllTeam.ID))
|
||||
require.NotNil(t, rubyInclAllDetail.SoftwareTitle)
|
||||
require.NotNil(t, rubyInclAllDetail.SoftwareTitle.SoftwarePackage)
|
||||
rubyInclAllInstallerID := rubyInclAllDetail.SoftwareTitle.SoftwarePackage.InstallerID
|
||||
|
||||
policy3, err := s.ds.NewTeamPolicy(ctx, inclAllTeam.ID, nil, fleet.PolicyPayload{
|
||||
Name: "policy3",
|
||||
Query: "SELECT 3;",
|
||||
Platform: "linux",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
mtplr = modifyTeamPolicyResponse{}
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d/policies/%d", inclAllTeam.ID, policy3.ID), modifyTeamPolicyRequest{
|
||||
ModifyPolicyPayload: fleet.ModifyPolicyPayload{
|
||||
SoftwareTitleID: optjson.Any[uint]{Set: true, Valid: true, Value: rubyInclAllTitleID},
|
||||
},
|
||||
}, http.StatusOK, &mtplr)
|
||||
|
||||
host1LastInstall, err = s.ds.GetHostLastInstallData(ctx, host.ID, rubyInclAllInstallerID)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, host1LastInstall)
|
||||
|
||||
// Send back a failed result for policy3.
|
||||
distributedResp = submitDistributedQueryResultsResponse{}
|
||||
s.DoJSONWithoutAuth("POST", "/api/osquery/distributed/write", genDistributedReqWithPolicyResults(
|
||||
host,
|
||||
map[uint]*bool{
|
||||
policy3.ID: ptr.Bool(false),
|
||||
},
|
||||
), http.StatusOK, &distributedResp)
|
||||
err = s.ds.UpdateHostPolicyCounts(ctx)
|
||||
require.NoError(t, err)
|
||||
policy3, err = s.ds.Policy(ctx, policy3.ID)
|
||||
require.NoError(t, err)
|
||||
// Host has all required labels, so the installer is in scope and the policy is marked failed.
|
||||
require.Equal(t, uint(0), policy3.PassingHostCount)
|
||||
require.Equal(t, uint(1), policy3.FailingHostCount)
|
||||
|
||||
// Installation attempt was made for ruby, because host has all required labels.
|
||||
host1LastInstall, err = s.ds.GetHostLastInstallData(ctx, host.ID, rubyInclAllInstallerID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, host1LastInstall)
|
||||
|
||||
// Upload vim.deb with labels_include_all: [lbl1, lbl3].
|
||||
// Host has lbl1 but NOT lbl3, so it is NOT in scope and install should be skipped.
|
||||
vimIncludeAllPayload := &fleet.UploadSoftwareInstallerPayload{
|
||||
InstallScript: "some deb install script",
|
||||
Filename: "vim.deb",
|
||||
TeamID: &inclAllTeam.ID,
|
||||
LabelsIncludeAll: []string{lbl1.Name, lbl3.Name},
|
||||
Platform: "linux",
|
||||
}
|
||||
s.uploadSoftwareInstaller(t, vimIncludeAllPayload, http.StatusOK, "")
|
||||
|
||||
resp = listSoftwareTitlesResponse{}
|
||||
s.DoJSON(
|
||||
"GET", "/api/latest/fleet/software/titles",
|
||||
listSoftwareTitlesRequest{},
|
||||
http.StatusOK, &resp,
|
||||
"query", "vim",
|
||||
"team_id", fmt.Sprint(inclAllTeam.ID),
|
||||
)
|
||||
require.Len(t, resp.SoftwareTitles, 1)
|
||||
require.NotNil(t, resp.SoftwareTitles[0].SoftwarePackage)
|
||||
vimInclAllTitleID := resp.SoftwareTitles[0].ID
|
||||
|
||||
var vimInclAllDetail getSoftwareTitleResponse
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/software/titles/%d", vimInclAllTitleID), nil, http.StatusOK, &vimInclAllDetail, "team_id", fmt.Sprint(inclAllTeam.ID))
|
||||
require.NotNil(t, vimInclAllDetail.SoftwareTitle)
|
||||
require.NotNil(t, vimInclAllDetail.SoftwareTitle.SoftwarePackage)
|
||||
vimInclAllInstallerID := vimInclAllDetail.SoftwareTitle.SoftwarePackage.InstallerID
|
||||
|
||||
policy4, err := s.ds.NewTeamPolicy(ctx, inclAllTeam.ID, nil, fleet.PolicyPayload{
|
||||
Name: "policy4",
|
||||
Query: "SELECT 4;",
|
||||
Platform: "linux",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
mtplr = modifyTeamPolicyResponse{}
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d/policies/%d", inclAllTeam.ID, policy4.ID), modifyTeamPolicyRequest{
|
||||
ModifyPolicyPayload: fleet.ModifyPolicyPayload{
|
||||
SoftwareTitleID: optjson.Any[uint]{Set: true, Valid: true, Value: vimInclAllTitleID},
|
||||
},
|
||||
}, http.StatusOK, &mtplr)
|
||||
|
||||
host1LastInstall, err = s.ds.GetHostLastInstallData(ctx, host.ID, vimInclAllInstallerID)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, host1LastInstall)
|
||||
|
||||
// Send back a failed result for policy4.
|
||||
distributedResp = submitDistributedQueryResultsResponse{}
|
||||
s.DoJSONWithoutAuth("POST", "/api/osquery/distributed/write", genDistributedReqWithPolicyResults(
|
||||
host,
|
||||
map[uint]*bool{
|
||||
policy4.ID: ptr.Bool(false),
|
||||
},
|
||||
), http.StatusOK, &distributedResp)
|
||||
err = s.ds.UpdateHostPolicyCounts(ctx)
|
||||
require.NoError(t, err)
|
||||
policy4, err = s.ds.Policy(ctx, policy4.ID)
|
||||
require.NoError(t, err)
|
||||
// Host is missing lbl3, so the installer is not in scope. Policy should not be counted as failed.
|
||||
require.Equal(t, uint(0), policy4.PassingHostCount)
|
||||
require.Equal(t, uint(0), policy4.FailingHostCount)
|
||||
|
||||
// No installation attempt for vim, because host is missing one of the required labels.
|
||||
host1LastInstall, err = s.ds.GetHostLastInstallData(ctx, host.ID, vimInclAllInstallerID)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, host1LastInstall)
|
||||
|
||||
// Now add lbl3 to the host and re-run the policy failure. vim should now be in scope.
|
||||
err = s.ds.RecordLabelQueryExecutions(context.Background(), host, map[uint]*bool{lbl3.ID: ptr.Bool(true)}, time.Now(), false)
|
||||
require.NoError(t, err)
|
||||
|
||||
distributedResp = submitDistributedQueryResultsResponse{}
|
||||
s.DoJSONWithoutAuth("POST", "/api/osquery/distributed/write", genDistributedReqWithPolicyResults(
|
||||
host,
|
||||
map[uint]*bool{
|
||||
policy4.ID: ptr.Bool(false),
|
||||
},
|
||||
), http.StatusOK, &distributedResp)
|
||||
err = s.ds.UpdateHostPolicyCounts(ctx)
|
||||
require.NoError(t, err)
|
||||
policy4, err = s.ds.Policy(ctx, policy4.ID)
|
||||
require.NoError(t, err)
|
||||
// Host now has all required labels, so the policy is marked as failed.
|
||||
require.Equal(t, uint(0), policy4.PassingHostCount)
|
||||
require.Equal(t, uint(1), policy4.FailingHostCount)
|
||||
|
||||
// Installation attempt was made for vim now that the host has all required labels.
|
||||
host1LastInstall, err = s.ds.GetHostLastInstallData(ctx, host.ID, vimInclAllInstallerID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, host1LastInstall)
|
||||
}
|
||||
|
||||
func (s *integrationEnterpriseTestSuite) TestPolicyAutomationLabelScopingRetrigger() {
|
||||
|
|
@ -19795,6 +20167,7 @@ func (s *integrationEnterpriseTestSuite) TestMaintainedApps() {
|
|||
swTitle = titleResp.SoftwareTitle
|
||||
require.NotNil(t, swTitle.SoftwarePackage)
|
||||
require.Empty(t, swTitle.SoftwarePackage.LabelsExcludeAny)
|
||||
require.Empty(t, swTitle.SoftwarePackage.LabelsIncludeAll)
|
||||
require.Len(t, swTitle.SoftwarePackage.LabelsIncludeAny, 2)
|
||||
gotNames := make(map[string]bool)
|
||||
for _, lbl := range swTitle.SoftwarePackage.LabelsIncludeAny {
|
||||
|
|
@ -19822,7 +20195,7 @@ func (s *integrationEnterpriseTestSuite) TestMaintainedApps() {
|
|||
req.LabelsExcludeAny = []string{lbl1.Name}
|
||||
addMAResp = addFleetMaintainedAppResponse{}
|
||||
r = s.Do("POST", "/api/latest/fleet/software/fleet_maintained_apps", req, http.StatusBadRequest)
|
||||
require.Contains(t, extractServerErrorText(r.Body), `Only one of "labels_include_any" or "labels_exclude_any" can be included`)
|
||||
require.Contains(t, extractServerErrorText(r.Body), `Only one of "labels_include_all", "labels_include_any" or "labels_exclude_any" can be included`)
|
||||
}
|
||||
|
||||
func (s *integrationEnterpriseTestSuite) TestUpgradeCodesFromMaintainedApps() {
|
||||
|
|
|
|||
|
|
@ -12303,6 +12303,27 @@ func (s *integrationMDMTestSuite) TestBatchAssociateAppStoreApps() {
|
|||
require.NoError(t, err)
|
||||
require.Len(t, assoc, 1)
|
||||
|
||||
// Add a label
|
||||
clr := createLabelResponse{}
|
||||
s.DoJSON("POST", "/api/latest/fleet/labels", createLabelRequest{
|
||||
LabelPayload: fleet.LabelPayload{
|
||||
Name: "label1" + t.Name(),
|
||||
Query: "SELECT 1;",
|
||||
},
|
||||
}, http.StatusOK, &clr)
|
||||
|
||||
label1 := clr.Label
|
||||
|
||||
clr = createLabelResponse{}
|
||||
s.DoJSON("POST", "/api/latest/fleet/labels", createLabelRequest{
|
||||
LabelPayload: fleet.LabelPayload{
|
||||
Name: "label2" + t.Name(),
|
||||
Query: "SELECT 2;",
|
||||
},
|
||||
}, http.StatusOK, &clr)
|
||||
|
||||
label2 := clr.Label
|
||||
|
||||
// Associating two apps we own
|
||||
beforeAssociation := time.Now()
|
||||
s.DoJSON("POST",
|
||||
|
|
@ -12310,7 +12331,7 @@ func (s *integrationMDMTestSuite) TestBatchAssociateAppStoreApps() {
|
|||
batchAssociateAppStoreAppsRequest{
|
||||
Apps: []fleet.VPPBatchPayload{
|
||||
{AppStoreID: s.appleVPPConfigSrvConfig.Assets[0].AdamID},
|
||||
{AppStoreID: s.appleVPPConfigSrvConfig.Assets[1].AdamID, SelfService: true, Categories: []string{"Browsers"}},
|
||||
{AppStoreID: s.appleVPPConfigSrvConfig.Assets[1].AdamID, SelfService: true, Categories: []string{"Browsers"}, LabelsIncludeAll: []string{label1.Name, label2.Name}},
|
||||
},
|
||||
}, http.StatusOK, &batchAssociateResponse, "team_name", tmGood.Name,
|
||||
)
|
||||
|
|
@ -12332,6 +12353,11 @@ func (s *integrationMDMTestSuite) TestBatchAssociateAppStoreApps() {
|
|||
var getSWTitle getSoftwareTitleResponse
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/software/titles/%d", st.ID), nil, http.StatusOK, &getSWTitle, "team_id", fmt.Sprint(tmGood.ID))
|
||||
s.Assert().ElementsMatch([]string{"Browsers"}, getSWTitle.SoftwareTitle.AppStoreApp.Categories)
|
||||
var labelNames []string
|
||||
for _, l := range getSWTitle.SoftwareTitle.AppStoreApp.LabelsIncludeAll {
|
||||
labelNames = append(labelNames, l.LabelName)
|
||||
}
|
||||
s.Assert().ElementsMatch([]string{label1.Name, label2.Name}, labelNames)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -13249,7 +13275,7 @@ func (s *integrationMDMTestSuite) TestVPPApps() {
|
|||
LabelsExcludeAny: []string{l2.Name},
|
||||
}
|
||||
res := s.Do("POST", "/api/latest/fleet/software/app_store_apps", addAppReq, http.StatusBadRequest)
|
||||
require.Contains(t, extractServerErrorText(res.Body), `Only one of "labels_include_any" or "labels_exclude_any" can be included`)
|
||||
require.Contains(t, extractServerErrorText(res.Body), `Only one of "labels_include_all", "labels_include_any" or "labels_exclude_any" can be included`)
|
||||
|
||||
// Now add it for real
|
||||
addAppReq.LabelsExcludeAny = []string{}
|
||||
|
|
@ -13266,6 +13292,7 @@ func (s *integrationMDMTestSuite) TestVPPApps() {
|
|||
require.NotNil(t, getSWTitle.SoftwareTitle.AppStoreApp)
|
||||
require.Equal(t, getSWTitle.SoftwareTitle.AppStoreApp.AdamID, includeAnyApp.AdamID)
|
||||
require.Empty(t, getSWTitle.SoftwareTitle.AppStoreApp.LabelsExcludeAny)
|
||||
require.Empty(t, getSWTitle.SoftwareTitle.AppStoreApp.LabelsIncludeAll)
|
||||
require.Equal(t, getSWTitle.SoftwareTitle.AppStoreApp.LabelsIncludeAny, []fleet.SoftwareScopeLabel{{LabelName: l1.Name, LabelID: l1.ID}})
|
||||
|
||||
// Add an app with exclude_any labels
|
||||
|
|
@ -13299,6 +13326,7 @@ func (s *integrationMDMTestSuite) TestVPPApps() {
|
|||
require.NotNil(t, getSWTitle.SoftwareTitle.AppStoreApp)
|
||||
require.Equal(t, getSWTitle.SoftwareTitle.AppStoreApp.AdamID, excludeAnyApp.AdamID)
|
||||
require.Empty(t, getSWTitle.SoftwareTitle.AppStoreApp.LabelsIncludeAny)
|
||||
require.Empty(t, getSWTitle.SoftwareTitle.AppStoreApp.LabelsIncludeAll)
|
||||
require.Equal(t, getSWTitle.SoftwareTitle.AppStoreApp.LabelsExcludeAny, []fleet.SoftwareScopeLabel{{LabelName: l2.Name, LabelID: l2.ID}})
|
||||
require.True(t, getSWTitle.SoftwareTitle.AppStoreApp.SelfService)
|
||||
|
||||
|
|
@ -13335,7 +13363,7 @@ func (s *integrationMDMTestSuite) TestVPPApps() {
|
|||
updateAppReq.LabelsIncludeAny = []string{l1.Name}
|
||||
updateAppReq.LabelsExcludeAny = []string{l1.Name}
|
||||
res = s.Do("PATCH", fmt.Sprintf("/api/latest/fleet/software/titles/%d/app_store_app", titleID), updateAppReq, http.StatusBadRequest)
|
||||
require.Contains(t, extractServerErrorText(res.Body), `Only one of "labels_include_any" or "labels_exclude_any" can be included.`)
|
||||
require.Contains(t, extractServerErrorText(res.Body), `Only one of "labels_include_all", "labels_include_any" or "labels_exclude_any" can be included.`)
|
||||
|
||||
// Attempt to update with a non-existent label. Should fail.
|
||||
updateAppReq.LabelsExcludeAny = []string{}
|
||||
|
|
@ -13352,6 +13380,7 @@ func (s *integrationMDMTestSuite) TestVPPApps() {
|
|||
require.Equal(t, updateAppResp.AppStoreApp.AdamID, excludeAnyApp.AdamID)
|
||||
require.Equal(t, updateAppResp.AppStoreApp.LabelsIncludeAny, []fleet.SoftwareScopeLabel{{LabelName: l2.Name, LabelID: l2.ID}})
|
||||
require.Empty(t, updateAppResp.AppStoreApp.LabelsExcludeAny)
|
||||
require.Empty(t, updateAppResp.AppStoreApp.LabelsIncludeAll)
|
||||
require.False(t, updateAppResp.AppStoreApp.SelfService)
|
||||
require.Equal(t, fleet.MacOSPlatform, updateAppResp.AppStoreApp.Platform)
|
||||
|
||||
|
|
@ -13367,6 +13396,7 @@ func (s *integrationMDMTestSuite) TestVPPApps() {
|
|||
require.Equal(t, getSWTitle.SoftwareTitle.AppStoreApp.AdamID, excludeAnyApp.AdamID)
|
||||
require.Equal(t, getSWTitle.SoftwareTitle.AppStoreApp.LabelsIncludeAny, []fleet.SoftwareScopeLabel{{LabelName: l2.Name, LabelID: l2.ID}})
|
||||
require.Empty(t, getSWTitle.SoftwareTitle.AppStoreApp.LabelsExcludeAny)
|
||||
require.Empty(t, getSWTitle.SoftwareTitle.AppStoreApp.LabelsIncludeAll)
|
||||
require.False(t, getSWTitle.SoftwareTitle.AppStoreApp.SelfService)
|
||||
|
||||
// Attempt an install on the host. This should fail because the host doesn't have the label
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/mdm/maintainedapps"
|
||||
maintained_apps "github.com/fleetdm/fleet/v4/server/mdm/maintainedapps"
|
||||
)
|
||||
|
||||
type addFleetMaintainedAppRequest struct {
|
||||
|
|
@ -20,6 +20,7 @@ type addFleetMaintainedAppRequest struct {
|
|||
UninstallScript string `json:"uninstall_script"`
|
||||
LabelsIncludeAny []string `json:"labels_include_any"`
|
||||
LabelsExcludeAny []string `json:"labels_exclude_any"`
|
||||
LabelsIncludeAll []string `json:"labels_include_all"`
|
||||
AutomaticInstall bool `json:"automatic_install"`
|
||||
Categories []string `json:"categories"`
|
||||
}
|
||||
|
|
@ -82,6 +83,7 @@ func addFleetMaintainedAppEndpoint(ctx context.Context, request interface{}, svc
|
|||
req.AutomaticInstall,
|
||||
req.LabelsIncludeAny,
|
||||
req.LabelsExcludeAny,
|
||||
req.LabelsIncludeAll,
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, context.DeadlineExceeded) {
|
||||
|
|
@ -93,7 +95,7 @@ func addFleetMaintainedAppEndpoint(ctx context.Context, request interface{}, svc
|
|||
return &addFleetMaintainedAppResponse{SoftwareTitleID: titleId}, nil
|
||||
}
|
||||
|
||||
func (svc *Service) AddFleetMaintainedApp(ctx context.Context, _ *uint, _ uint, _, _, _, _ string, _ bool, _ bool, _, _ []string) (uint, error) {
|
||||
func (svc *Service) AddFleetMaintainedApp(ctx context.Context, _ *uint, _ uint, _, _, _, _ string, _ bool, _ bool, _, _, _ []string) (uint, error) {
|
||||
// skipauth: No authorization check needed due to implementation returning
|
||||
// only license error.
|
||||
svc.authz.SkipAuthorization(ctx)
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ type uploadSoftwareInstallerRequest struct {
|
|||
UninstallScript string
|
||||
LabelsIncludeAny []string
|
||||
LabelsExcludeAny []string
|
||||
LabelsIncludeAll []string
|
||||
AutomaticInstall bool
|
||||
}
|
||||
|
||||
|
|
@ -49,6 +50,7 @@ type updateSoftwareInstallerRequest struct {
|
|||
SelfService *bool
|
||||
LabelsIncludeAny []string
|
||||
LabelsExcludeAny []string
|
||||
LabelsIncludeAll []string
|
||||
Categories []string
|
||||
DisplayName *string
|
||||
}
|
||||
|
|
@ -144,8 +146,8 @@ func (updateSoftwareInstallerRequest) DecodeRequest(ctx context.Context, r *http
|
|||
}
|
||||
|
||||
// decode labels and categories
|
||||
var inclAny, exclAny, categories []string
|
||||
var existsInclAny, existsExclAny, existsCategories bool
|
||||
var inclAny, exclAny, inclAll, categories []string
|
||||
var existsInclAny, existsExclAny, existsInclAll, existsCategories bool
|
||||
|
||||
inclAny, existsInclAny = r.MultipartForm.Value[string(fleet.LabelsIncludeAny)]
|
||||
switch {
|
||||
|
|
@ -167,6 +169,16 @@ func (updateSoftwareInstallerRequest) DecodeRequest(ctx context.Context, r *http
|
|||
decoded.LabelsExcludeAny = exclAny
|
||||
}
|
||||
|
||||
inclAll, existsInclAll = r.MultipartForm.Value[string(fleet.LabelsIncludeAll)]
|
||||
switch {
|
||||
case !existsInclAll:
|
||||
decoded.LabelsIncludeAll = nil
|
||||
case len(inclAll) == 1 && inclAll[0] == "":
|
||||
decoded.LabelsIncludeAll = []string{}
|
||||
default:
|
||||
decoded.LabelsIncludeAll = inclAll
|
||||
}
|
||||
|
||||
categories, existsCategories = r.MultipartForm.Value["categories"]
|
||||
switch {
|
||||
case !existsCategories:
|
||||
|
|
@ -235,6 +247,7 @@ func updateSoftwareInstallerEndpoint(ctx context.Context, request interface{}, s
|
|||
SelfService: req.SelfService,
|
||||
LabelsIncludeAny: req.LabelsIncludeAny,
|
||||
LabelsExcludeAny: req.LabelsExcludeAny,
|
||||
LabelsIncludeAll: req.LabelsIncludeAll,
|
||||
Categories: req.Categories,
|
||||
DisplayName: req.DisplayName,
|
||||
}
|
||||
|
|
@ -355,8 +368,8 @@ func (uploadSoftwareInstallerRequest) DecodeRequest(ctx context.Context, r *http
|
|||
}
|
||||
|
||||
// decode labels
|
||||
var inclAny, exclAny []string
|
||||
var existsInclAny, existsExclAny bool
|
||||
var inclAny, exclAny, inclAll []string
|
||||
var existsInclAny, existsExclAny, existsInclAll bool
|
||||
|
||||
inclAny, existsInclAny = r.MultipartForm.Value[string(fleet.LabelsIncludeAny)]
|
||||
switch {
|
||||
|
|
@ -378,6 +391,16 @@ func (uploadSoftwareInstallerRequest) DecodeRequest(ctx context.Context, r *http
|
|||
decoded.LabelsExcludeAny = exclAny
|
||||
}
|
||||
|
||||
inclAll, existsInclAll = r.MultipartForm.Value[string(fleet.LabelsIncludeAll)]
|
||||
switch {
|
||||
case !existsInclAll:
|
||||
decoded.LabelsIncludeAll = nil
|
||||
case len(inclAll) == 1 && inclAll[0] == "":
|
||||
decoded.LabelsIncludeAll = []string{}
|
||||
default:
|
||||
decoded.LabelsIncludeAll = inclAll
|
||||
}
|
||||
|
||||
val, ok = r.MultipartForm.Value["automatic_install"]
|
||||
if ok && len(val) > 0 && val[0] != "" {
|
||||
parsed, err := strconv.ParseBool(val[0])
|
||||
|
|
@ -434,6 +457,7 @@ func uploadSoftwareInstallerEndpoint(ctx context.Context, request interface{}, s
|
|||
UninstallScript: req.UninstallScript,
|
||||
LabelsIncludeAny: req.LabelsIncludeAny,
|
||||
LabelsExcludeAny: req.LabelsExcludeAny,
|
||||
LabelsIncludeAll: req.LabelsIncludeAll,
|
||||
AutomaticInstall: req.AutomaticInstall,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -212,7 +212,7 @@ func TestValidateSoftwareLabels(t *testing.T) {
|
|||
|
||||
t.Run("validate no update", func(t *testing.T) {
|
||||
t.Run("no auth context", func(t *testing.T) {
|
||||
_, err := eeservice.ValidateSoftwareLabels(context.Background(), svc, nil, nil, nil)
|
||||
_, err := eeservice.ValidateSoftwareLabels(context.Background(), svc, nil, nil, nil, nil)
|
||||
require.ErrorContains(t, err, "Authentication required")
|
||||
})
|
||||
|
||||
|
|
@ -220,7 +220,7 @@ func TestValidateSoftwareLabels(t *testing.T) {
|
|||
ctx = authz_ctx.NewContext(ctx, &authCtx)
|
||||
|
||||
t.Run("no auth checked", func(t *testing.T) {
|
||||
_, err := eeservice.ValidateSoftwareLabels(ctx, svc, nil, nil, nil)
|
||||
_, err := eeservice.ValidateSoftwareLabels(ctx, svc, nil, nil, nil, nil)
|
||||
require.ErrorContains(t, err, "Authentication required")
|
||||
})
|
||||
|
||||
|
|
@ -267,6 +267,7 @@ func TestValidateSoftwareLabels(t *testing.T) {
|
|||
name string
|
||||
payloadIncludeAny []string
|
||||
payloadExcludeAny []string
|
||||
payloadIncludeAll []string
|
||||
expectLabels map[string]fleet.LabelIdent
|
||||
expectScope fleet.LabelScope
|
||||
expectError string
|
||||
|
|
@ -276,6 +277,7 @@ func TestValidateSoftwareLabels(t *testing.T) {
|
|||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
"",
|
||||
"",
|
||||
},
|
||||
|
|
@ -283,6 +285,7 @@ func TestValidateSoftwareLabels(t *testing.T) {
|
|||
"include labels",
|
||||
[]string{"foo", "bar"},
|
||||
nil,
|
||||
nil,
|
||||
map[string]fleet.LabelIdent{
|
||||
"foo": {LabelID: 1, LabelName: "foo"},
|
||||
"bar": {LabelID: 2, LabelName: "bar"},
|
||||
|
|
@ -294,6 +297,7 @@ func TestValidateSoftwareLabels(t *testing.T) {
|
|||
"exclude labels",
|
||||
nil,
|
||||
[]string{"bar", "baz"},
|
||||
nil,
|
||||
map[string]fleet.LabelIdent{
|
||||
"bar": {LabelID: 2, LabelName: "bar"},
|
||||
"baz": {LabelID: 3, LabelName: "baz"},
|
||||
|
|
@ -306,14 +310,16 @@ func TestValidateSoftwareLabels(t *testing.T) {
|
|||
[]string{"foo"},
|
||||
[]string{"bar"},
|
||||
nil,
|
||||
nil,
|
||||
"",
|
||||
`Only one of "labels_include_any" or "labels_exclude_any" can be included.`,
|
||||
`Only one of "labels_include_all", "labels_include_any" or "labels_exclude_any" can be included.`,
|
||||
},
|
||||
{
|
||||
"non-existent label",
|
||||
[]string{"foo", "qux"},
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
"",
|
||||
`Couldn't update. Label "qux" doesn't exist. Please remove the label from the software`,
|
||||
},
|
||||
|
|
@ -321,6 +327,7 @@ func TestValidateSoftwareLabels(t *testing.T) {
|
|||
"duplicate label",
|
||||
[]string{"foo", "foo"},
|
||||
nil,
|
||||
nil,
|
||||
map[string]fleet.LabelIdent{
|
||||
"foo": {LabelID: 1, LabelName: "foo"},
|
||||
},
|
||||
|
|
@ -332,6 +339,7 @@ func TestValidateSoftwareLabels(t *testing.T) {
|
|||
nil,
|
||||
[]string{},
|
||||
nil,
|
||||
nil,
|
||||
"",
|
||||
"",
|
||||
},
|
||||
|
|
@ -340,13 +348,14 @@ func TestValidateSoftwareLabels(t *testing.T) {
|
|||
nil,
|
||||
[]string{""},
|
||||
nil,
|
||||
nil,
|
||||
"",
|
||||
`Couldn't update. Label "" doesn't exist. Please remove the label from the software`,
|
||||
},
|
||||
}
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := eeservice.ValidateSoftwareLabels(ctx, svc, nil, tt.payloadIncludeAny, tt.payloadExcludeAny)
|
||||
got, err := eeservice.ValidateSoftwareLabels(ctx, svc, nil, tt.payloadIncludeAny, tt.payloadExcludeAny, tt.payloadIncludeAll)
|
||||
if tt.expectError != "" {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tt.expectError)
|
||||
|
|
@ -362,7 +371,7 @@ func TestValidateSoftwareLabels(t *testing.T) {
|
|||
|
||||
t.Run("validate update", func(t *testing.T) {
|
||||
t.Run("no auth context", func(t *testing.T) {
|
||||
_, _, err := eeservice.ValidateSoftwareLabelsForUpdate(context.Background(), svc, nil, nil, nil)
|
||||
_, _, err := eeservice.ValidateSoftwareLabelsForUpdate(context.Background(), svc, nil, nil, nil, nil)
|
||||
require.ErrorContains(t, err, "Authentication required")
|
||||
})
|
||||
|
||||
|
|
@ -370,7 +379,7 @@ func TestValidateSoftwareLabels(t *testing.T) {
|
|||
ctx = authz_ctx.NewContext(ctx, &authCtx)
|
||||
|
||||
t.Run("no auth checked", func(t *testing.T) {
|
||||
_, _, err := eeservice.ValidateSoftwareLabelsForUpdate(ctx, svc, nil, nil, nil)
|
||||
_, _, err := eeservice.ValidateSoftwareLabelsForUpdate(ctx, svc, nil, nil, nil, nil)
|
||||
require.ErrorContains(t, err, "Authentication required")
|
||||
})
|
||||
|
||||
|
|
@ -402,6 +411,7 @@ func TestValidateSoftwareLabels(t *testing.T) {
|
|||
existingInstaller *fleet.SoftwareInstaller
|
||||
payloadIncludeAny []string
|
||||
payloadExcludeAny []string
|
||||
payloadIncludeAll []string
|
||||
shouldUpdate bool
|
||||
expectLabels map[string]fleet.LabelIdent
|
||||
expectScope fleet.LabelScope
|
||||
|
|
@ -412,6 +422,7 @@ func TestValidateSoftwareLabels(t *testing.T) {
|
|||
nil,
|
||||
nil,
|
||||
[]string{"foo"},
|
||||
nil,
|
||||
false,
|
||||
nil,
|
||||
"",
|
||||
|
|
@ -422,6 +433,7 @@ func TestValidateSoftwareLabels(t *testing.T) {
|
|||
&fleet.SoftwareInstaller{},
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
false,
|
||||
nil,
|
||||
"",
|
||||
|
|
@ -435,6 +447,7 @@ func TestValidateSoftwareLabels(t *testing.T) {
|
|||
},
|
||||
[]string{"foo", "bar"},
|
||||
nil,
|
||||
nil,
|
||||
true,
|
||||
map[string]fleet.LabelIdent{
|
||||
"foo": {LabelID: 1, LabelName: "foo"},
|
||||
|
|
@ -451,6 +464,7 @@ func TestValidateSoftwareLabels(t *testing.T) {
|
|||
},
|
||||
nil,
|
||||
[]string{"foo"},
|
||||
nil,
|
||||
true,
|
||||
map[string]fleet.LabelIdent{
|
||||
"foo": {LabelID: 1, LabelName: "foo"},
|
||||
|
|
@ -466,6 +480,7 @@ func TestValidateSoftwareLabels(t *testing.T) {
|
|||
},
|
||||
[]string{},
|
||||
nil,
|
||||
nil,
|
||||
true,
|
||||
nil,
|
||||
"",
|
||||
|
|
@ -479,6 +494,7 @@ func TestValidateSoftwareLabels(t *testing.T) {
|
|||
},
|
||||
[]string{"foo"},
|
||||
nil,
|
||||
nil,
|
||||
false,
|
||||
nil,
|
||||
"",
|
||||
|
|
@ -488,7 +504,7 @@ func TestValidateSoftwareLabels(t *testing.T) {
|
|||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
shouldUpate, got, err := eeservice.ValidateSoftwareLabelsForUpdate(ctx, svc, tt.existingInstaller, tt.payloadIncludeAny, tt.payloadExcludeAny)
|
||||
shouldUpate, got, err := eeservice.ValidateSoftwareLabelsForUpdate(ctx, svc, tt.existingInstaller, tt.payloadIncludeAny, tt.payloadExcludeAny, tt.payloadIncludeAll)
|
||||
if tt.expectError != "" {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), tt.expectError)
|
||||
|
|
|
|||
|
|
@ -379,6 +379,7 @@ func TestDeleteSoftwareTitleIcon(t *testing.T) {
|
|||
Platform: nil,
|
||||
LabelsIncludeAny: nil,
|
||||
LabelsExcludeAny: nil,
|
||||
LabelsIncludeAll: nil,
|
||||
}, nil
|
||||
}
|
||||
ds.DeleteSoftwareTitleIconFunc = func(ctx context.Context, teamID uint, titleID uint) error {
|
||||
|
|
@ -401,6 +402,7 @@ func TestDeleteSoftwareTitleIcon(t *testing.T) {
|
|||
SoftwareIconURL: ptr.String(""),
|
||||
LabelsIncludeAny: nil,
|
||||
LabelsExcludeAny: nil,
|
||||
LabelsIncludeAll: nil,
|
||||
SoftwareTitleID: 1,
|
||||
}
|
||||
require.Equal(t, expectedActivity, capturedActivity)
|
||||
|
|
@ -426,6 +428,7 @@ func TestDeleteSoftwareTitleIcon(t *testing.T) {
|
|||
Platform: &platform,
|
||||
LabelsIncludeAny: nil,
|
||||
LabelsExcludeAny: nil,
|
||||
LabelsIncludeAll: nil,
|
||||
}, nil
|
||||
}
|
||||
ds.DeleteSoftwareTitleIconFunc = func(ctx context.Context, teamID uint, titleID uint) error {
|
||||
|
|
@ -450,6 +453,7 @@ func TestDeleteSoftwareTitleIcon(t *testing.T) {
|
|||
SoftwareIconURL: ptr.String("fleetdm.com/icon.png"), // note this is supposed to be the vpp_apps.icon_url
|
||||
LabelsIncludeAny: nil,
|
||||
LabelsExcludeAny: nil,
|
||||
LabelsIncludeAll: nil,
|
||||
}
|
||||
require.Equal(t, expectedActivity, capturedActivity)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -765,6 +765,11 @@ func (ts *withServer) uploadSoftwareInstallerWithErrorNameReason(
|
|||
require.NoError(t, w.WriteField("labels_exclude_any", l))
|
||||
}
|
||||
}
|
||||
if payload.LabelsIncludeAll != nil {
|
||||
for _, l := range payload.LabelsIncludeAll {
|
||||
require.NoError(t, w.WriteField("labels_include_all", l))
|
||||
}
|
||||
}
|
||||
if payload.AutomaticInstall {
|
||||
require.NoError(t, w.WriteField("automatic_install", "true"))
|
||||
}
|
||||
|
|
@ -847,6 +852,11 @@ func (ts *withServer) updateSoftwareInstaller(
|
|||
require.NoError(t, w.WriteField("labels_exclude_any", l))
|
||||
}
|
||||
}
|
||||
if payload.LabelsIncludeAll != nil {
|
||||
for _, l := range payload.LabelsIncludeAll {
|
||||
require.NoError(t, w.WriteField("labels_include_all", l))
|
||||
}
|
||||
}
|
||||
if payload.Categories != nil {
|
||||
for _, c := range payload.Categories {
|
||||
require.NoError(t, w.WriteField("categories", c))
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ type addAppStoreAppRequest struct {
|
|||
AutomaticInstall bool `json:"automatic_install"`
|
||||
LabelsIncludeAny []string `json:"labels_include_any"`
|
||||
LabelsExcludeAny []string `json:"labels_exclude_any"`
|
||||
LabelsIncludeAll []string `json:"labels_include_all"`
|
||||
Categories []string `json:"categories"`
|
||||
Configuration json.RawMessage `json:"configuration,omitempty"`
|
||||
}
|
||||
|
|
@ -77,6 +78,7 @@ func addAppStoreAppEndpoint(ctx context.Context, request interface{}, svc fleet.
|
|||
SelfService: req.SelfService,
|
||||
LabelsIncludeAny: req.LabelsIncludeAny,
|
||||
LabelsExcludeAny: req.LabelsExcludeAny,
|
||||
LabelsIncludeAll: req.LabelsIncludeAll,
|
||||
AddAutoInstallPolicy: req.AutomaticInstall,
|
||||
Categories: req.Categories,
|
||||
Configuration: req.Configuration,
|
||||
|
|
@ -106,6 +108,7 @@ type updateAppStoreAppRequest struct {
|
|||
SelfService *bool `json:"self_service"`
|
||||
LabelsIncludeAny []string `json:"labels_include_any"`
|
||||
LabelsExcludeAny []string `json:"labels_exclude_any"`
|
||||
LabelsIncludeAll []string `json:"labels_include_all"`
|
||||
Categories []string `json:"categories"`
|
||||
Configuration json.RawMessage `json:"configuration,omitempty"`
|
||||
DisplayName *string `json:"display_name"`
|
||||
|
|
@ -133,6 +136,7 @@ func updateAppStoreAppEndpoint(ctx context.Context, request interface{}, svc fle
|
|||
SelfService: req.SelfService,
|
||||
LabelsIncludeAny: req.LabelsIncludeAny,
|
||||
LabelsExcludeAny: req.LabelsExcludeAny,
|
||||
LabelsIncludeAll: req.LabelsIncludeAll,
|
||||
Categories: req.Categories,
|
||||
Configuration: req.Configuration,
|
||||
DisplayName: req.DisplayName,
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ github.com/fleetdm/fleet/v4/server/fleet/SoftwarePackageSpec PostInstallScript f
|
|||
github.com/fleetdm/fleet/v4/server/fleet/SoftwarePackageSpec UninstallScript fleet.TeamSpecSoftwareAsset
|
||||
github.com/fleetdm/fleet/v4/server/fleet/SoftwarePackageSpec LabelsIncludeAny []string
|
||||
github.com/fleetdm/fleet/v4/server/fleet/SoftwarePackageSpec LabelsExcludeAny []string
|
||||
github.com/fleetdm/fleet/v4/server/fleet/SoftwarePackageSpec LabelsIncludeAll []string
|
||||
github.com/fleetdm/fleet/v4/server/fleet/SoftwarePackageSpec InstallDuringSetup optjson.Bool
|
||||
github.com/fleetdm/fleet/v4/server/fleet/SoftwarePackageSpec Icon fleet.TeamSpecSoftwareAsset
|
||||
github.com/fleetdm/fleet/v4/server/fleet/SoftwarePackageSpec Slug *string
|
||||
|
|
@ -129,6 +130,7 @@ github.com/fleetdm/fleet/v4/server/fleet/MaintainedAppSpec PostInstallScript fle
|
|||
github.com/fleetdm/fleet/v4/server/fleet/MaintainedAppSpec UninstallScript fleet.TeamSpecSoftwareAsset
|
||||
github.com/fleetdm/fleet/v4/server/fleet/MaintainedAppSpec LabelsIncludeAny []string
|
||||
github.com/fleetdm/fleet/v4/server/fleet/MaintainedAppSpec LabelsExcludeAny []string
|
||||
github.com/fleetdm/fleet/v4/server/fleet/MaintainedAppSpec LabelsIncludeAll []string
|
||||
github.com/fleetdm/fleet/v4/server/fleet/MaintainedAppSpec Categories []string
|
||||
github.com/fleetdm/fleet/v4/server/fleet/MaintainedAppSpec InstallDuringSetup optjson.Bool
|
||||
github.com/fleetdm/fleet/v4/server/fleet/MaintainedAppSpec Icon fleet.TeamSpecSoftwareAsset
|
||||
|
|
@ -140,6 +142,7 @@ github.com/fleetdm/fleet/v4/server/fleet/TeamSpecAppStoreApp AppStoreID string
|
|||
github.com/fleetdm/fleet/v4/server/fleet/TeamSpecAppStoreApp SelfService bool
|
||||
github.com/fleetdm/fleet/v4/server/fleet/TeamSpecAppStoreApp LabelsIncludeAny []string
|
||||
github.com/fleetdm/fleet/v4/server/fleet/TeamSpecAppStoreApp LabelsExcludeAny []string
|
||||
github.com/fleetdm/fleet/v4/server/fleet/TeamSpecAppStoreApp LabelsIncludeAll []string
|
||||
github.com/fleetdm/fleet/v4/server/fleet/TeamSpecAppStoreApp Categories []string
|
||||
github.com/fleetdm/fleet/v4/server/fleet/TeamSpecAppStoreApp InstallDuringSetup optjson.Bool
|
||||
github.com/fleetdm/fleet/v4/server/fleet/TeamSpecAppStoreApp Icon fleet.TeamSpecSoftwareAsset
|
||||
|
|
|
|||
Loading…
Reference in a new issue