Msp dashboard: Update software endpoints (#24299)

Changes:
- Updated the software page to display different error messages when
requests to software endpoints fail.
- Updated the edit software endpoint to:
- have three new exits, `softwareAlreadyExistsOnThisTeam`,
`couldNotReadVersion` and `softwareDeletionFailed`
- Create database records for newly undeployed software after it has
been removed from all teams on a Fleet instance.
- Return a `softwareAlreadyExistsOnThisTeam` response if undeployed
software is being transferred to a team it is already deployed to.
- Return a `couldNotReadVersion` response if the Fleet instance cannot
read the version information from an uploaded software installer
- Return a `softwareDeletionFailed` response if an installer cannot be
deleted via an API request (If it is configured to be installed during
the macOS setup experience)
- Updated the upload-software endpoint to return a `couldNotReadVersion`
response if the Fleet instance cannot read the version information from
an uploaded software installer
This commit is contained in:
Eric 2024-12-02 22:28:03 -06:00 committed by GitHub
parent 511ffd144f
commit 93d6c029f4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 124 additions and 20 deletions

View file

@ -46,9 +46,23 @@ module.exports = {
description: 'The provided replacement software\'s has the wrong extension.',
statusCode: 400,
},
softwareUploadFailed: {
description: 'The software upload failed'
}
},
softwareAlreadyExistsOnThisTeam: {
description: 'A software installer with this name already exists on the Fleet Instance',
},
couldNotReadVersion: {
description:'Fleet could not read version information from the provided software installer.'
},
softwareDeletionFailed: {
description: 'The specified software could not be deleted from the Fleet instance.',
statusCode: 409,
},
},
@ -167,7 +181,35 @@ module.exports = {
}
};
},
})
}
)
.intercept({response: {status: 409}}, async (error)=>{// handles errors related to duplicate software items.
if(!software.id) {// If the software does not have an ID, it not stored in the app's database/s3 bucket, so we can safely delete the file in s3.
await sails.rm(sails.config.uploads.prefixForFileDeletion+softwareFd);
}
return {'softwareAlreadyExistsOnThisTeam': error};
})
.intercept({name: 'AxiosError', response: {status: 400}}, async (error)=>{// Handles errors related to malformed installer packages
if(!software.id) {// If the software does not have an ID, it not stored in the app's database/s3 bucket, so we can safely delete the file in s3.
await sails.rm(sails.config.uploads.prefixForFileDeletion+softwareFd);
}
let axiosError = error;
if(axiosError.response.data) {
if(axiosError.response.data.errors && _.isArray(axiosError.response.data.errors)){
if(axiosError.response.data.errors[0] && axiosError.response.data.errors[0].reason) {
let errorMessageFromFleetInstance = axiosError.response.data.errors[0].reason;
if(_.startsWith(errorMessageFromFleetInstance, `Couldn't add. Fleet couldn't read the version`)){
return 'couldNotReadVersion';
} else {
sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API. Error returned from Fleet API: ${errorMessageFromFleetInstance}`);
return {'softwareUploadFailed': error};
}
}
}
}
sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API, ${require('util').inspect(error, {depth: 3})}`);
return {'softwareUploadFailed': error};
})
.intercept(async (error)=>{
// Note: with this current behavior, all errors from this upload are currently swallowed and a softwareUploadFailed response is returned.
// FUTURE: Test to make sure that uploading duplicate software to a team results in a 409 response.
@ -234,6 +276,33 @@ module.exports = {
};
},
})
.intercept({response: {status: 409}}, async (error)=>{// handles errors related to duplicate software items.
if(!software.id) {// If the software does not have an ID, it not stored in the app's database/s3 bucket, so we can safely delete the file in s3.
await sails.rm(sails.config.uploads.prefixForFileDeletion+softwareFd);
}
return {'softwareAlreadyExistsOnThisTeam': error};
})
.intercept({name: 'AxiosError', response: {status: 400}}, async (error)=>{// Handles errors related to malformed installer packages
if(!software.id) {// If the software does not have an ID, it not stored in the app's database/s3 bucket, so we can safely delete the file in s3.
await sails.rm(sails.config.uploads.prefixForFileDeletion+softwareFd);
}
let axiosError = error;
if(axiosError.response.data) {
if(axiosError.response.data.errors && _.isArray(axiosError.response.data.errors)){
if(axiosError.response.data.errors[0] && axiosError.response.data.errors[0].reason) {
let errorMessageFromFleetInstance = axiosError.response.data.errors[0].reason;
if(_.startsWith(errorMessageFromFleetInstance, `Couldn't add. Fleet couldn't read the version`)){
return 'couldNotReadVersion';
} else {
sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API. Error returned from Fleet API: ${errorMessageFromFleetInstance}`);
return {'softwareUploadFailed': error};
}
}
}
}
sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API, ${require('util').inspect(error, {depth: 3})}`);
return {'softwareUploadFailed': error};
})
.intercept(async (error)=>{
// Note: with this current behavior, all errors from this upload are currently swallowed and a softwareUploadFailed response is returned.
// FUTURE: Test to make sure that uploading duplicate software to a team results in a 409 response.
@ -279,6 +348,11 @@ module.exports = {
headers: {
Authorization: `Bearer ${sails.config.custom.fleetApiToken}`,
}
})
.intercept({raw:{statusCode: 409}}, (error)=>{
// If the Fleet instance's returns a 409 response, then the software is configured to be installed as
// part of the macOS setup experience, and must be removed before it can be deleted via API requests.
return {softwareDeletionFailed: error};
});
}
// If the software had been previously undeployed, delete the installer in s3 and the db record.
@ -289,9 +363,23 @@ module.exports = {
} else if(software.teams && newTeamIds.length === 0) {
// If this is a deployed software that is being unassigned, save information about the uploaded file in our s3 bucket.
for(let team of software.teams) {
// Now delete the software on the Fleet instance.
await sails.helpers.http.sendHttpRequest.with({
method: 'DELETE',
baseUrl: sails.config.custom.fleetBaseUrl,
url: `/api/v1/fleet/software/titles/${software.fleetApid}/available_for_install?team_id=${team.fleetApid}`,
headers: {
Authorization: `Bearer ${sails.config.custom.fleetApiToken}`,
}
})
.intercept({raw:{statusCode: 409}}, (error)=>{
// If the Fleet instance's returns a 409 response, then the software is configured to be installed as
// part of the macOS setup experience, and must be removed before it can be deleted via API requests.
return {softwareDeletionFailed: error};
});
}
if(newSoftware) {
// remove the old copy.
// console.log('Removing old package for ',softwareName);
await UndeployedSoftware.create({
uploadFd: softwareFd,
uploadMime: softwareMime,
@ -315,17 +403,6 @@ module.exports = {
uninstallScript,
});
}
// Now delete the software on the Fleet instance.
for(let team of software.teams) {
await sails.helpers.http.sendHttpRequest.with({
method: 'DELETE',
baseUrl: sails.config.custom.fleetBaseUrl,
url: `/api/v1/fleet/software/titles/${software.fleetApid}/available_for_install?team_id=${team.fleetApid}`,
headers: {
Authorization: `Bearer ${sails.config.custom.fleetApiToken}`,
}
});
}
} else {
// console.log('updating existing db record!');

View file

@ -35,6 +35,10 @@ module.exports = {
softwareUploadFailed: {
description:'An unexpected error occurred communicating with the Fleet API'
},
couldNotReadVersion: {
description:'Fleet could not read version information from the provided software installer.'
}
},
@ -100,13 +104,32 @@ module.exports = {
};
}
})
.intercept({response: {status: 409}}, async (error)=>{
.intercept({response: {status: 409}}, async (error)=>{// handles errors related to duplicate software items.
await sails.rm(sails.config.uploads.prefixForFileDeletion+uploadedSoftware.fd);
return {'softwareAlreadyExistsOnThisTeam': error};
})
.intercept({name: 'AxiosError'}, async (error)=>{
.intercept({name: 'AxiosError', response: {status: 400}}, async (error)=>{// Handles errors related to malformed installer packages
await sails.rm(sails.config.uploads.prefixForFileDeletion+uploadedSoftware.fd);
sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API, ${require('util').inspect(error, {depth: 2})}`);
let axiosError = error;
if(axiosError.response.data) {
if(axiosError.response.data.errors && _.isArray(axiosError.response.data.errors)){
if(axiosError.response.data.errors[0] && axiosError.response.data.errors[0].reason) {
let errorMessageFromFleetInstance = axiosError.response.data.errors[0].reason;
if(_.startsWith(errorMessageFromFleetInstance, `Couldn't add. Fleet couldn't read the version`)){
return 'couldNotReadVersion';
} else {
sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API. Error returned from Fleet API: ${errorMessageFromFleetInstance}`);
return {'softwareUploadFailed': error};
}
}
}
}
sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API, ${require('util').inspect(error, {depth: 3})}`);
return {'softwareUploadFailed': error};
})
.intercept({name: 'AxiosError'}, async (error)=>{// Handles any other error.
await sails.rm(sails.config.uploads.prefixForFileDeletion+uploadedSoftware.fd);
sails.log.warn(`When attempting to upload a software installer, an unexpected error occurred communicating with the Fleet API, ${require('util').inspect(error, {depth: 3})}`);
return {'softwareUploadFailed': error};
});
}

View file

@ -145,8 +145,11 @@
<p class="mb-2"><strong>Teams</strong></p>
<multifield :value="formData.teams" v-model="formData.newTeamIds" input-type="teamSelect" :select-options="teams" add-button-text="Add team"></multifield>
</div>
<cloud-error v-if="cloudError && cloudError.exit === 'wrongInstallerExtension'">{{cloudError.responseInfo.body}}</cloud-error>
<cloud-error v-else-if="cloudError"></cloud-error>
<cloud-error class="mb-2" v-if="cloudError && cloudError.exit === 'wrongInstallerExtension'">{{cloudError.responseInfo.body}}</cloud-error>
<cloud-error class="mb-2" v-else-if="cloudError && cloudError === 'couldNotReadVersion'">The Fleet instance could not read version information from the provided software installer.</cloud-error>
<cloud-error class="mb-2" v-else-if="cloudError && cloudError === 'softwareDeletionFailed'">This software has been configured to be installed as part of the macOS setup experience and cannot be removed from a team. Please remove this software from any teams you want to remove this from in the <a :href="`${fleetBaseUrl}/controls/setup-experience/install-software`" target="_blank">"Setup experience" tab of the Controls page</a> on your Fleet instance and try again </cloud-error>
<cloud-error class="mb-2" v-else-if="cloudError && cloudError === 'softwareAlreadyExistsOnThisTeam'">An error occured when transfering this software to a new team. A software installer with the same name as this software already exists on one or more of the selected teams.</cloud-error>
<cloud-error class="mb-2" v-else-if="cloudError"></cloud-error>
<div purpose="modal-buttons" class="d-flex flex-row justify-content-end align-items-center">
<ajax-button :syncing.sync="syncing" purpose="modal-button" type="submit">Save</ajax-button>
</div>
@ -189,6 +192,7 @@
</div>
<div class="invalid-feedback text-center" v-if="formErrors.teams">Please select the teams you want to deploy this software to.</div>
<cloud-error v-if="cloudError && cloudError === 'softwareAlreadyExistsOnThisTeam'">A software with the same name as the uploaded software already exists on one or more of the selected teams.</cloud-error>
<cloud-error v-if="cloudError && cloudError === 'couldNotReadVersion'">The Fleet instance could not read version information from the provided software installer.</cloud-error>
<cloud-error v-else-if="cloudError"></cloud-error>
<div purpose="modal-buttons" class="d-flex flex-row justify-content-end align-items-center">
<a purpose="cancel-button" @click="closeModal()">Cancel</a>