Deploy Packages using Slack

PDQ Deploy, PowerShell, Uncategorized

For those of you who don’t know, Slack is a team communication application and platform.

deploy applications using slack

Conversations are organized into channels where team members can chat, call, and share files. But one of the coolest features of Slack is their support for third party integrations, which is exactly what we’re going to use today to build a custom slash command to deploy packages directly from Slack!

Disclaimer: This project is not production ready, it’s just for funsies.

Creating a Slack app

  1. Let’s get the Slack app created. You’ll need a Slack team, which you can sign up for here https://slack.com/get-started. Even if you have an existing team, you’ll probably want a new one for development.
  2. After you’re logged in to Slack, head over to https://api.slack.com/apps. Click the Create New App button to display the following screen:
  3. Give the app a name and assign it to your Slack team, then click Create App.
  4. Next, it’s time to add features to the app. Since we’re doing a slash command, select Slash Commands.
  5. Click Create New Command.
  6. That will bring us to the command configuration display. Fill in the following fields and click Save.

Note: Keep this window open for a few more steps. The URL is intentionally fake. We’ll be back later to update it with a real one.

What these configuration details mean:

  • Command: this is the text that will be displayed when a user types “/” in the chat input field on Slack.
  • Request URL: when a user executes the deploy command, this is the URL Slack will notify with an HTTP Post request so that we can process it.
  • Short Description: tells the user what the command does.
  • Usage Hint: hints at the accepted parameters associated with the command. In our case, the name of the package and the name of the target computer.

Setting up the HTTP listener

Now that our Slack app has been created, we’re ready to start coding. The first thing we’ll do here is get a bare bones PowerShell script setup and configure it with ngrok.

In a directory of your choice, create a new PowerShell script file named “slack-deploy.ps1”. In it, add the following code:

# Create our instance of HttpListener
$listener = New-Object Net.HttpListener
# Add localhost on port 8080 to listen on
$listener.Prefixes.Add("http://+:8080/")
# Start up our listener
$listener.Start()

Write-Host "listening port 8080..."

While ($listener.IsListening) {

    # Waits for an incoming request
    $context = $listener.GetContext()
    
    # Request received from a client
    $request = $context.Request
    
    # Grab the Response object to be sent to the client
    $response = $context.Response
    $responseBuffer = $null

    # Testing to make sure response works
    $response.StatusCode = 200
    $responseOutput = "It works!"

    # Encode text to byte array
    $responseBuffer = [System.Text.Encoding]::UTF8.GetBytes($responseOutput)

    # Set the content type and length
    $response.ContentType = "text/html"
    $response.ContentLength64 = $responseBuffer.Length

    # Write out the response stream
    $responseOutput = $response.OutputStream
    $responseOutput.Write($responseBuffer, 0, $responseBuffer.Length)
    
    # Close the stream and release resources
    $responseOutput.Close()

}

$listener.Stop()

Open a PowerShell terminal (make sure to run as administrator), change to your working directory and run your script: ./slack-deploy.ps1

If all goes well, you should see “listening on port: 8080…”. When you do, open a web browser and navigate to http://localhost:8080. You should see “It works!” displayed.

Configuring Ngrok

Ngrok is a service that allows you to create a secure tunnel between our local machine and the public URL they provide.

  1. You’ll need to download ngrok from here: https://ngrok.com/download.
  2. Unzip the executable to a directory of your choice (I put mine in C:\util\ngrok).
  3. Navigate to Advanced System Settings (Win+Advanced system settings or Control Panel > System and Security > System > Advanced system settings) and click Environment Variables. We’re going to add the ngrok directory to our Path variable so we can run it just by typing “ngrok” in a terminal.
  4. In Environment Variables, double-click Path and the ‘Edit environment variable’ dialog will display.
  5. Click New and add the directory where you unzipped ngrok.
  6. Open up another terminal and run ngrok http 8080. It should start displaying Session Status, Web Interface, and the public URL that it’s forwarding to your local machine.
  7. When the Session Status switches to online, open up the URL it lists, for example, “https://abcd1234.ngrok.io”. Confirm that you get the same, “It works!” as you did locally.
  8. Now that we have a public URL, we can go back to our Slack command configuration and update its URL field. Under Slash Commands, click Edit next to your custom command, and update your command’s Request URL with your new ngrok URL:

    Note: anytime you restart the ngrok process, your unique URL will change requiring you to update this setting.
  9. Click Save and after the dialog closes, click Install App in left navigation menu under Settings.
  10. Click Install App again and then select Authorize to authorize the application.
  11. After that’s complete, open up Slack and confirm it’s successfully installed. In the chat input, when you type a forward slash ‘/’, you should see ‘Deploy’ show up as one of your options.

Woot! Almost there. Now we just need to finish up our PowerShell script, then we’ll be deploying packages Slack-style!

Processing the Deploy Command

Here’s the updated PowerShell script in full:

# Create our instance of HttpListener
$listener = New-Object Net.HttpListener
# Add localhost on port 8080 to listen on
$listener.Prefixes.Add("http://+:8080/")
# Start up our listener
$listener.Start()

Write-Host "listening port 8080..."

While ($listener.IsListening) {

    # Waits for an incoming request
    $context = $listener.GetContext()
    
    # Request received from a client
    $request = $context.Request
    
    # Grab the Response object to be sent to the client
    $response = $context.Response
    $responseBuffer = $null

    # Init response variables
    $statusCode = 500
    $responseOutput = ""

    if ($request.HttpMethod -ne "POST") {
        # Only accepting POST requests
        $statusCode = 404
        $responseOutput = "route not found!"
    }
    else {
        $size = $request.ContentLength64 + 1
        $buffer = New-Object byte[] $size
        $cmdInput = ""

        do {
            $count = $request.InputStream.Read($buffer, 0, $size)
            $cmdInput += $request.ContentEncoding.GetString($buffer, 0, $count)
        } until ($count -lt $size)        

        # Close input stream
        $request.InputStream.Close()

        # Regular expression to parse out deploy package and target computer parameters
        # Positive lookbehind for the words 'deploy&test' and 
        # Positive lookforward for the words ?=&response_url to capture the parameters in between
        $cmds = [regex]::match($cmdInput, "((?<=deploy&text=)(.*)(?=&response_url))", "SingleLine")

        if ($cmds.Success -ne $true) {
            # Failed to parse deploy commands, likely a bad request
            $statusCode = 400
            $responseOutput = "Command should be in the form '/deploy <package name> <target computer>' "
        }
        else {
            # Values will be in the form package+target, split on '+'
            $package, $target = $cmds.Groups[0].Value.Split("+")
            
            # Setup the PDQ deploy command with the package and target
            $deployCmd = "pdqdeploy deploy -package ${package} -targets ${target}"

            Write-Host "processing command: ${deploycmd}"

            try {
                # Set the response output to the console's output
                $responseOutput = Invoke-Expression($deployCmd)
                $statusCode = 200
            } catch {
                $responseOutput = "Deployment failed, make sure your package name and target computer exist and are correct"
                $statusCode = 400
            } finally {
                $cmdInput = ""
            }
        }
    }

    # Set the status code for response
    $response.StatusCode = $statusCode

    # Encode text to byte array
    $responseBuffer = [System.Text.Encoding]::UTF8.GetBytes($responseOutput)

    # Set the content type and length
    $response.ContentType = "text/html"
    $response.ContentLength64 = $responseBuffer.Length

    # Write out the response stream
    $responseOutput = $response.OutputStream
    $responseOutput.Write($responseBuffer, 0, $responseBuffer.Length)
    
    # Close the stream and release resources
    $responseOutput.Close()

}

$listener.Stop()

What’s being done here:

  • We’ve replaced the test variables with initial values.
  • In the first “if” statement, any requests that are not “Post” methods, we reject. This is the only method our script is concerned about.
  • We grab the content from the Post body and use a regular expression to parse out the package and target values.
  • If the package and target were successfully parsed, we run the PDQ Deploy command. If that succeeds, we set the response content to the console’s output.
  • Finally, we set the response status code and content and return it.

We’re done! Now let’s try it out. Restart the PowerShell script ./slack-deploy.ps1 and open up Slack.

For my test, I have a package named ‘TestPackage’ that I’ll run against my local machine.

  • In Slack:
  • After hitting enter, you should see something similar to:
  • To confirm in PDQ Deploy, click on All Deployments in the tree:

Now, cut the slack and get deploying!

Leave a Reply

Your email address will not be published. Required fields are marked *