32

I want to be able to ssh into an EC2 instance, and run some shell commands in it, like this.

How do I do it in boto3?

Community
  • 1
  • 1
Dawny33
  • 10,543
  • 21
  • 82
  • 134
  • 1
    Related: [Boto Execute shell command on ec2 instance](https://stackoverflow.com/q/15501845/55075) – kenorb Feb 07 '18 at 16:42
  • What is the difference between: ["client.exec_command()"](https://stackoverflow.com/a/42688515/9033534) & ["client.send_command()"](https://stackoverflow.com/a/45083322/9033534)? – Javi Mar 26 '19 at 10:13
  • 1
    @Javi `exec_command()` is using the Paramiko SSH library, `send_command()` is using the AWS System Manager via boto3. – Jon-Eric May 02 '23 at 19:15

7 Answers7

50

This thread is a bit old, but since I've spent a frustrating afternoon discovering a simple solution, I might as well share it.

NB This is not a strict answer to the OP's question, as it doesn't use ssh. But, one point of boto3 is that you don't have to - so I think in most circumstances this would be the preferred way of achieving the OP's goal, as s/he can use his/her existing boto3 configuration trivially.

AWS' Run Command is built into botocore (so this should apply to both boto and boto3, as far as I know) but disclaimer: I've only tested this with boto3.

def execute_commands_on_linux_instances(client, commands, instance_ids):
    """Runs commands on remote linux instances
    :param client: a boto/boto3 ssm client
    :param commands: a list of strings, each one a command to execute on the instances
    :param instance_ids: a list of instance_id strings, of the instances on which to execute the command
    :return: the response from the send_command function (check the boto3 docs for ssm client.send_command() )
    """

    resp = client.send_command(
        DocumentName="AWS-RunShellScript", # One of AWS' preconfigured documents
        Parameters={'commands': commands},
        InstanceIds=instance_ids,
    )
    return resp

# Example use:
ssm_client = boto3.client('ssm') # Need your credentials here
commands = ['echo "hello world"']
instance_ids = ['an_instance_id_string']
execute_commands_on_linux_instances(ssm_client, commands, instance_ids)

For windows instance powershell commands you'd use an alternative option:

        DocumentName="AWS-RunPowerShellScript",
thclark
  • 4,784
  • 3
  • 39
  • 65
  • 2
    when using the correct instance id, I'm still receiving a client error: ClientError: An error occurred (InvalidInstanceId) when calling the SendCommand operation: What should I do? Please help me, thanks. – Beatrice Lin Jan 02 '18 at 07:08
  • Hi Beatrice, this is most likely to occur when EITHER the instance id is given incorrectly (it needs to be a list of strings, like in the example given above [check it by doing `print(instance_ids)`] OR the instance id you're giving doesn't correspond to a running machine instance [log into your AWS console to check that the machine instance you're specifying is actually running]. Failing those checks, I'd suggest posting a new SO question :) – thclark Jan 02 '18 at 15:26
  • It looks like the error was actually due to IAM permission. I use paramiko instead of it. Thanks for your reply.! – Beatrice Lin Jan 03 '18 at 04:04
  • The other caveat is that the AWS SSM agent needs to be installed, up to date, and the IAM role the instance is running under has the appropriate permissions (add AmazonEC2RoleforSSM policy to role) – Dan G Oct 03 '18 at 14:58
26

You can use the following code snippet to ssh to an EC2 instance and run some command from boto3.

import boto3
import botocore
import paramiko

key = paramiko.RSAKey.from_private_key_file(path/to/mykey.pem)
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())

# Connect/ssh to an instance
try:
    # Here 'ubuntu' is user name and 'instance_ip' is public IP of EC2
    client.connect(hostname=instance_ip, username="ubuntu", pkey=key)

    # Execute a command(cmd) after connecting/ssh to an instance
    stdin, stdout, stderr = client.exec_command(cmd)
    print stdout.read()

    # close the client connection once the job is done
    client.close()
    break

except Exception, e:
    print e
Venkatesh Wadawadagi
  • 2,793
  • 21
  • 34
  • 1
    Yeah, this is the kind of thing. Boto3 doesn't have ssh. Seems to have been dropped in favour of using EC2 Run Command. You can use that to fire off commands. Frankly though, it's a bit of a pain. – freethebees Mar 30 '17 at 09:44
  • The sshclient works fine. But when run commands specific to awscli it fails with the error, 'aws' not found even when it is already installed – Anandhu Ajayakumar Mar 27 '18 at 06:12
  • @AnandhuAjayakumar: https://docs.aws.amazon.com/cli/latest/userguide/troubleshooting.html should help you! – Venkatesh Wadawadagi Mar 27 '18 at 06:21
  • 3
    Is there a reason to `import boto3` or `import botocore` in this code? – Terrence Brannon Dec 11 '18 at 22:37
  • 1
    I got a AttributeError: 'NoneType' object has no attribute 'time' paramiko error that was resolved with a time.sleep(5) after the exec_command call per this question: https://stackoverflow.com/questions/60037299/attributeerror-nonetype-object-has-no-attribute-time-paramiko - otherwise this worked great, thank you – soheildb Feb 25 '21 at 05:43
5

Here is how I have done

import boto3
import botocore
import boto
import paramiko

ec2 = boto3.resource('ec2')

instances = ec2.instances.filter(
    Filters=[{'Name': 'instance-state-name', 'Values': ['running']}])
i = 0
for instance in instances:
    print(instance.id, instance.instance_type)
    i+= 1
x = int(input("Enter your choice: "))
try:
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    privkey = paramiko.RSAKey.from_private_key_file('address to .pem key')
    ssh.connect(instance.public_dns_name,username='ec2-user',pkey=privkey)
    stdin, stdout, stderr = ssh.exec_command('python input_x.py')
    stdin.flush()
    data = stdout.read().splitlines()
    for line in data:
        x = line.decode()
        #print(line.decode())
        print(x,i)
        ssh.close()

For the credentails, I have added AWSCLI package, then in the terminal run

aws configure

enter the credentials. All of them will be saved in .aws folder, u can change the path too.

3

You can also use kitten python library for that which is just a wrapper around boto3. You can also run same command on multiple servers at the same time using this utility.

For Example.

kitten run uptime ubuntu 18.105.107.20
1

Boto provided a way to SSH into EC2 instances programmatically using Paramiko and then run commands. Boto3 does not include this functionality. You could probably modify the boto code to work with boto3 without a huge amount of effort. Or you could look into using something like fabric or ansible which provide a much more powerful way to remotely execute commands on EC2 instances.

garnaat
  • 44,310
  • 7
  • 123
  • 103
  • Boto3 removed that functionality because it no longer needed it to achieve the OP's goal - via AWS' Run Command system. See my answer for an example :) – thclark Jul 13 '17 at 14:19
1

use boto3 to discover instances and fabric to run commands on the instances

James Soubry
  • 1,142
  • 8
  • 4
0

You don't SSH from python. You can use boto3 module to interact with the EC2 instance.

Here you have a complete documentation of boto3 and what commands you can run with it.

Carles Mitjans
  • 4,786
  • 3
  • 19
  • 38
  • But, with `boto`, one can ssh into an instance, right? So, does it mean that the particular module is not migrated into boto3? – Dawny33 Mar 07 '17 at 10:04
  • 1
    You don't SSH into the instances. You just connect to EC2 service, and from there you can start/stop and interact with the different instances, but never SSH into them. For that you don' need `boto3` at all. You can simply SSH from your terminal. If you still want to execute commands using python over SSH, you can take a look at [this](http://stackoverflow.com/questions/3586106/perform-commands-over-ssh-with-python) question. – Carles Mitjans Mar 07 '17 at 10:11