Example #1
0
        /// <summary>
        /// Constructs a configuration step that executes a command under <b>sudo</b>
        /// on a specific Docker node.
        /// </summary>
        /// <param name="nodeName">The Docker node name.</param>
        /// <param name="command">The Linux command.</param>
        /// <param name="args">The command arguments.</param>
        /// <remarks>
        /// <note>
        /// You can add <see cref="CommandBundle.ArgBreak"/> as one of the arguments.  This is
        /// a meta argument that indicates that the following non-command line option
        /// is not to be considered to be the value for the previous command line option.
        /// This is a formatting hint for <see cref="ToBash(string)"/> and will
        /// not be included in the command itself.
        /// </note>
        /// </remarks>
        private CommandStep(string nodeName, string command, params object[] args)
        {
            Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(nodeName));
            Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(command));
            Covenant.Requires <ArgumentNullException>(args != null);

            this.nodeName      = nodeName;
            this.commandBundle = new CommandBundle(command, args);
        }
Example #2
0
        /// <summary>
        /// Ensures that the Docker <b>config.json</b> file for the node's root
        /// user matches that for the sysadmin user.
        /// </summary>
        private void SyncDockerConf(SshProxy <NodeDefinition> node)
        {
            // We also need to manage the login for the [root] account due
            // to issue
            //
            //      https://github.com/jefflill/NeonForge/issues/265

            // $hack(jeff.lill):
            //
            // We're simply going ensure that the [/root/.docker/config.json]
            // file matches the equivalent file for the node sysadmin account,
            // removing the root file if this was deleted for sysadmin.
            //
            // This is a bit of a hack because it assumes that the Docker config
            // for the root and sysadmin account never diverge, which is probably
            // a reasonable assumption given that these are managed hosts.
            //
            // We're also going to ensure that these directories and files have the
            // correct owners and permissions.

            var bundle = new CommandBundle("./sync.sh");

            bundle.AddFile("sync.sh",
                           $@"#!/bin/bash

if [ ! -d /root/.docker ] ; then
    mkdir -p /root/.docker
fi

if [ -f /home/{node.Username}/.docker/config.json ] ; then
    cp /home/{node.Username}/.docker/config.json /root/.docker/config.json
else
    if [ -f /root/.docker/config.json ] ; then
        rm /root/.docker/config.json
    fi
fi

if [ -d /root/.docker ] ; then
    chown -R root:root /root/.docker
    chmod 660 /root/.docker/*
fi

if [ -d /home/{node.Username}/.docker ] ; then
    chown -R {node.Username}:{node.Username} /home/{node.Username}/.docker
    chmod 660 /home/{node.Username}/.docker/*
fi
",
                           isExecutable: true);

            var response = node.SudoCommand(bundle);

            if (response.ExitCode != 0)
            {
                throw new HiveException(response.ErrorSummary);
            }
        }
Example #3
0
        /// <summary>
        /// Creates or updates a hive Docker binary config.
        /// </summary>
        /// <param name="configName">The config name.</param>
        /// <param name="value">The config value.</param>
        /// <param name="options">Optional command run options.</param>
        /// <exception cref="HiveException">Thrown if the operation failed.</exception>
        public void Set(string configName, byte[] value, RunOptions options = RunOptions.None)
        {
            Covenant.Requires <ArgumentException>(HiveDefinition.IsValidName(configName));
            Covenant.Requires <ArgumentNullException>(value != null);

            var bundle = new CommandBundle("./create-config.sh");

            bundle.AddFile("config.dat", value);
            bundle.AddFile("create-config.sh",
                           $@"#!/bin/bash

if docker config inspect {configName} ; then
    echo ""Config already exists; not setting it again.""
else
    cat config.dat | docker config create {configName} -

    # It appears that Docker configs may not be available
    # immediately after they are created.  So, we're going 
    # poll for a while until we can inspect the new config.

    count=0

    while [ $count -le 30 ]
    do
        if docker config inspect {configName} ; then
            exit 0
        fi
        
        sleep 1
        count=$(( count + 1 ))
    done

    echo ""Created config [{configName}] is not ready after 30 seconds."" >&2
    exit 1
fi
",
                           isExecutable: true);

            var response = hive.GetReachableManager().SudoCommand(bundle, options);

            if (response.ExitCode != 0)
            {
                throw new HiveException(response.ErrorSummary);
            }
        }
Example #4
0
        /// <inheritdoc/>
        public override string ToString()
        {
            var sb = new StringBuilder();

            sb.Append($"{commandBundle.Command}");

            foreach (var arg in CommandBundle.NormalizeArgs(commandBundle.Args))
            {
                sb.AppendWithSeparator(arg);
            }

            if (commandBundle.Count > 0)
            {
                sb.Append($" [files={commandBundle.Count}]");
            }

            return(sb.ToString());
        }
Example #5
0
        /// <summary>
        /// Removes then local Docker registry from the hive.
        /// </summary>
        /// <param name="progress">Optional action that will be called with a progress message.</param>
        /// <exception cref="HiveException">Thrown if no registry is deployed or there was an error removing it.</exception>
        public void PruneLocalRegistry(Action <string> progress = null)
        {
            // We're going to upload a script to one of the managers that handles
            // putting the [neon-registry] service into READ-ONLY mode, running
            // the garbage collection container and then restoring [neon-registry]
            // to READ/WRITE mode.
            //
            // The nice thing about this is that the operation will continue to
            // completion on the manager node even if we lose the SSH connection.

            var manager      = hive.GetReachableManager();
            var updateScript =
                @"#!/bin/bash
# Update [neon-registry] to READ-ONLY mode:

docker service update --env-rm READ_ONLY --env-add READ_ONLY=true neon-registry

# Prune the registry:

docker run \
   --name neon-registry-prune \
   --restart-condition=none \
   --mount type=volume,src=neon-registry,volume-driver=neon,dst=/var/lib/neon-registry \
   nhive/neon-registry garbage-collect

# Restore [neon-registry] to READ/WRITE mode:

docker service update --env-rm READ_ONLY --env-add READ_ONLY=false neon-registry
";
            var bundle = new CommandBundle("./collect.sh");

            bundle.AddFile("collect.sh", updateScript, isExecutable: true);

            progress?.Invoke("Registry prune started.");

            var pruneResponse = manager.SudoCommand(bundle, RunOptions.None);

            if (pruneResponse.ExitCode != 0)
            {
                throw new HiveException($"The prune operation failed.  The registry may be running in READ-ONLY mode: {pruneResponse.ErrorText}");
            }

            progress?.Invoke("Registry prune completed.");
        }
Example #6
0
        /// <summary>
        /// Sets a Vault access control policy.
        /// </summary>
        /// <param name="policy">The policy.</param>
        /// <returns>The command response.</returns>
        public CommandResponse SetPolicy(VaultPolicy policy)
        {
            Covenant.Requires <ArgumentNullException>(policy != null);

            VerifyToken();

            var bundle = new CommandBundle("./create-vault-policy.sh");

            bundle.AddFile("create-vault-policy.sh",
                           $@"#!/bin/bash
export VAULT_TOKEN={hive.HiveLogin.VaultCredentials.RootToken}
vault policy-write {policy.Name} policy.hcl
",
                           isExecutable: true);

            bundle.AddFile("policy.hcl", policy);

            var response = hive.GetReachableManager().SudoCommand(bundle, hive.SecureRunOptions | RunOptions.FaultOnError);

            response.BashCommand = bundle.ToBash();

            return(response);
        }
Example #7
0
        /// <summary>
        /// Executes a command on a specific hive manager node using the root Vault token.
        /// </summary>
        /// <param name="manager">The target manager.</param>
        /// <param name="command">The command (including the <b>vault</b>).</param>
        /// <param name="args">The optional arguments.</param>
        /// <returns>The command response.</returns>
        /// <remarks>
        /// <note>
        /// This method does not fault or throw an exception if the command returns
        /// a non-zero exit code.
        /// </note>
        /// </remarks>
        public CommandResponse CommandNoFault(SshProxy <NodeDefinition> manager, string command, params object[] args)
        {
            Covenant.Requires <ArgumentNullException>(manager != null);
            Covenant.Requires <ArgumentNullException>(command != null);

            VerifyToken();

            var scriptBundle = new CommandBundle(command, args);
            var bundle       = new CommandBundle("./vault-command.sh");

            bundle.AddFile("vault-command.sh",
                           $@"#!/bin/bash
export VAULT_TOKEN={hive.HiveLogin.VaultCredentials.RootToken}
{scriptBundle}
",
                           isExecutable: true);

            var response = manager.SudoCommand(bundle, hive.SecureRunOptions);

            response.BashCommand = bundle.ToBash();

            return(response);
        }
Example #8
0
        /// <summary>
        /// Deletes a hive Docker config.
        /// </summary>
        /// <param name="configName">The config name.</param>
        /// <param name="options">Optional command run options.</param>
        /// <exception cref="HiveException">Thrown if the operation failed.</exception>
        public void Remove(string configName, RunOptions options = RunOptions.None)
        {
            Covenant.Requires <ArgumentException>(HiveDefinition.IsValidName(configName));

            var bundle = new CommandBundle("./delete-config.sh");

            bundle.AddFile("delete-config.sh",
                           $@"#!/bin/bash
docker config inspect {configName}

if [ ""$?"" != ""0"" ] ; then
    echo ""Config doesn't exist.""
else
    docker config rm {configName}
fi
", isExecutable: true);

            var response = hive.GetReachableManager().SudoCommand(bundle, RunOptions.None);

            if (response.ExitCode != 0)
            {
                throw new HiveException(response.ErrorSummary);
            }
        }