diff --git a/prospero-common/src/main/java/org/wildfly/prospero/actions/FeaturesAddAction.java b/prospero-common/src/main/java/org/wildfly/prospero/actions/FeaturesAddAction.java index fc6b49c8f..925e1b175 100644 --- a/prospero-common/src/main/java/org/wildfly/prospero/actions/FeaturesAddAction.java +++ b/prospero-common/src/main/java/org/wildfly/prospero/actions/FeaturesAddAction.java @@ -17,6 +17,8 @@ package org.wildfly.prospero.actions; +import static org.wildfly.prospero.licenses.LicenseManager.LICENSES_FOLDER; + import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.jboss.galleon.ProvisioningDescriptionException; @@ -43,6 +45,7 @@ import org.wildfly.prospero.galleon.GalleonEnvironment; import org.wildfly.prospero.licenses.License; import org.wildfly.prospero.licenses.LicenseManager; +import org.wildfly.prospero.metadata.ProsperoMetadataUtils; import org.wildfly.prospero.model.FeaturePackTemplateManager; import org.wildfly.prospero.model.FeaturePackTemplate; import org.wildfly.prospero.model.ProsperoConfig; @@ -89,13 +92,13 @@ public class FeaturesAddAction { public FeaturesAddAction(MavenOptions mavenOptions, Path installDir, List repositories, Console console) throws MetadataException, ProvisioningException { this(mavenOptions, installDir, repositories, console, new DefaultCandidateActionsFactory(installDir), - new FeaturePackTemplateManager()); + new FeaturePackTemplateManager(), new LicenseManager()); } // used for testing FeaturesAddAction(MavenOptions mavenOptions, Path installDir, List repositories, Console console, CandidateActionsFactory candidateActionsFactory, - FeaturePackTemplateManager featurePackTemplateManager) + FeaturePackTemplateManager featurePackTemplateManager, LicenseManager licenseManager) throws MetadataException, ProvisioningException { this.installDir = InstallFolderUtils.toRealPath(installDir); @@ -110,7 +113,7 @@ public FeaturesAddAction(MavenOptions mavenOptions, Path installDir, List licenses, Path targetServer) throws I Files.createDirectory(licenseFolder); } final Path licenseAcceptFile = licenseFolder.resolve(LICENSE_AGREEMENT_FILENAME); - final Properties licenseApproveProperties = new Properties(); + final SortedProperties licenseApproveProperties = new SortedProperties(); if (Files.exists(licenseAcceptFile)) { try (FileInputStream inStream = new FileInputStream(licenseAcceptFile.toFile())) { licenseApproveProperties.load(inStream); @@ -163,17 +162,14 @@ public void recordAgreements(List licenses, Path targetServer) throws I } public void copyIfExists(Path sourceServer, Path targetServer) throws IOException { - final Path sourceLicenses = sourceServer.resolve(ProsperoMetadataUtils.METADATA_DIR).resolve(LICENSES_FOLDER).resolve(LICENSE_AGREEMENT_FILENAME); - final Path targetLicenses = targetServer.resolve(ProsperoMetadataUtils.METADATA_DIR).resolve(LICENSES_FOLDER).resolve(LICENSE_AGREEMENT_FILENAME); - if (!Files.exists(sourceLicenses)) { - return; - } - if (!Files.exists(targetLicenses.getParent())) { - Files.createDirectory(targetLicenses.getParent()); + final Path licencesDir = Path.of(ProsperoMetadataUtils.METADATA_DIR, LICENSES_FOLDER); + if (!Files.exists(sourceServer.resolve(licencesDir))) { + return; } - Files.copy(sourceLicenses, targetLicenses, StandardCopyOption.REPLACE_EXISTING); + // copy all licenses files + FileUtils.copyDirectory(sourceServer.resolve(licencesDir).toFile(), targetServer.resolve(licencesDir).toFile()); } private static URL getLicensesFile() { diff --git a/prospero-common/src/main/java/org/wildfly/prospero/licenses/SortedProperties.java b/prospero-common/src/main/java/org/wildfly/prospero/licenses/SortedProperties.java new file mode 100644 index 000000000..010cdba18 --- /dev/null +++ b/prospero-common/src/main/java/org/wildfly/prospero/licenses/SortedProperties.java @@ -0,0 +1,47 @@ +/* + * Copyright 2024 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wildfly.prospero.licenses; + +import java.util.Collections; +import java.util.Comparator; +import java.util.Enumeration; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.TreeSet; + +class SortedProperties extends Properties { + @Override + public Enumeration keys() { + return Collections.enumeration(keySet()); + } + + @Override + public Set keySet() { + final Set keyList = new TreeSet<>(Comparator.comparing(Object::toString)); + keyList.addAll(super.keySet()); + return keyList; + } + + @Override + public Set> entrySet() { + final TreeSet> treeSet = new TreeSet<>((o1, o2) -> o1.getKey().toString().compareTo(o2.toString())); + treeSet.addAll(super.entrySet()); + return treeSet; + } +} diff --git a/prospero-common/src/test/java/org/wildfly/prospero/actions/FeaturesAddActionTest.java b/prospero-common/src/test/java/org/wildfly/prospero/actions/FeaturesAddActionTest.java index 115ee0f79..ad74da9ec 100644 --- a/prospero-common/src/test/java/org/wildfly/prospero/actions/FeaturesAddActionTest.java +++ b/prospero-common/src/test/java/org/wildfly/prospero/actions/FeaturesAddActionTest.java @@ -35,6 +35,7 @@ import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import org.wildfly.channel.Channel; import org.wildfly.channel.ChannelManifest; @@ -46,6 +47,8 @@ import org.wildfly.prospero.api.exceptions.MetadataException; import org.wildfly.prospero.api.exceptions.OperationException; import org.wildfly.prospero.galleon.FeaturePackLocationParser; +import org.wildfly.prospero.licenses.License; +import org.wildfly.prospero.licenses.LicenseManager; import org.wildfly.prospero.model.FeaturePackTemplateManager; import org.wildfly.prospero.model.FeaturePackTemplate; import org.wildfly.prospero.model.ProsperoConfig; @@ -53,6 +56,7 @@ import org.wildfly.prospero.utils.MavenUtils; import org.wildfly.prospero.wfchannel.MavenSessionManager; +import java.io.FileInputStream; import java.io.IOException; import java.net.URL; import java.nio.charset.StandardCharsets; @@ -60,6 +64,7 @@ import java.nio.file.Path; import java.util.Collections; import java.util.List; +import java.util.Properties; import java.util.Set; import java.util.stream.Collectors; @@ -101,6 +106,8 @@ public class FeaturesAddActionTest { private FeaturesAddAction.CandidateActionsFactory candidateActionsFactory; @Mock private FeaturePackTemplateManager featurePackTemplateManager; + @Mock + private LicenseManager licenseManager; @Before public void setUp() throws Exception { @@ -984,6 +991,53 @@ public void findAndApplyTemplateWithAdditionalPackages() throws Exception { .contains("additional.package"); } + @Test + public void testAddLicenses() throws Exception { + // install base feature pack + final FeaturePackCreator creator = FeaturePackCreator.getInstance().addArtifactResolver(repo); + creator.newFeaturePack(FeaturePackLocation.fromString("org.test:base-pack:1.0.0:zip").getFPID()) + .getCreator() + .newFeaturePack(FeaturePackLocation.fromString("org.test:added-pack:1.0.0:zip").getFPID()) + .addDependency(FeaturePackLocation.fromString("org.test:base-pack:1.0.0")) + ; + deployFeaturePacks(creator); + // install + installFeaturePack(installDir, "org.test:base-pack:1.0.0:zip"); + + final Path licenses = installDir.resolve(METADATA_DIR).resolve(LicenseManager.LICENSES_FOLDER); + Files.createDirectories(licenses); + + // create licenses - base-license is accepted in the original server and added-license is accepted during + // addition of a feature pack + final License addedLicense = new License("added-license", "org.test:added-pack", "Added License", "Added license text"); + final License baseLicense = new License("base-license", "org.test:base-pack", "Base license", "Base license text"); + when(licenseManager.getLicenses(any())).thenReturn(List.of(addedLicense)); + // use real method to actually write out the content of licenses - this way we can check that both old and new licenses are saved + Mockito.doCallRealMethod().when(licenseManager).recordAgreements(any(), any()); + licenseManager.recordAgreements(List.of(baseLicense), installDir); + + getFeaturesAddAction().addFeaturePack("org.test:added-pack", NO_DEFAULT_CONFIGS, candidatePath); + + verify(prepareCandidateAction).buildCandidate(any(), any(), eq(ApplyCandidateAction.Type.FEATURE_ADD), + any()); + + + // verify candidate has licenses from both base server and newly added ones + final Properties properties = new Properties(); + final Path licensesDir = candidatePath.resolve(METADATA_DIR).resolve(LicenseManager.LICENSES_FOLDER); + properties.load(new FileInputStream(licensesDir.resolve("license_accepted.properties").toFile())); + assertThat(properties.stringPropertyNames()) + .contains("license.0.name", "license.1.name"); + assertThat(properties.getProperty("license.0.name")) + .isEqualTo("base-license"); + assertThat(properties.getProperty("license.1.name")) + .isEqualTo("added-license"); + assertThat(licensesDir.resolve("base-license.txt")) + .hasContent("Base license text"); + assertThat(licensesDir.resolve("added-license.txt")) + .hasContent("Added license text"); + } + private static GalleonFeaturePackConfig getFeaturePackConfig(GalleonProvisioningConfig config, String fpl) { return config.getFeaturePackDeps().stream().filter(f -> f.getLocation().toString().equals(fpl)).findFirst().get(); } @@ -1012,7 +1066,7 @@ private FeaturesAddAction getFeaturesAddAction() throws MetadataException, Provi final FeaturesAddAction featuresAddAction = new FeaturesAddAction(MavenOptions.OFFLINE_NO_CACHE, installDir, List.of(new Repository("test", repositoryUrl.toExternalForm())), null, - candidateActionsFactory, featurePackTemplateManager); + candidateActionsFactory, featurePackTemplateManager, licenseManager); return featuresAddAction; } diff --git a/prospero-common/src/test/java/org/wildfly/prospero/licenses/SortedPropertiesTest.java b/prospero-common/src/test/java/org/wildfly/prospero/licenses/SortedPropertiesTest.java new file mode 100644 index 000000000..43c062716 --- /dev/null +++ b/prospero-common/src/test/java/org/wildfly/prospero/licenses/SortedPropertiesTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2024 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.wildfly.prospero.licenses; + +import static org.assertj.core.api.Assertions.*; + +import java.io.FileOutputStream; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class SortedPropertiesTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Test + public void writeSortedProperties() throws Exception { + final SortedProperties properties = new SortedProperties(); + + properties.setProperty("bbb", "bar"); + properties.setProperty("aaa", "foo"); + properties.setProperty("ddd", "212"); + properties.setProperty("ccc", "123"); + + final Path propertiesFile = temp.newFile().toPath(); + try (FileOutputStream fos = new FileOutputStream(propertiesFile.toFile())) { + properties.store(fos, null); + } + + // first line is going to be a comment with timestamp, need to add it to the check + final String firstLine = Files.readAllLines(propertiesFile).get(0); + assertThat(firstLine) + .startsWith("#"); + assertThat(propertiesFile) + .hasContent(firstLine + "\naaa=foo\nbbb=bar\nccc=123\nddd=212"); + + } +} \ No newline at end of file