Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Msp dashboard: Update software endpoints #24299

Merged
merged 4 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 92 additions & 15 deletions ee/bulk-operations-dashboard/api/controllers/software/edit-software.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
},


Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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,
Expand All @@ -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!');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.'
}

},
Expand Down Expand Up @@ -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);
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: 2})}`);
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};
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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>
Expand Down
Loading