mirror of
https://github.com/fleetdm/fleet
synced 2026-04-21 13:37:30 +00:00
Fix fleetctl generate-gitops failing to include VPP fleet assignments (#42429)
This commit is contained in:
parent
4e7c6f33a7
commit
26d0dccc8e
5 changed files with 186 additions and 14 deletions
1
changes/33106-fix-generate-gitops-vpp
Normal file
1
changes/33106-fix-generate-gitops-vpp
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Fixed `fleetctl generate-gitops` failing to include VPP fleet assignments.
|
||||
|
|
@ -89,6 +89,7 @@ type generateGitopsClient interface {
|
|||
GetCertificateAuthoritiesSpec(includeSecrets bool) (*fleet.GroupedCertificateAuthorities, error)
|
||||
GetCertificateTemplates(teamID string) ([]*fleet.CertificateTemplateResponseSummary, error)
|
||||
GetFleetMaintainedApp(id uint) (*fleet.MaintainedApp, error)
|
||||
GetVPPTokens() ([]*fleet.VPPTokenDB, error)
|
||||
ListFleetMaintainedApps(teamID uint) ([]fleet.MaintainedApp, error)
|
||||
}
|
||||
|
||||
|
|
@ -1107,7 +1108,23 @@ func (cmd *GenerateGitopsCommand) generateMDM(mdm *fleet.MDM) (map[string]interf
|
|||
}
|
||||
if cmd.AppConfig.License.IsPremium() {
|
||||
result[jsonFieldName(t, "AppleBusinessManager")] = mdm.AppleBusinessManager
|
||||
result[jsonFieldName(t, "VolumePurchasingProgram")] = mdm.VolumePurchasingProgram
|
||||
vppTokens, err := cmd.Client.GetVPPTokens()
|
||||
if err != nil {
|
||||
fmt.Fprintf(cmd.CLI.App.ErrWriter, "Error fetching VPP tokens: %s\n", err)
|
||||
return nil, err
|
||||
}
|
||||
var vppConfig []fleet.MDMAppleVolumePurchasingProgramInfo
|
||||
for _, token := range vppTokens {
|
||||
teamNames := make([]string, 0, len(token.Teams))
|
||||
for _, team := range token.Teams {
|
||||
teamNames = append(teamNames, team.Name)
|
||||
}
|
||||
vppConfig = append(vppConfig, fleet.MDMAppleVolumePurchasingProgramInfo{
|
||||
Location: token.Location,
|
||||
Teams: teamNames,
|
||||
})
|
||||
}
|
||||
result[jsonFieldName(t, "VolumePurchasingProgram")] = vppConfig
|
||||
|
||||
var eulaPath string
|
||||
if cmd.AppConfig.MDM.EnabledAndConfigured {
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ type MockClient struct {
|
|||
IsFree bool
|
||||
TeamNameOverride string
|
||||
WithoutMDM bool
|
||||
WithoutVPP bool
|
||||
}
|
||||
|
||||
func (c *MockClient) GetAppConfig() (*fleet.EnrichedAppConfig, error) {
|
||||
|
|
@ -822,6 +823,24 @@ func (MockClient) GetCertificateTemplates(teamID string) ([]*fleet.CertificateTe
|
|||
return res, nil
|
||||
}
|
||||
|
||||
func (c *MockClient) GetVPPTokens() ([]*fleet.VPPTokenDB, error) {
|
||||
if c.WithoutVPP {
|
||||
return nil, nil
|
||||
}
|
||||
return []*fleet.VPPTokenDB{
|
||||
{
|
||||
ID: 1,
|
||||
Location: "Fleet Device Management Inc.",
|
||||
Teams: []fleet.TeamTuple{
|
||||
{ID: 1, Name: "💻 Workstations"},
|
||||
{ID: 2, Name: "💻🐣 Workstations (canary)"},
|
||||
{ID: 3, Name: "📱🏢 Company-owned mobile devices"},
|
||||
{ID: 4, Name: "📱🔐 Personal mobile devices"},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func maskSecret(value string, shouldShowSecret bool) string {
|
||||
if shouldShowSecret {
|
||||
return value
|
||||
|
|
@ -1963,7 +1982,7 @@ func verifyControlsHasMacosSetup(t *testing.T, controlsRaw map[string]interface{
|
|||
|
||||
func TestGenerateControlsAndMDMWithoutMDMEnabledAndConfigured(t *testing.T) {
|
||||
// Get the test app config.
|
||||
fleetClient := &MockClient{}
|
||||
fleetClient := &MockClient{WithoutVPP: true}
|
||||
appConfig, err := fleetClient.GetAppConfig()
|
||||
require.NoError(t, err)
|
||||
appConfig.MDM.EnabledAndConfigured = false
|
||||
|
|
@ -1971,7 +1990,6 @@ func TestGenerateControlsAndMDMWithoutMDMEnabledAndConfigured(t *testing.T) {
|
|||
appConfig.MDM.AppleBusinessManager = optjson.Slice[fleet.MDMAppleABMAssignmentInfo]{}
|
||||
appConfig.MDM.AppleServerURL = ""
|
||||
appConfig.MDM.EndUserAuthentication = fleet.MDMEndUserAuthentication{}
|
||||
appConfig.MDM.VolumePurchasingProgram = optjson.Slice[fleet.MDMAppleVolumePurchasingProgramInfo]{}
|
||||
|
||||
// Create the command.
|
||||
cmd := &GenerateGitopsCommand{
|
||||
|
|
@ -2006,6 +2024,144 @@ func TestGenerateControlsAndMDMWithoutMDMEnabledAndConfigured(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGenerateMDMVPPTokens(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
vppTokens []*fleet.VPPTokenDB
|
||||
expected []fleet.MDMAppleVolumePurchasingProgramInfo
|
||||
}{
|
||||
{
|
||||
name: "no VPP tokens",
|
||||
vppTokens: nil,
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "single token with teams",
|
||||
vppTokens: []*fleet.VPPTokenDB{
|
||||
{
|
||||
ID: 1,
|
||||
Location: "Acme Inc.",
|
||||
Teams: []fleet.TeamTuple{
|
||||
{ID: 1, Name: "Workstations"},
|
||||
{ID: 2, Name: "Servers"},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []fleet.MDMAppleVolumePurchasingProgramInfo{
|
||||
{Location: "Acme Inc.", Teams: []string{"Workstations", "Servers"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "multiple tokens with different teams",
|
||||
vppTokens: []*fleet.VPPTokenDB{
|
||||
{
|
||||
ID: 1,
|
||||
Location: "Acme Inc.",
|
||||
Teams: []fleet.TeamTuple{
|
||||
{ID: 1, Name: "Workstations"},
|
||||
},
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
Location: "Widgets LLC",
|
||||
Teams: []fleet.TeamTuple{
|
||||
{ID: 2, Name: "Servers"},
|
||||
{ID: 0, Name: fleet.TeamNameNoTeam},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []fleet.MDMAppleVolumePurchasingProgramInfo{
|
||||
{Location: "Acme Inc.", Teams: []string{"Workstations"}},
|
||||
{Location: "Widgets LLC", Teams: []string{"Servers", fleet.TeamNameNoTeam}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "token assigned to all teams (empty teams slice)",
|
||||
vppTokens: []*fleet.VPPTokenDB{
|
||||
{
|
||||
ID: 1,
|
||||
Location: "Acme Inc.",
|
||||
Teams: []fleet.TeamTuple{},
|
||||
},
|
||||
},
|
||||
expected: []fleet.MDMAppleVolumePurchasingProgramInfo{
|
||||
{Location: "Acme Inc.", Teams: []string{}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
fleetClient := &MockClient{WithoutVPP: true}
|
||||
appConfig, err := fleetClient.GetAppConfig()
|
||||
require.NoError(t, err)
|
||||
|
||||
// Use a wrapper to override the VPP tokens for this test case.
|
||||
wrapper := &vppMockClientWrapper{
|
||||
MockClient: fleetClient,
|
||||
vppTokens: tt.vppTokens,
|
||||
}
|
||||
|
||||
cmd := &GenerateGitopsCommand{
|
||||
Client: wrapper,
|
||||
CLI: cli.NewContext(cli.NewApp(), nil, nil),
|
||||
Messages: Messages{},
|
||||
FilesToWrite: make(map[string]any),
|
||||
AppConfig: appConfig,
|
||||
ScriptList: make(map[uint]string),
|
||||
}
|
||||
|
||||
mdmRaw, err := cmd.generateMDM(&appConfig.MDM)
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, mdmRaw, "volume_purchasing_program")
|
||||
|
||||
vppRaw := mdmRaw["volume_purchasing_program"]
|
||||
if tt.expected == nil {
|
||||
require.Nil(t, vppRaw)
|
||||
} else {
|
||||
vppResult, ok := vppRaw.([]fleet.MDMAppleVolumePurchasingProgramInfo)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, tt.expected, vppResult)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("GetVPPTokens error", func(t *testing.T) {
|
||||
fleetClient := &MockClient{WithoutVPP: true}
|
||||
appConfig, err := fleetClient.GetAppConfig()
|
||||
require.NoError(t, err)
|
||||
|
||||
wrapper := &vppMockClientWrapper{
|
||||
MockClient: fleetClient,
|
||||
vppErr: errors.New("vpp tokens unavailable"),
|
||||
}
|
||||
|
||||
cmd := &GenerateGitopsCommand{
|
||||
Client: wrapper,
|
||||
CLI: cli.NewContext(cli.NewApp(), nil, nil),
|
||||
Messages: Messages{},
|
||||
FilesToWrite: make(map[string]any),
|
||||
AppConfig: appConfig,
|
||||
ScriptList: make(map[uint]string),
|
||||
}
|
||||
|
||||
mdmRaw, err := cmd.generateMDM(&appConfig.MDM)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, mdmRaw)
|
||||
})
|
||||
}
|
||||
|
||||
// vppMockClientWrapper wraps MockClient but overrides GetVPPTokens.
|
||||
type vppMockClientWrapper struct {
|
||||
*MockClient
|
||||
vppTokens []*fleet.VPPTokenDB
|
||||
vppErr error
|
||||
}
|
||||
|
||||
func (w *vppMockClientWrapper) GetVPPTokens() ([]*fleet.VPPTokenDB, error) {
|
||||
return w.vppTokens, w.vppErr
|
||||
}
|
||||
|
||||
func TestSillyTeamNames(t *testing.T) {
|
||||
configureFMAManifestServer(t)
|
||||
sillyTeamNames := map[string]string{
|
||||
|
|
|
|||
|
|
@ -267,17 +267,7 @@
|
|||
"windows_settings": {
|
||||
"custom_settings": []
|
||||
},
|
||||
"volume_purchasing_program": [
|
||||
{
|
||||
"location": "Fleet Device Management Inc.",
|
||||
"teams": [
|
||||
"💻 Workstations",
|
||||
"💻🐣 Workstations (canary)",
|
||||
"📱🏢 Company-owned mobile devices",
|
||||
"📱🔐 Personal mobile devices"
|
||||
]
|
||||
}
|
||||
],
|
||||
"volume_purchasing_program": null,
|
||||
"android_enabled_and_configured": true,
|
||||
"android_settings": {
|
||||
"custom_settings": []
|
||||
|
|
|
|||
|
|
@ -40,6 +40,14 @@ func (c *Client) GetAppleBM() (*fleet.AppleBM, error) {
|
|||
return responseBody.AppleBM, err
|
||||
}
|
||||
|
||||
// GetVPPTokens retrieves the List Volume Purchasing Program (VPP) tokens
|
||||
func (c *Client) GetVPPTokens() ([]*fleet.VPPTokenDB, error) {
|
||||
verb, path := "GET", "/api/latest/fleet/vpp_tokens"
|
||||
var responseBody getVPPTokensResponse
|
||||
err := c.authenticatedRequestWithQuery(nil, verb, path, &responseBody, "")
|
||||
return responseBody.Tokens, err
|
||||
}
|
||||
|
||||
func (c *Client) CountABMTokens() (int, error) {
|
||||
verb, path := "GET", "/api/latest/fleet/abm_tokens/count"
|
||||
var responseBody countABMTokensResponse
|
||||
|
|
|
|||
Loading…
Reference in a new issue