Skip to content

Commit

Permalink
feat(rest.packages.provider): add endpoint for Package Descriptor ret…
Browse files Browse the repository at this point in the history
…rieval [backport release-5.4.0] (#4968)

feat(rest.packages.provider): add endpoint for Package Descriptor retrieval (#4934)

* feat: add MarketplacePackageDescriptor.java

* feat: add barebone getMarketplacePackageDescriptor method implementation

WIP

* feat: add getMarketplacePackageDescriptor to DeploymentAgentService interface

* feat: add isEclipseMarketplaceUrl check

WIP

* style: fix variable name

* feat: add quick&dirty descriptor check endpoint

STILL WIP: we should at least have a dedicated request type instead of
recycling InstallRequest

* feat: return json object

* debug: add debugging log

* feat: actually perform check on input url

* feat: add proper Kura version retrival

* refactor: move instantiation

* refactor: use proper exceptions

* style: add copyright header

* feat: use QueryParam for passing the URL to be checked

* feat: provide equals and hashCode overrides

* fix: do not log user-controlled data

* fix: replace character class by the character itself

* test: add unit tests for MarketplacePackageDescriptor

* style: add copyright header

* style: refactor into gerkhin style

* refactor: move given/when/then methods in their sections

* refactor: use the builder for gods sake

* style: add forgotten about files

* test: add DeploymentRestServiceUnitTest for marketplace descriptor

* refactor: move URL checks outside DeploymentAgentService to allow for testing

* test: update unit tests

* refactor: move method into its section

* test: add integration tests

* fix: remove commented out code

* test: add some more tests

* test: add test case for failing getMarketplacePackageDescriptor call

* docs: add Javadocs for DeploymentRestService

* docs: add javadocs for DeploymentAgentService

* test: add DeploymentAgentTest:getMarketplacePackageDescriptor tests (#11)

* ci(debug): set envvar to run testcontainers with podman

* refactor: user raw Mockserver instead of going through Testcontainers

* refactor: remove unnecessary dependencies

* ci: roll-back changes

* test: use random free port

* test: add null URL test

* test: reset MockServer status after each test

* feat: GET -> PUT

* test: GET -> PUT

* refactor(web2): modify UI so that it uses the newly introduced method

* build: downgrade mockserver 5.15.0 -> 5.14.0

* build: I'm a dum dum

* docs: add "since" annotation for newly introduced method

* style: update copyright headers

* chore: deployment agent service version bump to 1.1.0

* feat: add method for overriding SslManagerService used to establish connection

* style: restore formatting

* style: update copyright headers

* style: update copyright header

* build: switched to mockserver v 5.15.0

* docs: remove newly added test deps from NOTICE file
  • Loading branch information
mattdibi authored Nov 13, 2023
1 parent 0d64223 commit 7f41aa0
Show file tree
Hide file tree
Showing 19 changed files with 1,508 additions and 206 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Bundle-Version: 1.4.0
Bundle-Vendor: Eclipse Kura
Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))"
Service-Component: OSGI-INF/*.xml
Export-Package: org.eclipse.kura.deployment.agent; version="1.0.0"
Export-Package: org.eclipse.kura.deployment.agent; version="1.1.0"
Import-Package: javax.net.ssl,
org.apache.commons.io;version="1.4.9999",
org.apache.commons.lang3;version="[3.0,4.0)",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2011, 2020 Eurotech and/or its affiliates and others
* Copyright (c) 2011, 2023 Eurotech and/or its affiliates and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
Expand All @@ -12,6 +12,8 @@
*******************************************************************************/
package org.eclipse.kura.deployment.agent;

import org.eclipse.kura.ssl.SslManagerService;

public interface DeploymentAgentService {

/**
Expand Down Expand Up @@ -99,6 +101,38 @@ public interface DeploymentAgentService {
*/
public boolean isInstallingDeploymentPackage(String url);

/**
* Provides the Eclipse Marketplace Package Descriptor information of the deployment package identified by URL
* passed as parameter.
*
* @since 1.4.0
*
* @param url
* The URL of the deployment package descriptor. Note: the url accepted as argument should be
* already validated and such that it allows for downloading the descriptor file.
* @return the {@link MarketplacePackageDescriptor} object
*/
public MarketplacePackageDescriptor getMarketplacePackageDescriptor(String url);

/**
* Provides the Eclipse Marketplace Package Descriptor information of the deployment package identified by URL
* passed as parameter.
*
* @since 1.4.0
*
* @param url
* The URL of the deployment package descriptor. Note: the url accepted as argument should be
* already validated and such that it allows for downloading the descriptor file.
* @param sslManagerService
* The {@link SslManagerService} to use for establishing the SSL connection and downloading the
* descriptor file.
*
* @return the {@link MarketplacePackageDescriptor} object
*/
public MarketplacePackageDescriptor getMarketplacePackageDescriptor(String url,
SslManagerService sslManagerService);


/**
* Asks if the uninstallation of a deployment package with the given symbolic name is pending.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*******************************************************************************
* Copyright (c) 2023 Eurotech and/or its affiliates and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Eurotech
*******************************************************************************/
package org.eclipse.kura.deployment.agent;

import java.io.Serializable;
import java.util.Objects;

public class MarketplacePackageDescriptor implements Serializable {

private String nodeId;
private String url;
private String dpUrl;
private String minKuraVersion;
private String maxKuraVersion;
private String currentKuraVersion;
private boolean isCompatible;

private MarketplacePackageDescriptor(MarketplacePackageDescriptorBuilder builder) {
this.nodeId = builder.nodeId;
this.url = builder.url;
this.dpUrl = builder.dpUrl;
this.minKuraVersion = builder.minKuraVersion;
this.maxKuraVersion = builder.maxKuraVersion;
this.currentKuraVersion = builder.currentKuraVersion;
this.isCompatible = builder.isCompatible;
}

public String getNodeId() {
return nodeId;
}

public String getUrl() {
return url;
}

public String getDpUrl() {
return dpUrl;
}

public String getMinKuraVersion() {
return minKuraVersion;
}

public String getMaxKuraVersion() {
return maxKuraVersion;
}

public String getCurrentKuraVersion() {
return currentKuraVersion;
}

public boolean isCompatible() {
return isCompatible;
}

public static MarketplacePackageDescriptorBuilder builder() {
return new MarketplacePackageDescriptorBuilder();
}

@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof MarketplacePackageDescriptor)) {
return false;
}
MarketplacePackageDescriptor other = (MarketplacePackageDescriptor) obj;
return Objects.equals(this.nodeId, other.nodeId) && Objects.equals(this.url, other.url)
&& Objects.equals(this.dpUrl, other.dpUrl) && Objects.equals(this.minKuraVersion, other.minKuraVersion)
&& Objects.equals(this.maxKuraVersion, other.maxKuraVersion)
&& Objects.equals(this.currentKuraVersion, other.currentKuraVersion)
&& Objects.equals(this.isCompatible, other.isCompatible);
}

@Override
public int hashCode() {
return Objects.hash(this.nodeId, this.url, this.dpUrl, this.minKuraVersion, this.maxKuraVersion,
this.currentKuraVersion, this.isCompatible);
}

// Builder
public static class MarketplacePackageDescriptorBuilder {

private String nodeId = "";
private String url = "";
private String dpUrl = "";
private String minKuraVersion = "";
private String maxKuraVersion = "";
private String currentKuraVersion = "";
private boolean isCompatible = false;

public MarketplacePackageDescriptorBuilder nodeId(String nodeId) {
this.nodeId = nodeId;
return this;
}

public MarketplacePackageDescriptorBuilder url(String url) {
this.url = url;
return this;
}

public MarketplacePackageDescriptorBuilder dpUrl(String dpUrl) {
this.dpUrl = dpUrl;
return this;
}

public MarketplacePackageDescriptorBuilder minKuraVersion(String minKuraVersion) {
this.minKuraVersion = minKuraVersion;
return this;
}

public MarketplacePackageDescriptorBuilder maxKuraVersion(String maxKuraVersion) {
this.maxKuraVersion = maxKuraVersion;
return this;
}

public MarketplacePackageDescriptorBuilder currentKuraVersion(String currentKuraVersion) {
this.currentKuraVersion = currentKuraVersion;
return this;
}

public MarketplacePackageDescriptorBuilder isCompatible(boolean isCompatible) {
this.isCompatible = isCompatible;
return this;
}

public MarketplacePackageDescriptor build() {
return new MarketplacePackageDescriptor(this);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2011, 2022 Eurotech and/or its affiliates and others
* Copyright (c) 2011, 2023 Eurotech and/or its affiliates and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
Expand Down Expand Up @@ -37,13 +37,17 @@
import java.util.concurrent.TimeUnit;

import javax.net.ssl.HttpsURLConnection;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.kura.KuraErrorCode;
import org.eclipse.kura.KuraRuntimeException;
import org.eclipse.kura.configuration.ConfigurableComponent;
import org.eclipse.kura.deployment.agent.DeploymentAgentService;
import org.eclipse.kura.deployment.agent.MarketplacePackageDescriptor;
import org.eclipse.kura.deployment.agent.MarketplacePackageDescriptor.MarketplacePackageDescriptorBuilder;
import org.eclipse.kura.ssl.SslManagerService;
import org.eclipse.kura.system.SystemService;
import org.osgi.framework.Version;
Expand All @@ -56,6 +60,10 @@
import org.osgi.service.event.EventProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
* @author cdealti
Expand Down Expand Up @@ -254,6 +262,133 @@ public boolean isUninstallingDeploymentPackage(String name) {
}
}

@Override
public MarketplacePackageDescriptor getMarketplacePackageDescriptor(String url) {
return getMarketplacePackageDescriptor(url, this.sslManagerService);
}

@Override
public MarketplacePackageDescriptor getMarketplacePackageDescriptor(String url,
SslManagerService sslManagerServiceOverride) {
// Note: the url accepted as argument should be already validated and belonging to the
// Eclipse Marketplace domain such that it allows for downloading the descriptor file.
HttpsURLConnection connection = null;
MarketplacePackageDescriptorBuilder descriptorBuilder = MarketplacePackageDescriptor.builder();

try {
connection = (HttpsURLConnection) new URL(url).openConnection();
connection.setSSLSocketFactory(sslManagerServiceOverride.getSSLSocketFactory());

connection.setRequestMethod("GET");
connection.connect();

DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
dbf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
dbf.setXIncludeAware(false);
dbf.setExpandEntityReferences(false);

DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(connection.getInputStream());

final Node updateUrl = getFirstNode(doc, "updateurl");
if (updateUrl == null) {
throw new IllegalStateException("Cannot find download URL in the deployment package descriptor");
}
descriptorBuilder.dpUrl(updateUrl.getTextContent());

final Node node = getFirstNode(doc, "node");
if (node != null) {
final NamedNodeMap nodeAttributes = node.getAttributes();
descriptorBuilder.nodeId(getAttributeValue(nodeAttributes, "id"));
descriptorBuilder.url(getAttributeValue(nodeAttributes, "url"));
}

Node versionCompatibility = getFirstNode(doc, "versioncompatibility");
String minKuraVersion = null;
String maxKuraVersion = null;
if (versionCompatibility != null) {
NodeList children = versionCompatibility.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node n = children.item(i);
String nodeName = n.getNodeName();
if ("from".equalsIgnoreCase(nodeName)) {
minKuraVersion = n.getTextContent();
} else if ("to".equalsIgnoreCase(nodeName)) {
maxKuraVersion = n.getTextContent();
}
}
}
descriptorBuilder.minKuraVersion(minKuraVersion);
descriptorBuilder.maxKuraVersion(maxKuraVersion);

String kuraPropertyCompatibilityVersion = getMarketplaceCompatibilityVersionString();
Version kuraVersion = getMarketplaceCompatibilityVersion(kuraPropertyCompatibilityVersion);
if (kuraVersion != null) {
kuraPropertyCompatibilityVersion = kuraVersion.toString();
}

descriptorBuilder.currentKuraVersion(kuraPropertyCompatibilityVersion);
boolean isCompatible = checkCompatibility(minKuraVersion, maxKuraVersion, kuraVersion);
descriptorBuilder.isCompatible(isCompatible);

} catch (Exception e) {
throw new IllegalStateException(
"Failed to get deployment package descriptor from Eclipse Marketplace. Caused by: ", e);
} finally {
if (connection != null) {
connection.disconnect();
}
}

return descriptorBuilder.build();
}

private Node getFirstNode(final Document doc, final String tagName) {
final NodeList elements = doc.getElementsByTagName(tagName);
if (elements.getLength() == 0) {
return null;
}
return elements.item(0);
}

private String getAttributeValue(NamedNodeMap attributes, String attribute) {
final Node node = attributes.getNamedItem(attribute);
if (node == null) {
return null;
}
return node.getNodeValue();
}

private Version getMarketplaceCompatibilityVersion(String marketplaceCompatibilityVersion) {
try {
return new Version(marketplaceCompatibilityVersion);
} catch (Exception e) {
return null;
}
}

private String getMarketplaceCompatibilityVersionString() {
return this.systemService.getKuraMarketplaceCompatibilityVersion();
}

private boolean checkCompatibility(String minKuraVersionString, String maxKuraVersionString,
Version currentProductVersion) {
try {
boolean haveMinKuraVersion = minKuraVersionString != null && !minKuraVersionString.isEmpty();
boolean haveMaxKuraVersion = maxKuraVersionString != null && !maxKuraVersionString.isEmpty();

if (haveMinKuraVersion && currentProductVersion.compareTo(new Version(minKuraVersionString)) < 0
|| haveMaxKuraVersion && currentProductVersion.compareTo(new Version(maxKuraVersionString)) > 0) {
throw new IllegalArgumentException("Unsupported marketplace compatibility version");
}

return haveMinKuraVersion || haveMaxKuraVersion;
} catch (Exception e) {
return false;
}
}

private void execInstall(String url) {
DeploymentPackage dp = null;
Exception ex = null;
Expand Down Expand Up @@ -478,4 +613,4 @@ private void writeDPAPropertiesFile(Properties deployedPackages) {
logger.error("Error writing package configuration file", e);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Import-Package: com.google.gson;version="2.7.0",
javax.ws.rs.ext;version="2.0.1",
org.apache.commons.io;version="[2.0,3.0)",
org.eclipse.kura;version="[1.3,2.0)",
org.eclipse.kura.deployment.agent;version="[1.0,2.0)",
org.eclipse.kura.deployment.agent;version="[1.1,2.0)",
org.glassfish.jersey.media.multipart;version="[2.0,3.0)",
org.osgi.framework;version="1.8.0",
org.osgi.service.component;version="1.3.0",
Expand Down
Loading

0 comments on commit 7f41aa0

Please sign in to comment.