1

I have a private Azure Linux VM means it can be accessed only from jumpbox(access) vm. I need to deploy a script to this private VM. As this VM cannot even access any storage account/repo, I can't use Custom Script Extension for script deployment. So I thought of deploying the script using az vm run-command invoke by converting the SomeScript.sh to strings and echo it to the virtual machine. Different pieces of my code as below:

SomeScript.sh
#!/bin/bash

#
# CommandToExecute: ./SomeScript.sh ${CUST_NO}
#
#some more code

Function that converts the .sh file to strings:

function getCommandToExecute()
{
    local scriptName=$1
    local commandToExecute
    local currentLocation=$(dirname "$0")
    local scriptFullPath="$currentLocation/Environment/VmScripts/$scriptName"
    mapfile < $scriptFullPath
    printf -v escapedContents "%q\n" "${MAPFILE[@]}"
    commandToExecute+="echo "$escapedContents" > /usr/myapps/$scriptName"
    echo "$commandToExecute"
}

vm run command:

az vm run-command invoke -g $resourceGroupName \
-n $vmName --command-id RunShellScript \
--scripts "#!/bin/bash\n ${commandToExecute}"

If I use the "#!/bin/bash\n ${commandToExecute}" part (commandToExecute replaced with string scripts) in the RunCommand window in azure portal, the script works fine, but I can't make it work via run command due to this exception: \n[stdout]\n\n[stderr]\n/bin/sh: 1: /var/lib/waagent/run-command/download/133/script.sh: not found\n"

Any idea what is missing here? Or if there is a better alternative to handle this.

3
  • The documentation of az vm run-command invoke --scripts states Use @{file} to load script from a file. It is unclear whether file has to be on the client side or server side. Could you give it a shot and report the results? az vm run-command invoke ... --scripts '@{scriptFileOnYourLocalMachine}'. Commented Jun 12, 2020 at 17:33
  • @Socowi I think @{scriptFileOnYourLocalMachine} is for runnning a script on vm, but I am looking for deploying the SomeScript.sh on the VM so that other authorized users execute commands like SomeScript.sh ${CUST_NO} on same vm. Commented Jun 13, 2020 at 7:31
  • Thank you for ruling out possible misunderstandings. I should have pointed out that I wanted to create a solution that does @{deployMyScript.sh} and not @{scriptToBeDeployed.sh}. I just needed to know whether @{file} references files from the local host or files from the remote host. The name (any)scriptFileOnYourLocalMachine was a bit misleading, sorry for the confusion. Commented Jun 13, 2020 at 15:25

2 Answers 2

1

I think quoting the whole script and its deployment script for use with --scripts is a lot of work and error prone too. Luckily, there are easier alternatives for both quoting steps. The documentation of az vm run-command invoke --scripts states

Use @{file} to load script from a file.

Therefore, you can do

deploymentScript() {
  echo   '#! /usr/bin/env bash'
  printf 'tail -n+4 "${BASH_SOURCE[0]}" > %q\n' "$2"
  echo   'exit'
  cat "$1"
}
deploymentScript local.sh remote.sh > tmpDeploy.sh
az vm run-command invoke ... --scripts '@{tmpDeploy.sh}'
rm tmpDeploy.sh

Replace local.sh with the path of the local script you want to deploy and replace remote.sh with the remote path where the script should be deployed.

If you are lucky, then you might not even need tmpDeploy.sh. Try

az vm ... --scripts "@{<(deploymentScript local.sh remote.sh)}"

Some notes on the implementation:

  • The deployed script is an exact copy of your script file. Even embedded binary data is kept. The trick with tail $BASH_SOURCE is inspired by this answer.

  • The script can be arbitrarily long. Even huge scripts won't run into the error Argument list too long imposed by getconf ARG_MAX.

Sign up to request clarification or add additional context in comments.

1 Comment

That worked !!! :). I didn't use tmpDeploy.sh, so I changed the the syntax to --scripts "$(deploymentScript $scriptFullPath /usr/local/bin/$scriptName)" as @ is for .sh file. Thank you again.
0

There was issue with escaped string I mentioned above. After correcting the code, it all good now. My final code:

function getCommandToExecute()
{
    local scriptName=$1
    local commandToExecute
    local currentLocation=$(dirname "$0")
    local scriptFullPath="$currentLocation/Environment/VmScripts/$scriptName"
    local singleStringCommand=""

    mapfile -t < $scriptFullPath
    for line in "${MAPFILE[@]}"; 
    do 
        singleStringCommand+="$(printf '%s' "$line" | sed 's/[\"$]/\\&/g')"
        singleStringCommand+="\n"
    done
    commandToExecute+="echo "\"$singleStringCommand\"" > /usr/local/bin/$scriptName;"
    echo "$commandToExecute" 
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.