Martin Paul Eve bio photo

Martin Paul Eve

Professor of Literature, Technology and Publishing at Birkbeck, University of London and Technical Lead of Knowledge Commons at MESH Research, Michigan State University

Email (BBK) Email (MSU) Email (Personal) Books Bluesky Github Stackoverflow KC Works Institutional Repo Hypothes.is ORCID ID  ORCID iD Wikipedia Pictures for Re-Use

LocalStack is a great cloud emulation layer. It lets you simulate interaction with AWS, which is great for writing integration tests.

However, I wanted a system that, when run locally, would spin up the LocalStack server and then destroy it when done. But when running the test on GitLab CI, it will use the “service” provision of their continuous integration system and connect to that.

To do this, first you need to setup your .gitlab-ci.yml file:

run_tests:
    stage: test
    artifacts:
        when: always
        paths:
            - allure_results
        expire_in: 1 day
    image: 
      name: python:3.9
      entrypoint: ["/usr/bin/env"]
    variables:
      HOSTNAME_EXTERNAL: localstack
      DEFAULT_REGION: us-east-1
      AWS_ACCESS_KEY_ID: localkey
      AWS_SECRET_ACCESS_KEY: localsecret
    services:
      - name: localstack/localstack
        alias: localstack
    before_script:
        - pip install pytest pytest-mock pytest-asyncio allure-pytest
        - pip install -r $CI_PROJECT_DIR/crapiproxy/layer-python/requirements.txt
    script:
        - cd $CI_PROJECT_DIR/crapiproxy/tests
        - python -m pytest -v --alluredir=$CI_PROJECT_DIR/allure_results
        - cd -
        - ls
    allow_failure: true

To spin up the docker container when running locally, we need the pytest-localstack module, so add that to your requirements.

Then, to achieve the magic, I have a pytest fixture that looks like this:

@pytest.fixture()
def set_up_s3(self):
    sys.path.append("../../")
    sys.path.append("../src")
    sys.path.append("../src/plugins")

    from plugins.utils import aws_utils
    import settings

    # first check if localstack is running
    if not os.environ.get("GITLAB_CI"):
        import docker

        client = docker.from_env()

        with LocalstackSession(
            services=["s3"], docker_client=client
        ) as session:
            self._aws_connector = aws_utils.AWSConnector(
                bucket=settings.BUCKET,
                endpoint_url=session.endpoint_url("s3"),
            )
            res = self.stage_two_setup()
            yield res
    else:
        # change the endpoint URL to use GitLab CLI version
        self._aws_connector = aws_utils.AWSConnector(
            bucket=settings.BUCKET,
            endpoint_url="http://localstack:4566",
        )

        res = self.stage_two_setup()
        yield res

Essentially, we check for the existence of the GITLAB_CI variable to ascertain where we are running. When we are running on GitLab CI, we set the endpoint_url for all of our AWS calls to http://localstack:4566. (AWSConnector is a custom class that I wrote to handle this.)

However, if we are running locally, we start a LocalStack session. You have to do this this way, using the context manager, rather than the pytest fixture, because the fixture version runs automatically regardless of the location.