Part 1 - Azdev-ify what?
Published Oct 12 2022 07:30 AM 3,531 Views
Microsoft

By following a few conventions, the Azure Developer CLI (azd) makes it quick and easy to get up and running on Azure.  Applying those conventions to a project and create an azd template is what our team affectionately calls “azdev-ify”. 

 

azd commands are simple but the work azd does under the hood to connect the Azure services and supporting engineer systems, is complex. We want you to focus on business logic for your app. You can say goodbye to going to multiple places and running one-off scripts to stand up Azure resources. Everything is automatable and repeatable. You get a sample CI/CD pipeline set up. If Azure Monitor is included in the template infrastructure, you can even observe and analyze your application health!

 

To “azdev-ify" your project, it’s important to first understand how azd works. As PM for the tool, I am often asked what exactly the minimum is to get started. This is the first of a series of blog posts that focus on stepping through the process of making a simple Python app azd compatible.

 

So, let’s get started!

 

What makes a project compatible with azd?

An azd compatible project follows azd conventions. At the minimum, an azd-compatible repository must have the following:

  1. /infra – a folder for infrastructure code.
  2. Application source code.
  3. azure.yaml” - a metadata file located at the root of the project that describes the app and tells azd how to connect the application code to the Azure resources.

 

The “infra” folder

The infra folder contains the infrastructure as code (IaC) files you need to provision the Azure resources.

 

Update(11/2/2022): this blog post was originally published on Oct 12, 2022 (based on azd version 0.3.0-beta.2.) It has since been updated to sync with azd version 0.4.0-beta.1.

 

Let us take a moment to examine the folder content of our Todo azd templates:

sample-infra-folder.png

  1. main.parameters.json is a required file. It includes the parameters needed by the main.bicep.
  2. main.bicep is a required file and serves as an entry point. It references resources.bicep.
  3. resources.bicep defines the resources the app needs. To azd, this file is optional, and you can change the file name if you’d like. (We removed resource.bicep as of version 0.4.0-beta.1.)
  4. In /app, files are further organized by functionality and are referred by main.bicep:  

sample-app-folder.png

  1. /core folder is simply a reference library that contains all the building blocks (Bicep modules) used by all the azd templates our team authored. For now, we include the same /core folder in each of the ToDo azd template. And yes, we plan to eventually move /core to a Bicep registry.

 

The App

Let's start with something simple - TheCatSaidNo. To deploy this app to Azure, you need an Azure App Service with built-in language for Python.

 

To run it locally:

  1. Make sure you have Python (3.8+) installed
  2. Get the code by running
    git clone https://github.com/luabud/TheCatSaidNo​
  3. Build and Run
    cd thecatsaidno
    py -m venv .venv
    .venv\scripts\activate
    pip3 install -r ./requirements.txt
    flask run
  4. Browse to http://localhost:5000. An extremely useful website that is approved by your cat!

 

To azdev-ify

Before you get started, please make sure you have azd installed on your development environment. The code used in the blog is based on azd version 0.4.0-beta.1.

 

Step 1 - add the /infra folder and Bicep files to provision Azure resources

To deploy the Azure infrastructure so that your cat can say no:

  1. Create a new /infra folder in your TheCatSaidNo project
  2. Refer to a sample, e.g., ToDo Python Mongo. Copy everything in /infra except the /app folder to your /infra folder.
  3. main.bicep – remove outputs you don’t need. azd saves these outputs automatically to the active environment .env file so they can be used from within the application. The following is all you need for outputs. Make sure you modify the output for REACT_APP_WEB_BASE_URL to web.outputs.URI.
    output AZURE_LOCATION string = location
    output AZURE_TENANT_ID string = tenant().tenantId
    output REACT_APP_WEB_BASE_URL string = web.outputs.uri
  1. We need an Azure App Service Plan for defining a set of compute resources and an Azure App Service deployed in Python for hosting the app, I will keep it simple and reference the modules in /core directly. The resulting main.bicep should look like this:
    targetScope = 'subscription'
    
    @minLength(1)
    @maxLength(64)
    @description('Name of the the environment which is used to generate a short unique hash used in all resources.')
    param environmentName string
    
    @minLength(1)
    @description('Primary location for all resources')
    param location string
    
    // Optional parameters to override the default azd resource naming conventions. Update the main.parameters.json file to provide values. e.g.,:
    // "resourceGroupName": {
    //      "value": "myGroupName"
    // }
    param appServicePlanName string = ''
    param resourceGroupName string = ''
    param webServiceName string = ''
    // serviceName is used as value for the tag (azd-service-name) azd uses to identify
    param serviceName string = 'web'
    
    @description('Id of the user or app to assign application roles')
    param principalId string = ''
    
    var abbrs = loadJsonContent('./abbreviations.json')
    var resourceToken = toLower(uniqueString(subscription().id, environmentName, location))
    var tags = { 'azd-env-name': environmentName }
    
    // Organize resources in a resource group
    resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = {
      name: !empty(resourceGroupName) ? resourceGroupName : '${abbrs.resourcesResourceGroups}${environmentName}'
      location: location
      tags: tags
    }
    
    // The application frontend
    module web './core/host/appservice.bicep' = {
      name: serviceName
      scope: rg
      params: {
        name: !empty(webServiceName) ? webServiceName : '${abbrs.webSitesAppService}web-${resourceToken}'
        location: location
        tags: union(tags, { 'azd-service-name': serviceName })
        appServicePlanId: appServicePlan.outputs.id
        runtimeName: 'python'
        runtimeVersion: '3.8'
        scmDoBuildDuringDeployment: true
      }
    }
    
    // Create an App Service Plan to group applications under the same payment plan and SKU
    module appServicePlan './core/host/appserviceplan.bicep' = {
      name: 'appserviceplan'
      scope: rg
      params: {
        name: !empty(appServicePlanName) ? appServicePlanName : '${abbrs.webServerFarms}${resourceToken}'
        location: location
        tags: tags
        sku: {
          name: 'B1'
        }
      }
    }
    
    // App outputs
    output AZURE_LOCATION string = location
    output AZURE_TENANT_ID string = tenant().tenantId
    output REACT_APP_WEB_BASE_URL string = web.outputs.uri

    Important to note serviceName can be anything but must match the service name you specify later in azure.yaml. I am calling my frontend "web".

    param serviceName string = 'web'
  2. run azd init to initialize your project. 
    • select empty template
    • supply an environment name. Can be anything. I use "tsn106"
    • pick a region
    • supply an Azure subscription
  3. run azd provision to provision the Azure resources.  

azd-provision.png

Step 2 - connect the pieces and deploy the app

After running azd init, the azure.yaml file is also added to the root of your project. Think of azure.yaml as the app’s user manual for azd. azd refers to this file to learn more about the app so that it knows, for instance, what Azure service will be hosting your app, how to build and deploy your app.

 

Open and edit the file. `name` and `services` are the minimum requirements here:

  • name: the name of my project is TheCatSaidNo
  • services: TheCatSaidNo needs a web frontend hosted on Azure App Service. It has one service which is web. Make sure you use the same name in main.bicep, i.e.: 
    param serviceName string = 'web'

In Azure Portal, if you look at the Azure App Service provisioned in the previous step, you will notice these tags. This is how azd locates the host in the resource group rg-tsn106.

app-service-tags.png

  • project: It’s good to organize all code under /src but if you have a company convention you need to follow, you can overwrite the location using "project". In my case, source code for “web” is at the root of my project, so: 
    project: .​
  • The resulting azure.yaml looks like this: 
    name: TheCatSaidNo
    services:
      web:
        project: .
        language: py
        host: appservice

Finally, deploy the app by running: azd deploy.

azd-deploy.png

Time to call the cat over and show her the app running on Azure!

tcsn.gif

 

You can get the code for this blog here. Next, setting up Azure Monitor: Part 2 - Observability for your azd-compatible app.

 

We love your feedback! If you have any comments or ideas, feel free to add a comment or submit an issue to the Azure Developer CLI Repo.

Co-Authors
Version history
Last update:
‎Nov 06 2022 07:09 AM
Updated by: