Remove PUT endpoint, update to always use POST for setup experience scripts (#35818)

<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** Resolves #35309 

Followup changes, see
https://fleetdm.slack.com/archives/C019WG4GH0A/p1763137466439419 for
more context. We decided not to use the initially proposed PUT endpoint
at all and update the existing POST endpoint to have the desired
behavior

# Checklist for submitter

If some of the following don't apply, delete the relevant line.

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/guides/committing-changes.md#changes-files)
for more information.

- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)
- [x] If paths of existing endpoints are modified without backwards
compatibility, checked the frontend/CLI for any necessary changes

## Testing

- [x] Added/updated automated tests
- [x] Where appropriate, [automated tests simulate multiple hosts and
test for host
isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing)
(updates to one hosts's records do not affect another)

- [x] QA'd all new/changed functionality manually
This commit is contained in:
Jordan Montgomery
2025-11-17 11:29:23 -05:00
committed by GitHub
parent 67a954661c
commit 80ec7d4ede
17 changed files with 87 additions and 155 deletions
+1 -1
View File
@@ -565,7 +565,7 @@ func testListHostUpcomingActivities(t *testing.T, ds *Datastore) {
t.Log("h2SelfService", h2SelfService)
setupExpScript := &fleet.Script{Name: "setup_experience_script", ScriptContents: "setup_experience"}
err = ds.SetSetupExperienceScript(ctx, setupExpScript, false)
err = ds.SetSetupExperienceScript(ctx, setupExpScript)
require.NoError(t, err)
ses, err := ds.GetSetupExperienceScript(ctx, h2.TeamID)
require.NoError(t, err)
+1 -1
View File
@@ -8454,7 +8454,7 @@ func testHostsDeleteHosts(t *testing.T, ds *Datastore) {
require.NoError(t, err)
// Add a setup experience status result
err = ds.SetSetupExperienceScript(ctx, &fleet.Script{Name: "test.sh", ScriptContents: "echo foo"}, false)
err = ds.SetSetupExperienceScript(ctx, &fleet.Script{Name: "test.sh", ScriptContents: "echo foo"})
require.NoError(t, err)
added, err := ds.EnqueueSetupExperienceItems(ctx, host.Platform, host.UUID, 0)
+15 -17
View File
@@ -589,7 +589,7 @@ WHERE
return &script, nil
}
func (ds *Datastore) SetSetupExperienceScript(ctx context.Context, script *fleet.Script, allowUpdate bool) error {
func (ds *Datastore) SetSetupExperienceScript(ctx context.Context, script *fleet.Script) error {
err := ds.withRetryTxx(ctx, func(tx sqlx.ExtContext) error {
var err error
@@ -600,26 +600,24 @@ func (ds *Datastore) SetSetupExperienceScript(ctx context.Context, script *fleet
}
id, _ := scRes.LastInsertId()
// This clause allows for PUT semantics in some cases. The basic idea is:
// This clause allows for PUT semantics. The basic idea is:
// - no existing setup script -> go through the usual insert logic
// - existing setup script with different content -> delete(with all side effects) and re-insert
// - existing setup script with same content -> no-op
if allowUpdate {
gotSetupExperienceScript, err := ds.getSetupExperienceScript(ctx, tx, script.TeamID)
if err != nil && !fleet.IsNotFound(err) {
return err
}
// We will fall through on a notFound err - nothing to do here
if err == nil {
if gotSetupExperienceScript.ScriptContentID != uint(id) { // nolint:gosec // dismiss G115 - low risk here
err = ds.deleteSetupExperienceScript(ctx, tx, script.TeamID)
if err != nil {
return err
}
} else {
// no change
return nil
gotSetupExperienceScript, err := ds.getSetupExperienceScript(ctx, tx, script.TeamID)
if err != nil && !fleet.IsNotFound(err) {
return err
}
// We will fall through on a notFound err - nothing to do here
if err == nil {
if gotSetupExperienceScript.ScriptContentID != uint(id) { // nolint:gosec // dismiss G115 - low risk here
err = ds.deleteSetupExperienceScript(ctx, tx, script.TeamID)
if err != nil {
return err
}
} else {
// no change
return nil
}
}
+22 -25
View File
@@ -305,9 +305,9 @@ func testEnqueueSetupExperienceItems(t *testing.T, ds *Datastore) {
})
// Create some scripts and add them to setup experience
err = ds.SetSetupExperienceScript(ctx, &fleet.Script{Name: "script1", ScriptContents: "SCRIPT 1", TeamID: &team1.ID}, false)
err = ds.SetSetupExperienceScript(ctx, &fleet.Script{Name: "script1", ScriptContents: "SCRIPT 1", TeamID: &team1.ID})
require.NoError(t, err)
err = ds.SetSetupExperienceScript(ctx, &fleet.Script{Name: "script2", ScriptContents: "SCRIPT 2", TeamID: &team2.ID}, false)
err = ds.SetSetupExperienceScript(ctx, &fleet.Script{Name: "script2", ScriptContents: "SCRIPT 2", TeamID: &team2.ID})
require.NoError(t, err)
script1, err := ds.GetSetupExperienceScript(ctx, &team1.ID)
@@ -654,7 +654,7 @@ func testGetSetupExperienceTitles(t *testing.T, ds *Datastore) {
TeamID: &team1.ID,
Name: "the script.sh",
ScriptContents: "hello",
}, false)
})
require.NoError(t, err)
sec, err := ds.GetSetupExperienceCount(ctx, "darwin", &team1.ID)
@@ -1080,7 +1080,7 @@ func testSetupExperienceScriptCRUD(t *testing.T, ds *Datastore) {
ScriptContents: "echo foo",
}
err = ds.SetSetupExperienceScript(ctx, wantScript1, false)
err = ds.SetSetupExperienceScript(ctx, wantScript1)
require.NoError(t, err)
// get the script for team1
@@ -1102,7 +1102,7 @@ func testSetupExperienceScriptCRUD(t *testing.T, ds *Datastore) {
ScriptContents: "echo bar",
}
err = ds.SetSetupExperienceScript(ctx, wantScript2, false)
err = ds.SetSetupExperienceScript(ctx, wantScript2)
require.NoError(t, err)
// get the script for team2
@@ -1124,7 +1124,7 @@ func testSetupExperienceScriptCRUD(t *testing.T, ds *Datastore) {
ScriptContents: "echo bar",
}
err = ds.SetSetupExperienceScript(ctx, wantScriptNoTeam, false)
err = ds.SetSetupExperienceScript(ctx, wantScriptNoTeam)
require.NoError(t, err)
// get the script nil team id is equivalent to team id 0
@@ -1140,20 +1140,17 @@ func testSetupExperienceScriptCRUD(t *testing.T, ds *Datastore) {
require.NoError(t, err)
require.Equal(t, wantScriptNoTeam.ScriptContents, string(b))
// try to create another with name "script" and no team id
var existsErr fleet.AlreadyExistsError
err = ds.SetSetupExperienceScript(ctx, &fleet.Script{Name: "script", ScriptContents: "echo baz"}, false)
require.Error(t, err)
require.ErrorAs(t, err, &existsErr)
// try to create another with name "script" and no team id. Should succeed
err = ds.SetSetupExperienceScript(ctx, &fleet.Script{Name: "script", ScriptContents: "echo baz"})
require.NoError(t, err)
// try to create another script with no team id and a different name
err = ds.SetSetupExperienceScript(ctx, &fleet.Script{Name: "script2", ScriptContents: "echo baz"}, false)
require.Error(t, err)
require.ErrorAs(t, err, &existsErr)
// try to create another script with no team id and a different name. Should succeed
err = ds.SetSetupExperienceScript(ctx, &fleet.Script{Name: "script2", ScriptContents: "echo baz"})
require.NoError(t, err)
// try to add a script for a team that doesn't exist
var fkErr fleet.ForeignKeyError
err = ds.SetSetupExperienceScript(ctx, &fleet.Script{TeamID: ptr.Uint(42), Name: "script", ScriptContents: "echo baz"}, false)
err = ds.SetSetupExperienceScript(ctx, &fleet.Script{TeamID: ptr.Uint(42), Name: "script", ScriptContents: "echo baz"})
require.Error(t, err)
require.ErrorAs(t, err, &fkErr)
@@ -1174,8 +1171,8 @@ func testSetupExperienceScriptCRUD(t *testing.T, ds *Datastore) {
err = ds.DeleteSetupExperienceScript(ctx, ptr.Uint(42))
require.NoError(t, err) // TODO: confirm if we want to return not found on deletes
// add same script for team1 again but with "allowUpdate" set(even though there will be no update since it doesn't exist)
err = ds.SetSetupExperienceScript(ctx, wantScript1, true)
// add same script for team1 again(even though there will be no update since it doesn't exist)
err = ds.SetSetupExperienceScript(ctx, wantScript1)
require.NoError(t, err)
// get the script for team1
@@ -1190,8 +1187,8 @@ func testSetupExperienceScriptCRUD(t *testing.T, ds *Datastore) {
// so the content id should be the same as the old
require.Equal(t, oldScript1.ScriptContentID, newScript1.ScriptContentID)
// add same script for team1 again with "allowUpdate" set.
err = ds.SetSetupExperienceScript(ctx, wantScript1, true)
// add same script for team1 again
err = ds.SetSetupExperienceScript(ctx, wantScript1)
require.NoError(t, err)
// Verify that the script contents remained the same
@@ -1234,13 +1231,13 @@ func testUpdateSetupExperienceScriptWhileEnqueued(t *testing.T, ds *Datastore) {
ScriptContents: "echo updated foo",
}
err = ds.SetSetupExperienceScript(ctx, initialScript1, true)
err = ds.SetSetupExperienceScript(ctx, initialScript1)
require.NoError(t, err)
team1OriginalScript, err := ds.GetSetupExperienceScript(ctx, &team1.ID)
require.NoError(t, err)
require.NotNil(t, team1OriginalScript)
err = ds.SetSetupExperienceScript(ctx, initialScript2, true)
err = ds.SetSetupExperienceScript(ctx, initialScript2)
require.NoError(t, err)
team2OriginalScript, err := ds.GetSetupExperienceScript(ctx, &team2.ID)
require.NoError(t, err)
@@ -1272,7 +1269,7 @@ func testUpdateSetupExperienceScriptWhileEnqueued(t *testing.T, ds *Datastore) {
require.Equal(t, team2OriginalScript.ID, *host2OriginalItems[0].SetupExperienceScriptID)
// "Update" the script for team1 with its original contents which should cause no change to the enqueued execution
err = ds.SetSetupExperienceScript(ctx, initialScript1, true)
err = ds.SetSetupExperienceScript(ctx, initialScript1)
require.NoError(t, err)
team1UpdatedScript, err := ds.GetSetupExperienceScript(ctx, &team1.ID)
@@ -1293,7 +1290,7 @@ func testUpdateSetupExperienceScriptWhileEnqueued(t *testing.T, ds *Datastore) {
require.Equal(t, team2OriginalScript.ID, *host2NewItems[0].SetupExperienceScriptID)
// update script for team1 which should delete the enqueued execution
err = ds.SetSetupExperienceScript(ctx, updatedScript1, true)
err = ds.SetSetupExperienceScript(ctx, updatedScript1)
require.NoError(t, err)
team1UpdatedScript, err = ds.GetSetupExperienceScript(ctx, &team1.ID)
@@ -1354,7 +1351,7 @@ func testGetSetupExperienceScriptByID(t *testing.T, ds *Datastore) {
ScriptContents: "echo hello",
}
err := ds.SetSetupExperienceScript(ctx, script, false)
err := ds.SetSetupExperienceScript(ctx, script)
require.NoError(t, err)
scriptByTeamID, err := ds.GetSetupExperienceScript(ctx, nil)
+1 -1
View File
@@ -2207,7 +2207,7 @@ type Datastore interface {
GetSetupExperienceScriptByID(ctx context.Context, scriptID uint) (*Script, error)
// SetSetupExperienceScript sets the setup experience script to the given script.
SetSetupExperienceScript(ctx context.Context, script *Script, allowUpdate bool) error
SetSetupExperienceScript(ctx context.Context, script *Script) error
// DeleteSetupExperienceScript deletes the setup experience script for the given team.
DeleteSetupExperienceScript(ctx context.Context, teamID *uint) error
+2 -5
View File
@@ -1276,13 +1276,10 @@ type Service interface {
GetOrbitSetupExperienceStatus(ctx context.Context, orbitNodeKey string, forceRelease bool, resetFailedSetupSteps bool) (*SetupExperienceStatusPayload, error)
// GetSetupExperienceScript gets the current setup experience script for the given team.
GetSetupExperienceScript(ctx context.Context, teamID *uint, downloadRequested bool) (*Script, []byte, error)
// CreateSetupExperienceScript creates the setup experience script for the given team. An error is returned if a
// script already exists for the given team
CreateSetupExperienceScript(ctx context.Context, teamID *uint, name string, r io.Reader) error
// PutSetupExperienceScript sets the setup experience script for a given team, deleting the existing one if it exists
// SetSetupExperienceScript sets the setup experience script for a given team, deleting the existing one if it exists
// and is different and replacing it with a new one. Effectively an upsert operation which does nothing if the contents
// do not change
PutSetupExperienceScript(ctx context.Context, teamID *uint, name string, r io.Reader) error
SetSetupExperienceScript(ctx context.Context, teamID *uint, name string, r io.Reader) error
// DeleteSetupExperienceScript deletes the setup experience script for the given team.
DeleteSetupExperienceScript(ctx context.Context, teamID *uint) error
// SetupExperienceNextStep is a callback that processes the
+15 -15
View File
@@ -257,12 +257,12 @@ type SetOrUpdateCustomHostDeviceMappingFunc func(ctx context.Context, hostID uin
type SetOrUpdateIDPHostDeviceMappingFunc func(ctx context.Context, hostID uint, email string) error
type DeleteHostIDPFunc func(ctx context.Context, id uint) error
type SetOrUpdateHostSCIMUserMappingFunc func(ctx context.Context, hostID uint, scimUserID uint) error
type DeleteHostSCIMUserMappingFunc func(ctx context.Context, hostID uint) error
type DeleteHostIDPFunc func(ctx context.Context, id uint) error
type ListHostBatteriesFunc func(ctx context.Context, id uint) ([]*fleet.HostBattery, error)
type ListUpcomingHostMaintenanceWindowsFunc func(ctx context.Context, hid uint) ([]*fleet.HostMaintenanceWindow, error)
@@ -1407,7 +1407,7 @@ type GetSetupExperienceScriptFunc func(ctx context.Context, teamID *uint) (*flee
type GetSetupExperienceScriptByIDFunc func(ctx context.Context, scriptID uint) (*fleet.Script, error)
type SetSetupExperienceScriptFunc func(ctx context.Context, script *fleet.Script, allowUpdate bool) error
type SetSetupExperienceScriptFunc func(ctx context.Context, script *fleet.Script) error
type DeleteSetupExperienceScriptFunc func(ctx context.Context, teamID *uint) error
@@ -1961,15 +1961,15 @@ type DataStore struct {
SetOrUpdateIDPHostDeviceMappingFunc SetOrUpdateIDPHostDeviceMappingFunc
SetOrUpdateIDPHostDeviceMappingFuncInvoked bool
DeleteHostIDPFunc DeleteHostIDPFunc
DeleteHostIDPFuncInvoked bool
SetOrUpdateHostSCIMUserMappingFunc SetOrUpdateHostSCIMUserMappingFunc
SetOrUpdateHostSCIMUserMappingFuncInvoked bool
DeleteHostSCIMUserMappingFunc DeleteHostSCIMUserMappingFunc
DeleteHostSCIMUserMappingFuncInvoked bool
DeleteHostIDPFunc DeleteHostIDPFunc
DeleteHostIDPFuncInvoked bool
ListHostBatteriesFunc ListHostBatteriesFunc
ListHostBatteriesFuncInvoked bool
@@ -4811,6 +4811,13 @@ func (s *DataStore) SetOrUpdateIDPHostDeviceMapping(ctx context.Context, hostID
return s.SetOrUpdateIDPHostDeviceMappingFunc(ctx, hostID, email)
}
func (s *DataStore) DeleteHostIDP(ctx context.Context, id uint) error {
s.mu.Lock()
s.DeleteHostIDPFuncInvoked = true
s.mu.Unlock()
return s.DeleteHostIDPFunc(ctx, id)
}
func (s *DataStore) SetOrUpdateHostSCIMUserMapping(ctx context.Context, hostID uint, scimUserID uint) error {
s.mu.Lock()
s.SetOrUpdateHostSCIMUserMappingFuncInvoked = true
@@ -4825,13 +4832,6 @@ func (s *DataStore) DeleteHostSCIMUserMapping(ctx context.Context, hostID uint)
return s.DeleteHostSCIMUserMappingFunc(ctx, hostID)
}
func (s *DataStore) DeleteHostIDP(ctx context.Context, id uint) error {
s.mu.Lock()
s.DeleteHostIDPFuncInvoked = true
s.mu.Unlock()
return s.DeleteHostIDPFunc(ctx, id)
}
func (s *DataStore) ListHostBatteries(ctx context.Context, id uint) ([]*fleet.HostBattery, error) {
s.mu.Lock()
s.ListHostBatteriesFuncInvoked = true
@@ -8836,11 +8836,11 @@ func (s *DataStore) GetSetupExperienceScriptByID(ctx context.Context, scriptID u
return s.GetSetupExperienceScriptByIDFunc(ctx, scriptID)
}
func (s *DataStore) SetSetupExperienceScript(ctx context.Context, script *fleet.Script, allowUpdate bool) error {
func (s *DataStore) SetSetupExperienceScript(ctx context.Context, script *fleet.Script) error {
s.mu.Lock()
s.SetSetupExperienceScriptFuncInvoked = true
s.mu.Unlock()
return s.SetSetupExperienceScriptFunc(ctx, script, allowUpdate)
return s.SetSetupExperienceScriptFunc(ctx, script)
}
func (s *DataStore) DeleteSetupExperienceScript(ctx context.Context, teamID *uint) error {
+6 -18
View File
@@ -790,9 +790,7 @@ type GetOrbitSetupExperienceStatusFunc func(ctx context.Context, orbitNodeKey st
type GetSetupExperienceScriptFunc func(ctx context.Context, teamID *uint, downloadRequested bool) (*fleet.Script, []byte, error)
type CreateSetupExperienceScriptFunc func(ctx context.Context, teamID *uint, name string, r io.Reader) error
type PutSetupExperienceScriptFunc func(ctx context.Context, teamID *uint, name string, r io.Reader) error
type SetSetupExperienceScriptFunc func(ctx context.Context, teamID *uint, name string, r io.Reader) error
type DeleteSetupExperienceScriptFunc func(ctx context.Context, teamID *uint) error
@@ -2011,11 +2009,8 @@ type Service struct {
GetSetupExperienceScriptFunc GetSetupExperienceScriptFunc
GetSetupExperienceScriptFuncInvoked bool
CreateSetupExperienceScriptFunc CreateSetupExperienceScriptFunc
CreateSetupExperienceScriptFuncInvoked bool
PutSetupExperienceScriptFunc PutSetupExperienceScriptFunc
PutSetupExperienceScriptFuncInvoked bool
SetSetupExperienceScriptFunc SetSetupExperienceScriptFunc
SetSetupExperienceScriptFuncInvoked bool
DeleteSetupExperienceScriptFunc DeleteSetupExperienceScriptFunc
DeleteSetupExperienceScriptFuncInvoked bool
@@ -4809,18 +4804,11 @@ func (s *Service) GetSetupExperienceScript(ctx context.Context, teamID *uint, do
return s.GetSetupExperienceScriptFunc(ctx, teamID, downloadRequested)
}
func (s *Service) CreateSetupExperienceScript(ctx context.Context, teamID *uint, name string, r io.Reader) error {
func (s *Service) SetSetupExperienceScript(ctx context.Context, teamID *uint, name string, r io.Reader) error {
s.mu.Lock()
s.CreateSetupExperienceScriptFuncInvoked = true
s.SetSetupExperienceScriptFuncInvoked = true
s.mu.Unlock()
return s.CreateSetupExperienceScriptFunc(ctx, teamID, name, r)
}
func (s *Service) PutSetupExperienceScript(ctx context.Context, teamID *uint, name string, r io.Reader) error {
s.mu.Lock()
s.PutSetupExperienceScriptFuncInvoked = true
s.mu.Unlock()
return s.PutSetupExperienceScriptFunc(ctx, teamID, name, r)
return s.SetSetupExperienceScriptFunc(ctx, teamID, name, r)
}
func (s *Service) DeleteSetupExperienceScript(ctx context.Context, teamID *uint) error {
+1 -1
View File
@@ -168,7 +168,7 @@ func (c *Client) deleteMacOSSetupScript(teamID *uint) error {
}
func (c *Client) uploadMacOSSetupScript(filename string, data []byte, teamID *uint) error {
verb, path := "PUT", "/api/latest/fleet/setup_experience/script"
verb, path := "POST", "/api/latest/fleet/setup_experience/script"
var b bytes.Buffer
w := multipart.NewWriter(&b)
+1 -2
View File
@@ -409,8 +409,7 @@ func attachFleetAPIRoutes(r *mux.Router, svc fleet.Service, config config.FleetC
// Setup experience script endpoints:
ue.GET("/api/_version_/fleet/setup_experience/script", getSetupExperienceScriptEndpoint, getSetupExperienceScriptRequest{})
ue.POST("/api/_version_/fleet/setup_experience/script", createSetupExperienceScriptEndpoint, setSetupExperienceScriptRequest{})
ue.PUT("/api/_version_/fleet/setup_experience/script", putSetupExperienceScriptEndpoint, setSetupExperienceScriptRequest{})
ue.POST("/api/_version_/fleet/setup_experience/script", setSetupExperienceScriptEndpoint, setSetupExperienceScriptRequest{})
ue.DELETE("/api/_version_/fleet/setup_experience/script", deleteSetupExperienceScriptEndpoint, deleteSetupExperienceScriptRequest{})
// Fleet-maintained apps
@@ -69,32 +69,17 @@ func (s *integrationMDMTestSuite) TestSetupExperienceScript() {
require.Equal(t, int64(len(`echo "hello"`)), res.ContentLength)
require.Equal(t, fmt.Sprintf("attachment;filename=\"%s %s\"", time.Now().Format(time.DateOnly), "script42.sh"), res.Header.Get("Content-Disposition"))
// try to create script with same name, should fail because already exists with this name for this team
// try to update script with same name, should not fail because this is allowed
body, headers = generateNewScriptMultipartRequest(t,
"script42.sh", []byte(`echo "hello"`), s.token, map[string][]string{"team_id": {fmt.Sprintf("%d", tm.ID)}})
res = s.DoRawWithHeaders("POST", "/api/latest/fleet/setup_experience/script", body.Bytes(), http.StatusConflict, headers)
errMsg := extractServerErrorText(res.Body)
require.Contains(t, errMsg, "already exists") // TODO: confirm expected error message with product/frontend
// try to create with a different name for this team, should fail because another script already exists
// for this team
body, headers = generateNewScriptMultipartRequest(t,
"different.sh", []byte(`echo "hello"`), s.token, map[string][]string{"team_id": {fmt.Sprintf("%d", tm.ID)}})
res = s.DoRawWithHeaders("POST", "/api/latest/fleet/setup_experience/script", body.Bytes(), http.StatusConflict, headers)
errMsg = extractServerErrorText(res.Body)
require.Contains(t, errMsg, "already exists") // TODO: confirm expected error message with product/frontend
// try to update script with same name via PUT endpoint, should not fail because this is allowed
body, headers = generateNewScriptMultipartRequest(t,
"script42.sh", []byte(`echo "hello"`), s.token, map[string][]string{"team_id": {fmt.Sprintf("%d", tm.ID)}})
res = s.DoRawWithHeaders("PUT", "/api/latest/fleet/setup_experience/script", body.Bytes(), http.StatusOK, headers)
res = s.DoRawWithHeaders("POST", "/api/latest/fleet/setup_experience/script", body.Bytes(), http.StatusOK, headers)
err = json.NewDecoder(res.Body).Decode(&newScriptResp)
require.NoError(t, err)
// update with a different name and contents via PUT endpoint, should suceed
body, headers = generateNewScriptMultipartRequest(t,
"different.sh", []byte(`echo "hello2"`), s.token, map[string][]string{"team_id": {fmt.Sprintf("%d", tm.ID)}})
res = s.DoRawWithHeaders("PUT", "/api/latest/fleet/setup_experience/script", body.Bytes(), http.StatusOK, headers)
res = s.DoRawWithHeaders("POST", "/api/latest/fleet/setup_experience/script", body.Bytes(), http.StatusOK, headers)
err = json.NewDecoder(res.Body).Decode(&newScriptResp)
require.NoError(t, err)
@@ -1109,10 +1094,10 @@ func (s *integrationMDMTestSuite) TestSetupExperienceFlowUpdateScript() {
require.NoError(t, err)
require.Nil(t, cmd)
// PUT update the script, no changes, it does not get cancelled
// update the script but with no actual changes, it does not get cancelled
body, headers := generateNewScriptMultipartRequest(t,
"script.sh", []byte(`echo "hello"`), s.token, map[string][]string{"team_id": {fmt.Sprintf("%d", tm.ID)}})
s.DoRawWithHeaders("PUT", "/api/latest/fleet/setup_experience/script", body.Bytes(), http.StatusOK, headers)
s.DoRawWithHeaders("POST", "/api/latest/fleet/setup_experience/script", body.Bytes(), http.StatusOK, headers)
// call the /status endpoint, the software is still running and script should still be pending
statusResp = getOrbitSetupExperienceStatusResponse{}
@@ -1131,10 +1116,10 @@ func (s *integrationMDMTestSuite) TestSetupExperienceFlowUpdateScript() {
require.NotNil(t, statusResp.Results.Software[0].SoftwareTitleID)
require.NotZero(t, *statusResp.Results.Software[0].SoftwareTitleID)
// PUT update the script with changes, see it get cancelled
// update the script with changes, see it get cancelled
body, headers = generateNewScriptMultipartRequest(t,
"script2.sh", []byte(`echo "foobar"`), s.token, map[string][]string{"team_id": {fmt.Sprintf("%d", tm.ID)}})
s.DoRawWithHeaders("PUT", "/api/latest/fleet/setup_experience/script", body.Bytes(), http.StatusOK, headers)
s.DoRawWithHeaders("POST", "/api/latest/fleet/setup_experience/script", body.Bytes(), http.StatusOK, headers)
// call the /status endpoint, software is running, script is removed as it got cancelled by the update
statusResp = getOrbitSetupExperienceStatusResponse{}
+3 -27
View File
@@ -169,7 +169,7 @@ type setSetupExperienceScriptResponse struct {
func (r setSetupExperienceScriptResponse) Error() error { return r.Err }
func createSetupExperienceScriptEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (fleet.Errorer, error) {
func setSetupExperienceScriptEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (fleet.Errorer, error) {
req := request.(*setSetupExperienceScriptRequest)
scriptFile, err := req.Script.Open()
@@ -178,38 +178,14 @@ func createSetupExperienceScriptEndpoint(ctx context.Context, request interface{
}
defer scriptFile.Close()
if err := svc.CreateSetupExperienceScript(ctx, req.TeamID, filepath.Base(req.Script.Filename), scriptFile); err != nil {
if err := svc.SetSetupExperienceScript(ctx, req.TeamID, filepath.Base(req.Script.Filename), scriptFile); err != nil {
return setSetupExperienceScriptResponse{Err: err}, nil
}
return setSetupExperienceScriptResponse{}, nil
}
func (svc *Service) CreateSetupExperienceScript(ctx context.Context, teamID *uint, name string, r io.Reader) error {
// skipauth: No authorization check needed due to implementation returning
// only license error.
svc.authz.SkipAuthorization(ctx)
return fleet.ErrMissingLicense
}
func putSetupExperienceScriptEndpoint(ctx context.Context, request interface{}, svc fleet.Service) (fleet.Errorer, error) {
req := request.(*setSetupExperienceScriptRequest)
scriptFile, err := req.Script.Open()
if err != nil {
return setSetupExperienceScriptResponse{Err: err}, nil
}
defer scriptFile.Close()
if err := svc.PutSetupExperienceScript(ctx, req.TeamID, filepath.Base(req.Script.Filename), scriptFile); err != nil {
return setSetupExperienceScriptResponse{Err: err}, nil
}
return setSetupExperienceScriptResponse{}, nil
}
func (svc *Service) PutSetupExperienceScript(ctx context.Context, teamID *uint, name string, r io.Reader) error {
func (svc *Service) SetSetupExperienceScript(ctx context.Context, teamID *uint, name string, r io.Reader) error {
// skipauth: No authorization check needed due to implementation returning
// only license error.
svc.authz.SkipAuthorization(ctx)
+3 -3
View File
@@ -25,7 +25,7 @@ func TestSetupExperienceAuth(t *testing.T) {
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
return &fleet.AppConfig{}, nil
}
ds.SetSetupExperienceScriptFunc = func(ctx context.Context, script *fleet.Script, allowUpdate bool) error {
ds.SetSetupExperienceScriptFunc = func(ctx context.Context, script *fleet.Script) error {
return nil
}
@@ -199,7 +199,7 @@ func TestSetupExperienceAuth(t *testing.T) {
ctx = viewer.NewContext(ctx, viewer.Viewer{User: tt.user})
t.Run("setup experience script", func(t *testing.T) {
err := svc.CreateSetupExperienceScript(ctx, nil, "test.sh", strings.NewReader("echo"))
err := svc.SetSetupExperienceScript(ctx, nil, "test.sh", strings.NewReader("echo"))
checkAuthErr(t, tt.shouldFailGlobalWrite, err)
err = svc.DeleteSetupExperienceScript(ctx, nil)
checkAuthErr(t, tt.shouldFailGlobalWrite, err)
@@ -208,7 +208,7 @@ func TestSetupExperienceAuth(t *testing.T) {
_, _, err = svc.GetSetupExperienceScript(ctx, nil, true)
checkAuthErr(t, tt.shouldFailGlobalRead, err)
err = svc.CreateSetupExperienceScript(ctx, &teamID, "test.sh", strings.NewReader("echo"))
err = svc.SetSetupExperienceScript(ctx, &teamID, "test.sh", strings.NewReader("echo"))
checkAuthErr(t, tt.shouldFailTeamWrite, err)
err = svc.DeleteSetupExperienceScript(ctx, &teamID)
checkAuthErr(t, tt.shouldFailTeamWrite, err)