<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kohei Kawata</title>
    <description>The latest articles on DEV Community by Kohei Kawata (@koheikawata).</description>
    <link>https://dev.to/koheikawata</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F685298%2F1d853b97-3257-42f5-b9c9-e7f98912c707.jpg</url>
      <title>DEV Community: Kohei Kawata</title>
      <link>https://dev.to/koheikawata</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/koheikawata"/>
    <language>en</language>
    <item>
      <title>Virtual Network architecture 6 - Service Bus Private Endpoint</title>
      <dc:creator>Kohei Kawata</dc:creator>
      <pubDate>Mon, 29 Aug 2022 14:39:00 +0000</pubDate>
      <link>https://dev.to/koheikawata/virtual-network-architecture-6-service-bus-private-endpoint-oa</link>
      <guid>https://dev.to/koheikawata/virtual-network-architecture-6-service-bus-private-endpoint-oa</guid>
      <description>&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;This article is Part.6 of virtual network architecture series that includes the details of the private endpoint Azure Service Bus in &lt;a href="https://github.com/koheikawata/api-management-vnet"&gt;api-management-vnet&lt;/a&gt;. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-1-do-i-need-virtual-network-1nhk"&gt;Virtual Network architecture 1 - Do I need virtual network?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-2-deployment-pipelines-39p5"&gt;Virtual Network architecture 2 - Deployment pipelines&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-3-key-vault-private-endpoint-43nj"&gt;Virtual Network architecture 3 - Key Vault Private Endpoint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-4-sql-database-private-endpoit-49o2"&gt;Virtual Network architecture 4 - SQL Database Private Endpoit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-5-app-service-private-endpoint-5eao"&gt;Virtual Network architecture 5 - App Service Private Endpoint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-6-service-bus-private-endpoint-oa"&gt;Virtual Network architecture 6 - Service Bus Private Endpoint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-7-self-hosted-agent-32j4"&gt;Virtual Network architecture 7 - Self-hosted agent&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  TOC
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Private Endpoint configuration&lt;/li&gt;
&lt;li&gt;Public network access&lt;/li&gt;
&lt;li&gt;Support tier&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Private Endpoint configuration &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Private Endpoint&lt;/strong&gt;: Deploy the private endpoint and connect it to Service Bus and its subnet in &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/src/bicep/vnet/modules/PrivateEndpoint.bicep"&gt;PrivateEndpoint.bicep&lt;/a&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ServiceBusId:existingSb.id
VirtualNetwork3SubnetIdSb:existingVnet3.properties.subnets[0].id

resource PrivateEndpointSb 'Microsoft.Network/privateEndpoints@2021-03-01' = {
  properties: {
    privateLinkServiceConnections: [
      {
        properties: {
          privateLinkServiceId: ServiceBusId
          groupIds: [
            'namespace'
          ]
        }
      }
    ]
    subnet: {
      id: VirtualNetwork3SubnetIdSb
      properties: {
        privateEndpointNetworkPolicies: 'Enabled'
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Private DNS&lt;/strong&gt;: Deploy Private DNS with the DNS name &lt;code&gt;privatelink.servicebus.windows.net&lt;/code&gt; in &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/src/bicep/vnet/modules/PrivateDns2.bicep"&gt;PrivateDns2.bicep&lt;/a&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var pdns_name_sb = 'privatelink.servicebus.windows.net'

resource PrivateDnsSb 'Microsoft.Network/privateDnsZones@2020-06-01' = {
  name: pdns_name_sb
  location: 'global'
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Virtual network link&lt;/strong&gt;: Link the deployed Private DNS to the virtual network 3 where the Service Bus resides and the virtual network 2 so Azure Functions can access the Service Bus topic in &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/src/bicep/vnet/modules/PrivateDns2.bicep"&gt;PrivateDns2.bicep&lt;/a&gt;. The virtual network 2 and 3 are connected with Virtual Network Peering.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VirtualNetwork2Id:existingVnet2.id
VirtualNetwork3Id:existingVnet3.id

resource VnetLinkSb2 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = {
  name: '${PrivateDnsSb.name}/${PrivateDnsSb.name}-link2'
  location: 'global'
  properties: {
    registrationEnabled: false
    virtualNetwork: {
      id: VirtualNetwork2Id
    }
  }
}

resource VnetLinkSb3 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = {
  name: '${PrivateDnsSb.name}/${PrivateDnsSb.name}-link3'
  location: 'global'
  properties: {
    registrationEnabled: false
    virtualNetwork: {
      id: VirtualNetwork3Id
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Private DNS A record&lt;/strong&gt;: Create a DNS A record and set the IP address from the deployed private endpoint in &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/src/bicep/vnet/modules/PrivateDns2.bicep"&gt;PrivateDns2.bicep&lt;/a&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;output PrivateEndpointSbIpAddress string = PrivateEndpointSb.properties.customDnsConfigs[0].ipAddresses[0]

resource PrivateDnsASb 'Microsoft.Network/privateDnsZones/A@2020-06-01' = {
  name: '${PrivateDnsSb.name}/${ServiceBusName}'
  properties: {
    ttl: 3600
    aRecords: [
      {
        ipv4Address: PrivateEndpointSbIpAddress
      }
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Public network access &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Public network access is disabled in &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/src/bicep/vnet/modules/ServiceBus2.bicep"&gt;ServiceBus2.bicep&lt;/a&gt; so all access except through the Private Endpoint are denied.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource ServiceBus 'Microsoft.ServiceBus/namespaces@2022-01-01-preview' = {
  sku: {
    name: 'Premium'
  }
  properties: {
    publicNetworkAccess: 'Disabled'
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Support tier &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;According to the Microsoft documentation &lt;a href="https://docs.microsoft.com/en-us/azure/service-bus-messaging/private-link-service#important-points"&gt;Azure Service Bus Private Endpoint&lt;/a&gt;, only Premium tier supports Azure Service Bus Private Endpoint feature.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Virtual Network architecture 7 - Self-hosted agent</title>
      <dc:creator>Kohei Kawata</dc:creator>
      <pubDate>Mon, 29 Aug 2022 14:39:00 +0000</pubDate>
      <link>https://dev.to/koheikawata/virtual-network-architecture-7-self-hosted-agent-32j4</link>
      <guid>https://dev.to/koheikawata/virtual-network-architecture-7-self-hosted-agent-32j4</guid>
      <description>&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;This article is Part.7 of virtual network architecture series. I will share how &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/docker"&gt;Azure Pipelines self-hosted agent in the docker&lt;/a&gt; works in the sample template &lt;a href="https://github.com/koheikawata/api-management-vnet"&gt;api-management-vnet&lt;/a&gt;. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-1-do-i-need-virtual-network-1nhk"&gt;Virtual Network architecture 1 - Do I need virtual network?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-2-deployment-pipelines-39p5"&gt;Virtual Network architecture 2 - Deployment pipelines&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-3-key-vault-private-endpoint-43nj"&gt;Virtual Network architecture 3 - Key Vault Private Endpoint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-4-sql-database-private-endpoit-49o2"&gt;Virtual Network architecture 4 - SQL Database Private Endpoit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-5-app-service-private-endpoint-5eao"&gt;Virtual Network architecture 5 - App Service Private Endpoint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-6-service-bus-private-endpoint-oa"&gt;Virtual Network architecture 6 - Service Bus Private Endpoint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-7-self-hosted-agent-32j4"&gt;Virtual Network architecture 7 - Self-hosted agent&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  TOC
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Azure Container Instance&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Azure Container Instance &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Before I tried the self-hosted agent by myself, I thought the self-hosted agent running on Azure Container Instance is totally isolated from the Internet. And I was wondering how the self-hosted agent can access Azure Repos to get the updated software codes to deploy to services inside the virtual network. However, now I know Azure Container Instance is not isolated from the Internet at all. Below is some tips I found through my experience.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only outbound traffic of Azure Container Instance should be considered. And then the self-hosted agent does not need to have Private Endpoint, because Private Endpoint is only for inbound traffic.&lt;/li&gt;
&lt;li&gt;Similar with other Azure PaaS resources, a self-hosted agent in the docker running on Azure Container Instance has the public IP address. You have to protect Azure Container Instance in some ways, for example, described in &lt;a href="https://docs.microsoft.com/en-us/azure/container-instances/container-instances-egress-ip-address"&gt;Configure a single public IP address for outbound and inbound traffic to a container group&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;You can find what runtime the self-hosted agent installs when seeing &lt;code&gt;start.ps1&lt;/code&gt; on &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/docker?view=azure-devops#create-and-build-the-dockerfile"&gt;Create and build the Dockerfile&lt;/a&gt; for Windows, for example. The runtime controls the outbound traffic from the Azure Container Instance, and you do not need to take care of it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rbWYA2Ql--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0iv4kjbkliac92s7t6hz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rbWYA2Ql--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0iv4kjbkliac92s7t6hz.png" alt="Image description" width="880" height="379"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Virtual Network architecture 5 - App Service Private Endpoint</title>
      <dc:creator>Kohei Kawata</dc:creator>
      <pubDate>Mon, 29 Aug 2022 14:39:00 +0000</pubDate>
      <link>https://dev.to/koheikawata/virtual-network-architecture-5-app-service-private-endpoint-5eao</link>
      <guid>https://dev.to/koheikawata/virtual-network-architecture-5-app-service-private-endpoint-5eao</guid>
      <description>&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;This article is Part.5 of virtual network architecture series and includes the details of the private endpoint with Azure App Service and Azure Functions App in &lt;a href="https://github.com/koheikawata/api-management-vnet"&gt;api-management-vnet&lt;/a&gt;. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-1-do-i-need-virtual-network-1nhk"&gt;Virtual Network architecture 1 - Do I need virtual network?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-2-deployment-pipelines-39p5"&gt;Virtual Network architecture 2 - Deployment pipelines&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-3-key-vault-private-endpoint-43nj"&gt;Virtual Network architecture 3 - Key Vault Private Endpoint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-4-sql-database-private-endpoit-49o2"&gt;Virtual Network architecture 4 - SQL Database Private Endpoit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-5-app-service-private-endpoint-5eao"&gt;Virtual Network architecture 5 - App Service Private Endpoint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-6-service-bus-private-endpoint-oa"&gt;Virtual Network architecture 6 - Service Bus Private Endpoint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-7-self-hosted-agent-32j4"&gt;Virtual Network architecture 7 - Self-hosted agent&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  TOC
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Private Endpoint configuration&lt;/li&gt;
&lt;li&gt;Inbound and Outbound&lt;/li&gt;
&lt;li&gt;Access restrictions&lt;/li&gt;
&lt;li&gt;Support tier&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Private Endpoint configuration &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Private Endpoint&lt;/strong&gt;: Deploy the private endpoint and connect it to App Service and Functions and their subnets in &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/src/bicep/vnet/modules/PrivateEndpoint.bicep"&gt;PrivateEndpoint.bicep&lt;/a&gt;. Both App Service and Functions need two subnets for each for the purpose of &lt;a href="https://docs.microsoft.com/en-us/azure/app-service/overview-vnet-integration"&gt;V-net integration&lt;/a&gt; and Private Endpoint.

&lt;ul&gt;
&lt;li&gt;This sample template takes &lt;a href="https://docs.microsoft.com/en-us/azure/app-service/overview-vnet-integration#regional-virtual-network-integration"&gt;Regional V-net integration&lt;/a&gt; and it requires delegated subnet. The integration subnet has to be delegated to &lt;code&gt;Microsoft.Web/serverFarms&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The Private Endpoint for App Service and Function App has to have one subnet that is not the same subnet as the one for V-net integration, according to the Microsoft documentation &lt;a href="https://docs.microsoft.com/en-us/azure/app-service/networking/private-endpoint#conceptual-overview"&gt;Private Endpoint for App Service&lt;/a&gt;.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AppServiceId:existingAppService.id
FuncId:existingFunc.id
VirtualNetwork2SubnetIdAsePe:existingVnet2.properties.subnets[1].id
VirtualNetwork2SubnetIdFuncPe:existingVnet2.properties.subnets[5].id

resource VirtualNetwork2 'Microsoft.Network/virtualNetworks@2021-03-01' = {
  properties: {
    subnets: [
      {
        name: snet_name_2_ase_vi
        properties: {
          addressPrefix: snet_prefix_2_ase_vi
          networkSecurityGroup: {
            id: Nsg2AseViId
          }
          delegations: [
            {
              name: 'delegation'
              properties: {
                serviceName: 'Microsoft.Web/serverfarms'
              }
            }
          ]
          privateEndpointNetworkPolicies: 'Disabled'
        }
      }
      {
        name: snet_name_2_ase_pe
        properties: {
          addressPrefix: snet_prefix_2_ase_pe
          networkSecurityGroup: {
            id: Nsg2AsePeId
          }
          privateEndpointNetworkPolicies: 'Enabled'
        }
      }
      {
        name: snet_name_2_func_vi
        properties: {
          addressPrefix: snet_prefix_2_func_vi
          networkSecurityGroup: {
            id: Nsg2FuncViId
          }
          delegations: [
            {
              name: 'delegation'
              properties: {
                serviceName: 'Microsoft.Web/serverfarms'
              }
            }
          ]
          privateEndpointNetworkPolicies: 'Disabled'
        }
      }
      {
        name: snet_name_2_func_pe
        properties: {
          addressPrefix: snet_prefix_2_func_pe
          networkSecurityGroup: {
            id: Nsg2FuncPeId
          }
          privateEndpointNetworkPolicies: 'Enabled'
        }
      }
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Private DNS&lt;/strong&gt;: Deploy Private DNS with the DNS name &lt;code&gt;privatelink.azurewebsites.net&lt;/code&gt; in &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/src/bicep/vnet/modules/PrivateDns2.bicep"&gt;PrivateDns2.bicep&lt;/a&gt;. App Service and Functions can share one Private DNS Zone.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var pdns_name_app = 'privatelink.azurewebsites.net'

resource PrivateDnsApp 'Microsoft.Network/privateDnsZones@2020-06-01' = {
  name: pdns_name_app
  location: 'global'
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Virtual network link&lt;/strong&gt;: Link the deployed Private DNS to three of the virtual network  in &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/src/bicep/vnet/modules/PrivateDns2.bicep"&gt;PrivateDns2.bicep&lt;/a&gt;. This is because App Service and Functions reach out to all three virtual networks. Also, the virtual network 1 and 2 are peered, and the virtual network 2 and 3, too.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VirtualNetwork1Id:existingVnet1.id
VirtualNetwork2Id:existingVnet2.id
VirtualNetwork3Id:existingVnet3.id

resource VnetLinkApp1 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = {
  name: '${PrivateDnsApp.name}/${PrivateDnsApp.name}-link1'
  location: 'global'
  properties: {
    registrationEnabled: false
    virtualNetwork: {
      id: VirtualNetwork1Id
    }
  }
}

resource VnetLinkApp2 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = {
  name: '${PrivateDnsApp.name}/${PrivateDnsApp.name}-link2'
  location: 'global'
  properties: {
    registrationEnabled: false
    virtualNetwork: {
      id: VirtualNetwork2Id
    }
  }
}

resource VnetLinkApp3 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = {
  name: '${PrivateDnsApp.name}/${PrivateDnsApp.name}-link3'
  location: 'global'
  properties: {
    registrationEnabled: false
    virtualNetwork: {
      id: VirtualNetwork3Id
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Private DNS A record&lt;/strong&gt;: Create DNS A records and set the IP address from the deployed private endpoints in &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/src/bicep/vnet/modules/PrivateDns2.bicep"&gt;PrivateDns2.bicep&lt;/a&gt;. You have to create two A records each for App Service and Functions because both have another endpoint of SCM(Source Control Manager) site. You can see the documentation of &lt;a href="https://docs.microsoft.com/en-us/azure/app-service/resources-kudu"&gt;Kudu service&lt;/a&gt; about it. Both private endpoints need the same private IP address configurations.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;output PrivateEndpointAseIpAddress string = PrivateEndpointAse.properties.customDnsConfigs[0].ipAddresses[0]
output PrivateEndpointFuncIpAddress string = PrivateEndpointFunc.properties.customDnsConfigs[0].ipAddresses[0]

resource PrivateDnsAAse 'Microsoft.Network/privateDnsZones/A@2020-06-01' = {
  name: '${PrivateDnsApp.name}/${AppServiceName}'
  properties: {
    ttl: 3600
    aRecords: [
      {
        ipv4Address: PrivateEndpointAseIpAddress
      }
    ]
  }
}

resource PrivateDnsAAseScm 'Microsoft.Network/privateDnsZones/A@2020-06-01' = {
  name: '${PrivateDnsApp.name}/${AppServiceName}.scm'
  properties: {
    ttl: 3600
    aRecords: [
      {
        ipv4Address: PrivateEndpointAseIpAddress
      }
    ]
  }
}

resource PrivateDnsAFunc 'Microsoft.Network/privateDnsZones/A@2020-06-01' = {
  name: '${PrivateDnsApp.name}/${FuncName}'
  properties: {
    ttl: 3600
    aRecords: [
      {
        ipv4Address: PrivateEndpointFuncIpAddress
      }
    ]
  }
}

resource PrivateDnsAFuncScm 'Microsoft.Network/privateDnsZones/A@2020-06-01' = {
  name: '${PrivateDnsApp.name}/${FuncName}.scm'
  properties: {
    ttl: 3600
    aRecords: [
      {
        ipv4Address: PrivateEndpointFuncIpAddress
      }
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Inbound and Outbound &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;App Service and Functions have different network features of inbound and outbound. Inbound works with Private Endpoint and outbound with V-net integration, and both need to have two different subnets for each.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ZU_PjWSF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/015q2w48e7zjnavmogq2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ZU_PjWSF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/015q2w48e7zjnavmogq2.png" alt="Image description" width="841" height="610"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Access restrictions &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;According to the Microsoft documentation of &lt;a href="https://docs.microsoft.com/en-us/azure/app-service/networking/private-endpoint#conceptual-overview"&gt;Private Endpoints for Azure Web App&lt;/a&gt;, by default, enabling Private Endpoints to your Web App disables all public access. You do not need to configure &lt;a href="https://docs.microsoft.com/en-us/azure/app-service/app-service-ip-restrictions"&gt;Access restrictions&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Support tier &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Microsoft documentation of &lt;a href="https://docs.microsoft.com/en-us/azure/app-service/networking/private-endpoint"&gt;Private Endpoints for Azure Web App&lt;/a&gt; describes only Basic, Standard, PremiumV2, PremiumV3, IsolatedV2, Functions Premium support Private Endpoint.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Virtual Network architecture 4 - SQL Database Private Endpoit</title>
      <dc:creator>Kohei Kawata</dc:creator>
      <pubDate>Mon, 29 Aug 2022 14:38:00 +0000</pubDate>
      <link>https://dev.to/koheikawata/virtual-network-architecture-4-sql-database-private-endpoit-49o2</link>
      <guid>https://dev.to/koheikawata/virtual-network-architecture-4-sql-database-private-endpoit-49o2</guid>
      <description>&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;This article is Part.4 of virtual network architecture series. I explain the details of the private endpoint with Azure SQL Database in &lt;a href="https://github.com/koheikawata/api-management-vnet"&gt;api-management-vnet&lt;/a&gt;. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-1-do-i-need-virtual-network-1nhk"&gt;Virtual Network architecture 1 - Do I need virtual network?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-2-deployment-pipelines-39p5"&gt;Virtual Network architecture 2 - Deployment pipelines&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-3-key-vault-private-endpoint-43nj"&gt;Virtual Network architecture 3 - Key Vault Private Endpoint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-4-sql-database-private-endpoit-49o2"&gt;Virtual Network architecture 4 - SQL Database Private Endpoit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-5-app-service-private-endpoint-5eao"&gt;Virtual Network architecture 5 - App Service Private Endpoint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-6-service-bus-private-endpoint-oa"&gt;Virtual Network architecture 6 - Service Bus Private Endpoint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-7-self-hosted-agent-32j4"&gt;Virtual Network architecture 7 - Self-hosted agent&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  TOC
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Private Endpoint configuration&lt;/li&gt;
&lt;li&gt;Access to SQL Database&lt;/li&gt;
&lt;li&gt;SQL project update&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Private Endpoint configuration &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Private Endpoint&lt;/strong&gt;: Deploy the private endpoint and connect it to SQL Database and its subnet in &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/src/bicep/vnet/modules/PrivateEndpoint.bicep"&gt;PrivateEndpoint.bicep&lt;/a&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SqlServerId:existingSql.id
VirtualNetwork2SubnetIdSql:existingVnet2.properties.subnets[2].id

resource PrivateEndpointSql 'Microsoft.Network/privateEndpoints@2021-03-01' = {
  properties: {
    privateLinkServiceConnections: [
      {
        properties: {
          privateLinkServiceId: SqlServerId
          groupIds: [
            'sqlServer'
          ]
        }
      }
    ]
    subnet: {
      id: VirtualNetwork2SubnetIdSql
      properties: {
        privateEndpointNetworkPolicies: 'Enabled'
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Private DNS&lt;/strong&gt;: Deploy Private DNS with the DNS name &lt;code&gt;privatelink.database.windows.net&lt;/code&gt; in &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/src/bicep/vnet/modules/PrivateDns2.bicep"&gt;PrivateDns2.bicep&lt;/a&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var pdns_name_sql = 'privatelink${environment().suffixes.sqlServerHostname}'

resource PrivateDnsSql 'Microsoft.Network/privateDnsZones@2020-06-01' = {
  name: pdns_name_sql
  location: 'global'
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Virtual network link&lt;/strong&gt;: Link the deployed Private DNS to the virtual network where the SQL Server subnet exists in &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/src/bicep/vnet/modules/PrivateDns2.bicep"&gt;PrivateDns2.bicep&lt;/a&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VirtualNetwork2Id:existingVnet2.id

resource VnetLinkSql 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = {
  name: '${PrivateDnsSql.name}/${PrivateDnsSql.name}-link'
  location: 'global'
  properties: {
    registrationEnabled: false
    virtualNetwork: {
      id: VirtualNetwork2Id
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Private DNS A record&lt;/strong&gt;: Create a DNS A record and set the IP address from the deployed private endpoint in &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/src/bicep/vnet/modules/PrivateDns2.bicep"&gt;PrivateDns2.bicep&lt;/a&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;output PrivateEndpointSqlIpAddress string = PrivateEndpointSql.properties.customDnsConfigs[0].ipAddresses[0]

resource PrivateDnsASql 'Microsoft.Network/privateDnsZones/A@2020-06-01' = {
  name: '${PrivateDnsSql.name}/${SqlServerName}'
  properties: {
    ttl: 3600
    aRecords: [
      {
        ipv4Address: PrivateEndpointSqlIpAddress
      }
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Access to SQL Database &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;App Service and Functions need to access the SQL Database to insert and extract the data records. Both can access through V-net integration and Private Endpoint. In &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/src/bicep/vnet/modules/SqlDatabase2.bicep"&gt;SqlDatabase2.bicep&lt;/a&gt;, public network access is disabled so no one can access through the public IP.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource SqlServer 'Microsoft.Sql/servers@2021-02-01-preview' = {
  properties: {
    publicNetworkAccess: 'Disabled'
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--xZ5_imDc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/szwktor3ci7dnwcjh32h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--xZ5_imDc--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/szwktor3ci7dnwcjh32h.png" alt="Image description" width="880" height="679"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  SQL project update &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;The biggest problem is when you want to update SQL projects including data schema on the Azure SQL Database. In this sample template, you cannot update the SQL project because a Linux based self-hosted agent does not support build and deployment of SQL Database projects. On the other hand, a Windows based self-hosted agent in the docker container on Azure Container Instance cannot be deployed on the virtual network as I mentioned in the previous article &lt;a href="https://dev.to/koheikawata/virtual-network-architecture-2-deployment-pipelines-39p5#pipeline-vnet1"&gt;Virtual Network architecture 2 - Deployment pipelines&lt;/a&gt;. Possible workaround is to create a Windows based self-hosted agent on a Virtual Machine, to change IP filtering rules when in code deployment, or to wait for the Windows docker container supporting the virtual network deployment.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Virtual Network architecture 1 - Do I need virtual network?</title>
      <dc:creator>Kohei Kawata</dc:creator>
      <pubDate>Mon, 29 Aug 2022 14:38:00 +0000</pubDate>
      <link>https://dev.to/koheikawata/virtual-network-architecture-1-do-i-need-virtual-network-1nhk</link>
      <guid>https://dev.to/koheikawata/virtual-network-architecture-1-do-i-need-virtual-network-1nhk</guid>
      <description>&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;This is a part of virtual network architecture series. In this article, I discuss problems with software development for cloud native architectures and pros and cons of virtual network architecture.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-1-do-i-need-virtual-network-1nhk"&gt;Virtual Network architecture 1 - Do I need virtual network?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-2-deployment-pipelines-39p5"&gt;Virtual Network architecture 2 - Deployment pipelines&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-3-key-vault-private-endpoint-43nj"&gt;Virtual Network architecture 3 - Key Vault Private Endpoint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-4-sql-database-private-endpoit-49o2"&gt;Virtual Network architecture 4 - SQL Database Private Endpoit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-5-app-service-private-endpoint-5eao"&gt;Virtual Network architecture 5 - App Service Private Endpoint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-6-service-bus-private-endpoint-oa"&gt;Virtual Network architecture 6 - Service Bus Private Endpoint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-7-self-hosted-agent-32j4"&gt;Virtual Network architecture 7 - Self-hosted agent&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  TOC
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Background&lt;/li&gt;
&lt;li&gt;Problems&lt;/li&gt;
&lt;li&gt;How to deal with those problems&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Background &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;As many of enterprise cloud users are moving on to &lt;a href="https://docs.microsoft.com/en-us/dotnet/architecture/cloud-native/definition"&gt;cloud native&lt;/a&gt; technologies, so that they can meet continuously changing requirements and keep up with the velocity of fast application development. I often see those cloud users wondering where their security responsibilities lie. According to &lt;a href="https://docs.microsoft.com/en-us/azure/security/fundamentals/shared-responsibility"&gt;shared responsibility model&lt;/a&gt;, IaaS systems require fully to take care of secure infrastructures (including identity and directory infrastructure, applications, network controls, and operating system), while PaaS systems allow to offload some of those security responsibilities to the cloud provider. So I got a lof of questions from enterprise clients about in what areas they have to take security responsibilities and how to build the cloud native architecture strongly enough against potential attacks coming through the Internet. &lt;a href="https://docs.microsoft.com/en-us/security/zero-trust/"&gt;Zero trust&lt;/a&gt; approach is one of the important security concepts as PaaS resources are usually exposed over public endpoints by default and the traditional firewall-based security does not work. However, even with authentication and authorization mechanisms, the system is still threatened by network attacks and unauthorized traffic from the Internet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--V_7byKGd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hpdz5la9m9qvb56f7awd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--V_7byKGd--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hpdz5la9m9qvb56f7awd.png" alt="Image description" width="880" height="625"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In order to achieve both dynamic modern software architecture and enterprise production-ready security, Microsoft Azure offers &lt;a href="https://docs.microsoft.com/en-us/azure/private-link/private-link-overview"&gt;Azure Private Link&lt;/a&gt; technology that enables access to Azure PaaS Services over a private endpoint located in a virtual network. Most of Azure PaaS Services such as &lt;a href="https://docs.microsoft.com/en-us/azure/app-service/overview"&gt;Azure App Service&lt;/a&gt; have independent public endpoints while Azure IaaS Services such as &lt;a href="https://docs.microsoft.com/en-us/azure/virtual-machines/"&gt;Azure Virtual Machines&lt;/a&gt; are deployed on a virtual network. Private Link adds a private endpoint to Azure PaaS Services and enables access only from the virtual network through the private endpoint. What I want to emphasize here is that PaaS Services such as SQL Database is an independent entity on the internet and still has its public IP address to which someone on the internet can connect. Data access should be through Private Endpoint and private IP address, and the public IP is protected by IP filtering.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jfh5xB5m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rbwi7tpjkheguxuvqgen.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jfh5xB5m--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rbwi7tpjkheguxuvqgen.png" alt="Image description" width="880" height="849"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Problems &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;There would always be pros and cons when you adopt a new technology. Designing and developing a cloud native architecture with Azure Private Link costs you more. The pricing itself is usually not a big problem as one private endpoint costs around $2.4 per day. Instead, in reality the costs on developers become high.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Virtual network design and development workload&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Compared with architecture without virtual network, it requires more workload to design and develop. For example, in my past work with an enterprise cloud developers I designed the network architecture with four virutal networks, eleven subnets, eight network security group (NSG) configurations, two virtual network peerings, five private DNS zones, five private endpoints, and one service endpoint. And then I designed and implemented a Infrastructure as Code (IaC) piepeline so the enterprise clients can automate those virtual network infrastructure deployment and manage resources by code. It actually took two months by our team.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Local development&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With Azure Private Link technology, the cloud user can protect their Azure PaaS Services agaist any attacks and unauthorized access from the Internet, but at the same time the enterprise develpers cannot access those services either. For instance, without the virtual network protection the developers can test their code on their own local machines reading and writing data on a Azure SQL Database. When the virtual network envirnment limits access only from private endpoints, the developers' local machines cannot access the database on Azure. That would happen to other PaaS services (such as Azure App Service, Azure Key Vault, and Azure Service Bus).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. DevOps agent access&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;In my work, I usually use &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/agents?view=azure-devops&amp;amp;tabs=browser"&gt;Azure Pipelines agents&lt;/a&gt; (in Azure DevOps) to build, test, and deploy code to Azure PaaS Services. For example, I deploy a .NET application to Azure App Service and Azure Functions, OpenAPI specifications to Azure API Management, and data-tier applications (DACPAC) to Azure SQL Database. If the virtual network protects access from the Internet, even the Azure Pipelines agent cannot access the PaaS services for deployment.&lt;/p&gt;

&lt;h1&gt;
  
  
  How to deal with those problems &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;How we handle those problems depends on project contexts. Some enterprise companies have rigid company network restrictions, some have strong network design skills and experiences. It depends on the projects and development environments, but I would like to share how we usually deal with those network access problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Virtual network design and development workload&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Our team is proud of contributing to open source software assets and Microsoft platform that are broadly available. In every project, we create reusable and sharable software assets that can be widely applicable with the agreement of the enterprise clients. Our team practices growth mindset by trying new things and learning from others, and then reuse the learnings and create shared software assets. One examle is &lt;a href="https://github.com/Azure-Samples/MLOpsManufacturing"&gt;Azure-Samples/MLOpsManufacturing&lt;/a&gt; created with learnings from multiple projects. As we work more engagements with more clients, more and more other developers can reuse the assets and do not need to spend months designing network security architectures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Local development&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One workaround for local development problems is to create similar environments in your local machine. For example, you can create a database on a local SQL Server with the same configurations as the Azure SQL Database and set up your applications, so that developers can switch between local and Azure databases by changing a database connection string. Another workaround is to have multiple environments on Azure. For instance, you can create dev/stg/prod environments. And you can have the dev environment without virtual networks, but the stg/prod environments with virtual networks and private endpoints, so developers can easily test PaaS resources in the dev environment, but make sure of strong protection for the stage and prod environments. The other workaround is to use &lt;a href="https://docs.microsoft.com/en-us/azure/bastion/bastion-overview"&gt;Azure Bastion&lt;/a&gt; with virtual machines where developers can work on codes inside the virtual network. This workaround still has a bit problem because developers usually prefer to work with their own machine they are used to.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. DevOps agent access&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I see two options to solve Azure Pipelines agent access problems in the previous projects. The first option is to manage a &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/agents#install"&gt;Self-hosted agent&lt;/a&gt; where you build, test, and deploy your code to Azure PaaS resources in Azure Pipelines. This self-hosted agenet is located inside the virtual network, so it can access all of customers' Azure PaaS resources. You can set up your agent on Linux, macOS, Windows, and also on a Docker container. The downside of this options is you need to manage a IaaS virtual machine in your PaaS architecture, which causes you to maintain the compute infrastructure including update of OS, middleware, dependencies and tools. The other options is to create and remove firewall rules only when Azure Pipelines agent accesses to Azure PaaS resouces. For example, &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/deploy/sql-azure-dacpac-deployment"&gt;Azure SQL Database Deployment task&lt;/a&gt; has &lt;code&gt;IpDetectionMethod&lt;/code&gt; option that adds agent IP address range for allowed IP Address rules and &lt;code&gt;DeleteFirewallRule&lt;/code&gt; option that deletes the allowed IP address rule after the deployment. (Important notice is that this does not work for &lt;a href="https://docs.microsoft.com/en-us/azure/azure-sql/database/connectivity-settings#deny-public-network-access"&gt;Deny public network access to the Azure SQL Server&lt;/a&gt;.)&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Virtual Network architecture 2 - Deployment pipelines</title>
      <dc:creator>Kohei Kawata</dc:creator>
      <pubDate>Mon, 29 Aug 2022 14:38:00 +0000</pubDate>
      <link>https://dev.to/koheikawata/virtual-network-architecture-2-deployment-pipelines-39p5</link>
      <guid>https://dev.to/koheikawata/virtual-network-architecture-2-deployment-pipelines-39p5</guid>
      <description>&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;This article is Part.2 of virtual network architecture series. I will share the details of the entire pipeline in the sample template &lt;a href="https://github.com/koheikawata/api-management-vnet"&gt;api-management-vnet&lt;/a&gt;. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-1-do-i-need-virtual-network-1nhk"&gt;Virtual Network architecture 1 - Do I need virtual network?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-2-deployment-pipelines-39p5"&gt;Virtual Network architecture 2 - Deployment pipelines&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-3-key-vault-private-endpoint-43nj"&gt;Virtual Network architecture 3 - Key Vault Private Endpoint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-4-sql-database-private-endpoit-49o2"&gt;Virtual Network architecture 4 - SQL Database Private Endpoit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-5-app-service-private-endpoint-5eao"&gt;Virtual Network architecture 5 - App Service Private Endpoint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-6-service-bus-private-endpoint-oa"&gt;Virtual Network architecture 6 - Service Bus Private Endpoint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-7-self-hosted-agent-32j4"&gt;Virtual Network architecture 7 - Self-hosted agent&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  TOC
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Overview&lt;/li&gt;
&lt;li&gt;Architecture&lt;/li&gt;
&lt;li&gt;
Pipelines

&lt;ul&gt;
&lt;li&gt;Two pipelines overview&lt;/li&gt;
&lt;li&gt;pipeline-base.yml&lt;/li&gt;
&lt;li&gt;pipeline-vnet1.yml&lt;/li&gt;
&lt;li&gt;pipeline-vnet2.yml&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Overview &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;The sample template &lt;a href="https://github.com/koheikawata/api-management-vnet"&gt;api-management-vnet&lt;/a&gt; includes two scenario - 1) Base architecture without virtual network, 2) Virtual network architecture. Both architectures use the same Azure services below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Used in both architectures&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/get-started/what-is-azure-pipelines"&gt;Azure Pipelines&lt;/a&gt;: Deploy infrastructure, build and deploy codes, build and deploy self-hosted agent, execute integration tests by sending REST API requests.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.microsoft.com/en-us/azure/api-management/api-management-key-concepts"&gt;Azure API Management&lt;/a&gt;: Platform to manage APIs behind which are running on Azure App Service in this template.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.microsoft.com/en-us/azure/app-service/overview"&gt;Azure App Service&lt;/a&gt;: Hosting service of web APIs.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-overview"&gt;Azure Functions&lt;/a&gt;: Serverless hosting service that runs codes.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.microsoft.com/en-us/azure/azure-sql/azure-sql-iaas-vs-paas-what-is-overview"&gt;Azure SQL&lt;/a&gt;: SQL Server on the Azure cloud.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.microsoft.com/en-us/azure/key-vault/general/overview"&gt;Azure Key Vault&lt;/a&gt;: Secret management service that stores keys and certificates accessible from other Azure services.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-messaging-overview"&gt;Azure Service Bus&lt;/a&gt;: Message broker in the Azure cloud with message queue and pub-sub topics.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Only in virtual network architecture&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.microsoft.com/en-us/azure/application-gateway/overview"&gt;Azure Application Gateway&lt;/a&gt;: A Web traffic load balancer that works as a gateway of virtual network architecture in this template.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Architecture &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Both architectures have the same Web API, Functions, Database, and Service Bus topic. Azure Pipelines conducts the same integration tests that sends API requests to the Web API and Service Bus topic. There are two important differences in the virtual network architecture from the base architecture.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Application Gateway resides in front of the API Management. API Management and App Service cannot be accessed from the internet but only through the Application Gateway. So, the API endpoint is different.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Self-hosted agent is built in the first pipeline and deployed inside the virtual network subnet. The second pipeline runs on this self-hosted agent so it can access the private endpoints of App Service and Service Bus.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Base architecture&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cmyns7Uu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ovbinoqu1gli5lp9ip83.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cmyns7Uu--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ovbinoqu1gli5lp9ip83.png" alt="Image description" width="880" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Virtual network architecture&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--KuGgxV01--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ilckahhhj7ue7qvomw9x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--KuGgxV01--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ilckahhhj7ue7qvomw9x.png" alt="Image description" width="880" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Pipelines &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;You can run the two pipelines by following &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/docs/Getting-started.md"&gt;getting-started&lt;/a&gt; documentation. In this article, I am going to explain the details of pipeline steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two pipelines overview &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Base architecture has only &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/pipelines/pipeline-base.yml"&gt;pipeline-base.yml&lt;/a&gt; but Virtual network architecture includes &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/pipelines/pipeline-vnet1.yml"&gt;pipeline-vnet1.yml&lt;/a&gt; and &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/pipelines/pipeline-vnet2.yml"&gt;pipeline-vnet2.yml&lt;/a&gt;. The first pipeline of virtual network architecture deploys Azure resources without private endtpoints and builds a self-hosted agent, and the second pipeline runs on the self-hosted agent and deploys and sets up private points for resources. In the end of the second pipeline, it executes integration tests inside virtual network.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--F1xIYRVF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nzyy65sw2p87qncsp7nn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--F1xIYRVF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nzyy65sw2p87qncsp7nn.png" alt="Image description" width="880" height="505"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  pipeline-base.yml &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Step 1.&lt;/strong&gt; In &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/pipelines/templates/keyvault-secret.yml"&gt;keyvault-secret.yml&lt;/a&gt;, the pipeline creates Key Vault if it does not exist and pass secrets input from Azure Pipelines variables so you do not need to keep those secrets in the Azure Pipeline variables but after the second time the pipeline automatically detects pipeline variable and download secrets from existing Key Vault. This technique is described in &lt;a href="https://dev.to/koheikawata/azure-pipelines-secret-management-with-key-vault-3e01"&gt;Azure Pipelines secret management with Key Vault&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2.&lt;/strong&gt; &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/pipelines/templates/azure-resource.yml"&gt;azure-resource.yml&lt;/a&gt; deploys Azure resources with &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/src/bicep/base/main.bicep"&gt;main.bicep&lt;/a&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need to specify the secret variables handled in the previous Key Vault step. If you have more secret variables for improvement, you need to add variables here.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- job: AzureResourceDeployment
    variables:
    - template: ./variables.yml
    - name: SqlPass
      value: $[ stageDependencies.KeyVault.SQL_PASS.outputs['SQL_PASS.SQL_PASS'] ]
    - name: AadSecretClient
      value: $[ stageDependencies.KeyVault.AAD_SECRET_CLIENT.outputs['AAD_SECRET_CLIENT.AAD_SECRET_CLIENT'] ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Azure API Management takes around one hour to deploy. In order to avoid a time out error, it specifies &lt;code&gt;timeout: 0&lt;/code&gt;. However, I found errors sometimes after one hour. If you face a timeout error, you can just re-run the pipeline, and you will see everything goes smoothly after that.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- job: AzureResourceDeployment
    timeoutInMinutes: 0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3.&lt;/strong&gt; &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/pipelines/templates/sql.yml"&gt;sql.yml&lt;/a&gt; deploys SQL Database schema with &lt;code&gt;.dacpac&lt;/code&gt; file built by &lt;a href="https://github.com/koheikawata/api-management-vnet/tree/main/src/apps/WeatherDb"&gt;WeatherDB&lt;/a&gt; SQL project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4.&lt;/strong&gt; &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/pipelines/templates/func.yml"&gt;func.yml&lt;/a&gt; deploys the .NET C# code of &lt;a href="https://github.com/koheikawata/api-management-vnet/tree/main/src/apps/ServicebusListener"&gt;ServicebusListener&lt;/a&gt; to Azure Functions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5.&lt;/strong&gt; &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/pipelines/templates/webapi.yml"&gt;webapi.yml&lt;/a&gt; deploys the .NET C# code of &lt;a href="https://github.com/koheikawata/api-management-vnet/tree/main/src/apps/WeatherAPI"&gt;WeatherAPI&lt;/a&gt; project to Azure App Service. Furthermore, it defines the API in the API Management and generates &lt;code&gt;swagger.json&lt;/code&gt; and deploys it to the API Management.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- task: DotNetCoreCLI@2
  displayName: dotnet new tool-manifest
  inputs:
    command: custom
    custom: new
    arguments: tool-manifest
    workingDirectory: ${{ parameters.apiProjectDirectory }}
- task: DotNetCoreCLI@2
  displayName: dotnet tool install
  inputs:
    command: custom
    custom: tool
    arguments: install Swashbuckle.AspNetCore.Cli --version $(SWASHBUCKLE_VERSION)
    workingDirectory: ${{ parameters.apiProjectDirectory }}
- task: DotNetCoreCLI@2
  displayName: dotnet swagger tofile
  inputs:
    command: custom
    custom: swagger
    arguments: tofile --output ${{ parameters.swaggerPath }} ${{ parameters.apiDllPath }} $(SWAGGER_VERSION)
    workingDirectory: ${{ parameters.apiProjectDirectory }}
- task: AzureCLI@2
  displayName: Deploy API to API Management
  inputs:
    azureSubscription: ${{ parameters.azureSvcName }}
    scriptType: bash
    scriptLocation: inlineScript
    inlineScript: |
      az apim api import -g $(RESOURCE_GROUP_NAME) \
        --service-name $(API_MANAGEMENT_NAME) \
        --api-id $(API_NAME) \
        --path /$(API_NAME) \
        --specification-format OpenApiJson \
        --specification-path ${{ parameters.swaggerPath }} \
        --service-url $(API_MANAGEMENT_SERVICE_URL)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 6.&lt;/strong&gt; &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/pipelines/templates/integration-test-powershell.yml"&gt;integration-test-powershell.yml&lt;/a&gt; executes integration tests for deployed Web API and Functions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;With the powershell script below, it extracts OAuth2.0 token from Azure Active Directory so it can attach the token later when sending a REST request to the API Management. Because the Azure Pipelines agent sends a request with automation, it follows Client Credential Flow of OAuth2.0. This OAuth2.0 flow is explained in &lt;a href="https://dev.to/koheikawata/azure-ad-app-configuration-for-web-api-io2"&gt;Azure AD app configuration for Web API&lt;/a&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$clientSecret = Get-AzKeyVaultSecret -VaultName $env:KEYVAULT_NAME -Name $env:KVSECRET_NAME_AADCLIENT -AsPlainText
$authorizeUri = "https://login.microsoftonline.com/${{ parameters.aadTenantId }}/oauth2/v2.0/token"
$body = 'grant_type=client_credentials' + `
'&amp;amp;client_id=${{ parameters.aadAppidClient }}' + `
'&amp;amp;client_secret=' + $clientSecret + `
'&amp;amp;scope=api://${{ parameters.aadAppidBackend }}/.default'
$token = (Invoke-RestMethod -Method Post -Uri $authorizeUri -Body $body).access_token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The base architecture does not have Application Gateway and the endpoint is at the API Management. The route &lt;code&gt;/Weatherforecast&lt;/code&gt; is to GET and POST the data to Azure SQL Databse and &lt;code&gt;/Weatherforecast/message&lt;/code&gt; to send a message to Service Bus topic, which triggers Azure Functions and inserts that record in SQL Database.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;parameters:
  url: https://$(API_MANAGEMENT_NAME).azure-api.net
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$Uri = "${{ parameters.url }}/$env:API_NAME/Weatherforecast"
$UriMessage = "${{ parameters.url }}/$env:API_NAME/Weatherforecast/message"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  pipeline-vnet1.yml &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/pipelines/pipeline-vnet1.yml"&gt;pipeline-vnet1.yml&lt;/a&gt; includes steps below.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Store secrets from Pipeline variables to Key Vault&lt;/li&gt;
&lt;li&gt;Generate SSL certificate for Application Gateway backend pool&lt;/li&gt;
&lt;li&gt;Deploy Azure services&lt;/li&gt;
&lt;li&gt;Build a self-hosted agent&lt;/li&gt;
&lt;li&gt;Deploy projects to App Service, Functions, and SQL Database&lt;/li&gt;
&lt;li&gt;Execute integration tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After this pipeline is executed, some resources such as App Service, Key Vault, SQL Database, Functions, and Service Bus are not protected within the virtual network. Only Application Gateway and API Management are connected to and located in the virtual network. The self-hosted agent is built and deployed to Azure Container Instances and ready for usage.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ioplQOQo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xlxk7oevr6c9tit15v7v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ioplQOQo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xlxk7oevr6c9tit15v7v.png" alt="Image description" width="880" height="374"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1.&lt;/strong&gt; &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/pipelines/templates/cert-appgateway.yml"&gt;cert-appgateway.yml&lt;/a&gt; generates and sets in Key Vault a SSL certificate for the backend pool of API Management. The Key Vault requires to grant the access right for Azure Pipeliens Microsoft hosted agent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Set-AzKeyVaultAccessPolicy -VaultName $env:KEYVAULT_NAME -PermissionsToCertificates get,create -ObjectId ${{ parameters.aadObjectidSvc }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2.&lt;/strong&gt; &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/pipelines/templates/keyvault-secret.yml"&gt;keyvault-secret.yml&lt;/a&gt; is the same template as used in the Base architecture. This time it adds &lt;code&gt;AZP_TOKEN&lt;/code&gt; secret variable that is needed to deploy a self-hosted agent. How to get the personal access token is described in &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/docs/Getting-started.md#personal-access-token-for-self-hosted-agent"&gt;Getting-started&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3.&lt;/strong&gt; &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/pipelines/templates/azure-resource.yml"&gt;azure-resource.yml&lt;/a&gt; deploys Azure resources with &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/src/bicep/vnet/main1.bicep"&gt;main1.bicep&lt;/a&gt;. All the virtual networks and subnets are deployed this time with &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/src/bicep/vnet/modules/VirtualNetwork.bicep"&gt;VirtualNetwork.bicep&lt;/a&gt;. All the virtual network IP address ranges are defined in &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/src/bicep/vnet/main.parameters.json"&gt;main.parameters.json&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4.&lt;/strong&gt; &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/pipelines/templates/restart-appgateway.yml"&gt;restart-appgateway.yml&lt;/a&gt; restarts Application Gateway. I faced errors without restarting Application Gateway. According to the Microsoft documentation &lt;a href="https://docs.microsoft.com/en-us/azure/application-gateway/application-gateway-backend-health-troubleshooting#updates-to-the-dns-entries-of-the-backend-pool"&gt;Updates to the DNS entries of the backend pool&lt;/a&gt;, when you change DNS entries for the backend pool, Application Gateway must be restarted. In the Azure resource deployment with &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/src/bicep/vnet/modules/PrivateDns1.bicep"&gt;PrivateDns1.bicep&lt;/a&gt;, it creates the DNS A record for the API Management. Then you need to restart the Application Gateway.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource PrivateDnsAApim 'Microsoft.Network/privateDnsZones/A@2020-06-01' = {
  name: '${PrivateDnsApim.name}/${ApiManagementName}'
  properties: {
    ttl: 3600
    aRecords: [
      {
        ipv4Address: ApiManagementPrivateIPAddress
      }
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 5.&lt;/strong&gt; &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/pipelines/templates/self-hosted-agent.yml"&gt;self-hosted-agent.yml&lt;/a&gt; builds and deploys a Linux based self-hosted agent.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;As of August 2022, you can run a Windows based self-hosted agent in the docker container but Windows based Azure Container Instance cannot be deployed on the virtual network, according to the Microsoft documentation &lt;a href="https://docs.microsoft.com/en-us/azure/container-instances/container-instances-vnet"&gt;Deploy container instances into an Azure virtual network&lt;/a&gt;. Windows based self-hosted agent is needed to deploy SQL Database projects, but for now the Windows self-hosted agent build part is commented out.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The self-hosted agent container application is defined in &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/src/self-hosted-agent/linux/Dockerfile"&gt;Dockerfile&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;After the container image is pushed into Azure Container Registry, the deployment to Azure Container Instance is implemented in &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/src/bicep/self-hosted-agent/linux-agent.bicep"&gt;linux-agent.bicep&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 6.&lt;/strong&gt; &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/pipelines/templates/sql.yml"&gt;sql.yml&lt;/a&gt; is the same as used in the Base pipeline and deploys SQL Database schema with &lt;code&gt;.dacpac&lt;/code&gt; file built by &lt;a href="https://github.com/koheikawata/api-management-vnet/tree/main/src/apps/WeatherDb"&gt;WeatherDB&lt;/a&gt; SQL project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 7.&lt;/strong&gt; &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/pipelines/templates/func.yml"&gt;func.yml&lt;/a&gt; is the same as used in the Base pipeline and deploys the .NET C# code of &lt;a href="https://github.com/koheikawata/api-management-vnet/tree/main/src/apps/ServicebusListener"&gt;ServicebusListener&lt;/a&gt; to Azure Functions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 8.&lt;/strong&gt; &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/pipelines/templates/webapi.yml"&gt;webapi.yml&lt;/a&gt; is the same as used in the Base pipeline and deploys the .NET C# code of &lt;a href="https://github.com/koheikawata/api-management-vnet/tree/main/src/apps/WeatherAPI"&gt;WeatherAPI&lt;/a&gt; project to Azure App Service and defines the API in the API Management and generates &lt;code&gt;swagger.json&lt;/code&gt; and deploys it to the API Management.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 9.&lt;/strong&gt; &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/pipelines/templates/integration-test-powershell.yml"&gt;integration-test-powershell.yml&lt;/a&gt; is the same as used in the Base pipeline and executes integration tests for deployed Web API and Functions.&lt;/p&gt;

&lt;h2&gt;
  
  
  pipeline-vnet2.yml &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/pipelines/pipeline-vnet2.yml"&gt;pipeline-vnet2.yml&lt;/a&gt; includes steps below.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Execute integration tests before setting private endpoints&lt;/li&gt;
&lt;li&gt;Deploy and set private endpoints&lt;/li&gt;
&lt;li&gt;Execute integration tests for private endpoints&lt;/li&gt;
&lt;li&gt;Deploy projects to App Service and Functions with private endpoints by the self-hosted agent&lt;/li&gt;
&lt;li&gt;Execute integration tests for code deployment through private endpoints.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This pipeline deploys and set private endpoints so all Azure services are protected by the virtual network. You cannot deploy new software to the App Service and Functions any more as they are not allowed to access through the Internet. The self-hosted agent running on Azure Container Instance is located inside the virtual network and capable of building and deploying the new software.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--tqw8RLsN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pv7fuvxrpgk2zu47oews.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tqw8RLsN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pv7fuvxrpgk2zu47oews.png" alt="Image description" width="880" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see this pipeline use the self-hosted agent by specifying the pool like below. All steps below can be executed on a Linux based agent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pool:
  name: Default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 1.&lt;/strong&gt; &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/pipelines/templates/integration-test-bash.yml"&gt;integration-test-bash.yml&lt;/a&gt; executes integration tests before private endpoints are deployed and set up. The self-hosted agent is Linux based, and this integration test is written in bash scripts. Obtaining Azure Active Directory OAuth2.0 token by bash script is written in the way below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;clientSecret=$(az keyvault secret show --vault-name $(KEYVAULT_NAME) --name $(KVSECRET_NAME_AADCLIENT) --query value -o tsv)
authorizeUri="https://login.microsoftonline.com/${{ parameters.aadTenantId }}/oauth2/v2.0/token"
token=$(curl -X POST $authorizeUri -d "client_id=${{ parameters.aadAppidClient }}&amp;amp;grant_type=client_credentials&amp;amp;scope=api://${{ parameters.aadAppidBackend }}/.default&amp;amp;client_secret=$clientSecret" | jq ".access_token" | tr -d \")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2.&lt;/strong&gt; &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/pipelines/templates/private-endpoints.yml"&gt;private-endpoints.yml&lt;/a&gt; deploys Azure resources related to private endpoints with &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/src/bicep/vnet/main2.bicep"&gt;main2.bicep&lt;/a&gt;. All the virtual network IP address ranges are defined in &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/src/bicep/vnet/main.parameters.json"&gt;main.parameters.json&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3.&lt;/strong&gt; &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/pipelines/templates/restart-appgateway.yml"&gt;restart-appgateway.yml&lt;/a&gt; restarts Application Gateway. This should be executed for the same reason as the V-net pipeline 1 because private DNS A records for Key Vault, App Service, SQL Database, Funtions, and Service Bus are added.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4.&lt;/strong&gt; &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/pipelines/templates/integration-test-bash.yml"&gt;integration-test-bash.yml&lt;/a&gt; executes integration tests after private endpoints are set. This step can make sure all the configurations of private endpoints are correctly deployed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5.&lt;/strong&gt; &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/pipelines/templates/func.yml"&gt;func.yml&lt;/a&gt; is the same as used in the Base pipeline but this time is executed on the self-hosted agent. Functions do not have accessible public IP any more, and it should be connected through the private IP inside the virtual network.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 6.&lt;/strong&gt; &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/pipelines/templates/webapi.yml"&gt;webapi.yml&lt;/a&gt; is the same as used in the Base pipeline but this time is executed on the self-hosted agent. The App Service does not have accessible public IP any more, and it should be connected through the private IP inside the virtual network.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 7.&lt;/strong&gt; &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/pipelines/templates/integration-test-bash.yml"&gt;integration-test-bash.yml&lt;/a&gt; executes integration tests to make sure the code deployment to App Service and Funtions are correctly done through the self-hosted agent.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Virtual Network architecture 3 - Key Vault Private Endpoint</title>
      <dc:creator>Kohei Kawata</dc:creator>
      <pubDate>Mon, 29 Aug 2022 14:38:00 +0000</pubDate>
      <link>https://dev.to/koheikawata/virtual-network-architecture-3-key-vault-private-endpoint-43nj</link>
      <guid>https://dev.to/koheikawata/virtual-network-architecture-3-key-vault-private-endpoint-43nj</guid>
      <description>&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;This article is Part.3 of virtual network architecture series. I will share the details of the private endpoint with Key Vault in &lt;a href="https://github.com/koheikawata/api-management-vnet"&gt;api-management-vnet&lt;/a&gt;. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-1-do-i-need-virtual-network-1nhk"&gt;Virtual Network architecture 1 - Do I need virtual network?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-2-deployment-pipelines-39p5"&gt;Virtual Network architecture 2 - Deployment pipelines&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-3-key-vault-private-endpoint-43nj"&gt;Virtual Network architecture 3 - Key Vault Private Endpoint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-4-sql-database-private-endpoit-49o2"&gt;Virtual Network architecture 4 - SQL Database Private Endpoit&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-5-app-service-private-endpoint-5eao"&gt;Virtual Network architecture 5 - App Service Private Endpoint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-6-service-bus-private-endpoint-oa"&gt;Virtual Network architecture 6 - Service Bus Private Endpoint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/virtual-network-architecture-7-self-hosted-agent-32j4"&gt;Virtual Network architecture 7 - Self-hosted agent&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  TOC
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Private Endpoint configuration&lt;/li&gt;
&lt;li&gt;Access to Key Vault&lt;/li&gt;
&lt;li&gt;Application Gateway SSL certificate&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Private Endpoint configuration &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Private Endpoint&lt;/strong&gt;: Deploy the private endpoint and connect it to Key Vault and Key Vault's subnet in &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/src/bicep/vnet/modules/PrivateEndpoint.bicep"&gt;PrivateEndpoint.bicep&lt;/a&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;KeyVaultId:existingKv.id
VirtualNetwork2SubnetIdKv:existingVnet2.properties.subnets[3].id

properties: {
  privateLinkServiceConnections: [
    {
      name: pe_name_kv
      properties: {
        privateLinkServiceId: KeyVaultId
        groupIds: [
          'vault'
        ]
      }
    }
  ]
  subnet: {
    id: VirtualNetwork2SubnetIdKv
    properties: {
      privateEndpointNetworkPolicies: 'Enabled'
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Private DNS&lt;/strong&gt;: Deploy Private DNS with the DNS name &lt;code&gt;privatelink.vaultcore.azure.net&lt;/code&gt; in &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/src/bicep/vnet/modules/PrivateDns2.bicep"&gt;PrivateDns2.bicep&lt;/a&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var pdns_name_kv = 'privatelink.vaultcore.azure.net'

resource PrivateDnsKv 'Microsoft.Network/privateDnsZones@2020-06-01' = {
  name: pdns_name_kv
  location: 'global'
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Virtual network link&lt;/strong&gt;: Link the deployed Private DNS to the virtual network where the Key Vault subnet exists in &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/src/bicep/vnet/modules/PrivateDns2.bicep"&gt;PrivateDns2.bicep&lt;/a&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VirtualNetwork2Id:existingVnet2.id

resource VnetLinkKv 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = {
  name: '${PrivateDnsKv.name}/${PrivateDnsKv.name}-linkc'
  location: 'global'
  properties: {
    registrationEnabled: false
    virtualNetwork: {
      id: VirtualNetwork2Id
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Private DNS A record&lt;/strong&gt;: Create a DNS A record and set the IP address from the deployed private endpoint in &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/src/bicep/vnet/modules/PrivateDns2.bicep"&gt;PrivateDns2.bicep&lt;/a&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;output PrivateEndpointKvIpAddress string = PrivateEndpointKv.properties.customDnsConfigs[0].ipAddresses[0]

resource PrivateDnsAKv 'Microsoft.Network/privateDnsZones/A@2020-06-01' = {
  name: '${PrivateDnsKv.name}/${KeyVaultName}'
  properties: {
    ttl: 3600
    aRecords: [
      {
        ipv4Address: PrivateEndpointKvIpAddress
      }
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Access to Key Vault &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;App Service&lt;/strong&gt; extracts credentials from the Key Vault with Key Vault reference through the Private Endpoint. The outbound from App Service is through the v-net integration. The Key Vault reference is defined in &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/src/bicep/vnet/modules/AppService.bicep"&gt;AppService.bicep&lt;/a&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource AppServiceConfig 'Microsoft.Web/sites/config@2021-02-01' = {
  properties: {
    'Sql:ConnectionString': '@Microsoft.KeyVault(VaultName=${kv_name};SecretName=${kvsecret_name_sqlcs})'
    'Servicebus:ConnectionString': '@Microsoft.KeyVault(VaultName=${kv_name};SecretName=${kvsecret_name_sbcs})'
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Functions&lt;/strong&gt; extracts credentials from the Key Vault in the same way as the App Service.The Key Vault reference is defined in &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/src/bicep/vnet/modules/FunctionApp.bicep"&gt;FunctionApp.bicep&lt;/a&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource FuncConfig 'Microsoft.Web/sites/config@2021-02-01' = {
  properties: {
    'ServiceBusConnectionString': '@Microsoft.KeyVault(VaultName=${kv_name};SecretName=${kvsecret_name_sbcs})'
    'SqlConnectionString': '@Microsoft.KeyVault(VaultName=${kv_name};SecretName=${kvsecret_name_sqlcs})'
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Container Instance&lt;/strong&gt; running the self-hosted agent extracts Azure Active Directory client app secret from the Key Vault to get the Azure Active Directory OAuth2.0 token for the integration test in &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/pipelines/templates/integration-test-bash.yml"&gt;integration-test-bash.yml&lt;/a&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;clientSecret=$(az keyvault secret show --vault-name $(KEYVAULT_NAME) --name $(KVSECRET_NAME_AADCLIENT) --query value -o tsv)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Application Gateway&lt;/strong&gt; implements TLS termination for the backend pool, which means the API Management, with the certificate stored in the Key Vault. Application Gateway is not capable of accessing Key Vault through Private Endpoint but through Service Endpoint.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var agw_kv_secret_id = 'https://${kv_name}${environment().suffixes.keyvaultDns}/secrets/${kvcert_name_agw}'

sslCertificates: [
  {
    name: agw_ssl_certificate_name
    properties: {
      keyVaultSecretId: agw_kv_secret_id
    }
  }
]

httpListeners: [
  {
    properties: {
      sslCertificate: {
        id: resourceId('Microsoft.Network/applicationGateways/sslCertificates', agw_name, agw_ssl_certificate_name)
      }
    }
  }
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--nykCofn_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g71pxjlpaxdbbve03bm7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--nykCofn_--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g71pxjlpaxdbbve03bm7.png" alt="Image description" width="880" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Application Gateway SSL certificate &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;The problem is that Application Gateway cannot access Key Vault through Private Endpoint to extract SSL certificate in the configuration above, as of August 2022. This is discussed in the forum &lt;a href="https://github.com/MicrosoftDocs/azure-docs/issues/33157"&gt;Application Gateway: Integration with Key Vault does not work #33157&lt;/a&gt;. In sum, Application Gateway has to access Key Vault's public IP through &lt;a href="https://docs.microsoft.com/en-us/azure/virtual-network/virtual-network-service-endpoints-overview"&gt;Service Endpoint&lt;/a&gt;, not through private IP.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Service Endpoint&lt;/strong&gt;: To enable Service Endpoint for this case, you have to add the configuration when deploying the virtual network subnet for the Key Vault. You can see &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/src/bicep/vnet/modules/VirtualNetwork.bicep"&gt;VirtualNetwork.bicep&lt;/a&gt; and find the configuration below. The Service Endpoint configuration should be implemented on the virtual network subnet where the service that wants to access Key Vault, which means Application Gateway subnet.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource VirtualNetwork1 'Microsoft.Network/virtualNetworks@2021-03-01' = {
  properties: {
    subnets: [
      {
        name: snet_name_1_agw
        properties: {
          addressPrefix: snet_prefix_1_agw
          networkSecurityGroup: {
            id: Nsg1AgwId
          }
          serviceEndpoints: [
            {
              service: 'Microsoft.KeyVault'
              locations: [
                '*'
              ]
            }
          ]
        }
      }
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Public network access&lt;/strong&gt; should be enabled because Application Gateway reaches out to the Key Vault through the public IP, but you can limit the access only from the Application Gateway subnet in &lt;a href="https://github.com/koheikawata/api-management-vnet/blob/main/src/bicep/vnet/modules/KeyVault2.bicep"&gt;KeyVault2.bicep&lt;/a&gt;. &lt;code&gt;bypass: 'AzureServices'&lt;/code&gt; enables all Azure services to access the Key Vault through its public IP, &lt;code&gt;virtualNetworkRules: [{id: VirtualNetwork1SubnetIdAgw}]&lt;/code&gt; allows access from Application Gateway subnet, and &lt;code&gt;defaultAction: 'Deny'&lt;/code&gt; shut down all the access except Azure services and the Application Gateway virtual network subnet. All the access other than Application Gateway SSL certificate are through the Private Endpoint.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource KeyVault 'Microsoft.KeyVault/vaults@2021-10-01' = {
  properties: {
    publicNetworkAccess: 'Enabled'
    networkAcls: {
      bypass: 'AzureServices'
      defaultAction: 'Deny'
      virtualNetworkRules: [
        {
          id: VirtualNetwork1SubnetIdAgw
        }
      ]
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>Azure IoT Edge Integration Test template - Part.1</title>
      <dc:creator>Kohei Kawata</dc:creator>
      <pubDate>Sun, 28 Aug 2022 10:31:00 +0000</pubDate>
      <link>https://dev.to/koheikawata/azure-iot-edge-integration-test-template-part1-3911</link>
      <guid>https://dev.to/koheikawata/azure-iot-edge-integration-test-template-part1-3911</guid>
      <description>&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;This is a part of article series introducing Azure IoT Edge integration template - &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template"&gt;azure-iot-edge-integration-test-template&lt;/a&gt;. In this Part.1, I will explain the entire pipeline including Infrastructure as Code and integration test. Part.2 would provide the information about each IoT Edge module, and Part.3 share about the IoT Edge manifest generator.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/azure-iot-edge-integration-test-template-part2-378l"&gt;Azure IoT Edge Integration Test template - Part.2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/azure-iot-edge-integration-test-template-part3-5eld"&gt;Azure IoT Edge Integration Test template - Part.3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  TOC
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Overview&lt;/li&gt;
&lt;li&gt;Architecture&lt;/li&gt;
&lt;li&gt;Test steps&lt;/li&gt;
&lt;li&gt;
Infrastructure deployment

&lt;ul&gt;
&lt;li&gt;Network Security Group - inbound Port 22&lt;/li&gt;
&lt;li&gt;VM domain name label&lt;/li&gt;
&lt;li&gt;IoT Hub consumer group&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
Setup and installation

&lt;ul&gt;
&lt;li&gt;VM Service Connection&lt;/li&gt;
&lt;li&gt;Blob Contributor Role&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
Code deployment and test execution

&lt;ul&gt;
&lt;li&gt;edge-module.yml&lt;/li&gt;
&lt;li&gt;test-prep.yml&lt;/li&gt;
&lt;li&gt;test-execution.yml&lt;/li&gt;
&lt;li&gt;test-cleanup.yml&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Overview &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;When you have multiple Azure IoT Edge modules on an edge device and want to update codes of one of those modules, you can make sure the code quality by implementing linter and unit tests, but it is difficult to validate communications among modules. That is why executing integration tests every time you update the software.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7sYv5ejq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ee70envc6jzk8o8g1v4a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7sYv5ejq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ee70envc6jzk8o8g1v4a.png" alt="Image description" width="880" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this template, you can execute integration tests of Azure IoT Edge modules on Test Environment on Azure Virtual Machine. The test procedure is all automated by Azure Pipelines. By taking advantage of this template, you deploy and execute integration tests before deploying your code to the edge device.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--EXkC1EVP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j7eutd23kim7ysm7mzjf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--EXkC1EVP--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j7eutd23kim7ysm7mzjf.png" alt="Image description" width="880" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Architecture &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;This template includes six IoT Edge sample modules - &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/tree/main/src/apps/FileGenerator"&gt;FileGenerator&lt;/a&gt;, &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/tree/main/src/apps/FileUpdater"&gt;FileUpdater&lt;/a&gt;, &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/tree/main/src/apps/FileUploader"&gt;FileUploader&lt;/a&gt;, &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/tree/main/src/apps/IothubConnector"&gt;IothubConnector&lt;/a&gt;, &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/tree/main/src/apps/WeatherObserver"&gt;WeatherObserver&lt;/a&gt;, and &lt;a href="https://docs.microsoft.com/en-us/azure/iot-edge/how-to-store-data-blob"&gt;LocalBlobStorage&lt;/a&gt;. Details of IoT Edge modules are explained in &lt;a href="https://dev.to/koheikawata/azure-iot-edge-integration-test-template-part2-378l"&gt;Azure IoT Edge Integration Test template - Part.2&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--ggo4SCvA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vpgl4y6bkggnab9808ue.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--ggo4SCvA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vpgl4y6bkggnab9808ue.png" alt="Image description" width="880" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Test steps &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;You need to execute three steps, &lt;strong&gt;1) Infrastructure deployment&lt;/strong&gt;, &lt;strong&gt;2) Setup and installation&lt;/strong&gt;, and &lt;strong&gt;3) Code deployment and test execution&lt;/strong&gt;. Everything to know to run this template is described in &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/docs/Getting-started.md"&gt;Getting-started&lt;/a&gt;, but I am going instruct details and important points for those pipelines. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--I-pTYqSD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/15gr4qrzjpbj76limxtm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--I-pTYqSD--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/15gr4qrzjpbj76limxtm.png" alt="Image description" width="880" height="141"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Infrastructure deployment &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Azure resources needed for this template are defined in &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/src/bicep/main.bicep"&gt;main.bicep&lt;/a&gt; and you can run the IaC(Infrastructure as Code) pipeline of &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/pipelines/iac.yml"&gt;iac.yml&lt;/a&gt; on Azure Pipelines.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_gDBf_sE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hcve1i48n2n5dbzvxel7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_gDBf_sE--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hcve1i48n2n5dbzvxel7.png" alt="Image description" width="880" height="557"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Network Security Group - inbound Port 22 &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/src/bicep/main.bicep"&gt;main.bicep&lt;/a&gt; defines a Network Security Group inbound rule that opens Port 22. This is necessary for you to access the VM through SSH when installing Azure IoT Edge in the next step and for Azure Pipelines agent to set up VM's directory and grant directory access permissions in &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/pipelines/templates/test-prep.yml"&gt;test-prep.yml&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;properties: {
  securityRules: [
    {
      name: 'SSH'
      properties: {
        protocol: 'Tcp'
        sourcePortRange: '*'
        destinationPortRange: '22'
        sourceAddressPrefix: '*'
        destinationAddressPrefix: '*'
        access: 'Allow'
        priority: 100
        direction: 'Inbound'
      }
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  VM domain name label &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Setting up the domain name label for the VM is important because IP Address is dynamically allocated and Azure Pipelines agent accesses with VM Service Connection descrited in &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/docs/Getting-started.md#create-ssh-service-connection"&gt;Create SSH Service Connection&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Host name should be &lt;code&gt;edge-{BASE_NAME}.{LOCATION}.cloudapp.azure.com&lt;/code&gt;, which is defined in &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/src/bicep/main.bicep"&gt;main.bicep&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var dns_label = 'edge-${base_name}'

resource PublicIp 'Microsoft.Network/publicIPAddresses@2021-05-01' = {
  name: public_ip_name
  location: location
  sku: {
    name: 'Basic'
  }
  properties: {
    publicIPAllocationMethod: 'Dynamic'
    publicIPAddressVersion: 'IPv4'
    dnsSettings: {
      domainNameLabel: dns_label
    }
    idleTimeoutInMinutes: 4
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  IoT Hub consumer group &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;It is better to create &lt;a href="https://docs.microsoft.com/en-us/azure/event-hubs/event-hubs-features#consumer-groups"&gt;consumer groups&lt;/a&gt; of IoT Hub. If you want to consume messages on the IoT Hub through another tool such as &lt;a href="https://docs.microsoft.com/en-us/azure/iot-fundamentals/howto-use-iot-explorer"&gt;Azure IoT Hub Explorer&lt;/a&gt; that would cause errors of the integration test. &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/src/bicep/main.bicep"&gt;main.bicep&lt;/a&gt; deploys the consumer group that dedicates to Azure Pipelines agent executing integration tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;param iothub_cg_name string
resource IoTHubConsumerGroup 'Microsoft.Devices/IotHubs/eventHubEndpoints/ConsumerGroups@2021-07-02' = {
  name: '${IoTHub.name}/events/${iothub_cg_name}'
  properties:{
    name: iothub_cg_name
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Setup and installation &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;h2&gt;
  
  
  VM Service Connection &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;In this template, it uses &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/library/service-endpoints?view=azure-devops&amp;amp;tabs=yaml#ssh-service-connection"&gt;SSH service connection&lt;/a&gt;. It is possible you use bash commands like &lt;code&gt;ssh testuser@edge-{BASE_NAME}.{LOCATION}.cloudapp.azure.com&lt;/code&gt;. By combining &lt;code&gt;retryCountOnTaskFailure&lt;/code&gt; of Azure Pipelines task, you can handle unstable connection errors of SSH.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blob Contributor Role &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;You need to set up manually &lt;code&gt;Storage Blob Data Contributor&lt;/code&gt; of &lt;a href="https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles"&gt;Azure built-in roles&lt;/a&gt; of Azure RBAC, which is tied to your Azure Subscription so Azure Pipelines agent can generate SAS token of Blob Storage. You need Azure Subscription Owner or User Access Administrator role.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;az role assignment create `
    --role "Storage Blob Data Contributor" `
    --assignee {Object ID of Azure Service Connection} `
    --scope "/subscriptions/{Azure Subscription ID}/resourceGroups/rg-{BASE_NAME}/providers/Microsoft.Storage/storageAccounts/st{BASE_NAME}"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--yi_V6nAv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rljjg2cspql0v29adylg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--yi_V6nAv--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rljjg2cspql0v29adylg.png" alt="Image description" width="880" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Code deployment and test execution &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;h2&gt;
  
  
  &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/pipelines/templates/edge-module.yml"&gt;edge-module.yml&lt;/a&gt;  &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Call this template with each IoT Edge module. This builds and pushes container images to Azure Container Registry.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use not Azure Pipeline &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/build/docker"&gt;Docker task&lt;/a&gt; but docker commands because it does not need to create &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/library/service-endpoints?view=azure-devops&amp;amp;tabs=yaml#docker-registry-service-connection"&gt;Docker Registry service connection&lt;/a&gt; manually. The process that Azure Pipelines extracts Azure Container Registry key, buil, and push is all automated.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;acrkey=$(az acr credential show --name $(ACR_NAME) --query passwords[0].value -o tsv)
cd ${{ parameters.dockerfileDirectory }}
docker login -u $(ACR_NAME) -p $acrkey $(ACR_NAME).azurecr.io
docker build --rm -f Dockerfile -t $(ACR_NAME).azurecr.io/${{ parameters.repositoryName }}:$(Build.BuildNumber) .
docker push $(ACR_NAME).azurecr.io/${{ parameters.repositoryName }}:$(Build.BuildNumber)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/pipelines/templates/test-prep.yml"&gt;test-prep.yml&lt;/a&gt; &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Call this template with IoT Edge modules as parameters. The parameters are used for iterative tasks that check if module images exist in Azure Container Registry and each module is running on IoT Edge runtime.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- template: ./templates/test-prep.yml
  parameters:
    azureSvcName: $(AZURE_SVC_NAME)
    vmSshSvcName: $(VM_SVC_NAME)
    EdgeImages:
      module1:
        name: IothubConnector
        repository: iothub-connector
        tag: $(Build.BuildNumber)
      module2:
        name: WeatherObserver
        repository: weather-observer
        tag: $(Build.BuildNumber)
      module3:
        name: FileGenerator
        repository: file-generator
        tag: $(Build.BuildNumber)
      module4:
        name: FileUploader
        repository: file-uploader
        tag: $(Build.BuildNumber)
      module5:
        name: FileUpdater
        repository: file-updater
        tag: $(Build.BuildNumber)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;SSH task

&lt;ul&gt;
&lt;li&gt;Remove &lt;code&gt;/edge&lt;/code&gt; directory to refresh the leftover of past test executions.&lt;/li&gt;
&lt;li&gt;Grant read, write, and execute permissions to the host machine directory. Azure IoT Edge Hub with &lt;code&gt;UID 1000&lt;/code&gt; and Azure IoT Edge local blob storage with &lt;code&gt;user ID 11000&lt;/code&gt; and &lt;code&gt;user group ID 11000&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/iot-edge/how-to-access-host-storage-from-module?view=iotedge-2020-11#host-system-permissions"&gt;Host system permissions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/iot-edge/how-to-store-data-blob?view=iotedge-2020-11#granting-directory-access-to-container-user-on-linux"&gt;Granting directory access to container user on Linux&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Install the tree command so it can show the host machine directory in the log.&lt;/li&gt;
&lt;li&gt;Restart Azure IoT Edge runtime because you deleted the directory that the current modules implement bind mount. To refresh the connection between modules and directory, you need to restart the runtime, otherwise it would cause errors.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if [ -d "/edge" ]
then
  sudo rm -r /edge
fi

sudo mkdir -p $(FILE_UPLOADER_DIR)
sudo chown -R 1000 $(FILE_UPLOADER_DIR)
sudo chmod -R 700 $(FILE_UPLOADER_DIR)

sudo mkdir -p $(FILE_UPDATER_DIR)
sudo chown -R 1000 $(FILE_UPDATER_DIR)
sudo chmod -R 700 $(FILE_UPDATER_DIR)

sudo mkdir -p $(LOCAL_BLOB_STORAGE_DIR)
sudo chown -R 11000:11000 $(LOCAL_BLOB_STORAGE_DIR)
sudo chmod -R 700 $(LOCAL_BLOB_STORAGE_DIR)

sudo apt-get install tree
tree /edge

sudo iotedge system restart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/pipelines/templates/test-execution.yml"&gt;test-execution.yml&lt;/a&gt;  &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Use acynchronous bash method &lt;code&gt;&amp;amp;&lt;/code&gt; so the Azure Pipelines agent sends a direct method request to IoT Hub and at the same time listens to messages on IoT Hub sent from IoT Edge modules. &lt;code&gt;--timeout&lt;/code&gt; is currently set &lt;code&gt;30&lt;/code&gt; sec. Sometimes the response from IoT Edge is slow and they do not respond and cause errors. 30 seconds is probably is good time to wait. If it is longer than 30 seconds, something is going wrong on Edge modules.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;az iot hub invoke-module-method --hub-name $(IOTHUB_NAME) --device-id $(IOTHUB_DEVICE_ID) --module-id IothubConnector --method-name request_weather_report --method-payload '{"city": "Tokyo"}' &amp;amp;
testResult=$(az iot hub monitor-events --hub-name $(IOTHUB_NAME) --device-id $(IOTHUB_DEVICE_ID) --module-id IothubConnector --cg $(IOTHUB_CONSUMER_GROUP) --timeout 30 -y)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--v0RDiB-K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dmnom35bh0avniq1up62.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--v0RDiB-K--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dmnom35bh0avniq1up62.png" alt="Image description" width="880" height="219"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/pipelines/templates/test-cleanup.yml"&gt;test-cleanup.yml&lt;/a&gt;   &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Remove all directory but keep the blob container &lt;code&gt;weather&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;az storage blob directory delete --account-name $(STORAGE_ACCOUNT_NAME) --container-name $(BLOB_CONTAINER_NAME) --directory-path $(TEST_ORGANIZATION_NAME) --recursive
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>Azure IoT Edge Integration Test template - Part.2</title>
      <dc:creator>Kohei Kawata</dc:creator>
      <pubDate>Sun, 28 Aug 2022 10:31:00 +0000</pubDate>
      <link>https://dev.to/koheikawata/azure-iot-edge-integration-test-template-part2-378l</link>
      <guid>https://dev.to/koheikawata/azure-iot-edge-integration-test-template-part2-378l</guid>
      <description>&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;This article is following Part.1 and sharing important tips for IoT Edge modules in &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template"&gt;azure-iot-edge-integration-test-template&lt;/a&gt;. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/azure-iot-edge-integration-test-template-part1-3911"&gt;Azure IoT Edge Integration Test template - Part.1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/azure-iot-edge-integration-test-template-part3-5eld"&gt;Azure IoT Edge Integration Test template - Part.3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  TOC
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Overview&lt;/li&gt;
&lt;li&gt;IothubConnector&lt;/li&gt;
&lt;li&gt;WeatherObserver&lt;/li&gt;
&lt;li&gt;FileGenerator&lt;/li&gt;
&lt;li&gt;FileUploader&lt;/li&gt;
&lt;li&gt;FileUpdater&lt;/li&gt;
&lt;li&gt;LocalBlobStorage&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Overview &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;This sample template has six IoT Edge modules and two integration test data flows. One is Azure Pipelines agent sending &lt;a href="https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-devguide-direct-methods#method-invocation-for-iot-edge-modules"&gt;direct method&lt;/a&gt; and receiving a weather report and weather report files uploaded to Azure Blob Storage. Another integration test flow is the direct method request asks to download the archive weather files.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IzXQbbfo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v5807htov7kp439nx9uy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IzXQbbfo--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v5807htov7kp439nx9uy.png" alt="Image description" width="880" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  IothubConnector &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;This module depends both on &lt;a href="https://docs.microsoft.com/en-us/python/api/azure-iot-device/azure.iot.device.iothubmoduleclient"&gt;IoTHubModuleClient&lt;/a&gt; and &lt;a href="https://github.com/ros2/rclpy"&gt;rclypy(ROS Client Library for Python)&lt;/a&gt;. You cannot run this module locally without &lt;a href="https://docs.ros.org/en/foxy/Installation/Ubuntu-Install-Debians.html#environment-setup"&gt;ROS2 environment setup&lt;/a&gt; or &lt;a href="https://github.com/Azure/iotedgehubdev"&gt;iotedgehubdev&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;There some options to keep the application running and waiting for callback. &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/src/apps/IothubConnector/main.py"&gt;IothubConnector&lt;/a&gt; uses the edge signal handler using &lt;a href="https://docs.python.org/3/library/threading.html"&gt;threading&lt;/a&gt; and &lt;a href="https://docs.python.org/3/library/signal.html"&gt;signal&lt;/a&gt;. On the other hand, &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/src/apps/FileGenerator/main.py"&gt;FileGenerator&lt;/a&gt; uses &lt;code&gt;rclpy.spin()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def module_termination_handler(signal, frame):
    print ("IoTHubClient sample stopped")
    stop_event.set()

stop_event = threading.Event()
signal.signal(signal.SIGTERM, module_termination_handler)
try:
    stop_event.wait()
except Exception as e:
    print("Unexpected error %s " % e)
    raise
finally:
    print("Shutting down client")
    module_client.shutdown()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Direct method callback from IoT Hub is implemented with &lt;code&gt;method_request_handler()&lt;/code&gt;. On receiving a direct method request, it sends a request message to &lt;code&gt;WeatherReporter&lt;/code&gt; through &lt;a href="https://docs.microsoft.com/en-us/azure/iot-edge/module-composition?view=iotedge-2020-11#declare-routes"&gt;Azure Iot Edge route communication&lt;/a&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async def method_request_handler(method_request):
    print('Direct Method: ', method_request.name, method_request.payload)
    if method_request.name in request_method_list:
        response_payload, response_status = await request_method_list[method_request.name](method_request.payload, module_client)
    else:
        response_payload = {"Response": "Direct method {} not defined".format(method_request.name)}
        response_status = 404

    method_response = MethodResponse.create_from_method_request(method_request, response_status, response_payload)
    await module_client.send_method_response(method_response)

module_client.on_method_request_received = method_request_handler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async def request_weather_report(payload: dict, module_client: IoTHubModuleClient):
    try:
        json_string = json.dumps(payload)
        message = Message(json_string)
        await module_client.send_message_to_output(message, 'reportRequest')
        return {"Response": "Send weather report request for {}".format(payload['city'])}, 200
    except Exception as e:
        print(e)
        return {"Response": "Invalid parameter"}, 400
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Azure IoT Edge route communication callback is implemented with &lt;code&gt;receive_message_handler()&lt;/code&gt;. It receives messages from &lt;code&gt;WeatherReport&lt;/code&gt; module and sends requests to &lt;code&gt;FileGenerator&lt;/code&gt; module through ROS2 topic communication.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async def receive_message_handler(message_received):
    if message_received.input_name == 'reportResponse':
        message_telemetry = Message(
            data=message_received.data.decode('utf-8'),
            content_encoding='utf-8',
            content_type='application/json',
        )
        await module_client.send_message_to_output(message_telemetry, 'telemetry')
        print("Weather report sent to IoT Hub")

        ros2_publisher_client.ros2_publisher(message_received.data.decode('utf-8'))
        print("ROS2 message sent to FileGenerator")

    elif  message_received.input_name == 'updateResponse':
        message_telemetry = Message(
            data=message_received.data.decode('utf-8'),
            content_encoding='utf-8',
            content_type='application/json',
        )
        await module_client.send_message_to_output(message_telemetry, 'telemetry')
        print("Download status sent to IoT Hub")
    else:
        print("Message received on unknown input: {}".format(message_received.input_name))

module_client.on_message_received = receive_message_handler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  WeatherObserver &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/src/apps/WeatherObserver/WeatherObserver/Program.cs"&gt;WeatherObserver&lt;/a&gt; is written with .NET6 console app in C#. This is simple and independent from Azure IoT Edge or ROS2 and you can run it locally for debugging purpose.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The callback that processes requests from &lt;code&gt;IothubConnector&lt;/code&gt; is implemented with &lt;code&gt;ioTHubModuleClient.SetInputMessageHandlerAsync()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;await ioTHubModuleClient.SetInputMessageHandlerAsync(routeC2W, ParseRequests, ioTHubModuleClient).ConfigureAwait(false);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  FileGenerator &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/src/apps/FileGenerator/main.py"&gt;FileGenerator&lt;/a&gt; is a module written in Python that receives a request from &lt;code&gt;IothubConnector&lt;/code&gt; and generates weather report files in the edge host machine.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This module uses &lt;code&gt;rclpy.spin()&lt;/code&gt; to keep the application itself running and waiting for the requests.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;rclpy.init(args=args)
file_generator = FileGenerator()
rclpy.spin(file_generator)
file_generator.destroy_node()
rclpy.shutdown()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The callback for ROS2 requests is implemented by &lt;code&gt;create_subscription()&lt;/code&gt; of ROS2 topic subscription.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def __init__(self):
    super().__init__('file_generator')
    self.subscription = self.create_subscription(
        String,
        os.getenv('ROS_TOPIC_NAME'),
        self.listener_callback,
        10)
    self.subscription

def listener_callback(self, msg):
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  FileUploader &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/src/apps/FileUploader/FileUploader/Program.cs"&gt;FileUploader&lt;/a&gt; is a module written in .NET6 C# that extracts files generated by &lt;code&gt;FileGenerator&lt;/code&gt; and sends them to &lt;code&gt;LocalBlobStorage&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  FileUpdater &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/src/apps/FileUpdater/FileUpdater/Program.cs"&gt;FileUpdater&lt;/a&gt; modules is writtein in .NET6 C# and downloads archive files from Azure Blob Storage.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It uses SAS token that allows the module only to access a specific directory so this system can work for multi-tenant and multi edge device environments. This SAS token is generated by &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/src/apps/ManifestGenerator/ManifestGenerator/Program.cs"&gt;ManifestGenerator&lt;/a&gt; which is explained in &lt;a href="https://dev.to/koheikawata/azure-iot-edge-integration-test-template-part3-5eld"&gt;Part.3&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  LocalBlobStorage &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;This sample template uses &lt;a href="https://docs.microsoft.com/en-us/azure/iot-edge/how-to-store-data-blob"&gt;LocalBlobStorage&lt;/a&gt; that is built by Microsoft and used from &lt;code&gt;mcr.microsoft.com/azure-blob-storage:latest&lt;/code&gt;. The reason for using the built-in local blob is that Azure IoT Edge runtime and default modules including &lt;code&gt;EdgeHub&lt;/code&gt; and &lt;code&gt;EdgeAgent&lt;/code&gt; do not support file uploading features.&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Azure IoT Edge Integration Test template - Part.3</title>
      <dc:creator>Kohei Kawata</dc:creator>
      <pubDate>Sun, 28 Aug 2022 10:31:00 +0000</pubDate>
      <link>https://dev.to/koheikawata/azure-iot-edge-integration-test-template-part3-5eld</link>
      <guid>https://dev.to/koheikawata/azure-iot-edge-integration-test-template-part3-5eld</guid>
      <description>&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;This article is a part of series of article about &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template"&gt;azure-iot-edge-integration-test-template&lt;/a&gt;. Following Part.2, I am going to focus on &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/src/apps/ManifestGenerator/ManifestGenerator/Program.cs"&gt;ManifestGenerator&lt;/a&gt; in this article.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/azure-iot-edge-integration-test-template-part1-3911"&gt;Azure IoT Edge Integration Test template - Part.1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/koheikawata/azure-iot-edge-integration-test-template-part2-378l"&gt;Azure IoT Edge Integration Test template - Part.2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  TOC
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Deployment manifest&lt;/li&gt;
&lt;li&gt;What ManifestGenerator does&lt;/li&gt;
&lt;li&gt;
Configurations

&lt;ul&gt;
&lt;li&gt;Extract credentials&lt;/li&gt;
&lt;li&gt;Environment variables&lt;/li&gt;
&lt;li&gt;appsettings.json&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;IoTEdgeObjectModel NuGet package&lt;/li&gt;
&lt;li&gt;SAS token&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Deployment manifest &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;In this sample template, the .NET app &lt;code&gt;ManifestGenerator&lt;/code&gt; running on Azure Pipelines agent generates &lt;a href="https://docs.microsoft.com/en-us/azure/iot-edge/module-composition"&gt;Azure IoT Edge Deployment Manifest&lt;/a&gt; and deploys it to Azure IoT Hub. IoT Edge modules connected to the IoT Hub are going to pull container images pointed on the manifest.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1uMH1MSF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l0bds02fbozdlsu141fh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1uMH1MSF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l0bds02fbozdlsu141fh.png" alt="Image description" width="880" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  What ManifestGenerator does &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;The main purpose of &lt;code&gt;ManifestGenerator&lt;/code&gt; app is to generate &lt;code&gt;manifest.json&lt;/code&gt;. It includes the information of all IoT Edge modules including system default and custom.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Default module&lt;/strong&gt;: &lt;code&gt;edgeAgent&lt;/code&gt; and &lt;code&gt;edgeHub&lt;/code&gt; are default modules that manage modules and their communications.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom module&lt;/strong&gt;: You can specify what modules you want to deploy to the IoT Edge environment. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IoT Edge communication route&lt;/strong&gt;: You have to specify route name here. This is very important that those names are synchronized with routes defined in each module apps. For example, the route specified in the manifest is like &lt;code&gt;FROM /messages/modules/IothubConnector/outputs/reportRequest INTO BrokeredEndpoint("/modules/WeatherObserver/inputs/reportRequest")&lt;/code&gt;. &lt;code&gt;IothubConnector&lt;/code&gt; module needs to specify &lt;code&gt;reportRequest&lt;/code&gt; route name on its send message method, and &lt;code&gt;WeatherObserver&lt;/code&gt; to specify &lt;code&gt;reportRequest&lt;/code&gt; route name on its callback method.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image URL, Environment variables, Bind mount directory&lt;/strong&gt;: You have to specify image URL, environment variables, bind mount directory, and other configurations like below.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can see a sample here - &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/src/apps/ManifestGenerator/manifest.example.json"&gt;manifest.example.json&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"FileGenerator": {
  "version": "1.0",
  "type": "docker",
  "status": "running",
  "restartPolicy": "always",
  "settings": {
    "image": "crsample1.azurecr.io/file-generator:20220818.2",
    "createOptions": "{\"HostConfig\":{\"Binds\":[\"/edge/upload/reports:/genroot\"]}}"
  },
  "env": {
    "OUTPUT_DIRECTORY_PATH": {
      "value": "/genroot"
    },
    "ROS_TOPIC_NAME": {
      "value": "ros2_topic_download"
    }
  }
},
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--sJ26qwob--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zx83rvmcqjeb4f5djz7l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--sJ26qwob--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zx83rvmcqjeb4f5djz7l.png" alt="Image description" width="880" height="627"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Configurations &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;In this section, I am going to describe how to set configurations for &lt;code&gt;manifest.json&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extract credentials &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;manifest.json&lt;/code&gt; needs some credentials including IoT Hub connection string, Container Registry key, Storage Account key, local blob storage key. The first three can be extracted through Azure CLI command as Azure Pipelines agent has the access right with Azure Service Connection. For the local blob storage key, it could be anything of base64 string with length in 16 bytes. The Azure Pipeline agent generates the one randomly and sets it as environment variables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;az extension add --name azure-iot
iothubcs=$(az iot hub connection-string show --hub-name $(IOTHUB_NAME) -o tsv)
echo "##vso[task.setvariable variable=iotHubCs]$iothubcs"
acrkey=$(az acr credential show --name $(ACR_NAME) --query passwords[0].value -o tsv)
echo "##vso[task.setvariable variable=AcrKey]$acrkey"
storagekey=$(az storage account keys list --resource-group $(RESOURCE_GROUP_NAME) --account-name $(STORAGE_ACCOUNT_NAME) --query [0].value -o tsv)
echo "##vso[task.setvariable variable=storageAccountKey]$storagekey"
localstoragekey=$(openssl rand -base64 16)
echo "##vso[task.setvariable variable=LocalStorageKey]$localstoragekey"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Environment variables &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;With Azure Pipelines &lt;a href="https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/build/dotnet-core-cli?view=azure-devops"&gt;.NET Core CLI task&lt;/a&gt;, you can pass neccesary variables as .NET environment variables to &lt;code&gt;ManifestGenerator&lt;/code&gt; app when running it. Those variables include credentials Azure Pipelines agent generated in the previous step.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- task: DotNetCoreCLI@2
  displayName: Generate/deploy IoT Edge manifest
  inputs:
    command: run
    projects: $(Build.SourcesDirectory)/src/apps/ManifestGenerator/ManifestGenerator/ManifestGenerator.csproj
    arguments: --configuration Release
  env:
    STORAGE_ACCOUNT_NAME: $(STORAGE_ACCOUNT_NAME)
    STORAGE_ACCOUNT_KEY: $(storageAccountKey)
    ACR_NAME: $(ACR_NAME)
    ACR_PASS: $(AcrKey)
    IOTHUB_CONNECTOR_IMAGE: $(ACR_NAME).azurecr.io/${{ parameters.EdgeImages.module1.repository }}:${{ parameters.EdgeImages.module1.tag }}
    WEATHER_OBSERVER_IMAGE: $(ACR_NAME).azurecr.io/${{ parameters.EdgeImages.module2.repository }}:${{ parameters.EdgeImages.module2.tag }}
    FILE_GENERATOR_IMAGE: $(ACR_NAME).azurecr.io/${{ parameters.EdgeImages.module3.repository }}:${{ parameters.EdgeImages.module3.tag }}
    FILE_UPLOADER_IMAGE: $(ACR_NAME).azurecr.io/${{ parameters.EdgeImages.module4.repository }}:${{ parameters.EdgeImages.module4.tag }}
    FILE_UPDATER_IMAGE: $(ACR_NAME).azurecr.io/${{ parameters.EdgeImages.module5.repository }}:${{ parameters.EdgeImages.module5.tag }}
    IOTHUB_DEVICE_ID: $(IOTHUB_DEVICE_ID)
    IOTHUB_CONNECTION_STRING: $(iotHubCs)
    LOCAL_STORAGE_KEY: $(LocalStorageKey)
    ORGANIZATION_NAME: $(TEST_ORGANIZATION_NAME)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  appsettings.json &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;ManifestGenerator&lt;/code&gt; app retains &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/src/apps/ManifestGenerator/ManifestGenerator/appsettings.json"&gt;appsettings.json&lt;/a&gt; that specifies design values. You do not need to change it unless you change the design such as bind mount paths or SAS token expiration period.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "RouteTelemetry": "telemetry",
  "RouteReportRequest": "reportRequest",
  "RouteReportResponse": "reportResponse",
  "RouteUpdateRequest": "updateRequest",
  "RouteUpdateResponse": "updateResponse",
  "Ros2Topic": "ros2_topic_download",
  "FileGeneratorContainerBind": "/edge/upload/reports:/genroot",
  "FileUploaderContainerBind": "/edge/upload:/uploadroot",
  "FileUpdaterContainerBind": "/edge/download:/downloadroot",
  "LocalBlobStorageBind": "/edge/localblob:/blobroot",
  "CloudBlobContainerName": "weather",
  "LocalBlobContainerName": "weather",
  "LocalBlobAccountName": "stlocal",
  "LocalBlobEndpoint": "http://LocalBlobStorage:11002",
  "FileGeneratorWorkdir": "/genroot",
  "FileUploaderWorkdir": "/uploadroot",
  "FileUpdaterWorkdir": "/downloadroot",
  "SasExpirationMonths": 6
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  IoTEdgeObjectModel NuGet package &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;This sample template uses &lt;a href="https://www.nuget.org/packages/IoTEdgeObjectModel"&gt;IoTEdgeObjectModel&lt;/a&gt; NuGet package. This package helps you reduce lines of code you write for &lt;code&gt;manifest.json&lt;/code&gt;. You do not need to specify system module configuration or other default configurations. In my experience in the last project, I wrote a &lt;code&gt;manifest.json&lt;/code&gt; all by myself from scratch with C# dictionary instances. By using &lt;code&gt;IoTEdgeObjectModel&lt;/code&gt; package, you can reduce roughly 50% of your codes.&lt;/p&gt;

&lt;p&gt;The three main classes of this package are &lt;code&gt;EdgeAgentDesiredProperties&lt;/code&gt;, &lt;code&gt;EdgeHubDesiredProperties&lt;/code&gt;, &lt;code&gt;ModuleSpecificationDesiredProperties&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You specifies &lt;code&gt;edgeAgent&lt;/code&gt; properties like below with &lt;code&gt;EdgeModuleSpecification&lt;/code&gt; class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EdgeAgentDesiredProperties edgeAgentDesiredProperties = new ()
{
    SystemModuleVersion = "1.3",
    RegistryCredentials = new List&amp;lt;RegistryCredential&amp;gt;()
    {
        new RegistryCredential(acrName, $"{acrName}.azurecr.io", acrName, acrPass),
    },
    EdgeModuleSpecifications = new List&amp;lt;EdgeModuleSpecification&amp;gt;()
    {
        new EdgeModuleSpecification(name:"IothubConnector", image:iothubConnectorImage, environmentVariables:iothubConnectorEnv),
        new EdgeModuleSpecification(name:"WeatherObserver", image:weatherObserverImage),
        new EdgeModuleSpecification(name:"FileGenerator", image:fileGeneratorImage, createOptions:fileGeneratorCreateOptions, environmentVariables:fileGeneratorEnv),
        new EdgeModuleSpecification(name:"FileUploader", image:fileUploaderImage, createOptions:fileUploaderCreateOptions, environmentVariables:fileUploaderEnv),
        new EdgeModuleSpecification(name:"FileUpdater", image:fileUpdaterImage, createOptions:fileUpdaterCreateOptions, environmentVariables:fileUpdaterEnv),
        new EdgeModuleSpecification(name:"LocalBlobStorage", image:localBlobStorageImage, createOptions:localBlobStorageCreateOptions, environmentVariables:localBlobStorageEnv),
    },
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;EdgeHubDesiredProperties&lt;/code&gt; mainly specifies Azure IoT Edge route communication.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EdgeHubDesiredProperties edgeHubConfig = new ()
{
    Routes = new List&amp;lt;Route&amp;gt;()
    {
        new Route("route_telemetry", route_telemetry),
        new Route("route_c2w", route_c2w),
        new Route("route_w2c", route_w2c),
        new Route("route_w2u", route_w2u),
        new Route("route_u2w", route_u2w),
    },
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ModuleSpecificationDesiredProperties&lt;/code&gt; specifies custom modules and their module twin desired properties.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ModuleSpecificationDesiredProperties localBlobStorage = new ()
{
    Name = "LocalBlobStorage",
    DesiredProperties = new Dictionary&amp;lt;string, object&amp;gt;
    {
        ["deviceAutoDeleteProperties"] = new Dictionary&amp;lt;string, object&amp;gt;
        {
            ["deleteOn"] = true,
            ["deleteAfterMinutes"] = 5,
            ["retainWhileUploading"] = true,
        },
        ["deviceToCloudUploadProperties"] = new Dictionary&amp;lt;string, object&amp;gt;
        {
            ["uploadOn"] = true,
            ["uploadOrder"] = "NewestFirst",
            ["deleteAfterUpload"] = true,
            ["cloudStorageConnectionString"] = cloudStorageSasConnectionString,
            ["storageContainersForUpload"] = new Dictionary&amp;lt;string, object&amp;gt;
            {
                [localBlobContainerName] = new Dictionary&amp;lt;string, object&amp;gt;
                {
                    ["target"] = iotHubDeviceId,
                }
            },
        },
    },
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  SAS token &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ManifestGenerator&lt;/code&gt; has a service class &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/src/apps/ManifestGenerator/ManifestGenerator/Services/SasService.cs"&gt;SasService.cs&lt;/a&gt; that generates a &lt;a href="https://docs.microsoft.com/en-us/azure/storage/common/storage-sas-overview"&gt;SAS(Shared Access Signature) token&lt;/a&gt;. In this way, you can have one Azure Blob Storage for multiple edge devices from different entities. This SAS token makes edge devices follow the security boundary so edge devices cannot access data of different entities.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UTSOFR8P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e8y5duvylczuo0bhbahg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UTSOFR8P--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e8y5duvylczuo0bhbahg.png" alt="Image description" width="880" height="397"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It is important to convert a SAS token generated to a connection string. This connection string is set as an environment variable of &lt;code&gt;LocalBlobStorage&lt;/code&gt;. For &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/src/apps/FileUpdater/FileUpdater/Program.cs"&gt;FileUpdater&lt;/a&gt;, you can use &lt;code&gt;AzureSasCredential&lt;/code&gt; to convert the SAS token into the one readable for &lt;code&gt;BlobClient&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;string[] sasContents = weatherFileInfo.BlobSasUrl.Split('?');
AzureSasCredential azureSasCredential = new (sasContents[1]);
Uri blobUri = new (sasContents[0]);
BlobClient blobClient = new (blobUri, azureSasCredential, null);
await blobClient.DownloadToAsync(zipFilePath).ConfigureAwait(false);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, &lt;code&gt;LocalBlobClient&lt;/code&gt; by default needs a blob connection string, not SAS token. So you need to convert the SAS token into a blob connection string in &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/src/apps/ManifestGenerator/ManifestGenerator/Program.cs"&gt;ManfiestGenerator Program.cs&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;string sasUri = directoryClient.GenerateSasUri(sasBuilder).ToString();
string[] sasContents = sasUri.Split('?');
string sasConnectionString = $"BlobEndpoint=https://{this.dataLakeServiceClient.AccountName}.blob.core.windows.net/{blobContainerName}/{sasDirectory};SharedAccessSignature={sasContents[1]}";
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>devcontainer for ROS2 project</title>
      <dc:creator>Kohei Kawata</dc:creator>
      <pubDate>Fri, 26 Aug 2022 14:42:01 +0000</pubDate>
      <link>https://dev.to/koheikawata/devcontainer-for-ros2-project-31ng</link>
      <guid>https://dev.to/koheikawata/devcontainer-for-ros2-project-31ng</guid>
      <description>&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;In this article, I want to share how to create your local development environment for ROS2 projects. Recent code development like the one with ROS2 framework has become very complex and heavily depending on OSS packages. VS Code devcontainer makes it much easier to create your local development environment.&lt;/p&gt;

&lt;h1&gt;
  
  
  TOC
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Overview&lt;/li&gt;
&lt;li&gt;devcontainer.json&lt;/li&gt;
&lt;li&gt;Dockerfile&lt;/li&gt;
&lt;li&gt;
Directory structure

&lt;ul&gt;
&lt;li&gt;How to run the docker container development environment&lt;/li&gt;
&lt;li&gt;Where is the project directory?&lt;/li&gt;
&lt;li&gt;Experiment 1&lt;/li&gt;
&lt;li&gt;Experiment 2&lt;/li&gt;
&lt;li&gt;Experiment 3&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h1&gt;
  
  
  Overview &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;devcontainer&lt;/strong&gt; is one of Visual Studio Code Remote Development features that helps create local develop environment. Personally I love Windows OS and Visual Studio when developing .NET C# applications, but when in development with some other languages like Python, Typescript etc., VS Code devcontainer is really powerful. Here is my personal opinion of whe it is so useful.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Independent of your machine's operating system&lt;/strong&gt;: According to statistica.com, Windows has around 75 percent of the global market share of operating system in 2022. Combined with WSL(Windows Subsystem for Linux) technology, VS Code devcontainer enables you to run Linux development environment in your Windows machine.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Package verion combinations&lt;/strong&gt;: In general, you want to divide development environments depending on projects. For example, you can use &lt;code&gt;pyenv&lt;/code&gt;, &lt;code&gt;virtualenv&lt;/code&gt;, &lt;code&gt;conda&lt;/code&gt; or whatever virtual environment you prefer for Python. However, when you want to combine the Python environment with other framework, for example ROS2, it is going to be more complex. VS Code devcontainer solves this problem by creating a just in time docker container for each development environment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No need to install all dependencies from scratch&lt;/strong&gt;: To create your just in time development environment, you do not need to install all dependencies from scratch. You can utilize publicly available images from the well-known container registry. For example, I am using &lt;code&gt;ros:foxy-ros-base-focal&lt;/code&gt; image for my ROS2 developenvironment - &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/src/apps/IothubConnector/.devcontainer/Dockerfile" rel="noopener noreferrer"&gt;Dockerfile&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Debugger&lt;/strong&gt;: Visual Studio Code supports debuggers for different languages. For this exmaple - &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/src/apps/IothubConnector/.devcontainer/devcontainer.json" rel="noopener noreferrer"&gt;devcontainer.json&lt;/a&gt;, it uses &lt;code&gt;ms-python&lt;/code&gt; VS Code extension with which you can run a Python debugger in your local machine.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;VS Code support and extensions&lt;/strong&gt;: VS Code has built-in support and so many extensions for different languages. You can use those support even in the docker container development environment of devcontainer.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  .devcontainer/devcontainer.json &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;The sample &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/src/apps/IothubConnector/.devcontainer/devcontainer.json" rel="noopener noreferrer"&gt;devcontainer.json&lt;/a&gt; looks like below.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;dockerfile&lt;/strong&gt;: This points to &lt;code&gt;Dockerfile&lt;/code&gt; located in the same folder as &lt;code&gt;devcontainer.json&lt;/code&gt; under &lt;code&gt;.devcontainer&lt;/code&gt; directory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;workspaceFolder&lt;/strong&gt;: The directory in the docker container where &lt;code&gt;.devcontainer&lt;/code&gt; directory exists.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;extensions&lt;/strong&gt;: VS Code extensions you want to use in the docker contaienr development environment.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "name": "ROS devcontainer",
  "dockerFile": "Dockerfile",
  "workspaceFolder": "/workspaces/EdgeIntegrationTest/src/apps/FileGenerator",
  "settings": {},
  "extensions": [
        "ms-python.python"
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  .devcontainer/Dockerfile &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;The sample &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/src/apps/IothubConnector/.devcontainer/Dockerfile" rel="noopener noreferrer"&gt;Dockerfile&lt;/a&gt; under &lt;code&gt;.devcontainer&lt;/code&gt; directory looks like below.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;FROM ros:foxy-ros-base-focal&lt;/code&gt;: In this sample, it uses that ros image as the base image. It bases on &lt;a href="https://github.com/osrf/docker_images/blob/3f4fbca923d80f834f3a89b5960bad5582652519/ros/foxy/ubuntu/focal/ros-core/Dockerfile" rel="noopener noreferrer"&gt;ros:foxy-ros-core-focal&lt;/a&gt;, which bases on &lt;a href="https://github.com/tianon/docker-brew-ubuntu-core/blob/570d5970a8b18bc772ad2c3eb1ce8fd0887d991a/focal/Dockerfile" rel="noopener noreferrer"&gt;ubuntu:focal&lt;/a&gt;, which is the same as &lt;code&gt;ubuntu:20.04&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;COPY requirements.txt ./&lt;/code&gt;: Copy &lt;code&gt;requirements.txt&lt;/code&gt; to &lt;code&gt;/app&lt;/code&gt; in the docker container so the docker container can run &lt;code&gt;pip install&lt;/code&gt; to set up necessary Python dependencies.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;RUN echo 'source /opt/ros/foxy/setup.bash' &amp;gt;&amp;gt; /root/.bashrc&lt;/code&gt;: According to ROS2 documentation &lt;a href="https://docs.ros.org/en/foxy/Tutorials/Beginner-CLI-Tools/Configuring-ROS2-Environment.html#configuring-environment" rel="noopener noreferrer"&gt;Configuring environment&lt;/a&gt;, in order to set up the docker container to access ROS2 commands, you need to source the set up files in the docker container - &lt;code&gt;/opt/ros/foxy/setup.bash&lt;/code&gt;. To avoid running the source command every time, it adds the source command to shell startup script.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FROM ros:foxy-ros-base-focal

SHELL ["/bin/bash", "-c"]

WORKDIR /app

COPY requirements.txt ./
RUN apt update &amp;amp;&amp;amp; apt install -y \
    python3-pip \
    python3-colcon-common-extensions
RUN pip install -r requirements.txt

RUN echo 'source /opt/ros/foxy/setup.bash' &amp;gt;&amp;gt; /root/.bashrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The location of &lt;code&gt;setup.bash&lt;/code&gt; file in the docker container&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/opt
└── ros
    └── foxy
        ├── _local_setup_util.py
        ├── bin
        ├── cmake
        ├── include
        ├── lib
        ├── local_setup.bash
        ├── local_setup.sh
        ├── local_setup.zsh
        ├── opt
        ├── setup.bash
        ├── setup.sh
        ├── setup.zsh
        ├── share
        ├── src
        └── tools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Directory structure &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;h2&gt;
  
  
  How to run the docker container development environment &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The step to open the development environment is like below.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to your terminal and change your directory to where &lt;code&gt;.devcontainer&lt;/code&gt; folder exists.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;code .&lt;/code&gt; in your terminal, and you will see VS Code is coming up.&lt;/li&gt;
&lt;li&gt;Go to &lt;code&gt;Open a Remote Window&lt;/code&gt; and select &lt;code&gt;Reopen in Container&lt;/code&gt;, and you will see VS Code is reopened with the one running in the docker container.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Where is the project directory? &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;In this sample - &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/src/apps/IothubConnector/.devcontainer" rel="noopener noreferrer"&gt;.devcontainer&lt;/a&gt;, when the docker container VS Code is open, you will see your directory in the docker container is &lt;code&gt;/workspaces/EdgeIntegrationTest/src/apps/FileGenerator&lt;/code&gt;. You opened the devcontainer at &lt;code&gt;FileGenerator&lt;/code&gt; in your local machine directory. However, the docker container copied &lt;code&gt;EdgeIntegrationTest&lt;/code&gt; directory in your local machine to &lt;code&gt;/workspaces&lt;/code&gt; in the docker container.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;EdgeIntegrationTest
└── src
    └── apps
        └── FileGenerator
              ├── .devcontainer
              |   ├── Dockerfile
              |   ├── devcontainer.json
              |   └── requirements.txt
              ├── Dockerfile
              ├── main.py
              └── requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Is this because your articulate &lt;code&gt;"workspaceFolder": "/workspaces/EdgeIntegrationTest/src/apps/FileGenerator"&lt;/code&gt; in &lt;a href="https://github.com/koheikawata/azure-iot-edge-integration-test-template/blob/main/src/apps/IothubConnector/.devcontainer/devcontainer.json" rel="noopener noreferrer"&gt;devcontainer.json&lt;/a&gt;? No. If you change &lt;code&gt;workspaceFolder&lt;/code&gt; in &lt;code&gt;devcontainer.json&lt;/code&gt; to &lt;code&gt;"workspaceFolder": "/workspaces/FileGenerator"&lt;/code&gt;, you will see &lt;code&gt;EdgeIntegrationTest&lt;/code&gt; directory is copied under &lt;code&gt;/workspaces&lt;/code&gt; in the docker container and see the error below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjp3rd6kcqxa2o6ohosnl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjp3rd6kcqxa2o6ohosnl.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It seems like the VS Code Remote Development extension copies the directory where &lt;strong&gt;one&lt;/strong&gt; &lt;code&gt;.git&lt;/code&gt; exists over your current directory in which you &lt;code&gt;Reopen in Container&lt;/code&gt;. I tried three experiments below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Experiment 1 &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Reopen in Container at &lt;code&gt;FileGenerator&lt;/code&gt; in your local machine&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:.
└── FileGenerator
    ├── .devcontainer
    |   ├── Dockerfile
    |   ├── devcontainer.json
    |   └── requirements.txt
    ├── Dockerfile
    ├── main.py
    └── requirements.txt

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Your directory in the docker container&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@xxxxxxx:/workspaces/FileGenerator# 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Experiment 2 &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Reopen in Container at &lt;code&gt;FileGenerator&lt;/code&gt; in your local machine&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:.
└── ProjectFolder1
    ├── .git
    └── FileGenerator
        ├── .devcontainer
        |   ├── Dockerfile
        |   ├── devcontainer.json
        |   └── requirements.txt
        ├── Dockerfile
        ├── main.py
        └── requirements.txt

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Your directory in the docker container&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@xxxxxxx:/workspaces/ProjectFolder1/FileGenerator# 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Experiment 3 &lt;a&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Reopen in Container at &lt;code&gt;FileGenerator&lt;/code&gt; in your local machine&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;C:.
└── ProjectFolder2
    ├── .git
    └── ProjectFolder1
        ├── .git
        └── FileGenerator
            ├── .devcontainer
            |   ├── Dockerfile
            |   ├── devcontainer.json
            |   └── requirements.txt
            ├── Dockerfile
            ├── main.py
            └── requirements.txt

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Your directory in the docker container&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;root@xxxxxxx:/workspaces/ProjectFolder1/FileGenerator# 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
    <item>
      <title>C# Class design for cloud storage management application</title>
      <dc:creator>Kohei Kawata</dc:creator>
      <pubDate>Tue, 03 May 2022 13:10:41 +0000</pubDate>
      <link>https://dev.to/koheikawata/c-class-design-for-cloud-storage-management-application-3pdn</link>
      <guid>https://dev.to/koheikawata/c-class-design-for-cloud-storage-management-application-3pdn</guid>
      <description>&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;I want to share a C# class design pattern I recently did. I know there are a lot of different design patterns and there is no single best way, but learning different design patterns helps myself develop extensible and scalable applications.&lt;/p&gt;

&lt;p&gt;Sample code: &lt;a href="https://github.com/koheikawata/blob-console-app"&gt;blob-console-app&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  TOC
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Architecture&lt;/li&gt;
&lt;li&gt;Class design&lt;/li&gt;
&lt;li&gt;
Codes

&lt;ul&gt;
&lt;li&gt;List file type instance&lt;/li&gt;
&lt;li&gt;for instead of foreach&lt;/li&gt;
&lt;li&gt;Dependency Injection&lt;/li&gt;
&lt;li&gt;Upload file validation&lt;/li&gt;
&lt;li&gt;Generic class&lt;/li&gt;
&lt;li&gt;Metadata into dictionary&lt;/li&gt;
&lt;li&gt;Json deserialize&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Architecture &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Operators place files which they want to upload to Azure Blob Storage.&lt;/li&gt;
&lt;li&gt;The .NET console app checks the pointed directory in a certain interval and if those files are valid, it uploads to the Azure Blob Storage.&lt;/li&gt;
&lt;li&gt;The app deletes those files from the local file system once the files are uploaded.&lt;/li&gt;
&lt;li&gt;The app extracts metadata and attaches the blob on cloud.&lt;/li&gt;
&lt;li&gt;Currently the file types are &lt;code&gt;logs&lt;/code&gt; and &lt;code&gt;events&lt;/code&gt; which are located different directory in the local file system.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--aJgnHvkg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vy6mwlvbq93tmqr6x3fl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--aJgnHvkg--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vy6mwlvbq93tmqr6x3fl.png" alt="Image description" width="880" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Class design &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;I decided to use Interface, Abstract, and sub classes because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Currently file types are only &lt;code&gt;Logs&lt;/code&gt; and &lt;code&gt;Events&lt;/code&gt;, but the system will extend. For example, &lt;code&gt;DeviceInfo&lt;/code&gt; and &lt;code&gt;Requests&lt;/code&gt; will be added in the future.&lt;/li&gt;
&lt;li&gt;It is better for future extended file types to divide the base features and ones with different requirements for each file type so the system reuses the base class features in the future.&lt;/li&gt;
&lt;li&gt;In order to avoid changing the base class when adding new file types, an Interface class and abstract class defines base properties, and sub classes define metadata properties.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FolderName&lt;/code&gt; is a base property but it points to the directory for each file type. It is defined as Abstract property because Sub classes must override it and define for their own.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;JudgeValidFile()&lt;/code&gt; and &lt;code&gt;GetMetadata()&lt;/code&gt; have to be implemented in sub classes, and then they are defined as Abstract. &lt;code&gt;UploadBlobAsync()&lt;/code&gt;, &lt;code&gt;UploadMetadataAsync()&lt;/code&gt;, and &lt;code&gt;DeleteFiles()&lt;/code&gt; are defined as Abstract because they are implemented in the base class but can be overriden in sub classes accordingly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--7mVmPXry--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/88zg7ib55lkh0xrwu5n6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--7mVmPXry--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/88zg7ib55lkh0xrwu5n6.png" alt="Image description" width="880" height="625"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Codes &lt;a&gt;&lt;/a&gt;
&lt;/h1&gt;

&lt;h2&gt;
  
  
  List file type instance &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Currently the system has only two file types, &lt;code&gt;Logs&lt;/code&gt; and &lt;code&gt;Events&lt;/code&gt;. If you want to add another file type, only thing to do is to implement a new class for the file type and add a new instance here.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/koheikawata/blob-console-app/blob/main/src/BlobConsoleApp/Program.cs"&gt;Program.cs&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;List&amp;lt;IUploadData&amp;gt; uploadDataList = new ()
{
    new Logs(),
    new Events(),
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  for instead of foreach &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;for (int i = 0; i &amp;lt; uploadDataList.Count; ++i)&lt;/code&gt; processes differently depending on class instance added to the list.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It uses &lt;code&gt;for&lt;/code&gt; instead of &lt;code&gt;foreach&lt;/code&gt; because the class method &lt;code&gt;GetMetadata()&lt;/code&gt; gets metadata and put into its own property&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/koheikawata/blob-console-app/blob/main/src/BlobConsoleApp/Program.cs"&gt;Program.cs&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uploadDataList[i] = uploadDataList[i].GetMetadata();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Dependency Injection &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;It does not use &lt;a href="https://docs.microsoft.com/en-us/dotnet/core/extensions/dependency-injection"&gt;dependency injection&lt;/a&gt; but assigns the values to properties. This is because the value changes for different files found and file types. For example, &lt;code&gt;BlobClient&lt;/code&gt; needs a file name and directory to instantiate for different files and different file types. It cannot create an instance in constructor.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/koheikawata/blob-console-app/blob/main/src/BlobConsoleApp/Program.cs"&gt;Program.cs&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;List&amp;lt;IUploadData&amp;gt; uploadDataList = new()
{
    new Logs(),
    new Events(),
};

while (true)
{
    for (int i = 0; i &amp;lt; uploadDataList.Count; ++i)
    {
        uploadDataList[i].FileFullPaths = Directory.GetFiles(Path.Combine(localPath, uploadDataList[i].FolderName), "*", SearchOption.AllDirectories);

        foreach (string fileFullPath in uploadDataList[i].FileFullPaths)
        {
            uploadDataList[i].FileFullPath = fileFullPath;
            uploadDataList[i].FileName = Path.GetFileName(fileFullPath);
            uploadDataList[i].BlobClient = containerClient.GetBlobClient($"/{uploadDataList[i].FolderName}/{DateTime.Now.Year}/{DateTime.Now.Month}/{uploadDataList[i].FileName}");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Upload file validation &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;If a file found in the directory is not valid to upload, it stops the for loop and goes to a next file.&lt;/li&gt;
&lt;li&gt;How to valid files is different depending on file types. That is why &lt;code&gt;JudgeValidFile()&lt;/code&gt; is overriden in each file type class.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/koheikawata/blob-console-app/blob/main/src/BlobConsoleApp/Program.cs"&gt;Program.cs&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uploadDataList[i].JudgeValidFile();
if (uploadDataList[i].IsValidFile is false) continue;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/koheikawata/blob-console-app/blob/main/src/BlobConsoleApp/Data/Logs.cs"&gt;Logs.cs&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;this.IsValidFile = (Path.GetExtension(this.FileName) == ".csv" &amp;amp;&amp;amp; this.FileFullPaths.Contains(this.FileFullPath + ".json"));
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/koheikawata/blob-console-app/blob/main/src/BlobConsoleApp/Data/Events.cs"&gt;Events.cs&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;this.IsValidFile = (Path.GetExtension(this.FileName) == ".json");
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Generic class &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;I wanted to use a generic class for &lt;code&gt;UploadMetadataAsync()&lt;/code&gt; because it has to retrieve the properties dynamically depending on the sub class. I thought &lt;code&gt;UploadMetadataAsync&amp;lt;uploadDataList[i].GetType()&amp;gt;()&lt;/code&gt; works but actually not. After investigating sometime, I gave up using Generic class here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generic class&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public virtual async Task UploadMetadataAsync&amp;lt;T&amp;gt;() where T : IUploadData
{
    PropertyInfo[] propertyCollection = typeof(T).GetProperties();
    foreach (PropertyInfo property in propertyCollection)
    {
        blobMetadata[property.Name.ToString()] = property.GetValue(this).ToString();
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Caller&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;await uploadDataList[i].UploadMetadataAsync&amp;lt;uploadDataList[i].GetType()&amp;gt;().ConfigureAwait(true);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Metadata into dictionary &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;I realized I cannot use Generic class, and I was thinking how to insert only metadata properties into the dictionary for uploading. What I did is to see the difference of property between Abstract class and sub class. For example, the Abstract class &lt;code&gt;UploadData&lt;/code&gt; has only the base properties &lt;code&gt;FolderName&lt;/code&gt;, &lt;code&gt;IsValidFile&lt;/code&gt;, &lt;code&gt;FileFullPaths&lt;/code&gt;, &lt;code&gt;FileFullPath&lt;/code&gt;, &lt;code&gt;FileName&lt;/code&gt;, &lt;code&gt;BlobClient&lt;/code&gt;, but the sub class &lt;code&gt;Logs&lt;/code&gt; has the metadata properties &lt;code&gt;BeginTime&lt;/code&gt;, &lt;code&gt;EndTime&lt;/code&gt;, &lt;code&gt;Temperature&lt;/code&gt;, &lt;code&gt;Humidity&lt;/code&gt;, &lt;code&gt;Location&lt;/code&gt; in addition to the base properties.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://github.com/koheikawata/blob-console-app/blob/main/src/BlobConsoleApp/Data/UploadData.cs"&gt;UploadData.cs&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;this.blobMetadata.Clear();
PropertyInfo[] propertiesIUploadData = typeof(IUploadData).GetProperties();
IEnumerable&amp;lt;PropertyInfo&amp;gt; propertiesThis = this.GetType().GetRuntimeProperties();

foreach (PropertyInfo propertyThis in propertiesThis)
{
    int count = 0;
    foreach (PropertyInfo propertyIUploadData in propertiesIUploadData)
    {
        if (propertyThis.Name == propertyIUploadData.Name) ++count;
    }
    if (count == 0)
    {
        this.blobMetadata[propertyThis.Name.ToString()] = propertyThis.GetValue(this).ToString();
    }
}
await this.BlobClient.SetMetadataAsync(this.blobMetadata).ConfigureAwait(false);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Json deserialize &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;It is elegant to deserialize the value from a json file because I do not need to change the code &lt;code&gt;JsonSerializer.Deserialize&amp;lt;Logs&amp;gt;(jsonString)!;&lt;/code&gt; for any model property. However, the problem was that the sub class has both the base property and metadata property, and when in deserialization, it sets the metadata properties with the value from a json file and also the base properties with null.&lt;/li&gt;
&lt;li&gt;I do not like this, but I did not come up with an elegant workaround, and what I implemented is to keep the value to local variables and then set them back after deserialization.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;json file example&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "BeginTime": "2022-03-07T12:21:55Z",
  "EndTime": "2022-03-07T14:01:36Z",
  "Temperature": 25,
  "Humidity": 63,
  "Location": "Tokyo"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Logs class property&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public string FolderName { get; }
public bool IsValidFile { get; set; }
public string[] FileFullPaths { get; set; }
public string FileFullPath { get; set; }
public string FileName { get; set; }
public BlobClient BlobClient { get; set; }
public DateTime BeginTime { get; set; }
public DateTime EndTime { get; set; }
public int Temperature { get; set; }
public int Humidity { get; set; }
public string Location { get; set; }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/koheikawata/blob-console-app/blob/main/src/BlobConsoleApp/Data/Logs.cs"&gt;Logs.cs&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public override IUploadData GetMetadata()
{
    bool IsValidFile = this.IsValidFile;
    string[] FileFullPaths = this.FileFullPaths;
    string FileFullPath = this.FileFullPath;
    string FileName = this.FileName;
    BlobClient BlobClient = this.BlobClient;

    string jsonString = File.ReadAllText(this.FileFullPath + ".json");
    Logs log = JsonSerializer.Deserialize&amp;lt;Logs&amp;gt;(jsonString)!;

    log.IsValidFile = IsValidFile;
    log.FileFullPaths = FileFullPaths;
    log.FileFullPath = FileFullPath;
    log.FileName = FileName;
    log.BlobClient = BlobClient;

    return log;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
    </item>
  </channel>
</rss>
