Skip to content
This repository was archived by the owner on Jan 31, 2023. It is now read-only.

Commit 01c722e

Browse files
Katherine BlackKatherine Black
authored andcommitted
Put inspection results in an S3 bucket.
1 parent d10e206 commit 01c722e

File tree

5 files changed

+95
-116
lines changed

5 files changed

+95
-116
lines changed

README.md

Lines changed: 7 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -57,23 +57,11 @@ Finally, if you need to install a dev only dependency, use:
5757

5858
### Running
5959

60-
Before running, you must have set and exported the following environment variables so houndigrade can talk to Amazon SQS to share its results:
60+
Before running, you must have set and exported the following environment variables so houndigrade can talk to Amazon S3 to share its results:
6161

62-
- `QUEUE_CONNECTION_URL`
63-
- `AWS_SQS_QUEUE_NAME_PREFIX`
62+
- `RESULTS_BUCKET_NAME`
6463

65-
`AWS_SQS_QUEUE_NAME_PREFIX` should match what you use when running cloudigrade, and that is probably `${USER}-`.
66-
67-
`QUEUE_CONNECTION_URL` must be a well-formed SQS URL that includes your Amazon SQS access key and secret key. Many Amazon keys have URL-unfriendly characters. You may want to use a small helper script like this to generate a valid URL:
68-
69-
```python
70-
from os import environ
71-
from urllib.parse import quote
72-
print('sqs://{}:{}@'.format(
73-
quote(environ['AWS_SQS_ACCESS_KEY_ID'], safe=''),
74-
quote(environ['AWS_SQS_SECRET_ACCESS_KEY'], safe='')
75-
))
76-
```
64+
`RESULTS_BUCKET_NAME` should match the bucket name in which you want your results, the rest of the credentials are gathered from the environment.
7765

7866
To run houndigrade locally against minimal test disk images, follow these steps:
7967

@@ -131,34 +119,19 @@ If you wish to run a higher-level suite of integration tests, see
131119
132120
### Manually running in AWS
133121
134-
If you want to manually run houndigrade in AWS so that you can watch its output in real-time, you can *simulate* how the cloudigrade ECS task runs houndigrade by SSH-ing to an EC2 instance (running an ECS AMI) and running Docker with the arguments that would be used in the ECS task definition. For example:
122+
If you want to manually run houndigrade in AWS so that you can watch its output in real-time, you can *simulate* how the cloudigrade CloudInit task runs houndigrade by SSH-ing to an EC2 instance (running an ECS AMI) and running Docker with the arguments that would be used in the CloudInit task definition. For example:
135123
136124
docker run \
137125
--mount type=bind,source=/dev,target=/dev \
138126
--privileged --rm -i -t \
139-
-e AWS_ACCESS_KEY_ID=AWS_SQS_ACCESS_KEY_ID \
140-
-e AWS_DEFAULT_REGION="us-east-1" \
141-
-e AWS_SECRET_ACCESS_KEY="AWS_SQS_SECRET_ACCESS_KEY" \
142-
-e EXCHANGE_NAME="" \
143-
-e QUEUE_CONNECTION_URL="sqs://AWS_SQS_ACCESS_KEY_ID:AWS_SQS_SECRET_ACCESS_KEY@" \
144-
-e RESULTS_QUEUE_NAME="HOUNDIGRADE_RESULTS_QUEUE_NAME" \
127+
-e RESULTS_BUCKET_NAME=RESULTS_BUCKET_NAME \
145128
--name houndi \
146129
"registry.gitlab.com/cloudigrade/houndigrade:latest" \
147130
-c aws \
148-
-t ami-13469000000000000 /dev/sdf \
149-
-t ami-12345678900000000 /dev/sdg
131+
-t ami-13469000000000000 /dev/sdf
150132
151133
You will need to set appropriate values for the `-e` variables passed into the environment, each of the `-t` arguments that define the inspection targets, and the specific version of the houndigrade image you wish to use. When you attach volumes in AWS, you can define the device paths they'll use, and they should match your target arguments here. Alternatively, you can describe the running EC2 instance to get the device paths.
152134
153135
# Releasing Houndigrade
154136
155-
Releasing houndigrade is a simple process of tagging a new version in GitHub.
156-
157-
1. Navigate to the [releases page](https://github.com/cloudigrade/houndigrade/releases)
158-
2. Draft a new release
159-
3. Check the [Pull Requests page](https://github.com/cloudigrade/houndigrade/pulls) to see all the new changes since the last release
160-
4. For Tag Version we use [Semantic Versioning](https://semver.org/)
161-
5. For the main release body, please include merged PRs that will be part of this release, ideally linking to the PR itself.
162-
6. Press button, receive release. The [tag github actions workflow](https://github.com/cloudigrade/houndigrade/blob/master/.github/workflows/tag.yml) will test, build, tag, and get the image copied to quay.
163-
164-
You will find your image in both the [Github Container Registry](https://github.com/orgs/cloudigrade/packages/container/package/houndigrade) and in [Quay.io](https://quay.io/repository/cloudservices/houndigrade)
137+
Please refer to the [wiki](https://github.com/cloudigrade/houndigrade/wiki/Releasing-Houndigrade).

docker-compose.yml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,7 @@ services:
55
build: .
66
entrypoint: ./entrypoint.sh
77
environment:
8-
- AWS_DEFAULT_REGION
9-
- AWS_ACCESS_KEY_ID
10-
- AWS_SECRET_ACCESS_KEY
11-
- EXCHANGE_NAME
12-
- QUEUE_CONNECTION_URL
13-
- RESULTS_QUEUE_NAME=${AWS_SQS_QUEUE_NAME_PREFIX}inspection_results
8+
- RESULTS_BUCKET_NAME=${CLOUDIGRADE_ENVIRONMENT}-cloudigrade-inspections
149
volumes:
1510
- ./docker/dev-entrypoint.sh:/opt/houndigrade/entrypoint.sh
1611
- ./test-data:/test-data:ro

houndigrade/cli.py

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,17 @@
66
import os
77
import subprocess
88
import sys
9+
from base64 import b64encode
910
from contextlib import contextmanager
11+
from datetime import datetime
1012
from gettext import gettext as _
13+
from hashlib import md5
14+
from uuid import uuid4
1115

1216
import boto3
1317
import click
1418
import jsonpickle
1519
import sh
16-
from botocore.exceptions import ClientError
1720
from sentry_sdk import init
1821

1922
INSPECT_PATH = "/mnt/inspect"
@@ -322,46 +325,42 @@ def mount(partition, inspect_path):
322325
click.echo(_("UnMounting result {}.").format(unmount_result.exit_code))
323326

324327

325-
def _get_sqs_queue_url(queue_name):
328+
def generate_results_key():
326329
"""
327-
Get the SQS queue URL for the given queue name.
328-
329-
This has the side-effect on ensuring that the queue exists.
330-
331-
Note: This function was copied verbatim from `cloudigrade`.
332-
333-
FIXME: Move this function to a shared library.
334-
335-
Args:
336-
queue_name (str): the name of the target SQS queue
330+
Generate the key at which the results object will be placed.
337331
338332
Returns:
339-
str: the queue's URL.
333+
(str): String representation of the object key.
340334
341335
"""
342-
sqs = boto3.client("sqs")
343-
try:
344-
return sqs.get_queue_url(QueueName=queue_name)["QueueUrl"]
345-
except ClientError as e:
346-
if e.response["Error"]["Code"].endswith(".NonExistentQueue"):
347-
return sqs.create_queue(QueueName=queue_name)["QueueUrl"]
348-
raise
336+
now = datetime.now()
337+
time_path = now.strftime("%Y-%m/%d/%H.%M.%S")
338+
339+
return f"InspectionResults/{time_path}-{uuid4()}.json"
349340

350341

351342
def report_results(results):
352343
"""
353-
Places the results on a queue.
344+
Places results in the bucket.
354345
355346
Args:
356-
results (dict): The results of the finished inspection.
347+
results (s3.Object): Object representing the results stored in our S3 bucket.
357348
358349
"""
359-
message_body = jsonpickle.encode(results)
360-
queue_name = os.getenv("RESULTS_QUEUE_NAME")
361-
queue_url = _get_sqs_queue_url(queue_name)
350+
json_results = jsonpickle.encode(results)
351+
encoded_results = json_results.encode()
352+
results_md5 = b64encode(md5(encoded_results).digest()).decode()
353+
354+
bucket_name = os.getenv("RESULTS_BUCKET_NAME")
355+
356+
s3 = boto3.resource("s3")
357+
bucket = s3.Bucket(bucket_name)
358+
359+
results = bucket.put_object(
360+
Body=encoded_results, ContentMD5=results_md5, Key=generate_results_key()
361+
)
362362

363-
sqs = boto3.client("sqs")
364-
sqs.send_message(QueueUrl=queue_url, MessageBody=message_body)
363+
return results
365364

366365

367366
def check_for_rhel_certs(partition, results):

houndigrade/tests/test_get_sqs_queue_url.py

Lines changed: 0 additions & 49 deletions
This file was deleted.
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""Collection of tests for ``cli.report_results`` function."""
2+
from datetime import datetime
3+
from unittest import TestCase
4+
from unittest.mock import patch
5+
from uuid import uuid4
6+
7+
from cli import generate_results_key, report_results
8+
9+
10+
class TestGenerateResultsKey(TestCase):
11+
"""Test suite for houndigrade CLI's "generate_results_key" function."""
12+
13+
def test_generate_results_key(self):
14+
"""Test generating the key."""
15+
test_now = datetime.now()
16+
test_uuid = uuid4()
17+
test_time_path = test_now.strftime("%Y-%m/%d/%H.%M.%S")
18+
19+
test_key = f"InspectionResults/{test_time_path}-{test_uuid}.json"
20+
21+
with patch("cli.datetime") as mock_datetime, patch("cli.uuid4") as mock_uuid4:
22+
mock_datetime.now.return_value = test_now
23+
mock_uuid4.return_value = test_uuid
24+
25+
result_key = generate_results_key()
26+
27+
self.assertEqual(test_key, result_key)
28+
29+
@patch("cli.boto3")
30+
@patch("cli.b64encode")
31+
@patch("cli.md5")
32+
@patch("cli.jsonpickle")
33+
def test_report_results(
34+
self, mock_jsonpickle, mock_md5, mock_b64encode, mock_boto3
35+
):
36+
"""Verify we correctly report results."""
37+
mock_results = {"test": "results"}
38+
mock_results_bucket_name = "TestBucket"
39+
mock_json_encode = mock_jsonpickle.encode
40+
mock_utf_json = mock_json_encode.return_value.encode
41+
mock_md5_digest = mock_md5.return_value.digest
42+
mock_b64_decode = mock_b64encode.return_value.decode
43+
mock_resource = mock_boto3.resource
44+
mock_bucket = mock_resource.return_value.Bucket
45+
mock_put_object = mock_bucket.return_value.put_object
46+
47+
with patch.dict(
48+
"os.environ", {"RESULTS_BUCKET_NAME": mock_results_bucket_name}
49+
):
50+
report_results(mock_results)
51+
52+
mock_json_encode.assert_called_once_with(mock_results)
53+
mock_utf_json.assert_called_once()
54+
mock_md5.assert_called_once_with(mock_utf_json.return_value)
55+
mock_md5_digest.assert_called_once()
56+
mock_b64encode.assert_called_once_with(mock_md5_digest.return_value)
57+
mock_b64_decode.assert_called_once()
58+
59+
mock_resource.assert_called_once_with("s3")
60+
mock_bucket.assert_called_once_with(mock_results_bucket_name)
61+
mock_put_object.assert_called_once()

0 commit comments

Comments
 (0)