/* * JBoss, Home of Professional Open Source. * Copyright 2013, Red Hat, Inc., and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.patching.runner; import static org.jboss.as.patching.IoUtils.safeClose; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.xml.stream.XMLStreamException; import org.jboss.as.patching.Constants; import org.jboss.as.patching.IoUtils; import org.jboss.as.patching.PatchInfo; import org.jboss.as.patching.PatchingException; import org.jboss.as.patching.VerbosePatchInfo; import org.jboss.as.patching.ZipUtils; import org.jboss.as.patching.installation.AddOn; import org.jboss.as.patching.installation.InstallationManager; import org.jboss.as.patching.installation.InstallationManagerImpl; import org.jboss.as.patching.installation.InstalledIdentity; import org.jboss.as.patching.installation.Layer; import org.jboss.as.patching.installation.PatchableTarget; import org.jboss.as.patching.logging.PatchLogger; import org.jboss.as.patching.metadata.BundledPatch; import org.jboss.as.patching.metadata.BundledPatch.BundledPatchEntry; import org.jboss.as.patching.metadata.Patch; import org.jboss.as.patching.metadata.PatchBundleXml; import org.jboss.as.patching.metadata.PatchMerger; import org.jboss.as.patching.metadata.PatchMetadataResolver; import org.jboss.as.patching.metadata.PatchXml; import org.jboss.as.patching.tool.ContentVerificationPolicy; import org.jboss.as.patching.tool.PatchTool; import org.jboss.as.patching.tool.PatchingHistory; import org.jboss.as.patching.tool.PatchingResult; /** * The default patch tool implementation. * * @author Emanuel Muckenhuber */ public class PatchToolImpl implements PatchTool { private final InstallationManager manager; private final InstallationManager.ModificationCompletionCallback callback; private final IdentityPatchRunner runner; public PatchToolImpl(final InstallationManager manager) { this.manager = manager; this.runner = new IdentityPatchRunner(manager.getInstalledImage()); this.callback = runner; } @Override public List<String> getPatchStreams() throws PatchingException { final List<InstalledIdentity> installedIdentities = manager.getInstalledIdentities(); if(installedIdentities.size() == 1) { return Collections.singletonList(installedIdentities.get(0).getIdentity().getName()); } final List<String> result = new ArrayList<String>(installedIdentities.size()); for(InstalledIdentity ii : installedIdentities) { result.add(ii.getIdentity().getName()); } return result; } @Override public PatchInfo getPatchInfo() throws PatchingException { return getPatchInfo(null); } @Override public PatchInfo getPatchInfo(String streamName) throws PatchingException { try { final InstalledIdentity installedIdentity = streamName == null ? manager.getDefaultIdentity() : manager.getInstalledIdentity(streamName, null); final PatchableTarget.TargetInfo info = installedIdentity.getIdentity().loadTargetInfo(); final VerbosePatchInfo.Builder infoBuilder = VerbosePatchInfo.builder() .setVersion(installedIdentity.getIdentity().getVersion()) .setCumulativePatchId(info.getCumulativePatchID()) .setPatchIds(info.getPatchIDs()); for(Layer layer : installedIdentity.getLayers()) { infoBuilder.addLayerInfo(layer.getName(), layer.loadTargetInfo()); } for(AddOn addon : installedIdentity.getAddOns()) { infoBuilder.addAddOnInfo(addon.getName(), addon.loadTargetInfo()); } return infoBuilder.build(); } catch (IOException e) { // why throw a rethrowException(e) ? throw new RuntimeException(e); } } @Override public PatchingHistory getPatchingHistory() throws PatchingException { return getPatchingHistory(null); } @Override public PatchingHistory getPatchingHistory(String streamName) throws PatchingException { final InstalledIdentity identity = streamName == null ? manager.getDefaultIdentity() : manager.getInstalledIdentity(streamName, null); return PatchingHistory.Factory.getHistory(identity); } @Override public PatchingResult applyPatch(final File file, final ContentVerificationPolicy contentPolicy) throws PatchingException { try { if (file.isDirectory()) { final File patchXml = new File(file, PatchXml.PATCH_XML); if (patchXml.exists()) { // Shortcut exploded patches return execute(file, contentPolicy); } } try (final InputStream is = new FileInputStream(file)) { return applyPatch(is, contentPolicy); } } catch (Exception e) { throw rethrowException(e); } } @Override public PatchingResult applyPatch(final URL url, final ContentVerificationPolicy contentPolicy) throws PatchingException { try { final InputStream is = url.openStream(); try { return applyPatch(is, contentPolicy); } finally { if(is != null) try { is.close(); } catch (IOException e) { PatchLogger.ROOT_LOGGER.debugf(e, "failed to close input stream"); } } } catch (IOException e) { throw new PatchingException(e); } } @Override public PatchingResult applyPatch(final InputStream is, final ContentVerificationPolicy contentPolicy) throws PatchingException { return applyPatch(null, is, contentPolicy); } private PatchingResult applyPatch(final File parentWorkDir, final InputStream is, final ContentVerificationPolicy contentPolicy) throws PatchingException { File workDir = null; try { // Create a working dir workDir = parentWorkDir == null ? IdentityPatchRunner.createTempDir() : IdentityPatchRunner.createTempDir(parentWorkDir); try { // Save the content Path cachedContent = workDir.toPath().resolve("content"); Files.copy(is, cachedContent); // Unpack to the work dir ZipUtils.unzip(cachedContent.toFile(), workDir); } catch (IOException e) { throw PatchLogger.ROOT_LOGGER.cannotCopyFilesToTempDir(workDir.getAbsolutePath(), e.getMessage(), e); // add info that temp dir is involved } // Execute return execute(workDir, contentPolicy); } catch (Exception e) { throw rethrowException(e); } finally { if (workDir != null && !IoUtils.recursiveDelete(workDir)) { PatchLogger.ROOT_LOGGER.cannotDeleteFile(workDir.getAbsolutePath()); } } } @Override public PatchingResult rollback(final String patchId, final ContentVerificationPolicy contentPolicy, final boolean rollbackTo, final boolean resetConfiguration) throws PatchingException { return rollback(null, patchId, contentPolicy, rollbackTo, resetConfiguration); } @Override public PatchingResult rollback(final String streamName, final String patchId, final ContentVerificationPolicy contentPolicy, final boolean rollbackTo, final boolean resetConfiguration) throws PatchingException { InstalledIdentity targetIdentity = null; if (streamName == null) { for (InstalledIdentity identity : manager.getInstalledIdentities()) { if (identity.getAllInstalledPatches().contains(patchId)) { if (targetIdentity != null) { throw new PatchingException(PatchLogger.ROOT_LOGGER.patchIdFoundInMoreThanOneStream(patchId, targetIdentity.getIdentity().getName(), identity.getIdentity().getName())); } targetIdentity = identity; } } if(targetIdentity == null) { throw PatchLogger.ROOT_LOGGER.patchNotFoundInHistory(patchId); } } else { targetIdentity = manager.getInstalledIdentity(streamName, null); } // Rollback the patch final InstallationManager.InstallationModification modification = targetIdentity.modifyInstallation(runner); try { return runner.rollbackPatch(patchId, contentPolicy, rollbackTo, resetConfiguration, modification); } catch (Exception e) { modification.cancel(); throw rethrowException(e); } } @Override public PatchingResult rollbackLast(final ContentVerificationPolicy contentPolicy, final boolean resetConfiguration) throws PatchingException { return rollbackLast(null, contentPolicy, resetConfiguration); } @Override public PatchingResult rollbackLast(final String streamName, final ContentVerificationPolicy contentPolicy, final boolean resetConfiguration) throws PatchingException { final InstalledIdentity targetIdentity = streamName == null ? manager.getDefaultIdentity() : manager.getInstalledIdentity(streamName, null); final InstallationManager.InstallationModification modification = targetIdentity.modifyInstallation(runner); try { return runner.rollbackLast(contentPolicy, resetConfiguration, modification); } catch (Exception e) { modification.cancel(); throw rethrowException(e); } } protected PatchingResult execute(final File workDir, final ContentVerificationPolicy contentPolicy) throws PatchingException, IOException, XMLStreamException { final File patchBundleXml = new File(workDir, PatchBundleXml.MULTI_PATCH_XML); if (patchBundleXml.exists()) { final InputStream patchIs = new FileInputStream(patchBundleXml); try { // Handle multi patch installs final BundledPatch bundledPatch = PatchBundleXml.parse(patchIs); return applyPatchBundle(workDir, bundledPatch, contentPolicy); } finally { safeClose(patchIs); } } else { // Parse the xml File patchXml = new File(workDir, PatchXml.PATCH_XML); PatchMetadataResolver patchResolver = parsePatchXml(patchXml); Patch patch = patchResolver.resolvePatch(null, null); final InstalledIdentity installedIdentity = manager.getInstalledIdentity(patch.getIdentity().getName(), null); final String currentVersion = installedIdentity.getIdentity().getVersion(); if(!Constants.UNKNOWN.equals(currentVersion) && !patch.getIdentity().getVersion().equals(currentVersion)) { patchXml = new File(workDir, currentVersion + PatchMerger.PATCH_XML_SUFFIX); if(!patchXml.exists()) { throw new PatchingException("The patch does not contain metadata for currently installed " + patch.getIdentity().getName() + " version " + currentVersion); } patchResolver = parsePatchXml(patchXml); patch = patchResolver.resolvePatch(null, null); } return apply(patchResolver, PatchContentProvider.DefaultContentProvider.create(workDir), contentPolicy); } } private PatchMetadataResolver parsePatchXml(final File patchXml) throws XMLStreamException, IOException { InputStream patchIS = null; try { patchIS = new FileInputStream(patchXml); return PatchXml.parse(patchIS); } finally { safeClose(patchIS); } } protected PatchingResult apply(final PatchMetadataResolver patchResolver, final PatchContentProvider contentProvider, final ContentVerificationPolicy contentPolicy) throws PatchingException { // Apply the patch final org.jboss.as.patching.metadata.Identity identity = patchResolver.resolvePatch(null, null).getIdentity(); final InstallationManager.InstallationModification modification = ((InstallationManagerImpl)manager). getInstalledIdentity(identity.getName(), identity.getVersion()).modifyInstallation(callback); try { return runner.applyPatch(patchResolver, contentProvider, contentPolicy, modification); } catch (Exception e) { modification.cancel(); throw rethrowException(e); } } protected PatchingResult applyPatchBundle(final File workDir, final BundledPatch bundledPatch, final ContentVerificationPolicy contentPolicy) throws PatchingException, IOException { final List<BundledPatchEntry> patches = bundledPatch.getPatches(); if(patches.isEmpty()) { throw new PatchingException(PatchLogger.ROOT_LOGGER.patchBundleIsEmpty()); } PatchingResult result = null; BundledPatchEntry lastCommittedEntry = null; final List<BundledPatch.BundledPatchEntry> results = new ArrayList<BundledPatch.BundledPatchEntry>(patches.size()); final List<InstalledIdentity> installedIdentities = manager.getInstalledIdentities(); for(BundledPatchEntry entry : patches) { // TODO this has to be checked against the specific one targeted by the patch boolean alreadyApplied = false; for (InstalledIdentity identity : installedIdentities) { if (identity.getAllInstalledPatches().contains(entry.getPatchId())) { alreadyApplied = true; break; } } if(alreadyApplied) { continue; } if(result != null) { result.commit(); results.add(0, lastCommittedEntry); } final File patch = new File(workDir, entry.getPatchPath()); final FileInputStream is = new FileInputStream(patch); PatchingResult currentResult = null; try { currentResult = applyPatch(workDir, is, contentPolicy); } catch (PatchingException e) { // Undo the changes included as part of this patch for (BundledPatch.BundledPatchEntry committed : results) { try { rollback(committed.getPatchId(), contentPolicy, false, false).commit(); } catch (PatchingException oe) { PatchLogger.ROOT_LOGGER.debugf(oe, "failed to rollback patch '%s'", committed.getPatchId()); } } throw e; } finally { safeClose(is); } if (currentResult != null) { result = currentResult; lastCommittedEntry = entry; } } if (result == null) { throw new PatchingException(); } return new WrappedMultiInstallPatch(result, contentPolicy, results); } static PatchingException rethrowException(final Exception e) { if (e instanceof PatchingException) { return (PatchingException) e; } else { return new PatchingException(e); } } class WrappedMultiInstallPatch implements PatchingResult { private final PatchingResult last; private final ContentVerificationPolicy policy; private final List<BundledPatch.BundledPatchEntry> committed; WrappedMultiInstallPatch(PatchingResult last, ContentVerificationPolicy policy, List<BundledPatch.BundledPatchEntry> committed) { this.last = last; this.policy = policy; this.committed = committed; } @Override public String getPatchId() { return last.getPatchId(); } @Override public PatchInfo getPatchInfo() { return last.getPatchInfo(); } @Override public void commit() { last.commit(); } @Override public void rollback() { last.rollback(); // Rollback the last for (final BundledPatch.BundledPatchEntry entry : committed) { try { PatchToolImpl.this.rollback(entry.getPatchId(), policy, false, false).commit(); } catch (Exception e) { PatchLogger.ROOT_LOGGER.debugf(e, "failed to rollback patch '%s'", entry.getPatchId()); } } } } }