Search code examples
amazon-web-servicesboto3amazon-ecsaws-ssm

How can I get output from boto3 ecs execute_command?


I have an ECS task running on Fargate on which I want to run a command in boto3 and get back the output. I can do so in the awscli just fine.

➜ aws ecs execute-command --cluster cluster1 \                                                                                   
    --task abc \
    --container container1 \
    --interactive \
    --command 'echo hi'    

The Session Manager plugin was installed successfully. Use the AWS CLI to start a session.

Starting session with SessionId: ecs-execute-command-0f913e47ae7801aeb
hi

Exiting session with sessionId: ecs-execute-command-0f913e47ae7801aeb.

But I cannot sort out how to get the output for the same in boto3.

ecs = boto3.client("ecs")
ssm = boto3.client("ssm")
exec_resp = ecs.execute_command(
    cluster=self.cluster,
    task=self.task,
    container=self.container,
    interactive=True,
    command="echo hi",
)
s_active = ssm.describe_sessions(
    State="Active",
    Filters=[
        {
            "key": "SessionId",
            "value": exec_resp["session"]["sessionId"],
        },
    ],
)
# Here I get the document for the active session.
doc_active = ssm.get_document(Name=s_active["Sessions"][0]["DocumentName"])
# Now I wait for the session to finish.
s_history = {}
done = False
while not done:
    s_history = ssm.describe_sessions(
        State="History",
        Filters=[
            {
                "key": "SessionId",
                "value": exec_resp["session"]["sessionId"],
            },
        ],
    )
    done = len(s_history["Sessions"]) > 0
doc_history = ssm.get_document(Name=s_history["Sessions"][0]["DocumentName"])

Now the session is terminating and I get another document back, but there still doesn't seem to be output anywhere. Has anybody gotten output from this? How?


For anybody arriving seeking a similar solution, I have created a tool for making this task simple. It is called interloper. This is mostly thanks to the excellent answer by Andrey.


Solution

  • Ok, basically by reading the ssm session manager plugin source code I came up with the following simplified reimplementation that is capable of just grabbing the command output: (you need to pip install websocket-client construct)

    import json
    import uuid
    
    import boto3
    import construct as c
    import websocket
    
    ecs = boto3.client("ecs")
    ssm = boto3.client("ssm")
    exec_resp = ecs.execute_command(
        cluster=self.cluster,
        task=self.task,
        container=self.container,
        interactive=True,
        command="ls -la /",
    )
    
    session = exec_resp['session']
    connection = websocket.create_connection(session['streamUrl'])
    try:
        init_payload = {
            "MessageSchemaVersion": "1.0",
            "RequestId": str(uuid.uuid4()),
            "TokenValue": session['tokenValue']
        }
        connection.send(json.dumps(init_payload))
    
        AgentMessageHeader = c.Struct(
            'HeaderLength' / c.Int32ub,
            'MessageType' / c.PaddedString(32, 'ascii'),
        )
    
        AgentMessagePayload = c.Struct(
            'PayloadLength' / c.Int32ub,
            'Payload' / c.PaddedString(c.this.PayloadLength, 'ascii')
        )
    
        while True:
            response = connection.recv()
    
            message = AgentMessageHeader.parse(response)
    
            if 'channel_closed' in message.MessageType:
                raise Exception('Channel closed before command output was received')
    
            if 'output_stream_data' in message.MessageType:
                break
    
    finally:
        connection.close()
    
    payload_message = AgentMessagePayload.parse(response[message.HeaderLength:])
    
    print(payload_message.Payload)