Commit 9096b412 authored by Marco Descher's avatar Marco Descher 🏔

[15349] Update p2 service

parent 36f0d9e5
Pipeline #10452 passed with stages
in 2 minutes and 20 seconds
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="activate" immediate="true" name="info.elexis.server.core.p2.console.ConsoleCommandProvider">
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.3.0" activate="activate" immediate="true" name="info.elexis.server.core.p2.console.ConsoleCommandProvider">
<service>
<provide interface="org.eclipse.osgi.framework.console.CommandProvider"/>
</service>
<reference cardinality="1..1" field="provisioner" interface="info.elexis.server.core.p2.IProvisioner" name="provisioner"/>
<implementation class="info.elexis.server.core.p2.console.ConsoleCommandProvider"/>
</scr:component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="info.elexis.server.core.p2.internal.HTTPService">
<service>
<provide interface="info.elexis.server.core.p2.internal.HTTPService"/>
</service>
<implementation class="info.elexis.server.core.p2.internal.HTTPService"/>
</scr:component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="activate" immediate="true" name="info.elexis.server.core.p2.internal.Provisioner">
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="activate" deactivate="deactivate" immediate="true" name="info.elexis.server.core.p2.internal.Provisioner">
<service>
<provide interface="info.elexis.server.core.p2.IProvisioner"/>
</service>
<reference bind="setAgentProvider" cardinality="1..1" interface="org.eclipse.equinox.p2.core.IProvisioningAgentProvider" name="AgentProvider" policy="static" unbind="unsetAgentProvider"/>
<implementation class="info.elexis.server.core.p2.internal.Provisioner"/>
</scr:component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.3.0" immediate="true" name="info.elexis.server.core.p2.jaxrs.P2Resource">
<service>
<provide interface="info.elexis.server.core.p2.jaxrs.P2Resource"/>
</service>
<reference cardinality="1..1" field="provisioner" interface="info.elexis.server.core.p2.IProvisioner" name="provisioner"/>
<implementation class="info.elexis.server.core.p2.jaxrs.P2Resource"/>
</scr:component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" name="info.elexis.server.core.p2.jaxrs.UpdateResource">
<service>
<provide interface="info.elexis.server.core.p2.jaxrs.UpdateResource"/>
</service>
<implementation class="info.elexis.server.core.p2.jaxrs.UpdateResource"/>
</scr:component>
\ No newline at end of file
package info.elexis.server.core.p2;
public class Constants {
public static final String PLUGIN_ID = "info.elexis.server.core.p2";
}
package info.elexis.server.core.p2;
import java.net.URI;
import java.util.Collection;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.operations.Update;
import info.elexis.server.core.p2.internal.RepoInfo;
public interface IProvisioner {
public IStatus install(IInstallableUnit unit, IProgressMonitor monitor);
/**
* Install a feature by its id
*
* @param feature
* @param monitor
* @return
*/
public IStatus install(String feature, IProgressMonitor monitor);
/**
* Perform updates on a list of installable units generated via
* {@link #getAvailableUpdates()}
*
* @param updates
* @param monitor
* @return
*/
public IStatus update(Collection<Update> updates, IProgressMonitor monitor);
public IStatus uninstall(String feature, IProgressMonitor monitor);
/**
* @return all features installed in the current local profile
*/
public Collection<IInstallableUnit> getInstalledFeatures();
/**
* @return all features available on the update site
*/
public Collection<IInstallableUnit> getAllAvailableFeatures();
/**
*
* @param id the feature id
* @return a single {@link IInstallableUnit} by its id out of the pool of
* {@link #getAllAvailableFeatures()}, <code>null</code> if none found
*/
public IInstallableUnit getFeatureInAllAvailableFeatures(String id);
/**
*
* @return the current list of updatable features
*/
public Collection<Update> getAvailableUpdates();
/**
* @param iu
* @return the locally installed feature for a given {@link IInstallableUnit} or
* <code>null</code> if not available
*/
public IInstallableUnit getInstalledFeature(IInstallableUnit iu);
public void addRepository(URI location, String username, String password);
/**
*
* @param location
* @return <code>true</code> if repository was removed or it was already not
* part of the list
*/
public boolean removeRepository(URI location);
/**
*
* @return information about the current repository state
*/
public RepoInfo getRepositoryInfo();
}
package info.elexis.server.core.p2.console;
import java.util.Arrays;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Collection;
import java.util.Iterator;
import java.util.Optional;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.equinox.p2.metadata.IInstallableUnit;
import org.eclipse.equinox.p2.operations.Update;
import org.eclipse.osgi.framework.console.CommandInterpreter;
import org.eclipse.osgi.framework.console.CommandProvider;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import ch.elexis.core.console.AbstractConsoleCommandProvider;
import ch.elexis.core.console.CmdAdvisor;
import info.elexis.server.core.p2.internal.HTTPServiceHelper;
import info.elexis.server.core.p2.internal.ProvisioningHelper;
import ch.elexis.core.console.ConsoleProgressMonitor;
import ch.elexis.core.status.StatusUtil;
import info.elexis.server.core.p2.IProvisioner;
@Component(service = CommandProvider.class, immediate = true)
public class ConsoleCommandProvider extends AbstractConsoleCommandProvider {
@Reference
private IProvisioner provisioner;
@Activate
public void activate() {
register(this.getClass());
......@@ -27,29 +39,32 @@ public class ConsoleCommandProvider extends AbstractConsoleCommandProvider {
executeCommand("p2", ci);
}
@CmdAdvisor(description = "get possible updates")
public void __p2_update_check() {
Update[] possibleUpdates = ProvisioningHelper.getPossibleUpdates();
Arrays.asList(possibleUpdates).stream().forEach(c -> ci.println(c));
@CmdAdvisor(description = "list possible updates")
public void __p2_update_list() {
Collection<Update> availableUpdates = provisioner.getAvailableUpdates();
availableUpdates.stream().forEach(c -> ci.println(c));
}
@CmdAdvisor(description = "update all installed features")
public String __p2_update_execute() {
return ProvisioningHelper.updateAllFeatures().getMessage();
Collection<Update> availableUpdates = provisioner.getAvailableUpdates();
IStatus update = provisioner.update(availableUpdates, new ConsoleProgressMonitor(ci));
return StatusUtil.printStatus(update);
}
@CmdAdvisor(description = "list the installed features")
public String __p2_features_listLocal() {
return ProvisioningHelper
.getAllInstalledFeatures().stream().map(i -> i.getId() + " (" + i.getVersion() + ") "
+ i.getProperty("git-repo-url") + " " + i.getProperty("git-rev"))
.reduce((u, t) -> u + "\n" + t).get();
public String __p2_features_list() {
Collection<IInstallableUnit> installedFeatures = provisioner.getInstalledFeatures();
Optional<String> reduce = installedFeatures.stream().map(i -> i.getId() + " (" + i.getVersion() + ") "
+ i.getProperty("git-repo-url") + " " + i.getProperty("git-rev")).reduce((u, t) -> u + "\n" + t);
return reduce.orElse("fail");
}
@CmdAdvisor(description = "install a feature")
public String __p2_features_install(Iterator<String> args) {
if (args.hasNext()) {
return ProvisioningHelper.unInstallFeature(args.next(), true);
IStatus status = provisioner.install(args.next(), new ConsoleProgressMonitor(ci));
return StatusUtil.printStatus(status);
}
return missingArgument("featureName");
}
......@@ -57,38 +72,59 @@ public class ConsoleCommandProvider extends AbstractConsoleCommandProvider {
@CmdAdvisor(description = "uninstall a feature")
public String __p2_features_uninstall(Iterator<String> args) {
if (args.hasNext()) {
return ProvisioningHelper.unInstallFeature(args.next(), false);
IStatus status = provisioner.uninstall(args.next(), new ConsoleProgressMonitor(ci));
return StatusUtil.printStatus(status);
}
return missingArgument("featureName");
}
@CmdAdvisor(description = "list configured repositories")
public String __p2_repo_list() {
return HTTPServiceHelper.getRepoInfo(null).toString();
return provisioner.getRepositoryInfo().toString();
}
@CmdAdvisor(description = "add a repository")
public String __p2_repo_add(Iterator<String> args) {
if (args.hasNext()) {
final String url = args.next();
String user = null;
final String _url = args.next();
URI uri;
try {
URL url = new URL(_url);
uri = url.toURI();
} catch (MalformedURLException | URISyntaxException e) {
return e.getMessage();
}
String username = null;
String password = null;
if (args.hasNext()) {
user = args.next();
username = args.next();
}
if (args.hasNext()) {
password = args.next();
}
return HTTPServiceHelper.doRepositoryAdd(url, user, password).getStatusInfo().toString();
provisioner.addRepository(uri, username, password);
return ok();
}
return missingArgument("url [user] [password]");
}
@CmdAdvisor(description = "remove a repository")
public String __p2_repo_remove(Iterator<String> args) {
if (args.hasNext()) {
final String url = args.next();
return HTTPServiceHelper.doRepositoryRemove(url).getStatusInfo().toString();
final String _url = args.next();
URI uri;
try {
URL url = new URL(_url);
uri = url.toURI();
} catch (MalformedURLException | URISyntaxException e) {
return e.getMessage();
}
boolean success = provisioner.removeRepository(uri);
if (success) {
return ok();
}
}
return missingArgument("url");
return "fail";
}
}
package info.elexis.server.core.p2.internal;
import java.util.Optional;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import org.osgi.service.component.annotations.Component;
@Component(service = HTTPService.class, immediate = true)
@Path("/p2")
public class HTTPService {
@Context
private HttpServletRequest req;
@GET
@Path("/repositories")
public Response listRepositories(@QueryParam("filter") String filter) {
return HTTPServiceHelper.doRepositoryList(filter);
}
// @GET
// @Path("/repositories/add")
// public Response addRepository(@QueryParam("location") String location) {
// Optional<String> locStr = Optional.ofNullable(location);
// if (locStr.isPresent()) {
// return HTTPServiceHelper.doRepositoryAdd(locStr.get());
// }
// return Response.status(Response.Status.BAD_REQUEST).build();
// }
@GET
@Path("/repositories/remove")
public Response handleRemove(@QueryParam("location") String location) {
Optional<String> locStr = Optional.ofNullable(location);
if (locStr.isPresent()) {
return HTTPServiceHelper.doRepositoryRemove(locStr.get());
}
return Response.status(Response.Status.BAD_REQUEST).build();
}
@GET
@Path("/update")
public Response performUpdate() {
return HTTPServiceHelper.doUpdateAllFeatures();
}
}
package info.elexis.server.core.p2.internal;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import javax.ws.rs.core.Response;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.equinox.p2.core.ProvisionException;
import org.eclipse.equinox.p2.repository.IRepositoryManager;
import org.eclipse.equinox.p2.repository.artifact.IArtifactRepository;
import org.eclipse.equinox.p2.repository.artifact.IArtifactRepositoryManager;
import org.eclipse.equinox.p2.repository.metadata.IMetadataRepository;
import org.eclipse.equinox.p2.repository.metadata.IMetadataRepositoryManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HTTPServiceHelper {
private static Logger log = LoggerFactory.getLogger(HTTPServiceHelper.class);
public static RepoInfo getRepoInfo(String filterStr) {
int filter = IRepositoryManager.REPOSITORIES_ALL;
try {
if (filterStr != null) {
filter = Integer.parseInt(filterStr);
}
} catch (NumberFormatException localException) {
log.warn("NaN " + filterStr, localException);
throw new IllegalArgumentException("NaN " + filterStr);
}
RepoInfo info = new RepoInfo();
IMetadataRepositoryManager metadataRepoMgr = Provisioner.getInstance().getMetadataRepositoryManager();
IMetadataRepository repo;
for (URI repoLoc : metadataRepoMgr.getKnownRepositories(filter)) {
String repoName;
try {
repo = metadataRepoMgr.loadRepository(repoLoc, new TimeoutProgressMonitor(2000));
repoName = repo.getName();
} catch (OperationCanceledException localOperationCanceledException) {
log.warn("timeout", localOperationCanceledException);
repoName = "timeout";
} catch (ProvisionException localProvisionException) {
log.warn("FAIL", localProvisionException);
repoName = "FAILED: " + localProvisionException.getLocalizedMessage();
}
info.addMetadataRepoElement(repoName, repoLoc);
}
IArtifactRepositoryManager articaftRepoMgr = Provisioner.getInstance().getArtifactRepositoryManager();
IArtifactRepository artRepo;
for (URI repoLoc : articaftRepoMgr.getKnownRepositories(filter)) {
String repoName;
try {
artRepo = articaftRepoMgr.loadRepository(repoLoc, new TimeoutProgressMonitor(2000));
repoName = artRepo.getName();
} catch (OperationCanceledException localOperationCanceledException) {
log.warn("timeout", localOperationCanceledException);
repoName = "timeout";
} catch (ProvisionException localProvisionException) {
log.warn("FAIL", localProvisionException);
repoName = "FAILED: " + localProvisionException.getLocalizedMessage();
}
info.addArtifactRepoElement(repoName, repoLoc);
}
return info;
}
public static Response doRepositoryList(String filterStr) {
try {
RepoInfo info = getRepoInfo(filterStr);
return Response.ok(info).build();
} catch (IllegalArgumentException e) {
return Response.status(Response.Status.BAD_REQUEST).build();
}
}
public static Response doRepositoryAdd(String locStr, String username, String password) {
URI location = null;
try {
locStr = URLDecoder.decode(locStr, "ASCII");
location = new URI(locStr);
} catch (URISyntaxException | UnsupportedEncodingException e) {
log.warn("Exception parsing URI " + locStr, e);
return Response.status(Response.Status.BAD_REQUEST).build();
}
if (location.isAbsolute()) {
ProvisioningHelper.addRepository(location, username, password);
return Response.ok().build();
}
log.warn("Tried to add non absolute location: {}", location);
return Response.status(Response.Status.BAD_REQUEST).build();
}
public static Response doRepositoryRemove(String locStr) {
URI location = null;
try {
locStr = URLDecoder.decode(locStr, "ASCII");
location = new URI(locStr);
} catch (URISyntaxException | UnsupportedEncodingException e) {
log.warn("Exception parsing URI " + locStr, e);
return Response.status(Response.Status.BAD_REQUEST).build();
}
boolean result = ProvisioningHelper.removeRepository(location);
if (result) {
return Response.ok().build();
}
return Response.serverError().build();
}
public static Response doUpdateAllFeatures() {
IStatus updateStatus = ProvisioningHelper.updateAllFeatures();
return createResponseFromStatus("doUpdateAllFeatures", updateStatus);
}
private static Response createResponseFromStatus(String op, IStatus stat) {
if (stat.isOK()) {
return Response.ok().build();
}
log.warn("Error performing operation [{}] : Code {} / {}", op, stat.getCode(), stat.getMessage());
return Response.serverError().build();
}
}
//package info.elexis.server.core.p2.internal;
//
//import java.io.UnsupportedEncodingException;
//import java.net.URI;
//import java.net.URISyntaxException;
//import java.net.URLDecoder;
//
//import javax.ws.rs.core.Response;
//
//import org.eclipse.core.runtime.IStatus;
//import org.eclipse.core.runtime.OperationCanceledException;
//import org.eclipse.equinox.p2.core.ProvisionException;
//import org.eclipse.equinox.p2.repository.IRepositoryManager;
//import org.eclipse.equinox.p2.repository.artifact.IArtifactRepository;
//import org.eclipse.equinox.p2.repository.artifact.IArtifactRepositoryManager;
//import org.eclipse.equinox.p2.repository.metadata.IMetadataRepository;
//import org.eclipse.equinox.p2.repository.metadata.IMetadataRepositoryManager;
//import org.slf4j.Logger;
//import org.slf4j.LoggerFactory;
//
//public class HTTPServiceHelper {
//
// private static Logger log = LoggerFactory.getLogger(HTTPServiceHelper.class);
//
// public static RepoInfo getRepoInfo(String filterStr) {
//
// }
//
// public static Response doRepositoryList(String filterStr) {
// try {
// RepoInfo info = getRepoInfo(filterStr);
// return Response.ok(info).build();
// } catch (IllegalArgumentException e) {
// return Response.status(Response.Status.BAD_REQUEST).build();
// }
// }
//
// public static Response doRepositoryAdd(String locStr, String username, String password) {
// URI location = null;
// try {
// locStr = URLDecoder.decode(locStr, "ASCII");
// location = new URI(locStr);
// } catch (URISyntaxException | UnsupportedEncodingException e) {
// log.warn("Exception parsing URI " + locStr, e);
// return Response.status(Response.Status.BAD_REQUEST).build();
// }
//
// if (location.isAbsolute()) {
// ProvisioningHelper.addRepository(location, username, password);
// return Response.ok().build();
// }
//
// log.warn("Tried to add non absolute location: {}", location);
// return Response.status(Response.Status.BAD_REQUEST).build();
// }
//
// public static Response doRepositoryRemove(String locStr) {
// URI location = null;
// try {
// locStr = URLDecoder.decode(locStr, "ASCII");
// location = new URI(locStr);
// } catch (URISyntaxException | UnsupportedEncodingException e) {
// log.warn("Exception parsing URI " + locStr, e);
// return Response.status(Response.Status.BAD_REQUEST).build();
// }
//
// boolean result = ProvisioningHelper.removeRepository(location);
// if (result) {
// return Response.ok().build();
// }
//
// return Response.serverError().build();
// }
//
// public static Response doUpdateAllFeatures() {
// IStatus updateStatus = ProvisioningHelper.updateAllFeatures();
// return createResponseFromStatus("doUpdateAllFeatures", updateStatus);
// }
//
// private static Response createResponseFromStatus(String op, IStatus stat) {
// if (stat.isOK()) {
// return Response.ok().build();
// }
// log.warn("Error performing operation [{}] : Code {} / {}", op, stat.getCode(), stat.getMessage());
// return Response.serverError().build();
// }
//
//}
......@@ -10,9 +10,9 @@ import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class RepoInfo {
@XmlElement
public List<RepoElement> metadataRepos = new ArrayList<RepoInfo.RepoElement>();
public List<RepoElement> metadataRepos = new ArrayList<>();
@XmlElement
public List<RepoElement> artifactRepos = new ArrayList<RepoInfo.RepoElement>();
public List<RepoElement> artifactRepos = new ArrayList<>();
public void addMetadataRepoElement(String repoName, URI repoLoc) {
metadataRepos.add(new RepoElement(repoName, repoLoc));
......
......@@ -39,6 +39,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import info.elexis.server.core.common.util.CoreUtil;
import info.elexis.server.core.p2.Constants;
@SuppressWarnings("restriction")
public class SecureStoragePasswordProvider extends org.eclipse.equinox.security.storage.provider.PasswordProvider {
......@@ -48,8 +49,6 @@ public class SecureStoragePasswordProvider extends org.eclipse.equinox.security.
private static final String ENCODING = "UTF-8"; //$NON-NLS-1$
private static final int BYTE_ARRAY_SIZE = 1024;
public static final String PLUGIN_ID = "info.elexis.server.core.p2";
@Override
public PBEKeySpec getPassword(IPreferencesContainer container, int passwordType) {
if (CoreUtil.getHomeDirectory().toString() == null)
......@@ -115,7 +114,7 @@ public class SecureStoragePasswordProvider extends org.eclipse.equinox.security.
}
private SecretKeySpec getKeySpec() {
String ksPref = Platform.getPreferencesService().getString(PLUGIN_ID, IPreferenceConstants.CACHED_KEY, "", //$NON-NLS-1$
String ksPref = Platform.getPreferencesService().getString(Constants.PLUGIN_ID, IPreferenceConstants.CACHED_KEY, "", //$NON-NLS-1$
null);
byte[] key = null;
......@@ -140,7 +139,7 @@ public class SecureStoragePasswordProvider extends org.eclipse.equinox.security.
SecretKey skey = kgen.generateKey();
key = skey.getEncoded();
byte[] b64 = Base64.encode(skey.getEncoded());
IEclipsePreferences node = InstanceScope.INSTANCE.getNode(PLUGIN_ID);
IEclipsePreferences node = InstanceScope.INSTANCE.getNode(Constants.PLUGIN_ID);
node.put(IPreferenceConstants.CACHED_KEY, new String(b64));
node.flush();
} catch (NoSuchAlgorithmException e) {
......
package info.elexis.server.core.p2.jaxrs;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.equinox.p2.operations.Update;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import info.elexis.server.core.p2.internal.ProvisioningHelper;
import info.elexis.server.core.p2.IProvisioner;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@Api(tags = { "p2" })
@Path("p2")
@Component(service = UpdateResource.class, immediate = true)
@Component(service = P2Resource.class, immediate = true)
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public class UpdateResource {
public class P2Resource {
@Reference
private IProvisioner provisioner;
@GET
@Path("updates")
@ApiOperation(nickname = "checkUpdate", value = "check for available updates")
public Response checkUpdates() {
Update[] possibleUpdates = ProvisioningHelper.getPossibleUpdates();
List<String> resultList = Arrays.asList(possibleUpdates).stream().map(Update::toString)
.collect(Collectors.toList());
return Response.ok(resultList).build();
// Update[] possibleUpdates = ProvisioningHelper.getPossibleUpdates();
// List<String> resultList = Arrays.asList(possibleUpdates).stream().map(Update::toString)
// .collect(Collectors.toList());
return Response.ok(null).build();
}
@POST
@Path("updates")
@ApiOperation(nickname = "executeUpdate", value = "check for available updates")
public Response executeUpdates() {
IStatus updateAllFeatures = ProvisioningHelper.updateAllFeatures();
if (updateAllFeatures.isOK()) {
return Response.ok().