Disable update for docker containers (#715)

Also add docker info to about page and sentry context
pull/6/head
ta264 6 years ago committed by GitHub
parent 6afece237c
commit 4be01a5a95
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -98,6 +98,7 @@ class GeneralSettings extends Component {
isResettingApiKey, isResettingApiKey,
isMono, isMono,
isWindows, isWindows,
isDocker,
mode, mode,
onInputChange, onInputChange,
onConfirmResetApiKey, onConfirmResetApiKey,
@ -161,6 +162,7 @@ class GeneralSettings extends Component {
advancedSettings={advancedSettings} advancedSettings={advancedSettings}
settings={settings} settings={settings}
isMono={isMono} isMono={isMono}
isDocker={isDocker}
onInputChange={onInputChange} onInputChange={onInputChange}
/> />
@ -202,6 +204,7 @@ GeneralSettings.propTypes = {
hasSettings: PropTypes.bool.isRequired, hasSettings: PropTypes.bool.isRequired,
isMono: PropTypes.bool.isRequired, isMono: PropTypes.bool.isRequired,
isWindows: PropTypes.bool.isRequired, isWindows: PropTypes.bool.isRequired,
isDocker: PropTypes.bool.isRequired,
mode: PropTypes.string.isRequired, mode: PropTypes.string.isRequired,
onInputChange: PropTypes.func.isRequired, onInputChange: PropTypes.func.isRequired,
onConfirmResetApiKey: PropTypes.func.isRequired, onConfirmResetApiKey: PropTypes.func.isRequired,

@ -26,6 +26,7 @@ function createMapStateToProps() {
isResettingApiKey, isResettingApiKey,
isMono: systemStatus.isMono, isMono: systemStatus.isMono,
isWindows: systemStatus.isWindows, isWindows: systemStatus.isWindows,
isDocker: systemStatus.isDocker,
mode: systemStatus.mode, mode: systemStatus.mode,
...sectionSettings ...sectionSettings
}; };

@ -16,6 +16,7 @@ function UpdateSettings(props) {
advancedSettings, advancedSettings,
settings, settings,
isMono, isMono,
isDocker,
onInputChange onInputChange
} = props; } = props;
@ -35,6 +36,14 @@ function UpdateSettings(props) {
{ key: 'script', value: 'Script' } { key: 'script', value: 'Script' }
]; ];
if (isDocker) {
return (
<FieldSet legend="Updates">
<div>Updating is disabled inside a docker container. Update the container image instead.</div>
</FieldSet>
);
}
return ( return (
<FieldSet legend="Updates"> <FieldSet legend="Updates">
<FormGroup <FormGroup
@ -117,6 +126,7 @@ UpdateSettings.propTypes = {
advancedSettings: PropTypes.bool.isRequired, advancedSettings: PropTypes.bool.isRequired,
settings: PropTypes.object.isRequired, settings: PropTypes.object.isRequired,
isMono: PropTypes.bool.isRequired, isMono: PropTypes.bool.isRequired,
isDocker: PropTypes.bool.isRequired,
onInputChange: PropTypes.func.isRequired onInputChange: PropTypes.func.isRequired
}; };

@ -16,6 +16,7 @@ class About extends Component {
const { const {
version, version,
isMonoRuntime, isMonoRuntime,
isDocker,
runtimeVersion, runtimeVersion,
migrationVersion, migrationVersion,
appData, appData,
@ -42,6 +43,14 @@ class About extends Component {
/> />
} }
{
isDocker &&
<DescriptionListItem
title="Docker"
data={'True'}
/>
}
<DescriptionListItem <DescriptionListItem
title="DB Migration" title="DB Migration"
data={migrationVersion} data={migrationVersion}
@ -83,6 +92,7 @@ About.propTypes = {
version: PropTypes.string.isRequired, version: PropTypes.string.isRequired,
isMonoRuntime: PropTypes.bool.isRequired, isMonoRuntime: PropTypes.bool.isRequired,
runtimeVersion: PropTypes.string.isRequired, runtimeVersion: PropTypes.string.isRequired,
isDocker: PropTypes.bool.isRequired,
migrationVersion: PropTypes.number.isRequired, migrationVersion: PropTypes.number.isRequired,
appData: PropTypes.string.isRequired, appData: PropTypes.string.isRequired,
startupPath: PropTypes.string.isRequired, startupPath: PropTypes.string.isRequired,

@ -24,6 +24,7 @@ class Updates extends Component {
error, error,
items, items,
isInstallingUpdate, isInstallingUpdate,
isDocker,
shortDateFormat, shortDateFormat,
onInstallLatestPress onInstallLatestPress
} = this.props; } = this.props;
@ -48,24 +49,34 @@ class Updates extends Component {
{ {
hasUpdateToInstall && hasUpdateToInstall &&
<div className={styles.updateAvailable}> <div className={styles.updateAvailable}>
<SpinnerButton {
className={styles.updateAvailable} !isDocker &&
kind={kinds.PRIMARY} <SpinnerButton
isSpinning={isInstallingUpdate} className={styles.updateAvailable}
onPress={onInstallLatestPress} kind={kinds.PRIMARY}
> isSpinning={isInstallingUpdate}
Install Latest onPress={onInstallLatestPress}
</SpinnerButton> >
Install Latest
{ </SpinnerButton>
isFetching && }
<LoadingIndicator
className={styles.loading} {
size={20} isDocker &&
/> <div className={styles.upToDateMessage}>
} An update is available. Please update your Docker image and re-create the container.
</div> </div>
}
{
isFetching &&
<LoadingIndicator
className={styles.loading}
size={20}
/>
}
</div>
} }
{ {
@ -162,6 +173,7 @@ Updates.propTypes = {
error: PropTypes.object, error: PropTypes.object,
items: PropTypes.array.isRequired, items: PropTypes.array.isRequired,
isInstallingUpdate: PropTypes.bool.isRequired, isInstallingUpdate: PropTypes.bool.isRequired,
isDocker: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired, shortDateFormat: PropTypes.string.isRequired,
onInstallLatestPress: PropTypes.func.isRequired onInstallLatestPress: PropTypes.func.isRequired
}; };

@ -6,6 +6,7 @@ import { fetchUpdates } from 'Store/Actions/systemActions';
import { executeCommand } from 'Store/Actions/commandActions'; import { executeCommand } from 'Store/Actions/commandActions';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector'; import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
import * as commandNames from 'Commands/commandNames'; import * as commandNames from 'Commands/commandNames';
import Updates from './Updates'; import Updates from './Updates';
@ -14,7 +15,8 @@ function createMapStateToProps() {
(state) => state.system.updates, (state) => state.system.updates,
createUISettingsSelector(), createUISettingsSelector(),
createCommandExecutingSelector(commandNames.APPLICATION_UPDATE), createCommandExecutingSelector(commandNames.APPLICATION_UPDATE),
(updates, uiSettings, isInstallingUpdate) => { createSystemStatusSelector(),
(updates, uiSettings, isInstallingUpdate, systemStatus) => {
const { const {
isFetching, isFetching,
isPopulated, isPopulated,
@ -28,6 +30,7 @@ function createMapStateToProps() {
error, error,
items, items,
isInstallingUpdate, isInstallingUpdate,
isDocker: systemStatus.isDocker,
shortDateFormat: uiSettings.shortDateFormat shortDateFormat: uiSettings.shortDateFormat
}; };
} }

@ -64,6 +64,7 @@ namespace Lidarr.Api.V1.System
IsLinux = OsInfo.IsLinux, IsLinux = OsInfo.IsLinux,
IsOsx = OsInfo.IsOsx, IsOsx = OsInfo.IsOsx,
IsWindows = OsInfo.IsWindows, IsWindows = OsInfo.IsWindows,
IsDocker = _osInfo.IsDocker,
Mode = _runtimeInfo.Mode, Mode = _runtimeInfo.Mode,
Branch = _configFileProvider.Branch, Branch = _configFileProvider.Branch,
Authentication = _configFileProvider.AuthenticationMethod, Authentication = _configFileProvider.AuthenticationMethod,

@ -14,7 +14,9 @@ namespace NzbDrone.Common.EnvironmentInfo
public static bool IsLinux => Os == Os.Linux; public static bool IsLinux => Os == Os.Linux;
public static bool IsOsx => Os == Os.Osx; public static bool IsOsx => Os == Os.Osx;
public static bool IsWindows => Os == Os.Windows; public static bool IsWindows => Os == Os.Windows;
public static bool IsDocker { get; }
// this needs to not be static so we can mock it
public bool IsDocker { get; }
public string Version { get; } public string Version { get; }
public string Name { get; } public string Name { get; }
@ -45,10 +47,6 @@ namespace NzbDrone.Common.EnvironmentInfo
else else
{ {
Os = Os.Linux; Os = Os.Linux;
if (File.Exists("/proc/1/cgroup") && File.ReadAllText("/proc/1/cgroup").Contains("/docker/"))
{
IsDocker = true;
}
} }
break; break;
} }
@ -88,8 +86,14 @@ namespace NzbDrone.Common.EnvironmentInfo
FullName = Name; FullName = Name;
} }
if (IsLinux && File.Exists("/proc/1/cgroup") && File.ReadAllText("/proc/1/cgroup").Contains("/docker/"))
{
IsDocker = true;
}
Environment.SetEnvironmentVariable("OS_NAME", Name); Environment.SetEnvironmentVariable("OS_NAME", Name);
Environment.SetEnvironmentVariable("OS_VERSION", Version); Environment.SetEnvironmentVariable("OS_VERSION", Version);
Environment.SetEnvironmentVariable("OS_IS_DOCKER", IsDocker.ToString());
} }
} }
@ -98,6 +102,8 @@ namespace NzbDrone.Common.EnvironmentInfo
string Version { get; } string Version { get; }
string Name { get; } string Name { get; }
string FullName { get; } string FullName { get; }
bool IsDocker { get; }
} }
public enum Os public enum Os

@ -260,10 +260,12 @@ namespace NzbDrone.Common.Instrumentation.Sentry
// populated these values yet // populated these values yet
var osName = Environment.GetEnvironmentVariable("OS_NAME"); var osName = Environment.GetEnvironmentVariable("OS_NAME");
var osVersion = Environment.GetEnvironmentVariable("OS_VERSION"); var osVersion = Environment.GetEnvironmentVariable("OS_VERSION");
var isDocker = Environment.GetEnvironmentVariable("OS_IS_DOCKER");
var runTimeVersion = Environment.GetEnvironmentVariable("RUNTIME_VERSION"); var runTimeVersion = Environment.GetEnvironmentVariable("RUNTIME_VERSION");
sentryEvent.SetTag("os_name", osName); sentryEvent.SetTag("os_name", osName);
sentryEvent.SetTag("os_version", $"{osName} {osVersion}"); sentryEvent.SetTag("os_version", $"{osName} {osVersion}");
sentryEvent.SetTag("is_docker", isDocker);
sentryEvent.SetTag("runtime_version", $"{PlatformInfo.PlatformName} {runTimeVersion}"); sentryEvent.SetTag("runtime_version", $"{PlatformInfo.PlatformName} {runTimeVersion}");
sentryEvent.SetTag("sqlite_version", $"{DatabaseVersion}"); sentryEvent.SetTag("sqlite_version", $"{DatabaseVersion}");
sentryEvent.SetTag("database_migration", $"{DatabaseMigration}"); sentryEvent.SetTag("database_migration", $"{DatabaseMigration}");

@ -5,6 +5,7 @@ using Moq;
using NUnit.Framework; using NUnit.Framework;
using NzbDrone.Common.Disk; using NzbDrone.Common.Disk;
using NzbDrone.Common.EnsureThat; using NzbDrone.Common.EnsureThat;
using NzbDrone.Common.EnvironmentInfo;
using NzbDrone.Core.Download; using NzbDrone.Core.Download;
using NzbDrone.Core.Download.Clients; using NzbDrone.Core.Download.Clients;
using NzbDrone.Core.HealthCheck.Checks; using NzbDrone.Core.HealthCheck.Checks;
@ -86,7 +87,9 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
private void GivenDocker() private void GivenDocker()
{ {
// Mocker.GetMock<IOsInfo>()
.Setup(x => x.IsDocker)
.Returns(true);
} }
[Test] [Test]
@ -139,9 +142,10 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
} }
[Test] [Test]
[Explicit("Only works if running inside a docker container")]
public void should_return_docker_path_mapping_error_if_on_docker_and_root_missing() public void should_return_docker_path_mapping_error_if_on_docker_and_root_missing()
{ {
GivenDocker();
Subject.Check().ShouldBeError(wikiFragment: "docker-bad-remote-path-mapping"); Subject.Check().ShouldBeError(wikiFragment: "docker-bad-remote-path-mapping");
} }
@ -215,9 +219,10 @@ namespace NzbDrone.Core.Test.HealthCheck.Checks
} }
[Test] [Test]
[Explicit("Only works if running inside a docker container")]
public void should_return_docker_mapping_error_on_track_import_failed_event_inside_docker_if_folder_does_not_exist() public void should_return_docker_mapping_error_on_track_import_failed_event_inside_docker_if_folder_does_not_exist()
{ {
GivenDocker();
clientStatus.IsLocalhost = false; clientStatus.IsLocalhost = false;
var importEvent = new TrackImportFailedEvent(null, null, true, downloadItem); var importEvent = new TrackImportFailedEvent(null, null, true, downloadItem);

@ -87,6 +87,16 @@ namespace NzbDrone.Core.Test.UpdateTests
.Returns(true); .Returns(true);
} }
[Test]
public void should_not_update_if_inside_docker()
{
Mocker.GetMock<IOsInfo>().Setup(x => x.IsDocker).Returns(true);
Subject.Invoking(x => x.Execute(new ApplicationUpdateCommand()))
.ShouldThrow<CommandFailedException>()
.WithMessage("Updating is disabled inside a docker container. Please update the container image.");
}
[Test] [Test]
public void should_delete_sandbox_before_update_if_folder_exists() public void should_delete_sandbox_before_update_if_folder_exists()
{ {

@ -58,7 +58,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
{ {
return new HealthCheck(GetType(), HealthCheckResult.Error, $"Remote download client {client.Definition.Name} places downloads in {folder.FullPath} but this is not a valid {_osInfo.Name} path. Review your remote path mappings and download client settings.", "#bad-remote-path-mapping"); return new HealthCheck(GetType(), HealthCheckResult.Error, $"Remote download client {client.Definition.Name} places downloads in {folder.FullPath} but this is not a valid {_osInfo.Name} path. Review your remote path mappings and download client settings.", "#bad-remote-path-mapping");
} }
else if (OsInfo.IsDocker) else if (_osInfo.IsDocker)
{ {
return new HealthCheck(GetType(), HealthCheckResult.Error, $"You are using docker; download client {client.Definition.Name} places downloads in {folder.FullPath} but this is not a valid {_osInfo.Name} path. Review your remote path mappings and download client settings.", "#docker-bad-remote-path-mapping"); return new HealthCheck(GetType(), HealthCheckResult.Error, $"You are using docker; download client {client.Definition.Name} places downloads in {folder.FullPath} but this is not a valid {_osInfo.Name} path. Review your remote path mappings and download client settings.", "#docker-bad-remote-path-mapping");
} }
@ -70,7 +70,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
if (!_diskProvider.FolderExists(folder.FullPath)) if (!_diskProvider.FolderExists(folder.FullPath))
{ {
if (OsInfo.IsDocker) if (_osInfo.IsDocker)
{ {
return new HealthCheck(GetType(), HealthCheckResult.Error, $"You are using docker; download client {client.Definition.Name} places downloads in {folder.FullPath} but this directory does not appear to exist inside the container. Review your remote path mappings and container volume settings.", "#docker-bad-remote-path-mapping"); return new HealthCheck(GetType(), HealthCheckResult.Error, $"You are using docker; download client {client.Definition.Name} places downloads in {folder.FullPath} but this directory does not appear to exist inside the container. Review your remote path mappings and container volume settings.", "#docker-bad-remote-path-mapping");
} }
@ -141,7 +141,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
{ {
return new HealthCheck(GetType(), HealthCheckResult.Error, $"Remote download client {client.Definition.Name} reported files in {dlpath} but this is not a valid {_osInfo.Name} path. Review your remote path mappings and download client settings.", "#bad-remote-path-mapping"); return new HealthCheck(GetType(), HealthCheckResult.Error, $"Remote download client {client.Definition.Name} reported files in {dlpath} but this is not a valid {_osInfo.Name} path. Review your remote path mappings and download client settings.", "#bad-remote-path-mapping");
} }
else if (OsInfo.IsDocker) else if (_osInfo.IsDocker)
{ {
return new HealthCheck(GetType(), HealthCheckResult.Error, $"You are using docker; download client {client.Definition.Name} reported files in {dlpath} but this is not a valid {_osInfo.Name} path. Review your remote path mappings and download client settings.", "#docker-bad-remote-path-mapping"); return new HealthCheck(GetType(), HealthCheckResult.Error, $"You are using docker; download client {client.Definition.Name} reported files in {dlpath} but this is not a valid {_osInfo.Name} path. Review your remote path mappings and download client settings.", "#docker-bad-remote-path-mapping");
} }
@ -157,7 +157,7 @@ namespace NzbDrone.Core.HealthCheck.Checks
} }
// if it's a remote client/docker, likely missing path mappings // if it's a remote client/docker, likely missing path mappings
if (OsInfo.IsDocker) if (_osInfo.IsDocker)
{ {
return new HealthCheck(GetType(), HealthCheckResult.Error, $"You are using docker; download client {client.Definition.Name} reported files in {dlpath} but this directory does not appear to exist inside the container. Review your remote path mappings and container volume settings.", "#docker-bad-remote-path-mapping"); return new HealthCheck(GetType(), HealthCheckResult.Error, $"You are using docker; download client {client.Definition.Name} reported files in {dlpath} but this directory does not appear to exist inside the container. Review your remote path mappings and container volume settings.", "#docker-bad-remote-path-mapping");
} }

@ -32,6 +32,7 @@ namespace NzbDrone.Core.Update
private readonly IConfigFileProvider _configFileProvider; private readonly IConfigFileProvider _configFileProvider;
private readonly IRuntimeInfo _runtimeInfo; private readonly IRuntimeInfo _runtimeInfo;
private readonly IBackupService _backupService; private readonly IBackupService _backupService;
private readonly IOsInfo _osInfo;
public InstallUpdateService(ICheckUpdateService checkUpdateService, public InstallUpdateService(ICheckUpdateService checkUpdateService,
@ -46,6 +47,7 @@ namespace NzbDrone.Core.Update
IConfigFileProvider configFileProvider, IConfigFileProvider configFileProvider,
IRuntimeInfo runtimeInfo, IRuntimeInfo runtimeInfo,
IBackupService backupService, IBackupService backupService,
IOsInfo osInfo,
Logger logger) Logger logger)
{ {
if (configFileProvider == null) if (configFileProvider == null)
@ -64,6 +66,7 @@ namespace NzbDrone.Core.Update
_configFileProvider = configFileProvider; _configFileProvider = configFileProvider;
_runtimeInfo = runtimeInfo; _runtimeInfo = runtimeInfo;
_backupService = backupService; _backupService = backupService;
_osInfo = osInfo;
_logger = logger; _logger = logger;
} }
@ -204,6 +207,11 @@ namespace NzbDrone.Core.Update
return; return;
} }
if (_osInfo.IsDocker)
{
throw new CommandFailedException("Updating is disabled inside a docker container. Please update the container image.");
}
if (OsInfo.IsNotWindows && !_configFileProvider.UpdateAutomatically && message.Trigger != CommandTrigger.Manual) if (OsInfo.IsNotWindows && !_configFileProvider.UpdateAutomatically && message.Trigger != CommandTrigger.Manual)
{ {
_logger.ProgressDebug("Auto-update not enabled, not installing available update"); _logger.ProgressDebug("Auto-update not enabled, not installing available update");

Loading…
Cancel
Save