New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[FIXED JENKINS-28843] Stored the unique value in the job configuration #1
Changes from 6 commits
2706618
18da636
19e7474
b5b5f08
7762111
1de8efd
1991180
76f95d1
944138b
07f5de0
cbee5b5
c38cbb0
4258879
9cb4df5
f4700e8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
/target/ | ||
/.classpath | ||
/.project | ||
/.settings |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,27 @@ | ||
package org.jenkinsci.plugins.uniqueid; | ||
|
||
import hudson.ExtensionPoint; | ||
|
||
import jenkins.model.Jenkins; | ||
|
||
import java.io.UnsupportedEncodingException; | ||
import java.util.UUID; | ||
|
||
import javax.annotation.Nullable; | ||
|
||
import org.apache.commons.codec.binary.Base64; | ||
|
||
/** | ||
* An abstraction to persistently store and retrieve unique id's | ||
* for various Jenkins model objects. | ||
* | ||
* These keys are guaranteed to be unique with a Jenkins | ||
* and immutable across the lifetime of the given object. | ||
* | ||
* Implementations should not store the ID inside any specific item configuration as it is | ||
* common for users top copy items either through the UI or manually and this will cause the | ||
* IDs to become non-unique. | ||
* | ||
* | ||
* @param <T> | ||
*/ | ||
|
@@ -26,17 +37,19 @@ public IdStore (Class<T> forType) { | |
* Creates an unique id for the given object. | ||
* Subsequent calls are idempotent. | ||
* | ||
* @param object | ||
* @param object the object to make the id for. | ||
* @throws Exception if we could not store the unique ID for some reason. | ||
*/ | ||
public abstract void make(T object); | ||
public abstract void make(T object) throws Exception; | ||
|
||
/** | ||
* Get the id for this given object. | ||
* @param object | ||
* @return the id or null if none assigned. | ||
* @return the id or {@code null} if none assigned. | ||
* @throws Exception if we could not retrieve the unique ID for some reason. | ||
*/ | ||
@Nullable | ||
public abstract String get(T object); | ||
public abstract String get(T object) throws Exception; | ||
|
||
public boolean supports(Class clazz) { | ||
return type.isAssignableFrom(clazz); | ||
|
@@ -62,8 +75,9 @@ public static <C> IdStore<C> forClass(Class<C> clazz) { | |
* Convenience method which makes the id for the given object. | ||
* | ||
* @throws java.lang.IllegalArgumentException if the type is not supported. | ||
* @throws Exception if we could not store the unique ID for some reason. | ||
*/ | ||
public static void makeId(Object object) { | ||
public static void makeId(Object object) throws IllegalArgumentException, Exception { | ||
IdStore store = forClass(object.getClass()); | ||
if (store == null) { | ||
throw new IllegalArgumentException("Unsupported type: " + object.getClass().getName()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems more like an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the previous API was IllegalArgumeException - so I do not want to change all callers. |
||
|
@@ -76,8 +90,9 @@ public static void makeId(Object object) { | |
* Convenience method which retrieves the id for the given object. | ||
* | ||
* @throws java.lang.IllegalArgumentException if the type is not supported. | ||
* @throws Exception if we could not store the unique ID for some reason. | ||
*/ | ||
public static String getId(Object object) { | ||
public static String getId(Object object) throws IllegalArgumentException, Exception { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think a fallback to a legacy store is required here (as I've understood initial test suites) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No - there should be no fallback at all. |
||
IdStore store = forClass(object.getClass()); | ||
if (store == null) { | ||
throw new IllegalArgumentException("Unsupported type: " + object.getClass().getName()); | ||
|
@@ -86,4 +101,20 @@ public static String getId(Object object) { | |
} | ||
} | ||
|
||
/** | ||
* Generates a new unique ID. | ||
* Subclasses do not need to use this to create unique IDs and are free to create IDs by other methods. | ||
* @return a string that should be unique against all jenkins instances. | ||
*/ | ||
protected static String generateUniqueID() { | ||
try { | ||
return Base64.encodeBase64String(UUID.randomUUID().toString().getBytes("UTF-8")).substring(0, 30); | ||
} catch (UnsupportedEncodingException e) { | ||
// impossible condition | ||
Error err = new InternalError("The JLS mandates UTF-8 yet it is not available on this JVM. Your JVM is broken."); | ||
err.initCause(e); | ||
throw err; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Couldn't this just be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not in the ancient JDK that this compiles against :-( |
||
} | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
package org.jenkinsci.plugins.uniqueid.impl; | ||
|
||
import hudson.Extension; | ||
import hudson.init.InitMilestone; | ||
import hudson.init.Initializer; | ||
import hudson.model.Item; | ||
import hudson.model.PersistenceRoot; | ||
import hudson.model.Job; | ||
import hudson.model.Run; | ||
import hudson.util.RunList; | ||
|
||
import jenkins.model.Jenkins; | ||
|
||
import java.io.File; | ||
import java.io.IOException; | ||
import java.util.List; | ||
import java.util.logging.Level; | ||
import java.util.logging.Logger; | ||
|
||
import javax.annotation.Nonnull; | ||
|
||
import org.jenkinsci.plugins.uniqueid.implv2.PersistenceRootIdStore; | ||
import org.kohsuke.accmod.Restricted; | ||
import org.kohsuke.accmod.restrictions.NoExternalUse; | ||
|
||
/** | ||
* Converts legacy UniqueIDs that are stored inside a Folder/Job/Run configuration to UniqueIDs that are stored alongside the Folder/Job/Run. | ||
* | ||
*/ | ||
@Restricted(NoExternalUse.class) | ||
@Extension | ||
public class IdStoreMigratorV1ToV2 { | ||
|
||
private static Logger LOGGER = Logger.getLogger(IdStoreMigratorV1ToV2.class.getName()); | ||
|
||
/** | ||
* Migrates any IDs stored in Folder/Job/Run configuration | ||
* @throws IOException | ||
*/ | ||
|
||
@Initializer(after=InitMilestone.JOB_LOADED, before=InitMilestone.COMPLETED, fatal=true) | ||
public static void migrateIdStore() throws IOException { | ||
Jenkins jenkins = Jenkins.getInstance(); | ||
if (jenkins == null) { | ||
throw new IllegalStateException("Jenkins is null, so it is impossible to migrate the IDs"); | ||
} | ||
File marker = new File(jenkins.getRootDir(), "unique-id-migration.txt"); | ||
if (marker.exists()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. next time... |
||
LOGGER.log(Level.INFO, "Migration of IDStore already perfomed, so skipping migration."); | ||
return; | ||
} | ||
LOGGER.log(Level.INFO, "Starting migration of IDs"); | ||
|
||
performMigration(jenkins); | ||
|
||
LOGGER.log(Level.INFO, "Finished migration of IDs"); | ||
if (!marker.createNewFile()) { | ||
throw new IOException("Failed to record the completion of the IDStore Migration. " + | ||
"This will cause performance issues on subsequent startup. " + | ||
"Please create an empty file at '" + marker.getCanonicalPath() + "'"); | ||
} | ||
} | ||
|
||
@SuppressWarnings("unchecked") | ||
static void performMigration(@Nonnull Jenkins jenkins) { | ||
List<Item> allItems = jenkins.getAllItems(); | ||
|
||
for (Item item : allItems) { | ||
// can only be Folder or Job here (not a run) - and these both implement PersistenceRoot | ||
if (item instanceof PersistenceRoot) { | ||
migrate((PersistenceRoot) item); | ||
} | ||
else { | ||
LOGGER.log(Level.WARNING, "Expected item of type Folder or Job which implement PersistenceRoot, but got a {0} so can not migrate the IdStore for this item", | ||
item.getClass().getName()); | ||
} | ||
|
||
if (item instanceof Job) { | ||
// need to migrate the RunIDs if they exist. | ||
Job<? extends Job, ? extends Run> job = (Job<? extends Job, ? extends Run>) item; | ||
RunList<? extends Run> builds = job.getBuilds(); | ||
for (Run build : builds) { | ||
migrate(build); | ||
} | ||
} | ||
} | ||
} | ||
|
||
private static void migrate(PersistenceRoot pr) { | ||
LOGGER.log(Level.FINE, "migrating {0}" , pr.toString()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. remove There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done in followup #3 |
||
try { | ||
String id = LegacyIdStore.getId(pr); | ||
if (id != null) { | ||
PersistenceRootIdStore.create(pr, id); | ||
LegacyIdStore.removeId(pr); | ||
} | ||
} catch (IOException ex) { | ||
// need to rethrow (but add some context first) otherwise the migration will continue to run | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure what behavior is preferable. Probably it's better to continue the migration. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If we continue that the user can end up with a broken analytics system... and mutilple Items that contain non-unique ID |
||
// and it will not have migrated everything :-( | ||
throw new IDStoreMigrationException("Failure whilst migrating " + pr.toString(), ex); | ||
} | ||
} | ||
|
||
/** | ||
* Exception to indicate a failure to migrate the IDStore. | ||
*/ | ||
private static class IDStoreMigrationException extends RuntimeException { | ||
|
||
public IDStoreMigrationException(String message, Throwable cause) { | ||
super(message,cause); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It causes a binary compatibility issue, but I doubt anybody uses the plugin's API actively. Let's bump the major version digit BTW
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes - but the alternative is to make this return a
boolean
and have callers check.Neither are good in my opinion - I could change this to be
IOException
(but that is specific to our implementation )