Artifactory clean up

Artifactory clean up

Last modified: June 2, 2020
You are here:
Estimated reading time: 10 min

Often we find that a customer’s artifactory is slowly getting bigger and bigger, but there is no process in place to maintain it – locating and removing un-needed artifacts.

The following will describe how to delete old artifacts from an Artifactory’s repository using Jenkins, groovy scripts and REST API to automate the process.

This process uses what JFrog calls User Plugins. You can read more about here: https://www.jfrog.com/confluence/display/JFROG/User+Plugins

There are sample user plugins here: https://github.com/JFrog/artifactory-user-plugins

The process outline

The process outline is as follows:

  1. Create administrative user in Artifactory
  2. Mark existing artifacts to be ignored by the cleanup plugin
  3. Create the Groovy script that will become the user plugins
  4. Copy the Groovy scripts (plugins) into plugin directory
  5. Load plugins into Artifactory using REST API
  6. Create a new logger for clean-up process
  7. Define a Jenkins pipeline to:
  • Execute the plugins (with different options)
  • Trigger Artifactory’s empty trash function
  • Trigger Artifactory’s Garbage collector process

Please note: In this scenario, I’ve chosen to control the plugins using Jenkins and setup the pipeline to run once a day using cron. It is possible to set the cron inside the plugin’s groovy script and let it run internally inside Artifactory without Jenkins.

Also: The user plugins can only be run on an Artifactory Pro server.

For code demo, assume you have an Artifactory Pro 7.3.2 running as Docker container on a Ubuntu 18.04.4 LTS machine.

Artifactory Docker run command

docker run --name artifactory --restart=always -v /art_pro_data:/var/opt/jfrog/artifactory -d -p 8081:8081 -p 8082:8082 docker.bintray.io/jfrog/artifactory-pro:7.3.2

Create administrative user in Artifactory

If you’re planing on controlling these plugins using Jenkins, you have to setup an Admin user in Artifactory and use it’s credentials in Jenkins’ pipeline. Only an Admin user can run certain action like loading plugins into the system.

Mark existing artifacts to be ignored by the cleanup plugin

If you already have a repository filled with artifacts, it’s easier to set the initial ‘do not delete’ property using Artifactory’s UI. After that, it’s better to add this property, automatically (using Jenkins), to each upload of an artifact. This will insure that the artifact(s) will never be deleted using the artifactCleanup plugin.

In Artifactory’s UI

  1. Select the artifact or path you wish to exclude from the delete (cleanup) process.
  2. Click the Properties tab
  3. Under Add: Property
  • Type cleanup.skip in the Name field
  • Type true in the Value field
  1. If you are setting this property to a path and you want all artifacts under this path to be set as well, check the Recursive option.
  2. Click Add

In Jenkins Pipeline

Add the props property with value cleanup.skip=true to the File Spec.

def upload_spec = """{
  "files": [{
    "pattern": "$archive_filename",
    "target": "$upload_dir/",
    "props": "cleanup.skip=true"
  }]
}"""

Create the Groovy script that will become the user plugins

For the process of artifacts cleanup we will need two groovy script:

  • artifactCleanup.groovy – This script will delete the artifacts.
  • deleteEmptyDirs.groovy – This script will delete the empty directories or paths left after the artifacts deletion.

I’ve started with downloading the above script from the user plugins github and than sligthly modified them to suite the customer needs.

The full script code is at the end of this readme.

Copy the Groovy scripts (plugins) into plugin directory

After you have updated the groovy scripts as needed (if it was neccasry), copy them into the following path inside Artifactory’s file system:

$JFROG_HOME/artifactory/var/etc/artifactory/plugins

For example, in my production server the extrenal (not inside the docker container) path is:

/mnt/Jfrog_Dev/artifactory/etc/artifactory/plugins

Load plugins into Artifactory using REST API

To load the plugins into Artifactory use the following REST API command. If you make changes to the scripts use this command to reload the pluging and the new changes.

curl -u admin:pass -X POST http://artifactory:8081/artifactory/api/plugins/reload

Note: If you setup a Jenkins pipeline to control this process, you can reload the plugins using Jenkins and don’t have to run the above command manually.

Create a new logger for clean-up process

All the logs created by the plugins are written into the master log (called console.log). It’s more convenient to have a separate logger for the cleanup plugins. Here are the instruction for creating such a log.

All logging settings are in file logback.xml. It’s an XML file and it’s located here:

$JFROG_HOME/artifactory/var/etc/artifactory/logback.xml

Open the file in a text editor, locate the block that start with:

<appender name="ACCESS" class="ch.qos.logback.core.rolling.RollingFileAppender">

and add the following after the end of the block:

<!-- User Plugins Appenders -->
<appender name="CLEANUP" class="ch.qos.logback.core.rolling.RollingFileAppender">
    <File>${log.dir}/artifactory-cleanup.log</File>
    <rollingPolicy class="org.jfrog.common.logging.logback.rolling.FixedWindowWithDateRollingPolicy">
        <FileNamePattern>${log.dir.archived}/artifactory-cleanup.%i.log.gz</FileNamePattern>
    </rollingPolicy>
    <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
        <MaxFileSize>25MB</MaxFileSize>
    </triggeringPolicy>
    <encoder class="ch.qos.logback.core.encoder.LayoutWrappingEncoder">
        <layout class="org.jfrog.common.logging.logback.layout.BackTracePatternLayout">
            <pattern>%date{yyyy-MM-dd'T'HH:mm:ss.SSS, UTC}Z [jfrt ] - %m%n</pattern>
        </layout>
    </encoder>
</appender>

Then, in the same file locate the section marked as <!-- specialized appenders --> and add the following just above it:

<!-- user plugins loggers -->
<logger name="artifactCleanup" level="info">
    <appender-ref ref="CLEANUP"/>
</logger>
<logger name="deleteEmptyDirs" level="info"/>

Note: If you have chosen to not create a separate logger, you can skip the part of adding the <appender> section. And if that is the case, then you need to add the following <logger> section instead:

<!-- user plugins loggers -->
<logger name="artifactCleanup" level="info"/>
<logger name="deleteEmptyDirs" level="info"/>

This will add the logging for the cleanup into the main logger only.

Location of logs in Artifactory

All logs are located in the following path:

$JFROG_HOME/artifactory/var/log

If you created a custom log it will be here:

$JFROG_HOME/artifactory/var/log/artifactory-cleanup.log

If not, the logs will be in the following path:

$JFROG_HOME/artifactory/var/log/console.log

Define a Jenkins pipeline

It’s possible to setup the cleanup plugins internally in Artifactory, but when controlling the plugins with Jenkins, you get more control over the plugins with Jenkins parameters combined with a pipeline.

The full Jenkins pipeline code is listed at the end of this readme file.

A little information on the piepline

Because the cleanup plugin has many parameters that control how it executes, I’ve created a parameter in the pipeline to correspond to each parameter in the cleanup script. All the parameter can be adjusted except the repos parameter. The reason this parameter is hard-codded to a specific repository is because, in my production server, there are two develop teams that use the same Artifactory server and I don’t want to delete the other team’s artifacts by mistake. I did setup the pipeline in such a way that you can easily make the necessary changes to make this option editable.

Note: All the parameters have default values, so the job can run using Jenkins cron trigger and perform the default clean periodically.

Groovy Scripts

artifactCleanup.groovy

import org.apache.commons.lang3.StringUtils
import org.artifactory.api.repo.exception.ItemNotFoundRuntimeException
import org.artifactory.exception.CancelException

import groovy.json.JsonSlurper
import groovy.time.TimeCategory
import groovy.time.TimeDuration
import groovy.transform.Field

import java.text.SimpleDateFormat

@Field final String CONFIG_FILE_PATH = "plugins/${this.class.name}.json"
@Field final String PROPERTIES_FILE_PATH = "plugins/${this.class.name}.properties"
@Field final String DEFAULT_TIME_UNIT = "day"
@Field final int DEFAULT_TIME_INTERVAL = 21 // 21 days = 3 weeks

class Global {
    static Boolean stopCleaning = false
    static Boolean pauseCleaning = false
    static int paceTimeMS = 0
}

def pluginGroup = 'cleaners'

executions {
    cleanup(groups: [pluginGroup]) { params ->
        def timeUnit = params['timeUnit'] ? params['timeUnit'][0] as String : DEFAULT_TIME_UNIT
        def timeInterval = params['timeInterval'] ? params['timeInterval'][0] as int : DEFAULT_TIME_INTERVAL
        def repos = params['repos'] as String[]
        def dryRun = params['dryRun'] ? new Boolean(params['dryRun'][0]) : true
        def disablePropertiesSupport = params['disablePropertiesSupport'] ? new Boolean(params['disablePropertiesSupport'][0]) : false
        def paceTimeMS = params['paceTimeMS'] ? params['paceTimeMS'][0] as int : 0
        
        // Enable fallback support for deprecated month parameter
        if ( params['months'] && !params['timeInterval'] ) {
            log.info('Deprecated month parameter is still in use, please use the new timeInterval parameter instead!', properties)
            timeInterval = params['months'][0] as int
        } else if ( params['months'] ) {
            log.warn('Deprecated month parameter and the new timeInterval are used in parallel: month has been ignored.', properties)
        }

        artifactCleanup(timeUnit, timeInterval, repos, log, paceTimeMS, dryRun, disablePropertiesSupport)
    }

    cleanupCtl(groups: [pluginGroup]) { params ->
        def command = params['command'] ? params['command'][0] as String : ''

        switch ( command ) {
            case "stop":
                Global.stopCleaning = true
                log.info "Stop request detected"
                break
            case "adjustPaceTimeMS":
                def adjustPaceTimeMS = params['value'] ? params['value'][0] as int : 0
                Global.paceTimeMS += adjustPaceTimeMS
                log.info "Pacing adjustment request detected, adjusting pace time by $adjustPaceTimeMS to new value of $Global.paceTimeMS"
                break
            case "pause":
                Global.pauseCleaning = true
                log.info "Pause request detected"
                break
            case "resume":
                Global.pauseCleaning = false
                log.info "Resume request detected"
                break
            default:
                log.info "Missing or invalid command, '$command'"
        }
    }
}

def deprecatedConfigFile = new File(ctx.artifactoryHome.etcDir, PROPERTIES_FILE_PATH)
def configFile = new File(ctx.artifactoryHome.etcDir, CONFIG_FILE_PATH)

if ( deprecatedConfigFile.exists() ) {

    if ( !configFile.exists() ) {
        def config = new ConfigSlurper().parse(deprecatedConfigFile.toURL())
        log.info "Schedule job policy list: $config.policies"

        config.policies.each{ policySettings ->
            def cron = policySettings[ 0 ] ? policySettings[ 0 ] as String : ["0 0 5 ? * 1"]
            def repos = policySettings[ 1 ] ? policySettings[ 1 ] as String[] : ["__none__"]
            def months = policySettings[ 2 ] ? policySettings[ 2 ] as int : 6
            def paceTimeMS = policySettings[ 3 ] ? policySettings[ 3 ] as int : 0
            def dryRun = policySettings[ 4 ] ? policySettings[ 4 ] as Boolean : false
            def disablePropertiesSupport = policySettings[ 5 ] ? policySettings[ 5 ] as Boolean : false

            jobs {
                "scheduledCleanup_$cron"(cron: cron) {
                    log.info "Policy settings for scheduled run at($cron): repo list($repos), timeUnit(month), timeInterval($months), paceTimeMS($paceTimeMS) dryrun($dryRun) disablePropertiesSupport($disablePropertiesSupport)"
                    artifactCleanup( "month", months, repos, log, paceTimeMS, dryRun, disablePropertiesSupport )
                }
            }
        }
    } else  {
        log.warn "Deprecated 'artifactCleanup.properties' file is still present, but ignored. You should remove the file."
    }
}

if ( configFile.exists() ) {
  
    def config = new JsonSlurper().parse(configFile.toURL())
    log.info "Schedule job policy list: $config.policies"

    config.policies.each{ policySettings ->
        def cron = policySettings.containsKey("cron") ? policySettings.cron as String : ["0 0 5 ? * 1"]
        def repos = policySettings.containsKey("repos") ? policySettings.repos as String[] : ["__none__"]
        def timeUnit = policySettings.containsKey("timeUnit") ? policySettings.timeUnit as String : DEFAULT_TIME_UNIT
        def timeInterval = policySettings.containsKey("timeInterval") ? policySettings.timeInterval as int : DEFAULT_TIME_INTERVAL
        def paceTimeMS = policySettings.containsKey("paceTimeMS") ? policySettings.paceTimeMS as int : 0
        def dryRun = policySettings.containsKey("dryRun") ? new Boolean(policySettings.dryRun) : false
        def disablePropertiesSupport = policySettings.containsKey("disablePropertiesSupport") ? new Boolean(policySettings.disablePropertiesSupport) : false

        jobs {
            "scheduledCleanup_$cron"(cron: cron) {
                log.info "Policy settings for scheduled run at($cron): repo list($repos), timeUnit($timeUnit), timeInterval($timeInterval), paceTimeMS($paceTimeMS) dryrun($dryRun) disablePropertiesSupport($disablePropertiesSupport)"
                artifactCleanup( timeUnit, timeInterval, repos, log, paceTimeMS, dryRun, disablePropertiesSupport )
            }
        }
    }  
}

if ( deprecatedConfigFile.exists() && configFile.exists() ) {
    log.warn "The deprecated artifactCleanup.properties and the new artifactCleanup.json are defined in parallel. You should migrate the old file and remove it."
}

private def artifactCleanup(String timeUnit, int timeInterval, String[] repos, log, paceTimeMS, dryRun = false, disablePropertiesSupport = false) {
    log.info "Starting artifact cleanup for repositories $repos, until $timeInterval ${timeUnit}s ago with pacing interval $paceTimeMS ms, dryrun: $dryRun, disablePropertiesSupport: $disablePropertiesSupport"

    // Create Map(repo, paths) of skiped paths (or others properties supported in future ...)
    def skip = [:]
    if ( ! disablePropertiesSupport && repos){
        skip = getSkippedPaths(repos)
    }

    def calendarUntil = Calendar.getInstance()

    calendarUntil.add(mapTimeUnitToCalendar(timeUnit), -timeInterval)

    def calendarUntilFormatted = new SimpleDateFormat("yyyy/MM/dd HH:mm").format(calendarUntil.getTime());
    log.info "Removing all artifacts not downloaded since $calendarUntilFormatted"

    Global.stopCleaning = false
    int cntFoundArtifacts = 0
    int cntNoDeletePermissions = 0
    long bytesFound = 0
    long bytesFoundWithNoDeletePermission = 0
    
    //def artifactsCleanedUp = searches.artifactsNotDownloadedSince(calendarUntil, calendarUntil, repos)
    def artifactsCleanedUp = searches.artifactsCreatedOrModifiedInRange(null, calendarUntil, repos)
    artifactsCleanedUp.find {
        try {
            while ( Global.pauseCleaning ) {
                log.info "Pausing by request"
                sleep( 60000 )
            }

            if ( Global.stopCleaning ) {
                log.info "Stopping by request, ending loop"
                return true
            }

            if ( ! disablePropertiesSupport && skip[ it.repoKey ] && StringUtils.startsWithAny(it.path, skip[ it.repoKey ])){
                if (log.isDebugEnabled()){
                    log.debug "Skip $it"
                }
                return false
            }

            bytesFound += repositories.getItemInfo(it)?.getSize()
            cntFoundArtifacts++
            if (!security.canDelete(it)) {
                bytesFoundWithNoDeletePermission += repositories.getItemInfo(it)?.getSize()
                cntNoDeletePermissions++
            }
            if (dryRun) {
                log.info "Found $it, $cntFoundArtifacts/$artifactsCleanedUp.size total $bytesFound bytes"
                log.info "\t==> currentUser: ${security.currentUser().getUsername()}"
                log.info "\t==> canDelete: ${security.canDelete(it)}"

            } else {
                if (security.canDelete(it)) {
                    log.info "Deleting $it, $cntFoundArtifacts/$artifactsCleanedUp.size total $bytesFound bytes"
                    repositories.delete it
                } else {
                    log.info "Can't delete $it (user ${security.currentUser().getUsername()} has no delete permissions), " +
                            "$cntFoundArtifacts/$artifactsCleanedUp.size total $bytesFound bytes"
                }
            }
        } catch (ItemNotFoundRuntimeException ex) {
            log.info "Failed to find $it, skipping"
        }

        def sleepTime = (Global.paceTimeMS > 0) ? Global.paceTimeMS : paceTimeMS
        if (sleepTime > 0) {
            sleep( sleepTime )
        }

        return false
    }

    if (dryRun) {
        log.info "Dry run - nothing deleted. Found $cntFoundArtifacts artifacts consuming $bytesFound bytes"
        if (cntNoDeletePermissions > 0) {
            log.info "$cntNoDeletePermissions artifacts cannot be deleted due to lack of permissions ($bytesFoundWithNoDeletePermission bytes)"
        }
    } else {
        log.info "Finished cleanup, deleting $cntFoundArtifacts artifacts that took up $bytesFound bytes"
        if (cntNoDeletePermissions > 0) {
            log.info "$cntNoDeletePermissions artifacts could not be deleted due to lack of permissions ($bytesFoundWithNoDeletePermission bytes)"
        }
    }
}

private def getSkippedPaths(String[] repos) {
    def timeStart = new Date()
    def skip = [:]
    for (String repoKey : repos){
        def pathsTmp = []
        def aql = "items.find({\"repo\":\"" + repoKey + "\",\"type\": \"any\",\"@cleanup.skip\":\"true\"}).include(\"repo\", \"path\", \"name\", \"type\")"
        searches.aql(aql.toString()) {
            for (item in it) {
                def path = item.path + '/' + item.name
                // Root path case behavior
                if ('.' == item.path){
                    path = item.name
                }
                if ('folder' == item.type){
                    path += '/'
                }
                if (log.isTraceEnabled()){
                    log.trace "skip found for " + repoKey + ":" + path
                }
                pathsTmp.add(path)
            }
        }

        // Simplify list to have only parent paths
        def paths = []
        for (path in pathsTmp.sort{ it }) {
            if (paths.size == 0 || ! path.startsWith(paths[-1])) {
                if (log.isTraceEnabled()){
                    log.trace "skip added for " + repoKey + ":" + path
                }
                paths.add(path)
            }
        }

        if (paths.size > 0){
            skip[repoKey] = paths.toArray(new String[paths.size])
        }
    }
    def timeStop = new Date()
    TimeDuration duration = TimeCategory.minus(timeStop, timeStart)
    log.info "Elapsed time to retrieve paths to skip: " + duration
    return skip
}

private def mapTimeUnitToCalendar (String timeUnit) {
    switch ( timeUnit ) {
        case "minute":
            return Calendar.MINUTE
        case "hour":
            return Calendar.HOUR
        case "day":
            return Calendar.DAY_OF_YEAR
        case "month":
            return Calendar.MONTH
        case "year":
            return Calendar.YEAR
        default:
            def errorMessage = "$timeUnit is no valid time unit. Please check your request or scheduled policy."
            log.error errorMessage
            throw new CancelException(errorMessage, 400)
    }
}

deleteEmptyDirs.groovy

import groovy.transform.Field
import org.artifactory.repo.RepoPath

import static java.lang.Thread.sleep
import static org.artifactory.repo.RepoPathFactory.create

/**
 *
 * @originalAuthor jbaruch
 * @since 16/08/12
 *
 * @adaptedForRadwinBy boris_t
 * @since 14/04/20
 *
 */

executions {

    deleteEmptyDirsPlugin(version: '1.1', description: 'Deletes empty directories', users: ['admin'].toSet()) { params ->
        if (!params || !params.paths) {
            def errorMessage = 'Paths parameter is mandatory, please supply it.'
            log.error errorMessage
            status = 400
            message = errorMessage
        } else {
            deleteEmptyDirectories(params.paths as String[])
        }
    }
}

private def deleteEmptyDirectories(String[] paths) {
    def totalDeletedDirs = 0
    paths.each {
        log.info "Beginning deleting empty directories for path($it)"
        def deletedDirs = deleteEmptyDirsRecursively create(it)
        log.info "Deleted($deletedDirs) empty directories for given path($it)"
        totalDeletedDirs += deletedDirs
    }
    log.info "Finished deleting total($totalDeletedDirs) directories"
}

def deleteEmptyDirsRecursively(RepoPath path) {
    def deletedDirs = 0
    // let's let other threads to do something.
    sleep 50
    // if not folder - we're done, nothing to do here
    if (repositories.getItemInfo(path).folder) {
        def children = repositories.getChildren path
        children.each {
            deletedDirs += deleteEmptyDirsRecursively it.repoPath
        }
        // now let's check again
        if (repositories.getChildren(path).empty) {
            // it is folder, and no children - delete!
            log.info "Deleting empty directory($path)"
            repositories.delete path
            deletedDirs += 1
        }
    }

    return deletedDirs
}

Full Jenkins pipeline code

properties([
    parameters([
        [$class: 'ChoiceParameter', 
            name: 'Reload_Plugins', 
            choiceType: 'PT_SINGLE_SELECT', 
            description: '<font size=3>Reload Plugins? Select Yes or No.</font>', 
            filterLength: 1, 
            filterable: false, 
            randomName: 'choice-parameter-108460119586981', 
            script: [
                $class: 'GroovyScript', 
                fallbackScript: [
                    classpath: [], 
                    sandbox: false, 
                    script: 
                        ''
                ], 
                script: [
                    classpath: [], 
                    sandbox: false, 
                    script: 
                        'return ["YES","NO:selected"]'
                ]
            ]
        ], 
        [$class: 'DynamicReferenceParameter', 
            name: 'Dry_Run',
            choiceType: 'ET_FORMATTED_HTML', 
            description: '',
            randomName: 'choice-parameter-108440134895850', 
            referencedParameters: 'Reload_Plugins', 
            omitValueField: true,
            script: [
                $class: 'GroovyScript', 
                fallbackScript: [
                    classpath: [], 
                    sandbox: false, 
                    script: ''
                ], 
                script: [
                    classpath: [], 
                    sandbox: false, 
                    script: '''
                        if (Reload_Plugins.equals("YES")) {
                            return "<table style=width:100%><tr><td align=left><input name=value type=checkbox disabled></td><td align=left width=100%><font size=3>N/A</font></td></tr><tr><td colspan=2><font size=3>Dry Run? Check this option if you wish to run without <b>deleting</b></span> artifacts.</font></td></tr></table>"
                        }

                        return "<table style=width:100%><tr><td align=left><input name=value type=checkbox></td></tr><tr><td><font size=3>Dry Run? Check this option if you wish to run without <b>deleting</b></span> artifacts.</font></td></tr></table>"
                    '''
                ]
            ]
        ],
        [$class: 'CascadeChoiceParameter',
            choiceType: 'PT_SINGLE_SELECT',
            description: '<font size=3>The unit (type) of the time interval.</font>',
            filterLength: 1,
            filterable: false,
            name: 'Time_Unit',
            randomName: 'choice-parameter-2930846449195033',
            referencedParameters: 'Reload_Plugins',
            script:
                [$class: 'GroovyScript',
                fallbackScript:
                    [classpath: [],
                    sandbox: false,
                    script: ''
                    ],
                    script:
                    [classpath: [],
                    sandbox: false,
                        script: '''
                            
                            if (Reload_Plugins.equals("YES")) {
                                return ["N/A:selected"]
                            }

                            return ["Year", "Month", "Day:selected", "Hour", "Minute"]
                        '''
                    ]
                ]
            ],
        [$class: 'DynamicReferenceParameter', 
            name: 'Time_Interval',
            choiceType: 'ET_FORMATTED_HTML', 
            description: '',
            randomName: 'choice-parameter-108460134875850', 
            referencedParameters: 'Reload_Plugins', 
            omitValueField: true,
            script: [
                $class: 'GroovyScript', 
                fallbackScript: [
                    classpath: [], 
                    sandbox: false, 
                    script: ''
                ], 
                script: [
                    classpath: [], 
                    sandbox: false, 
                    script: 
                        '''
                            if (Reload_Plugins.equals("YES")) {
                                return "<table style=width:100%><tr><td align=left><input name=value type=text value='N/A' disabled></td></tr><tr><td><font size=3><font size=3>The time interval to look back before deleting an artifact.</font></font></td></tr></table>"
                            }

                            return "<table style=width:100%><tr><td align=left><input name=value type=text value='21'></td></tr><tr><td><font size=3><font size=3>The time interval to look back before deleting an artifact.</font></font></td></tr></table>"
                        '''
                ]
            ]
        ],
        [$class: 'DynamicReferenceParameter', 
            name: 'Target_Repositories',
            choiceType: 'ET_FORMATTED_HTML', 
            description: '',
            randomName: 'choice-parameter-108461134575850', 
            referencedParameters: 'Reload_Plugins', 
            omitValueField: true,
            script: [
                $class: 'GroovyScript', 
                fallbackScript: [
                    classpath: [], 
                    sandbox: false, 
                    script: ''
                ], 
                script: [
                    classpath: [], 
                    sandbox: false, 
                    script: 
                        '''
                            if (Reload_Plugins.equals("YES")) {
                                return "<table style=width:100%><tr><td align=left><input name=value type=text value='N/A' disabled></td></tr><tr><td><font size=3>A list of repositories to clean. This parameter is hardcoded to Segway repo.</font></td></tr></table>"
                            }

                            return "<table style=width:100%><tr><td align=left><input name=value type=text value='Segway' disabled></td></tr><tr><td><font size=3>A list of repositories to clean. This parameter is hardcoded to Segway repo.</font></td></tr></table>"
                        '''
                ]
            ]
        ],
        [$class: 'DynamicReferenceParameter', 
            name: 'Pace_Time_ms',
            choiceType: 'ET_FORMATTED_HTML', 
            description: '',
            randomName: 'choice-parameter-108260134873850', 
            referencedParameters: 'Reload_Plugins', 
            omitValueField: true,
            script: [
                $class: 'GroovyScript', 
                fallbackScript: [
                    classpath: [], 
                    sandbox: false, 
                    script: ''
                ], 
                script: [
                    classpath: [], 
                    sandbox: false, 
                    script: 
                        '''
                            if (Reload_Plugins.equals("YES")) {
                                return "<table style=width:100%><tr><td align=left><input name=value type=text value='N/A' disabled></td></tr><tr><td><font size=3>The number of milliseconds to delay between delete operations</font></td></tr></table>"
                            }

                            return "<table style=width:100%><tr><td align=left><input name=value type=text value='0'></td></tr><tr><td><font size=3>The number of milliseconds to delay between delete operations</font></td></tr></table>"
                        '''
                ]
            ]
        ],
        [$class: 'DynamicReferenceParameter', 
            name: 'Ignore_Properties',
            choiceType: 'ET_FORMATTED_HTML', 
            description: '',
            randomName: 'choice-parameter-118460134875857', 
            referencedParameters: 'Reload_Plugins', 
            omitValueField: true,
            script: [
                $class: 'GroovyScript', 
                fallbackScript: [
                    classpath: [], 
                    sandbox: false, 
                    script: ''
                ], 
                script: [
                    classpath: [], 
                    sandbox: false, 
                    script: 
                        '''
                            if (Reload_Plugins.equals("YES")) {
                                return "<table style=width:100%><tr><td align=left><input name=value type=checkbox disabled></td><td align=left width=100%><font size=3>N/A</font></td></tr><tr><td colspan=2><font size=3>Ignore Artifacts Properties? Check this option if you wish to <b>ignore aertifactory properties</b></span>. <b>NOT RECOMMENDET!</b></font></td></tr></table>"
                            }

                            return "<table style=width:100%><tr><td align=left><input name=value type=checkbox></td></tr><tr><td><font size=3>Ignore Artifacts Properties? Check this option if you wish to <b>ignore aertifactory properties</b></span>. <b>NOT RECOMMENDET!</b></font></td></tr></table>"
                        '''
                ]
            ]
        ],        
    ])
])

// Get/Set Parameters values
def reload_plugins = (env.Reload_Plugins == "YES") ? true : false
def dry_run = (env.Dry_Run == "true") ? true : false
def time_unit = env.Time_Unit.toLowerCase()
def time_interval = env.Time_Interval.isInteger() ? env.Time_Interval.toInteger() : 21 // default = 21
def target_repositories = env.Target_Repositories
def pace_time_ms = env.Pace_Time_ms.isInteger() ? env.Pace_Time_ms.toInteger() : 0 // default = 0
def ignore_properties = (env.Ignore_Properties == "true") ? true : false

// Artifactory API vars
def artifactory_api = "http://artifactory:8081/artifactory/api"
def execute_cleaup = artifactory_api + "/plugins/execute/cleanup?params="
def execute_del_dir = artifactory_api + "/plugins/execute/deleteEmptyDirsPlugin?params="

pipeline {
    agent any
    stages {
        stage('Reload Plugins') {
            when { expression { return reload_plugins } }
            steps {
                script {
                    withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'jenkins_artifactory_admin_user', usernameVariable: 'username', passwordVariable: 'password']]) {
                        def encoded_pass = URLEncoder.encode(password, "UTF-8")
                        sh """
                            curl -u $username:$encoded_pass -X POST "$artifactory_api/plugins/reload"
                        """
                    }
                }               
            }
        }  // End stage Reload Plugins
        stage('Cleanup Plugin') {
            when { expression { return !reload_plugins } }
            steps {
                script {
                    def curl_uri = execute_cleaup
                    // Add params
                    curl_uri += "timeUnit=$time_unit;"
                    curl_uri += "timeInterval=$time_interval;"
                    curl_uri += "repos=$target_repositories;"
                    curl_uri += "dryRun=$dry_run;"
                    curl_uri += "paceTimeMS=$pace_time_ms;"
                    curl_uri += "disablePropertiesSupport=$ignore_properties"
                    withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'jenkins_artifactory_admin_user', usernameVariable: 'username', passwordVariable: 'password']]) {
                        def encoded_pass = URLEncoder.encode(password, "UTF-8")
                        sh """
                            curl -i -u $username:$encoded_pass -X POST "$curl_uri"
                        """
                    }
                }
            }
        }  // End stage Cleanup Plugin
        stage('Delete Empty Dirs Plugin') {
            // Run this stage only if reload_plugins=false and dry_run=false
            when { expression { return (!reload_plugins && !dry_run) } }
            steps {
                 script {
                    def curl_uri = execute_del_dir
                    // Add params
                    curl_uri += "paths=$target_repositories"
                    withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'jenkins_artifactory_admin_user', usernameVariable: 'username', passwordVariable: 'password']]) {
                        def encoded_pass = URLEncoder.encode(password, "UTF-8")
                        sh """
                            curl -i -u $username:$encoded_pass -X POST "$curl_uri"
                        """
                    }
                }
            }
        }  // End stage Delete Empty Dirs Plugin
        stage('Empty Trash Can') {
            // Run this stage only if reload_plugins=false and dry_run=false
            when { expression { return (!reload_plugins && !dry_run) } }
            steps {
                script {
                    withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'jenkins_artifactory_admin_user', usernameVariable: 'username', passwordVariable: 'password']]) {
                        def encoded_pass = URLEncoder.encode(password, "UTF-8")
                        sh """
                            curl -u $username:$encoded_pass -X POST "$artifactory_api/trash/empty"
                        """
                    }
                }
            }
        }  // End stage Empty Trash Can
        stage('Garbage Collector') {
            // Run this stage only if reload_plugins=false and dry_run=false
            when { expression { return (!reload_plugins && !dry_run) } }
            steps {
                script {
                    withCredentials([[$class: 'UsernamePasswordMultiBinding', credentialsId: 'jenkins_artifactory_admin_user', usernameVariable: 'username', passwordVariable: 'password']]) {
                        def encoded_pass = URLEncoder.encode(password, "UTF-8")
                        sh """
                            curl -u $username:$encoded_pass -X POST "$artifactory_api/system/storage/gc"
                        """
                    }
                }
            }
        }  // End stage Garbage Collector
    }
}

Related Artifactory REST API Commands

Reload Plugins

curl -u admin:pass -X POST http://artifactory:8081/artifactory/api/plugins/reload

Retrieve Plugin Info

curl -u admin:pass -X GET http://artifactory:8081/artifactory/api/plugins

Execute Cleanup Artifacts Plugin (severel variations)

curl -i -u admin:pass -X POST "http://artifactory:8081/artifactory/api/plugins/execute/cleanup?params=timeUnit=month;timeInterval=20;repos=Segway;dryRun=true;paceTimeMS=2;disablePropertiesSupport=false"

curl -i -u admin:pass -X POST "http://artifactory:8081/artifactory/api/plugins/execute/cleanup?params=timeUnit=hour;timeInterval=1;repos=Segway;dryRun=true;paceTimeMS=2;disablePropertiesSupport=false"

curl -i -u admin:pass -X POST "http://artifactory:8081/artifactory/api/plugins/execute/cleanup?params=timeUnit=month;timeInterval=3;repos=Segway;dryRun=true;disablePropertiesSupport=false"

curl -i -u admin:pass -X POST "http://artifactory:8081/artifactory/api/plugins/execute/cleanup?params=timeUnit=month;timeInterval=20;repos=Segway;dryRun=true"

curl -i -u admin:pass -X POST "http://artifactory:8081/artifactory/api/plugins/execute/cleanup?params=timeUnit=month;timeInterval=5;repos=Segway;dryRun=true"

curl -i -u admin:pass -X POST "http://artifactory:8081/artifactory/api/plugins/execute/cleanup?params=repos=Segway;dryRun=true;disablePropertiesSupport=false"

Execute Delete Empty Dirs Plugin

curl -i -u admin:pass -X POST "http://artifactory:8081/artifactory/api/plugins/execute/deleteEmptyDirsPlugin?params=paths=Segway"

Empty Trash Can

curl -i -u admin:pass -X POST "http://artifactory:8081/artifactory/api/trash/empty"

Run Garbage Collection

curl -i -u admin:pass -X POST "http://artifactory:8081/artifactory/api/system/storage/gc"
Was this article helpful?
Dislike 0
Views: 1235
Go to Top