Reacting to events raised by Octopus

Ryan Rousseau - Apr 18 '20 - - Dev Community

Song recommendation of the day

This song played while I was testing the function. I love the original, and this is a well-done cover.

The plan

In the last post, I cut my teeth on the Twilio API. I successfully sent a text message and even got a shiny, new Octopus Step Template out of it.

Today, I set up a Firebase cloud function that accepts an event from Octopus when a deployment requires approval.

Configuring Firebase

Firebase is an application development platform with a large number of offerings. Firebase can host your application and database. It also provides features like authentication and A/B testing.

I followed the Getting Started guide for Cloud Functions. These steps included creating a new project, updating the Firebase CLI on my machine, and calling firebase init to configure the project locally.

I initialized the project with only the functions module for now.

Adding my settings to the Firebase configuration

You can set environment configuration settings to your application with the Firebase CLI.

I set up a few settings that I need in my function. Real values have been replaced to protect the innocent me.

firebase functions:config:set octwilio.octopus.token="This is a token provided by Octopus"
firebase functions:config:set octwilio.twilio.account_sid="The account ID from Twilio"
firebase functions:config:set octwilio.twilio.auth_token="The auth token from Twilio"
firebase functions:config:set octwilio.twilio.approval.from_number="+15555555555"
firebase functions:config:set octwilio.twilio.approval.to_number="+15555555555"

Coding the function

I based this function on one I wrote for adding Slack notifications for all production deployments.

Let us start with our cloud function definition. It is a function triggered by an HTTPS request. It is named approvalRaised. I can activate it with a request to https://us-central1-octwilio.cloudfunctions.net/approvalRaised.

exports.approvalRaised = functions.https.onRequest((req, res) => {
    return authorizeRequest(req, res)
        .then(getPayload)
        .then(sendTwilioMessage)
        .then(() => { return res.status(200).send(); });
});

It takes the request and passes it through a pipeline of functions.

The first one, authorizeRequest, checks for a secret token in the header. The function returns an unauthorized response if the token is incorrect or missing.

function authorizeRequest(req) {
    const providedToken = req.get('octwilio-token');
    const token = functions.config().octwilio.octopus.token;

    if (!providedToken || providedToken !== token) {
        return Promise.reject({
            code: 401,
            message: 'Missing or invalid token'
        });
    }

    return Promise.resolve(req);
}

The getPayload function checks for the subscription payload from Octopus and passes it to the next function. The function returns a bad request response if the payload is missing.

function getPayload(req) {
    const payload = req.body.Payload;

    if (payload) {
        console.log(payload);
        return Promise.resolve(payload);
    }

    return Promise.reject({
        code: 400,
        message: 'No payload provided'
    });
}

The sendTwilioMessage function sends a message through Twilio.

The message it is sending is static for now. I will address that in the next post.

The details needed to make the request are pulled from the Firebase configuration. My secrets are safe!

function sendTwilioMessage() {
    let config = functions.config().octwilio;
    let client = new twilio(config.twilio.account_sid, config.twilio.auth_token);

    return client.messages.create({
        body: 'An approval is required',
        to: config.twilio.approval.to_number,
        from: config.twilio.approval.from_number
    }).then((message) => console.log(message.sid));
}

The code was at a good point to test. I published the function to Firebase with the following command:

firebase deploy --only functions

Creating and testing the subscription

Subscriptions let you act on events raised from Octopus Deploy. You can configure an email notification with the target event details included. You can also send the event payload to a webhook.

I configured my subscription to trigger on Manual intervention interruption raised events. The subscription sends the payload to my approvalRaised cloud function and includes the secret token in the header.

Approval Required subscription in Octopus

I added a manual intervention step to my project. Then I created a new release and deployed it to the Development environment.

Approval required!

The deployment is waiting for approval

Outbound requests from Firebase Cloud Functions

I should have received a text message for this, but I didn't.

I checked the logs for the function in Firebase and immediately saw the issue.

Billing account not configured. External network is not accessible and quotas are severely limited. Configure billing account to remove these restrictions

You cannot make external network requests on the free Firebase plan. I changed my plan to the pay-as-you-go tier and tried again.

A message from Twilio that approval is required

Success!

Next time

Next time, I will add some of the deployment details into the message and handle a reply to the message.

Cover photo by Isabel Galvez on Unsplash.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .