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.

John
  • 351
  • 5
  • 18
  • The [documentation of az vm run-command invoke --scripts](https://learn.microsoft.com/en-us/cli/azure/vm/run-command?view=azure-cli-latest#az-vm-run-command-invoke) 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}'`. – Socowi Jun 12 '20 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. – John Jun 13 '20 at 07:31
  • Thank you for ruling out possible misunderstandings. I should have pointed out that I wanted to create a solution that does [`@{deployMyScript.sh}`](https://stackoverflow.com/a/62361755/6770384) 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. – Socowi Jun 13 '20 at 15:25

2 Answers2

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.

Socowi
  • 25,550
  • 3
  • 32
  • 54
  • 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. – John Jun 14 '20 at 16:19
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" 
}
John
  • 351
  • 5
  • 18