├── .git-blame-ignore-revs ├── .github ├── dependabot.yml └── workflows │ └── jenkins-security-scan.yml ├── .gitignore ├── .mvn ├── extensions.xml └── maven.config ├── Jenkinsfile ├── README.md ├── images ├── ManagementLink.png ├── backupStore.png ├── restore.png ├── thinBackup.png ├── thinBackupOld.png └── thinBackupSettings.png ├── pom.xml └── src ├── main ├── java │ └── org │ │ └── jvnet │ │ └── hudson │ │ └── plugins │ │ └── thinbackup │ │ ├── ThinBackupMgmtLink.java │ │ ├── ThinBackupPeriodicWork.java │ │ ├── ThinBackupPluginImpl.java │ │ ├── backup │ │ ├── BackupSet.java │ │ ├── DirectoriesZipper.java │ │ ├── HudsonBackup.java │ │ └── PluginList.java │ │ ├── restore │ │ ├── HudsonRestore.java │ │ └── PluginRestoreUpdateCenter.java │ │ └── utils │ │ ├── EnvironmentVariableNotDefinedException.java │ │ ├── ExistsAndReadableFileFilter.java │ │ └── Utils.java ├── resources │ ├── index.jelly │ └── org │ │ └── jvnet │ │ └── hudson │ │ └── plugins │ │ └── thinbackup │ │ ├── ThinBackupMgmtLink │ │ ├── index.jelly │ │ ├── index.properties │ │ ├── index_de.properties │ │ ├── restoreOptions.jelly │ │ ├── restoreOptions.properties │ │ └── restoreOptions_de.properties │ │ └── ThinBackupPluginImpl │ │ ├── config.jelly │ │ ├── config.properties │ │ ├── config_de.properties │ │ ├── help-backupAdditionalFiles.html │ │ ├── help-backupAdditionalFiles_de.html │ │ ├── help-backupBuildArchive.html │ │ ├── help-backupBuildArchive_de.html │ │ ├── help-backupBuildResults.html │ │ ├── help-backupBuildResults_de.html │ │ ├── help-backupBuildsToKeepOnly.html │ │ ├── help-backupBuildsToKeepOnly_de.html │ │ ├── help-backupConfigHistory.html │ │ ├── help-backupConfigHistory_de.html │ │ ├── help-backupNextBuildNumber.html │ │ ├── help-backupNextBuildNumber_de.html │ │ ├── help-backupPath.html │ │ ├── help-backupPath_de.html │ │ ├── help-backupPluginArchives.html │ │ ├── help-backupPluginArchives_de.html │ │ ├── help-backupUserContents.html │ │ ├── help-backupUserContents_de.html │ │ ├── help-cleanupDiff.html │ │ ├── help-cleanupDiff_de.html │ │ ├── help-diffBackupSchedule.html │ │ ├── help-diffBackupSchedule_de.html │ │ ├── help-excludedFilesRegex.html │ │ ├── help-excludedFilesRegex_de.html │ │ ├── help-failFast.html │ │ ├── help-failFast_de.html │ │ ├── help-forceQuietModeTimeout.html │ │ ├── help-forceQuietModeTimeout_de.html │ │ ├── help-fullBackupSchedule.html │ │ ├── help-fullBackupSchedule_de.html │ │ ├── help-moveOldBackupsToZipFile.html │ │ ├── help-moveOldBackupsToZipFile_de.html │ │ ├── help-nrMaxStoredFull.html │ │ ├── help-nrMaxStoredFull_de.html │ │ ├── help-waitForIdle.html │ │ └── help-waitForIdle_de.html └── webapp │ └── help │ ├── help-restore.html │ ├── help-restoreNextBuildNumber.html │ ├── help-restoreNextBuildNumber_de.html │ ├── help-restorePlugins.html │ ├── help-restorePlugins_de.html │ └── help-restore_de.html └── test ├── java └── org │ └── jvnet │ └── hudson │ └── plugins │ └── thinbackup │ ├── TestConfigWithUi.java │ ├── TestHelper.java │ ├── TestJenkinsConfigAsCode.java │ ├── TestJenkinsWithOldConfig.java │ ├── TestThinBackupPeriodicWork.java │ ├── backup │ ├── BackupDirStructureSetup.java │ ├── TestBackupMatrixJob.java │ ├── TestBackupMultibranchJob.java │ ├── TestBackupPromotedJob.java │ ├── TestBackupSet.java │ ├── TestBackupWithCloudBeesFolder.java │ ├── TestBackupZipping.java │ ├── TestHudsonBackup.java │ └── TestPluginList.java │ ├── restore │ └── TestRestore.java │ └── utils │ └── TestUtils.java └── resources ├── configuration-as-code.yml └── org └── jvnet └── hudson └── plugins └── thinbackup └── TestJenkinsWithOldConfig └── thinBackup.xml /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # .git-blame-ignore-revs 2 | # Format code with Spotless (#129) 3 | 1b3fc78013924a7887abea6863145918f1e5d53d 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "maven" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | # Add assignees 13 | assignees: 14 | - "StefanSpieker" 15 | -------------------------------------------------------------------------------- /.github/workflows/jenkins-security-scan.yml: -------------------------------------------------------------------------------- 1 | name: Jenkins Security Scan 2 | on: 3 | push: 4 | branches: 5 | - "master" 6 | pull_request: 7 | types: [ opened, synchronize, reopened ] 8 | workflow_dispatch: 9 | 10 | permissions: 11 | security-events: write 12 | contents: read 13 | actions: read 14 | 15 | jobs: 16 | security-scan: 17 | uses: jenkins-infra/jenkins-security-scan/.github/workflows/jenkins-security-scan.yaml@v2 18 | with: 19 | java-cache: 'maven' # Optionally enable use of a build dependency cache. Specify 'maven' or 'gradle' as appropriate. 20 | # java-version: 21 # Optionally specify what version of Java to set up for the build, or remove to use a recent default. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /.settings 3 | /.classpath 4 | /.project 5 | /work 6 | *~ 7 | # IntelliJ project files 8 | *.iml 9 | *.iws 10 | *.ipr 11 | /.idea/* 12 | -------------------------------------------------------------------------------- /.mvn/extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | io.jenkins.tools.incrementals 4 | git-changelist-maven-extension 5 | 1.8 6 | 7 | 8 | -------------------------------------------------------------------------------- /.mvn/maven.config: -------------------------------------------------------------------------------- 1 | -Pconsume-incrementals 2 | -Pmight-produce-incrementals 3 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env groovy 2 | 3 | /* `buildPlugin` step provided by: https://github.com/jenkins-infra/pipeline-library */ 4 | buildPlugin( 5 | useContainerAgent: true, 6 | configurations: [ 7 | [platform: 'linux', jdk: 21], 8 | [platform: 'windows', jdk: 17], 9 | ]) 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Thin Backup Plugin 2 | 3 | [![Build Status](https://ci.jenkins.io/job/Plugins/job/thin-backup-plugin/job/master/badge/icon)](https://ci.jenkins.io/job/Plugins/job/thin-backup-plugin/job/master/) 4 | [![GitHub release](https://img.shields.io/github/v/release/jenkinsci/thin-backup-plugin.svg?label=release)](https://github.com/jenkinsci/thin-backup-plugin/releases/latest) 5 | [![Jenkins Plugin Installs](https://img.shields.io/jenkins/plugin/i/thinBackup.svg?color=blue)](https://plugins.jenkins.io/thinBackup/) 6 | 7 | This plugin simply backs up the global and job specific configurations (not the archive or the workspace). It can be scheduled and only backs up the most vital configuration info. It also let's you decide what vital means for you. For example it does backup the credentials.xml, but will not backup the server keys. Therefore any backup, which is used on an fresh server, will see the credentials, but is not able to correctly decrypt them. Therefore the credentials will be corrupt. So any migration use case will need the secrets additionally, or make use of the backup additional files feature. 8 | Be aware that in general the controller key should be stored somewhere else, as stated in the Jenkins [backup guidelines](https://www.jenkins.io/doc/book/system-administration/backing-up/#back-up-the-controller-key-separately). 9 | 10 | 11 | * [Thin Backup Plugin](#thin-backup-plugin) 12 | * [Documentation](#documentation) 13 | * [Backup Now](#backup-now) 14 | * [Restore](#restore) 15 | * [Settings](#settings) 16 | * [Jenkins Configuration as Code (JCasC) support](#jenkins-configuration-as-code-jcasc-support) 17 | * [Backup process](#backup-process) 18 | * [Feature requests or bug reports](#feature-requests-or-bug-reports) 19 | 20 | ## Documentation 21 | 22 | This plugin adds a management link to "Manage Jenkins" in the Tools section, called ThinBackup which looks like this: 23 | 24 | ![](images/ManagementLink.png) 25 | 26 | This link provides the following actions: 27 | 28 | ![](images/thinBackup.png) 29 | 30 | **Note:** If you are using a version older than 2.0 this will also contain a settings button, as illustrated in [this screenshot](/images/thinBackupOld.png) 31 | 32 | ### Backup Now 33 | 34 | Triggers a manual full back up right now. 35 | 36 | ### Restore 37 | 38 | ![](images/restore.png) 39 | 40 | Select the data of the backup you would like to restore. After the restore finishes you are 41 | redirected to the plugin main page. To activate the restored settings you need to restart Jenkins. 42 | 43 | **Note**: The file **nextBuildNumber** will not be backed up or restored to prevent buildnumber 44 | collision by default. The archive and the workspace will not be deleted or changed, but all backed 45 | up files will simply be overwritten with the restored versions (e.g., `config.xml`, `thinBackup.xml` → for more info simply look in a backup). 46 | 47 | ![](images/backupStore.png). 48 | 49 | #### Restore next build number file (if found in backup) 50 | 51 | If this option is enabled, the file nextBuildNumber will also be restored. 52 | 53 | **Note**: Take special care when restoring a backup that contains a `nextBuildNumber` file, as this 54 | may potentially cause a lot of problems. 55 | 56 | #### Restore plugins 57 | 58 | If this option is enabled, the plugins get restored. You need an active internet connection to the 59 | update server, because plugins will be downloaded from the update server to keep the backup small. 60 | 61 | ### Settings 62 | 63 | **Note:** The settings are present in the global configuration since version 2.0 64 | 65 | ![](/images/thinBackupSettings.png) 66 | 67 | #### Backup directory 68 | 69 | Specify the backup directory. The Jenkins process needs write access to this directory. You can 70 | use environment variables like `${JENKINS_WORKSPACE}` to specify the path. 71 | 72 | #### Backup schedule for full backups 73 | 74 | Specify schedule when a full backup is triggered. Cron notation is used. A full backup backs up 75 | all files even if there were no changes. 76 | 77 | #### Backup schedule for differential backups 78 | 79 | Specify schedule when a differential backup is triggered. Cron notation is used. A differential 80 | backup stores only modified data since the last full backup. If there are no changes detected, no 81 | diff backup will be created. 82 | 83 | **Note**: You do not need to specify a differential backup schedule if you only need full backups. 84 | Because differential backups depend on full backups, a schedule for full backups is mandatory if you 85 | specify a differential backup schedule. 86 | 87 | #### Wait until Jenkins is idle to perform a backup 88 | 89 | It is very recommended to enable this option (default). Nevertheless, many users report that the 90 | quiet mode is blocking execution of long-running jobs. If you disable this option, then the backup 91 | will be made without waiting for a safe state of your instance. In other words, the backup will be 92 | done immediately, and this could corrupt backups. 93 | 94 | #### Force Jenkins to quiet mode after specified amount of minutes 95 | 96 | Force a quiet mode after the specified time in minutes to force a safe environment for the back up. 97 | Zero means the quiet mode if forced directly when the back up is triggered by the scheduler. Read 98 | more about the back up process below. 99 | 100 | #### Max number of backup sets 101 | 102 | To save disk space, you can specify the maximum number of stored backup sets. A backup set is 103 | defined as a full backup together with its referencing diff backups. Older backup sets will be 104 | deleted after the next full backup action. This also applies to zipped backup sets. 105 | 106 | #### Files excluded from backup 107 | 108 | If you have specific files you do not want to backup, entering a regex here which identifies those 109 | files will prevent them being backed up. All files with a name matching this regular expression 110 | will not be backed up. Leave empty if not needed. If the expression is invalid, it will be 111 | disregarded. 112 | 113 | #### Backup build results 114 | 115 | If this option is enabled, build results will also be backed up. This is potentially a lot of data, 116 | so think carefully about it. Once you decide to backup build results, also get the option about 117 | backing up your build archives. Once again, be careful with this option because it could be very 118 | **time consuming** and probably needs **a ton of disk space**! 119 | 120 | #### Backup next build number file 121 | If this option is enabled, the file `nextBuildNumber` will also be backed up. 122 | 123 | #### Backup 'config-history' folder 124 | If this option is enabled, the directory `config-history` will also be backed up. The plugin which creates this folder is the [Job Configuration History Plugin](https://plugins.jenkins.io/jobConfigHistory/). 125 | 126 | #### Backup 'userContents' 127 | 128 | Jenkins provides a URL where you can put common data (e.g., static HTML, Tools, etc.). You can 129 | backup all of this data if you check this option. 130 | 131 | #### Backup only builds marked to keep 132 | 133 | If this option is enabled, only results/artifacts on builds which are marked "Keep this build 134 | forever" are backed up. 135 | 136 | #### Backup additional files 137 | 138 | This feature enables the user to include additional files into the backup. For example to include fingerprints and the files for secrets; add the following regular expression : `(fingerprints|.*|.*|.*\.xml)(secrets|.*\..*)(secrets|.*)$`. 139 | This allows the plugin to be used in migration scenarions. For example the secrets will not automatically by backuped, but are needed to descrypt the backuped credentials. Therefore the additional files field is needed to create a fully functional backup of your jenkins server data. 140 | 141 | > ***Important***: Be aware that in general the controller key should be stored somewhere else, as stated in the Jenkins [backup guidelines](https://www.jenkins.io/doc/book/system-administration/backing-up/#back-up-the-controller-key-separately) 142 | 143 | #### Clean up differential backups 144 | 145 | If this option is enabled, all differential backups are removed whenever a new full backup is done. 146 | 147 | #### Move old backups to ZIP files 148 | 149 | If this is checked, then whenever a new full backup is performed, then all old backup sets will be 150 | moved to ZIP files. Each ZIP file will contain one backup set (i.e., one full backup and any 151 | differential backups referencing it). The file name will identify the timeframe where the backups 152 | are included (i.e., the timestamp of the full backup and the timestamp of the latest differential 153 | backup). 154 | 155 | **Note**: The setting "Max number of backup sets" applies to backup ZIP files created by *thinBackup* 156 | as well. 157 | 158 | **Note**: In case "Clean up differential backups" is checked, differential cleanup will be performed 159 | before zipping is done, and therefore no differential backups will be in the ZIP files. 160 | 161 | ## Jenkins Configuration as Code (JCasC) support 162 | 163 | Since version 2.0 the plugin fully supports JCasC. An example config as a basis can be used from here. 164 | 165 | **Note**: Remember to escape backslashes in the YAML 166 | 167 | ```yaml 168 | unclassified: 169 | thinBackup: 170 | backupAdditionalFiles: false 171 | backupAdditionalFilesRegex: "^.*\\.(txt)$" 172 | backupBuildArchive: false 173 | backupBuildResults: true 174 | backupBuildsToKeepOnly: false 175 | backupConfigHistory: false 176 | backupNextBuildNumber: false 177 | backupPath: "c:\\temp\\thin-backup" 178 | backupPluginArchives: false 179 | backupUserContents: false 180 | cleanupDiff: false 181 | diffBackupSchedule: "0 12 * * 1-5" 182 | excludedFilesRegex: "^.*\\.(log)$" 183 | failFast: true 184 | forceQuietModeTimeout: 120 185 | fullBackupSchedule: "0 12 * * 1" 186 | moveOldBackupsToZipFile: false 187 | nrMaxStoredFull: -1 188 | waitForIdle: true 189 | ``` 190 | 191 | ## Backup process 192 | 193 | Because many of you are asking why Jenkins is going to shutdown when a backup is triggered, I 194 | decided to explain my ideas behind the backup process. 195 | 196 | First of all, **Jenkins will not be shutdown at any time**. Second, I use the built-in quiet mode 197 | (Jenkins is going to shutdown) to ensure a safe environment during the backup process and cancel 198 | the quiet mode afterward. This could be misleading, **but there is no point where Jenkins will be 199 | shutdown**. 200 | 201 | So, what is a safe environment? A safe environment would mean that no jobs are running. Because a 202 | running job could change a file in the workspace (results, build output, etc.), in this case the 203 | file could be locked. This situation should be avoided by using the quiet mode. 204 | 205 | Here is an example of how it works: 206 | - The scheduler triggers a backup. 207 | - There are active jobs and some jobs are waiting in the queue. 208 | - The backup waits for a safe environment (recommended way). This means running jobs will be 209 | finished in any case. Waiting jobs will be started as long as Jenkins is not in quiet mode. The 210 | "Force Jenkins to quiet mode after specified minutes" option will force Jenkins after the 211 | specified time into quiet mode. From this point, no further jobs will be started until the backup 212 | is finished. 213 | - In the case of "Wait until Jenkins is idle to perform a backup" is disabled, then the backup 214 | will be done immediately. 215 | - The backup starts when no job is running. 216 | - Once the backup is finished the quiet mode will be canceled. Manually canceling the quiet mode 217 | will force a new quiet period. 218 | 219 | ## Feature requests or bug reports 220 | 221 | Please let us know if you have another option or feature for this plugin by entering an issue or 222 | write us an email. 223 | 224 | Before submitting a feature request or bug report, you can check if it has already been submitted by 225 | searching issues in JIRA categorized under the thin-backup-plugin component or directly on the [plugin site](https://plugins.jenkins.io/thinBackup/#issues). 226 | -------------------------------------------------------------------------------- /images/ManagementLink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/thin-backup-plugin/21a2a3871a050ea07038f7479325731b2c7bee91/images/ManagementLink.png -------------------------------------------------------------------------------- /images/backupStore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/thin-backup-plugin/21a2a3871a050ea07038f7479325731b2c7bee91/images/backupStore.png -------------------------------------------------------------------------------- /images/restore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/thin-backup-plugin/21a2a3871a050ea07038f7479325731b2c7bee91/images/restore.png -------------------------------------------------------------------------------- /images/thinBackup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/thin-backup-plugin/21a2a3871a050ea07038f7479325731b2c7bee91/images/thinBackup.png -------------------------------------------------------------------------------- /images/thinBackupOld.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/thin-backup-plugin/21a2a3871a050ea07038f7479325731b2c7bee91/images/thinBackupOld.png -------------------------------------------------------------------------------- /images/thinBackupSettings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/thin-backup-plugin/21a2a3871a050ea07038f7479325731b2c7bee91/images/thinBackupSettings.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | org.jenkins-ci.plugins 7 | plugin 8 | 5.17 9 | 10 | 11 | 12 | org.jvnet.hudson.plugins 13 | thinBackup 14 | ${revision}${changelist} 15 | hpi 16 | ThinBackup 17 | Backs up the most important global and job specific configuration files. 18 | https://github.com/jenkinsci/thin-backup-plugin 19 | 20 | 21 | 22 | GPLv3 23 | repo 24 | Copyright (C) 2011 Matthias Steinkogler, Thomas Fürer 25 | 26 | This program is free software: you can redistribute it and/or modify 27 | it under the terms of the GNU General Public License as published by 28 | the Free Software Foundation, either version 3 of the License, or 29 | (at your option) any later version. 30 | 31 | This program is distributed in the hope that it will be useful, 32 | but WITHOUT ANY WARRANTY; without even the implied warranty of 33 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 34 | GNU General Public License for more details. 35 | 36 | You should have received a copy of the GNU General Public License 37 | along with this program. If not, see http://www.gnu.org/licenses. 38 | 39 | 40 | 41 | 42 | 43 | tofuatjava 44 | Thomas Fuerer 45 | tfuerer.javanet@gmail.com 46 | CET 47 | 48 | 49 | alienllama 50 | Matthias Steinkogler 51 | alienllama@gmail.com 52 | Borland (a Microfocus Company) 53 | www.borland.com 54 | CET 55 | 56 | 57 | StefanSpieker 58 | Stefan Spieker 59 | CET 60 | 61 | 62 | 63 | 64 | scm:git:https://github.com/${gitHubRepo}.git 65 | scm:git:https://github.com/${gitHubRepo}.git 66 | ${scmTag} 67 | https://github.com/${gitHubRepo} 68 | 69 | 70 | 71 | 2.1.4 72 | -SNAPSHOT 73 | 74 | 2.479 75 | ${jenkins.baseline}.3 76 | jenkinsci/thin-backup-plugin 77 | false 78 | InjectedIT 79 | 2.0 80 | 81 | 82 | 83 | 84 | 85 | io.jenkins.tools.bom 86 | bom-${jenkins.baseline}.x 87 | 4862.vc32a_71c3e731 88 | pom 89 | import 90 | 91 | 92 | 93 | 94 | 95 | 96 | io.jenkins.plugins 97 | ionicons-api 98 | 99 | 100 | io.jenkins 101 | configuration-as-code 102 | test 103 | 104 | 105 | io.jenkins.configuration-as-code 106 | test-harness 107 | test 108 | 109 | 110 | 111 | 112 | 113 | repo.jenkins-ci.org 114 | https://repo.jenkins-ci.org/public/ 115 | 116 | 117 | 118 | 119 | repo.jenkins-ci.org 120 | https://repo.jenkins-ci.org/public/ 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /src/main/java/org/jvnet/hudson/plugins/thinbackup/ThinBackupMgmtLink.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 Matthias Steinkogler, Thomas Fürer 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see http://www.gnu.org/licenses. 16 | */ 17 | package org.jvnet.hudson.plugins.thinbackup; 18 | 19 | import edu.umd.cs.findbugs.annotations.NonNull; 20 | import hudson.Extension; 21 | import hudson.model.ManagementLink; 22 | import hudson.model.TaskListener; 23 | import hudson.util.ListBoxModel; 24 | import java.io.File; 25 | import java.io.IOException; 26 | import java.text.ParseException; 27 | import java.text.SimpleDateFormat; 28 | import java.util.Date; 29 | import java.util.List; 30 | import java.util.concurrent.TimeUnit; 31 | import java.util.logging.Logger; 32 | import jenkins.model.Jenkins; 33 | import jenkins.util.Timer; 34 | import org.jvnet.hudson.plugins.thinbackup.restore.HudsonRestore; 35 | import org.jvnet.hudson.plugins.thinbackup.utils.Utils; 36 | import org.kohsuke.stapler.QueryParameter; 37 | import org.kohsuke.stapler.StaplerRequest2; 38 | import org.kohsuke.stapler.StaplerResponse2; 39 | import org.kohsuke.stapler.verb.POST; 40 | 41 | /** 42 | * A backup solution for Hudson. Backs up configuration files from Hudson and its jobs. 43 | *

44 | * Originally based on the Backup plugin by Vincent Sellier, Manufacture Fran�aise des Pneumatiques Michelin, Romain 45 | * Seguy, et.al. Subsequently heavily modified. 46 | */ 47 | @Extension 48 | public class ThinBackupMgmtLink extends ManagementLink { 49 | private static final String THIN_BACKUP_SUBPATH = "/thinBackup"; 50 | 51 | private static final Logger LOGGER = Logger.getLogger("hudson.plugins.thinbackup"); 52 | 53 | @Override 54 | public String getDisplayName() { 55 | return "ThinBackup"; 56 | } 57 | 58 | @Override 59 | public String getIconFileName() { 60 | return "symbol-archive-outline plugin-ionicons-api"; 61 | } 62 | 63 | @Override 64 | public String getUrlName() { 65 | return "thinBackup"; 66 | } 67 | 68 | @Override 69 | public String getDescription() { 70 | return "Backup your global and job specific configuration."; 71 | } 72 | 73 | @POST 74 | public void doBackupManual(final StaplerRequest2 res, final StaplerResponse2 rsp) throws IOException { 75 | final Jenkins jenkins = Jenkins.get(); 76 | jenkins.checkPermission(Jenkins.ADMINISTER); 77 | LOGGER.info("Starting manual backup."); 78 | 79 | final ThinBackupPeriodicWork manualBackupWorker = new ThinBackupPeriodicWork() { 80 | @Override 81 | protected void execute(final TaskListener arg0) { 82 | backupNow(BackupType.FULL); 83 | } 84 | }; 85 | Timer.get().schedule(manualBackupWorker, 0, TimeUnit.SECONDS); 86 | 87 | rsp.sendRedirect(res.getContextPath() + THIN_BACKUP_SUBPATH); 88 | } 89 | 90 | @POST 91 | public void doRestore( 92 | final StaplerRequest2 res, 93 | final StaplerResponse2 rsp, 94 | @QueryParameter("restoreBackupFrom") final String restoreBackupFrom, 95 | @QueryParameter("restoreNextBuildNumber") final String restoreNextBuildNumber, 96 | @QueryParameter("restorePlugins") final String restorePlugins) 97 | throws IOException { 98 | LOGGER.info("Starting restore operation."); 99 | 100 | final Jenkins jenkins = Jenkins.get(); 101 | jenkins.checkPermission(Jenkins.ADMINISTER); 102 | 103 | jenkins.doQuietDown(); 104 | LOGGER.fine("Waiting until executors are idle to perform restore..."); 105 | Utils.waitUntilIdle(); 106 | 107 | try { 108 | final File jenkinsHome = jenkins.getRootDir(); 109 | final Date restoreFromDate = new SimpleDateFormat(Utils.DISPLAY_DATE_FORMAT).parse(restoreBackupFrom); 110 | 111 | final HudsonRestore hudsonRestore = new HudsonRestore( 112 | jenkinsHome, 113 | ThinBackupPluginImpl.get().getExpandedBackupPath(), 114 | restoreFromDate, 115 | "on".equals(restoreNextBuildNumber), 116 | "on".equals(restorePlugins)); 117 | hudsonRestore.restore(); 118 | 119 | LOGGER.info("Restore finished."); 120 | } catch (ParseException e) { 121 | LOGGER.severe("Cannot parse restore option. Aborting."); 122 | } catch (final Exception ise) { 123 | LOGGER.severe("Could not restore. Aborting."); 124 | } finally { 125 | jenkins.doCancelQuietDown(); 126 | rsp.sendRedirect(res.getContextPath() + THIN_BACKUP_SUBPATH); 127 | } 128 | } 129 | 130 | public ThinBackupPluginImpl getConfiguration() { 131 | return ThinBackupPluginImpl.get(); 132 | } 133 | 134 | public List getAvailableBackups() { 135 | Jenkins.get().checkPermission(Jenkins.ADMINISTER); 136 | final ThinBackupPluginImpl plugin = ThinBackupPluginImpl.get(); 137 | return Utils.getBackupsAsDates(new File(plugin.getExpandedBackupPath())); 138 | } 139 | 140 | @POST 141 | public ListBoxModel doFillBackupItems() { 142 | Jenkins.get().checkPermission(Jenkins.ADMINISTER); 143 | final ThinBackupPluginImpl plugin = ThinBackupPluginImpl.get(); 144 | final List backupsAsDates = Utils.getBackupsAsDates(new File(plugin.getExpandedBackupPath())); 145 | var model = new ListBoxModel(); 146 | for (String entry : backupsAsDates) { 147 | model.add(new ListBoxModel.Option(entry)); 148 | } 149 | return model; 150 | } 151 | 152 | /** 153 | * Name of the category for this management link. Exists so that plugins with core dependency pre-dating the version 154 | * when this was introduced can define a category. 155 | *

156 | * 157 | * @return name of the desired category, one of the enum values of Category, e.g. {@code STATUS}. 158 | * @since 2.226 159 | */ 160 | @NonNull 161 | @Override 162 | public Category getCategory() { 163 | return Category.TOOLS; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/main/java/org/jvnet/hudson/plugins/thinbackup/ThinBackupPeriodicWork.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 Matthias Steinkogler, Thomas Fürer 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see http://www.gnu.org/licenses. 16 | */ 17 | package org.jvnet.hudson.plugins.thinbackup; 18 | 19 | import hudson.Extension; 20 | import hudson.model.AsyncPeriodicWork; 21 | import hudson.model.TaskListener; 22 | import hudson.scheduler.CronTab; 23 | import java.io.IOException; 24 | import java.text.MessageFormat; 25 | import java.util.Calendar; 26 | import java.util.Date; 27 | import java.util.concurrent.TimeUnit; 28 | import java.util.logging.Level; 29 | import java.util.logging.Logger; 30 | import jenkins.model.Jenkins; 31 | import org.jvnet.hudson.plugins.thinbackup.backup.HudsonBackup; 32 | import org.jvnet.hudson.plugins.thinbackup.utils.Utils; 33 | 34 | @Extension 35 | public class ThinBackupPeriodicWork extends AsyncPeriodicWork { 36 | 37 | private static final Logger LOGGER = Logger.getLogger("hudson.plugins.thinbackup"); 38 | 39 | private final ThinBackupPluginImpl plugin = ThinBackupPluginImpl.get(); 40 | 41 | public enum BackupType { 42 | NONE, 43 | FULL, 44 | DIFF 45 | } 46 | 47 | public ThinBackupPeriodicWork() { 48 | super("ThinBackup Worker Thread"); 49 | } 50 | 51 | @Override 52 | public long getRecurrencePeriod() { 53 | return MIN; 54 | } 55 | 56 | @Override 57 | protected void execute(final TaskListener arg0) { 58 | final long currentTime = System.currentTimeMillis(); 59 | final String fullCron = plugin.getFullBackupSchedule(); 60 | final String diffCron = plugin.getDiffBackupSchedule(); 61 | 62 | final BackupType type = getNextScheduledBackupType(currentTime, fullCron, diffCron); 63 | if (type != BackupType.NONE) { 64 | backupNow(type); 65 | } 66 | } 67 | 68 | protected void backupNow(final BackupType type) { 69 | final Jenkins jenkins = Jenkins.get(); 70 | final boolean inQuietModeBeforeBackup = jenkins.isQuietingDown(); 71 | 72 | String backupPath = null; 73 | try { 74 | backupPath = plugin.getExpandedBackupPath(); 75 | 76 | if (backupPath != null && !backupPath.isEmpty()) { 77 | if (plugin.isWaitForIdle()) { 78 | LOGGER.fine("Wait until executors are idle to perform backup."); 79 | Utils.waitUntilIdleAndSwitchToQuietMode(plugin.getForceQuietModeTimeout(), TimeUnit.MINUTES); 80 | } else { 81 | LOGGER.warning( 82 | "Do not wait until Jenkins is idle to perform backup. This could cause corrupt backups."); 83 | } 84 | 85 | new HudsonBackup(plugin, type).backup(); 86 | LOGGER.info("Backup process finished successfully."); 87 | } else { 88 | LOGGER.warning("ThinBackup is not configured yet: No backup path set."); 89 | } 90 | } catch (final IOException e) { 91 | final String msg = MessageFormat.format( 92 | "Cannot perform a backup. Please be sure Jenkins has write privileges in the configured backup path ''{0}''.", 93 | backupPath); 94 | LOGGER.log(Level.SEVERE, msg, e); 95 | } finally { 96 | if (!inQuietModeBeforeBackup) { 97 | jenkins.doCancelQuietDown(); 98 | } else { 99 | LOGGER.warning( 100 | "Still in quiet mode as before. The quiet mode needs to be canceled manually, because it is not clear who is putting Jenkins into quiet mode."); 101 | } 102 | } 103 | } 104 | 105 | static BackupType getNextScheduledBackupType(final long currentTime, final String fullCron, final String diffCron) { 106 | final long fullDelay = calculateDelay(currentTime, BackupType.FULL, fullCron); 107 | final long diffDelay = calculateDelay(currentTime, BackupType.DIFF, diffCron); 108 | 109 | BackupType res = null; 110 | long delay; 111 | if ((fullDelay == -1) && (diffDelay == -1)) { 112 | return BackupType.NONE; 113 | } else if ((fullDelay != -1) && (diffDelay == -1)) { 114 | res = BackupType.FULL; 115 | delay = fullDelay; 116 | } else if ((fullDelay == -1) && (diffDelay != -1)) { 117 | res = BackupType.DIFF; 118 | delay = diffDelay; 119 | } else { 120 | res = BackupType.DIFF; 121 | delay = diffDelay; 122 | if (fullDelay <= diffDelay) { 123 | delay = fullDelay; 124 | res = BackupType.FULL; 125 | } 126 | } 127 | 128 | return delay < MIN ? res : BackupType.NONE; 129 | } 130 | 131 | static long calculateDelay(final long currentTime, final BackupType backupType, final String cron) { 132 | CronTab cronTab; 133 | try { 134 | if (cron == null || cron.isEmpty()) { 135 | return -1; 136 | } 137 | 138 | cronTab = new CronTab(cron); 139 | 140 | final Calendar nextExecution = cronTab.ceil(currentTime); 141 | final long delay = nextExecution.getTimeInMillis() - currentTime; 142 | 143 | if (LOGGER.isLoggable(Level.FINE)) { 144 | LOGGER.fine(MessageFormat.format( 145 | "Current time: {0,date,medium} {0,time,long}. Next execution ({3}) in {2} seconds which is {1,date,medium} {1,time,long}", 146 | new Date(currentTime), 147 | nextExecution.getTime(), 148 | TimeUnit.MILLISECONDS.toSeconds(delay), 149 | backupType)); 150 | } 151 | 152 | if (delay < 0) { 153 | final String msg = 154 | "Delay is a negative number, which means the next execution is in the past! This happens for Hudson/Jenkins installations with version 1.395 or below. Please upgrade to fix this."; 155 | LOGGER.severe(msg); 156 | throw new IllegalStateException(msg); 157 | } 158 | 159 | return delay; 160 | } catch (final IllegalArgumentException e) { 161 | LOGGER.warning(MessageFormat.format( 162 | "Cannot parse the specified ''Backup schedule for {0} backups''. Check cron notation.", 163 | backupType)); 164 | return -1; 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/main/java/org/jvnet/hudson/plugins/thinbackup/backup/DirectoriesZipper.java: -------------------------------------------------------------------------------- 1 | package org.jvnet.hudson.plugins.thinbackup.backup; 2 | 3 | import java.io.BufferedInputStream; 4 | import java.io.BufferedOutputStream; 5 | import java.io.Closeable; 6 | import java.io.File; 7 | import java.io.FileInputStream; 8 | import java.io.IOException; 9 | import java.nio.file.Files; 10 | import java.util.Collection; 11 | import java.util.Collections; 12 | import java.util.logging.Level; 13 | import java.util.logging.Logger; 14 | import java.util.zip.ZipEntry; 15 | import java.util.zip.ZipOutputStream; 16 | import org.apache.commons.io.DirectoryWalker; 17 | 18 | public class DirectoriesZipper extends DirectoryWalker implements Closeable { 19 | private static final Logger LOGGER = Logger.getLogger("hudson.plugins.thinbackup"); 20 | 21 | public static final int BUFFER_SIZE = 512 * 1024; 22 | 23 | private final ZipOutputStream zipStream; 24 | private final String rootPath; 25 | 26 | public DirectoriesZipper(final File zipFile) throws IOException { 27 | if (!zipFile.createNewFile()) { 28 | LOGGER.log(Level.WARNING, "{0} already exists. Previous backup will be overridden.", zipFile.getName()); 29 | } 30 | zipStream = new ZipOutputStream(new BufferedOutputStream(Files.newOutputStream(zipFile.toPath()))); 31 | this.rootPath = zipFile.getParent(); 32 | } 33 | 34 | public void addToZip(final File directory) throws IOException { 35 | walk(directory, Collections.emptyList()); 36 | } 37 | 38 | @Override 39 | public void close() throws IOException { 40 | zipStream.close(); 41 | } 42 | 43 | @Override 44 | protected void handleFile(final File file, final int depth, final Collection results) { 45 | try (FileInputStream fi = new FileInputStream(file); 46 | BufferedInputStream origin = new BufferedInputStream(fi)) { 47 | // make entry relative to the root directory 48 | String entryPath = file.getAbsolutePath(); 49 | entryPath = entryPath.replace(rootPath + File.separator, ""); 50 | final ZipEntry entry = new ZipEntry(entryPath); 51 | 52 | zipStream.putNextEntry(entry); 53 | int count; 54 | final byte[] buffer = new byte[BUFFER_SIZE]; 55 | while ((count = origin.read(buffer)) != -1) { 56 | zipStream.write(buffer, 0, count); 57 | } 58 | } catch (final IOException ioe) { 59 | LOGGER.log(Level.SEVERE, "Could not create ZIP entry", ioe); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/org/jvnet/hudson/plugins/thinbackup/backup/PluginList.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 Matthias Steinkogler, Thomas Fürer 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see http://www.gnu.org/licenses. 16 | */ 17 | package org.jvnet.hudson.plugins.thinbackup.backup; 18 | 19 | import edu.umd.cs.findbugs.annotations.NonNull; 20 | import hudson.XmlFile; 21 | import java.io.File; 22 | import java.io.IOException; 23 | import java.util.HashMap; 24 | import java.util.Map; 25 | import java.util.Map.Entry; 26 | import java.util.Objects; 27 | import jenkins.model.Jenkins; 28 | 29 | public class PluginList implements Comparable { 30 | private Map plugins; 31 | private final File pluginsXml; 32 | 33 | public PluginList(final File pluginsXml) { 34 | this.pluginsXml = pluginsXml; 35 | plugins = new HashMap<>(); 36 | } 37 | 38 | public Map getPlugins() { 39 | return plugins; 40 | } 41 | 42 | public void add(final String name, final String version) { 43 | plugins.put(name, version); 44 | } 45 | 46 | public void setPlugins(final Map plugins) { 47 | this.plugins = plugins; 48 | } 49 | 50 | public void save() throws IOException { 51 | new XmlFile(Jenkins.XSTREAM, pluginsXml).write(this); 52 | } 53 | 54 | public void load() throws IOException { 55 | final XmlFile xmlFile = new XmlFile(Jenkins.XSTREAM, pluginsXml); 56 | if (xmlFile.exists()) { 57 | xmlFile.unmarshal(this); 58 | } 59 | } 60 | 61 | @Override 62 | public boolean equals(Object o) { 63 | if (this == o) { 64 | return true; 65 | } 66 | if (o == null || getClass() != o.getClass()) { 67 | return false; 68 | } 69 | PluginList that = (PluginList) o; 70 | return Objects.equals(plugins, that.plugins); 71 | } 72 | 73 | @Override 74 | public int hashCode() { 75 | return Objects.hash(plugins); 76 | } 77 | 78 | @Override 79 | public int compareTo(@NonNull final PluginList other) { 80 | if (other == null) { 81 | return -1; 82 | } 83 | 84 | final Map plugins2 = other.getPlugins(); 85 | 86 | if (plugins2.size() != plugins.size()) { 87 | return -1; 88 | } 89 | 90 | for (final Entry entry : this.plugins.entrySet()) { 91 | final String plugin = entry.getKey(); 92 | final String version = entry.getValue(); 93 | final String prevVersion = plugins2.get(plugin); 94 | 95 | if ((version == null || version.isEmpty()) 96 | || prevVersion == null 97 | || prevVersion.isEmpty() 98 | || !version.equals(prevVersion)) { 99 | return -1; 100 | } 101 | } 102 | 103 | return 0; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/org/jvnet/hudson/plugins/thinbackup/restore/HudsonRestore.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 Matthias Steinkogler, Thomas Fürer 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see http://www.gnu.org/licenses. 16 | */ 17 | package org.jvnet.hudson.plugins.thinbackup.restore; 18 | 19 | import static org.jvnet.hudson.plugins.thinbackup.backup.HudsonBackup.COMPLETED_BACKUP_FILE; 20 | 21 | import hudson.PluginManager; 22 | import hudson.model.UpdateCenter; 23 | import hudson.model.UpdateCenter.UpdateCenterJob; 24 | import hudson.model.UpdateSite; 25 | import hudson.model.UpdateSite.Plugin; 26 | import java.io.BufferedReader; 27 | import java.io.File; 28 | import java.io.FileFilter; 29 | import java.io.FileReader; 30 | import java.io.FilenameFilter; 31 | import java.io.IOException; 32 | import java.nio.charset.StandardCharsets; 33 | import java.nio.file.Files; 34 | import java.nio.file.StandardOpenOption; 35 | import java.text.SimpleDateFormat; 36 | import java.util.ArrayList; 37 | import java.util.Collection; 38 | import java.util.Date; 39 | import java.util.HashMap; 40 | import java.util.List; 41 | import java.util.Map; 42 | import java.util.Map.Entry; 43 | import java.util.concurrent.Future; 44 | import java.util.logging.Level; 45 | import java.util.logging.Logger; 46 | import jenkins.model.Jenkins; 47 | import org.apache.commons.io.FileUtils; 48 | import org.apache.commons.io.filefilter.DirectoryFileFilter; 49 | import org.apache.commons.io.filefilter.FileFileFilter; 50 | import org.apache.commons.io.filefilter.FileFilterUtils; 51 | import org.apache.commons.io.filefilter.IOFileFilter; 52 | import org.apache.commons.io.filefilter.TrueFileFilter; 53 | import org.jvnet.hudson.plugins.thinbackup.ThinBackupPeriodicWork.BackupType; 54 | import org.jvnet.hudson.plugins.thinbackup.backup.BackupSet; 55 | import org.jvnet.hudson.plugins.thinbackup.backup.HudsonBackup; 56 | import org.jvnet.hudson.plugins.thinbackup.backup.PluginList; 57 | import org.jvnet.hudson.plugins.thinbackup.utils.Utils; 58 | 59 | public class HudsonRestore { 60 | private static final int SLEEP_TIMEOUT = 500; 61 | 62 | private static final Logger LOGGER = Logger.getLogger("hudson.plugins.thinbackup"); 63 | 64 | private final String backupPath; 65 | private final File hudsonHome; 66 | private final Date restoreFromDate; 67 | private final boolean restoreNextBuildNumber; 68 | private final boolean restorePlugins; 69 | private final Map> availablePluginLocations; 70 | 71 | public HudsonRestore( 72 | final File hudsonConfigurationPath, 73 | final String backupPath, 74 | final Date restoreFromDate, 75 | final boolean restoreNextBuildNumber, 76 | final boolean restorePlugins) { 77 | this.hudsonHome = hudsonConfigurationPath; 78 | this.backupPath = backupPath; 79 | this.restoreFromDate = restoreFromDate; 80 | this.restoreNextBuildNumber = restoreNextBuildNumber; 81 | this.restorePlugins = restorePlugins; 82 | this.availablePluginLocations = new HashMap<>(); 83 | } 84 | 85 | public void restore() { 86 | if (backupPath == null || backupPath.isEmpty()) { 87 | LOGGER.severe("Backup path not specified for restoration. Aborting."); 88 | return; 89 | } 90 | if (restoreFromDate == null) { 91 | LOGGER.severe("Backup date to restore from was not specified. Aborting."); 92 | return; 93 | } 94 | 95 | try { 96 | boolean success = restoreFromDirectories(backupPath); 97 | if (!success) { 98 | success = restoreFromZipFile(); 99 | } 100 | if (!success) { 101 | LOGGER.severe("Could not restore backup."); 102 | } else { 103 | LOGGER.info("Restore completed successfully."); 104 | } 105 | } catch (final IOException ioe) { 106 | LOGGER.log(Level.SEVERE, "Could not restore backup.", ioe); 107 | } 108 | } 109 | 110 | private boolean restoreFromDirectories(final String parentDirectory) throws IOException { 111 | boolean success = false; 112 | 113 | IOFileFilter suffixFilter = FileFilterUtils.and( 114 | FileFilterUtils.suffixFileFilter( 115 | new SimpleDateFormat(Utils.DIRECTORY_NAME_DATE_FORMAT).format(restoreFromDate)), 116 | DirectoryFileFilter.DIRECTORY); 117 | 118 | final File[] candidates = new File(parentDirectory).listFiles((FileFilter) suffixFilter); 119 | if (candidates == null) { 120 | return false; 121 | } 122 | 123 | if (candidates.length > 1) { 124 | LOGGER.severe(String.format( 125 | "More than one backup with date '%s' found. This is not allowed. Aborting restore.", 126 | new SimpleDateFormat(Utils.DISPLAY_DATE_FORMAT).format(restoreFromDate))); 127 | } else if (candidates.length == 1) { 128 | final File toRestore = candidates[0]; 129 | if (toRestore.getName().startsWith(BackupType.DIFF.toString())) { 130 | final File referencedFullBackup = Utils.getReferencedFullBackup(toRestore); 131 | restore(referencedFullBackup); 132 | } 133 | restore(toRestore); 134 | success = true; 135 | } else { 136 | LOGGER.info(String.format( 137 | "No backup directories with date '%s' found. Will try to find a backup in ZIP files next...", 138 | new SimpleDateFormat(Utils.DISPLAY_DATE_FORMAT).format(restoreFromDate))); 139 | } 140 | 141 | return success; 142 | } 143 | 144 | private boolean restoreFromZipFile() throws IOException { 145 | boolean success = false; 146 | 147 | IOFileFilter zippedBackupSetsFilter = FileFilterUtils.and( 148 | FileFilterUtils.prefixFileFilter(BackupSet.BACKUPSET_ZIPFILE_PREFIX), 149 | FileFilterUtils.suffixFileFilter(HudsonBackup.ZIP_FILE_EXTENSION), 150 | FileFileFilter.INSTANCE); 151 | 152 | final File[] candidates = new File(backupPath).listFiles((FileFilter) zippedBackupSetsFilter); 153 | if (candidates != null) { 154 | for (final File candidate : candidates) { 155 | final BackupSet backupSet = new BackupSet(candidate); 156 | if (backupSet.isValid() && backupSet.containsBackupForDate(restoreFromDate)) { 157 | final BackupSet unzippedBackup = backupSet.unzip(); 158 | if (unzippedBackup.isValid()) { 159 | success = restoreFromDirectories(backupSet.getUnzipDir().getAbsolutePath()); 160 | } 161 | backupSet.deleteUnzipDir(); 162 | } 163 | } 164 | } 165 | 166 | return success; 167 | } 168 | 169 | private void restore(final File toRestore) throws IOException { 170 | final IOFileFilter nextBuildNumberFileFilter = FileFilterUtils.nameFileFilter("nextBuildNumber"); 171 | final IOFileFilter noBackupCompletedFile = 172 | FileFilterUtils.notFileFilter(FileFilterUtils.nameFileFilter(COMPLETED_BACKUP_FILE)); 173 | IOFileFilter restoreNextBuildNumberFilter; 174 | 175 | if (restoreNextBuildNumber) { 176 | restoreNextBuildNumberFilter = noBackupCompletedFile; 177 | 178 | final Collection restore = 179 | FileUtils.listFiles(toRestore, nextBuildNumberFileFilter, TrueFileFilter.INSTANCE); 180 | final Map nextBuildNumbers = new HashMap<>(); 181 | for (final File file : restore) { 182 | try (BufferedReader reader = new BufferedReader(new FileReader(file, StandardCharsets.UTF_8))) { 183 | nextBuildNumbers.put(file.getParentFile().getName(), Integer.parseInt(reader.readLine())); 184 | } 185 | } 186 | 187 | final Collection current = 188 | FileUtils.listFiles(hudsonHome, nextBuildNumberFileFilter, TrueFileFilter.INSTANCE); 189 | for (final File file : current) { 190 | try (BufferedReader reader = new BufferedReader(new FileReader(file, StandardCharsets.UTF_8))) { 191 | final int currentBuildNumber = Integer.parseInt(reader.readLine()); 192 | final Integer toRestoreNextBuildNumber = 193 | nextBuildNumbers.get(file.getParentFile().getName()); 194 | if (currentBuildNumber < toRestoreNextBuildNumber) { 195 | restoreNextBuildNumber(file, toRestoreNextBuildNumber); 196 | } 197 | } 198 | } 199 | } else { 200 | restoreNextBuildNumberFilter = FileFilterUtils.and( 201 | FileFilterUtils.notFileFilter(nextBuildNumberFileFilter), noBackupCompletedFile); 202 | } 203 | 204 | FileUtils.copyDirectory(toRestore, this.hudsonHome, restoreNextBuildNumberFilter, true); 205 | 206 | if (restorePlugins) { 207 | restorePlugins(toRestore); 208 | } 209 | } 210 | 211 | private void restorePlugins(File toRestore) throws IOException { 212 | File[] list = toRestore.listFiles((FilenameFilter) FileFilterUtils.nameFileFilter("installedPlugins.xml")); 213 | if (list == null) { 214 | LOGGER.severe("Cannot restore plugins because null is returned for files to restore."); 215 | return; 216 | } 217 | if (list.length != 1) { 218 | LOGGER.severe( 219 | "Cannot restore plugins because no or multiple files with the name 'installedPlugins.xml' are in the backup."); 220 | return; 221 | } 222 | 223 | if (list[0] == null) { 224 | LOGGER.severe("Cannot restore plugins because backuped plugin is null."); 225 | return; 226 | } 227 | File backupedPlugins = list[0]; 228 | 229 | PluginList pluginList = new PluginList(backupedPlugins); 230 | pluginList.load(); 231 | Map toRestorePlugins = pluginList.getPlugins(); 232 | List> pluginRestoreJobs = new ArrayList<>(toRestorePlugins.size()); 233 | Jenkins jenkins = Jenkins.get(); 234 | PluginManager pluginManager = jenkins.getPluginManager(); 235 | for (Entry entry : toRestorePlugins.entrySet()) { 236 | if (pluginManager.getPlugin(entry.getKey()) 237 | == null) { // if any version of this plugin is installed do nothing 238 | Future monitor = installPlugin(entry.getKey(), entry.getValue()); 239 | if (monitor != null) { 240 | pluginRestoreJobs.add(monitor); 241 | } 242 | } else { 243 | LOGGER.info("Plugin '" + entry.getKey() + "' already installed. Please check manually."); 244 | } 245 | } 246 | 247 | boolean finished = pluginRestoreJobs.isEmpty(); 248 | while (!finished) { 249 | try { 250 | Thread.sleep(SLEEP_TIMEOUT); 251 | } catch (InterruptedException e) { 252 | LOGGER.log(Level.WARNING, "Interrupted!", e); 253 | Thread.currentThread().interrupt(); 254 | } 255 | for (Future future : pluginRestoreJobs) { 256 | finished = future.isDone(); 257 | if (!finished) { 258 | break; 259 | } 260 | } 261 | } 262 | } 263 | 264 | private void restoreNextBuildNumber(File file, Integer toRestoreNextBuildNumber) throws IOException { 265 | final String buildNumber = toRestoreNextBuildNumber + "\n"; 266 | Files.write(file.toPath(), buildNumber.getBytes(StandardCharsets.UTF_8), StandardOpenOption.TRUNCATE_EXISTING); 267 | } 268 | 269 | private Future installPlugin(String pluginID, String version) { 270 | if (!version.contains("SNAPSHOT") && !"Hudson core".equals(pluginID) && !"Jenkins core".equals(pluginID)) { 271 | Jenkins jenkins = Jenkins.get(); 272 | UpdateCenter updateCenter = jenkins.getUpdateCenter(); 273 | 274 | for (UpdateSite site : updateCenter.getSites()) { 275 | List availablePlugins; 276 | if (this.availablePluginLocations.containsKey(site.getId())) { 277 | availablePlugins = this.availablePluginLocations.get(site.getId()); 278 | } else { 279 | availablePlugins = site.getAvailables(); 280 | this.availablePluginLocations.put(site.getId(), availablePlugins); 281 | } 282 | 283 | for (Plugin plugin : availablePlugins) { 284 | if (plugin.name.equals(pluginID)) { 285 | LOGGER.log(Level.INFO, "Restore plugin ' {0} '.", pluginID); 286 | if (!plugin.version.equals(version)) { 287 | jenkins.checkPermission(Jenkins.ADMINISTER); 288 | PluginRestoreUpdateCenter pruc = new PluginRestoreUpdateCenter(); 289 | 290 | return pruc.addNewJob( 291 | pruc.new PluginRestoreJob(site, Jenkins.getAuthentication2(), plugin, version)); 292 | } else { 293 | return plugin.deploy(); 294 | } 295 | } 296 | } 297 | } 298 | } 299 | LOGGER.log( 300 | Level.WARNING, 301 | "Cannot find plugin ' {0} ' with the version ' {1} '. Please install manually!", 302 | new Object[] {pluginID, version}); 303 | return null; 304 | } 305 | } 306 | -------------------------------------------------------------------------------- /src/main/java/org/jvnet/hudson/plugins/thinbackup/restore/PluginRestoreUpdateCenter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 Matthias Steinkogler, Thomas Fürer 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see http://www.gnu.org/licenses. 16 | */ 17 | package org.jvnet.hudson.plugins.thinbackup.restore; 18 | 19 | import hudson.PluginManager; 20 | import hudson.model.UpdateCenter; 21 | import hudson.model.UpdateSite; 22 | import hudson.model.UpdateSite.Plugin; 23 | import java.io.File; 24 | import java.io.IOException; 25 | import java.net.MalformedURLException; 26 | import java.net.URL; 27 | import java.util.HashSet; 28 | import java.util.Set; 29 | import java.util.concurrent.Future; 30 | import jenkins.model.Jenkins; 31 | import org.springframework.security.core.Authentication; 32 | 33 | public class PluginRestoreUpdateCenter extends UpdateCenter { 34 | public class PluginRestoreJob extends DownloadJob { 35 | 36 | private Plugin plugin; 37 | private String version; 38 | 39 | private final PluginManager pm; 40 | 41 | public PluginRestoreJob(UpdateSite site, Authentication auth, Plugin plugin, String version) { 42 | super(site, auth); 43 | this.plugin = plugin; 44 | this.version = version; 45 | 46 | Jenkins jenkins = Jenkins.get(); 47 | this.pm = jenkins.getPluginManager(); 48 | } 49 | 50 | @Override 51 | protected URL getURL() throws MalformedURLException { 52 | String latestVersion = plugin.version; 53 | String newUrl = plugin.url.replace(latestVersion, version); 54 | return new URL(newUrl); 55 | } 56 | 57 | @Override 58 | protected File getDestination() { 59 | return new File(pm.rootDir, plugin.name + ".hpi"); 60 | } 61 | 62 | @Override 63 | public String getName() { 64 | return plugin.getDisplayName(); 65 | } 66 | 67 | @Override 68 | protected void onSuccess() { 69 | pm.pluginUploaded = true; 70 | } 71 | 72 | @Override 73 | public String toString() { 74 | return super.toString() + "[plugin=" + plugin.title + "]"; 75 | } 76 | 77 | @Override 78 | protected void _run() throws IOException, InstallationStatus { 79 | super._run(); 80 | } 81 | } 82 | 83 | private Set knownUpdateSites = new HashSet<>(); 84 | 85 | synchronized Future addNewJob(UpdateCenterJob job) { 86 | if (knownUpdateSites.add(job.site)) { 87 | new ConnectionCheckJob(job.site).submit(); 88 | } 89 | return job.submit(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/org/jvnet/hudson/plugins/thinbackup/utils/EnvironmentVariableNotDefinedException.java: -------------------------------------------------------------------------------- 1 | package org.jvnet.hudson.plugins.thinbackup.utils; 2 | 3 | public class EnvironmentVariableNotDefinedException extends IllegalArgumentException { 4 | 5 | public EnvironmentVariableNotDefinedException() { 6 | super(); 7 | } 8 | 9 | public EnvironmentVariableNotDefinedException(final String message) { 10 | super(message); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/org/jvnet/hudson/plugins/thinbackup/utils/ExistsAndReadableFileFilter.java: -------------------------------------------------------------------------------- 1 | package org.jvnet.hudson.plugins.thinbackup.utils; 2 | 3 | import java.io.File; 4 | import java.io.Serializable; 5 | import org.apache.commons.io.filefilter.AbstractFileFilter; 6 | import org.apache.commons.io.filefilter.FileFilterUtils; 7 | import org.apache.commons.io.filefilter.IOFileFilter; 8 | 9 | public class ExistsAndReadableFileFilter extends AbstractFileFilter implements Serializable { 10 | private static final long serialVersionUID = 1L; 11 | 12 | /** 13 | * Wraps the supplied filter to make if safe against broken symlinks and missing read permissions 14 | * @param filter The core filter that'll be wrapped inside safety checks 15 | * @return The wrapped file filter 16 | */ 17 | public static IOFileFilter wrapperFilter(final IOFileFilter filter) { 18 | return FileFilterUtils.asFileFilter((dir, name) -> { 19 | File file = new File(dir, name); 20 | if (file.exists() && file.canRead()) { 21 | return filter.accept(file); 22 | } 23 | return false; 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/resources/index.jelly: -------------------------------------------------------------------------------- 1 | 18 | 19 |
20 | Backups the most important global and job specific configuration files. 21 |
-------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupMgmtLink/index.jelly: -------------------------------------------------------------------------------- 1 | 18 | 19 | 21 | 22 | 23 | 24 |

25 | ${it.displayName} 26 |

27 |

28 | ${%backup_settings_moved} 29 |

30 |
31 | 32 | 36 | 37 | 38 | 39 | 43 | 44 | 45 |
46 | 47 |
48 |
49 |
50 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupMgmtLink/index.properties: -------------------------------------------------------------------------------- 1 | backup_manual_tooltip = Click to start the backup now. 2 | backup_now = Backup now 3 | backup_settings_moved = Settings are now integrated in global configuration. 4 | restore = Restore 5 | restore_options_tooltip = Click to open the restore. 6 | settings = Settings 7 | 8 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupMgmtLink/index_de.properties: -------------------------------------------------------------------------------- 1 | backup_manual_tooltip = Klicken, um das Backup jetzt zu starten. 2 | backup_now = Backup starten 3 | backup_settings_moved = Einstellungen sind nun in den globalen Einstellungen integriert. 4 | restore = Wiederherstellen 5 | restore_options_tooltip = Klicken, um zum Wiederherstellen zu gelangen. 6 | settings = Einstellungen 7 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupMgmtLink/restoreOptions.jelly: -------------------------------------------------------------------------------- 1 | 18 | 19 | 21 | 22 | 23 | 24 |

25 | ${%restore_configuration} 26 |

27 | 28 | 29 | 30 | 31 |
32 | 37 |
38 |
39 | 40 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
50 | 51 | 52 | 53 | 54 | 55 | 56 |
57 |
58 |
59 |
60 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupMgmtLink/restoreOptions.properties: -------------------------------------------------------------------------------- 1 | restore_backup_from = Restore backup from: 2 | restore_configuration = Restore Configuration 3 | restore_next_build_number = Restore next build number file (if found in backup) 4 | restore_options = Restore options 5 | restore_plugins = Restore plugins 6 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupMgmtLink/restoreOptions_de.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/thin-backup-plugin/21a2a3871a050ea07038f7479325731b2c7bee91/src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupMgmtLink/restoreOptions_de.properties -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/config.jelly: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 44 | 45 | 46 | 47 | 48 | 49 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/config.properties: -------------------------------------------------------------------------------- 1 | backup_additional_files = Backup additional files 2 | backup_additional_files_regex = Files included in backup (regular expression) 3 | backup_build_archive = Backup build archive 4 | backup_build_keep = Backup only builds marked to keep 5 | backup_build_number = Backup next build number file 6 | backup_config_history = Backup 'config-history' folder 7 | backup_path = Backup directory 8 | backup_plugin_archive = Backup plugins archives 9 | backup_results = Backup build results 10 | backup_settings = Thin Backup settings 11 | backup_user_content = Backup 'userContent' folder 12 | clean_differential = Clean up differential backups 13 | diff_backup_schedule = Backup schedule for differential backups 14 | exclude_files_regex = Files excluded from backup (regular expression) 15 | force_quite_minutes = Force Jenkins to quiet mode after specified minutes 16 | full_backup_schedule = Backup schedule for full backups 17 | max_backup_full = Max number of backup sets 18 | move = Move old backups to ZIP files 19 | thin_backup_configuration=ThinBackup Configuration 20 | wait_for_idle = Wait until Jenkins is idle to perform a backup 21 | fail_fast = Stop the backup as soon as an exception occurs in the file handling 22 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/config_de.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/thin-backup-plugin/21a2a3871a050ea07038f7479325731b2c7bee91/src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/config_de.properties -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-backupAdditionalFiles.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | If this option is enabled, the matching files or folders will also be backed up. Even more data! 28 |

29 |

30 | If you have specific files not normally backed up, that you also want to back up, entering a regex here which identifies those files will include them. All files with a name matching this regular expression will be backed up. If the expression is invalid, it will be disregarded. 31 |
32 | Note that this only allows filename patterns, (not full ant-style patterns) but positive and negative look-aheads are effective at filtering out the primary folder. 33 |
34 | Example:
35 | ^(email\-templates|.*\.jelly)$ will include email-templates/*.jelly . 36 |
37 | ^(?!jobs)(?!plugins)(?!logs)(?!users)(log|.*\.xml)$ will include *.xml and log/*.xml while skipping the jobs, logs, users and plugins folders under the home folder. 38 |

39 | 40 |
41 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-backupAdditionalFiles_de.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | Falls diese Option aktiviert ist, werden die gematchten Daten ebenfalls zum Backup hinzugefügt, was größere Backups bedeutet! 28 |

29 |

30 | Falls es zusätzliche Dateien gibt, die ins Backup aufgenommen werden sollen, so gibt es die Möglichkeit eine RegEx anzugeben. Alle Dateien mit einem Namen, der über die RegEx gematcht werden, werden dem Backup hinzugefügt. 31 | Falls der Ausdruck nicht valide ist, wird er ignoriert. 32 |
33 | Beachte dies erlaubt nur Dateinamen Muster, (Keine ant-style patterns) aber positive und negative look-aheads sind effektiv um den primären Ordner herauszufiltern. 34 |
35 | Beispiel:
36 | ^(email\-templates|.*\.jelly)$ wird email-templates/*.jelly herausfiltern. 37 |
38 | ^(?!jobs)(?!plugins)(?!logs)(?!users)(log|.*\.xml)$ wird *.xml und log/*.xml entahlten, wobei jobs, logs, users und plugins Ornder ausgenommen sind. 39 |

40 | 41 |
42 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-backupBuildArchive.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | If this option is enabled, the build archive within the build results will also be backed up. Even more data! 28 |

29 |
30 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-backupBuildArchive_de.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | Falls diese Option aktiviert ist, werden die Build Archive ebenfalls zum Backup hinzugefügt, was größere Backups bedeutet! 28 |

29 |
30 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-backupBuildResults.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | If this option is enabled, build results will also be backed up. This is potentially a lot of data, so think carefully about it. 28 |

29 |

30 | NOTE:
31 | Files created by the Change Log History Plugin 32 | will be backed up as well if this options is checked. 33 |

34 |
35 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-backupBuildResults_de.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | Falls diese Option aktiviert ist, werden die Build Resultate ebenfalls zum Backup hinzugefügt, was größere Backups bedeutet! 28 |

29 |

30 | HINWEIS:
31 | Dateien die vom Change Log History Plugin erstellt wurden, werden ebenfalls dem Backup hinzugefügt, falls die Option angewählt wurde. 32 |

33 |
34 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-backupBuildsToKeepOnly.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | If this option is enabled, only builds results/artifacts on builds which are marked Keep this build forever are backed up. 28 |

29 |
30 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-backupBuildsToKeepOnly_de.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | Falls diese Option aktiviert ist, werden nur die Build Resultate und Artefakte zum Backup hinzugefügt, die mittels Keep this build forever markiert wurden. 28 |

29 |
30 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-backupConfigHistory.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | If this option is enabled, the directory config-history will also be backed up. 28 |

29 |
30 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-backupConfigHistory_de.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | Falls diese Option aktiviert ist, wird das Verzeichnis config-history zum Backup hinzugefügt. 28 |

29 |
30 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-backupNextBuildNumber.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | If this option is enabled, the file nextBuildNumber will also be backed up. 28 |

29 |

30 | NOTE:
31 | In the case that the existing build number is greater than the build number that should be restored the build number will not be restored. This should prevent from situations where the build number is not unique anymore. 32 |

33 |
34 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-backupNextBuildNumber_de.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | Falls diese Option aktiviert ist, wird die Datei nextBuildNumber zum Backup hinzugefügt. 28 |

29 |

30 | HINWEIS:
31 | Falls es bereits eine existierende größere Build Nummer gibt, wird die kleinere Nummer nicht wiederhergestellt. Das soll vor doppelten Build Nummern schützen. 32 |

33 |
34 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-backupPath.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | Specify the backup directory. The Jenkins process needs write access to this directory. 28 |

29 |

30 | NOTE:
The backup path can contain environment variables. The format is ${ENV_VAR}. 31 |

32 |
33 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-backupPath_de.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | Verzeichnis in dem die Backups abgelegt werden. Der Jenkins Prozess benötigt dort Schreibrechte! 28 |

29 |

30 | HINWEIS:
Der Backup Pfad kann Umgebungsvariablen enthalten. Das Format ist ${ENV_VAR}. 31 |

32 |
33 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-backupPluginArchives.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | If this option is enabled, the actual plugin archives can be backed up as well. Depending on your plugin mix, this could add quite a few MB's! 28 |

29 |
30 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-backupPluginArchives_de.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | Falls diese Option aktiviert ist, werden die aktuellen Plugin-Archive ebenfalls dem Backup hinzugefügt. Abhängig vom Plugin Umfang kann das einige zusätzliche MB bedeuten! 28 |

29 |
30 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-backupUserContents.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | If this option is enabled, the directory userContent will also be backed up. 28 |

29 |

30 | NOTE:
It is taken for granted that only files in the directory 31 | $JENKINS_HOME/userContent are backed up. If you have linked files in 32 | your webcontent you need to backup this manually. 33 |

34 |
35 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-backupUserContents_de.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | Falls diese Option aktiviert ist, wird das Verzeichnis userContent zum Backup hinzugefügt. 28 |

29 |

30 | HINWEIS:
Es werden nur Dateien und Verzeichnisse aus $JENKINS_HOME/userContent zum 31 | Backup hinzugefügt. Falls Links innerhalb des Ordners verwendet werden, müssen diese Dateien manuell 32 | gesichert werden. 33 |

34 |
35 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-cleanupDiff.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | If this option is enabled, all differential backups are removed whenever a new full backup is done. 28 |

29 |
30 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-cleanupDiff_de.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | Falls diese Option aktiviert ist, werden alle differentiellen Backups gelöscht, sobald ein Full Backup erstellt wurde. 28 |

29 |
30 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-diffBackupSchedule.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | Specifies the execution schedule in a cron notation when a differential backup should be done. Look at Wikipedia for more information. 28 |

29 |

30 | A differential backup stores only complete files whose modification is done after the last full backup. 31 |

32 |

33 | NOTE:
For a differential backup at least one full backup is needed. If no full backup is available it will be done instead of the first differential backup. 34 |

35 |
36 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-diffBackupSchedule_de.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | Spezifiziert in CRON Notation den Ausführungsplan, wann differenzielle Backups durchgeführt werden sollen. 28 | Mehr Informationen dazu gibt es hier: Wikipedia 29 |

30 |

31 | Ein differenzielles Backup speichert nur vollständige Dateien, die sich seit dem letzten Vollbackup geändert haben. 32 |

33 |

34 | HINWEIS:
Für ein differenzielles Backup muss es mindestens ein Vollbackup geben. Falls es noch kein Vollbackup gibt, wird dieses zuerst erstellt. 35 |

36 |
37 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-excludedFilesRegex.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | If you have specific files you do not want to back up, entering a regex here which identifies those files will prevent them being backed up. All files with a name matching this regular expression will not be backed up. Leave empty if not needed. If the expression is invalid, it will be disregarded. 28 |

29 | Example:
30 | ^.*\.(log)$ will exclude all files ending with the extension .log. 31 |

32 |
33 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-excludedFilesRegex_de.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | Falls es spezifische Dateien gibt, die nicht ins Backup aufgenommen werden sollen, kann hier it einem RegEx angegeben werden, welche Dateien ignoriert werden sollen. 28 | Alle Dateien, deren Name den RegEx matcht werden nicht dem Backup hinzugefügt. Das Feld kann leer gelassen werden, falls die Funktion nicht benötigt wird. 29 | Falls der Ausdruck ungültig ist, wird er ebenfalls ignoriert. 30 |

31 | Beispiel:
32 | ^.*\.(log)$ wird alle Files mit der Dateiendung .log ausnehmen. 33 |

34 |
35 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-failFast.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | If this is checked, then whenever the backup process runs into an exception in the course of handling files, it will stop processing any other data.
28 | (default behaviour: true) 29 |

30 |
31 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-failFast_de.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | Wenn diese Option gewählt ist, schlägt das Backup fehl, sobald eine Exception im Verlauf des Kopierens von Daten geworfen wird.
28 | Es werden dann auch keine Folgeaufgaben (wie beispielsweise das Entfernen alter Backups) durchgeführt.
29 | (Standard: true) 30 |

31 |
32 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-forceQuietModeTimeout.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | The specified cron triggers that the backup should be done. To be sure that nothing get changed during the backup 28 | Jenkins will be switched to a 'Quiet Mode' where no new jobs in the queue are handled. 29 |
30 | However this value in minutes specifies when Jenkins gets forced to the 'Quiet Mode'. 31 |

32 |

33 | NOTE:
If set to -1, it will never force Jenkins to the 'Quiet Mode'. 34 |

35 |
36 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-forceQuietModeTimeout_de.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | Der spezifizierte Cron triggert, wann das Backup durchgeführt werden soll. Um sicherzustellen, dass nichts während des Backups geändert wird, 28 | Jenkins wird in einen 'Quiet Mode' versetzt, in dem keine Jobs von der Queue gestartet werden. 29 |
30 | Der Wert in Minuten gibt an, wann Jenkins in den 'Quiet Mode' gezwungen wird. 31 |

32 |

33 | HINWEIS:
Wenn es auf -1 gesetzt ist, wird es Jenkins niemals in den 'Quiet Mode' zwingen. 34 |

35 |
36 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-fullBackupSchedule.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | Specifies the execution schedule in a cron notation when a full backup 28 | should be done. Look at Wikipedia 29 | for more information. 30 |

31 | 32 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 |
33 | Example: 34 |
CRON ExpressionDescription
0 12 * * 1-5Executes on 12:00 every weekday (Mo-Fr)
44 |
45 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-fullBackupSchedule_de.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | Spezifiziert in CRON Notation den Ausführungsplan, wann vollständige Backups durchgeführt werden sollen. 28 | Mehr Informationen dazu gibt es hier: Wikipedia 29 |

30 | 31 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 |
32 | Beispiel: 33 |
CRON ExpressionBeschreibung
0 12 * * 1-5Wird um 12:00 an jedem Wochentag (Mo-Fr) ausgeführt
43 |
44 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-moveOldBackupsToZipFile.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | If this is checked, then whenever a new full backup is performed all old backup sets will be moved to ZIP files. 28 | Each ZIP file will contain one backup set, i.e. one full backup and any diff backups referencing it. 29 | The filename will identify the timeframe where the backups are included (i.e. the timestamp of the full backup and the timestamp of the latest diff backup). 30 |

31 |

32 | NOTE:
33 | The setting "Max number of backup sets" applies to backup ZIP files created by thinBackup as well. 34 | Also note that in case "Clean up differential backups" is checked it is performed before zipping is run (i.e. there will be no diff backups in the ZIP files). 35 |

36 |
37 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-moveOldBackupsToZipFile_de.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | Falls diese Option aktiviert ist, werden nach jedem neuen Full Backup alle alten Backups in ein ZIP file verschoben. 28 | Jede ZIP-Datei wird ein Vollbackup und alle differenziellen Backups die darauf referenzieren enthalten. 29 | Der Dateiname wird das Zeitintervall identifizieren aus dem Backups enthalten sind. Also den Zeitstempel des Vollbackups und des letzten inkrementellen Backups. 30 |

31 |

32 | HINWEIS:
33 | Die Einstellung Maximale Anzahl an Backup Sätzen gilt ebenfalls für ZIP-Dateien, die von thinBackup erstellt wurden. 34 | Im Falle dass Differentielle Backups bereinigen aktiviert ist, werden, werden vor dem zippen die differenziellen Backups gelöscht. 35 | Es werden also keine differenziellen Backups in der ZIP-Datei vorhanden sein. 36 |

37 |
38 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-nrMaxStoredFull.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | To save disk space you can specify the maximum number of stored backup sets. 28 | A backup set is defined as a full backup together with its referencing diff backups. 29 | Older backup sets will be deleted after the next full backup action. 30 |

31 |
32 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-nrMaxStoredFull_de.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | Um Plattenplatz zu sparen kann die maximale Anzahl an Backup Satz definiert werden. 28 | Ein Backup Satz ist definiert als ein Vollbackup und die referenzierenden inkrementellen Backups. 29 | Ältere Backup Sätze werden nach dem nächsten Vollbackup gelöscht. 30 |

31 |
32 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-waitForIdle.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | Wait until Jenkins is idle to perform a backup (recommended/default setting). However, you can do a backup without waiting for the idle condition. This may cause problems which results in corrupt backups, because files that will be written in the same moment as they will be backuped could be locked in the file system. 28 | Nevertheless, in the case of long running jobs (e.g. test jobs) you probably do not have a free time slot for your backup task. During these jobs you are sure nothing is changed or locked in the file system (e.g. because the build job is done on an agent). In this case you could disable this option to perform a backup in parallel to a running job. 29 |

30 |

31 | NOTE:
We cannot 100% guarantee that the backup is correct if you disable this option. 32 |

33 |
34 | -------------------------------------------------------------------------------- /src/main/resources/org/jvnet/hudson/plugins/thinbackup/ThinBackupPluginImpl/help-waitForIdle_de.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | Warte bis Jenkins idle ist um ein Backup auszuführen (empfohlene/Standard Einstellung). Es kann auch ein Backup ausgeführt werden, ohne auf die idle Bedingung zu warten. 28 | Dies kann Probleme verursachen, die zu korrupten Backups führen, da in Dateien geschrieben werden könnte, während die Datei ins Backup kopiert wird. 29 | Bei lang laufenden Jobs (z.B. Test Jobs) kann es sein, dass es keine freien Zeitslots für das Backup gibt. Falls die Jobs nur auf Agents laufen, kann diese Option deaktiviert werden. 30 |

31 |

32 | HINWEIS:
Es kann nicht 100% garantiert werden, dass das BAckup vollständig ist, falls diese Option deaktiviert ist. 33 |

34 |
35 | -------------------------------------------------------------------------------- /src/main/webapp/help/help-restore.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | Choose an available date of a backup and press the button.
28 | After the restore action is finished, you will need to restart Jenkins. 29 |

30 |

31 | NOTE:
The restore operation overwrites the configuration with the backup data. 32 |

33 |
34 | -------------------------------------------------------------------------------- /src/main/webapp/help/help-restoreNextBuildNumber.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | If this option is enabled, the file nextBuildNumber will also be restored. 28 |

29 |

30 | NOTE:
Take special care when restoring a backup that contains a nextBuildNumber file, as this may potentially cause a lot of problems. 31 |

32 |
33 | -------------------------------------------------------------------------------- /src/main/webapp/help/help-restoreNextBuildNumber_de.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | Falls diese Option aktiviert ist, wird die Datei nextBuildNumber aus dem Backup wiederhergestellt. 28 |

29 |

30 | HINWEIS:
Besondere Aufmerksamkeit ist nötig wenn ein Backup mit nextBuildNumber Dateien wiederhergestellt wird, 31 | da dies potenziell viele Probleme verursachen kann. 32 |

33 |
34 | -------------------------------------------------------------------------------- /src/main/webapp/help/help-restorePlugins.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | If this option is enabled, the plugins get restored.
28 | You need an active internet connection to the update server, because plugins will be downloaded from the update server to keep the backup small. 29 |

30 |
31 | -------------------------------------------------------------------------------- /src/main/webapp/help/help-restorePlugins_de.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | Falls diese Option aktiviert ist, werden die Plugins aus dem Backup wiederhergestellt.
28 | Es wird eine aktive Internetverbindung zum Update Server benötigt, da die Plugins neu heruntergeladen werden um das Backup klein zu halten. 29 |

30 |
31 | -------------------------------------------------------------------------------- /src/main/webapp/help/help-restore_de.html: -------------------------------------------------------------------------------- 1 | 24 | 25 |
26 |

27 | Wähle ein verfügbares Backup-Datum und klicke den Button.
28 | Nach dem Restore fertig ist, muss Jenkins neugestartet werden. 29 |

30 |

31 | HINWEIS:
Die Restore Operation überschreibt die Konfiguration mit den Dateien aus dem Backup. 32 |

33 |
34 | -------------------------------------------------------------------------------- /src/test/java/org/jvnet/hudson/plugins/thinbackup/TestConfigWithUi.java: -------------------------------------------------------------------------------- 1 | package org.jvnet.hudson.plugins.thinbackup; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import org.htmlunit.html.HtmlForm; 6 | import org.htmlunit.html.HtmlNumberInput; 7 | import org.htmlunit.html.HtmlTextInput; 8 | import org.junit.jupiter.api.Test; 9 | import org.jvnet.hudson.test.JenkinsRule; 10 | import org.jvnet.hudson.test.junit.jupiter.WithJenkins; 11 | 12 | @WithJenkins 13 | class TestConfigWithUi { 14 | 15 | /** 16 | * Sets backupPath and max stored full backups and checks that it survives the next visit. 17 | */ 18 | @Test 19 | void uiAndStorage(JenkinsRule r) throws Throwable { 20 | assertEquals("", ThinBackupPluginImpl.get().getBackupPath()); 21 | assertEquals(-1, ThinBackupPluginImpl.get().getNrMaxStoredFull()); 22 | final HtmlForm config = r.createWebClient().goTo("configure").getFormByName("config"); 23 | 24 | HtmlTextInput textbox = config.getInputByName("_.backupPath"); 25 | HtmlNumberInput numberInput = config.getInputByName("_.nrMaxStoredFull"); 26 | textbox.setText("c:\\temp"); 27 | numberInput.setText("10"); 28 | r.submit(config); 29 | assertEquals("c:\\temp", ThinBackupPluginImpl.get().getBackupPath(), "global config page let us edit it"); 30 | assertEquals(10, ThinBackupPluginImpl.get().getNrMaxStoredFull(), "global config page let us edit it"); 31 | 32 | r.restart(); 33 | 34 | assertEquals("c:\\temp", ThinBackupPluginImpl.get().getBackupPath(), "still there after restart of Jenkins"); 35 | assertEquals(10, ThinBackupPluginImpl.get().getNrMaxStoredFull(), "global config page let us edit it"); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/org/jvnet/hudson/plugins/thinbackup/TestHelper.java: -------------------------------------------------------------------------------- 1 | package org.jvnet.hudson.plugins.thinbackup; 2 | 3 | import java.io.File; 4 | import java.io.FileWriter; 5 | import java.io.IOException; 6 | import java.io.Writer; 7 | import org.jvnet.hudson.plugins.thinbackup.backup.HudsonBackup; 8 | 9 | public class TestHelper { 10 | public static final String CONFIG_XML_CONTENTS = "FILLED WITH DATA... "; 11 | public static final String CONCRETE_BUILD_DIRECTORY_NAME = "42"; 12 | public static final String TEST_JOB_NAME = "test"; 13 | 14 | /** 15 | * When deleting multibranch jobs / folders or removing them we saw leftover directories in the Jenkins 16 | * filesystem. They do not contain a config.xml nor any other file. We simulate a structure like that here: 17 | *
JENKINS_HOME/jobs/jobName
 18 |      * '- jobs
19 | * @param jenkinsHome 20 | * @param jobName 21 | * @return 22 | */ 23 | public static File createMaliciousMultiJob(File jenkinsHome, String jobName) throws Exception { 24 | final File emptyJobDir = newFolder(jenkinsHome, HudsonBackup.JOBS_DIR_NAME, "empty"); 25 | newFolder(emptyJobDir, jobName); 26 | 27 | return emptyJobDir; 28 | } 29 | 30 | public static File addNewBuildToJob(File job) throws Exception { 31 | final File builds = newFolder(job, HudsonBackup.BUILDS_DIR_NAME); 32 | final File build = newFolder(builds, CONCRETE_BUILD_DIRECTORY_NAME); 33 | 34 | final File changelogDir = newFolder(build, HudsonBackup.CHANGELOG_HISTORY_PLUGIN_DIR_NAME); 35 | newFile(changelogDir, "1.xml"); 36 | newFile(changelogDir, "2.xml"); 37 | 38 | newFile(build, "build.xml"); 39 | newFile(build, "changelog.xml"); 40 | newFile(build, "log"); 41 | newFile(build, "revision.txt"); 42 | newFile(build, "logfile.log"); 43 | newFile(build, "logfile.xlog"); 44 | 45 | final File archiveDir = newFolder(build, HudsonBackup.ARCHIVE_DIR_NAME); 46 | newFile(archiveDir, "someFile.log"); 47 | 48 | return build; 49 | } 50 | 51 | public static void addSingleConfigurationResult(File job) throws Exception { 52 | File configurations = newFolder(job, HudsonBackup.CONFIGURATIONS_DIR_NAME); 53 | File axis_x = newFolder(configurations, "axis-x"); 54 | File xValueA = newFolder(axis_x, "a"); 55 | File xValueB = newFolder(axis_x, "b"); 56 | 57 | addNewBuildToJob(xValueA); 58 | addNewBuildToJob(xValueB); 59 | 60 | newFile(xValueA, "config.xml"); 61 | File nextBuildnumber = newFile(xValueA, "nextBuildNumber"); 62 | addBuildNumber(nextBuildnumber); 63 | 64 | newFile(xValueB, "config.xml"); 65 | nextBuildnumber = newFile(xValueB, "nextBuildNumber"); 66 | addBuildNumber(nextBuildnumber); 67 | } 68 | 69 | public static void addSinglePromotionResult(File job) throws Exception { 70 | File promotions = newFolder(job, HudsonBackup.PROMOTIONS_DIR_NAME); 71 | File promotion_x = newFolder(promotions, "promotion-x"); 72 | 73 | addNewBuildToJob(promotion_x); 74 | 75 | newFile(promotion_x, "config.xml"); 76 | File nextBuildnumber = newFile(promotion_x, "nextBuildNumber"); 77 | addBuildNumber(nextBuildnumber); 78 | } 79 | 80 | public static void addSingleMultibranchResult(File job) throws Exception { 81 | File configurations = newFolder(job, HudsonBackup.MULTIBRANCH_DIR_NAME); 82 | File branch1 = newFolder(configurations, "master"); 83 | File branch2 = newFolder(configurations, "development"); 84 | 85 | addNewBuildToJob(branch1); 86 | addNewBuildToJob(branch2); 87 | 88 | newFile(branch1, "config.xml"); 89 | File nextBuildnumber = newFile(branch1, "nextBuildNumber"); 90 | addBuildNumber(nextBuildnumber); 91 | 92 | newFile(branch2, "config.xml"); 93 | nextBuildnumber = newFile(branch2, "nextBuildNumber"); 94 | addBuildNumber(nextBuildnumber); 95 | 96 | File indexing = newFolder(job, HudsonBackup.INDEXING_DIR_NAME); 97 | newFile(indexing, "indexing.xml"); 98 | newFile(indexing, "indexing.log"); 99 | } 100 | 101 | private static void addBuildNumber(final File nextBuildNumberFile) { 102 | try (Writer w = new FileWriter(nextBuildNumberFile)) { 103 | w.write("1234"); 104 | } catch (final IOException e) { 105 | // catch me if you can! 106 | } 107 | } 108 | 109 | public static File newFolder(File root, String... subDirs) throws Exception { 110 | String subFolder = String.join("/", subDirs); 111 | File result = new File(root, subFolder); 112 | if (!result.exists() && !result.mkdirs()) { 113 | throw new IOException("Couldn't create folders " + result.getAbsolutePath()); 114 | } 115 | return result; 116 | } 117 | 118 | public static File newFile(File root, String filename) throws Exception { 119 | File result = new File(root, filename); 120 | if (!result.exists() && !result.createNewFile()) { 121 | throw new IOException("Couldn't create file " + result.getAbsolutePath()); 122 | } 123 | return result; 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/test/java/org/jvnet/hudson/plugins/thinbackup/TestJenkinsConfigAsCode.java: -------------------------------------------------------------------------------- 1 | package org.jvnet.hudson.plugins.thinbackup; 2 | 3 | import static org.junit.jupiter.api.Assertions.*; 4 | 5 | import io.jenkins.plugins.casc.misc.ConfiguredWithCode; 6 | import io.jenkins.plugins.casc.misc.JenkinsConfiguredWithCodeRule; 7 | import io.jenkins.plugins.casc.misc.junit.jupiter.WithJenkinsConfiguredWithCode; 8 | import org.junit.jupiter.api.Test; 9 | 10 | @WithJenkinsConfiguredWithCode 11 | class TestJenkinsConfigAsCode { 12 | 13 | @Test 14 | @ConfiguredWithCode("configuration-as-code.yml") 15 | void should_support_configuration_as_code(JenkinsConfiguredWithCodeRule r) { 16 | ThinBackupPluginImpl thinBackupPluginConfig = ThinBackupPluginImpl.get(); 17 | final String backupPath = thinBackupPluginConfig.getBackupPath(); 18 | // test strings 19 | assertEquals("c:\\temp\\thin-backup", backupPath); 20 | assertEquals("0 12 * * 1-5", thinBackupPluginConfig.getDiffBackupSchedule()); 21 | assertEquals("0 12 * * 1", thinBackupPluginConfig.getFullBackupSchedule()); 22 | assertEquals("^.*\\.(log)$", thinBackupPluginConfig.getExcludedFilesRegex()); 23 | assertEquals("^.*\\.(txt)$", thinBackupPluginConfig.getBackupAdditionalFilesRegex()); 24 | // test numbers 25 | assertEquals(120, thinBackupPluginConfig.getForceQuietModeTimeout()); 26 | assertEquals(-1, thinBackupPluginConfig.getNrMaxStoredFull()); 27 | // test booleans 28 | assertTrue(thinBackupPluginConfig.isWaitForIdle()); 29 | assertTrue(thinBackupPluginConfig.isBackupBuildResults()); 30 | assertTrue(thinBackupPluginConfig.isFailFast()); 31 | 32 | assertFalse(thinBackupPluginConfig.isCleanupDiff()); 33 | assertFalse(thinBackupPluginConfig.isMoveOldBackupsToZipFile()); 34 | assertFalse(thinBackupPluginConfig.isBackupBuildArchive()); 35 | assertFalse(thinBackupPluginConfig.isBackupPluginArchives()); 36 | assertFalse(thinBackupPluginConfig.isBackupUserContents()); 37 | assertFalse(thinBackupPluginConfig.isBackupConfigHistory()); 38 | assertFalse(thinBackupPluginConfig.isBackupAdditionalFiles()); 39 | assertFalse(thinBackupPluginConfig.isBackupNextBuildNumber()); 40 | assertFalse(thinBackupPluginConfig.isBackupBuildsToKeepOnly()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/org/jvnet/hudson/plugins/thinbackup/TestJenkinsWithOldConfig.java: -------------------------------------------------------------------------------- 1 | package org.jvnet.hudson.plugins.thinbackup; 2 | 3 | import static org.hamcrest.MatcherAssert.assertThat; 4 | import static org.hamcrest.Matchers.hasItemInArray; 5 | import static org.hamcrest.Matchers.not; 6 | import static org.junit.jupiter.api.Assertions.assertEquals; 7 | 8 | import hudson.ExtensionList; 9 | import java.io.File; 10 | import org.junit.jupiter.api.Test; 11 | import org.jvnet.hudson.test.JenkinsRule; 12 | import org.jvnet.hudson.test.junit.jupiter.WithJenkins; 13 | import org.jvnet.hudson.test.recipes.LocalData; 14 | 15 | @WithJenkins 16 | class TestJenkinsWithOldConfig { 17 | 18 | @LocalData 19 | @Test 20 | void testOldConfig(JenkinsRule r) { 21 | final File rootDir = r.jenkins.getRootDir(); 22 | final String[] list = rootDir.list(); 23 | // check that data is converted 24 | assertThat(list, not(hasItemInArray("thinBackup.xml"))); 25 | assertThat(list, hasItemInArray("org.jvnet.hudson.plugins.thinbackup.ThinBackupPluginImpl.xml")); 26 | // check that config is correctly set 27 | final ExtensionList plugins = r.jenkins.getExtensionList(ThinBackupPluginImpl.class); 28 | assertEquals(1, plugins.size()); 29 | assertEquals("c:\\temp\\thin-backup", plugins.get(0).getBackupPath()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/org/jvnet/hudson/plugins/thinbackup/TestThinBackupPeriodicWork.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 Matthias Steinkogler, Thomas Fürer 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see http://www.gnu.org/licenses. 16 | */ 17 | package org.jvnet.hudson.plugins.thinbackup; 18 | 19 | import static org.junit.jupiter.api.Assertions.assertEquals; 20 | 21 | import org.junit.jupiter.api.Test; 22 | 23 | class TestThinBackupPeriodicWork { 24 | 25 | @Test 26 | void testGetNextScheduledBackup() { 27 | final long currentTime = System.currentTimeMillis(); 28 | final String fullCron = "* * * * *"; 29 | final String diffCron = "* * * * *"; 30 | final ThinBackupPeriodicWork.BackupType backupType = 31 | ThinBackupPeriodicWork.getNextScheduledBackupType(currentTime, fullCron, diffCron); 32 | assertEquals(ThinBackupPeriodicWork.BackupType.FULL, backupType); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/org/jvnet/hudson/plugins/thinbackup/backup/BackupDirStructureSetup.java: -------------------------------------------------------------------------------- 1 | package org.jvnet.hudson.plugins.thinbackup.backup; 2 | 3 | import static org.jvnet.hudson.plugins.thinbackup.TestHelper.newFolder; 4 | import static org.jvnet.hudson.plugins.thinbackup.utils.Utils.getFormattedDirectory; 5 | 6 | import java.io.File; 7 | import java.nio.file.Path; 8 | import java.util.Calendar; 9 | import org.junit.jupiter.api.BeforeEach; 10 | import org.junit.jupiter.api.io.TempDir; 11 | import org.jvnet.hudson.plugins.thinbackup.ThinBackupPeriodicWork.BackupType; 12 | 13 | public class BackupDirStructureSetup { 14 | 15 | @TempDir 16 | protected Path tmpFolder; 17 | 18 | protected File backupDir; 19 | 20 | protected File full1; 21 | protected File diff11; 22 | protected File diff12; 23 | protected File diff13; 24 | protected File diff14; 25 | 26 | protected File full2; 27 | protected File diff21; 28 | 29 | protected File full3; 30 | protected File diff31; 31 | 32 | protected File diff41; 33 | 34 | @BeforeEach 35 | void setup() throws Exception { 36 | backupDir = tmpFolder.toFile(); 37 | 38 | final Calendar cal = Calendar.getInstance(); 39 | cal.set(2011, Calendar.JANUARY, 1, 0, 0); 40 | full1 = newFolder(getFormattedDirectory(backupDir, BackupType.FULL, cal.getTime())); 41 | full1.setLastModified(cal.getTimeInMillis()); 42 | cal.set(2011, Calendar.JANUARY, 1, 0, 1); 43 | diff11 = newFolder(getFormattedDirectory(backupDir, BackupType.DIFF, cal.getTime())); 44 | diff11.setLastModified(cal.getTimeInMillis()); 45 | cal.set(2011, Calendar.JANUARY, 1, 0, 2); 46 | diff12 = newFolder(getFormattedDirectory(backupDir, BackupType.DIFF, cal.getTime())); 47 | diff12.setLastModified(cal.getTimeInMillis()); 48 | cal.set(2011, Calendar.JANUARY, 1, 0, 3); 49 | diff13 = newFolder(getFormattedDirectory(backupDir, BackupType.DIFF, cal.getTime())); 50 | diff13.setLastModified(cal.getTimeInMillis()); 51 | cal.set(2011, Calendar.JANUARY, 1, 0, 4); 52 | diff14 = newFolder(getFormattedDirectory(backupDir, BackupType.DIFF, cal.getTime())); 53 | diff14.setLastModified(cal.getTimeInMillis()); 54 | 55 | cal.set(2011, Calendar.FEBRUARY, 1, 0, 0); 56 | full2 = newFolder(getFormattedDirectory(backupDir, BackupType.FULL, cal.getTime())); 57 | full2.setLastModified(cal.getTimeInMillis()); 58 | cal.set(2011, Calendar.FEBRUARY, 1, 0, 1); 59 | diff21 = newFolder(getFormattedDirectory(backupDir, BackupType.DIFF, cal.getTime())); 60 | diff21.setLastModified(cal.getTimeInMillis()); 61 | 62 | cal.set(2011, Calendar.MARCH, 1, 0, 0); 63 | full3 = newFolder(getFormattedDirectory(backupDir, BackupType.FULL, cal.getTime())); 64 | full3.setLastModified(cal.getTimeInMillis()); 65 | cal.set(2011, Calendar.MARCH, 1, 0, 1); 66 | diff31 = newFolder(getFormattedDirectory(backupDir, BackupType.DIFF, cal.getTime())); 67 | diff31.setLastModified(cal.getTimeInMillis()); 68 | 69 | cal.set(2010, Calendar.APRIL, 1, 0, 1); 70 | diff41 = newFolder(getFormattedDirectory(backupDir, BackupType.DIFF, cal.getTime())); 71 | diff41.setLastModified(cal.getTimeInMillis()); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/test/java/org/jvnet/hudson/plugins/thinbackup/backup/TestBackupMatrixJob.java: -------------------------------------------------------------------------------- 1 | package org.jvnet.hudson.plugins.thinbackup.backup; 2 | 3 | import static org.hamcrest.MatcherAssert.assertThat; 4 | import static org.hamcrest.Matchers.hasItems; 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | import static org.junit.jupiter.api.Assertions.assertTrue; 7 | import static org.jvnet.hudson.plugins.thinbackup.TestHelper.newFolder; 8 | 9 | import hudson.model.FreeStyleProject; 10 | import java.io.File; 11 | import java.nio.file.Path; 12 | import java.util.Date; 13 | import java.util.List; 14 | import org.junit.jupiter.api.Test; 15 | import org.junit.jupiter.api.io.TempDir; 16 | import org.jvnet.hudson.plugins.thinbackup.TestHelper; 17 | import org.jvnet.hudson.plugins.thinbackup.ThinBackupPeriodicWork.BackupType; 18 | import org.jvnet.hudson.plugins.thinbackup.ThinBackupPluginImpl; 19 | import org.jvnet.hudson.plugins.thinbackup.utils.Utils; 20 | import org.jvnet.hudson.test.JenkinsRule; 21 | import org.jvnet.hudson.test.junit.jupiter.WithJenkins; 22 | 23 | @WithJenkins 24 | class TestBackupMatrixJob { 25 | 26 | @TempDir 27 | private File tmpFolder; 28 | 29 | @Test 30 | void testGeneralFilesAndFolders(JenkinsRule r) throws Exception { 31 | File backupDir = newFolder(tmpFolder, "junit"); 32 | 33 | final ThinBackupPluginImpl thinBackupPlugin = ThinBackupPluginImpl.get(); 34 | thinBackupPlugin.setBackupPath(backupDir.getAbsolutePath()); 35 | thinBackupPlugin.setBackupBuildResults(true); 36 | final File rootDir = r.jenkins.getRootDir(); 37 | final Date date = new Date(); 38 | 39 | // create job 40 | FreeStyleProject project = r.createFreeStyleProject("test"); 41 | project.getRootDir(); 42 | 43 | // run backup 44 | new HudsonBackup(thinBackupPlugin, BackupType.FULL, date, r.jenkins).backup(); 45 | final String[] listedBackupDirs = backupDir.list(); 46 | assertEquals(1, listedBackupDirs.length); 47 | 48 | Path backupFolderName = 49 | Utils.getFormattedDirectory(backupDir, BackupType.FULL, date).toPath(); 50 | final List listedBackupFiles = List.of(backupFolderName.toFile().list()); 51 | 52 | // check basic files are there 53 | assertThat( 54 | listedBackupFiles, 55 | hasItems( 56 | "installedPlugins.xml", 57 | "config.xml", 58 | "org.jvnet.hudson.plugins.thinbackup.ThinBackupPluginImpl.xml", 59 | "hudson.model.UpdateCenter.xml", 60 | "jobs")); 61 | } 62 | 63 | @Test 64 | void testMatrixFilesAndFolders(JenkinsRule r) throws Exception { 65 | File backupDir = newFolder(tmpFolder, "junit"); 66 | 67 | final ThinBackupPluginImpl thinBackupPlugin = ThinBackupPluginImpl.get(); 68 | thinBackupPlugin.setBackupPath(backupDir.getAbsolutePath()); 69 | thinBackupPlugin.setBackupBuildResults(true); 70 | final File rootDir = r.jenkins.getRootDir(); 71 | final Date date = new Date(); 72 | 73 | // create job 74 | FreeStyleProject project = r.createFreeStyleProject("test"); 75 | 76 | // put in additional stuff in jobs folder 77 | TestHelper.addNewBuildToJob(project.getRootDir()); 78 | 79 | TestHelper.addSingleConfigurationResult(project.getRootDir()); 80 | 81 | // run backup 82 | new HudsonBackup(thinBackupPlugin, BackupType.FULL, date, r.jenkins).backup(); 83 | final String[] listedBackupDirs = backupDir.list(); 84 | assertEquals(1, listedBackupDirs.length); 85 | 86 | Path backupFolderName = 87 | Utils.getFormattedDirectory(backupDir, BackupType.FULL, date).toPath(); 88 | final List listedBackupFiles = List.of(backupFolderName.toFile().list()); 89 | 90 | // check basic files are there 91 | assertThat( 92 | listedBackupFiles, 93 | hasItems( 94 | "installedPlugins.xml", 95 | "config.xml", 96 | "org.jvnet.hudson.plugins.thinbackup.ThinBackupPluginImpl.xml", 97 | "hudson.model.UpdateCenter.xml", 98 | "jobs")); 99 | 100 | File jobBackup = new File(backupFolderName.toFile(), "jobs/" + TestHelper.TEST_JOB_NAME); 101 | 102 | assertTrue(new File(jobBackup, HudsonBackup.CONFIGURATIONS_DIR_NAME).exists()); 103 | assertTrue(new File(jobBackup, HudsonBackup.CONFIGURATIONS_DIR_NAME + "/axis-x/a").exists()); 104 | assertTrue(new File(jobBackup, HudsonBackup.CONFIGURATIONS_DIR_NAME + "/axis-x/b").exists()); 105 | assertTrue(new File( 106 | jobBackup, 107 | HudsonBackup.CONFIGURATIONS_DIR_NAME + "/axis-x/a/" + HudsonBackup.BUILDS_DIR_NAME + "/" 108 | + TestHelper.CONCRETE_BUILD_DIRECTORY_NAME + "/build.xml") 109 | .exists()); 110 | assertTrue(new File( 111 | jobBackup, 112 | HudsonBackup.CONFIGURATIONS_DIR_NAME + "/axis-x/b/" + HudsonBackup.BUILDS_DIR_NAME + "/" 113 | + TestHelper.CONCRETE_BUILD_DIRECTORY_NAME + "/build.xml") 114 | .exists()); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/test/java/org/jvnet/hudson/plugins/thinbackup/backup/TestBackupMultibranchJob.java: -------------------------------------------------------------------------------- 1 | package org.jvnet.hudson.plugins.thinbackup.backup; 2 | 3 | import static org.hamcrest.MatcherAssert.assertThat; 4 | import static org.hamcrest.Matchers.hasItems; 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | import static org.junit.jupiter.api.Assertions.assertTrue; 7 | import static org.jvnet.hudson.plugins.thinbackup.TestHelper.newFolder; 8 | 9 | import hudson.model.FreeStyleProject; 10 | import java.io.File; 11 | import java.nio.file.Path; 12 | import java.util.Date; 13 | import java.util.List; 14 | import org.junit.jupiter.api.Test; 15 | import org.junit.jupiter.api.io.TempDir; 16 | import org.jvnet.hudson.plugins.thinbackup.TestHelper; 17 | import org.jvnet.hudson.plugins.thinbackup.ThinBackupPeriodicWork.BackupType; 18 | import org.jvnet.hudson.plugins.thinbackup.ThinBackupPluginImpl; 19 | import org.jvnet.hudson.plugins.thinbackup.utils.Utils; 20 | import org.jvnet.hudson.test.JenkinsRule; 21 | import org.jvnet.hudson.test.junit.jupiter.WithJenkins; 22 | 23 | @WithJenkins 24 | class TestBackupMultibranchJob { 25 | 26 | @TempDir 27 | private File tmpFolder; 28 | 29 | @Test 30 | void testFullBuildResultsBackup(JenkinsRule r) throws Exception { 31 | File backupDir = newFolder(tmpFolder, "junit"); 32 | 33 | final ThinBackupPluginImpl thinBackupPlugin = ThinBackupPluginImpl.get(); 34 | thinBackupPlugin.setBackupPath(backupDir.getAbsolutePath()); 35 | thinBackupPlugin.setBackupBuildResults(true); 36 | final File rootDir = r.jenkins.getRootDir(); 37 | final Date date = new Date(); 38 | 39 | // create job 40 | FreeStyleProject project = r.createFreeStyleProject("test"); 41 | project.getRootDir(); 42 | 43 | TestHelper.addNewBuildToJob(project.getRootDir()); 44 | 45 | TestHelper.addSingleMultibranchResult(project.getRootDir()); 46 | 47 | // run backup 48 | new HudsonBackup(thinBackupPlugin, BackupType.FULL, date, r.jenkins).backup(); 49 | final String[] listedBackupDirs = backupDir.list(); 50 | assertEquals(1, listedBackupDirs.length); 51 | 52 | Path backupFolderName = 53 | Utils.getFormattedDirectory(backupDir, BackupType.FULL, date).toPath(); 54 | final List listedBackupFiles = List.of(backupFolderName.toFile().list()); 55 | 56 | // check basic files are there 57 | assertThat( 58 | listedBackupFiles, 59 | hasItems( 60 | "installedPlugins.xml", 61 | "config.xml", 62 | "org.jvnet.hudson.plugins.thinbackup.ThinBackupPluginImpl.xml", 63 | "hudson.model.UpdateCenter.xml", 64 | "jobs")); 65 | 66 | File jobBackup = new File(backupFolderName.toFile(), "jobs/" + TestHelper.TEST_JOB_NAME); 67 | 68 | assertTrue(new File(jobBackup, HudsonBackup.MULTIBRANCH_DIR_NAME).exists()); 69 | assertTrue(new File(jobBackup, HudsonBackup.MULTIBRANCH_DIR_NAME + "/master").exists()); 70 | assertTrue(new File(jobBackup, HudsonBackup.MULTIBRANCH_DIR_NAME + "/development").exists()); 71 | assertTrue(new File( 72 | jobBackup, 73 | HudsonBackup.MULTIBRANCH_DIR_NAME + "/master/" + HudsonBackup.BUILDS_DIR_NAME + "/" 74 | + TestHelper.CONCRETE_BUILD_DIRECTORY_NAME + "/build.xml") 75 | .exists()); 76 | assertTrue(new File( 77 | jobBackup, 78 | HudsonBackup.MULTIBRANCH_DIR_NAME + "/development/" + HudsonBackup.BUILDS_DIR_NAME + "/" 79 | + TestHelper.CONCRETE_BUILD_DIRECTORY_NAME + "/build.xml") 80 | .exists()); 81 | 82 | assertTrue(new File(jobBackup, HudsonBackup.INDEXING_DIR_NAME).exists()); 83 | assertTrue(new File(jobBackup, HudsonBackup.INDEXING_DIR_NAME + "/indexing.xml").exists()); 84 | assertTrue(new File(jobBackup, HudsonBackup.INDEXING_DIR_NAME + "/indexing.log").exists()); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/test/java/org/jvnet/hudson/plugins/thinbackup/backup/TestBackupPromotedJob.java: -------------------------------------------------------------------------------- 1 | package org.jvnet.hudson.plugins.thinbackup.backup; 2 | 3 | import static org.hamcrest.MatcherAssert.assertThat; 4 | import static org.hamcrest.Matchers.hasItems; 5 | import static org.junit.jupiter.api.Assertions.assertEquals; 6 | import static org.junit.jupiter.api.Assertions.assertTrue; 7 | import static org.jvnet.hudson.plugins.thinbackup.TestHelper.newFolder; 8 | 9 | import hudson.model.FreeStyleProject; 10 | import java.io.File; 11 | import java.nio.file.Path; 12 | import java.util.Date; 13 | import java.util.List; 14 | import org.junit.jupiter.api.Test; 15 | import org.junit.jupiter.api.io.TempDir; 16 | import org.jvnet.hudson.plugins.thinbackup.TestHelper; 17 | import org.jvnet.hudson.plugins.thinbackup.ThinBackupPeriodicWork.BackupType; 18 | import org.jvnet.hudson.plugins.thinbackup.ThinBackupPluginImpl; 19 | import org.jvnet.hudson.plugins.thinbackup.utils.Utils; 20 | import org.jvnet.hudson.test.JenkinsRule; 21 | import org.jvnet.hudson.test.junit.jupiter.WithJenkins; 22 | 23 | @WithJenkins 24 | class TestBackupPromotedJob { 25 | 26 | @TempDir 27 | private File tmpFolder; 28 | 29 | @Test 30 | void testFullBuildResultsBackup(JenkinsRule r) throws Exception { 31 | File backupDir = newFolder(tmpFolder, "junit"); 32 | 33 | final ThinBackupPluginImpl thinBackupPlugin = ThinBackupPluginImpl.get(); 34 | thinBackupPlugin.setBackupPath(backupDir.getAbsolutePath()); 35 | thinBackupPlugin.setBackupBuildResults(true); 36 | final File rootDir = r.jenkins.getRootDir(); 37 | final Date date = new Date(); 38 | 39 | // create job 40 | FreeStyleProject project = r.createFreeStyleProject("test"); 41 | final File projectRootDir = project.getRootDir(); 42 | 43 | TestHelper.addNewBuildToJob(projectRootDir); 44 | 45 | TestHelper.addSinglePromotionResult(projectRootDir); 46 | 47 | // run backup 48 | new HudsonBackup(thinBackupPlugin, BackupType.FULL, date, r.jenkins).backup(); 49 | final String[] listedBackupDirs = backupDir.list(); 50 | assertEquals(1, listedBackupDirs.length); 51 | 52 | Path backupFolderName = 53 | Utils.getFormattedDirectory(backupDir, BackupType.FULL, date).toPath(); 54 | final List listedBackupFiles = List.of(backupFolderName.toFile().list()); 55 | 56 | // check basic files are there 57 | assertThat( 58 | listedBackupFiles, 59 | hasItems( 60 | "installedPlugins.xml", 61 | "config.xml", 62 | "org.jvnet.hudson.plugins.thinbackup.ThinBackupPluginImpl.xml", 63 | "hudson.model.UpdateCenter.xml", 64 | "jobs")); 65 | 66 | File jobBackup = new File(backupFolderName.toFile(), "jobs/" + TestHelper.TEST_JOB_NAME); 67 | 68 | assertTrue(new File(jobBackup, HudsonBackup.PROMOTIONS_DIR_NAME).exists()); 69 | assertTrue(new File(jobBackup, HudsonBackup.PROMOTIONS_DIR_NAME + "/promotion-x").exists()); 70 | assertTrue(new File( 71 | jobBackup, 72 | HudsonBackup.PROMOTIONS_DIR_NAME + "/promotion-x/" + HudsonBackup.BUILDS_DIR_NAME + "/" 73 | + TestHelper.CONCRETE_BUILD_DIRECTORY_NAME + "/build.xml") 74 | .exists()); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/test/java/org/jvnet/hudson/plugins/thinbackup/backup/TestBackupSet.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 Matthias Steinkogler, Thomas Fürer 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see http://www.gnu.org/licenses. 16 | */ 17 | package org.jvnet.hudson.plugins.thinbackup.backup; 18 | 19 | import static org.junit.jupiter.api.Assertions.*; 20 | import static org.jvnet.hudson.plugins.thinbackup.TestHelper.newFile; 21 | 22 | import java.io.File; 23 | import org.junit.jupiter.api.Test; 24 | 25 | class TestBackupSet extends BackupDirStructureSetup { 26 | 27 | @Test 28 | void testSimpleBackupSet() { 29 | final BackupSet setFromFull = new BackupSet(full2); 30 | assertTrue(setFromFull.isValid()); 31 | assertEquals(1, setFromFull.getDiffBackups().size()); 32 | assertEquals(full2, setFromFull.getFullBackup()); 33 | assertTrue(setFromFull.getDiffBackups().contains(diff21)); 34 | 35 | final BackupSet setFromDiff = new BackupSet(diff21); 36 | assertTrue(setFromDiff.isValid()); 37 | assertEquals(1, setFromDiff.getDiffBackups().size()); 38 | assertEquals(full2, setFromDiff.getFullBackup()); 39 | assertTrue(setFromDiff.getDiffBackups().contains(diff21)); 40 | } 41 | 42 | @Test 43 | void testDelete() throws Exception { 44 | final BackupSet setFromFull = new BackupSet(full1); 45 | assertTrue(setFromFull.isValid()); 46 | assertEquals(4, setFromFull.getDiffBackups().size()); 47 | assertEquals(full1, setFromFull.getFullBackup()); 48 | assertTrue(setFromFull.getDiffBackups().contains(diff11)); 49 | assertTrue(setFromFull.getDiffBackups().contains(diff12)); 50 | assertTrue(setFromFull.getDiffBackups().contains(diff13)); 51 | assertTrue(setFromFull.getDiffBackups().contains(diff14)); 52 | 53 | assertEquals(10, backupDir.list().length); 54 | setFromFull.delete(); 55 | assertEquals(5, backupDir.list().length); 56 | } 57 | 58 | @Test 59 | void testInvalidSet() throws Exception { 60 | final BackupSet setFromDiff = new BackupSet(diff41); 61 | assertFalse(setFromDiff.isValid()); 62 | assertFalse(setFromDiff.isValid()); 63 | assertNull(setFromDiff.getFullBackup()); 64 | assertNull(setFromDiff.getDiffBackups()); 65 | 66 | assertEquals(10, backupDir.list().length); 67 | setFromDiff.delete(); 68 | assertEquals(10, backupDir.list().length); 69 | } 70 | 71 | @Test 72 | void testBackupSetCompare() { 73 | final BackupSet backupSet1 = new BackupSet(full1); 74 | final BackupSet backupSet2 = new BackupSet(full2); 75 | final BackupSet invalidBackupSet = new BackupSet(diff41); 76 | 77 | assertEquals(0, backupSet1.compareTo(backupSet1)); 78 | assertEquals(-1, backupSet1.compareTo(backupSet2)); 79 | assertEquals(1, backupSet2.compareTo(backupSet1)); 80 | assertEquals(1, backupSet1.compareTo(invalidBackupSet)); 81 | assertEquals(1, backupSet2.compareTo(invalidBackupSet)); 82 | assertEquals(-1, invalidBackupSet.compareTo(backupSet1)); 83 | } 84 | 85 | @Test 86 | void testBackupSetContainsDirectory() throws Exception { 87 | final BackupSet backupSet1 = new BackupSet(full1); 88 | 89 | assertTrue(backupSet1.containsDirectory(full1)); 90 | assertTrue(backupSet1.containsDirectory(new File(full1.getAbsolutePath()))); 91 | assertTrue(backupSet1.containsDirectory(diff11)); 92 | assertTrue(backupSet1.containsDirectory(diff12)); 93 | assertTrue(backupSet1.containsDirectory(diff13)); 94 | assertTrue(backupSet1.containsDirectory(diff14)); 95 | assertFalse(backupSet1.containsDirectory(null)); 96 | assertFalse(backupSet1.containsDirectory(full2)); 97 | assertFalse(backupSet1.containsDirectory(diff21)); 98 | assertFalse(backupSet1.containsDirectory(new File(diff21.getAbsolutePath()))); 99 | 100 | final BackupSet invalidBackupSet = new BackupSet(diff41); 101 | assertFalse(invalidBackupSet.containsDirectory(diff41)); 102 | 103 | final File tempDir = new File(System.getProperty("java.io.tmpdir")); 104 | backupDir = new File(tempDir, "BackupDirForHudsonBackupTest"); 105 | final File testFile = newFile(tmpFolder.toFile(), "tempFile.nxt"); 106 | assertFalse(backupSet1.containsDirectory(testFile)); 107 | assertFalse(backupSet1.containsDirectory(new File(testFile.getAbsolutePath()))); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/test/java/org/jvnet/hudson/plugins/thinbackup/backup/TestBackupWithCloudBeesFolder.java: -------------------------------------------------------------------------------- 1 | package org.jvnet.hudson.plugins.thinbackup.backup; 2 | 3 | import static org.hamcrest.MatcherAssert.assertThat; 4 | import static org.hamcrest.Matchers.containsInAnyOrder; 5 | import static org.hamcrest.Matchers.hasItems; 6 | import static org.junit.jupiter.api.Assertions.assertEquals; 7 | import static org.junit.jupiter.api.Assertions.assertTrue; 8 | import static org.jvnet.hudson.plugins.thinbackup.TestHelper.newFolder; 9 | 10 | import hudson.model.FreeStyleProject; 11 | import java.io.File; 12 | import java.nio.file.Path; 13 | import java.util.Date; 14 | import java.util.List; 15 | import org.junit.jupiter.api.Test; 16 | import org.junit.jupiter.api.io.TempDir; 17 | import org.jvnet.hudson.plugins.thinbackup.ThinBackupPeriodicWork.BackupType; 18 | import org.jvnet.hudson.plugins.thinbackup.ThinBackupPluginImpl; 19 | import org.jvnet.hudson.plugins.thinbackup.utils.Utils; 20 | import org.jvnet.hudson.test.JenkinsRule; 21 | import org.jvnet.hudson.test.MockFolder; 22 | import org.jvnet.hudson.test.junit.jupiter.WithJenkins; 23 | 24 | @WithJenkins 25 | class TestBackupWithCloudBeesFolder { 26 | 27 | @TempDir 28 | private File tmpFolder; 29 | 30 | @Test 31 | void testWithFolder(JenkinsRule r) throws Exception { 32 | File backupDir = newFolder(tmpFolder, "junit"); 33 | 34 | final ThinBackupPluginImpl thinBackupPlugin = ThinBackupPluginImpl.get(); 35 | thinBackupPlugin.setBackupPath(backupDir.getAbsolutePath()); 36 | thinBackupPlugin.setBackupBuildResults(true); 37 | final File rootDir = r.jenkins.getRootDir(); 38 | final Date date = new Date(); 39 | 40 | // create job 41 | r.createFreeStyleProject("freeStyleJob"); 42 | 43 | // create folder 44 | final MockFolder folder1 = r.createFolder("folder1"); 45 | 46 | // create job in folder1 47 | var elementsJob = folder1.createProject(FreeStyleProject.class, "elements"); 48 | r.assertBuildStatusSuccess(elementsJob.scheduleBuild2(0)); 49 | 50 | // run backup 51 | new HudsonBackup(thinBackupPlugin, BackupType.FULL, date, r.jenkins).backup(); 52 | final String[] listedBackupDirs = backupDir.list(); 53 | assertEquals(1, listedBackupDirs.length); 54 | 55 | Path backupFolderName = 56 | Utils.getFormattedDirectory(backupDir, BackupType.FULL, date).toPath(); 57 | final List listedBackupFiles = List.of(backupFolderName.toFile().list()); 58 | 59 | // check basic files are there 60 | assertThat( 61 | listedBackupFiles, 62 | hasItems( 63 | "installedPlugins.xml", 64 | "config.xml", 65 | "org.jvnet.hudson.plugins.thinbackup.ThinBackupPluginImpl.xml", 66 | "hudson.model.UpdateCenter.xml", 67 | "jobs")); 68 | 69 | assertTrue(new File(backupFolderName.toFile(), "jobs/folder1/jobs/elements/config.xml").exists()); 70 | 71 | // check folder and job is there 72 | File jobBackup = new File(backupFolderName.toFile(), "jobs"); 73 | final List listedFolder = List.of(jobBackup.list()); 74 | assertThat(listedFolder, containsInAnyOrder("folder1", "freeStyleJob")); 75 | 76 | // check folder for config and jobs folder 77 | File elementBackup = new File(jobBackup, "folder1"); 78 | final List listedElements = List.of(elementBackup.list()); 79 | assertThat(listedElements, containsInAnyOrder("config.xml", "jobs")); 80 | 81 | // check job is in folder 82 | File folderJobDir = new File(elementBackup, "jobs/elements"); 83 | final List listedJobElements = List.of(folderJobDir.list()); 84 | assertThat(listedJobElements, containsInAnyOrder("config.xml", "builds")); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/test/java/org/jvnet/hudson/plugins/thinbackup/backup/TestBackupZipping.java: -------------------------------------------------------------------------------- 1 | package org.jvnet.hudson.plugins.thinbackup.backup; 2 | 3 | import static org.hamcrest.MatcherAssert.assertThat; 4 | import static org.hamcrest.Matchers.hasItems; 5 | import static org.hamcrest.Matchers.lessThan; 6 | import static org.junit.jupiter.api.Assertions.assertEquals; 7 | import static org.junit.jupiter.api.Assertions.assertFalse; 8 | import static org.junit.jupiter.api.Assertions.assertNotNull; 9 | import static org.junit.jupiter.api.Assertions.assertTrue; 10 | import static org.jvnet.hudson.plugins.thinbackup.TestHelper.newFolder; 11 | 12 | import hudson.model.FreeStyleProject; 13 | import java.io.File; 14 | import java.nio.file.Path; 15 | import java.time.Duration; 16 | import java.time.Instant; 17 | import java.util.Date; 18 | import java.util.Enumeration; 19 | import java.util.List; 20 | import java.util.zip.ZipEntry; 21 | import java.util.zip.ZipFile; 22 | import org.junit.jupiter.api.Test; 23 | import org.junit.jupiter.api.io.TempDir; 24 | import org.jvnet.hudson.plugins.thinbackup.ThinBackupPeriodicWork; 25 | import org.jvnet.hudson.plugins.thinbackup.ThinBackupPluginImpl; 26 | import org.jvnet.hudson.plugins.thinbackup.utils.Utils; 27 | import org.jvnet.hudson.test.JenkinsRule; 28 | import org.jvnet.hudson.test.MockFolder; 29 | import org.jvnet.hudson.test.junit.jupiter.WithJenkins; 30 | 31 | @WithJenkins 32 | class TestBackupZipping { 33 | 34 | @TempDir 35 | private File tmpFolder; 36 | 37 | @Test 38 | void testThinBackupZipper(JenkinsRule r) throws Exception { 39 | File backupDir = newFolder(tmpFolder, "junit"); 40 | 41 | final ThinBackupPluginImpl thinBackupPlugin = ThinBackupPluginImpl.get(); 42 | thinBackupPlugin.setBackupPath(backupDir.getAbsolutePath()); 43 | thinBackupPlugin.setBackupBuildResults(true); 44 | 45 | Instant now = Instant.now(); // current date 46 | Instant before = now.minus(Duration.ofDays(7)); 47 | Date dateBefore = Date.from(before); 48 | 49 | final File rootDir = r.jenkins.getRootDir(); 50 | final Date date = Date.from(now); 51 | 52 | // create job 53 | r.createFreeStyleProject("freeStyleJob"); 54 | 55 | // create folder 56 | final MockFolder folder1 = r.createFolder("folder1"); 57 | 58 | // create job in folder1 59 | folder1.createProject(FreeStyleProject.class, "elements"); 60 | 61 | // run backup full 62 | new HudsonBackup(thinBackupPlugin, ThinBackupPeriodicWork.BackupType.FULL, dateBefore, r.jenkins).backup(); 63 | 64 | // create another job 65 | folder1.createProject(FreeStyleProject.class, "elements2"); 66 | 67 | // run backup diff 68 | new HudsonBackup(thinBackupPlugin, ThinBackupPeriodicWork.BackupType.DIFF, date, r.jenkins).backup(); 69 | 70 | final String[] listedBackupDirs = backupDir.list(); 71 | assertEquals(2, listedBackupDirs.length); 72 | 73 | Path backupFolderName = Utils.getFormattedDirectory( 74 | backupDir, ThinBackupPeriodicWork.BackupType.FULL, dateBefore) 75 | .toPath(); 76 | final List listedBackupFiles = List.of(backupFolderName.toFile().list()); 77 | 78 | // check basic files are there 79 | assertThat( 80 | listedBackupFiles, 81 | hasItems( 82 | "installedPlugins.xml", 83 | "config.xml", 84 | "org.jvnet.hudson.plugins.thinbackup.ThinBackupPluginImpl.xml", 85 | "hudson.model.UpdateCenter.xml", 86 | "jobs")); 87 | 88 | // create BackupSet 89 | final File[] listedFiles = backupDir.listFiles(); 90 | assertEquals(2, listedFiles.length); 91 | final BackupSet backupSetFromDirectory = new BackupSet(listedFiles[0]); 92 | 93 | assertTrue(backupSetFromDirectory.isValid()); 94 | assertFalse(backupSetFromDirectory.isInZipFile()); 95 | assertEquals(backupSetFromDirectory, backupSetFromDirectory.unzip()); 96 | 97 | // zip files 98 | final File zippedBackupSet = backupSetFromDirectory.zipTo(backupDir); 99 | assertNotNull(zippedBackupSet); 100 | 101 | File[] files = backupDir.listFiles(); 102 | assertEquals(3, files.length); 103 | 104 | final ZipFile zipFile = new ZipFile(zippedBackupSet); 105 | final Enumeration zipEntries = zipFile.entries(); 106 | int entryCount = 0; 107 | while (zipEntries.hasMoreElements()) { 108 | zipEntries.nextElement(); 109 | ++entryCount; 110 | } 111 | assertThat(entryCount, lessThan(30)); 112 | zipFile.close(); 113 | 114 | final BackupSet backupSetFromZip = new BackupSet(zippedBackupSet); 115 | assertTrue(backupSetFromZip.isValid()); 116 | assertTrue(backupSetFromZip.isInZipFile()); 117 | 118 | assertEquals(backupSetFromDirectory.getFullBackupName(), backupSetFromZip.getFullBackupName()); 119 | assertEquals( 120 | backupSetFromDirectory.getDiffBackupsNames().size(), 121 | backupSetFromZip.getDiffBackupsNames().size()); 122 | 123 | final BackupSet backupSetFromUnzippedZip = backupSetFromZip.unzip(); 124 | assertTrue(backupSetFromUnzippedZip.isValid()); 125 | assertFalse(backupSetFromUnzippedZip.isInZipFile()); 126 | assertNotNull(backupSetFromUnzippedZip.getFullBackup()); 127 | assertTrue(backupSetFromUnzippedZip.getFullBackup().exists()); 128 | assertNotNull(backupSetFromUnzippedZip.getDiffBackups()); 129 | for (final File diffBackup : backupSetFromUnzippedZip.getDiffBackups()) { 130 | assertTrue(diffBackup.exists()); 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/test/java/org/jvnet/hudson/plugins/thinbackup/backup/TestPluginList.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 Matthias Steinkogler, Thomas Fürer 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see http://www.gnu.org/licenses. 16 | */ 17 | package org.jvnet.hudson.plugins.thinbackup.backup; 18 | 19 | import static org.junit.jupiter.api.Assertions.assertEquals; 20 | 21 | import java.io.File; 22 | import java.io.IOException; 23 | import org.apache.commons.io.FileUtils; 24 | import org.junit.jupiter.api.AfterEach; 25 | import org.junit.jupiter.api.BeforeEach; 26 | import org.junit.jupiter.api.Test; 27 | 28 | class TestPluginList { 29 | 30 | private PluginList pluginList; 31 | private File pluginsXml; 32 | private File pluginsXml2; 33 | 34 | @BeforeEach 35 | void setup() throws IOException { 36 | pluginsXml = File.createTempFile("pluginList", ".xml"); 37 | pluginList = new PluginList(pluginsXml); 38 | pluginList.add("default", "0.1"); 39 | } 40 | 41 | @AfterEach 42 | void teardown() { 43 | FileUtils.deleteQuietly(pluginsXml); 44 | FileUtils.deleteQuietly(pluginsXml2); 45 | } 46 | 47 | @Test 48 | void testAdd() { 49 | assertEquals(1, pluginList.getPlugins().size()); 50 | pluginList.add("plugin", "0.1"); 51 | assertEquals(2, pluginList.getPlugins().size()); 52 | } 53 | 54 | @Test 55 | void testCompareToEqualPluginList() throws IOException { 56 | pluginsXml2 = File.createTempFile("pluginList2", ".xml"); 57 | final PluginList pluginList2 = new PluginList(pluginsXml2); 58 | pluginList2.add("default", "0.1"); 59 | 60 | assertEquals(0, pluginList.compareTo(pluginList2)); 61 | assertEquals(0, pluginList2.compareTo(pluginList)); 62 | } 63 | 64 | @Test 65 | void testCompareToNotEqualPluginList() throws IOException { 66 | pluginsXml2 = File.createTempFile("pluginList2", ".xml"); 67 | final PluginList pluginList2 = new PluginList(pluginsXml2); 68 | 69 | assertEquals(-1, pluginList.compareTo(pluginList2)); 70 | assertEquals(-1, pluginList2.compareTo(pluginList)); 71 | 72 | pluginList2.add("default", "0.2"); 73 | 74 | assertEquals(-1, pluginList.compareTo(pluginList2)); 75 | assertEquals(-1, pluginList2.compareTo(pluginList)); 76 | } 77 | 78 | @Test 79 | void testCompareToDifferentSecondPluginList() throws IOException { 80 | pluginsXml2 = File.createTempFile("pluginList2", ".xml"); 81 | final PluginList pluginList2 = new PluginList(pluginsXml2); 82 | 83 | pluginList2.add("hudson", "0.2"); 84 | 85 | assertEquals(-1, pluginList.compareTo(pluginList2)); 86 | assertEquals(-1, pluginList2.compareTo(pluginList)); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/test/java/org/jvnet/hudson/plugins/thinbackup/restore/TestRestore.java: -------------------------------------------------------------------------------- 1 | package org.jvnet.hudson.plugins.thinbackup.restore; 2 | 3 | import static org.hamcrest.MatcherAssert.assertThat; 4 | import static org.hamcrest.Matchers.containsString; 5 | import static org.junit.jupiter.api.Assertions.*; 6 | import static org.jvnet.hudson.plugins.thinbackup.TestHelper.newFile; 7 | import static org.jvnet.hudson.plugins.thinbackup.TestHelper.newFolder; 8 | import static org.jvnet.hudson.plugins.thinbackup.backup.HudsonBackup.NEXT_BUILD_NUMBER_FILE_NAME; 9 | import static org.jvnet.hudson.test.LogRecorder.recorded; 10 | 11 | import hudson.model.FreeStyleProject; 12 | import java.io.File; 13 | import java.nio.file.Files; 14 | import java.nio.file.StandardOpenOption; 15 | import java.text.SimpleDateFormat; 16 | import java.util.Date; 17 | import java.util.List; 18 | import java.util.logging.Level; 19 | import org.junit.jupiter.api.Test; 20 | import org.junit.jupiter.api.io.TempDir; 21 | import org.jvnet.hudson.plugins.thinbackup.ThinBackupPeriodicWork; 22 | import org.jvnet.hudson.plugins.thinbackup.ThinBackupPluginImpl; 23 | import org.jvnet.hudson.plugins.thinbackup.backup.HudsonBackup; 24 | import org.jvnet.hudson.plugins.thinbackup.utils.Utils; 25 | import org.jvnet.hudson.test.JenkinsRule; 26 | import org.jvnet.hudson.test.LogRecorder; 27 | import org.jvnet.hudson.test.junit.jupiter.WithJenkins; 28 | 29 | @WithJenkins 30 | class TestRestore { 31 | 32 | @TempDir 33 | private File tmpFolder; 34 | 35 | @Test 36 | void testRestoreFromFolder(JenkinsRule r) throws Exception { 37 | // create to test afterward 38 | File backupDir = newFolder(tmpFolder, "junit"); 39 | 40 | final ThinBackupPluginImpl thinBackupPlugin = ThinBackupPluginImpl.get(); 41 | thinBackupPlugin.setBackupPath(backupDir.getAbsolutePath()); 42 | thinBackupPlugin.setBackupBuildResults(true); 43 | thinBackupPlugin.setBackupNextBuildNumber(true); 44 | final File rootDir = r.jenkins.getRootDir(); 45 | final Date date = new Date(); 46 | 47 | // create 2 jobs 48 | final FreeStyleProject test = r.createFreeStyleProject("test"); 49 | final FreeStyleProject test2 = r.createFreeStyleProject("test2"); 50 | 51 | // add build info to one 52 | final File test2rootDir = test2.getRootDir(); 53 | final File nextNumber = new File(test2rootDir, NEXT_BUILD_NUMBER_FILE_NAME); 54 | Files.write(nextNumber.toPath(), "42".getBytes(), StandardOpenOption.CREATE_NEW); 55 | final File buildsDir = newFolder(test2rootDir, "builds", "1"); 56 | // add some log files 57 | newFile(buildsDir, "log"); // should be copied 58 | newFile(buildsDir, "log.txt"); // should be copied 59 | newFile(buildsDir, "logfile.log"); // should NOT be copied 60 | newFile(buildsDir, "logfile.xlog"); // should be copied 61 | 62 | // run backup 63 | new HudsonBackup(thinBackupPlugin, ThinBackupPeriodicWork.BackupType.FULL, date, r.jenkins).backup(); 64 | 65 | // delete jobs 66 | test.delete(); 67 | test2.delete(); 68 | 69 | // check that files are gone 70 | final File jobs = new File(rootDir, "jobs"); 71 | String[] jobList = jobs.list(); 72 | assertEquals(0, jobList.length); 73 | 74 | // now do the restore without build number 75 | HudsonRestore hudsonRestore = new HudsonRestore(rootDir, backupDir.getAbsolutePath(), date, false, false); 76 | hudsonRestore.restore(); 77 | 78 | // verify jobs are back 79 | jobList = jobs.list(); 80 | assertEquals(2, jobList.length); 81 | 82 | // verify NextBuildNumber is missing 83 | assertFalse(new File(test2rootDir, "nextBuildNumber").exists()); 84 | 85 | // restore from backup INCLUDING build number 86 | hudsonRestore = new HudsonRestore(rootDir, backupDir.getAbsolutePath(), date, true, false); 87 | hudsonRestore.restore(); 88 | 89 | assertTrue(new File(test2rootDir, "nextBuildNumber").exists()); 90 | } 91 | 92 | @Test 93 | void testRestoreFromZip(JenkinsRule r) throws Exception { 94 | // create to test afterward 95 | File backupDir = newFolder(tmpFolder, "junit"); 96 | 97 | final ThinBackupPluginImpl thinBackupPlugin = ThinBackupPluginImpl.get(); 98 | thinBackupPlugin.setBackupPath(backupDir.getAbsolutePath()); 99 | thinBackupPlugin.setBackupBuildResults(true); 100 | thinBackupPlugin.setBackupNextBuildNumber(true); 101 | final File rootDir = r.jenkins.getRootDir(); 102 | Date date = new Date(); 103 | 104 | // create 2 jobs 105 | final FreeStyleProject test = r.createFreeStyleProject("test"); 106 | final FreeStyleProject test2 = r.createFreeStyleProject("test2"); 107 | 108 | // add build info to one 109 | final File test2rootDir = test2.getRootDir(); 110 | final File nextNumber = new File(test2rootDir, NEXT_BUILD_NUMBER_FILE_NAME); 111 | Files.write(nextNumber.toPath(), "42".getBytes(), StandardOpenOption.CREATE_NEW); 112 | final File buildsDir = newFolder(test2rootDir, "builds", "1"); 113 | // add some log files 114 | newFile(buildsDir, "log"); // should be copied 115 | newFile(buildsDir, "log.txt"); // should be copied 116 | newFile(buildsDir, "logfile.log"); // should NOT be copied 117 | newFile(buildsDir, "logfile.xlog"); // should be copied 118 | 119 | // run backup 120 | new HudsonBackup(thinBackupPlugin, ThinBackupPeriodicWork.BackupType.FULL, date, r.jenkins).backup(); 121 | 122 | List backupsAsDates = Utils.getBackupsAsDates(backupDir); 123 | assertEquals(1, backupsAsDates.size()); 124 | 125 | // create zips with older backups 126 | Utils.moveOldBackupsToZipFile(backupDir, null); 127 | 128 | // check that backupset is present 129 | File[] files = backupDir.listFiles(); 130 | assertEquals(1, files.length); 131 | 132 | // delete jobs 133 | test.delete(); 134 | test2.delete(); 135 | 136 | // check that files are gone 137 | final File jobs = new File(rootDir, "jobs"); 138 | String[] jobList = jobs.list(); 139 | assertEquals(0, jobList.length); 140 | 141 | // check that backup is available 142 | backupsAsDates = Utils.getBackupsAsDates(backupDir); 143 | assertEquals(1, backupsAsDates.size()); 144 | 145 | final Date restoreFromDate = new SimpleDateFormat(Utils.DISPLAY_DATE_FORMAT).parse(backupsAsDates.get(0)); 146 | 147 | // now do the restore without build number 148 | HudsonRestore hudsonRestore = 149 | new HudsonRestore(rootDir, backupDir.getAbsolutePath(), restoreFromDate, false, false); 150 | hudsonRestore.restore(); 151 | 152 | // verify jobs are back 153 | jobList = jobs.list(); 154 | assertEquals(2, jobList.length); 155 | 156 | // verify NextBuildNumber is missing 157 | assertFalse(new File(test2rootDir, "nextBuildNumber").exists()); 158 | 159 | // restore from backup INCLUDING build number 160 | hudsonRestore = new HudsonRestore(rootDir, backupDir.getAbsolutePath(), restoreFromDate, true, false); 161 | hudsonRestore.restore(); 162 | 163 | assertTrue(new File(test2rootDir, "nextBuildNumber").exists()); 164 | } 165 | 166 | @Test 167 | void testLogsForRestoringWithoutBackupPath(JenkinsRule r) { 168 | try (LogRecorder l = new LogRecorder().capture(3).record("hudson.plugins.thinbackup", Level.SEVERE)) { 169 | final HudsonRestore hudsonRestore = new HudsonRestore(null, null, null, false, false); 170 | hudsonRestore.restore(); 171 | assertThat( 172 | l, recorded(Level.SEVERE, containsString("Backup path not specified for restoration. Aborting."))); 173 | } 174 | } 175 | 176 | @Test 177 | void testLogsForRestoringWithoutRestoreFromDate(JenkinsRule r) { 178 | try (LogRecorder l = new LogRecorder().capture(3).record("hudson.plugins.thinbackup", Level.SEVERE)) { 179 | final HudsonRestore hudsonRestore = new HudsonRestore(null, "/var/backup", null, false, false); 180 | hudsonRestore.restore(); 181 | assertThat( 182 | l, 183 | recorded(Level.SEVERE, containsString("Backup date to restore from was not specified. Aborting."))); 184 | } 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/test/java/org/jvnet/hudson/plugins/thinbackup/utils/TestUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 Matthias Steinkogler, Thomas Fürer 3 | * 4 | * This program is free software: you can redistribute it and/or modify 5 | * it under the terms of the GNU General Public License as published by 6 | * the Free Software Foundation, either version 3 of the License, or 7 | * (at your option) any later version. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program. If not, see http://www.gnu.org/licenses. 16 | */ 17 | package org.jvnet.hudson.plugins.thinbackup.utils; 18 | 19 | import static org.junit.jupiter.api.Assertions.*; 20 | 21 | import java.io.File; 22 | import java.text.ParseException; 23 | import java.util.Calendar; 24 | import java.util.Date; 25 | import java.util.HashMap; 26 | import java.util.List; 27 | import java.util.Map; 28 | import org.junit.jupiter.api.Test; 29 | import org.jvnet.hudson.plugins.thinbackup.ThinBackupPeriodicWork.BackupType; 30 | import org.jvnet.hudson.plugins.thinbackup.backup.BackupDirStructureSetup; 31 | import org.jvnet.hudson.plugins.thinbackup.backup.BackupSet; 32 | 33 | class TestUtils extends BackupDirStructureSetup { 34 | 35 | @Test 36 | void testConvertToDirectoryNameDateFormat() throws ParseException { 37 | final String displayDate = "2011-02-13 10:48"; 38 | final String fileDate = Utils.convertToDirectoryNameDateFormat(displayDate); 39 | assertEquals("2011-02-13_10-48", fileDate); 40 | } 41 | 42 | @Test 43 | void testBadFormatConvertToDirectoryNameDateFormat() { 44 | assertThrows(ParseException.class, () -> Utils.convertToDirectoryNameDateFormat("2011-02-13-10:48")); 45 | } 46 | 47 | @Test 48 | void testWrongFormatConvertToDirectoryNameDateFormat() { 49 | assertThrows(ParseException.class, () -> Utils.convertToDirectoryNameDateFormat("FULL-2011-02-13_10-48")); 50 | } 51 | 52 | @Test 53 | void testEmptyDateConvertToDirectoryNameDateFormat() { 54 | assertThrows(ParseException.class, () -> Utils.convertToDirectoryNameDateFormat("")); 55 | } 56 | 57 | @Test 58 | void testGetDateFromValidBackupDir() { 59 | final Calendar cal = Calendar.getInstance(); 60 | cal.clear(); 61 | cal.set(2011, Calendar.FEBRUARY, 13, 10, 48); 62 | final Date expected = cal.getTime(); 63 | 64 | String displayDate = "FULL-2011-02-13_10-48"; 65 | Date tmp = Utils.getDateFromBackupDirectoryName(displayDate); 66 | assertNotNull(tmp); 67 | assertEquals(expected, tmp); 68 | 69 | displayDate = "DIFF-2011-02-13_10-48"; 70 | tmp = Utils.getDateFromBackupDirectoryName(displayDate); 71 | assertNotNull(tmp); 72 | assertEquals(expected, tmp); 73 | } 74 | 75 | @Test 76 | void testGetDateFromInvalidBackupDir() { 77 | final String displayDate = "DWDWD-2011-02-13_10-48"; 78 | final Date tmp = Utils.getDateFromBackupDirectoryName(displayDate); 79 | assertNull(tmp); 80 | } 81 | 82 | @Test 83 | void testGetBackupTypeDirectories() { 84 | final List fullBackupDirs = Utils.getBackupTypeDirectories(backupDir, BackupType.FULL); 85 | assertEquals(3, fullBackupDirs.size()); 86 | 87 | final List diffBackupDirs = Utils.getBackupTypeDirectories(backupDir, BackupType.DIFF); 88 | assertEquals(7, diffBackupDirs.size()); 89 | } 90 | 91 | @Test 92 | void testGetReferencedFullBackup() { 93 | File fullBackup = Utils.getReferencedFullBackup(diff11); 94 | assertEquals(full1, fullBackup); 95 | 96 | fullBackup = Utils.getReferencedFullBackup(diff12); 97 | assertEquals(full1, fullBackup); 98 | 99 | fullBackup = Utils.getReferencedFullBackup(diff13); 100 | assertEquals(full1, fullBackup); 101 | 102 | fullBackup = Utils.getReferencedFullBackup(diff14); 103 | assertEquals(full1, fullBackup); 104 | 105 | fullBackup = Utils.getReferencedFullBackup(full1); 106 | assertEquals(full1, fullBackup); 107 | 108 | fullBackup = Utils.getReferencedFullBackup(diff41); 109 | assertNull(fullBackup); 110 | } 111 | 112 | @Test 113 | void testGetReferencingDiffBackups() { 114 | List diffBackups = Utils.getReferencingDiffBackups(full1); 115 | assertEquals(4, diffBackups.size()); 116 | assertTrue(diffBackups.contains(diff11)); 117 | assertTrue(diffBackups.contains(diff12)); 118 | assertTrue(diffBackups.contains(diff13)); 119 | assertTrue(diffBackups.contains(diff14)); 120 | 121 | diffBackups = Utils.getReferencingDiffBackups(diff41); 122 | assertEquals(0, diffBackups.size()); 123 | } 124 | 125 | @Test 126 | void testGetBackups() { 127 | final List backups = Utils.getBackupsAsDates(backupDir.getAbsoluteFile()); 128 | assertEquals(9, backups.size()); 129 | } 130 | 131 | @Test 132 | void testGetValidBackupSets() { 133 | final List validBackupSets = Utils.getValidBackupSetsFromDirectories(backupDir); 134 | assertEquals(3, validBackupSets.size()); 135 | } 136 | 137 | @Test 138 | void testExpandEnvironmentVariables() { 139 | final Map map = new HashMap<>(); 140 | map.put("TEST_VAR", "REPLACEMENT"); 141 | String path = "${TEST_VAR}"; 142 | assertEquals("REPLACEMENT", Utils.internalExpandEnvironmentVariables(path, map)); 143 | path = "1${TEST_VAR}2"; 144 | assertEquals("1REPLACEMENT2", Utils.internalExpandEnvironmentVariables(path, map)); 145 | path = "1${TEST_VAR2"; 146 | assertEquals("1${TEST_VAR2", Utils.internalExpandEnvironmentVariables(path, map)); 147 | path = "1${TEST_VAR}2${3"; 148 | assertEquals("1REPLACEMENT2${3", Utils.internalExpandEnvironmentVariables(path, map)); 149 | path = "1${TEST_VAR}2${TEST_VAR}3"; 150 | assertEquals("1REPLACEMENT2REPLACEMENT3", Utils.internalExpandEnvironmentVariables(path, map)); 151 | 152 | assertThrows( 153 | EnvironmentVariableNotDefinedException.class, 154 | () -> Utils.internalExpandEnvironmentVariables("1${NO_VALUE_DEFINED}", map)); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/test/resources/configuration-as-code.yml: -------------------------------------------------------------------------------- 1 | unclassified: 2 | thinBackup: 3 | backupAdditionalFiles: false 4 | backupAdditionalFilesRegex: "^.*\\.(txt)$" 5 | backupBuildArchive: false 6 | backupBuildResults: true 7 | backupBuildsToKeepOnly: false 8 | backupConfigHistory: false 9 | backupNextBuildNumber: false 10 | backupPath: "c:\\temp\\thin-backup" 11 | backupPluginArchives: false 12 | backupUserContents: false 13 | cleanupDiff: false 14 | diffBackupSchedule: "0 12 * * 1-5" 15 | excludedFilesRegex: "^.*\\.(log)$" 16 | failFast: true 17 | forceQuietModeTimeout: 120 18 | fullBackupSchedule: "0 12 * * 1" 19 | moveOldBackupsToZipFile: false 20 | nrMaxStoredFull: -1 21 | waitForIdle: true 22 | -------------------------------------------------------------------------------- /src/test/resources/org/jvnet/hudson/plugins/thinbackup/TestJenkinsWithOldConfig/thinBackup.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | H 12 * * 1-5 4 | 5 | c:\temp\thin-backup 6 | 10 7 | 8 | true 9 | 1 10 | false 11 | true 12 | true 13 | false 14 | false 15 | false 16 | false 17 | false 18 | 19 | true 20 | false 21 | 22 | --------------------------------------------------------------------------------