Create
cancel
Showing results for 
Search instead for 
Did you mean: 
Sign up Log in
Celebration

Earn badges and make progress

You're on your way to the next level! Join the Kudos program to earn points and save your progress.

Deleted user Avatar
Deleted user

Level 1: Seed

25 / 150 points

Next: Root

Avatar

1 badge earned

Collect

Participate in fun challenges

Challenges come and go, but your rewards stay with you. Do more to earn more!

Challenges
Coins

Gift kudos to your peers

What goes around comes around! Share the love by gifting kudos to your peers.

Recognition
Ribbon

Rise up in the ranks

Keep earning points to reach the top of the leaderboard. It resets every quarter so you always have a chance!

Leaderboard

Come for the products,
stay for the community

The Atlassian Community can help you and your team get more value out of Atlassian products and practices.

Atlassian Community about banner
4,559,547
Community Members
 
Community Events
184
Community Groups

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.
Jun 05, 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

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

Confluence administration->Retention rules

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.

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

 

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)
}
}
}

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.

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)
}

}
}
 

Thanks.
I will test it. 

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.
Jun 06, 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.
Jun 06, 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. 

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

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)
}
}
}

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.

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

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)
}
}
}
}

Oooo, going to try it now :)

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.

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.

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)}"
}
}
}
}

@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

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