Script to delete attachment's version

Nguyen Tran
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
June 5, 2019

I'm trying to write a script job to scan and delete attachment versions on a page.

For example on a page I have attach file A and reattach a few times to get 5 versions for file A. I want to write a script to delete the first 4 version.

Here's my attempt:

import com.atlassian.confluence.pages.Attachment
import com.atlassian.confluence.pages.AttachmentManager
import com.atlassian.sal.api.component.ComponentLocator


def attachmentManager = ComponentLocator.getComponent(AttachmentManager)

hits.each { attachment ->
def attachmentOnPage = attachmentManager.getAttachments(attachment)
if (attachmentOnPage.size() >1)
{
log.warn "attachmentOnPage: ${attachmentOnPage}"
log.warn "size: ${attachmentOnPage.size()}"
int size = attachmentOnPage.size()
int sizeMax = size - 1
List<String> attachmentDeleteList = new ArrayList<String>()
for(int i=0;i<size;i++)
{
if (i<sizeMax)
{
def attachmentSingle = attachmentOnPage.get(i)
attachmentDeleteList.add(attachmentSingle)
log.warn "attachmentDeleteList: ${attachmentDeleteList}"
//log.warn "attachmentSingle: ${attachmentSingle}"
//attachmentManager.removeAttachmentFromServer(attachmentSingle)
}
}
}
}

However, when I try to run the removeAttachmentFromServer method, it delete every versions and the file itself in the database. I also tried the removeAttachment(attachmentDeleteList) and it does the same thing. Is there any method that just delete the version not the attachment itself.

4 answers

0 votes
Jeff Abbott January 23, 2023

You can set retention rules to automatically remove attachments over a number/count.

Confluence administration->Retention rules

oib May 26, 2023

Yes, this 'Confluence Retention Rule' feature is a very good addition to Confluence.

The only problem I have with it, is that it is not really about data retention, as it only works on historic data (versions older than the latest). And trash bin.

In our test, we found that the system needed 6 weeks to calm down. That is, after 6 weeks to total page size of the site did not change significantly anymore. I only checked total pages number and not total attachments number. I assume, as it is in the same process, the background process works on both items simultaneously.

The important message I would like to convey is:
  Do not expect immediate results from this feature.

0 votes
Sinan Yildirim January 24, 2022

This REST Call is asking for confirmation. How to confirm it via script in Windows PowerShell?

https://www.your-conlfuence.com/pages/confirmattachmentversionremoval.action?pageId=######&fileName=<your file name>&version=n

 

0 votes
Rafał Żydek November 2, 2020

Here

 

import com.atlassian.confluence.spaces.SpaceManager
import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.confluence.pages.PageManager
import com.atlassian.confluence.pages.AttachmentManager

def attachments
def attachmentManager = ComponentLocator.getComponent(AttachmentManager)
def spaceManager = ComponentLocator.getComponent(SpaceManager)
def pageManager = ComponentLocator.getComponent(PageManager)
def space=spaceManager.getSpace('RUBIK')
def pages = pageManager.getPages(space, true)
for (page in pages) { //loop for every active page in the space
for (attachment in attachmentManager.getLatestVersionsOfAttachments(page)) { //list of latest attachments
attachmentManager.getPreviousVersions(attachment).each { //for every not latest version
attachmentManager.removeAttachmentVersionFromServer(it)
}
}
}
Piotr Nowak January 28, 2021

hi,
Thank you, the script is very useful.
Could anyone tell me how to modify this script to leave eg the last 5 versions of each attachment? I'm not a developer and I'm trying to modify it by trial and error.

Rafał Żydek January 28, 2021

Not final work, but only a concept. Good luck.

 

for (attachment in attachmentManager.getLatestVersionsOfAttachments(page)) { //list of latest attachments
def newestVersion = attachment.version //not sure if this is right field
attachmentManager.getPreviousVersions(attachment).each { //for every not latest version

//here need some logs to see if the results are sorted from the newest

//something like that:
if(it.version < newestVersion-5) {
attachmentManager.removeAttachmentVersionFromServer(it)
}

}
}
 
Piotr Nowak January 29, 2021

Thanks.
I will test it. 

Jeff Abbott January 23, 2023

I get the following error after each delete.
Object of class [com.atlassian.confluence.pages.Page] with identifier [6521421]: optimistic locking failed; nested exception is net.sf.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.atlassian.confluence.pages.Page#6521421]

0 votes
Bill Bailey
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
June 6, 2019

Well maybe it is easier to do via a REST call? The URL for deleting an attachment version takes the form of:

https://www.your-conlfuence.com/pages/confirmattachmentversionremoval.action?pageId=######&fileName=<your file name>&version=n

Where n is the version number. Now I don't know if you delete version 1, if the rest of the attachments will renumber so you will have to experiment (meaning you may have to delete version 1 four times in your case).

Nguyen Tran
Rising Star
Rising Star
Rising Stars are recognized for providing high-quality answers to other users. Rising Stars receive a certificate of achievement and are on the path to becoming Community Leaders.
June 6, 2019

We would like to automate this using the scheduled script job in Scriptrunner though. I've managed to make my script works. Will post the answer here shortly. 

Syed Shah
I'm New Here
I'm New Here
Those new to the Atlassian Community have posted less than three times. Give them a warm welcome!
December 6, 2019

Hey Nyugen, 

Any chance you got this script working - looking to do something similar so would be a great help.

Thanks

Like Alex Gregory likes this
Rafał Żydek November 2, 2020

I've made it.

 

import com.atlassian.confluence.spaces.SpaceManager
import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.confluence.pages.PageManager
import com.atlassian.confluence.pages.AttachmentManager

def attachments
def attachmentManager = ComponentLocator.getComponent(AttachmentManager)
def spaceManager = ComponentLocator.getComponent(SpaceManager)
def pageManager = ComponentLocator.getComponent(PageManager)
def space=spaceManager.getSpace('RUBIK')
def pages = pageManager.getPages(space, true)
for (page in pages) { //loop for every active page in the space

for (attachment in attachmentManager.getLatestVersionsOfAttachments(page)) { //list of latest attachments
attachmentManager.getPreviousVersions(attachment).each { //for every not latest version
attachmentManager.removeAttachmentVersionFromServer(it)
}
}
}
Steve Letch June 21, 2021

Thanks for this, quick question, do you know if this works fine on pages regardless of restrictions? 

 

I've been looking at using the built in scripts but they have massive limitations, such as only showing (even to an admin) pages they have explicit access to. This is due to the built in script abiding by the confines of the quick search bar, that doesn't give admins search capability on something they don't have explicit access to. Then to make matters worse, when I tied to get the space owner to run it, it errors out.

Steve Letch June 21, 2021

Also, do you know whether or not this triggers the content updated notification email or not? A space owner was bombarded with the notification emails after we used the built in script, which isn't useful if we want to do this in the background for many spaces.

 

Is it possible you could put up a variation that runs across the entire instance?

 

thanks

Steve Letch June 24, 2021
Rafał Żydek July 5, 2021

This is working on admin permission of app. Doesn't look on restrictions. 

I suggest to test it first on acc on few spaces. In some cases we have got problems with missing attachments.

 

About all spaces variant:

 

import com.atlassian.confluence.spaces.SpaceManager
import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.confluence.pages.PageManager
import com.atlassian.confluence.pages.AttachmentManager

def attachments
def attachmentManager = ComponentLocator.getComponent(AttachmentManager)
def spaceManager = ComponentLocator.getComponent(SpaceManager)
def pageManager = ComponentLocator.getComponent(PageManager)
def allSpaces = spaceManager.getAllSpaces()

for( def space : allSpaces)
{
def pages = pageManager.getPages(space, true)
for (page in pages) { //loop for every active page in the space
for (attachment in attachmentManager.getLatestVersionsOfAttachments(page)) { //list of latest attachments
attachmentManager.getPreviousVersions(attachment).each { //for every not latest version
attachmentManager.removeAttachmentVersionFromServer(it)
}
}
}
}

Steve Letch July 12, 2021

Oooo, going to try it now :)

Steve Letch July 12, 2021

Where do you run it from for it to not run as a regular user but to run as the system? Do you just put it in the jobs area of Scriptrunner? Although that does ask for a user to run as.

Rafał Żydek July 12, 2021

This script is based on java classes inside confluence. You need to run as Admin on Confluence to use it / run it.

 

Some scripts I think use current login user, other (script and function) doesn't need any authentication to run a code.

Steve Letch July 12, 2021

Ah ok so just stick it in the jobs area. It seems to slowly be running through it anyway, this ia n improvement over the Scriptrunner built in script as it always times out when using theirs because of proxy timeouts, even when raised to 10 minutes.

 

You may be able to help me with another issue. I need to bulk add a specific group to every page in my instance. Because of this issue:

https://jira.atlassian.com/browse/CONFSERVER-36393

 

But the script Adaptavist made for me doesn't work because of this issue:

https://jira.atlassian.com/browse/CONFSERVER-58536

 

This is the script below, do you have any ideas on how it could get to work?

import com.atlassian.confluence.core.ContentPermissionManager
import com.atlassian.confluence.pages.Page
import com.atlassian.confluence.pages.PageManager
import com.atlassian.confluence.security.ContentPermission
import com.atlassian.confluence.security.ContentPermissionSet
import com.atlassian.confluence.spaces.Space
import com.atlassian.confluence.spaces.SpaceManager
import com.atlassian.sal.api.component.ComponentLocator

import static com.atlassian.confluence.security.ContentPermission.EDIT_PERMISSION
import static com.atlassian.confluence.security.ContentPermission.VIEW_PERMISSION

/**
* This script runs through all spaces and pages (with true parameter in getPages, eagerly fetch permissions at the same time)
* and assign view permission to the 'groupToAddPermission' variable. If this group already has View or edit permission,
* the script does not make any change
*/
SpaceManager spaceManager = ComponentLocator.getComponent(SpaceManager)
PageManager pageManager = ComponentLocator.getComponent(PageManager)
ContentPermissionManager contentPermissionManager = ComponentLocator.getComponent(ContentPermissionManager)
final String groupToAddPermission = 'confluence-administrators'

ContentPermission viewPermission = ContentPermission.createGroupPermission(VIEW_PERMISSION, groupToAddPermission)
ContentPermission editPermission = ContentPermission.createGroupPermission(EDIT_PERMISSION, groupToAddPermission)
log.error "view Permission ${viewPermission}"
log.error "edit Permission ${editPermission}"

spaceManager.allSpaces.each { Space space ->
log.error "Space ${space}"
if(space.key == '<KEY>'){
pageManager.getPages(space, true).each { Page page ->
log.error "Page ${page}"
ContentPermissionSet viewContentPermissionSet = page.getContentPermissionSet(VIEW_PERMISSION)
ContentPermissionSet editContentPermissionSet = page.getContentPermissionSet(EDIT_PERMISSION)
log.error "viewContentPermissionSet: ${viewContentPermissionSet}"
log.error "editContentPermissionSet: ${editContentPermissionSet}"
boolean containsGroupPermissions = viewContentPermissionSet?.contains(viewPermission) || editContentPermissionSet?.contains(editPermission)
boolean containsAnyRestrictions = viewContentPermissionSet || editContentPermissionSet

if (!containsGroupPermissions && containsAnyRestrictions) {
log.error "Assign new Content"
contentPermissionManager.addContentPermission(viewPermission, page)
log.error "viewContentPermissionSet Result1: ${ page.getContentPermissionSet(VIEW_PERMISSION)}"
log.error "editContentPermissionSet Result1: ${ page.getContentPermissionSet(EDIT_PERMISSION)}"
}
}
}
}
Steve Letch October 25, 2021

@Rafał Żydek 

 

Do you think you could combine your script with the one further up, so it can run on all pages and also keep the last 5 versions?

 

Been having trouble trying to combine the two myself.

 

thanks

oib October 26, 2021

To be honest I don't have right now time to try this. From our side, we probably decided on that plugin. It can help us keeping always required numbers of old version pages.

 

Purge Versions for Confluence | Atlassian Marketplace

 

You can also try to use it as dry run.

Suggest an answer

Log in or Sign up to answer
TAGS
AUG Leaders

Atlassian Community Events