/// <summary> /// <para> /// Installs the tool scripts, making them executable. /// </para> /// <note> /// Any <b>".sh"</b> file extensions will be removed for ease-of-use. /// </note> /// </summary> /// <param name="controller">The setup controller.</param> public void BaseInstallToolScripts(ISetupController controller) { Covenant.Requires <ArgumentException>(controller != null, nameof(controller)); InvokeIdempotent("base/tool-scripts", () => { controller.LogProgress(this, verb: "setup", message: "tools (base)"); // Upload any tool scripts to the neonKUBE bin folder, stripping // the [*.sh] file type (if present) and then setting execute // permissions. var scriptsFolder = KubeHelper.Resources.GetDirectory("/Tools"); // $hack(jefflill): https://github.com/nforgeio/neonKUBE/issues/1121 foreach (var file in scriptsFolder.GetFiles()) { var targetName = file.Name; if (Path.GetExtension(targetName) == ".sh") { targetName = Path.GetFileNameWithoutExtension(targetName); } using (var toolStream = file.OpenStream()) { UploadText(LinuxPath.Combine(KubeNodeFolder.Bin, targetName), toolStream, permissions: "744"); } } }); }
/// <summary> /// Edits the [neon-proxy-public-bridge.sh] and [neon-proxy-private-bridge.sh] /// scripts to remove the [VAULT_CREDENTIALS] environment variable so the new /// .NET based proxy bridge image will work properly. /// </summary> /// <param name="node">The target node.</param> private void UpdateProxyBridgeScripts(SshProxy <NodeDefinition> node) { var scriptNames = new string[] { "neon-proxy-public-bridge.sh", "neon-proxy-private-bridge.sh" }; foreach (var scriptName in scriptNames) { var scriptPath = LinuxPath.Combine(HiveHostFolders.Scripts, scriptName); var scriptText = node.DownloadText(scriptName); var sbEdited = new StringBuilder(); using (var reader = new StringReader(scriptText)) { foreach (var line in reader.Lines()) { if (!line.Contains("--env VAULT_CREDENTIALS=")) { sbEdited.AppendLineLinux(line); } } } node.UploadText(scriptPath, sbEdited.ToString(), permissions: "700"); } }
/// <summary> /// Starts a neonHIVE related Docker container on a node and also uploads a script /// to make it easy to restart the container manually or for hive updates. /// </summary> /// <param name="node">The target hive node.</param> /// <param name="containerName">Identifies the container.</param> /// <param name="image">The Docker image to be used by the container.</param> /// <param name="runOptions">Optional run options (defaults to <see cref="RunOptions.FaultOnError"/>).</param> /// <param name="commands">The commands required to start the container.</param> /// <remarks> /// <para> /// This method performs the following steps: /// </para> /// <list type="number"> /// <item> /// Passes <paramref name="image"/> to <see cref="Program.ResolveDockerImage(string)"/> to /// obtain the actual image to be started. /// </item> /// <item> /// Generates the first few lines of the script file that sets the /// default image as the <c>TARGET_IMAGE</c> macro and then overrides /// this with the script parameter (if there is one). /// </item> /// <item> /// Appends the commands to the script, replacing any text that matches /// <see cref="ImagePlaceholderArg"/> with <c>${TARGET_IMAGE}</c> to make it easy /// for services to be upgraded later. /// </item> /// <item> /// Starts the container. /// </item> /// <item> /// Uploads the generated script to the node to [<see cref="HiveHostFolders.Scripts"/>/<paramref name="containerName"/>.sh]. /// </item> /// </list> /// </remarks> public static void StartContainer(SshProxy <NodeDefinition> node, string containerName, string image, RunOptions runOptions = RunOptions.FaultOnError, params IBashCommandFormatter[] commands) { Covenant.Requires <ArgumentNullException>(node != null); Covenant.Requires <ArgumentNullException>(!string.IsNullOrWhiteSpace(containerName)); Covenant.Requires <ArgumentNullException>(!string.IsNullOrWhiteSpace(image)); Covenant.Requires <ArgumentNullException>(commands != null); Covenant.Requires <ArgumentNullException>(commands.Length > 0); node.Status = $"start: {containerName}"; // Generate the container start script. var script = CreateStartScript(containerName, image, true, commands); // Upload the script to the target node and set permissions. var scriptPath = LinuxPath.Combine(HiveHostFolders.Scripts, $"{containerName}.sh"); node.UploadText(scriptPath, script); node.SudoCommand($"chmod 740 {scriptPath}"); // Run the script without a parameter to start the container. node.IdempotentDockerCommand($"setup/{containerName}", null, runOptions, scriptPath); node.Status = string.Empty; }
/// <summary> /// Pushes a file to device /// </summary> /// <param name="localFilePath">the absolute path to file on local host</param> /// <returns>destination path on device for file</returns> /// <exception cref="IOException">if fatal error occurred when pushing file</exception> public String SyncPackageToDevice(String localFilePath) { try { String packageFileName = Path.GetFileName(localFilePath); // only root has access to /data/local/tmp/... not sure how adb does it then... // workitem: 16823 // workitem: 19711 String remoteFilePath = LinuxPath.Combine(TEMP_DIRECTORY_FOR_INSTALL, packageFileName); Console.WriteLine(String.Format("Uploading {0} onto device '{1}'", packageFileName, SerialNumber)); Log.d(packageFileName, String.Format("Uploading {0} onto device '{1}'", packageFileName, SerialNumber)); SyncService sync = SyncService; if (sync != null) { String message = String.Format("Uploading file onto device '{0}'", SerialNumber); Log.d(LOG_TAG, message); SyncResult result = sync.PushFile(localFilePath, remoteFilePath, SyncService.NullProgressMonitor); if (result.Code != ErrorCodeHelper.RESULT_OK) { throw new IOException(String.Format("Unable to upload file: {0}", result.Message)); } } else { throw new IOException("Unable to open sync connection!"); } return(remoteFilePath); } catch (IOException e) { Log.e(LOG_TAG, String.Format("Unable to open sync connection! reason: {0}", e.Message)); throw; } }
/// <summary> /// this is a fallback if the mkdir -p fails for somereason /// </summary> /// <param name="path"></param> internal void MakeDirectoryFallbackInternal(string path, CommandErrorReceiver cer) { string[] segs = path.Split(new char[] { LinuxPath.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries); FileEntry current = Device.FileListingService.Root; foreach (var pathItem in segs) { FileEntry[] entries = Device.FileListingService.GetChildren(current, true, null); bool found = false; foreach (var e in entries) { if (string.Compare(e.Name, pathItem, false) == 0) { current = e; found = true; break; } } if (!found) { current = FileEntry.FindOrCreate(Device, LinuxPath.Combine(current.FullPath, pathItem + new string(new char[] { LinuxPath.DirectorySeparatorChar }))); Device.ExecuteShellCommand("mkdir {0}", cer, current.FullEscapedPath); } } }
public void When2ArgsAndPath1IsEmptyAndPath2IsRooted_ShouldReturnRootedPath2( ) { var fixture = new Fixture( ); var p2 = fixture.Create("/path2-"); var result = LinuxPath.Combine(string.Empty, p2); Assert.Equal("/{0}/".With(p2).REReplace("//", "/"), result); }
public void When2ArgsAndPath1IsEmpty_ShouldReturnRelativePath2( ) { var fixture = new Fixture( ); var p2 = fixture.Create("path2-"); var result = LinuxPath.Combine(string.Empty, p2); Assert.Equal("./{0}/".With(p2), result); }
public void When4ArgsAndPath4IsNull_ShouldThrowArgumentNullException( ) { var fixture = new Fixture( ); Assert.Throws <ArgumentNullException> (() => { var result = LinuxPath.Combine(fixture.Create("path1"), fixture.Create("path2"), fixture.Create("path3"), null); }); }
public void WhenArgsArrayIsNull_ShouldThrowArgumentNullException( ) { var fixture = new Fixture( ); string[] args = null; Assert.Throws <ArgumentNullException> (() => { LinuxPath.Combine(args); }); }
public void When2ArgsAndPath2IsRooted_ShouldReturnPath2( ) { var fixture = new Fixture( ); var p1 = fixture.Create("/path1-"); var p2 = fixture.Create("/path2-"); var result = LinuxPath.Combine(p1, p2); Assert.Equal("{0}/".With(p2), result); }
public void When2ArgsAndPathsHaveSeparators_ShouldReturnCombined( ) { var fixture = new Fixture( ); var p1 = "{0}/".With(fixture.Create("/path1")); var p2 = fixture.Create("path2"); var result = LinuxPath.Combine(p1, p2); Assert.Equal("{0}/{1}".With(p1, p2).REReplace("//", "/"), result); }
public void When2Args_ShouldReturnCombined( ) { var fixture = new Fixture( ); var p1 = fixture.Create("/path1"); var p2 = fixture.Create("path2"); var result = LinuxPath.Combine(p1, p2); Assert.Equal("{0}/{1}".With(p1, p2), result); }
public void WhenPathIsDirectory_ShouldReturnEmpty( ) { var fixture = new Fixture( ); var p1 = fixture.Create("/path1-"); var p2 = "{0}/".With(fixture.Create("path2-")); var p = LinuxPath.Combine(p1, p2); var result = LinuxPath.GetFileName(p); Assert.Equal(string.Empty, result); }
public void When4Args_ShouldReturnCombined( ) { var fixture = new Fixture( ); var p1 = fixture.Create("/path1"); var p2 = fixture.Create("path2/"); var p3 = fixture.Create("./path3"); var p4 = fixture.Create("path4/"); var result = LinuxPath.Combine(p1, p2, p3, p4); Assert.Equal("{0}/{1}/{2}/{3}".With(p1, p2, p3, p4), result); }
public void WhenPathIsFileWithoutExtension_ShouldReturnFileWithoutExtension( ) { var fixture = new Fixture( ); var p1 = fixture.Create("/path1-"); var p2 = "{0}/".With(fixture.Create("path2-")); var f = "{0}".With(fixture.Create("file-")); var p = LinuxPath.Combine(p1, p2, f); var result = LinuxPath.GetFileName(p); Assert.Equal(f, result); }
/// <summary> /// Updates a service or container start script on a hive node with a new image. /// </summary> /// <param name="node">The target hive node.</param> /// <param name="scriptName">The script name (without the <b>.sh</b>).</param> /// <param name="image">The fully qualified image name.</param> private static void UpdateStartScript(SshProxy <NodeDefinition> node, string scriptName, string image) { var scriptPath = LinuxPath.Combine(HiveHostFolders.Scripts, $"{scriptName}.sh"); node.Status = $"edit: {scriptPath}"; if (node.FileExists(scriptPath)) { var curScript = node.DownloadText(scriptPath); var sbNewScript = new StringBuilder(); // Scan for the generated code section and then replace the first // line that looks like: // // TARGET_IMAGE=OLD-IMAGE // // with the new image and then upload the change. using (var reader = new StringReader(curScript)) { var inGenerated = false; var wasEdited = false; foreach (var line in reader.Lines()) { if (wasEdited) { sbNewScript.AppendLine(line); continue; } if (!inGenerated && line.StartsWith(ServiceHelper.ParamSectionMarker)) { inGenerated = true; } if (line.StartsWith("TARGET_IMAGE=")) { sbNewScript.AppendLine($"TARGET_IMAGE={image}"); wasEdited = true; } else { sbNewScript.AppendLine(line); } } } node.UploadText(scriptPath, sbNewScript.ToString(), permissions: "740"); } node.Status = string.Empty; }
public SyncResult DoPush(IEnumerable <FileSystemInfo> files, string remotePath, ISyncProgressMonitor monitor) { if (monitor == null) { throw new ArgumentNullException("monitor", "Monitor cannot be null"); } // check if we're canceled if (monitor.IsCanceled) { return(new SyncResult(ErrorCodeHelper.RESULT_CANCELED)); } foreach (FileSystemInfo f in files) { // check if we're canceled if (monitor.IsCanceled) { return(new SyncResult(ErrorCodeHelper.RESULT_CANCELED)); } // append the name of the directory/file to the remote path string dest = LinuxPath.Combine(remotePath, f.Name); if (f.Exists) { if (f.IsDirectory()) { DirectoryInfo fsiDir = f as DirectoryInfo; monitor.StartSubTask(f.FullName, dest); SyncResult result = this.DoPush(fsiDir.GetFileSystemInfos(), dest, monitor); if (result.Code != ErrorCodeHelper.RESULT_OK) { return(result); } monitor.Advance(1); } else if (f.IsFile()) { monitor.StartSubTask(f.FullName, dest); SyncResult result = this.DoPushFile(f.FullName, dest, monitor); if (result.Code != ErrorCodeHelper.RESULT_OK) { return(result); } } } } return(new SyncResult(ErrorCodeHelper.RESULT_OK)); }
public void WhenArgsArray_ShouldReturnCombined( ) { var fixture = new Fixture( ); var p1 = fixture.Create("/path1"); var p2 = fixture.Create("path2"); var p3 = fixture.Create("path3"); var p4 = fixture.Create("path4"); var p5 = fixture.Create("path5"); var result = LinuxPath.Combine(p1, p2, p3, p4, p5); var expected = "{0}/{1}/{2}/{3}/{4}".With(p1, p2, p3, p4, p5); Assert.Equal(expected, result); }
public void WhenArgsArrayContainsNullItem_ShouldThrowArgumentNullException( ) { var fixture = new Fixture( ); var p1 = fixture.Create("/path1"); string p2 = null; var p3 = fixture.Create("path3"); var p4 = fixture.Create("path4"); var p5 = fixture.Create("path5"); Assert.Throws <ArgumentNullException> (() => { LinuxPath.Combine(p1, p2, p3, p4, p5); }); }
/// <summary> /// Static constructor. /// </summary> static Node() { if (NeonHelper.IsLinux) { Name = File.ReadAllLines(LinuxPath.Combine(HostMount, "etc/hostname")).First().Trim(); } else { Name = "emulated"; } AgentId = Guid.NewGuid().ToString("d"); }
public virtual bool Adapt(string localDirectory, string file, string remoteDirectory) { ValidateState(); mEventProvider.OnActivityLog(new ActivityLogEventArgs(Resources.MsgActivityAdaptPushFile, true)); if (!Push(Path.Combine(localDirectory, file), remoteDirectory)) { return(false); } mEventProvider.OnActivityLog(new ActivityLogEventArgs(Resources.MsgActivityAdaptChangePermissions, true)); return(Shell(string.Format("chmod 644 {0}", LinuxPath.Combine(remoteDirectory, file)))); }
/// <summary> /// <para> /// Installs one of the Helm charts that was pre-positioned on the node /// VM image. These can be fond in the <see cref="KubeNodeFolder.Helm"/> /// with a folder for each chart. /// </para> /// <note> /// This command <b>DOES NOT WAIT</b> for the Helm chart to be completely /// installed and any target services or assets to be running because that /// does not appear to be reliable. You'll need to explicitly verify that /// deployment has completed when necessary. /// </note> /// </summary> /// <param name="chartName">The Helm chart folder name.</param> /// <param name="releaseName">Optional component release name. This defaults to <paramref name="chartName"/>.</param> /// <param name="namespace"> /// Optional namespace where Kubernetes namespace where the Helm chart should /// be installed. This defaults to <b>"default"</b>. /// </param> /// <param name="timeout">Optional timeout. This defaults to <b>unlimited</b>.</param> /// <param name="values">Optional Helm chart value overrides.</param> public void InstallProvisionedHelmChart( string chartName, string releaseName = null, string @namespace = "default", TimeSpan timeout = default, List <KeyValuePair <string, object> > values = null) { Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(chartName), nameof(chartName)); Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(@namespace), nameof(@namespace)); if (string.IsNullOrEmpty(releaseName)) { releaseName = chartName; } var valueArgs = new StringBuilder(); if (values != null) { foreach (var value in values) { var valueType = value.Value.GetType(); if (valueType == typeof(string)) { valueArgs.AppendWithSeparator($"--set-string {value.Key}=\"{value.Value}\"", @"\n"); } else if (valueType == typeof(int)) { valueArgs.AppendWithSeparator($"--set {value.Key}={value.Value}", @"\n"); } else { throw new NotImplementedException(); } } } var timeoutArg = string.Empty; if (timeout > TimeSpan.Zero) { timeoutArg = $"--timeout {(int)Math.Ceiling(timeout.TotalSeconds)}s"; } var chartFolderPath = LinuxPath.Combine(KubeNodeFolder.Helm, chartName); var chartValuesPath = LinuxPath.Combine(chartFolderPath, "values.yaml"); SudoCommand($"helm install {releaseName} {chartFolderPath} --namespace {@namespace} -f {chartValuesPath} {valueArgs} {timeoutArg}", RunOptions.Defaults | RunOptions.FaultOnError); }
public void When2ArgsAndPath2ContainsInvalidCharacter_ShouldThrowArgumentException( ) { var fixture = new Fixture( ); int errorCount = 0; for (var x = 0; x < LinuxPathConsts.InvalidPathChars.Length; ++x) { try { string result = LinuxPath.Combine("/{0}".With(fixture.Create("path1")), "{0}-{1}".With(fixture.Create("path2"), LinuxPathConsts.InvalidPathChars[x])); } catch (ArgumentException) { errorCount++; } } Assert.Equal(LinuxPathConsts.InvalidPathChars.Length, errorCount); }
/// <summary> /// Installs the Helm charts as a single ZIP archive written to the /// neonKUBE node's Helm folder. /// </summary> /// <param name="node">The node instance.</param> /// <param name="controller">The setup controller.</param> public static void NodeInstallHelmArchive(this ILinuxSshProxy node, ISetupController controller) { Covenant.Requires <ArgumentNullException>(controller != null, nameof(controller)); using (var ms = new MemoryStream()) { controller.LogProgress(node, verb: "setup", message: "helm charts (zip)"); var helmFolder = KubeSetup.Resources.GetDirectory("/Helm"); // $hack(jefflill): https://github.com/nforgeio/neonKUBE/issues/1121 helmFolder.Zip(ms, searchOptions: SearchOption.AllDirectories, zipOptions: StaticZipOptions.LinuxLineEndings); ms.Seek(0, SeekOrigin.Begin); node.Upload(LinuxPath.Combine(KubeNodeFolder.Helm, "charts.zip"), ms, permissions: "660"); } }
/// <summary> /// Prepends any required generic hive updates initialization steps /// to a setup controller. /// </summary> /// <param name="controller">The setup controller.</param> protected void Initialize(SetupController <NodeDefinition> controller) { Covenant.Requires <ArgumentNullException>(controller != null); controller.AddStep(GetStepLabel("initialize"), (node, stepDelay) => { node.Status = "update state"; var updateFolder = LinuxPath.Combine(HiveHostFolders.State, "update", ToVersion.ToString()); node.SudoCommand("mkdir -p", updateFolder); node.SudoCommand("chmod 770", updateFolder); }, noParallelLimit: true, position: 0); }
public override bool Push(string localFile, string remotePath) { ValidateState(); string remoteFile = remotePath; try { if (ADBDevice.FileListingService.FindFileEntry(remotePath).IsDirectory) { remoteFile = LinuxPath.Combine(remotePath, Path.GetFileName(localFile)); } } catch (FileNotFoundException) { } return(ProcessSyncResult(ADBDevice.SyncService.PushFile(localFile, remoteFile, new SingleFileProgressMonitor(this, localFile)))); }
/// <summary> /// Update the Elasticsearch container launch scripts to enable automatic /// memory settings based on any cgroup limits. /// </summary> /// <param name="node">The target node.</param> private void UpdateElasticsearch(SshProxy <NodeDefinition> node) { // This method is called for all cluster nodes, even those // that aren't currently hosting Elasticsearch, so we can // update any scripts that may have been orphaned (for // consistency). // // The update consists of replacing the script line that // sets the [ES_JAVA_OPTS] environment variable with: // // --env ES_JAVA_OPTS=-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap \ // // To ensure that this feature is enabled in favor of the // old hacked memory level settings. var scriptPath = LinuxPath.Combine(HiveHostFolders.Scripts, "neon-log-esdata.sh"); node.InvokeIdempotentAction(GetIdempotentTag("neon-log-esdata"), () => { if (node.FileExists(scriptPath)) { node.Status = $"edit: {scriptPath}"; var orgScript = node.DownloadText(scriptPath); var newScript = new StringBuilder(); foreach (var line in new StringReader(orgScript).Lines()) { if (line.Contains("ES_JAVA_OPTS=")) { newScript.AppendLine(" --env \"ES_JAVA_OPTS=-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap\" \\"); } else { newScript.AppendLine(line); } } node.UploadText(scriptPath, newScript.ToString(), permissions: ""); node.Status = string.Empty; } }); }
/// <summary> /// Appends the steps required to start a neonHIVE related Docker container and upload /// a script to the hive managers to make it easy to restart the service manually or /// for hive updates. /// </summary> /// <param name="hive">The target hive.</param> /// <param name="steps">The target step list.</param> /// <param name="node">The target hive node.</param> /// <param name="containerName">Identifies the service.</param> /// <param name="image">The Docker image to be used by the container.</param> /// <param name="command">The <c>docker service create ...</c> command.</param> /// <param name="runOptions">Optional run options (defaults to <see cref="RunOptions.FaultOnError"/>).</param> /// <remarks> /// <para> /// This method performs the following steps: /// </para> /// <list type="number"> /// <item> /// Passes <paramref name="image"/> to <see cref="Program.ResolveDockerImage(string)"/> to /// obtain the actual image to be started. /// </item> /// <item> /// Generates the first few lines of the script file that sets the /// default image as the <c>TARGET_IMAGE</c> macro and then overrides /// this with the script parameter (if there is one). We also add /// a Docker command that pulls the image. /// </item> /// <item> /// Appends the commands to the script, replacing any text that matches /// <see cref="ImagePlaceholderArg"/> with <c>${TARGET_IMAGE}</c> to make it easy /// for services to be upgraded later. /// </item> /// <item> /// Starts the service. /// </item> /// <item> /// Uploads the generated script to each hive manager to [<see cref="HiveHostFolders.Scripts"/>/<paramref name="containerName"/>.sh]. /// </item> /// </list> /// </remarks> public static void AddContainerStartSteps(HiveProxy hive, ConfigStepList steps, SshProxy <NodeDefinition> node, string containerName, string image, IBashCommandFormatter command, RunOptions runOptions = RunOptions.FaultOnError) { Covenant.Requires <ArgumentNullException>(hive != null); Covenant.Requires <ArgumentNullException>(steps != null); Covenant.Requires <ArgumentNullException>(!string.IsNullOrWhiteSpace(containerName)); Covenant.Requires <ArgumentNullException>(!string.IsNullOrWhiteSpace(image)); Covenant.Requires <ArgumentNullException>(command != null); // Generate the container start script. var script = CreateStartScript(containerName, image, true, command); // Add steps to upload the script to the managers and then call the script // to create the container on the target node. var scriptPath = LinuxPath.Combine(HiveHostFolders.Scripts, $"{containerName}.sh"); steps.Add(hive.GetFileUploadSteps(node, scriptPath, script, permissions: "740")); steps.Add(CommandStep.CreateIdempotentDocker(node.Name, $"setup/{containerName}", scriptPath)); }
/// <summary> /// Appends the steps required to start a neonHIVE related Docker service and upload /// a script to the hive managers to make it easy to restart the service manually or /// for hive updates. /// </summary> /// <param name="hive">The target hive.</param> /// <param name="steps">The target step list.</param> /// <param name="serviceName">Identifies the service.</param> /// <param name="image">The Docker image to be used by the service.</param> /// <param name="command">The <c>docker service create ...</c> command.</param> /// <param name="runOptions">Optional run options (defaults to <see cref="RunOptions.FaultOnError"/>).</param> /// <remarks> /// <para> /// This method performs the following steps: /// </para> /// <list type="number"> /// <item> /// Passes <paramref name="image"/> to <see cref="Program.ResolveDockerImage(string)"/> to /// obtain the actual image to be started. /// </item> /// <item> /// Generates the first few lines of the script file that sets the /// default image as the <c>TARGET_IMAGE</c> macro and then overrides /// this with the script parameter (if there is one). /// </item> /// <item> /// Appends the commands to the script, replacing any text that matches /// <see cref="ImagePlaceholderArg"/> with <c>${TARGET_IMAGE}</c> to make it easy /// for services to be upgraded later. /// </item> /// <item> /// Starts the service. /// </item> /// <item> /// Uploads the generated script to each hive manager to [<see cref="HiveHostFolders.Scripts"/>/<paramref name="serviceName"/>.sh]. /// </item> /// </list> /// </remarks> public static void AddServiceStartSteps(HiveProxy hive, ConfigStepList steps, string serviceName, string image, IBashCommandFormatter command, RunOptions runOptions = RunOptions.FaultOnError) { Covenant.Requires <ArgumentNullException>(hive != null); Covenant.Requires <ArgumentNullException>(steps != null); Covenant.Requires <ArgumentNullException>(!string.IsNullOrWhiteSpace(serviceName)); Covenant.Requires <ArgumentNullException>(!string.IsNullOrWhiteSpace(image)); Covenant.Requires <ArgumentNullException>(command != null); // Generate the service start script. var script = CreateStartScript(serviceName, image, false, command); // Add steps to upload the script to the managers and then call the script // to create the service on the first manager. var scriptPath = LinuxPath.Combine(HiveHostFolders.Scripts, $"{serviceName}.sh"); steps.Add(hive.GetFileUploadSteps(hive.Managers, scriptPath, script, permissions: "740")); steps.Add(CommandStep.CreateIdempotentDocker(hive.FirstManager.Name, $"setup/{serviceName}", scriptPath)); }
/// <summary> /// Starts a neonHIVE related Docker service and also uploads a script to the /// hive managers to make it easy to restart the service manually or for hive /// updates. /// </summary> /// <param name="hive">The target hive.</param> /// <param name="serviceName">Identifies the service.</param> /// <param name="image">The Docker image to be used by the service.</param> /// <param name="command">The <c>docker service create ...</c> command.</param> /// <param name="runOptions">Optional run options (defaults to <see cref="RunOptions.FaultOnError"/>).</param> /// <remarks> /// <para> /// This method performs the following steps: /// </para> /// <list type="number"> /// <item> /// Passes <paramref name="image"/> to <see cref="Program.ResolveDockerImage(string)"/> to /// obtain the actual image to be started. /// </item> /// <item> /// Generates the first few lines of the script file that sets the /// default image as the <c>TARGET_IMAGE</c> macro and then overrides /// this with the script parameter (if there is one). /// </item> /// <item> /// Appends the commands to the script, replacing any text that matches /// <see cref="ImagePlaceholderArg"/> with <c>${TARGET_IMAGE}</c> to make it easy /// for services to be upgraded later. /// </item> /// <item> /// Starts the service. /// </item> /// <item> /// Uploads the generated script to each hive manager to [<see cref="HiveHostFolders.Scripts"/>/<paramref name="serviceName"/>.sh]. /// </item> /// </list> /// </remarks> public static void StartService(HiveProxy hive, string serviceName, string image, IBashCommandFormatter command, RunOptions runOptions = RunOptions.FaultOnError) { Covenant.Requires <ArgumentNullException>(hive != null); Covenant.Requires <ArgumentNullException>(!string.IsNullOrWhiteSpace(serviceName)); Covenant.Requires <ArgumentNullException>(!string.IsNullOrWhiteSpace(image)); Covenant.Requires <ArgumentNullException>(command != null); var firstManager = hive.FirstManager; firstManager.Status = $"start: {serviceName}"; // Generate the service start script. var script = CreateStartScript(serviceName, image, false, command); // Upload the script to each of the manager nodes and set permissions. var scriptPath = LinuxPath.Combine(HiveHostFolders.Scripts, $"{serviceName}.sh"); foreach (var manager in hive.Managers) { manager.UploadText(scriptPath, script); manager.SudoCommand($"chmod 740 {scriptPath}"); } // Run the script without a parameter on the first manager to start the service. firstManager.IdempotentDockerCommand($"setup/{serviceName}", response => { if (response.ExitCode != 0) { firstManager.Fault(response.ErrorSummary); } }, runOptions, scriptPath); firstManager.Status = string.Empty; }