Merge pull request #1109 from scm-manager/feature/os_specific_homes

OS Specific Homes
This commit is contained in:
René Pfeuffer
2020-04-27 09:17:53 +02:00
committed by GitHub
11 changed files with 522 additions and 138 deletions

View File

@@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed
- Removed the `requires` attribute on the `@Extension` annotation and instead create a new `@Requires` annotation ([#1097](https://github.com/scm-manager/scm-manager/pull/1097))
- Use os specific locations for scm home directory ([#1109](https://github.com/scm-manager/scm-manager/pull/1109))
- Use Library/Logs/SCM-Manager on OSX for logging ([#1109](https://github.com/scm-manager/scm-manager/pull/1109))
### Fixed
- Protocol URI for git commands under windows ([#1108](https://github.com/scm-manager/scm-manager/pull/1108))

View File

@@ -83,6 +83,8 @@ repositories over http.
### SCM Manager 2
- [Getting started](v2/getting-started.md)
- [Base directory](v2/basedirectory.md)
- [Logging](v2/logging.md)
- [Configuration for Intellij IDEA](v2/intellij-idea-configuration.md)
- [SCM v2 Test Cases](v2/test-cases.md)
- [Table of decisions made during development](v2/decision-table.md)

45
docs/v2/basedirectory.md Normal file
View File

@@ -0,0 +1,45 @@
# Base Directory
The SCM-Manager base directory aka. home directory,
contains all data which is created by SCM-Manager such as repositories and configurations.
The location of the base directory depends on your operating system and type of installation.
| Type of Installation | Base directory |
|----------------------|----------------|
| Docker | /var/lib/scm |
| RPM | /var/lib/scm |
| DEB | /var/lib/scm |
| Unix | ~/.scm |
| Mac OS X | ~/Library/Application Support/SCM-Manager |
| Windows | %APPDATA%\SCM-Manager |
## Change base directory location
The location of the base directory can be changed by using one of the following ways.
The preferences are the following: Properties file over system property over environment variable.
### Environment variable
By setting the environment variable **SCM_HOME** e.g.:
```bash
export SCM_HOME=/home/scm
/opt/scm-server/bin/scm-server
```
For rpm and deb installations the variable can be changed via the file `/etc/default/scm-server`.
## System property
The path can be changed by setting the system property **scm.home** e.g.:
```bash
-Dscm.home=/home/scm
```
## Properties file
If SCM-Manager finds a file called `scm.properties` on the class path it reads the property `scm.home` e.g.:
```properties
scm.home=/home/scm
```

32
docs/v2/logging.md Normal file
View File

@@ -0,0 +1,32 @@
# Logging
SCM-Manager logs information which can be useful, if the system does not behave as expected.
The logging behavior depends on your operating system and installation.
| Type of Installation | Logging |
|----------------------|---------|
| Docker | stdout |
| RPM | /var/log/scm |
| DEB | /var/log/scm |
| Unix | $BASEDIR/logs |
| Mac OS X | ~/Library/Logs/SCM-Manager |
| Windows | $BASEDIR\logs |
The location of the **$BASEDIR** can be found [here](basedirectory).
## Configuration
The logging behaviour of SCM-Manager can be configured via an xml file.
The syntax and properties can be found [here](http://logback.qos.ch/manual/configuration.html).
The location of the file depends also on the type of installation.
| Type of Installation | Path |
|----------------------|---------|
| Docker | /opt/scm-server/conf/logging.xml |
| RPM | /etc/scm/logging.xml |
| DEB | /etc/scm/logging.xml |
| Unix | $EXTRACT_PATH/scm-server/conf/logging.xml |
| Mac OS X | $EXTRACT_PATH/scm-server/conf/logging.xml |
| Windows | $EXTRACT_PATH/scm-server/conf/logging.xml |
**$EXTRACT_PATH** is the path were you etract the content of the package.

View File

@@ -0,0 +1,142 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm;
import com.google.common.base.Strings;
import sonia.scm.util.SystemUtil;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.Properties;
/**
* Determines the base directory for SCM-Manager.
* This class should not be used directory, use {@link SCMContextProvider#getBaseDirectory()} instead.
*
* @since 2.0.0
*/
final class BaseDirectory {
/** Environment variable for the SCM-Manager base directory */
static final String ENVIRONMENT_VARIABLE = "SCM_HOME";
/** Java system property for the SCM-Manager base directory */
static final String SYSTEM_PROPERTY = "scm.home";
/** Classpath resource for the SCM-Manager base directory */
@SuppressWarnings("java:S1075") // it is already configurable
static final String CLASSPATH_RESOURCE = "/scm.properties";
/** Property name in resource file */
static final String RESOURCE_PROPERTY = "scm.home";
private final Platform platform;
private final String classPathResource;
private final Map<String,String> environment;
private final Properties systemProperties;
BaseDirectory(Platform platform, String classPathResource, Map<String, String> environment, Properties systemProperties) {
this.platform = platform;
this.classPathResource = classPathResource;
this.environment = environment;
this.systemProperties = systemProperties;
}
/**
* Returns the determined base directory.
*
* @return base directory
*/
@SuppressWarnings("java:S5304") // it is safe to use environment in this case
static Path get() {
return new BaseDirectory(
SystemUtil.getPlatform(),
CLASSPATH_RESOURCE,
System.getenv(),
System.getProperties()
).find();
}
Path find() {
String directory = getFromResource();
if (Strings.isNullOrEmpty(directory)) {
directory = getFromSystemProperty();
}
if (Strings.isNullOrEmpty(directory)) {
directory = getFromEnvironmentVariable();
}
if (Strings.isNullOrEmpty(directory)) {
directory = getOsSpecificDefault();
}
return Paths.get(directory);
}
private String getFromResource() {
try (InputStream input = BasicContextProvider.class.getResourceAsStream(classPathResource))
{
if (input != null)
{
Properties properties = new Properties();
properties.load(input);
return properties.getProperty(RESOURCE_PROPERTY);
}
}
catch (IOException ex)
{
throw new ConfigurationException("could not load properties form resource " + CLASSPATH_RESOURCE, ex);
}
return null;
}
private String getFromEnvironmentVariable() {
return environment.get(ENVIRONMENT_VARIABLE);
}
private String getFromSystemProperty() {
return systemProperties.getProperty(SYSTEM_PROPERTY);
}
private String getOsSpecificDefault() {
if (platform.isMac()) {
return getOsxDefault();
} else if (platform.isWindows()) {
return getWindowsDefault();
}
return systemProperties.getProperty("user.home") + "/.scm";
}
private String getOsxDefault() {
return systemProperties.getProperty("user.home") + "/Library/Application Support/SCM-Manager";
}
private String getWindowsDefault() {
return environment.get("APPDATA") + "\\SCM-Manager";
}
}

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm;
//~--- non-JDK imports --------------------------------------------------------
@@ -30,21 +30,21 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import sonia.scm.util.Util;
//~--- JDK imports ------------------------------------------------------------
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Path;
import java.util.Locale;
import java.util.Properties;
//~--- JDK imports ------------------------------------------------------------
/**
* The default implementation of {@link SCMContextProvider}.
*
* @author Sebastian Sdorra
*/
@SuppressWarnings("java:S106") // we can not use logger until base directory is not determined
public class BasicContextProvider implements SCMContextProvider
{
@@ -174,58 +174,29 @@ public class BasicContextProvider implements SCMContextProvider
*
* @return base directory SCM-Manager
*/
private File findBaseDirectory()
{
String path = getPathFromResource();
private File findBaseDirectory() {
File directory = BaseDirectory.get().toFile();
if (Util.isEmpty(path))
{
path = System.getProperty(DIRECTORY_PROPERTY);
if (Util.isEmpty(path))
{
path = System.getenv(DIRECTORY_ENVIRONMENT);
if (Util.isEmpty(path))
{
path = System.getProperty("user.home").concat(File.separator).concat(
DIRECTORY_DEFAULT);
}
}
}
File directory = new File(path);
if (!directory.exists() &&!directory.mkdirs())
{
String msg = "could not create home directory at ".concat(
directory.getAbsolutePath());
// do not use logger
// http://www.slf4j.org/codes.html#substituteLogger
System.err.println("===================================================");
System.err.append("Error: ").println(msg);
System.err.println("===================================================");
throw new IllegalStateException(msg);
}
else if (directory.exists() && !directory.canWrite())
{
String msg = "could not modify home directory at ".concat(
directory.getAbsolutePath());
// do not use logger
// http://www.slf4j.org/codes.html#substituteLogger
System.err.println("===================================================");
System.err.append("Error: ").println(msg);
System.err.println("===================================================");
throw new IllegalStateException(msg);
if (!directory.exists() &&!directory.mkdirs()) {
error("could not create home directory at " + directory.getAbsolutePath());
} else if (directory.exists() && !directory.canWrite()) {
error("could not modify home directory at " + directory.getAbsolutePath());
}
return directory;
}
private void error(String msg) {
// do not use logger
// http://www.slf4j.org/codes.html#substituteLogger
System.err.println("===================================================");
System.err.append("Error: ").println(msg);
System.err.println("===================================================");
throw new IllegalStateException(msg);
}
/**
* Find the current stage.
*
@@ -256,11 +227,11 @@ public class BasicContextProvider implements SCMContextProvider
}
private String determineVersion() {
String version = System.getProperty(VERSION_PROPERTY);
if (Strings.isNullOrEmpty(version)) {
version = loadVersion();
String v = System.getProperty(VERSION_PROPERTY);
if (Strings.isNullOrEmpty(v)) {
v = loadVersion();
}
return version;
return v;
}
/**
@@ -304,59 +275,6 @@ public class BasicContextProvider implements SCMContextProvider
return properties.getProperty(MAVEN_PROPERTY_VERSION, VERSION_DEFAULT);
}
//~--- get methods ----------------------------------------------------------
/**
* Load path from classpath resource.
*
*
* @return path from classpath resource or null
*/
private String getPathFromResource()
{
String path = null;
InputStream input = null;
try
{
input =
BasicContextProvider.class.getResourceAsStream(DIRECTORY_RESOURCE);
if (input != null)
{
Properties properties = new Properties();
properties.load(input);
path = properties.getProperty(DIRECTORY_PROPERTY);
}
}
catch (IOException ex)
{
throw new ConfigurationException(
"could not load properties form resource ".concat(DIRECTORY_RESOURCE),
ex);
}
finally
{
// do not use logger or IOUtil,
// http://www.slf4j.org/codes.html#substituteLogger
if (input != null)
{
try
{
input.close();
}
catch (IOException ex)
{
ex.printStackTrace(System.err);
}
}
}
return path;
}
//~--- fields ---------------------------------------------------------------
/** The base directory of the SCM-Manager */

View File

@@ -0,0 +1,124 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm;
import com.google.common.collect.ImmutableMap;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import java.util.Map;
import java.util.Properties;
import static org.assertj.core.api.Assertions.assertThat;
class BaseDirectoryTest {
@Test
void shouldGetFromClassPathResource() {
BaseDirectory directory = builder().withClassPathResource("/sonia/scm/basedirectory.properties").create();
assertThat(directory.find().toString()).isEqualTo("/tmp/scm_home");
}
@Test
void shouldGetFromSystemProperty() {
BaseDirectory directory = builder().withSystemProperty(BaseDirectory.SYSTEM_PROPERTY, "/tmp/scm_home").create();
assertThat(directory.find().toString()).isEqualTo("/tmp/scm_home");
}
@Test
void shouldGetFromEnvironmentVariable() {
BaseDirectory directory = builder().withEnvironment(BaseDirectory.ENVIRONMENT_VARIABLE, "/tmp/scm_home").create();
assertThat(directory.find().toString()).isEqualTo("/tmp/scm_home");
}
@Nested
class OsSpecificDefaults {
@Test
void linux() {
BaseDirectory directory = builder().withSystemProperty("user.home", "/tmp").create();
assertThat(directory.find().toString()).isEqualTo("/tmp/.scm");
}
@Test
void osx() {
BaseDirectory directory = builder().withOsx().withSystemProperty("user.home", "/tmp").create();
assertThat(directory.find().toString()).isEqualTo("/tmp/Library/Application Support/SCM-Manager");
}
@Test
void windows() {
BaseDirectory directory = builder().withWindows().withEnvironment("APPDATA", "/tmp").create();
assertThat(directory.find().toString()).isEqualTo("/tmp\\SCM-Manager");
}
}
private BaseDirectoryBuilder builder() {
return new BaseDirectoryBuilder();
}
static class BaseDirectoryBuilder {
private Platform platform = platform("Linux");
private String classPathResource = "/scm.properties";
private Map<String, String> environment = ImmutableMap.of();
private Properties systemProperties = new Properties();
public BaseDirectoryBuilder withOsx() {
this.platform = platform("Mac OS X");
return this;
}
public BaseDirectoryBuilder withWindows() {
this.platform = platform("Windows");
return this;
}
private Platform platform(String osName) {
return new Platform(osName, "64", "x86_64");
}
public BaseDirectoryBuilder withClassPathResource(String classPathResource) {
this.classPathResource = classPathResource;
return this;
}
public BaseDirectoryBuilder withEnvironment(String key, String value) {
this.environment = ImmutableMap.of(key, value);
return this;
}
public BaseDirectoryBuilder withSystemProperty(String key, String value) {
systemProperties.put(key, value);
return this;
}
public BaseDirectory create() {
return new BaseDirectory(platform, classPathResource, environment, systemProperties);
}
}
}

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm;
import org.junit.jupiter.api.BeforeEach;

View File

@@ -0,0 +1,25 @@
#
# MIT License
#
# Copyright (c) 2020-present Cloudogu GmbH and Contributors
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
scm.home = /tmp/scm_home

View File

@@ -21,63 +21,64 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm;
//~--- non-JDK imports --------------------------------------------------------
import ch.qos.logback.core.PropertyDefinerBase;
import com.google.common.annotations.VisibleForTesting;
import sonia.scm.util.SystemUtil;
//~--- JDK imports ------------------------------------------------------------
import java.io.File;
import java.util.Properties;
/**
* Resolve directory path for SCM-Manager logs.
*
* @author Sebastian Sdorra
*/
public class ScmLogFilePropertyDefiner extends PropertyDefinerBase
{
public class ScmLogFilePropertyDefiner extends PropertyDefinerBase {
/** Field description */
public static final String LOG_DIRECTORY = "logs";
private final String logDirectoryPath;
//~--- constructors ---------------------------------------------------------
public ScmLogFilePropertyDefiner() {
this(SCMContext.getContext(), SystemUtil.getPlatform(), System.getProperties());
}
/**
* Constructs ...
*
*/
public ScmLogFilePropertyDefiner()
{
File logDirectory = new File(SCMContext.getContext().getBaseDirectory(),
LOG_DIRECTORY);
@VisibleForTesting
ScmLogFilePropertyDefiner(SCMContextProvider context, Platform platform, Properties properties) {
File logDirectory = resolveDirectory(context, platform, properties);
if (!logDirectory.exists() &&!logDirectory.mkdirs())
{
if (!logDirectory.exists() && !logDirectory.mkdirs()) {
throw new ConfigurationException(
"could not create log directory ".concat(logDirectory.getPath()));
"could not create log directory ".concat(logDirectory.getPath()));
}
this.logDirectoryPath = logDirectory.getAbsolutePath();
}
//~--- get methods ----------------------------------------------------------
private File resolveDirectory(SCMContextProvider context, Platform platform, Properties properties) {
if (platform.isMac()) {
return resolveOsX(properties);
} else {
return resolveDefault(context);
}
}
private File resolveOsX(Properties properties) {
return new File(properties.getProperty("user.home"), "Library/Logs/SCM-Manager");
}
private File resolveDefault(SCMContextProvider context) {
return new File(context.getBaseDirectory(), "logs");
}
/**
* Method description
*
*
* @return
*/
@Override
public String getPropertyValue()
{
public String getPropertyValue() {
return logDirectoryPath;
}
//~--- fields ---------------------------------------------------------------
/** Field description */
private String logDirectoryPath;
}

View File

@@ -0,0 +1,93 @@
/*
* MIT License
*
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junitpioneer.jupiter.TempDirectory;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.nio.file.Path;
import java.util.Properties;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
@ExtendWith({MockitoExtension.class, TempDirectory.class})
class ScmLogFilePropertyDefinerTest {
@Mock
private SCMContextProvider context;
@Test
void shouldReturnPath(@TempDirectory.TempDir Path tempDir) {
when(context.getBaseDirectory()).thenReturn(tempDir.toFile());
ScmLogFilePropertyDefiner definer = builder().create();
Path logDirectory = tempDir.resolve("logs");
assertThat(definer.getPropertyValue()).isEqualTo(logDirectory.toAbsolutePath().toString());
}
@Test
void shouldReturnOsxPath(@TempDirectory.TempDir Path tempDir) {
ScmLogFilePropertyDefiner definer = builder()
.withOs("Mac OS X")
.withUserHome(tempDir.toAbsolutePath().toString())
.create();
Path logDirectory = tempDir.resolve("Library/Logs/SCM-Manager");
assertThat(definer.getPropertyValue()).isEqualTo(logDirectory.toAbsolutePath().toString());
}
private ScmLogFilePropertyDefinerBuilder builder() {
return new ScmLogFilePropertyDefinerBuilder();
}
private class ScmLogFilePropertyDefinerBuilder {
private Platform platform;
private Properties properties = new Properties();
public ScmLogFilePropertyDefinerBuilder() {
withOs("Linux");
}
public ScmLogFilePropertyDefinerBuilder withOs(String osName) {
platform = new Platform(osName, "64", "x86_64");
return this;
}
public ScmLogFilePropertyDefinerBuilder withUserHome(String path) {
properties.setProperty("user.home", path);
return this;
}
public ScmLogFilePropertyDefiner create() {
return new ScmLogFilePropertyDefiner(context, platform, properties);
}
}
}