Mark O'Regan: CV Challenge 
Tags: azure, serverless, devops, staticsite
Published: 2021-09-27 16:19:09


## Finished site 
[Mark O'Regan's CV Challenge ](https://www.cvoregan.com/)
## The Requirements
The requirements of the challenge were as follows:
* Get an Azure Foundation Certification
* Front-end: Create a resume website
 - HTML, CSS, JavaScript
 - Host on Azure Static Sites 
 - Use HTTPS for security 
 - Custom domain name
 - Set Azure DNS 
 - Use JavaScript to call Azure Counter Function
* Back-end
 - Set up a Cosmos database to store the hit counter
 - Azure Function that serves as an API to connect web app to cosmos db.
 - Write tests that validate API tests
* Infrastructure-as-code
 - Pulumi - Web site, azure function, cosmos db, DNS 
 - Add source control for the site via Azure Devops
 - Implement automated CI/CD for the front-end
 - Implement automated CI/CD for the back-end
* Write a blog post detailing the experience

## Implementation 
There are quite a few requirements listed above but they basically boils down to a 4 main areas, a front-end, a back-end, infrastructure as code and two CI/CD pipelines for publishing front and back-ends. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k6jr1o2zmeurs2y7d959.jpg)

### Front-end 
I wanted to keep the front-end straight forward so grabbed the bootstrap [album template](https://getbootstrap.com/docs/5.1/examples/album/) and used it to create a basic CV site. I choose Azure Static Web App as the site host because it can be easily integrated with Azure Devops pipelines. 

First up I will the infrasturcture as code that creates
 
The following segment of code is taken pulumi and will create a resource group called "cvfrontend". A static site is created first.  
 
```python

import pulumi
from pulumi_azure_native import resources
import pulumi_azure_native as azure_native
import pulumi_azure_native.documentdb as documentdb
import pulumi_azure_native.resources as resources
import pulumi_azure_native.storage as storage
# Create an Azure Resource Group
resource_group = resources.ResourceGroup('cvfrontend')
site_name = "cvsitefrontend"

static_site = azure_native.web.StaticSite("staticSite",
    branch="master",
    build_properties=azure_native.web.StaticSiteBuildPropertiesArgs(
        api_location="api",
        app_artifact_location="build",
        app_location="app",
    ),
    location="West Europe",
    name=site_name,
    repository_token="add your token",
    repository_url="add your git url to repo",
    resource_group_name=resource_group.name,
    sku=azure_native.web.SkuDescriptionArgs(
        name="Free",
        tier="Free",
    ))
```   
I registered a Domain name "cvoregan.com" using Azure Portal which created two resources in the image below. A domain name and DNS Zone associated with the domain. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y91t9vqhlqdacbxpfvb8.png)

Once these domain name resources were available I then wrote some pulumi code to create a CNAME record. The record points the static site host Url and bind it to www.cvoregan.com.  

```python
record_set = azure_native.network.RecordSet("cvStaticSiteRecordSet",
    cname_record=azure_native.network.CnameRecordArgs(
        cname=static_site.default_hostname,
    ),    
    record_type="CNAME",
    relative_record_set_name="www",
    resource_group_name=resource_group.name,
    ttl=3600,
    zone_name="cvoregan.com")

static_site_custom_domain = azure_native.web.StaticSiteCustomDomain("cvStaticSiteCustomDomain",
    domain_name="www.cvoregan.com",
    name=site_name,
    resource_group_name=resource_group.name)
```

The site would have a Javascript call that retrieved and updated the counter and displayed  

Where the front-end became more interesting to me was implementing the 

```python
# Create an Azure resource (Storage Account)
storage_account = storage.StorageAccount(
    "cvcosmostr",
    resource_group_name=resource_group.name,
    sku=storage.SkuArgs(
        name=storage.SkuName.STANDARD_LRS,
    ),
    kind=storage.Kind.STORAGE_V2)

# Cosmos DB Account
cosmosdb_account = documentdb.DatabaseAccount(
    "sqladminacc",
    resource_group_name=resource_group.name,
    database_account_offer_type=documentdb.DatabaseAccountOfferType.STANDARD,
    locations=[documentdb.LocationArgs(
        location_name=resource_group.location,
        failover_priority=0,
    )],
    consistency_policy=documentdb.ConsistencyPolicyArgs(
        default_consistency_level=documentdb.DefaultConsistencyLevel.SESSION,
    ))

# Cosmos DB Database
db = documentdb.SqlResourceSqlDatabase(
    "AzureCVDB",
    resource_group_name=resource_group.name,
    account_name=cosmosdb_account.name,
    resource=documentdb.SqlDatabaseResourceArgs(
        id="AzureCVDB",
    ))

# Cosmos DB SQL Container
db_container = documentdb.SqlResourceSqlContainer(
    "container",
    resource_group_name=resource_group.name,
    account_name=cosmosdb_account.name,
    database_name=db.name,
    resource=documentdb.SqlContainerResourceArgs(
        id="counter",
        partition_key=documentdb.ContainerPartitionKeyArgs(
            paths=["/count"],
            kind="Hash",
        )
    ))

account_keys = pulumi.Output.all(cosmosdb_account.name, resource_group.name).apply(
    lambda arg: documentdb.list_database_account_keys(account_name=arg[0], resource_group_name=arg[1]))
```

Back-end resources
The back-end is an HTTP triggered Azure Functions with Cosmos DB input and output binding. The Function is triggered, it retrieves the CosmosDB item, adds 1 to it, and saves it and returns its value to the caller. CORS