mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
Add VPP install automation in GitOps (#25400)
For #23531. # Checklist for submitter If some of the following don't apply, delete the relevant line. <!-- Note that API documentation changes are now addressed by the product design team. --> - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements) - [x] Added/updated automated tests - [x] A detailed QA plan exists on the associated ticket (if it isn't there, work with the product group's QA engineer to add it) - [x] Manual QA for all new/changed functionality
This commit is contained in:
parent
d8897e0cca
commit
4f0a2e2af9
24 changed files with 760 additions and 205 deletions
|
|
@ -92,9 +92,10 @@ func applyCommand() *cli.Command {
|
|||
baseDir := filepath.Dir(flFilename)
|
||||
|
||||
teamsSoftwareInstallers := make(map[string][]fleet.SoftwarePackageResponse)
|
||||
teamsVPPApps := make(map[string][]fleet.VPPAppResponse)
|
||||
teamsScripts := make(map[string][]fleet.ScriptResponse)
|
||||
|
||||
_, _, _, err = fleetClient.ApplyGroup(c.Context, false, specs, baseDir, logf, nil, opts, teamsSoftwareInstallers, teamsScripts)
|
||||
_, _, _, _, err = fleetClient.ApplyGroup(c.Context, false, specs, baseDir, logf, nil, opts, teamsSoftwareInstallers, teamsVPPApps, teamsScripts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ func gitopsCommand() *cli.Command {
|
|||
|
||||
// we keep track of team software installers and scripts for correct policy application
|
||||
teamsSoftwareInstallers := make(map[string][]fleet.SoftwarePackageResponse)
|
||||
teamsVPPApps := make(map[string][]fleet.VPPAppResponse)
|
||||
teamsScripts := make(map[string][]fleet.ScriptResponse)
|
||||
|
||||
// We keep track of the secrets to check if duplicates exist during dry run
|
||||
|
|
@ -227,7 +228,7 @@ func gitopsCommand() *cli.Command {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
assumptions, err := fleetClient.DoGitOps(c.Context, config, flFilename, logf, flDryRun, teamDryRunAssumptions, appConfig, teamsSoftwareInstallers, teamsScripts)
|
||||
assumptions, err := fleetClient.DoGitOps(c.Context, config, flFilename, logf, flDryRun, teamDryRunAssumptions, appConfig, teamsSoftwareInstallers, teamsVPPApps, teamsScripts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1105,6 +1105,9 @@ func TestGitOpsBasicGlobalAndTeam(t *testing.T) {
|
|||
ds.BatchInsertVPPAppsFunc = func(ctx context.Context, apps []*fleet.VPPApp) error {
|
||||
return nil
|
||||
}
|
||||
ds.GetVPPAppsFunc = func(ctx context.Context, teamID *uint) ([]fleet.VPPAppResponse, error) {
|
||||
return []fleet.VPPAppResponse{}, nil
|
||||
}
|
||||
|
||||
const (
|
||||
fleetServerURL = "https://fleet.example.com"
|
||||
|
|
@ -1930,6 +1933,9 @@ func TestGitOpsTeamSofwareInstallers(t *testing.T) {
|
|||
ds.SetTeamVPPAppsFunc = func(ctx context.Context, teamID *uint, adamIDs []fleet.VPPAppTeam) error {
|
||||
return nil
|
||||
}
|
||||
ds.GetVPPAppsFunc = func(ctx context.Context, teamID *uint) ([]fleet.VPPAppResponse, error) {
|
||||
return []fleet.VPPAppResponse{}, nil
|
||||
}
|
||||
ds.BatchInsertVPPAppsFunc = func(ctx context.Context, apps []*fleet.VPPApp) error {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -1990,6 +1996,106 @@ func TestGitOpsTeamSoftwareInstallersQueryEnv(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestGitOpsNoTeamVPPPolicies(t *testing.T) {
|
||||
startAndServeVPPServer(t)
|
||||
|
||||
cases := []struct {
|
||||
noTeamFile string
|
||||
wantErr string
|
||||
vppApps []fleet.VPPAppResponse
|
||||
}{
|
||||
{
|
||||
noTeamFile: "testdata/gitops/subdir/no_team_vpp_policies_valid.yml",
|
||||
vppApps: []fleet.VPPAppResponse{
|
||||
{ // for more test coverage
|
||||
Platform: fleet.MacOSPlatform,
|
||||
},
|
||||
{ // for more test coverage
|
||||
TitleID: ptr.Uint(122),
|
||||
Platform: fleet.MacOSPlatform,
|
||||
},
|
||||
{
|
||||
TeamID: ptr.Uint(0),
|
||||
TitleID: ptr.Uint(123),
|
||||
AppStoreID: "1",
|
||||
Platform: fleet.IOSPlatform,
|
||||
},
|
||||
{
|
||||
TeamID: ptr.Uint(0),
|
||||
TitleID: ptr.Uint(124),
|
||||
AppStoreID: "1",
|
||||
Platform: fleet.MacOSPlatform,
|
||||
},
|
||||
{
|
||||
TeamID: ptr.Uint(0),
|
||||
TitleID: ptr.Uint(125),
|
||||
AppStoreID: "1",
|
||||
Platform: fleet.IPadOSPlatform,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, c := range cases {
|
||||
t.Run(filepath.Base(c.noTeamFile), func(t *testing.T) {
|
||||
ds, _, _ := setupFullGitOpsPremiumServer(t)
|
||||
tokExpire := time.Now().Add(time.Hour)
|
||||
token, err := test.CreateVPPTokenEncoded(tokExpire, "fleet", "ca")
|
||||
require.NoError(t, err)
|
||||
|
||||
ds.SetTeamVPPAppsFunc = func(ctx context.Context, teamID *uint, adamIDs []fleet.VPPAppTeam) error {
|
||||
return nil
|
||||
}
|
||||
ds.BatchInsertVPPAppsFunc = func(ctx context.Context, apps []*fleet.VPPApp) error {
|
||||
return nil
|
||||
}
|
||||
ds.GetVPPAppsFunc = func(ctx context.Context, teamID *uint) ([]fleet.VPPAppResponse, error) {
|
||||
return c.vppApps, nil
|
||||
}
|
||||
ds.GetVPPTokenByTeamIDFunc = func(ctx context.Context, teamID *uint) (*fleet.VPPTokenDB, error) {
|
||||
return &fleet.VPPTokenDB{
|
||||
ID: 1,
|
||||
OrgName: "Fleet",
|
||||
Location: "Earth",
|
||||
RenewDate: tokExpire,
|
||||
Token: string(token),
|
||||
Teams: nil,
|
||||
}, nil
|
||||
}
|
||||
labelToIDs := map[string]uint{
|
||||
fleet.BuiltinLabelMacOS14Plus: 1,
|
||||
"a": 2,
|
||||
"b": 3,
|
||||
}
|
||||
ds.LabelIDsByNameFunc = func(ctx context.Context, labels []string) (map[string]uint, error) {
|
||||
// for this test, recognize labels a and b (as well as the built-in macos 14+ one)
|
||||
ret := make(map[string]uint)
|
||||
for _, lbl := range labels {
|
||||
id, ok := labelToIDs[lbl]
|
||||
if ok {
|
||||
ret[lbl] = id
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
t.Setenv("APPLE_BM_DEFAULT_TEAM", "")
|
||||
globalFile := "./testdata/gitops/global_config_no_paths.yml"
|
||||
dstPath := filepath.Join(filepath.Dir(c.noTeamFile), "no-team.yml")
|
||||
t.Cleanup(func() {
|
||||
os.Remove(dstPath)
|
||||
})
|
||||
err = file.Copy(c.noTeamFile, dstPath, 0o755)
|
||||
require.NoError(t, err)
|
||||
_, err = runAppNoChecks([]string{"gitops", "-f", globalFile, "-f", dstPath})
|
||||
if c.wantErr == "" {
|
||||
require.NoError(t, err)
|
||||
} else {
|
||||
require.ErrorContains(t, err, c.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitOpsNoTeamSoftwareInstallers(t *testing.T) {
|
||||
startSoftwareInstallerServer(t)
|
||||
startAndServeVPPServer(t)
|
||||
|
|
@ -2035,6 +2141,9 @@ func TestGitOpsNoTeamSoftwareInstallers(t *testing.T) {
|
|||
ds.BatchInsertVPPAppsFunc = func(ctx context.Context, apps []*fleet.VPPApp) error {
|
||||
return nil
|
||||
}
|
||||
ds.GetVPPAppsFunc = func(ctx context.Context, teamID *uint) ([]fleet.VPPAppResponse, error) {
|
||||
return []fleet.VPPAppResponse{}, nil
|
||||
}
|
||||
ds.GetVPPTokenByTeamIDFunc = func(ctx context.Context, teamID *uint) (*fleet.VPPTokenDB, error) {
|
||||
return &fleet.VPPTokenDB{
|
||||
ID: 1,
|
||||
|
|
@ -2114,6 +2223,9 @@ func TestGitOpsTeamVPPApps(t *testing.T) {
|
|||
ds.BatchInsertVPPAppsFunc = func(ctx context.Context, apps []*fleet.VPPApp) error {
|
||||
return nil
|
||||
}
|
||||
ds.GetVPPAppsFunc = func(ctx context.Context, teamID *uint) ([]fleet.VPPAppResponse, error) {
|
||||
return []fleet.VPPAppResponse{}, nil
|
||||
}
|
||||
|
||||
ds.GetVPPTokenByTeamIDFunc = func(ctx context.Context, teamID *uint) (*fleet.VPPTokenDB, error) {
|
||||
return &fleet.VPPTokenDB{
|
||||
|
|
|
|||
|
|
@ -397,7 +397,8 @@ Use the stop and reset subcommands to manage the server and dependencies once st
|
|||
// so pass in the current working directory.
|
||||
teamsSoftwareInstallers := make(map[string][]fleet.SoftwarePackageResponse)
|
||||
teamsScripts := make(map[string][]fleet.ScriptResponse)
|
||||
_, _, _, err = client.ApplyGroup(c.Context, false, specs, ".", logf, nil, fleet.ApplyClientSpecOptions{}, teamsSoftwareInstallers, teamsScripts)
|
||||
teamsVPPApps := make(map[string][]fleet.VPPAppResponse)
|
||||
_, _, _, _, err = client.ApplyGroup(c.Context, false, specs, ".", logf, nil, fleet.ApplyClientSpecOptions{}, teamsSoftwareInstallers, teamsVPPApps, teamsScripts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
11
cmd/fleetctl/testdata/gitops/subdir/no_team_vpp_policies_valid.yml
vendored
Normal file
11
cmd/fleetctl/testdata/gitops/subdir/no_team_vpp_policies_valid.yml
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
name: No team
|
||||
controls:
|
||||
policies:
|
||||
- name: Slack on macOS is installed
|
||||
platform: darwin
|
||||
query: SELECT 1 FROM apps WHERE name = 'Slack.app';
|
||||
install_software:
|
||||
app_store_id: "1"
|
||||
software:
|
||||
app_store_apps:
|
||||
- app_store_id: "1"
|
||||
|
|
@ -39,9 +39,9 @@ func (svc *Service) getVPPToken(ctx context.Context, teamID *uint) (string, erro
|
|||
return token.Token, nil
|
||||
}
|
||||
|
||||
func (svc *Service) BatchAssociateVPPApps(ctx context.Context, teamName string, payloads []fleet.VPPBatchPayload, dryRun bool) error {
|
||||
func (svc *Service) BatchAssociateVPPApps(ctx context.Context, teamName string, payloads []fleet.VPPBatchPayload, dryRun bool) ([]fleet.VPPAppResponse, error) {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Team{}, fleet.ActionRead); err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var teamID *uint
|
||||
|
|
@ -50,15 +50,15 @@ func (svc *Service) BatchAssociateVPPApps(ctx context.Context, teamName string,
|
|||
if err != nil {
|
||||
// If this is a dry run, the team may not have been created yet
|
||||
if dryRun && fleet.IsNotFound(err) {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
teamID = &tm.ID
|
||||
}
|
||||
|
||||
if err := svc.authz.Authorize(ctx, &fleet.SoftwareInstaller{TeamID: teamID}, fleet.ActionWrite); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "validating authorization")
|
||||
return nil, ctxerr.Wrap(ctx, err, "validating authorization")
|
||||
}
|
||||
|
||||
// Adding VPP apps will add them to all available platforms per decision:
|
||||
|
|
@ -88,7 +88,7 @@ func (svc *Service) BatchAssociateVPPApps(ctx context.Context, teamName string,
|
|||
if dryRun {
|
||||
// On dry runs return early because the VPP token might not exist yet
|
||||
// and we don't want to apply the VPP apps.
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var vppAppTeams []fleet.VPPAppTeam
|
||||
|
|
@ -96,7 +96,7 @@ func (svc *Service) BatchAssociateVPPApps(ctx context.Context, teamName string,
|
|||
if len(payloads) > 0 {
|
||||
token, err := svc.getVPPToken(ctx, teamID)
|
||||
if err != nil {
|
||||
return fleet.NewUserMessageError(ctxerr.Wrap(ctx, err, "could not retrieve vpp token"), http.StatusUnprocessableEntity)
|
||||
return nil, fleet.NewUserMessageError(ctxerr.Wrap(ctx, err, "could not retrieve vpp token"), http.StatusUnprocessableEntity)
|
||||
}
|
||||
|
||||
for _, payload := range payloadsWithPlatform {
|
||||
|
|
@ -104,7 +104,7 @@ func (svc *Service) BatchAssociateVPPApps(ctx context.Context, teamName string,
|
|||
payload.Platform = fleet.MacOSPlatform
|
||||
}
|
||||
if payload.Platform != fleet.IOSPlatform && payload.Platform != fleet.IPadOSPlatform && payload.Platform != fleet.MacOSPlatform {
|
||||
return fleet.NewInvalidArgumentError("app_store_apps.platform",
|
||||
return nil, fleet.NewInvalidArgumentError("app_store_apps.platform",
|
||||
fmt.Sprintf("platform must be one of '%s', '%s', or '%s", fleet.IOSPlatform, fleet.IPadOSPlatform, fleet.MacOSPlatform))
|
||||
}
|
||||
vppAppTeams = append(vppAppTeams, fleet.VPPAppTeam{
|
||||
|
|
@ -121,7 +121,7 @@ func (svc *Service) BatchAssociateVPPApps(ctx context.Context, teamName string,
|
|||
|
||||
assets, err := vpp.GetAssets(token, nil)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "unable to retrieve assets")
|
||||
return nil, ctxerr.Wrap(ctx, err, "unable to retrieve assets")
|
||||
}
|
||||
|
||||
assetMap := map[string]struct{}{}
|
||||
|
|
@ -137,22 +137,22 @@ func (svc *Service) BatchAssociateVPPApps(ctx context.Context, teamName string,
|
|||
|
||||
if len(missingAssets) != 0 {
|
||||
reqErr := ctxerr.Errorf(ctx, "requested app not available on vpp account: %s", strings.Join(missingAssets, ","))
|
||||
return fleet.NewUserMessageError(reqErr, http.StatusUnprocessableEntity)
|
||||
return nil, fleet.NewUserMessageError(reqErr, http.StatusUnprocessableEntity)
|
||||
}
|
||||
}
|
||||
|
||||
if len(vppAppTeams) > 0 {
|
||||
apps, err := getVPPAppsMetadata(ctx, vppAppTeams)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "refreshing VPP app metadata")
|
||||
return nil, ctxerr.Wrap(ctx, err, "refreshing VPP app metadata")
|
||||
}
|
||||
if len(apps) == 0 {
|
||||
return fleet.NewInvalidArgumentError("app_store_apps",
|
||||
return nil, fleet.NewInvalidArgumentError("app_store_apps",
|
||||
"no valid apps found matching the provided app store IDs and platforms")
|
||||
}
|
||||
|
||||
if err := svc.ds.BatchInsertVPPApps(ctx, apps); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "inserting vpp app metadata")
|
||||
return nil, ctxerr.Wrap(ctx, err, "inserting vpp app metadata")
|
||||
}
|
||||
// Filter out the apps with invalid platforms
|
||||
if len(apps) != len(vppAppTeams) {
|
||||
|
|
@ -165,12 +165,16 @@ func (svc *Service) BatchAssociateVPPApps(ctx context.Context, teamName string,
|
|||
}
|
||||
if err := svc.ds.SetTeamVPPApps(ctx, teamID, vppAppTeams); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return fleet.NewUserMessageError(ctxerr.Wrap(ctx, err, "no vpp token to set team vpp assets"), http.StatusUnprocessableEntity)
|
||||
return nil, fleet.NewUserMessageError(ctxerr.Wrap(ctx, err, "no vpp token to set team vpp assets"), http.StatusUnprocessableEntity)
|
||||
}
|
||||
return ctxerr.Wrap(ctx, err, "set team vpp assets")
|
||||
return nil, ctxerr.Wrap(ctx, err, "set team vpp assets")
|
||||
}
|
||||
|
||||
return nil
|
||||
if len(vppAppTeams) == 0 {
|
||||
return []fleet.VPPAppResponse{}, nil
|
||||
}
|
||||
|
||||
return svc.ds.GetVPPApps(ctx, teamID)
|
||||
}
|
||||
|
||||
func (svc *Service) GetAppStoreApps(ctx context.Context, teamID *uint) ([]*fleet.VPPApp, error) {
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ type PolicyRunScript struct {
|
|||
|
||||
type PolicyInstallSoftware struct {
|
||||
PackagePath string `json:"package_path"`
|
||||
AppStoreID string `json:"app_store_id"`
|
||||
}
|
||||
|
||||
type Query struct {
|
||||
|
|
@ -564,7 +565,7 @@ func parsePolicies(top map[string]json.RawMessage, result *GitOps, baseDir strin
|
|||
for _, item := range policies {
|
||||
item := item
|
||||
if item.Path == nil {
|
||||
if err := parsePolicyInstallSoftware(baseDir, result.TeamName, &item, result.Software.Packages); err != nil {
|
||||
if err := parsePolicyInstallSoftware(baseDir, result.TeamName, &item, result.Software.Packages, result.Software.AppStoreApps); err != nil {
|
||||
multiError = multierror.Append(multiError, fmt.Errorf("failed to parse policy install_software %q: %v", item.Name, err))
|
||||
continue
|
||||
}
|
||||
|
|
@ -600,7 +601,7 @@ func parsePolicies(top map[string]json.RawMessage, result *GitOps, baseDir strin
|
|||
multiError, fmt.Errorf("nested paths are not supported: %s in %s", *pp.Path, *item.Path),
|
||||
)
|
||||
} else {
|
||||
if err := parsePolicyInstallSoftware(filepath.Dir(filePath), result.TeamName, pp, result.Software.Packages); err != nil {
|
||||
if err := parsePolicyInstallSoftware(filepath.Dir(filePath), result.TeamName, pp, result.Software.Packages, result.Software.AppStoreApps); err != nil {
|
||||
multiError = multierror.Append(multiError, fmt.Errorf("failed to parse policy install_software %q: %v", pp.Name, err))
|
||||
continue
|
||||
}
|
||||
|
|
@ -684,36 +685,56 @@ func parsePolicyRunScript(baseDir string, teamName *string, policy *Policy, scri
|
|||
return nil
|
||||
}
|
||||
|
||||
func parsePolicyInstallSoftware(baseDir string, teamName *string, policy *Policy, packages []*fleet.SoftwarePackageSpec) error {
|
||||
func parsePolicyInstallSoftware(baseDir string, teamName *string, policy *Policy, packages []*fleet.SoftwarePackageSpec, appStoreApps []*fleet.TeamSpecAppStoreApp) error {
|
||||
if policy.InstallSoftware == nil {
|
||||
policy.SoftwareTitleID = ptr.Uint(0) // unset the installer
|
||||
return nil
|
||||
}
|
||||
if policy.InstallSoftware != nil && policy.InstallSoftware.PackagePath != "" && teamName == nil {
|
||||
if policy.InstallSoftware != nil && (policy.InstallSoftware.PackagePath != "" || policy.InstallSoftware.AppStoreID != "") && teamName == nil {
|
||||
return errors.New("install_software can only be set on team policies")
|
||||
}
|
||||
if policy.InstallSoftware.PackagePath == "" {
|
||||
return errors.New("empty package_path")
|
||||
if policy.InstallSoftware.PackagePath == "" && policy.InstallSoftware.AppStoreID == "" {
|
||||
return errors.New("install_software must include either a package path or app store app ID")
|
||||
}
|
||||
fileBytes, err := os.ReadFile(resolveApplyRelativePath(baseDir, policy.InstallSoftware.PackagePath))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read install_software.package_path file %q: %v", policy.InstallSoftware.PackagePath, err)
|
||||
if policy.InstallSoftware.PackagePath != "" && policy.InstallSoftware.AppStoreID != "" {
|
||||
return errors.New("install_software must have only one of package_path or app_store_id")
|
||||
}
|
||||
var policyInstallSoftwareSpec fleet.SoftwarePackageSpec
|
||||
if err := yaml.Unmarshal(fileBytes, &policyInstallSoftwareSpec); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal install_software.package_path file %s: %v", policy.InstallSoftware.PackagePath, err)
|
||||
|
||||
if policy.InstallSoftware.PackagePath != "" {
|
||||
fileBytes, err := os.ReadFile(resolveApplyRelativePath(baseDir, policy.InstallSoftware.PackagePath))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read install_software.package_path file %q: %v", policy.InstallSoftware.PackagePath, err)
|
||||
}
|
||||
var policyInstallSoftwareSpec fleet.SoftwarePackageSpec
|
||||
if err := yaml.Unmarshal(fileBytes, &policyInstallSoftwareSpec); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal install_software.package_path file %s: %v", policy.InstallSoftware.PackagePath, err)
|
||||
}
|
||||
installerOnTeamFound := false
|
||||
for _, pkg := range packages {
|
||||
if pkg.URL == policyInstallSoftwareSpec.URL {
|
||||
installerOnTeamFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !installerOnTeamFound {
|
||||
return fmt.Errorf("install_software.package_path URL %s not found on team: %s", policyInstallSoftwareSpec.URL, policy.InstallSoftware.PackagePath)
|
||||
}
|
||||
policy.InstallSoftwareURL = policyInstallSoftwareSpec.URL
|
||||
}
|
||||
installerOnTeamFound := false
|
||||
for _, pkg := range packages {
|
||||
if pkg.URL == policyInstallSoftwareSpec.URL {
|
||||
installerOnTeamFound = true
|
||||
break
|
||||
|
||||
if policy.InstallSoftware.AppStoreID != "" {
|
||||
appOnTeamFound := false
|
||||
for _, app := range appStoreApps {
|
||||
if app.AppStoreID == policy.InstallSoftware.AppStoreID {
|
||||
appOnTeamFound = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !appOnTeamFound {
|
||||
return fmt.Errorf("install_software.app_store_id %s not found on team %s", policy.InstallSoftware.AppStoreID, *teamName)
|
||||
}
|
||||
}
|
||||
if !installerOnTeamFound {
|
||||
return fmt.Errorf("install_software.package_path URL %s not found on team: %s", policyInstallSoftwareSpec.URL, policy.InstallSoftware.PackagePath)
|
||||
}
|
||||
policy.InstallSoftwareURL = policyInstallSoftwareSpec.URL
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -264,12 +264,16 @@ func TestValidGitOpsYaml(t *testing.T) {
|
|||
require.False(t, gitops.Software.Packages[0].SelfService)
|
||||
require.Equal(t, "https://ftp.mozilla.org/pub/firefox/releases/129.0.2/mac/en-US/Firefox%20129.0.2.pkg", gitops.Software.Packages[1].URL)
|
||||
require.True(t, gitops.Software.Packages[1].SelfService)
|
||||
|
||||
require.Len(t, gitops.Software.AppStoreApps, 1)
|
||||
require.Equal(t, gitops.Software.AppStoreApps[0].AppStoreID, "123456")
|
||||
require.False(t, gitops.Software.AppStoreApps[0].SelfService)
|
||||
}
|
||||
|
||||
// Check policies
|
||||
expectedPoliciesCount := 5
|
||||
if test.isTeam {
|
||||
expectedPoliciesCount = 8
|
||||
expectedPoliciesCount = 9
|
||||
}
|
||||
require.Len(t, gitops.Policies, expectedPoliciesCount)
|
||||
assert.Equal(t, "😊 Failing policy", gitops.Policies[0].Name)
|
||||
|
|
@ -283,14 +287,18 @@ func TestValidGitOpsYaml(t *testing.T) {
|
|||
assert.NotNil(t, gitops.Policies[5].InstallSoftware)
|
||||
assert.Equal(t, "./microsoft-teams.pkg.software.yml", gitops.Policies[5].InstallSoftware.PackagePath)
|
||||
|
||||
assert.Equal(t, "Script run policy", gitops.Policies[6].Name)
|
||||
assert.NotNil(t, gitops.Policies[6].RunScript)
|
||||
assert.Equal(t, "./lib/collect-fleetd-logs.sh", gitops.Policies[6].RunScript.Path)
|
||||
assert.Equal(t, "Slack on macOS is installed", gitops.Policies[6].Name)
|
||||
assert.NotNil(t, gitops.Policies[6].InstallSoftware)
|
||||
assert.Equal(t, "123456", gitops.Policies[6].InstallSoftware.AppStoreID)
|
||||
|
||||
assert.Equal(t, "🔥 Failing policy with script", gitops.Policies[7].Name)
|
||||
assert.Equal(t, "Script run policy", gitops.Policies[7].Name)
|
||||
assert.NotNil(t, gitops.Policies[7].RunScript)
|
||||
assert.Equal(t, "./lib/collect-fleetd-logs.sh", gitops.Policies[7].RunScript.Path)
|
||||
|
||||
assert.Equal(t, "🔥 Failing policy with script", gitops.Policies[8].Name)
|
||||
assert.NotNil(t, gitops.Policies[8].RunScript)
|
||||
// . or .. depending on whether with paths or without
|
||||
assert.Contains(t, gitops.Policies[7].RunScript.Path, "./lib/collect-fleetd-logs.sh")
|
||||
assert.Contains(t, gitops.Policies[8].RunScript.Path, "./lib/collect-fleetd-logs.sh")
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
@ -911,7 +919,19 @@ policies:
|
|||
package_path:
|
||||
`
|
||||
_, err = gitOpsFromString(t, config)
|
||||
assert.ErrorContains(t, err, "empty package_path")
|
||||
assert.ErrorContains(t, err, "must include either a package path or app store app ID")
|
||||
|
||||
config = getTeamConfig([]string{"policies"})
|
||||
config += `
|
||||
policies:
|
||||
- name: Some policy
|
||||
query: SELECT 1;
|
||||
install_software:
|
||||
package_path: ./some_path.yml
|
||||
app_store_id: "123456"
|
||||
`
|
||||
_, err = gitOpsFromString(t, config)
|
||||
assert.ErrorContains(t, err, "must have only one of package_path or app_store_id")
|
||||
|
||||
// Software has a URL that's too big
|
||||
tooBigURL := fmt.Sprintf("https://ftp.mozilla.org/%s", strings.Repeat("a", 4000-23))
|
||||
|
|
@ -929,6 +949,18 @@ software:
|
|||
_, err = GitOpsFromFile(path, basePath, &appConfig, nopLogf)
|
||||
assert.ErrorContains(t, err, fmt.Sprintf("software URL \"%s\" is too long, must be 4000 characters or less", tooBigURL))
|
||||
|
||||
// Policy references a VPP app not present on the team
|
||||
config = getTeamConfig([]string{"policies"})
|
||||
config += `
|
||||
policies:
|
||||
- name: Some policy
|
||||
query: SELECT 1;
|
||||
install_software:
|
||||
app_store_id: "123456"
|
||||
`
|
||||
_, err = gitOpsFromString(t, config)
|
||||
assert.ErrorContains(t, err, "not found on team")
|
||||
|
||||
// Policy references a software installer not present in the team.
|
||||
config = getTeamConfig([]string{"policies"})
|
||||
config += `
|
||||
|
|
|
|||
7
pkg/spec/testdata/team_config.yml
vendored
7
pkg/spec/testdata/team_config.yml
vendored
|
|
@ -26,6 +26,11 @@ policies:
|
|||
resolution: There is no resolution for this policy.
|
||||
query: SELECT 1 FROM osquery_info WHERE start_time < 0;
|
||||
- path: ./team_install_software.policies.yml
|
||||
- name: Slack on macOS is installed
|
||||
platform: darwin
|
||||
query: SELECT 1 FROM apps WHERE name = 'Slack.app';
|
||||
install_software:
|
||||
app_store_id: "123456"
|
||||
- name: Script run policy
|
||||
platform: linux
|
||||
description: This should run a script on failure
|
||||
|
|
@ -34,6 +39,8 @@ policies:
|
|||
path: ./lib/collect-fleetd-logs.sh
|
||||
- path: ./policies/script-policy.yml
|
||||
software:
|
||||
app_store_apps:
|
||||
- app_store_id: "123456"
|
||||
packages:
|
||||
- path: ./microsoft-teams.pkg.software.yml
|
||||
- url: https://ftp.mozilla.org/pub/firefox/releases/129.0.2/mac/en-US/Firefox%20129.0.2.pkg
|
||||
|
|
|
|||
7
pkg/spec/testdata/team_config_no_paths.yml
vendored
7
pkg/spec/testdata/team_config_no_paths.yml
vendored
|
|
@ -121,6 +121,11 @@ policies:
|
|||
query: SELECT 1 FROM apps WHERE name = 'Microsoft Teams.app' AND version_compare(bundle_short_version, '24193.1707.3028.4282') >= 0;
|
||||
install_software:
|
||||
package_path: ./microsoft-teams.pkg.software.yml
|
||||
- name: Slack on macOS is installed
|
||||
platform: darwin
|
||||
query: SELECT 1 FROM apps WHERE name = 'Slack.app';
|
||||
install_software:
|
||||
app_store_id: "123456"
|
||||
- name: Script run policy
|
||||
platform: linux
|
||||
description: This should run a script on failure
|
||||
|
|
@ -135,6 +140,8 @@ policies:
|
|||
run_script:
|
||||
path: ./lib/collect-fleetd-logs.sh
|
||||
software:
|
||||
app_store_apps:
|
||||
- app_store_id: "123456"
|
||||
packages:
|
||||
- path: ./microsoft-teams.pkg.software.yml
|
||||
- url: https://ftp.mozilla.org/pub/firefox/releases/129.0.2/mac/en-US/Firefox%20129.0.2.pkg
|
||||
|
|
|
|||
|
|
@ -825,6 +825,8 @@ func (ds *Datastore) ApplyPolicySpecs(ctx context.Context, authorID uint, specs
|
|||
teamNameToID := make(map[string]*uint, 1)
|
||||
teamIDToPolicies := make(map[*uint][]*fleet.PolicySpec, 1)
|
||||
softwareInstallerIDs := make(map[*uint]map[uint]*uint) // teamID -> titleID -> softwareInstallerID
|
||||
vppAppsTeamsIDs := make(map[*uint]map[uint]*uint) // teamID -> titleID -> vppAppsTeamsID
|
||||
vppTitleIDs := make(map[uint]struct{}) // set when a title is a VPP app rather than a software installer
|
||||
|
||||
// Get the team IDs
|
||||
for _, spec := range specs {
|
||||
|
|
@ -853,7 +855,7 @@ func (ds *Datastore) ApplyPolicySpecs(ctx context.Context, authorID uint, specs
|
|||
teamIDToPolicies[teamID] = append(teamIDToPolicies[teamID], spec)
|
||||
}
|
||||
|
||||
// Get software installer ids from software title ids.
|
||||
// Get software installer ids + VPP apps teams IDs from software title IDs.
|
||||
for _, spec := range specs {
|
||||
if spec.SoftwareTitleID == nil || *spec.SoftwareTitleID == 0 {
|
||||
continue
|
||||
|
|
@ -861,20 +863,36 @@ func (ds *Datastore) ApplyPolicySpecs(ctx context.Context, authorID uint, specs
|
|||
if spec.Team == "" {
|
||||
return ctxerr.Wrap(ctx, errSoftwareTitleIDOnGlobalPolicy, "create policy from spec")
|
||||
}
|
||||
var softwareInstallerID uint
|
||||
err := sqlx.GetContext(ctx, queryerContext, &softwareInstallerID,
|
||||
`SELECT id FROM software_installers WHERE global_or_team_id = ? AND title_id = ?`,
|
||||
teamNameToID[spec.Team], spec.SoftwareTitleID)
|
||||
var ids struct {
|
||||
SoftwareInstallerID *uint `db:"si_id"`
|
||||
VPPAppsTeamsID *uint `db:"vat_id"`
|
||||
}
|
||||
err := sqlx.GetContext(ctx, queryerContext, &ids,
|
||||
`SELECT id si_id, NULL vat_id FROM software_installers WHERE global_or_team_id = ? AND title_id = ?
|
||||
UNION
|
||||
SELECT NULL si_id, vat.id vat_id FROM vpp_apps_teams vat
|
||||
JOIN vpp_apps va ON va.adam_id = vat.adam_id AND va.platform = vat.platform
|
||||
WHERE global_or_team_id = ? AND title_id = ?`,
|
||||
teamNameToID[spec.Team], spec.SoftwareTitleID, teamNameToID[spec.Team], spec.SoftwareTitleID)
|
||||
if err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return ctxerr.Wrap(ctx, notFound("SoftwareInstaller").WithID(*spec.SoftwareTitleID), "get software installer id")
|
||||
}
|
||||
return ctxerr.Wrap(ctx, err, "get software installer id")
|
||||
}
|
||||
if len(softwareInstallerIDs[teamNameToID[spec.Team]]) == 0 {
|
||||
softwareInstallerIDs[teamNameToID[spec.Team]] = make(map[uint]*uint)
|
||||
if ids.SoftwareInstallerID != nil {
|
||||
if len(softwareInstallerIDs[teamNameToID[spec.Team]]) == 0 {
|
||||
softwareInstallerIDs[teamNameToID[spec.Team]] = make(map[uint]*uint)
|
||||
}
|
||||
softwareInstallerIDs[teamNameToID[spec.Team]][*spec.SoftwareTitleID] = ids.SoftwareInstallerID
|
||||
}
|
||||
if ids.VPPAppsTeamsID != nil {
|
||||
if len(vppAppsTeamsIDs[teamNameToID[spec.Team]]) == 0 {
|
||||
vppAppsTeamsIDs[teamNameToID[spec.Team]] = make(map[uint]*uint)
|
||||
}
|
||||
vppAppsTeamsIDs[teamNameToID[spec.Team]][*spec.SoftwareTitleID] = ids.VPPAppsTeamsID
|
||||
vppTitleIDs[*spec.SoftwareTitleID] = struct{}{}
|
||||
}
|
||||
softwareInstallerIDs[teamNameToID[spec.Team]][*spec.SoftwareTitleID] = &softwareInstallerID
|
||||
}
|
||||
|
||||
// Get the query and platforms of the current policies so that we can check if query or platform changed later, if needed
|
||||
|
|
@ -883,6 +901,7 @@ func (ds *Datastore) ApplyPolicySpecs(ctx context.Context, authorID uint, specs
|
|||
Query string `db:"query"`
|
||||
Platforms string `db:"platforms"`
|
||||
SoftwareInstallerID *uint `db:"software_installer_id"`
|
||||
VPPAppsTeamsID *uint `db:"vpp_apps_teams_id"`
|
||||
ScriptID *uint `db:"script_id"`
|
||||
}
|
||||
teamIDToPoliciesByName := make(map[*uint]map[string]policyLite, len(teamIDToPolicies))
|
||||
|
|
@ -897,10 +916,10 @@ func (ds *Datastore) ApplyPolicySpecs(ctx context.Context, authorID uint, specs
|
|||
var args []interface{}
|
||||
var err error
|
||||
if teamID == nil {
|
||||
query, args, err = sqlx.In("SELECT name, query, platforms, software_installer_id, script_id FROM policies WHERE team_id IS NULL AND name IN (?)", policyNames)
|
||||
query, args, err = sqlx.In("SELECT name, query, platforms, software_installer_id, vpp_apps_teams_id, script_id FROM policies WHERE team_id IS NULL AND name IN (?)", policyNames)
|
||||
} else {
|
||||
query, args, err = sqlx.In(
|
||||
"SELECT name, query, platforms, software_installer_id, script_id FROM policies WHERE team_id = ? AND name IN (?)", *teamID, policyNames,
|
||||
"SELECT name, query, platforms, software_installer_id, vpp_apps_teams_id, script_id FROM policies WHERE team_id = ? AND name IN (?)", *teamID, policyNames,
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
|
|
@ -930,9 +949,10 @@ func (ds *Datastore) ApplyPolicySpecs(ctx context.Context, authorID uint, specs
|
|||
critical,
|
||||
calendar_events_enabled,
|
||||
software_installer_id,
|
||||
vpp_apps_teams_id,
|
||||
script_id,
|
||||
checksum
|
||||
) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, %s)
|
||||
) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, %s)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
query = VALUES(query),
|
||||
description = VALUES(description),
|
||||
|
|
@ -942,15 +962,22 @@ func (ds *Datastore) ApplyPolicySpecs(ctx context.Context, authorID uint, specs
|
|||
critical = VALUES(critical),
|
||||
calendar_events_enabled = VALUES(calendar_events_enabled),
|
||||
software_installer_id = VALUES(software_installer_id),
|
||||
vpp_apps_teams_id = VALUES(vpp_apps_teams_id),
|
||||
script_id = VALUES(script_id)
|
||||
`, policiesChecksumComputedColumn(),
|
||||
)
|
||||
for teamID, teamPolicySpecs := range teamIDToPolicies {
|
||||
for _, spec := range teamPolicySpecs {
|
||||
var softwareInstallerID *uint
|
||||
var vppAppsTeamsID *uint
|
||||
if spec.SoftwareTitleID != nil {
|
||||
softwareInstallerID = softwareInstallerIDs[teamNameToID[spec.Team]][*spec.SoftwareTitleID]
|
||||
if _, ok := vppTitleIDs[*spec.SoftwareTitleID]; !ok {
|
||||
softwareInstallerID = softwareInstallerIDs[teamNameToID[spec.Team]][*spec.SoftwareTitleID]
|
||||
} else {
|
||||
vppAppsTeamsID = vppAppsTeamsIDs[teamNameToID[spec.Team]][*spec.SoftwareTitleID]
|
||||
}
|
||||
}
|
||||
|
||||
scriptID := spec.ScriptID
|
||||
if spec.ScriptID != nil && *spec.ScriptID == 0 {
|
||||
scriptID = nil
|
||||
|
|
@ -958,8 +985,9 @@ func (ds *Datastore) ApplyPolicySpecs(ctx context.Context, authorID uint, specs
|
|||
|
||||
res, err := tx.ExecContext(
|
||||
ctx,
|
||||
query, spec.Name, spec.Query, spec.Description, authorID, spec.Resolution, teamID, spec.Platform, spec.Critical,
|
||||
spec.CalendarEventsEnabled, softwareInstallerID, scriptID,
|
||||
query,
|
||||
spec.Name, spec.Query, spec.Description, authorID, spec.Resolution, teamID, spec.Platform, spec.Critical,
|
||||
spec.CalendarEventsEnabled, softwareInstallerID, vppAppsTeamsID, scriptID,
|
||||
)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "exec ApplyPolicySpecs insert")
|
||||
|
|
@ -973,7 +1001,7 @@ func (ds *Datastore) ApplyPolicySpecs(ctx context.Context, authorID uint, specs
|
|||
shouldRemoveAllPolicyMemberships bool
|
||||
removePolicyStats bool
|
||||
)
|
||||
// Figure out if the query, platform or software installer changed.
|
||||
// Figure out if the query, platform, software installer, or VPP app changed.
|
||||
var softwareInstallerID *uint
|
||||
if spec.SoftwareTitleID != nil {
|
||||
softwareInstallerID = softwareInstallerIDs[teamID][*spec.SoftwareTitleID]
|
||||
|
|
@ -988,6 +1016,11 @@ func (ds *Datastore) ApplyPolicySpecs(ctx context.Context, authorID uint, specs
|
|||
(prev.SoftwareInstallerID != nil && softwareInstallerID != nil && *prev.SoftwareInstallerID != *softwareInstallerID)):
|
||||
shouldRemoveAllPolicyMemberships = true
|
||||
removePolicyStats = true
|
||||
case teamID != nil &&
|
||||
((prev.VPPAppsTeamsID == nil && spec.SoftwareTitleID != nil) ||
|
||||
(prev.VPPAppsTeamsID != nil && vppAppsTeamsID != nil && *prev.VPPAppsTeamsID != *vppAppsTeamsID)):
|
||||
shouldRemoveAllPolicyMemberships = true
|
||||
removePolicyStats = true
|
||||
case teamID != nil &&
|
||||
((prev.ScriptID == nil && spec.ScriptID != nil) ||
|
||||
(prev.ScriptID != nil && spec.ScriptID != nil && *prev.ScriptID != *spec.ScriptID)):
|
||||
|
|
|
|||
|
|
@ -4654,6 +4654,25 @@ func testApplyPolicySpecWithInstallers(t *testing.T, ds *Datastore) {
|
|||
require.NoError(t, err)
|
||||
require.NotNil(t, installer5.TitleID)
|
||||
|
||||
test.CreateInsertGlobalVPPToken(t, ds)
|
||||
|
||||
// create VPP apps
|
||||
va1, err := ds.InsertVPPAppWithTeam(ctx, &fleet.VPPApp{
|
||||
Name: "vpp1", BundleIdentifier: "com.app.vpp1",
|
||||
VPPAppTeam: fleet.VPPAppTeam{VPPAppID: fleet.VPPAppID{AdamID: "adam_vpp_app_1", Platform: fleet.MacOSPlatform}},
|
||||
}, &team1.ID)
|
||||
require.NoError(t, err)
|
||||
va2, err := ds.InsertVPPAppWithTeam(ctx, &fleet.VPPApp{
|
||||
Name: "vpp2", BundleIdentifier: "com.app.vpp2",
|
||||
VPPAppTeam: fleet.VPPAppTeam{VPPAppID: fleet.VPPAppID{AdamID: "adam_vpp_app_2", Platform: fleet.MacOSPlatform}},
|
||||
}, &team2.ID)
|
||||
require.NoError(t, err)
|
||||
va1NoTeam, err := ds.InsertVPPAppWithTeam(ctx, &fleet.VPPApp{
|
||||
Name: "vpp1", BundleIdentifier: "com.app.vpp1",
|
||||
VPPAppTeam: fleet.VPPAppTeam{VPPAppID: fleet.VPPAppID{AdamID: "adam_vpp_app_1", Platform: fleet.MacOSPlatform}},
|
||||
}, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Installers cannot be assigned to global policies.
|
||||
err = ds.ApplyPolicySpecs(ctx, user1.ID, []*fleet.PolicySpec{
|
||||
{
|
||||
|
|
@ -4698,34 +4717,78 @@ func testApplyPolicySpecWithInstallers(t *testing.T, ds *Datastore) {
|
|||
Platform: "linux",
|
||||
SoftwareTitleID: installer3.TitleID,
|
||||
},
|
||||
{
|
||||
Name: "VPP Team policy 1",
|
||||
Query: "SELECT 1;",
|
||||
Description: "Description 1",
|
||||
Resolution: "Resolution 1",
|
||||
Team: "team1",
|
||||
Platform: "darwin",
|
||||
SoftwareTitleID: &va1.TitleID,
|
||||
},
|
||||
{
|
||||
Name: "VPP Team policy 2",
|
||||
Query: "SELECT 2;",
|
||||
Description: "Description 2",
|
||||
Resolution: "Resolution 2",
|
||||
Team: "team2",
|
||||
Platform: "linux",
|
||||
SoftwareTitleID: &va2.TitleID,
|
||||
},
|
||||
{
|
||||
Name: "VPP No team policy 3",
|
||||
Query: "SELECT 3;",
|
||||
Description: "Description 3",
|
||||
Resolution: "Resolution 3",
|
||||
Team: "No team",
|
||||
Platform: "linux",
|
||||
SoftwareTitleID: &va1NoTeam.TitleID,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
team1Policies, _, err := ds.ListTeamPolicies(ctx, team1.ID, fleet.ListOptions{}, fleet.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, team1Policies, 1)
|
||||
require.Len(t, team1Policies, 2)
|
||||
|
||||
require.NotNil(t, team1Policies[0].SoftwareInstallerID)
|
||||
require.NotNil(t, team1Policies[1].VPPAppsTeamsID)
|
||||
policy1Team1 := team1Policies[0]
|
||||
require.Equal(t, installer1.InstallerID, *team1Policies[0].SoftwareInstallerID)
|
||||
vppPolicy1Team1 := team1Policies[1]
|
||||
va1Meta, err := ds.GetVPPAppMetadataByTeamAndTitleID(ctx, &team1.ID, va1.TitleID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, va1Meta.VPPAppsTeamsID, *vppPolicy1Team1.VPPAppsTeamsID)
|
||||
|
||||
team2Policies, _, err := ds.ListTeamPolicies(ctx, team2.ID, fleet.ListOptions{}, fleet.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, team2Policies, 1)
|
||||
require.Len(t, team2Policies, 2)
|
||||
require.NotNil(t, team2Policies[0].SoftwareInstallerID)
|
||||
require.Equal(t, installer2.InstallerID, *team2Policies[0].SoftwareInstallerID)
|
||||
vppPolicy2Team2 := team2Policies[1]
|
||||
va2Meta, err := ds.GetVPPAppMetadataByTeamAndTitleID(ctx, &team2.ID, va2.TitleID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, va2Meta.VPPAppsTeamsID, *vppPolicy2Team2.VPPAppsTeamsID)
|
||||
|
||||
noTeamPolicies, _, err := ds.ListTeamPolicies(ctx, fleet.PolicyNoTeamID, fleet.ListOptions{}, fleet.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, noTeamPolicies, 1)
|
||||
require.Len(t, noTeamPolicies, 2)
|
||||
require.NotNil(t, noTeamPolicies[0].SoftwareInstallerID)
|
||||
require.Equal(t, installer3.InstallerID, *noTeamPolicies[0].SoftwareInstallerID)
|
||||
vppNoTeamPolicy := noTeamPolicies[1]
|
||||
vNoTeamMeta, err := ds.GetVPPAppMetadataByTeamAndTitleID(ctx, ptr.Uint(0), va1NoTeam.TitleID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, vNoTeamMeta.VPPAppsTeamsID, *vppNoTeamPolicy.VPPAppsTeamsID)
|
||||
|
||||
// Record policy execution on policy1Team1.
|
||||
err = ds.RecordPolicyQueryExecutions(ctx, host1Team1, map[uint]*bool{
|
||||
policy1Team1.ID: ptr.Bool(false),
|
||||
policy1Team1.ID: ptr.Bool(false),
|
||||
vppPolicy1Team1.ID: ptr.Bool(false),
|
||||
}, time.Now(), false)
|
||||
require.NoError(t, err)
|
||||
err = ds.UpdateHostPolicyCounts(ctx)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Unset software installer from "Team policy 1".
|
||||
// Unset software installer from "Team policy 1" and the VPP policy.
|
||||
err = ds.ApplyPolicySpecs(ctx, user1.ID, []*fleet.PolicySpec{
|
||||
{
|
||||
Name: "Team policy 1",
|
||||
|
|
@ -4736,14 +4799,25 @@ func testApplyPolicySpecWithInstallers(t *testing.T, ds *Datastore) {
|
|||
Platform: "darwin",
|
||||
SoftwareTitleID: nil,
|
||||
},
|
||||
{
|
||||
Name: "VPP Team policy 1",
|
||||
Query: "SELECT 1;",
|
||||
Description: "Description 1",
|
||||
Resolution: "Resolution 1",
|
||||
Team: "team1",
|
||||
Platform: "darwin",
|
||||
SoftwareTitleID: nil,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
team1Policies, _, err = ds.ListTeamPolicies(ctx, team1.ID, fleet.ListOptions{}, fleet.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, team1Policies, 1)
|
||||
require.Len(t, team1Policies, 2)
|
||||
require.Nil(t, team1Policies[0].SoftwareInstallerID)
|
||||
require.Nil(t, team1Policies[1].VPPAppsTeamsID)
|
||||
// Should not clear results because we've cleared not changed/set-new installer.
|
||||
require.Equal(t, uint(1), team1Policies[0].FailingHostCount)
|
||||
require.Equal(t, uint(1), team1Policies[1].FailingHostCount)
|
||||
|
||||
// Set "Team policy 1" to a software installer on team2.
|
||||
err = ds.ApplyPolicySpecs(ctx, user1.ID, []*fleet.PolicySpec{
|
||||
|
|
@ -4761,6 +4835,21 @@ func testApplyPolicySpecWithInstallers(t *testing.T, ds *Datastore) {
|
|||
var notFoundErr *notFoundError
|
||||
require.ErrorAs(t, err, ¬FoundErr)
|
||||
|
||||
// Set "Team policy 1" to a VPP app on team2.
|
||||
err = ds.ApplyPolicySpecs(ctx, user1.ID, []*fleet.PolicySpec{
|
||||
{
|
||||
Name: "Team policy 1",
|
||||
Query: "SELECT 1;",
|
||||
Description: "Description 1",
|
||||
Resolution: "Resolution 1",
|
||||
Team: "team1",
|
||||
Platform: "darwin",
|
||||
SoftwareTitleID: &va2.TitleID,
|
||||
},
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.ErrorAs(t, err, ¬FoundErr)
|
||||
|
||||
// Set "No team policy 3" to a software installer on team2.
|
||||
err = ds.ApplyPolicySpecs(ctx, user1.ID, []*fleet.PolicySpec{
|
||||
{
|
||||
|
|
@ -4776,6 +4865,21 @@ func testApplyPolicySpecWithInstallers(t *testing.T, ds *Datastore) {
|
|||
require.Error(t, err)
|
||||
require.ErrorAs(t, err, ¬FoundErr)
|
||||
|
||||
// Set "No Team policy 3" to a VPP app on team2.
|
||||
err = ds.ApplyPolicySpecs(ctx, user1.ID, []*fleet.PolicySpec{
|
||||
{
|
||||
Name: "No team policy 3",
|
||||
Query: "SELECT 3;",
|
||||
Description: "Description 3",
|
||||
Resolution: "Resolution 3",
|
||||
Team: "No team",
|
||||
Platform: "darwin",
|
||||
SoftwareTitleID: &va2.TitleID,
|
||||
},
|
||||
})
|
||||
require.Error(t, err)
|
||||
require.ErrorAs(t, err, ¬FoundErr)
|
||||
|
||||
// Set "Team policy 1" to a software title that doesn't exist.
|
||||
err = ds.ApplyPolicySpecs(ctx, user1.ID, []*fleet.PolicySpec{
|
||||
{
|
||||
|
|
@ -4821,10 +4925,11 @@ func testApplyPolicySpecWithInstallers(t *testing.T, ds *Datastore) {
|
|||
require.NoError(t, err)
|
||||
team2Policies, _, err = ds.ListTeamPolicies(ctx, team2.ID, fleet.ListOptions{}, fleet.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, team2Policies, 1)
|
||||
require.Len(t, team2Policies, 2)
|
||||
require.Nil(t, team2Policies[0].SoftwareInstallerID)
|
||||
require.Equal(t, va2Meta.VPPAppsTeamsID, *team2Policies[1].VPPAppsTeamsID) // stays set since Apply doesn't delete
|
||||
|
||||
// Apply team policies associated to two installers (again, with two installers with the same title).
|
||||
// Apply team policies associated to two installers (again, with two installers with the same title), and same with VPP apps
|
||||
tfr4, err := fleet.NewTempFileReader(strings.NewReader("hello3"), t.TempDir)
|
||||
require.NoError(t, err)
|
||||
installer4ID, _, err := ds.MatchOrCreateSoftwareInstaller(ctx, &fleet.UploadSoftwareInstallerPayload{
|
||||
|
|
@ -4845,6 +4950,12 @@ func testApplyPolicySpecWithInstallers(t *testing.T, ds *Datastore) {
|
|||
installer4, err := ds.GetSoftwareInstallerMetadataByID(ctx, installer4ID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, installer2.TitleID)
|
||||
va4Team2, err := ds.InsertVPPAppWithTeam(ctx, &fleet.VPPApp{
|
||||
Name: "vpp4", BundleIdentifier: "com.app.vpp4",
|
||||
VPPAppTeam: fleet.VPPAppTeam{VPPAppID: fleet.VPPAppID{AdamID: "adam_vpp_app_4", Platform: fleet.MacOSPlatform}},
|
||||
}, &team2.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = ds.ApplyPolicySpecs(ctx, user1.ID, []*fleet.PolicySpec{
|
||||
{
|
||||
Name: "Team policy 1",
|
||||
|
|
@ -4864,13 +4975,35 @@ func testApplyPolicySpecWithInstallers(t *testing.T, ds *Datastore) {
|
|||
Platform: "linux",
|
||||
SoftwareTitleID: installer4.TitleID,
|
||||
},
|
||||
{
|
||||
Name: "VPP Team policy 1",
|
||||
Query: "SELECT 1;",
|
||||
Description: "Description 1",
|
||||
Resolution: "Resolution 1",
|
||||
Team: "team1",
|
||||
Platform: "darwin",
|
||||
SoftwareTitleID: &va1.TitleID,
|
||||
},
|
||||
{
|
||||
Name: "VPP Team policy 2",
|
||||
Query: "SELECT 2;",
|
||||
Description: "Description 2",
|
||||
Resolution: "Resolution 2",
|
||||
Team: "team2",
|
||||
Platform: "linux",
|
||||
SoftwareTitleID: &va4Team2.TitleID,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
team1Policies, _, err = ds.ListTeamPolicies(ctx, team1.ID, fleet.ListOptions{}, fleet.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, team1Policies, 1)
|
||||
require.Len(t, team1Policies, 2)
|
||||
require.NotNil(t, team1Policies[0].SoftwareInstallerID)
|
||||
require.Equal(t, installer1.InstallerID, *team1Policies[0].SoftwareInstallerID)
|
||||
require.NotNil(t, team1Policies[1].VPPAppsTeamsID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, va1Meta.VPPAppsTeamsID, *team1Policies[1].VPPAppsTeamsID)
|
||||
|
||||
// Should clear results because we've are setting an installer.
|
||||
require.Equal(t, uint(0), team1Policies[0].FailingHostCount)
|
||||
countBiggerThanZero := true
|
||||
|
|
@ -4884,13 +5017,18 @@ func testApplyPolicySpecWithInstallers(t *testing.T, ds *Datastore) {
|
|||
require.False(t, countBiggerThanZero)
|
||||
team2Policies, _, err = ds.ListTeamPolicies(ctx, team2.ID, fleet.ListOptions{}, fleet.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, team2Policies, 1)
|
||||
require.Len(t, team2Policies, 2)
|
||||
require.NotNil(t, team2Policies[0].SoftwareInstallerID)
|
||||
require.Equal(t, installer4.InstallerID, *team2Policies[0].SoftwareInstallerID)
|
||||
require.NotNil(t, team2Policies[1].VPPAppsTeamsID)
|
||||
va4Team2Meta, err := ds.GetVPPAppMetadataByTeamAndTitleID(ctx, &team2.ID, va4Team2.TitleID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, va4Team2Meta.VPPAppsTeamsID, *team2Policies[1].VPPAppsTeamsID)
|
||||
|
||||
// Record policy execution on policy1Team1 to test that setting the same installer won't clear results.
|
||||
// Record policy execution on policy1Team1 + VPP equivalent to test that setting the same installer won't clear results.
|
||||
err = ds.RecordPolicyQueryExecutions(ctx, host1Team1, map[uint]*bool{
|
||||
policy1Team1.ID: ptr.Bool(false),
|
||||
policy1Team1.ID: ptr.Bool(false),
|
||||
vppPolicy1Team1.ID: ptr.Bool(false),
|
||||
}, time.Now(), false)
|
||||
require.NoError(t, err)
|
||||
err = ds.UpdateHostPolicyCounts(ctx)
|
||||
|
|
@ -4905,11 +5043,20 @@ func testApplyPolicySpecWithInstallers(t *testing.T, ds *Datastore) {
|
|||
Platform: "darwin",
|
||||
SoftwareTitleID: installer1.TitleID,
|
||||
},
|
||||
{
|
||||
Name: "VPP Team policy 1",
|
||||
Query: "SELECT 1;",
|
||||
Description: "Description 1",
|
||||
Resolution: "Resolution 1",
|
||||
Team: "team1",
|
||||
Platform: "darwin",
|
||||
SoftwareTitleID: &va1.TitleID,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
team1Policies, _, err = ds.ListTeamPolicies(ctx, team1.ID, fleet.ListOptions{}, fleet.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, team1Policies, 1)
|
||||
require.Len(t, team1Policies, 2)
|
||||
require.Equal(t, uint(1), team1Policies[0].FailingHostCount)
|
||||
countBiggerThanZero = false
|
||||
ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
|
||||
|
|
@ -4920,6 +5067,22 @@ func testApplyPolicySpecWithInstallers(t *testing.T, ds *Datastore) {
|
|||
)
|
||||
})
|
||||
require.True(t, countBiggerThanZero)
|
||||
require.Equal(t, uint(1), team1Policies[1].FailingHostCount)
|
||||
countBiggerThanZero = false
|
||||
ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
|
||||
return sqlx.GetContext(ctx, q,
|
||||
&countBiggerThanZero,
|
||||
`SELECT COUNT(*) > 0 FROM policy_membership WHERE policy_id = ?`,
|
||||
team1Policies[1].ID,
|
||||
)
|
||||
})
|
||||
require.True(t, countBiggerThanZero)
|
||||
|
||||
va4Team1, err := ds.InsertVPPAppWithTeam(ctx, &fleet.VPPApp{
|
||||
Name: "vpp4", BundleIdentifier: "com.app.vpp4",
|
||||
VPPAppTeam: fleet.VPPAppTeam{VPPAppID: fleet.VPPAppID{AdamID: "adam_vpp_app_4", Platform: fleet.MacOSPlatform}},
|
||||
}, &team1.ID)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Now change the installer, should clear results.
|
||||
err = ds.ApplyPolicySpecs(ctx, user1.ID, []*fleet.PolicySpec{
|
||||
|
|
@ -4932,11 +5095,20 @@ func testApplyPolicySpecWithInstallers(t *testing.T, ds *Datastore) {
|
|||
Platform: "darwin",
|
||||
SoftwareTitleID: installer5.TitleID,
|
||||
},
|
||||
{
|
||||
Name: "VPP Team policy 1",
|
||||
Query: "SELECT 1;",
|
||||
Description: "Description 1",
|
||||
Resolution: "Resolution 1",
|
||||
Team: "team1",
|
||||
Platform: "darwin",
|
||||
SoftwareTitleID: &va4Team1.TitleID,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
team1Policies, _, err = ds.ListTeamPolicies(ctx, team1.ID, fleet.ListOptions{}, fleet.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, team1Policies, 1)
|
||||
require.Len(t, team1Policies, 2)
|
||||
require.Equal(t, uint(0), team1Policies[0].FailingHostCount)
|
||||
countBiggerThanZero = true
|
||||
ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
|
||||
|
|
@ -4947,6 +5119,16 @@ func testApplyPolicySpecWithInstallers(t *testing.T, ds *Datastore) {
|
|||
)
|
||||
})
|
||||
require.False(t, countBiggerThanZero)
|
||||
require.Equal(t, uint(0), team1Policies[1].FailingHostCount)
|
||||
countBiggerThanZero = true
|
||||
ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
|
||||
return sqlx.GetContext(ctx, q,
|
||||
&countBiggerThanZero,
|
||||
`SELECT COUNT(*) > 0 FROM policy_membership WHERE policy_id = ?`,
|
||||
team1Policies[1].ID,
|
||||
)
|
||||
})
|
||||
require.False(t, countBiggerThanZero)
|
||||
}
|
||||
|
||||
func testTeamPoliciesNoTeam(t *testing.T, ds *Datastore) {
|
||||
|
|
|
|||
|
|
@ -270,6 +270,25 @@ func (ds *Datastore) InsertVPPAppWithTeam(ctx context.Context, app *fleet.VPPApp
|
|||
return app, nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) GetVPPApps(ctx context.Context, teamID *uint) ([]fleet.VPPAppResponse, error) {
|
||||
var tmID uint
|
||||
if teamID != nil {
|
||||
tmID = *teamID
|
||||
}
|
||||
var results []fleet.VPPAppResponse
|
||||
|
||||
// intentionally using writer as this is called right after batch-setting VPP apps
|
||||
if err := sqlx.SelectContext(ctx, ds.writer(ctx), &results, `
|
||||
SELECT vat.team_id, va.title_id, vat.adam_id app_store_id, vat.platform
|
||||
FROM vpp_apps_teams vat
|
||||
JOIN vpp_apps va ON va.adam_id = vat.adam_id AND va.platform = vat.platform
|
||||
WHERE global_or_team_id = ?`, tmID); err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "get VPP apps")
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) GetAssignedVPPApps(ctx context.Context, teamID *uint) (map[fleet.VPPAppID]fleet.VPPAppTeam, error) {
|
||||
stmt := `
|
||||
SELECT
|
||||
|
|
@ -358,17 +377,14 @@ ON DUPLICATE KEY UPDATE
|
|||
}
|
||||
|
||||
func removeVPPAppTeams(ctx context.Context, tx sqlx.ExtContext, appID fleet.VPPAppID, teamID *uint) error {
|
||||
stmt := `
|
||||
DELETE FROM
|
||||
vpp_apps_teams
|
||||
WHERE
|
||||
adam_id = ?
|
||||
AND
|
||||
team_id = ?
|
||||
AND
|
||||
platform = ?
|
||||
`
|
||||
_, err := tx.ExecContext(ctx, stmt, appID.AdamID, teamID, appID.Platform)
|
||||
_, err := tx.ExecContext(ctx, `UPDATE policies p
|
||||
JOIN vpp_apps_teams vat ON vat.id = p.vpp_apps_teams_id AND vat.adam_id = ? AND vat.team_id = ? AND vat.platform = ?
|
||||
SET vpp_apps_teams_id = NULL`, appID.AdamID, teamID, appID.Platform)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "unsetting vpp app policy associations from team")
|
||||
}
|
||||
|
||||
_, err = tx.ExecContext(ctx, `DELETE FROM vpp_apps_teams WHERE adam_id = ? AND team_id = ? AND platform = ?`, appID.AdamID, teamID, appID.Platform)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "deleting vpp app from team")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -601,6 +601,26 @@ func testSetTeamVPPApps(t *testing.T, ds *Datastore) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// create policies using two of the apps
|
||||
app1Meta, err := ds.GetVPPAppMetadataByTeamAndTitleID(ctx, &team.ID, app1.TitleID)
|
||||
require.NoError(t, err)
|
||||
app2Meta, err := ds.GetVPPAppMetadataByTeamAndTitleID(ctx, &team.ID, app2.TitleID)
|
||||
require.NoError(t, err)
|
||||
policy1, err := ds.NewTeamPolicy(ctx, team.ID, nil, fleet.PolicyPayload{
|
||||
Name: "Policy 1",
|
||||
Query: "SELECT 1;",
|
||||
Platform: "darwin",
|
||||
VPPAppsTeamsID: &app1Meta.VPPAppsTeamsID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
policy2, err := ds.NewTeamPolicy(ctx, team.ID, nil, fleet.PolicyPayload{
|
||||
Name: "Policy 2",
|
||||
Query: "SELECT 1;",
|
||||
Platform: "darwin",
|
||||
VPPAppsTeamsID: &app2Meta.VPPAppsTeamsID,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
assigned, err = ds.GetAssignedVPPApps(ctx, &team.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, assigned, 2)
|
||||
|
|
@ -617,6 +637,10 @@ func testSetTeamVPPApps(t *testing.T, ds *Datastore) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
policy1, err = ds.Policy(ctx, policy1.ID)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, policy1.VPPAppsTeamsID)
|
||||
|
||||
assigned, err = ds.GetAssignedVPPApps(ctx, &team.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, assigned, 3)
|
||||
|
|
@ -634,6 +658,10 @@ func testSetTeamVPPApps(t *testing.T, ds *Datastore) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
policy1, err = ds.Policy(ctx, policy1.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, app1Meta.VPPAppsTeamsID, *policy1.VPPAppsTeamsID)
|
||||
|
||||
assigned, err = ds.GetAssignedVPPApps(ctx, &team.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, assigned, 3)
|
||||
|
|
@ -666,6 +694,14 @@ func testSetTeamVPPApps(t *testing.T, ds *Datastore) {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
policy1, err = ds.Policy(ctx, policy1.ID)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, policy1.VPPAppsTeamsID)
|
||||
|
||||
policy2, err = ds.Policy(ctx, policy2.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, app2Meta.VPPAppsTeamsID, *policy2.VPPAppsTeamsID)
|
||||
|
||||
// Remove all apps
|
||||
err = ds.SetTeamVPPApps(ctx, &team.ID, []fleet.VPPAppTeam{})
|
||||
require.NoError(t, err)
|
||||
|
|
@ -673,6 +709,14 @@ func testSetTeamVPPApps(t *testing.T, ds *Datastore) {
|
|||
assigned, err = ds.GetAssignedVPPApps(ctx, &team.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, assigned, 0)
|
||||
|
||||
policy1, err = ds.Policy(ctx, policy1.ID)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, policy1.VPPAppsTeamsID)
|
||||
|
||||
policy2, err = ds.Policy(ctx, policy2.ID)
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, policy2.VPPAppsTeamsID)
|
||||
}
|
||||
|
||||
func testGetVPPAppByTeamAndTitleID(t *testing.T, ds *Datastore) {
|
||||
|
|
|
|||
|
|
@ -1817,6 +1817,7 @@ type Datastore interface {
|
|||
|
||||
BatchInsertVPPApps(ctx context.Context, apps []*VPPApp) error
|
||||
GetAssignedVPPApps(ctx context.Context, teamID *uint) (map[VPPAppID]VPPAppTeam, error)
|
||||
GetVPPApps(ctx context.Context, teamID *uint) ([]VPPAppResponse, error)
|
||||
SetTeamVPPApps(ctx context.Context, teamID *uint, appIDs []VPPAppTeam) error
|
||||
InsertVPPAppWithTeam(ctx context.Context, app *VPPApp, teamID *uint) (*VPPApp, error)
|
||||
|
||||
|
|
|
|||
|
|
@ -770,7 +770,7 @@ type Service interface {
|
|||
GetVPPTokens(ctx context.Context) ([]*VPPTokenDB, error)
|
||||
DeleteVPPToken(ctx context.Context, tokenID uint) error
|
||||
|
||||
BatchAssociateVPPApps(ctx context.Context, teamName string, payloads []VPPBatchPayload, dryRun bool) error
|
||||
BatchAssociateVPPApps(ctx context.Context, teamName string, payloads []VPPBatchPayload, dryRun bool) ([]VPPAppResponse, error)
|
||||
|
||||
// GetHostDEPAssignment retrieves the host DEP assignment for the specified host.
|
||||
GetHostDEPAssignment(ctx context.Context, host *Host) (*HostDEPAssignment, error)
|
||||
|
|
|
|||
|
|
@ -158,6 +158,19 @@ type SoftwarePackageResponse struct {
|
|||
URL string `json:"url" db:"url"`
|
||||
}
|
||||
|
||||
// VPPAppResponse is the response type used when applying app store apps by batch.
|
||||
type VPPAppResponse struct {
|
||||
// TeamID is the ID of the team.
|
||||
// A value of nil means it is scoped to hosts that are assigned to "No team".
|
||||
TeamID *uint `json:"team_id" db:"team_id"`
|
||||
// TitleID is the id of the software title associated with the software installer.
|
||||
TitleID *uint `json:"title_id" db:"title_id"`
|
||||
// AppStoreID is the ADAM ID for this app (set when uploading via batch/gitops).
|
||||
AppStoreID string `json:"app_store_id" db:"app_store_id"`
|
||||
// Platform is the platform this title ID corresponds to
|
||||
Platform AppleDevicePlatform `json:"platform" db:"platform"`
|
||||
}
|
||||
|
||||
// AuthzType implements authz.AuthzTyper.
|
||||
func (s *SoftwareInstaller) AuthzType() string {
|
||||
return "installable_entity"
|
||||
|
|
|
|||
|
|
@ -1143,6 +1143,8 @@ type BatchInsertVPPAppsFunc func(ctx context.Context, apps []*fleet.VPPApp) erro
|
|||
|
||||
type GetAssignedVPPAppsFunc func(ctx context.Context, teamID *uint) (map[fleet.VPPAppID]fleet.VPPAppTeam, error)
|
||||
|
||||
type GetVPPAppsFunc func(ctx context.Context, teamID *uint) ([]fleet.VPPAppResponse, error)
|
||||
|
||||
type SetTeamVPPAppsFunc func(ctx context.Context, teamID *uint, appIDs []fleet.VPPAppTeam) error
|
||||
|
||||
type InsertVPPAppWithTeamFunc func(ctx context.Context, app *fleet.VPPApp, teamID *uint) (*fleet.VPPApp, error)
|
||||
|
|
@ -2887,6 +2889,9 @@ type DataStore struct {
|
|||
GetAssignedVPPAppsFunc GetAssignedVPPAppsFunc
|
||||
GetAssignedVPPAppsFuncInvoked bool
|
||||
|
||||
GetVPPAppsFunc GetVPPAppsFunc
|
||||
GetVPPAppsFuncInvoked bool
|
||||
|
||||
SetTeamVPPAppsFunc SetTeamVPPAppsFunc
|
||||
SetTeamVPPAppsFuncInvoked bool
|
||||
|
||||
|
|
@ -6907,6 +6912,13 @@ func (s *DataStore) GetAssignedVPPApps(ctx context.Context, teamID *uint) (map[f
|
|||
return s.GetAssignedVPPAppsFunc(ctx, teamID)
|
||||
}
|
||||
|
||||
func (s *DataStore) GetVPPApps(ctx context.Context, teamID *uint) ([]fleet.VPPAppResponse, error) {
|
||||
s.mu.Lock()
|
||||
s.GetVPPAppsFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.GetVPPAppsFunc(ctx, teamID)
|
||||
}
|
||||
|
||||
func (s *DataStore) SetTeamVPPApps(ctx context.Context, teamID *uint, appIDs []fleet.VPPAppTeam) error {
|
||||
s.mu.Lock()
|
||||
s.SetTeamVPPAppsFuncInvoked = true
|
||||
|
|
|
|||
|
|
@ -42,37 +42,37 @@ type MDMBootstrapPackageStore struct {
|
|||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func (fs *MDMBootstrapPackageStore) Get(ctx context.Context, packageID string) (io.ReadCloser, int64, error) {
|
||||
fs.mu.Lock()
|
||||
fs.GetFuncInvoked = true
|
||||
fs.mu.Unlock()
|
||||
return fs.GetFunc(ctx, packageID)
|
||||
func (s *MDMBootstrapPackageStore) Get(ctx context.Context, packageID string) (io.ReadCloser, int64, error) {
|
||||
s.mu.Lock()
|
||||
s.GetFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.GetFunc(ctx, packageID)
|
||||
}
|
||||
|
||||
func (fs *MDMBootstrapPackageStore) Put(ctx context.Context, packageID string, content io.ReadSeeker) error {
|
||||
fs.mu.Lock()
|
||||
fs.PutFuncInvoked = true
|
||||
fs.mu.Unlock()
|
||||
return fs.PutFunc(ctx, packageID, content)
|
||||
func (s *MDMBootstrapPackageStore) Put(ctx context.Context, packageID string, content io.ReadSeeker) error {
|
||||
s.mu.Lock()
|
||||
s.PutFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.PutFunc(ctx, packageID, content)
|
||||
}
|
||||
|
||||
func (fs *MDMBootstrapPackageStore) Exists(ctx context.Context, packageID string) (bool, error) {
|
||||
fs.mu.Lock()
|
||||
fs.ExistsFuncInvoked = true
|
||||
fs.mu.Unlock()
|
||||
return fs.ExistsFunc(ctx, packageID)
|
||||
func (s *MDMBootstrapPackageStore) Exists(ctx context.Context, packageID string) (bool, error) {
|
||||
s.mu.Lock()
|
||||
s.ExistsFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.ExistsFunc(ctx, packageID)
|
||||
}
|
||||
|
||||
func (fs *MDMBootstrapPackageStore) Cleanup(ctx context.Context, usedPackageIDs []string, removeCreatedBefore time.Time) (int, error) {
|
||||
fs.mu.Lock()
|
||||
fs.CleanupFuncInvoked = true
|
||||
fs.mu.Unlock()
|
||||
return fs.CleanupFunc(ctx, usedPackageIDs, removeCreatedBefore)
|
||||
func (s *MDMBootstrapPackageStore) Cleanup(ctx context.Context, usedPackageIDs []string, removeCreatedBefore time.Time) (int, error) {
|
||||
s.mu.Lock()
|
||||
s.CleanupFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.CleanupFunc(ctx, usedPackageIDs, removeCreatedBefore)
|
||||
}
|
||||
|
||||
func (fs *MDMBootstrapPackageStore) Sign(ctx context.Context, fileID string) (string, error) {
|
||||
fs.mu.Lock()
|
||||
fs.SignFuncInvoked = true
|
||||
fs.mu.Unlock()
|
||||
return fs.SignFunc(ctx, fileID)
|
||||
func (s *MDMBootstrapPackageStore) Sign(ctx context.Context, fileID string) (string, error) {
|
||||
s.mu.Lock()
|
||||
s.SignFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.SignFunc(ctx, fileID)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -423,8 +423,9 @@ func (c *Client) ApplyGroup(
|
|||
appconfig *fleet.EnrichedAppConfig,
|
||||
opts fleet.ApplyClientSpecOptions,
|
||||
teamsSoftwareInstallers map[string][]fleet.SoftwarePackageResponse,
|
||||
teamsVPPApps map[string][]fleet.VPPAppResponse,
|
||||
teamsScripts map[string][]fleet.ScriptResponse,
|
||||
) (map[string]uint, map[string][]fleet.SoftwarePackageResponse, map[string][]fleet.ScriptResponse, error) {
|
||||
) (map[string]uint, map[string][]fleet.SoftwarePackageResponse, map[string][]fleet.VPPAppResponse, map[string][]fleet.ScriptResponse, error) {
|
||||
logfn := func(format string, args ...interface{}) {
|
||||
if logf != nil {
|
||||
logf(format, args...)
|
||||
|
|
@ -437,7 +438,7 @@ func (c *Client) ApplyGroup(
|
|||
logfn("[!] ignoring queries, dry run mode only supported for 'config' and 'team' specs\n")
|
||||
} else {
|
||||
if err := c.ApplyQueries(specs.Queries); err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("applying queries: %w", err)
|
||||
return nil, nil, nil, nil, fmt.Errorf("applying queries: %w", err)
|
||||
}
|
||||
logfn("[+] applied %d queries\n", len(specs.Queries))
|
||||
}
|
||||
|
|
@ -448,7 +449,7 @@ func (c *Client) ApplyGroup(
|
|||
logfn("[!] ignoring labels, dry run mode only supported for 'config' and 'team' specs\n")
|
||||
} else {
|
||||
if err := c.ApplyLabels(specs.Labels); err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("applying labels: %w", err)
|
||||
return nil, nil, nil, nil, fmt.Errorf("applying labels: %w", err)
|
||||
}
|
||||
logfn("[+] applied %d labels\n", len(specs.Labels))
|
||||
}
|
||||
|
|
@ -459,7 +460,7 @@ func (c *Client) ApplyGroup(
|
|||
logfn("[!] ignoring packs, dry run mode only supported for 'config' and 'team' specs\n")
|
||||
} else {
|
||||
if err := c.ApplyPacks(specs.Packs); err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("applying packs: %w", err)
|
||||
return nil, nil, nil, nil, fmt.Errorf("applying packs: %w", err)
|
||||
}
|
||||
logfn("[+] applied %d packs\n", len(specs.Packs))
|
||||
}
|
||||
|
|
@ -479,7 +480,7 @@ func (c *Client) ApplyGroup(
|
|||
if (windowsCustomSettings != nil && macosCustomSettings != nil) || len(windowsCustomSettings)+len(macosCustomSettings) > 0 {
|
||||
fileContents, err := getProfilesContents(baseDir, macosCustomSettings, windowsCustomSettings, opts.ExpandEnvConfigProfiles)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
// Figure out if MDM should be enabled.
|
||||
assumeEnabled := false
|
||||
|
|
@ -493,30 +494,30 @@ func (c *Client) ApplyGroup(
|
|||
}
|
||||
}
|
||||
if err := c.ApplyNoTeamProfiles(fileContents, opts.ApplySpecOptions, assumeEnabled); err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("applying custom settings: %w", err)
|
||||
return nil, nil, nil, nil, fmt.Errorf("applying custom settings: %w", err)
|
||||
}
|
||||
}
|
||||
if macosSetup := extractAppCfgMacOSSetup(specs.AppConfig); macosSetup != nil {
|
||||
if macosSetup.BootstrapPackage.Value != "" {
|
||||
pkg, err := c.ValidateBootstrapPackageFromURL(macosSetup.BootstrapPackage.Value)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("applying fleet config: %w", err)
|
||||
return nil, nil, nil, nil, fmt.Errorf("applying fleet config: %w", err)
|
||||
}
|
||||
|
||||
if !opts.DryRun {
|
||||
if err := c.EnsureBootstrapPackage(pkg, uint(0)); err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("applying fleet config: %w", err)
|
||||
return nil, nil, nil, nil, fmt.Errorf("applying fleet config: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if macosSetup.MacOSSetupAssistant.Value != "" {
|
||||
content, err := c.validateMacOSSetupAssistant(resolveApplyRelativePath(baseDir, macosSetup.MacOSSetupAssistant.Value))
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("applying fleet config: %w", err)
|
||||
return nil, nil, nil, nil, fmt.Errorf("applying fleet config: %w", err)
|
||||
}
|
||||
if !opts.DryRun {
|
||||
if err := c.uploadMacOSSetupAssistant(content, nil, macosSetup.MacOSSetupAssistant.Value); err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("applying fleet config: %w", err)
|
||||
return nil, nil, nil, nil, fmt.Errorf("applying fleet config: %w", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -526,7 +527,7 @@ func (c *Client) ApplyGroup(
|
|||
for i, f := range scripts {
|
||||
b, err := os.ReadFile(f)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("applying no-team scripts: %w", err)
|
||||
return nil, nil, nil, nil, fmt.Errorf("applying no-team scripts: %w", err)
|
||||
}
|
||||
scriptPayloads[i] = fleet.ScriptPayload{
|
||||
ScriptContents: b,
|
||||
|
|
@ -535,14 +536,14 @@ func (c *Client) ApplyGroup(
|
|||
}
|
||||
noTeamScripts, err := c.ApplyNoTeamScripts(scriptPayloads, opts.ApplySpecOptions)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("applying no-team scripts: %w", err)
|
||||
return nil, nil, nil, nil, fmt.Errorf("applying no-team scripts: %w", err)
|
||||
}
|
||||
teamsScripts["No team"] = noTeamScripts
|
||||
}
|
||||
|
||||
rules, err := extractAppCfgYaraRules(specs.AppConfig)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("applying yara rules: %w", err)
|
||||
return nil, nil, nil, nil, fmt.Errorf("applying yara rules: %w", err)
|
||||
}
|
||||
if rules != nil {
|
||||
rulePayloads := make([]fleet.YaraRule, len(rules))
|
||||
|
|
@ -550,7 +551,7 @@ func (c *Client) ApplyGroup(
|
|||
path := resolveApplyRelativePath(baseDir, f.Path)
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("applying yara rules: %w", err)
|
||||
return nil, nil, nil, nil, fmt.Errorf("applying yara rules: %w", err)
|
||||
}
|
||||
rulePayloads[i] = fleet.YaraRule{
|
||||
Contents: string(b),
|
||||
|
|
@ -561,7 +562,7 @@ func (c *Client) ApplyGroup(
|
|||
}
|
||||
|
||||
if err := c.ApplyAppConfig(specs.AppConfig, opts.ApplySpecOptions); err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("applying fleet config: %w", err)
|
||||
return nil, nil, nil, nil, fmt.Errorf("applying fleet config: %w", err)
|
||||
}
|
||||
if opts.DryRun {
|
||||
logfn("[+] would've applied fleet config\n")
|
||||
|
|
@ -572,7 +573,7 @@ func (c *Client) ApplyGroup(
|
|||
|
||||
if specs.EnrollSecret != nil {
|
||||
if err := c.ApplyEnrollSecretSpec(specs.EnrollSecret, opts.ApplySpecOptions); err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("applying enroll secrets: %w", err)
|
||||
return nil, nil, nil, nil, fmt.Errorf("applying enroll secrets: %w", err)
|
||||
}
|
||||
if opts.DryRun {
|
||||
logfn("[+] would've applied enroll secrets\n")
|
||||
|
|
@ -591,7 +592,8 @@ func (c *Client) ApplyGroup(
|
|||
for k, profileSpecs := range tmMDMSettings {
|
||||
fileContents, err := getProfilesContents(baseDir, profileSpecs.macos, profileSpecs.windows, opts.ExpandEnvConfigProfiles)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("Team %s: %w", k, err) // TODO: consider adding team name to improve error messages generally for other parts of the config because multiple team configs can be processed at once
|
||||
// TODO: consider adding team name to improve error messages generally for other parts of the config because multiple team configs can be processed at once
|
||||
return nil, nil, nil, nil, fmt.Errorf("Team %s: %w", k, err)
|
||||
}
|
||||
tmFileContents[k] = fileContents
|
||||
}
|
||||
|
|
@ -612,28 +614,28 @@ func (c *Client) ApplyGroup(
|
|||
if setup.BootstrapPackage.Value != "" {
|
||||
bp, err := c.ValidateBootstrapPackageFromURL(setup.BootstrapPackage.Value)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("applying teams: %w", err)
|
||||
return nil, nil, nil, nil, fmt.Errorf("applying teams: %w", err)
|
||||
}
|
||||
tmBootstrapPackages[k] = bp
|
||||
}
|
||||
if setup.MacOSSetupAssistant.Value != "" {
|
||||
b, err := c.validateMacOSSetupAssistant(resolveApplyRelativePath(baseDir, setup.MacOSSetupAssistant.Value))
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("applying teams: %w", err)
|
||||
return nil, nil, nil, nil, fmt.Errorf("applying teams: %w", err)
|
||||
}
|
||||
tmMacSetupAssistants[k] = b
|
||||
}
|
||||
if setup.Script.Value != "" {
|
||||
b, err := c.validateMacOSSetupScript(resolveApplyRelativePath(baseDir, setup.Script.Value))
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("applying teams: %w", err)
|
||||
return nil, nil, nil, nil, fmt.Errorf("applying teams: %w", err)
|
||||
}
|
||||
tmMacSetupScript[k] = fileContent{Filename: filepath.Base(setup.Script.Value), Content: b}
|
||||
}
|
||||
if viaGitOps {
|
||||
m, err := extractTeamOrNoTeamMacOSSetupSoftware(baseDir, setup.Software.Value)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
tmSoftwareMacOSSetup[k] = m
|
||||
tmMacSetupSoftware[k] = setup.Software.Value
|
||||
|
|
@ -647,7 +649,7 @@ func (c *Client) ApplyGroup(
|
|||
for i, f := range paths {
|
||||
b, err := os.ReadFile(f)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("applying fleet config: %w", err)
|
||||
return nil, nil, nil, nil, fmt.Errorf("applying fleet config: %w", err)
|
||||
}
|
||||
scriptPayloads[i] = fleet.ScriptPayload{
|
||||
ScriptContents: b,
|
||||
|
|
@ -664,7 +666,7 @@ func (c *Client) ApplyGroup(
|
|||
installDuringSetupKeys := tmSoftwareMacOSSetup[tmName]
|
||||
softwarePayloads, err := buildSoftwarePackagesPayload(software, installDuringSetupKeys)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("applying software installers for team %q: %w", tmName, err)
|
||||
return nil, nil, nil, nil, fmt.Errorf("applying software installers for team %q: %w", tmName, err)
|
||||
}
|
||||
tmSoftwarePackagesPayloads[tmName] = softwarePayloads
|
||||
for _, swSpec := range software {
|
||||
|
|
@ -708,7 +710,7 @@ func (c *Client) ApplyGroup(
|
|||
// packages or vpp apps.
|
||||
for tmName, setupSw := range tmMacSetupSoftware {
|
||||
if err := validateTeamOrNoTeamMacOSSetupSoftware(tmName, setupSw, tmSoftwarePackageByPath[tmName], tmSoftwareAppsByAppID[tmName]); err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -722,7 +724,7 @@ func (c *Client) ApplyGroup(
|
|||
// In dry-run, the team names returned are the old team names (when team name is modified via gitops)
|
||||
teamIDsByName, err = c.ApplyTeams(specs.Teams, teamOpts)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("applying teams: %w", err)
|
||||
return nil, nil, nil, nil, fmt.Errorf("applying teams: %w", err)
|
||||
}
|
||||
|
||||
// When using GitOps, the team name could change, so we need to check for that
|
||||
|
|
@ -749,7 +751,7 @@ func (c *Client) ApplyGroup(
|
|||
} else {
|
||||
logfn("[+] applying MDM profiles for team %s\n", tmName)
|
||||
if err := c.ApplyTeamProfiles(currentTeamName, profs, teamOpts); err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("applying custom settings for team %q: %w", tmName, err)
|
||||
return nil, nil, nil, nil, fmt.Errorf("applying custom settings for team %q: %w", tmName, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -758,7 +760,7 @@ func (c *Client) ApplyGroup(
|
|||
for tmName, tmID := range teamIDsByName {
|
||||
if bp, ok := tmBootstrapPackages[tmName]; ok {
|
||||
if err := c.EnsureBootstrapPackage(bp, tmID); err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("uploading bootstrap package for team %q: %w", tmName, err)
|
||||
return nil, nil, nil, nil, fmt.Errorf("uploading bootstrap package for team %q: %w", tmName, err)
|
||||
}
|
||||
}
|
||||
if b, ok := tmMacSetupAssistants[tmName]; ok {
|
||||
|
|
@ -770,11 +772,11 @@ func (c *Client) ApplyGroup(
|
|||
// to render a more helpful error message.
|
||||
parts := strings.Split(err.Error(), ".")
|
||||
if len(parts) < 2 {
|
||||
return nil, nil, nil, fmt.Errorf("unexpected error while uploading macOS setup assistant for team %q: %w", tmName, err)
|
||||
return nil, nil, nil, nil, fmt.Errorf("unexpected error while uploading macOS setup assistant for team %q: %w", tmName, err)
|
||||
}
|
||||
return nil, nil, nil, fmt.Errorf("Couldn't edit macos_setup_assistant. Response from Apple: %s. Learn more at %s", strings.Trim(parts[1], " "), "https://fleetdm.com/learn-more-about/dep-profile")
|
||||
return nil, nil, nil, nil, fmt.Errorf("Couldn't edit macos_setup_assistant. Response from Apple: %s. Learn more at %s", strings.Trim(parts[1], " "), "https://fleetdm.com/learn-more-about/dep-profile")
|
||||
}
|
||||
return nil, nil, nil, fmt.Errorf("uploading macOS setup assistant for team %q: %w", tmName, err)
|
||||
return nil, nil, nil, nil, fmt.Errorf("uploading macOS setup assistant for team %q: %w", tmName, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -783,11 +785,11 @@ func (c *Client) ApplyGroup(
|
|||
for tmName, tmID := range teamIDsByName {
|
||||
if fc, ok := tmMacSetupScript[tmName]; ok {
|
||||
if err := c.uploadMacOSSetupScript(fc.Filename, fc.Content, &tmID); err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("uploading setup experience script for team %q: %w", tmName, err)
|
||||
return nil, nil, nil, nil, fmt.Errorf("uploading setup experience script for team %q: %w", tmName, err)
|
||||
}
|
||||
} else {
|
||||
if err := c.deleteMacOSSetupScript(&tmID); err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("deleting setup experience script for team %q: %w", tmName, err)
|
||||
return nil, nil, nil, nil, fmt.Errorf("deleting setup experience script for team %q: %w", tmName, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -798,7 +800,7 @@ func (c *Client) ApplyGroup(
|
|||
currentTeamName := getTeamName(tmName)
|
||||
scriptResponses, err := c.ApplyTeamScripts(currentTeamName, scripts, opts.ApplySpecOptions)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("applying scripts for team %q: %w", tmName, err)
|
||||
return nil, nil, nil, nil, fmt.Errorf("applying scripts for team %q: %w", tmName, err)
|
||||
}
|
||||
teamsScripts[tmName] = scriptResponses
|
||||
}
|
||||
|
|
@ -810,7 +812,7 @@ func (c *Client) ApplyGroup(
|
|||
logfn("[+] applying %d software packages for team %s\n", len(software), tmName)
|
||||
installers, err := c.ApplyTeamSoftwareInstallers(currentTeamName, software, opts.ApplySpecOptions)
|
||||
if err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("applying software installers for team %q: %w", tmName, err)
|
||||
return nil, nil, nil, nil, fmt.Errorf("applying software installers for team %q: %w", tmName, err)
|
||||
}
|
||||
teamsSoftwareInstallers[tmName] = installers
|
||||
}
|
||||
|
|
@ -820,9 +822,11 @@ func (c *Client) ApplyGroup(
|
|||
// For non-dry run, currentTeamName and tmName are the same
|
||||
currentTeamName := getTeamName(tmName)
|
||||
logfn("[+] applying %d app store apps for team %s\n", len(apps), tmName)
|
||||
if err := c.ApplyTeamAppStoreAppsAssociation(currentTeamName, apps, opts.ApplySpecOptions); err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("applying app store apps for team: %q: %w", tmName, err)
|
||||
appsResponse, err := c.ApplyTeamAppStoreAppsAssociation(currentTeamName, apps, opts.ApplySpecOptions)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, fmt.Errorf("applying app store apps for team: %q: %w", tmName, err)
|
||||
}
|
||||
teamsVPPApps[tmName] = appsResponse
|
||||
}
|
||||
}
|
||||
if opts.DryRun {
|
||||
|
|
@ -836,7 +840,7 @@ func (c *Client) ApplyGroup(
|
|||
if len(specs.Policies) > 0 {
|
||||
// Policy names must be unique, return error if duplicate policy names are found
|
||||
if policyName := fleet.FirstDuplicatePolicySpecName(specs.Policies); policyName != "" {
|
||||
return nil, nil, nil, fmt.Errorf(
|
||||
return nil, nil, nil, nil, fmt.Errorf(
|
||||
"applying policies: policy names must be unique. Please correct policy %q and try again.", policyName,
|
||||
)
|
||||
}
|
||||
|
|
@ -850,7 +854,7 @@ func (c *Client) ApplyGroup(
|
|||
}
|
||||
}
|
||||
if err := c.ApplyPolicies(specs.Policies); err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("applying policies: %w", err)
|
||||
return nil, nil, nil, nil, fmt.Errorf("applying policies: %w", err)
|
||||
}
|
||||
logfn("[+] applied %d policies\n", len(specs.Policies))
|
||||
}
|
||||
|
|
@ -861,13 +865,13 @@ func (c *Client) ApplyGroup(
|
|||
logfn("[!] ignoring user roles, dry run mode only supported for 'config' and 'team' specs\n")
|
||||
} else {
|
||||
if err := c.ApplyUsersRoleSecretSpec(specs.UsersRoles); err != nil {
|
||||
return nil, nil, nil, fmt.Errorf("applying user roles: %w", err)
|
||||
return nil, nil, nil, nil, fmt.Errorf("applying user roles: %w", err)
|
||||
}
|
||||
logfn("[+] applied user roles\n")
|
||||
}
|
||||
}
|
||||
|
||||
return teamIDsByName, teamsSoftwareInstallers, teamsScripts, nil
|
||||
return teamIDsByName, teamsSoftwareInstallers, teamsVPPApps, teamsScripts, nil
|
||||
}
|
||||
|
||||
func extractTeamOrNoTeamMacOSSetupSoftware(baseDir string, software []*fleet.MacOSSetupSoftware) (map[fleet.MacOSSetupSoftware]struct{}, error) {
|
||||
|
|
@ -1447,6 +1451,7 @@ func (c *Client) DoGitOps(
|
|||
appConfig *fleet.EnrichedAppConfig,
|
||||
// pass-by-ref to build lists
|
||||
teamsSoftwareInstallers map[string][]fleet.SoftwarePackageResponse,
|
||||
teamsVPPApps map[string][]fleet.VPPAppResponse,
|
||||
teamsScripts map[string][]fleet.ScriptResponse,
|
||||
) (*fleet.TeamSpecsDryRunAssumptions, error) {
|
||||
baseDir := filepath.Dir(fullFilename)
|
||||
|
|
@ -1710,18 +1715,20 @@ func (c *Client) DoGitOps(
|
|||
}
|
||||
|
||||
// Apply org settings, scripts, enroll secrets, team entities (software, scripts, etc.), and controls.
|
||||
teamIDsByName, teamsSoftwareInstallers, teamsScripts, err := c.ApplyGroup(ctx, true, &group, baseDir, logf, appConfig, fleet.ApplyClientSpecOptions{
|
||||
teamIDsByName, teamsSoftwareInstallers, teamsVPPApps, teamsScripts, err := c.ApplyGroup(ctx, true, &group, baseDir, logf, appConfig, fleet.ApplyClientSpecOptions{
|
||||
ApplySpecOptions: fleet.ApplySpecOptions{
|
||||
DryRun: dryRun,
|
||||
},
|
||||
ExpandEnvConfigProfiles: true,
|
||||
}, teamsSoftwareInstallers, teamsScripts)
|
||||
}, teamsSoftwareInstallers, teamsVPPApps, teamsScripts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var teamSoftwareInstallers []fleet.SoftwarePackageResponse
|
||||
var teamVPPApps []fleet.VPPAppResponse
|
||||
var teamScripts []fleet.ScriptResponse
|
||||
|
||||
if config.TeamName != nil {
|
||||
if !config.IsNoTeam() {
|
||||
if len(teamIDsByName) != 1 {
|
||||
|
|
@ -1739,18 +1746,20 @@ func (c *Client) DoGitOps(
|
|||
config.TeamID = &teamID
|
||||
}
|
||||
teamSoftwareInstallers = teamsSoftwareInstallers[*config.TeamName]
|
||||
teamVPPApps = teamsVPPApps[*config.TeamName]
|
||||
teamScripts = teamsScripts[*config.TeamName]
|
||||
} else {
|
||||
noTeamSoftwareInstallers, err := c.doGitOpsNoTeamSoftware(config, baseDir, appConfig, logFn, dryRun)
|
||||
noTeamSoftwareInstallers, noTeamVPPApps, err := c.doGitOpsNoTeamSoftware(config, baseDir, appConfig, logFn, dryRun)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
teamSoftwareInstallers = noTeamSoftwareInstallers
|
||||
teamVPPApps = noTeamVPPApps
|
||||
teamScripts = teamsScripts["No team"]
|
||||
}
|
||||
}
|
||||
|
||||
err = c.doGitOpsPolicies(config, teamSoftwareInstallers, teamScripts, logFn, dryRun)
|
||||
err = c.doGitOpsPolicies(config, teamSoftwareInstallers, teamVPPApps, teamScripts, logFn, dryRun)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -1773,9 +1782,9 @@ func (c *Client) doGitOpsNoTeamSoftware(
|
|||
appconfig *fleet.EnrichedAppConfig,
|
||||
logFn func(format string, args ...interface{}),
|
||||
dryRun bool,
|
||||
) ([]fleet.SoftwarePackageResponse, error) {
|
||||
) ([]fleet.SoftwarePackageResponse, []fleet.VPPAppResponse, error) {
|
||||
if !config.IsNoTeam() || appconfig == nil || !appconfig.License.IsPremium() {
|
||||
return nil, nil
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// marshaling dance to get the macos_setup data - config.Controls.MacOSSetup
|
||||
|
|
@ -1785,11 +1794,11 @@ func (c *Client) doGitOpsNoTeamSoftware(
|
|||
// the untyped map.
|
||||
b, err := json.Marshal(config.Controls.MacOSSetup)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("applying software installers: json-encode controls.macos_setup: %w", err)
|
||||
return nil, nil, fmt.Errorf("applying software installers: json-encode controls.macos_setup: %w", err)
|
||||
}
|
||||
var macOSSetup fleet.MacOSSetup
|
||||
if err := json.Unmarshal(b, &macOSSetup); err != nil {
|
||||
return nil, fmt.Errorf("applying software installers: json-decode controls.macos_setup: %w", err)
|
||||
return nil, nil, fmt.Errorf("applying software installers: json-decode controls.macos_setup: %w", err)
|
||||
}
|
||||
|
||||
// load the no-team macos_setup.script if any
|
||||
|
|
@ -1797,14 +1806,14 @@ func (c *Client) doGitOpsNoTeamSoftware(
|
|||
if macOSSetup.Script.Value != "" {
|
||||
b, err := c.validateMacOSSetupScript(resolveApplyRelativePath(baseDir, macOSSetup.Script.Value))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("applying no team macos_setup.script: %w", err)
|
||||
return nil, nil, fmt.Errorf("applying no team macos_setup.script: %w", err)
|
||||
}
|
||||
macosSetupScript = &fileContent{Filename: filepath.Base(macOSSetup.Script.Value), Content: b}
|
||||
}
|
||||
|
||||
noTeamSoftwareMacOSSetup, err := extractTeamOrNoTeamMacOSSetupSoftware(baseDir, macOSSetup.Software.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var softwareInstallers []fleet.SoftwarePackageResponse
|
||||
|
|
@ -1838,30 +1847,31 @@ func (c *Client) doGitOpsNoTeamSoftware(
|
|||
}
|
||||
|
||||
if err := validateTeamOrNoTeamMacOSSetupSoftware(*config.TeamName, macOSSetup.Software.Value, packagesByPath, appsByAppID); err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
swPkgPayload, err := buildSoftwarePackagesPayload(packages, noTeamSoftwareMacOSSetup)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("applying software installers: %w", err)
|
||||
return nil, nil, fmt.Errorf("applying software installers: %w", err)
|
||||
}
|
||||
|
||||
if macosSetupScript != nil {
|
||||
logFn("[+] applying macos setup experience script for 'No team'\n")
|
||||
if err := c.uploadMacOSSetupScript(macosSetupScript.Filename, macosSetupScript.Content, nil); err != nil {
|
||||
return nil, fmt.Errorf("uploading setup experience script for No team: %w", err)
|
||||
return nil, nil, fmt.Errorf("uploading setup experience script for No team: %w", err)
|
||||
}
|
||||
} else if err := c.deleteMacOSSetupScript(nil); err != nil {
|
||||
return nil, fmt.Errorf("deleting setup experience script for No team: %w", err)
|
||||
return nil, nil, fmt.Errorf("deleting setup experience script for No team: %w", err)
|
||||
}
|
||||
|
||||
logFn("[+] applying %d software packages for 'No team'\n", len(swPkgPayload))
|
||||
softwareInstallers, err = c.ApplyNoTeamSoftwareInstallers(swPkgPayload, fleet.ApplySpecOptions{DryRun: dryRun})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("applying software installers: %w", err)
|
||||
return nil, nil, fmt.Errorf("applying software installers: %w", err)
|
||||
}
|
||||
logFn("[+] applying %d app store apps for 'No team'\n", len(appsPayload))
|
||||
if err := c.ApplyNoTeamAppStoreAppsAssociation(appsPayload, fleet.ApplySpecOptions{DryRun: dryRun}); err != nil {
|
||||
return nil, fmt.Errorf("applying app store apps: %w", err)
|
||||
vppApps, err := c.ApplyNoTeamAppStoreAppsAssociation(appsPayload, fleet.ApplySpecOptions{DryRun: dryRun})
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("applying app store apps: %w", err)
|
||||
}
|
||||
|
||||
if dryRun {
|
||||
|
|
@ -1869,10 +1879,10 @@ func (c *Client) doGitOpsNoTeamSoftware(
|
|||
} else {
|
||||
logFn("[+] applied 'No Team' software packages\n")
|
||||
}
|
||||
return softwareInstallers, nil
|
||||
return softwareInstallers, vppApps, nil
|
||||
}
|
||||
|
||||
func (c *Client) doGitOpsPolicies(config *spec.GitOps, teamSoftwareInstallers []fleet.SoftwarePackageResponse, teamScripts []fleet.ScriptResponse, logFn func(format string, args ...interface{}), dryRun bool) error {
|
||||
func (c *Client) doGitOpsPolicies(config *spec.GitOps, teamSoftwareInstallers []fleet.SoftwarePackageResponse, teamVPPApps []fleet.VPPAppResponse, teamScripts []fleet.ScriptResponse, logFn func(format string, args ...interface{}), dryRun bool) error {
|
||||
var teamID *uint // Global policies (nil)
|
||||
switch {
|
||||
case config.TeamID != nil: // Team policies
|
||||
|
|
@ -1882,7 +1892,8 @@ func (c *Client) doGitOpsPolicies(config *spec.GitOps, teamSoftwareInstallers []
|
|||
}
|
||||
if teamID != nil {
|
||||
// Get software titles of packages for the team.
|
||||
softwareTitleURLs := make(map[string]uint)
|
||||
softwareTitleIDsByInstallerURL := make(map[string]uint)
|
||||
softwareTitleIDsByAppStoreAppID := make(map[string]uint)
|
||||
for _, softwareInstaller := range teamSoftwareInstallers {
|
||||
if softwareInstaller.TitleID == nil {
|
||||
// Should not happen, but to not panic we just log a warning.
|
||||
|
|
@ -1894,23 +1905,53 @@ func (c *Client) doGitOpsPolicies(config *spec.GitOps, teamSoftwareInstallers []
|
|||
logFn("[!] software installer without url: team_id=%d, title_id=%d\n", *teamID, *softwareInstaller.TitleID)
|
||||
continue
|
||||
}
|
||||
softwareTitleURLs[softwareInstaller.URL] = *softwareInstaller.TitleID
|
||||
softwareTitleIDsByInstallerURL[softwareInstaller.URL] = *softwareInstaller.TitleID
|
||||
}
|
||||
for _, vppApp := range teamVPPApps {
|
||||
if vppApp.Platform != fleet.MacOSPlatform {
|
||||
continue // ignore iPad/iPhone VPP apps as they aren't relevant for policies
|
||||
}
|
||||
if vppApp.TitleID == nil {
|
||||
// Should not happen, but to not panic we just log a warning.
|
||||
logFn("[!] VPP app without title id: team_id=%d, app_store_id=%s\n", *teamID, vppApp.AppStoreID)
|
||||
continue
|
||||
}
|
||||
if vppApp.AppStoreID == "" {
|
||||
// Should not happen because we previously applied apps via gitops, but to not panic we just log a warning.
|
||||
logFn("[!] VPP app without app ID: team_id=%d, title_id=%d\n", *teamID, *vppApp.TitleID)
|
||||
continue
|
||||
}
|
||||
softwareTitleIDsByAppStoreAppID[vppApp.AppStoreID] = *vppApp.TitleID
|
||||
}
|
||||
|
||||
for i := range config.Policies {
|
||||
config.Policies[i].SoftwareTitleID = ptr.Uint(0) // 0 unsets the installer
|
||||
|
||||
if config.Policies[i].InstallSoftware == nil {
|
||||
continue
|
||||
}
|
||||
softwareTitleID, ok := softwareTitleURLs[config.Policies[i].InstallSoftwareURL]
|
||||
if !ok {
|
||||
// Should not happen because software packages are uploaded first.
|
||||
if !dryRun {
|
||||
logFn("[!] software URL without software title id: %s\n", config.Policies[i].InstallSoftwareURL)
|
||||
if config.Policies[i].InstallSoftwareURL != "" {
|
||||
softwareTitleID, ok := softwareTitleIDsByInstallerURL[config.Policies[i].InstallSoftwareURL]
|
||||
if !ok {
|
||||
// Should not happen because software packages are uploaded first.
|
||||
if !dryRun {
|
||||
logFn("[!] software URL without software title ID: %s\n", config.Policies[i].InstallSoftwareURL)
|
||||
}
|
||||
continue
|
||||
}
|
||||
continue
|
||||
config.Policies[i].SoftwareTitleID = &softwareTitleID
|
||||
}
|
||||
if config.Policies[i].InstallSoftware.AppStoreID != "" {
|
||||
softwareTitleID, ok := softwareTitleIDsByAppStoreAppID[config.Policies[i].InstallSoftware.AppStoreID]
|
||||
if !ok {
|
||||
// Should not happen because app store apps are uploaded first.
|
||||
if !dryRun {
|
||||
logFn("[!] software app store app ID without software title ID: %s\n", config.Policies[i].InstallSoftware.AppStoreID)
|
||||
}
|
||||
continue
|
||||
}
|
||||
config.Policies[i].SoftwareTitleID = &softwareTitleID
|
||||
}
|
||||
config.Policies[i].SoftwareTitleID = &softwareTitleID
|
||||
}
|
||||
|
||||
// Get scripts for the team.
|
||||
|
|
|
|||
|
|
@ -105,24 +105,29 @@ func (c *Client) ApplyTeamSoftwareInstallers(tmName string, softwareInstallers [
|
|||
return c.applySoftwareInstallers(softwareInstallers, query, opts.DryRun)
|
||||
}
|
||||
|
||||
func (c *Client) ApplyTeamAppStoreAppsAssociation(tmName string, vppBatchPayload []fleet.VPPBatchPayload, opts fleet.ApplySpecOptions) error {
|
||||
func (c *Client) ApplyTeamAppStoreAppsAssociation(tmName string, vppBatchPayload []fleet.VPPBatchPayload, opts fleet.ApplySpecOptions) ([]fleet.VPPAppResponse, error) {
|
||||
query, err := url.ParseQuery(opts.RawQuery())
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
query.Add("team_name", tmName)
|
||||
return c.applyAppStoreAppsAssociation(vppBatchPayload, query)
|
||||
}
|
||||
|
||||
func (c *Client) ApplyNoTeamAppStoreAppsAssociation(vppBatchPayload []fleet.VPPBatchPayload, opts fleet.ApplySpecOptions) error {
|
||||
func (c *Client) ApplyNoTeamAppStoreAppsAssociation(vppBatchPayload []fleet.VPPBatchPayload, opts fleet.ApplySpecOptions) ([]fleet.VPPAppResponse, error) {
|
||||
query, err := url.ParseQuery(opts.RawQuery())
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
return c.applyAppStoreAppsAssociation(vppBatchPayload, query)
|
||||
}
|
||||
|
||||
func (c *Client) applyAppStoreAppsAssociation(vppBatchPayload []fleet.VPPBatchPayload, query url.Values) error {
|
||||
func (c *Client) applyAppStoreAppsAssociation(vppBatchPayload []fleet.VPPBatchPayload, query url.Values) ([]fleet.VPPAppResponse, error) {
|
||||
verb, path := "POST", "/api/latest/fleet/software/app_store_apps/batch"
|
||||
return c.authenticatedRequestWithQuery(map[string]interface{}{"app_store_apps": vppBatchPayload}, verb, path, nil, query.Encode())
|
||||
var appsResponse batchAssociateAppStoreAppsResponse
|
||||
err := c.authenticatedRequestWithQuery(map[string]interface{}{"app_store_apps": vppBatchPayload}, verb, path, &appsResponse, query.Encode())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return appsResponse.Apps, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -778,7 +778,7 @@ func TestGitOpsErrors(t *testing.T) {
|
|||
err = json.Unmarshal([]byte(tt.rawJSON), &config.OrgSettings)
|
||||
require.NoError(t, err)
|
||||
config.OrgSettings["secrets"] = []*fleet.EnrollSecret{}
|
||||
_, err = client.DoGitOps(ctx, config, "/filename", nil, false, nil, nil, nil, nil)
|
||||
_, err = client.DoGitOps(ctx, config, "/filename", nil, false, nil, nil, nil, nil, nil)
|
||||
assert.ErrorContains(t, err, tt.wantErr)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10423,8 +10423,11 @@ func (s *integrationMDMTestSuite) TestBatchAssociateAppStoreApps() {
|
|||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
var batchAssociateResponse batchAssociateAppStoreAppsResponse
|
||||
|
||||
// No vpp token set, but request is empty so it succeeds (clears VPP apps for the team).
|
||||
s.Do("POST", batchURL, batchAssociateAppStoreAppsRequest{}, http.StatusNoContent, "team_name", tmGood.Name)
|
||||
s.DoJSON("POST", batchURL, batchAssociateAppStoreAppsRequest{}, http.StatusOK, &batchAssociateResponse, "team_name", tmGood.Name)
|
||||
require.Len(t, batchAssociateResponse.Apps, 0)
|
||||
|
||||
// No vpp token set, try association
|
||||
// FIXME
|
||||
|
|
@ -10443,7 +10446,8 @@ func (s *integrationMDMTestSuite) TestBatchAssociateAppStoreApps() {
|
|||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/vpp_tokens/%d/teams", vppRes.Token.ID), patchVPPTokensTeamsRequest{TeamIDs: []uint{}}, http.StatusOK, &resPatchVPP)
|
||||
|
||||
// Remove all vpp associations from team with no members
|
||||
s.Do("POST", batchURL, batchAssociateAppStoreAppsRequest{}, http.StatusNoContent, "team_name", tmGood.Name)
|
||||
s.DoJSON("POST", batchURL, batchAssociateAppStoreAppsRequest{}, http.StatusOK, &batchAssociateResponse, "team_name", tmGood.Name)
|
||||
require.Len(t, batchAssociateResponse.Apps, 0)
|
||||
|
||||
// host with valid serial number
|
||||
hValid, err := s.ds.NewHost(context.Background(), &fleet.Host{
|
||||
|
|
@ -10468,7 +10472,8 @@ func (s *integrationMDMTestSuite) TestBatchAssociateAppStoreApps() {
|
|||
require.Len(t, assoc, 0)
|
||||
|
||||
// Remove all vpp associations from team with no members
|
||||
s.Do("POST", batchURL, batchAssociateAppStoreAppsRequest{}, http.StatusNoContent, "team_name", tmGood.Name)
|
||||
s.DoJSON("POST", batchURL, batchAssociateAppStoreAppsRequest{}, http.StatusOK, &batchAssociateResponse, "team_name", tmGood.Name)
|
||||
require.Len(t, batchAssociateResponse.Apps, 0)
|
||||
|
||||
// Incorrect type check
|
||||
incorrectTypes := struct {
|
||||
|
|
@ -10498,7 +10503,10 @@ func (s *integrationMDMTestSuite) TestBatchAssociateAppStoreApps() {
|
|||
require.Len(t, assoc, 0)
|
||||
|
||||
// Associating an app we own
|
||||
s.Do("POST", batchURL, batchAssociateAppStoreAppsRequest{Apps: []fleet.VPPBatchPayload{{AppStoreID: s.appleVPPConfigSrvConfig.Assets[0].AdamID}}}, http.StatusNoContent, "team_name", tmGood.Name)
|
||||
s.DoJSON("POST", batchURL, batchAssociateAppStoreAppsRequest{Apps: []fleet.VPPBatchPayload{
|
||||
{AppStoreID: s.appleVPPConfigSrvConfig.Assets[0].AdamID},
|
||||
}}, http.StatusOK, &batchAssociateResponse, "team_name", tmGood.Name)
|
||||
require.Len(t, batchAssociateResponse.Apps, 1)
|
||||
|
||||
assoc, err = s.ds.GetAssignedVPPApps(ctx, &tmGood.ID)
|
||||
require.NoError(t, err)
|
||||
|
|
@ -10519,15 +10527,16 @@ func (s *integrationMDMTestSuite) TestBatchAssociateAppStoreApps() {
|
|||
require.Len(t, assoc, 1)
|
||||
|
||||
// Associating two apps we own
|
||||
s.Do("POST",
|
||||
s.DoJSON("POST",
|
||||
batchURL,
|
||||
batchAssociateAppStoreAppsRequest{
|
||||
Apps: []fleet.VPPBatchPayload{
|
||||
{AppStoreID: s.appleVPPConfigSrvConfig.Assets[0].AdamID},
|
||||
{AppStoreID: s.appleVPPConfigSrvConfig.Assets[1].AdamID, SelfService: true},
|
||||
},
|
||||
}, http.StatusNoContent, "team_name", tmGood.Name,
|
||||
}, http.StatusOK, &batchAssociateResponse, "team_name", tmGood.Name,
|
||||
)
|
||||
require.Len(t, batchAssociateResponse.Apps, 4)
|
||||
assoc, err = s.ds.GetAssignedVPPApps(ctx, &tmGood.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, assoc, 4)
|
||||
|
|
@ -10544,15 +10553,16 @@ func (s *integrationMDMTestSuite) TestBatchAssociateAppStoreApps() {
|
|||
|
||||
// Reverse self-service associations
|
||||
// Associating two apps we own
|
||||
s.Do("POST",
|
||||
s.DoJSON("POST",
|
||||
batchURL,
|
||||
batchAssociateAppStoreAppsRequest{
|
||||
Apps: []fleet.VPPBatchPayload{
|
||||
{AppStoreID: s.appleVPPConfigSrvConfig.Assets[0].AdamID, SelfService: true},
|
||||
{AppStoreID: s.appleVPPConfigSrvConfig.Assets[1].AdamID, SelfService: false},
|
||||
},
|
||||
}, http.StatusNoContent, "team_name", tmGood.Name,
|
||||
}, http.StatusOK, &batchAssociateResponse, "team_name", tmGood.Name,
|
||||
)
|
||||
require.Len(t, batchAssociateResponse.Apps, 4)
|
||||
assoc, err = s.ds.GetAssignedVPPApps(ctx, &tmGood.ID)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, assoc, 4)
|
||||
|
|
@ -10575,10 +10585,11 @@ func (s *integrationMDMTestSuite) TestBatchAssociateAppStoreApps() {
|
|||
}, assoc[fleet.VPPAppID{AdamID: s.appleVPPConfigSrvConfig.Assets[1].AdamID, Platform: fleet.MacOSPlatform}])
|
||||
|
||||
// Associate an app with a team with no team members
|
||||
s.Do("POST", batchURL, batchAssociateAppStoreAppsRequest{Apps: []fleet.VPPBatchPayload{{AppStoreID: s.appleVPPConfigSrvConfig.Assets[0].AdamID}}}, http.StatusNoContent, "team_name", tmEmpty.Name)
|
||||
s.DoJSON("POST", batchURL, batchAssociateAppStoreAppsRequest{Apps: []fleet.VPPBatchPayload{{AppStoreID: s.appleVPPConfigSrvConfig.Assets[0].AdamID}}}, http.StatusOK, &batchAssociateResponse, "team_name", tmEmpty.Name)
|
||||
|
||||
// Remove all vpp associations
|
||||
s.Do("POST", batchURL, batchAssociateAppStoreAppsRequest{}, http.StatusNoContent, "team_name", tmGood.Name)
|
||||
s.DoJSON("POST", batchURL, batchAssociateAppStoreAppsRequest{}, http.StatusOK, &batchAssociateResponse, "team_name", tmGood.Name)
|
||||
require.Len(t, batchAssociateResponse.Apps, 0)
|
||||
|
||||
assoc, err = s.ds.GetAssignedVPPApps(ctx, &tmGood.ID)
|
||||
require.NoError(t, err)
|
||||
|
|
|
|||
|
|
@ -767,25 +767,25 @@ func (b *batchAssociateAppStoreAppsRequest) DecodeBody(ctx context.Context, r io
|
|||
}
|
||||
|
||||
type batchAssociateAppStoreAppsResponse struct {
|
||||
Err error `json:"error,omitempty"`
|
||||
Apps []fleet.VPPAppResponse `json:"app_store_apps"`
|
||||
Err error `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (r batchAssociateAppStoreAppsResponse) error() error { return r.Err }
|
||||
|
||||
func (r batchAssociateAppStoreAppsResponse) Status() int { return http.StatusNoContent }
|
||||
|
||||
func batchAssociateAppStoreAppsEndpoint(ctx context.Context, request any, svc fleet.Service) (errorer, error) {
|
||||
req := request.(*batchAssociateAppStoreAppsRequest)
|
||||
if err := svc.BatchAssociateVPPApps(ctx, req.TeamName, req.Apps, req.DryRun); err != nil {
|
||||
apps, err := svc.BatchAssociateVPPApps(ctx, req.TeamName, req.Apps, req.DryRun)
|
||||
if err != nil {
|
||||
return batchAssociateAppStoreAppsResponse{Err: err}, nil
|
||||
}
|
||||
return batchAssociateAppStoreAppsResponse{}, nil
|
||||
return batchAssociateAppStoreAppsResponse{Apps: apps}, nil
|
||||
}
|
||||
|
||||
func (svc *Service) BatchAssociateVPPApps(ctx context.Context, teamName string, payloads []fleet.VPPBatchPayload, dryRun bool) error {
|
||||
func (svc *Service) BatchAssociateVPPApps(ctx context.Context, teamName string, payloads []fleet.VPPBatchPayload, dryRun bool) ([]fleet.VPPAppResponse, error) {
|
||||
// skipauth: No authorization check needed due to implementation returning
|
||||
// only license error.
|
||||
svc.authz.SkipAuthorization(ctx)
|
||||
|
||||
return fleet.ErrMissingLicense
|
||||
return nil, fleet.ErrMissingLicense
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue