Skip to content

Mastodon Integration Revisited

Back in February, I wrote about a simple python script that would auto-post on mastodon when I published a new article on this blog and then would use that post's replies as the comment thread for the article.

Of course, right after that I posted one more brief article and then went blog silent, using my mastodon for bite sized updates instead. But that's another story.

Good bye, Lambda

The other day, I was checking on my AWS bill and decided I needed to make a change to the integration. At the time, I'd chosen AWS lambda fronted by AWS API Gateway. My blog being low traffic, the calls to lambda were essentially free, however that last little thing I had to do go get things working, creating a NAT, was costing me more than my other ECS services combined. The NAT is always on, so i'm paying hourly costs 24/7, same with the elastic IP that is attached to it.

Hello, ECS

I already run a bunch of small sites as AWS ECS services and love it. Each is just a docker container I work on locally, push to ECR and the deploy via ECS, which does a rolling deployment for me. And the backend is Fargate, so i'm only paying for the times the sites are serving traffic.

The original code expected a Lambda event payload, which captured the HTTP request as JSON and I sent back a response as another Lambda event payload. While I usually develop my python webapps in Django, I wanted to keep it as close to and simple as the original, so I chose Flask.

With this, the entrypoint lambda_handler became a Flask route:

@app.route('/')
def postblog():
    url = None
    try:
        url = request.args.get('url')
        if not url:
            raise BadRequest("No `url` query argument provided")
        if not BLOG_POST_RE.match(url):
            logger.debug(f"url `{url}` is not handled")
            raise BadRequest("Provided `url` is not a handled")
        logger.debug(f"invoked for {url}")
        toot_id = get_toot_id(url)
        logger.info(f"resolved {url} to toot_id {toot_id}")
        return (
            dict(host=MASTODON_HOST,
                 user=MASTODON_USER,
                 toot_id=str(toot_id)),
            200,
            {'Access-Control-Allow-Origin': '*'}
        )
    except BadRequest as e:
        return (
            dict(code=400, error=e.args[0]),
            400,
            {'Access-Control-Allow-Origin': '*'}
        )
    except Exception:
        logger.exception(f"request for {url} failed")
        raise

The app itself is initialized with:

app = Flask(__name__)

And the whole thing is run via gunicorn:

/opt/venv/bin/gunicorn postblog.app  --bind "0.0.0.0:8000"

The only other addition was a status endpoint that serves as the health-check that ECS requires:

@app.route('/status')
def status():
    return dict(code=200, message='OK', rev=os.environ['VERSION'])