Add integration tests for the setup experience flow with automatic and forced release (#22909)
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -12,7 +12,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
@@ -8596,14 +8595,14 @@ func (s *integrationEnterpriseTestSuite) TestAllSoftwareTitles() {
|
||||
SelfService: false,
|
||||
TeamID: &team1.ID,
|
||||
}
|
||||
s.uploadSoftwareInstaller(payloadRubyTm1, http.StatusOK, "")
|
||||
s.uploadSoftwareInstaller(t, payloadRubyTm1, http.StatusOK, "")
|
||||
|
||||
payloadEmacs := &fleet.UploadSoftwareInstallerPayload{
|
||||
InstallScript: "install",
|
||||
Filename: "emacs.deb",
|
||||
SelfService: true,
|
||||
}
|
||||
s.uploadSoftwareInstaller(payloadEmacs, http.StatusOK, "")
|
||||
s.uploadSoftwareInstaller(t, payloadEmacs, http.StatusOK, "")
|
||||
|
||||
payloadVim := &fleet.UploadSoftwareInstallerPayload{
|
||||
InstallScript: "install",
|
||||
@@ -8611,7 +8610,7 @@ func (s *integrationEnterpriseTestSuite) TestAllSoftwareTitles() {
|
||||
SelfService: true,
|
||||
TeamID: ptr.Uint(0),
|
||||
}
|
||||
s.uploadSoftwareInstaller(payloadVim, http.StatusOK, "")
|
||||
s.uploadSoftwareInstaller(t, payloadVim, http.StatusOK, "")
|
||||
|
||||
resp = listSoftwareTitlesResponse{}
|
||||
s.DoJSON(
|
||||
@@ -8631,7 +8630,7 @@ func (s *integrationEnterpriseTestSuite) TestAllSoftwareTitles() {
|
||||
Filename: "ruby_arm64.deb",
|
||||
TeamID: &team2.ID,
|
||||
}
|
||||
s.uploadSoftwareInstaller(payloadRubyTm2, http.StatusOK, "")
|
||||
s.uploadSoftwareInstaller(t, payloadRubyTm2, http.StatusOK, "")
|
||||
|
||||
// We should only see the one we uploaded to team 1
|
||||
resp = listSoftwareTitlesResponse{}
|
||||
@@ -10056,7 +10055,7 @@ func (s *integrationEnterpriseTestSuite) TestListHostSoftware() {
|
||||
Filename: "ruby.deb",
|
||||
Version: "1:2.5.1",
|
||||
}
|
||||
s.uploadSoftwareInstaller(payload, http.StatusOK, "")
|
||||
s.uploadSoftwareInstaller(t, payload, http.StatusOK, "")
|
||||
titleID := getSoftwareTitleID(t, s.ds, "ruby", "deb_packages")
|
||||
|
||||
// update it to be self-service
|
||||
@@ -10198,7 +10197,7 @@ func (s *integrationEnterpriseTestSuite) TestListHostSoftware() {
|
||||
Filename: "dummy_installer.pkg",
|
||||
Version: "0.0.2", // The version can be anything -- we match on title
|
||||
}
|
||||
s.uploadSoftwareInstaller(payload, http.StatusOK, "")
|
||||
s.uploadSoftwareInstaller(t, payload, http.StatusOK, "")
|
||||
|
||||
// Get software available for install
|
||||
getHostSw = getHostSoftwareResponse{}
|
||||
@@ -10320,7 +10319,7 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerUploadDownloadAndD
|
||||
Platform: "linux",
|
||||
}
|
||||
|
||||
s.uploadSoftwareInstaller(payload, http.StatusOK, "")
|
||||
s.uploadSoftwareInstaller(t, payload, http.StatusOK, "")
|
||||
|
||||
// check activity
|
||||
s.lastActivityOfTypeMatches(fleet.ActivityTypeAddedSoftware{}.ActivityName(), `{"software_title": "ruby", "software_package": "ruby.deb", "team_name": null, "team_id": null, "self_service": false}`, 0)
|
||||
@@ -10329,7 +10328,7 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerUploadDownloadAndD
|
||||
_, titleID := checkSoftwareInstaller(t, payload)
|
||||
|
||||
// upload again fails
|
||||
s.uploadSoftwareInstaller(payload, http.StatusConflict, "already exists")
|
||||
s.uploadSoftwareInstaller(t, payload, http.StatusConflict, "already exists")
|
||||
|
||||
// orbit-downloading fails with invalid orbit node key
|
||||
s.Do("POST", "/api/fleet/orbit/software_install/package?alt=media", orbitDownloadSoftwareInstallerRequest{
|
||||
@@ -10368,7 +10367,7 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerUploadDownloadAndD
|
||||
Platform: "linux",
|
||||
SelfService: true,
|
||||
}
|
||||
s.uploadSoftwareInstaller(payload, http.StatusOK, "")
|
||||
s.uploadSoftwareInstaller(t, payload, http.StatusOK, "")
|
||||
|
||||
// check the software installer
|
||||
installerID, titleID := checkSoftwareInstaller(t, payload)
|
||||
@@ -10377,7 +10376,7 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerUploadDownloadAndD
|
||||
s.lastActivityOfTypeMatches(fleet.ActivityTypeAddedSoftware{}.ActivityName(), fmt.Sprintf(`{"software_title": "ruby", "software_package": "ruby.deb", "team_name": "%s", "team_id": %d, "self_service": true}`, createTeamResp.Team.Name, createTeamResp.Team.ID), 0)
|
||||
|
||||
// upload again fails
|
||||
s.uploadSoftwareInstaller(payload, http.StatusConflict, "already exists")
|
||||
s.uploadSoftwareInstaller(t, payload, http.StatusConflict, "already exists")
|
||||
|
||||
// download the installer
|
||||
r := s.Do("GET", fmt.Sprintf("/api/latest/fleet/software/titles/%d/package?alt=media", titleID), nil, http.StatusOK, "team_id", fmt.Sprintf("%d", *payload.TeamID))
|
||||
@@ -10483,7 +10482,7 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerUploadDownloadAndD
|
||||
Platform: "linux",
|
||||
SelfService: true,
|
||||
}
|
||||
s.uploadSoftwareInstaller(payload, http.StatusOK, "")
|
||||
s.uploadSoftwareInstaller(t, payload, http.StatusOK, "")
|
||||
|
||||
// check the software installer
|
||||
installerID, titleID := checkSoftwareInstaller(t, payload)
|
||||
@@ -10492,7 +10491,7 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerUploadDownloadAndD
|
||||
s.lastActivityOfTypeMatches(fleet.ActivityTypeAddedSoftware{}.ActivityName(), fmt.Sprintf(`{"software_title": "ruby", "software_package": "ruby.deb", "team_name": null, "team_id": 0, "self_service": true}`), 0)
|
||||
|
||||
// upload again fails
|
||||
s.uploadSoftwareInstaller(payload, http.StatusConflict, "already exists")
|
||||
s.uploadSoftwareInstaller(t, payload, http.StatusConflict, "already exists")
|
||||
|
||||
// download the installer
|
||||
r := s.Do("GET", fmt.Sprintf("/api/latest/fleet/software/titles/%d/package?alt=media", titleID), nil, http.StatusOK, "team_id", fmt.Sprintf("%d", 0))
|
||||
@@ -10578,7 +10577,7 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerUploadDownloadAndD
|
||||
StorageID: "df06d9ce9e2090d9cb2e8cd1f4d7754a803dc452bf93e3204e3acd3b95508628",
|
||||
Platform: "linux",
|
||||
}
|
||||
s.uploadSoftwareInstaller(payload, http.StatusOK, "")
|
||||
s.uploadSoftwareInstaller(t, payload, http.StatusOK, "")
|
||||
|
||||
logger := kitlog.NewLogfmtLogger(os.Stderr)
|
||||
|
||||
@@ -11574,7 +11573,7 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerHostRequests() {
|
||||
Title: "ruby",
|
||||
TeamID: teamID,
|
||||
}
|
||||
s.uploadSoftwareInstaller(payload, http.StatusOK, "")
|
||||
s.uploadSoftwareInstaller(t, payload, http.StatusOK, "")
|
||||
titleID := getSoftwareTitleID(t, s.ds, payload.Title, "deb_packages")
|
||||
|
||||
// Get title with software installer
|
||||
@@ -11591,7 +11590,7 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallerHostRequests() {
|
||||
Title: "DummyApp.app",
|
||||
TeamID: teamID,
|
||||
}
|
||||
s.uploadSoftwareInstaller(payloadDummy, http.StatusOK, "")
|
||||
s.uploadSoftwareInstaller(t, payloadDummy, http.StatusOK, "")
|
||||
pkgTitleID := getSoftwareTitleID(t, s.ds, payloadDummy.Title, "apps")
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/software/titles/%d", pkgTitleID), nil, http.StatusOK, &respTitle, "team_id",
|
||||
fmt.Sprintf("%d", *teamID))
|
||||
@@ -11941,7 +11940,7 @@ func (s *integrationEnterpriseTestSuite) TestSelfServiceSoftwareInstall() {
|
||||
Title: "ruby",
|
||||
SelfService: false,
|
||||
}
|
||||
s.uploadSoftwareInstaller(payloadNoSS, http.StatusOK, "")
|
||||
s.uploadSoftwareInstaller(t, payloadNoSS, http.StatusOK, "")
|
||||
titleIDNoSS := getSoftwareTitleID(t, s.ds, payloadNoSS.Title, "deb_packages")
|
||||
|
||||
payloadSS := &fleet.UploadSoftwareInstallerPayload{
|
||||
@@ -11952,7 +11951,7 @@ func (s *integrationEnterpriseTestSuite) TestSelfServiceSoftwareInstall() {
|
||||
Title: "emacs",
|
||||
SelfService: true,
|
||||
}
|
||||
s.uploadSoftwareInstaller(payloadSS, http.StatusOK, "")
|
||||
s.uploadSoftwareInstaller(t, payloadSS, http.StatusOK, "")
|
||||
titleIDSS := getSoftwareTitleID(t, s.ds, payloadSS.Title, "deb_packages")
|
||||
|
||||
// cannot self-install if software installer does not allow it
|
||||
@@ -12022,7 +12021,7 @@ func (s *integrationEnterpriseTestSuite) TestHostSoftwareInstallResult() {
|
||||
Filename: "ruby.deb",
|
||||
Title: "ruby",
|
||||
}
|
||||
s.uploadSoftwareInstaller(payload, http.StatusOK, "")
|
||||
s.uploadSoftwareInstaller(t, payload, http.StatusOK, "")
|
||||
titleID := getSoftwareTitleID(t, s.ds, payload.Title, "deb_packages")
|
||||
payload2 := &fleet.UploadSoftwareInstallerPayload{
|
||||
InstallScript: "install script 2",
|
||||
@@ -12031,7 +12030,7 @@ func (s *integrationEnterpriseTestSuite) TestHostSoftwareInstallResult() {
|
||||
Filename: "vim.deb",
|
||||
Title: "vim",
|
||||
}
|
||||
s.uploadSoftwareInstaller(payload2, http.StatusOK, "")
|
||||
s.uploadSoftwareInstaller(t, payload2, http.StatusOK, "")
|
||||
titleID2 := getSoftwareTitleID(t, s.ds, payload2.Title, "deb_packages")
|
||||
payload3 := &fleet.UploadSoftwareInstallerPayload{
|
||||
InstallScript: "install script 3",
|
||||
@@ -12040,7 +12039,7 @@ func (s *integrationEnterpriseTestSuite) TestHostSoftwareInstallResult() {
|
||||
Filename: "emacs.deb",
|
||||
Title: "emacs",
|
||||
}
|
||||
s.uploadSoftwareInstaller(payload3, http.StatusOK, "")
|
||||
s.uploadSoftwareInstaller(t, payload3, http.StatusOK, "")
|
||||
titleID3 := getSoftwareTitleID(t, s.ds, payload3.Title, "deb_packages")
|
||||
|
||||
latestInstallUUID := func() string {
|
||||
@@ -12276,64 +12275,6 @@ func (s *integrationEnterpriseTestSuite) TestHostScriptSoftDelete() {
|
||||
require.EqualValues(t, 0, *scriptRes.ExitCode)
|
||||
}
|
||||
|
||||
func (s *integrationEnterpriseTestSuite) uploadSoftwareInstaller(
|
||||
payload *fleet.UploadSoftwareInstallerPayload,
|
||||
expectedStatus int,
|
||||
expectedError string,
|
||||
) {
|
||||
t := s.T()
|
||||
t.Helper()
|
||||
openFile := func(name string) *os.File {
|
||||
f, err := os.Open(filepath.Join("testdata", "software-installers", name))
|
||||
require.NoError(t, err)
|
||||
return f
|
||||
}
|
||||
|
||||
f := openFile(payload.Filename)
|
||||
defer f.Close()
|
||||
|
||||
payload.InstallerFile = f
|
||||
|
||||
var b bytes.Buffer
|
||||
w := multipart.NewWriter(&b)
|
||||
|
||||
// add the software field
|
||||
fw, err := w.CreateFormFile("software", payload.Filename)
|
||||
require.NoError(t, err)
|
||||
n, err := io.Copy(fw, payload.InstallerFile)
|
||||
require.NoError(t, err)
|
||||
require.NotZero(t, n)
|
||||
|
||||
// add the team_id field
|
||||
if payload.TeamID != nil {
|
||||
require.NoError(t, w.WriteField("team_id", fmt.Sprintf("%d", *payload.TeamID)))
|
||||
}
|
||||
// add the remaining fields
|
||||
require.NoError(t, w.WriteField("install_script", payload.InstallScript))
|
||||
require.NoError(t, w.WriteField("pre_install_query", payload.PreInstallQuery))
|
||||
require.NoError(t, w.WriteField("post_install_script", payload.PostInstallScript))
|
||||
require.NoError(t, w.WriteField("uninstall_script", payload.UninstallScript))
|
||||
if payload.SelfService {
|
||||
require.NoError(t, w.WriteField("self_service", "true"))
|
||||
}
|
||||
|
||||
w.Close()
|
||||
|
||||
headers := map[string]string{
|
||||
"Content-Type": w.FormDataContentType(),
|
||||
"Accept": "application/json",
|
||||
"Authorization": fmt.Sprintf("Bearer %s", s.token),
|
||||
}
|
||||
|
||||
r := s.DoRawWithHeaders("POST", "/api/latest/fleet/software/package", b.Bytes(), expectedStatus, headers)
|
||||
defer r.Body.Close()
|
||||
|
||||
if expectedError != "" {
|
||||
errMsg := extractServerErrorText(r.Body)
|
||||
require.Contains(t, errMsg, expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
func getSoftwareTitleID(t *testing.T, ds *mysql.Datastore, title, source string) uint {
|
||||
var id uint
|
||||
mysql.ExecAdhocSQL(t, ds, func(q sqlx.ExtContext) error {
|
||||
@@ -12559,7 +12500,7 @@ func (s *integrationEnterpriseTestSuite) TestPKGNewSoftwareTitleFlow() {
|
||||
Filename: "dummy_installer.pkg",
|
||||
TeamID: &team.ID,
|
||||
}
|
||||
s.uploadSoftwareInstaller(payload, http.StatusOK, "")
|
||||
s.uploadSoftwareInstaller(t, payload, http.StatusOK, "")
|
||||
|
||||
resp := listSoftwareTitlesResponse{}
|
||||
s.DoJSON(
|
||||
@@ -12663,7 +12604,7 @@ func (s *integrationEnterpriseTestSuite) TestPKGNoVersion() {
|
||||
Filename: "no_version.pkg",
|
||||
TeamID: &team.ID,
|
||||
}
|
||||
s.uploadSoftwareInstaller(payload, http.StatusBadRequest, "Couldn't add. Fleet couldn't read the version from no_version.pkg.")
|
||||
s.uploadSoftwareInstaller(t, payload, http.StatusBadRequest, "Couldn't add. Fleet couldn't read the version from no_version.pkg.")
|
||||
}
|
||||
|
||||
// 1. host reports software
|
||||
@@ -12731,7 +12672,7 @@ func (s *integrationEnterpriseTestSuite) TestPKGSoftwareAlreadyReported() {
|
||||
Filename: "dummy_installer.pkg",
|
||||
TeamID: &team.ID,
|
||||
}
|
||||
s.uploadSoftwareInstaller(payload, http.StatusOK, "")
|
||||
s.uploadSoftwareInstaller(t, payload, http.StatusOK, "")
|
||||
|
||||
resp = listSoftwareTitlesResponse{}
|
||||
s.DoJSON(
|
||||
@@ -12795,7 +12736,7 @@ func (s *integrationEnterpriseTestSuite) TestPKGSoftwareReconciliation() {
|
||||
Filename: "dummy_installer.pkg",
|
||||
TeamID: &team.ID,
|
||||
}
|
||||
s.uploadSoftwareInstaller(payload, http.StatusOK, "")
|
||||
s.uploadSoftwareInstaller(t, payload, http.StatusOK, "")
|
||||
|
||||
resp := listSoftwareTitlesResponse{}
|
||||
s.DoJSON(
|
||||
@@ -13704,7 +13645,7 @@ func (s *integrationEnterpriseTestSuite) TestPolicyAutomationsSoftwareInstallers
|
||||
Filename: "dummy_installer.pkg",
|
||||
TeamID: &team1.ID,
|
||||
}
|
||||
s.uploadSoftwareInstaller(pkgPayload, http.StatusOK, "")
|
||||
s.uploadSoftwareInstaller(t, pkgPayload, http.StatusOK, "")
|
||||
// Get software title ID of the uploaded installer.
|
||||
resp := listSoftwareTitlesResponse{}
|
||||
s.DoJSON(
|
||||
@@ -13767,7 +13708,7 @@ func (s *integrationEnterpriseTestSuite) TestPolicyAutomationsSoftwareInstallers
|
||||
s.token = adminToken
|
||||
})
|
||||
s.token = adminTeam1Session.Key
|
||||
s.uploadSoftwareInstaller(rubyPayload, http.StatusOK, "")
|
||||
s.uploadSoftwareInstaller(t, rubyPayload, http.StatusOK, "")
|
||||
s.token = adminToken
|
||||
err = s.ds.DeleteUser(ctx, adminTeam1.ID)
|
||||
require.NoError(t, err)
|
||||
@@ -13812,7 +13753,7 @@ func (s *integrationEnterpriseTestSuite) TestPolicyAutomationsSoftwareInstallers
|
||||
// author (the admin that uploaded the installer).
|
||||
SelfService: true,
|
||||
}
|
||||
s.uploadSoftwareInstaller(fleetOsqueryPayload, http.StatusOK, "")
|
||||
s.uploadSoftwareInstaller(t, fleetOsqueryPayload, http.StatusOK, "")
|
||||
// Get software title ID of the uploaded installer.
|
||||
resp = listSoftwareTitlesResponse{}
|
||||
s.DoJSON(
|
||||
@@ -14829,7 +14770,7 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareInstallersWithoutBundleIden
|
||||
Filename: "dummy_installer.pkg",
|
||||
Version: "0.0.2",
|
||||
}
|
||||
s.uploadSoftwareInstaller(payload, http.StatusOK, "")
|
||||
s.uploadSoftwareInstaller(t, payload, http.StatusOK, "")
|
||||
}
|
||||
|
||||
func (s *integrationEnterpriseTestSuite) TestSoftwareUploadRPM() {
|
||||
@@ -14847,7 +14788,7 @@ func (s *integrationEnterpriseTestSuite) TestSoftwareUploadRPM() {
|
||||
Filename: "ruby.rpm",
|
||||
Title: "ruby",
|
||||
}
|
||||
s.uploadSoftwareInstaller(payload, http.StatusOK, "")
|
||||
s.uploadSoftwareInstaller(t, payload, http.StatusOK, "")
|
||||
titleID := getSoftwareTitleID(t, s.ds, payload.Title, "rpm_packages")
|
||||
|
||||
latestInstallUUID := func() string {
|
||||
|
||||
@@ -1301,3 +1301,440 @@ func (s *integrationMDMTestSuite) TestSetupExperienceScript() {
|
||||
// try deleting the team script again
|
||||
s.Do("DELETE", fmt.Sprintf("/api/latest/fleet/setup_experience/script?team_id=%d", tm.ID), nil, http.StatusOK) // TODO: confirm if we want to return not found
|
||||
}
|
||||
|
||||
func (s *integrationMDMTestSuite) createTeamDeviceForSetupExperienceWithProfileSoftwareAndScript() (device godep.Device, host *fleet.Host, tm *fleet.Team) {
|
||||
t := s.T()
|
||||
ctx := context.Background()
|
||||
|
||||
// enroll a device in a team with software to install and a script to execute
|
||||
s.enableABM("fleet-setup-experience")
|
||||
tm, err := s.ds.NewTeam(ctx, &fleet.Team{Name: "team 1"})
|
||||
require.NoError(t, err)
|
||||
|
||||
teamDevice := godep.Device{SerialNumber: uuid.New().String(), Model: "MacBook Pro", OS: "osx", OpType: "added"}
|
||||
|
||||
// add a team profile
|
||||
teamProfile := mobileconfigForTest("N1", "I1")
|
||||
s.Do("POST", "/api/v1/fleet/mdm/apple/profiles/batch", batchSetMDMAppleProfilesRequest{Profiles: [][]byte{teamProfile}}, http.StatusNoContent, "team_id", fmt.Sprint(tm.ID))
|
||||
|
||||
// add a macOS software to install
|
||||
payloadDummy := &fleet.UploadSoftwareInstallerPayload{
|
||||
InstallScript: "install",
|
||||
Filename: "dummy_installer.pkg",
|
||||
Title: "DummyApp.app",
|
||||
TeamID: &tm.ID,
|
||||
}
|
||||
s.uploadSoftwareInstaller(t, payloadDummy, http.StatusOK, "")
|
||||
titleID := getSoftwareTitleID(t, s.ds, payloadDummy.Title, "apps")
|
||||
var swInstallResp putSetupExperienceSoftwareResponse
|
||||
s.DoJSON("PUT", "/api/v1/fleet/setup_experience/software", putSetupExperienceSoftwareRequest{TeamID: tm.ID, TitleIDs: []uint{titleID}}, http.StatusOK, &swInstallResp)
|
||||
|
||||
// add a script to execute
|
||||
body, headers := generateNewScriptMultipartRequest(t,
|
||||
"script.sh", []byte(`echo "hello"`), s.token, map[string][]string{"team_id": {fmt.Sprintf("%d", tm.ID)}})
|
||||
s.DoRawWithHeaders("POST", "/api/latest/fleet/setup_experience/script", body.Bytes(), http.StatusOK, headers)
|
||||
|
||||
// no bootstrap package, no custom setup assistant (those are already tested
|
||||
// in the DEPEnrollReleaseDevice tests).
|
||||
|
||||
s.pushProvider.PushFunc = func(pushes []*mdm.Push) (map[string]*push.Response, error) {
|
||||
return map[string]*push.Response{}, nil
|
||||
}
|
||||
|
||||
s.mockDEPResponse("fleet-setup-experience", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
encoder := json.NewEncoder(w)
|
||||
switch r.URL.Path {
|
||||
case "/session":
|
||||
err := encoder.Encode(map[string]string{"auth_session_token": "xyz"})
|
||||
require.NoError(t, err)
|
||||
case "/profile":
|
||||
err := encoder.Encode(godep.ProfileResponse{ProfileUUID: uuid.New().String()})
|
||||
require.NoError(t, err)
|
||||
case "/server/devices":
|
||||
err := encoder.Encode(godep.DeviceResponse{Devices: []godep.Device{teamDevice}})
|
||||
require.NoError(t, err)
|
||||
case "/devices/sync":
|
||||
// This endpoint is polled over time to sync devices from
|
||||
// ABM, send a repeated serial
|
||||
err := encoder.Encode(godep.DeviceResponse{Devices: []godep.Device{teamDevice}, Cursor: "foo"})
|
||||
require.NoError(t, err)
|
||||
case "/profile/devices":
|
||||
b, err := io.ReadAll(r.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
var prof profileAssignmentReq
|
||||
require.NoError(t, json.Unmarshal(b, &prof))
|
||||
|
||||
var resp godep.ProfileResponse
|
||||
resp.ProfileUUID = prof.ProfileUUID
|
||||
resp.Devices = make(map[string]string, len(prof.Devices))
|
||||
for _, device := range prof.Devices {
|
||||
resp.Devices[device] = string(fleet.DEPAssignProfileResponseSuccess)
|
||||
}
|
||||
err = encoder.Encode(resp)
|
||||
require.NoError(t, err)
|
||||
default:
|
||||
_, _ = w.Write([]byte(`{}`))
|
||||
}
|
||||
}))
|
||||
|
||||
// trigger a profile sync
|
||||
s.runDEPSchedule()
|
||||
|
||||
// the (ghost) host now exists
|
||||
listHostsRes := listHostsResponse{}
|
||||
s.DoJSON("GET", "/api/latest/fleet/hosts", nil, http.StatusOK, &listHostsRes)
|
||||
require.Len(t, listHostsRes.Hosts, 1)
|
||||
require.Equal(t, listHostsRes.Hosts[0].HardwareSerial, teamDevice.SerialNumber)
|
||||
enrolledHost := listHostsRes.Hosts[0].Host
|
||||
|
||||
// transfer it to the team
|
||||
s.Do("POST", "/api/v1/fleet/hosts/transfer",
|
||||
addHostsToTeamRequest{TeamID: &tm.ID, HostIDs: []uint{enrolledHost.ID}}, http.StatusOK)
|
||||
|
||||
return teamDevice, enrolledHost, tm
|
||||
}
|
||||
|
||||
func (s *integrationMDMTestSuite) TestSetupExperienceFlowWithSoftwareAndScriptAutoRelease() {
|
||||
t := s.T()
|
||||
ctx := context.Background()
|
||||
|
||||
teamDevice, enrolledHost, _ := s.createTeamDeviceForSetupExperienceWithProfileSoftwareAndScript()
|
||||
|
||||
// enroll the host
|
||||
depURLToken := loadEnrollmentProfileDEPToken(t, s.ds)
|
||||
mdmDevice := mdmtest.NewTestMDMClientAppleDEP(s.server.URL, depURLToken)
|
||||
mdmDevice.SerialNumber = teamDevice.SerialNumber
|
||||
err := mdmDevice.Enroll()
|
||||
require.NoError(t, err)
|
||||
|
||||
// run the worker to process the DEP enroll request
|
||||
s.runWorker()
|
||||
// run the worker to assign configuration profiles
|
||||
s.awaitTriggerProfileSchedule(t)
|
||||
|
||||
var cmds []*micromdm.CommandPayload
|
||||
cmd, err := mdmDevice.Idle()
|
||||
require.NoError(t, err)
|
||||
for cmd != nil {
|
||||
|
||||
var fullCmd micromdm.CommandPayload
|
||||
require.NoError(t, plist.Unmarshal(cmd.Raw, &fullCmd))
|
||||
|
||||
// Can be useful for debugging
|
||||
//switch cmd.Command.RequestType {
|
||||
//case "InstallProfile":
|
||||
// fmt.Println(">>>> device received command: ", cmd.CommandUUID, cmd.Command.RequestType, string(fullCmd.Command.InstallProfile.Payload))
|
||||
//case "InstallEnterpriseApplication":
|
||||
// if fullCmd.Command.InstallEnterpriseApplication.ManifestURL != nil {
|
||||
// fmt.Println(">>>> device received command: ", cmd.CommandUUID, cmd.Command.RequestType, *fullCmd.Command.InstallEnterpriseApplication.ManifestURL)
|
||||
// } else {
|
||||
// fmt.Println(">>>> device received command: ", cmd.CommandUUID, cmd.Command.RequestType)
|
||||
// }
|
||||
//default:
|
||||
// fmt.Println(">>>> device received command: ", cmd.Command.RequestType)
|
||||
//}
|
||||
|
||||
cmds = append(cmds, &fullCmd)
|
||||
cmd, err = mdmDevice.Acknowledge(cmd.CommandUUID)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// expected commands: install fleetd (install enterprise), install profiles
|
||||
// (custom one, fleetd configuration, fleet CA root)
|
||||
require.Len(t, cmds, 4)
|
||||
var installProfileCount, installEnterpriseCount, otherCount int
|
||||
var profileCustomSeen, profileFleetdSeen, profileFleetCASeen, profileFileVaultSeen bool
|
||||
for _, cmd := range cmds {
|
||||
switch cmd.Command.RequestType {
|
||||
case "InstallProfile":
|
||||
installProfileCount++
|
||||
if strings.Contains(string(cmd.Command.InstallProfile.Payload), "<string>I1</string>") {
|
||||
profileCustomSeen = true
|
||||
} else if strings.Contains(string(cmd.Command.InstallProfile.Payload), fmt.Sprintf("<string>%s</string>", mobileconfig.FleetdConfigPayloadIdentifier)) {
|
||||
profileFleetdSeen = true
|
||||
} else if strings.Contains(string(cmd.Command.InstallProfile.Payload), fmt.Sprintf("<string>%s</string>", mobileconfig.FleetCARootConfigPayloadIdentifier)) {
|
||||
profileFleetCASeen = true
|
||||
} else if strings.Contains(string(cmd.Command.InstallProfile.Payload), fmt.Sprintf("<string>%s</string", mobileconfig.FleetFileVaultPayloadIdentifier)) &&
|
||||
strings.Contains(string(cmd.Command.InstallProfile.Payload), "ForceEnableInSetupAssistant") {
|
||||
profileFileVaultSeen = true
|
||||
}
|
||||
|
||||
case "InstallEnterpriseApplication":
|
||||
installEnterpriseCount++
|
||||
default:
|
||||
otherCount++
|
||||
}
|
||||
}
|
||||
require.Equal(t, 3, installProfileCount)
|
||||
require.Equal(t, 1, installEnterpriseCount)
|
||||
require.Equal(t, 0, otherCount)
|
||||
require.True(t, profileCustomSeen)
|
||||
require.True(t, profileFleetdSeen)
|
||||
require.True(t, profileFleetCASeen)
|
||||
require.False(t, profileFileVaultSeen)
|
||||
|
||||
// simulate fleetd being installed and the host being orbit-enrolled now
|
||||
enrolledHost.OsqueryHostID = ptr.String(mdmDevice.UUID)
|
||||
orbitKey := setOrbitEnrollment(t, enrolledHost, s.ds)
|
||||
enrolledHost.OrbitNodeKey = &orbitKey
|
||||
|
||||
// there shouldn't be a worker Release Device pending job (we don't release that way anymore)
|
||||
pending, err := s.ds.GetQueuedJobs(ctx, 1, time.Now().UTC().Add(time.Minute))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, pending, 0)
|
||||
|
||||
// call the /status endpoint, the software and script should be pending
|
||||
var statusResp getOrbitSetupExperienceStatusResponse
|
||||
s.DoJSON("POST", "/api/fleet/orbit/setup_experience/status", json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *enrolledHost.OrbitNodeKey)), http.StatusOK, &statusResp)
|
||||
require.Nil(t, statusResp.Results.BootstrapPackage) // no bootstrap package involved
|
||||
require.Nil(t, statusResp.Results.AccountConfiguration) // no SSO involved
|
||||
require.Len(t, statusResp.Results.ConfigurationProfiles, 3) // fleetd config, root CA, custom profile
|
||||
var profNames []string
|
||||
var profStatuses []fleet.MDMDeliveryStatus
|
||||
for _, prof := range statusResp.Results.ConfigurationProfiles {
|
||||
profNames = append(profNames, prof.Name)
|
||||
profStatuses = append(profStatuses, prof.Status)
|
||||
}
|
||||
require.ElementsMatch(t, []string{"N1", "Fleetd configuration", "Fleet root certificate authority (CA)"}, profNames)
|
||||
require.ElementsMatch(t, []fleet.MDMDeliveryStatus{fleet.MDMDeliveryVerifying, fleet.MDMDeliveryVerifying, fleet.MDMDeliveryVerifying}, profStatuses)
|
||||
|
||||
// the software and script are still pending
|
||||
require.NotNil(t, statusResp.Results.Script)
|
||||
require.Equal(t, "script.sh", statusResp.Results.Script.Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusPending, statusResp.Results.Script.Status)
|
||||
require.Len(t, statusResp.Results.Software, 1)
|
||||
require.Equal(t, "DummyApp.app", statusResp.Results.Software[0].Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusPending, statusResp.Results.Software[0].Status)
|
||||
require.NotNil(t, statusResp.Results.Software[0].SoftwareTitleID)
|
||||
require.NotZero(t, *statusResp.Results.Software[0].SoftwareTitleID)
|
||||
|
||||
// no MDM command got enqueued due to the /status call (device not released yet)
|
||||
cmd, err = mdmDevice.Idle()
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, cmd)
|
||||
|
||||
// TODO(mna): here we should call the "state machine" to trigger creation of
|
||||
// the software install and script execution requests, but that is not
|
||||
// implemented yet.
|
||||
|
||||
// TODO(mna): when callback of software/script results are implemented, this will
|
||||
// automatically update the /status responses, and once everything has run it will
|
||||
// automatically release the device.
|
||||
|
||||
// record a result for software installation
|
||||
/*
|
||||
var installResp orbitPostSoftwareInstallResultResponse
|
||||
s.DoJSON("POST", "/api/fleet/orbit/software_install/result",
|
||||
json.RawMessage(fmt.Sprintf(`{
|
||||
"orbit_node_key": %q,
|
||||
"install_uuid": %q,
|
||||
"install_script_exit_code": 0,
|
||||
"install_script_output": "ok"
|
||||
}`, *enrolledHost.OrbitNodeKey, installUUID)), http.StatusOK, &installResp)
|
||||
|
||||
// status still shows script as pending
|
||||
statusResp = getOrbitSetupExperienceStatusResponse{}
|
||||
s.DoJSON("POST", "/api/fleet/orbit/setup_experience/status", json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *enrolledHost.OrbitNodeKey)), http.StatusOK, &statusResp)
|
||||
require.Nil(t, statusResp.Results.BootstrapPackage) // no bootstrap package involved
|
||||
require.Nil(t, statusResp.Results.AccountConfiguration) // no SSO involved
|
||||
require.Len(t, statusResp.Results.ConfigurationProfiles, 3) // fleetd config, root CA, custom profile
|
||||
require.NotNil(t, statusResp.Results.Script)
|
||||
require.Equal(t, "script.sh", statusResp.Results.Script.Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusPending, statusResp.Results.Script.Status)
|
||||
require.Len(t, statusResp.Results.Software, 1)
|
||||
require.Equal(t, "DummyApp.app", statusResp.Results.Software[0].Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusSuccess, statusResp.Results.Software[0].Status)
|
||||
|
||||
// no MDM command got enqueued due to the /status call (device not released yet)
|
||||
cmd, err = mdmDevice.Idle()
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, cmd)
|
||||
|
||||
// record a result for script execution
|
||||
var scriptResp orbitPostScriptResultResponse
|
||||
s.DoJSON("POST", "/api/fleet/orbit/scripts/result",
|
||||
json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q, "execution_id": %q, "exit_code": 0, "output": "ok"}`, *enrolledHost.OrbitNodeKey, execID)),
|
||||
http.StatusOK, &scriptResp)
|
||||
|
||||
// check that the host received the device configured command automatically
|
||||
cmd, err = mdmDevice.Idle()
|
||||
require.NoError(t, err)
|
||||
cmds = cmds[:0]
|
||||
for cmd != nil {
|
||||
var fullCmd micromdm.CommandPayload
|
||||
require.NoError(t, plist.Unmarshal(cmd.Raw, &fullCmd))
|
||||
cmds = append(cmds, &fullCmd)
|
||||
cmd, err = mdmDevice.Acknowledge(cmd.CommandUUID)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
require.Len(t, cmds, 1)
|
||||
var deviceConfiguredCount int
|
||||
for _, cmd := range cmds {
|
||||
switch cmd.Command.RequestType {
|
||||
case "DeviceConfigured":
|
||||
deviceConfiguredCount++
|
||||
default:
|
||||
otherCount++
|
||||
}
|
||||
}
|
||||
require.Equal(t, 1, deviceConfiguredCount)
|
||||
require.Equal(t, 0, otherCount)
|
||||
*/
|
||||
}
|
||||
|
||||
func (s *integrationMDMTestSuite) TestSetupExperienceFlowWithSoftwareAndScriptForceRelease() {
|
||||
t := s.T()
|
||||
ctx := context.Background()
|
||||
|
||||
teamDevice, enrolledHost, _ := s.createTeamDeviceForSetupExperienceWithProfileSoftwareAndScript()
|
||||
|
||||
// enroll the host
|
||||
depURLToken := loadEnrollmentProfileDEPToken(t, s.ds)
|
||||
mdmDevice := mdmtest.NewTestMDMClientAppleDEP(s.server.URL, depURLToken)
|
||||
mdmDevice.SerialNumber = teamDevice.SerialNumber
|
||||
err := mdmDevice.Enroll()
|
||||
require.NoError(t, err)
|
||||
|
||||
// run the worker to process the DEP enroll request
|
||||
s.runWorker()
|
||||
// run the worker to assign configuration profiles
|
||||
s.awaitTriggerProfileSchedule(t)
|
||||
|
||||
var cmds []*micromdm.CommandPayload
|
||||
cmd, err := mdmDevice.Idle()
|
||||
require.NoError(t, err)
|
||||
for cmd != nil {
|
||||
|
||||
var fullCmd micromdm.CommandPayload
|
||||
require.NoError(t, plist.Unmarshal(cmd.Raw, &fullCmd))
|
||||
|
||||
// Can be useful for debugging
|
||||
//switch cmd.Command.RequestType {
|
||||
//case "InstallProfile":
|
||||
// fmt.Println(">>>> device received command: ", cmd.CommandUUID, cmd.Command.RequestType, string(fullCmd.Command.InstallProfile.Payload))
|
||||
//case "InstallEnterpriseApplication":
|
||||
// if fullCmd.Command.InstallEnterpriseApplication.ManifestURL != nil {
|
||||
// fmt.Println(">>>> device received command: ", cmd.CommandUUID, cmd.Command.RequestType, *fullCmd.Command.InstallEnterpriseApplication.ManifestURL)
|
||||
// } else {
|
||||
// fmt.Println(">>>> device received command: ", cmd.CommandUUID, cmd.Command.RequestType)
|
||||
// }
|
||||
//default:
|
||||
// fmt.Println(">>>> device received command: ", cmd.Command.RequestType)
|
||||
//}
|
||||
|
||||
cmds = append(cmds, &fullCmd)
|
||||
cmd, err = mdmDevice.Acknowledge(cmd.CommandUUID)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// expected commands: install fleetd (install enterprise), install profiles
|
||||
// (custom one, fleetd configuration, fleet CA root)
|
||||
require.Len(t, cmds, 4)
|
||||
var installProfileCount, installEnterpriseCount, otherCount int
|
||||
var profileCustomSeen, profileFleetdSeen, profileFleetCASeen, profileFileVaultSeen bool
|
||||
for _, cmd := range cmds {
|
||||
switch cmd.Command.RequestType {
|
||||
case "InstallProfile":
|
||||
installProfileCount++
|
||||
if strings.Contains(string(cmd.Command.InstallProfile.Payload), "<string>I1</string>") {
|
||||
profileCustomSeen = true
|
||||
} else if strings.Contains(string(cmd.Command.InstallProfile.Payload), fmt.Sprintf("<string>%s</string>", mobileconfig.FleetdConfigPayloadIdentifier)) {
|
||||
profileFleetdSeen = true
|
||||
} else if strings.Contains(string(cmd.Command.InstallProfile.Payload), fmt.Sprintf("<string>%s</string>", mobileconfig.FleetCARootConfigPayloadIdentifier)) {
|
||||
profileFleetCASeen = true
|
||||
} else if strings.Contains(string(cmd.Command.InstallProfile.Payload), fmt.Sprintf("<string>%s</string", mobileconfig.FleetFileVaultPayloadIdentifier)) &&
|
||||
strings.Contains(string(cmd.Command.InstallProfile.Payload), "ForceEnableInSetupAssistant") {
|
||||
profileFileVaultSeen = true
|
||||
}
|
||||
|
||||
case "InstallEnterpriseApplication":
|
||||
installEnterpriseCount++
|
||||
default:
|
||||
otherCount++
|
||||
}
|
||||
}
|
||||
require.Equal(t, 3, installProfileCount)
|
||||
require.Equal(t, 1, installEnterpriseCount)
|
||||
require.Equal(t, 0, otherCount)
|
||||
require.True(t, profileCustomSeen)
|
||||
require.True(t, profileFleetdSeen)
|
||||
require.True(t, profileFleetCASeen)
|
||||
require.False(t, profileFileVaultSeen)
|
||||
|
||||
// simulate fleetd being installed and the host being orbit-enrolled now
|
||||
enrolledHost.OsqueryHostID = ptr.String(mdmDevice.UUID)
|
||||
orbitKey := setOrbitEnrollment(t, enrolledHost, s.ds)
|
||||
enrolledHost.OrbitNodeKey = &orbitKey
|
||||
|
||||
// there shouldn't be a worker Release Device pending job (we don't release that way anymore)
|
||||
pending, err := s.ds.GetQueuedJobs(ctx, 1, time.Now().UTC().Add(time.Minute))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, pending, 0)
|
||||
|
||||
// call the /status endpoint, the software and script should be pending
|
||||
var statusResp getOrbitSetupExperienceStatusResponse
|
||||
s.DoJSON("POST", "/api/fleet/orbit/setup_experience/status", json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q}`, *enrolledHost.OrbitNodeKey)), http.StatusOK, &statusResp)
|
||||
require.Nil(t, statusResp.Results.BootstrapPackage) // no bootstrap package involved
|
||||
require.Nil(t, statusResp.Results.AccountConfiguration) // no SSO involved
|
||||
require.Len(t, statusResp.Results.ConfigurationProfiles, 3) // fleetd config, root CA, custom profile
|
||||
var profNames []string
|
||||
var profStatuses []fleet.MDMDeliveryStatus
|
||||
for _, prof := range statusResp.Results.ConfigurationProfiles {
|
||||
profNames = append(profNames, prof.Name)
|
||||
profStatuses = append(profStatuses, prof.Status)
|
||||
}
|
||||
require.ElementsMatch(t, []string{"N1", "Fleetd configuration", "Fleet root certificate authority (CA)"}, profNames)
|
||||
require.ElementsMatch(t, []fleet.MDMDeliveryStatus{fleet.MDMDeliveryVerifying, fleet.MDMDeliveryVerifying, fleet.MDMDeliveryVerifying}, profStatuses)
|
||||
|
||||
// the software and script are still pending
|
||||
require.NotNil(t, statusResp.Results.Script)
|
||||
require.Equal(t, "script.sh", statusResp.Results.Script.Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusPending, statusResp.Results.Script.Status)
|
||||
require.Len(t, statusResp.Results.Software, 1)
|
||||
require.Equal(t, "DummyApp.app", statusResp.Results.Software[0].Name)
|
||||
require.Equal(t, fleet.SetupExperienceStatusPending, statusResp.Results.Software[0].Status)
|
||||
require.NotNil(t, statusResp.Results.Software[0].SoftwareTitleID)
|
||||
require.NotZero(t, *statusResp.Results.Software[0].SoftwareTitleID)
|
||||
|
||||
// no MDM command got enqueued due to the /status call (device not released yet)
|
||||
cmd, err = mdmDevice.Idle()
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, cmd)
|
||||
|
||||
// call the /status endpoint again but this time force the release
|
||||
s.DoJSON("POST", "/api/fleet/orbit/setup_experience/status", json.RawMessage(fmt.Sprintf(`{"orbit_node_key": %q, "force_release": true}`, *enrolledHost.OrbitNodeKey)), http.StatusOK, &statusResp)
|
||||
|
||||
// the software and script are still pending
|
||||
require.NotNil(t, statusResp.Results.Script)
|
||||
require.Equal(t, fleet.SetupExperienceStatusPending, statusResp.Results.Script.Status)
|
||||
require.Len(t, statusResp.Results.Software, 1)
|
||||
require.Equal(t, fleet.SetupExperienceStatusPending, statusResp.Results.Software[0].Status)
|
||||
|
||||
// check that the host received the device configured command even if
|
||||
// software and script are still pending
|
||||
cmd, err = mdmDevice.Idle()
|
||||
require.NoError(t, err)
|
||||
cmds = cmds[:0]
|
||||
for cmd != nil {
|
||||
var fullCmd micromdm.CommandPayload
|
||||
require.NoError(t, plist.Unmarshal(cmd.Raw, &fullCmd))
|
||||
cmds = append(cmds, &fullCmd)
|
||||
cmd, err = mdmDevice.Acknowledge(cmd.CommandUUID)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
require.Len(t, cmds, 1)
|
||||
var deviceConfiguredCount int
|
||||
for _, cmd := range cmds {
|
||||
switch cmd.Command.RequestType {
|
||||
case "DeviceConfigured":
|
||||
deviceConfiguredCount++
|
||||
default:
|
||||
otherCount++
|
||||
}
|
||||
}
|
||||
require.Equal(t, 1, deviceConfiguredCount)
|
||||
require.Equal(t, 0, otherCount)
|
||||
}
|
||||
|
||||
@@ -6,11 +6,13 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -527,3 +529,61 @@ func (ts *withServer) lastActivityOfTypeDoesNotMatch(name, details string, id ui
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (ts *withServer) uploadSoftwareInstaller(
|
||||
t *testing.T,
|
||||
payload *fleet.UploadSoftwareInstallerPayload,
|
||||
expectedStatus int,
|
||||
expectedError string,
|
||||
) {
|
||||
t.Helper()
|
||||
openFile := func(name string) *os.File {
|
||||
f, err := os.Open(filepath.Join("testdata", "software-installers", name))
|
||||
require.NoError(t, err)
|
||||
return f
|
||||
}
|
||||
|
||||
f := openFile(payload.Filename)
|
||||
defer f.Close()
|
||||
|
||||
payload.InstallerFile = f
|
||||
|
||||
var b bytes.Buffer
|
||||
w := multipart.NewWriter(&b)
|
||||
|
||||
// add the software field
|
||||
fw, err := w.CreateFormFile("software", payload.Filename)
|
||||
require.NoError(t, err)
|
||||
n, err := io.Copy(fw, payload.InstallerFile)
|
||||
require.NoError(t, err)
|
||||
require.NotZero(t, n)
|
||||
|
||||
// add the team_id field
|
||||
if payload.TeamID != nil {
|
||||
require.NoError(t, w.WriteField("team_id", fmt.Sprintf("%d", *payload.TeamID)))
|
||||
}
|
||||
// add the remaining fields
|
||||
require.NoError(t, w.WriteField("install_script", payload.InstallScript))
|
||||
require.NoError(t, w.WriteField("pre_install_query", payload.PreInstallQuery))
|
||||
require.NoError(t, w.WriteField("post_install_script", payload.PostInstallScript))
|
||||
require.NoError(t, w.WriteField("uninstall_script", payload.UninstallScript))
|
||||
if payload.SelfService {
|
||||
require.NoError(t, w.WriteField("self_service", "true"))
|
||||
}
|
||||
|
||||
w.Close()
|
||||
|
||||
headers := map[string]string{
|
||||
"Content-Type": w.FormDataContentType(),
|
||||
"Accept": "application/json",
|
||||
"Authorization": fmt.Sprintf("Bearer %s", ts.token),
|
||||
}
|
||||
|
||||
r := ts.DoRawWithHeaders("POST", "/api/latest/fleet/software/package", b.Bytes(), expectedStatus, headers)
|
||||
defer r.Body.Close()
|
||||
|
||||
if expectedError != "" {
|
||||
errMsg := extractServerErrorText(r.Body)
|
||||
require.Contains(t, errMsg, expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user