{
    "metadata": {
        "kernelspec": {
            "name": "python3",
            "display_name": "Python 3"
        },
        "language_info": {
            "name": "python",
            "version": "3.6.6",
            "mimetype": "text/x-python",
            "codemirror_mode": {
                "name": "ipython",
                "version": 3
            },
            "pygments_lexer": "ipython3",
            "nbconvert_exporter": "python",
            "file_extension": ".py"
        }
    },
    "nbformat_minor": 2,
    "nbformat": 4,
    "cells": [
        {
            "cell_type": "markdown",
            "source": [
                "![Microsoft](https://raw.githubusercontent.com/microsoft/azuredatastudio/main/extensions/resource-deployment/images/microsoft-small-logo.png)\n",
                "## Deploy Azure SQL Edge to an Azure VM via IoT hub\n",
                "This notebook will walk you through the end to end setup of Azure SQL Edge.\n",
                "1. Create an Azure Edge VM as a virtual IoT device, you can go the \"Default Settings\" cell and adjust the vm_size variable based on your needs. Available sizes and pricing information can be found [here](https://docs.microsoft.com/azure/virtual-machines/linux/sizes).\n",
                "1. Create an Azure IoT hub, you can go to the \"Default Settings\" cell and adjust value of the following variables based on your needs: iot_hub_sku and iot_hub_units. Available SKUs and pricing information can be found [here](https://azure.microsoft.com/pricing/details/iot-hub/).\n",
                "1. Add the device to the IoT hub\n",
                "1. Deploy SQL Edge module to the device with optional dacpac\n",
                "1. Enable connecting to the SQL Edge instance on the device\n",
                "\n",
                "### Dependencies\n",
                "- Azure CLI. For more information, see [Azure CLI Installation](https://docs.microsoft.com/cli/azure/install-azure-cli?view=azure-cli-latest).\n",
                "\n",
                "<span style=\"color:red\"><font size=\"3\">Please press the \"Run all\" button to run the notebook</font></span>"
            ],
            "metadata": {
                "azdata_cell_guid": "15b8cfc7-dd7f-4db8-9a3c-2151932fe7b5"
            }
        },
        {
            "cell_type": "markdown",
            "source": [
                "### Check dependencies"
            ],
            "metadata": {
                "azdata_cell_guid": "f78f4ff3-d4c9-4c3e-853f-4add05061eb0"
            }
        },
        {
            "cell_type": "code",
            "source": [
                "import pandas,sys,os,json,html,getpass,time,ntpath,uuid\n",
                "pandas_version = pandas.__version__.split('.')\n",
                "pandas_major = int(pandas_version[0])\n",
                "pandas_minor = int(pandas_version[1])\n",
                "pandas_patch = int(pandas_version[2])\n",
                "if not (pandas_major > 0 or (pandas_major == 0 and pandas_minor > 24) or (pandas_major == 0 and pandas_minor == 24 and pandas_patch >= 2)):\n",
                "    sys.exit('Please upgrade the Notebook dependency before you can proceed, you can do it by running the \"Reinstall Notebook dependencies\" command in command palette (View menu -> Command Palette…).')\n",
                "\n",
                "def run_command(command:str, displayCommand:str = \"\", returnObject:bool = False):\n",
                "    print(\"Executing: \" + (displayCommand if displayCommand != \"\" else command))\n",
                "    if returnObject:\n",
                "        output = os.popen(command).read()\n",
                "        print(f'Command successfully executed')\n",
                "        return json.loads(''.join(output))\n",
                "    else:\n",
                "        !{command}\n",
                "        if _exit_code != 0:\n",
                "            sys.exit(f'Command execution failed with exit code: {str(_exit_code)}.\\n')\n",
                "        else:\n",
                "            print(f'Command successfully executed')\n",
                "\n",
                "run_command(command='az --version')"
            ],
            "metadata": {
                "azdata_cell_guid": "70b9744f-eb59-44e8-9b35-db590ac4651d",
                "tags": [
                    "hide_input"
                ]
            },
            "outputs": [],
            "execution_count": null
        },
        {
            "cell_type": "markdown",
            "source": [
                "### Install Azure IoT extension for Azure CLI"
            ],
            "metadata": {
                "azdata_cell_guid": "a7f15c68-1725-4caa-b4f7-ddc2b4934883"
            }
        },
        {
            "cell_type": "code",
            "source": [
                "run_command('az extension add --name azure-cli-iot-ext')"
            ],
            "metadata": {
                "azdata_cell_guid": "55bb2f96-6f7f-4aa0-9daf-d0f7f9d9243c",
                "tags": [
                    "hide_input"
                ]
            },
            "outputs": [],
            "execution_count": null
        },
        {
            "cell_type": "markdown",
            "source": [
                "### Required information"
            ],
            "metadata": {
                "azdata_cell_guid": "b5dc5586-06e8-44d9-8bc3-2861d510efe5"
            }
        },
        {
            "cell_type": "code",
            "source": [
                "azure_subscription_id = os.environ[\"AZDATA_NB_VAR_ASDE_SUBSCRIPTIONID\"]\n",
                "azure_resource_group = os.environ[\"AZDATA_NB_VAR_ASDE_RESOURCEGROUP\"]\n",
                "azure_location = os.environ[\"AZDATA_NB_VAR_ASDE_AZURE_LOCATION\"]\n",
                "sa_password = os.environ[\"AZDATA_NB_VAR_SA_PASSWORD\"]\n",
                "vm_admin = os.environ[\"AZDATA_NB_VAR_ASDE_VM_ADMIN\"]\n",
                "vm_password = os.environ[\"AZDATA_NB_VAR_ASDE_VM_PASSWORD\"]\n",
                "dacpac_path = os.environ[\"AZDATA_NB_VAR_ASDE_DACPAC_PATH\"]\n",
                "sql_port = os.environ[\"AZDATA_NB_VAR_ASDE_SQL_PORT\"]\n",
                "new_rg_flag = os.environ[\"AZDATA_NB_VAR_ASDE_NEW_RESOURCEGROUP\"]\n",
                "new_rg_name = os.environ[\"AZDATA_NB_VAR_ASDE_NEW_RESOURCEGROUP_NAME\"]\n",
                "\n",
                "if new_rg_flag == 'true':\n",
                "    azure_resource_group = new_rg_name\n",
                "print(f'Subscription: {azure_subscription_id}')\n",
                "print(f'Resource group: {azure_resource_group}')\n",
                "print(f'Location: {azure_location}')\n",
                "print(f'VM admin username: {vm_admin}')\n",
                "print(f'VM admin password: ******')\n",
                "print(f'SQL Server port: {sql_port}')\n",
                "print(f'SQL Server sa password: ******')\n",
                "print(f'Dacpac path: {dacpac_path}')"
            ],
            "metadata": {
                "azdata_cell_guid": "dde9388b-f623-4d62-bb74-36a05f5d2ea3",
                "tags": [
                    "hide_input"
                ]
            },
            "outputs": [],
            "execution_count": null
        },
        {
            "cell_type": "markdown",
            "source": [
                "### Default settings"
            ],
            "metadata": {
                "azdata_cell_guid": "2a5755eb-85a7-4237-8d87-04cdab13cf40"
            }
        },
        {
            "cell_type": "code",
            "source": [
                "suffix = time.strftime(\"%y%m%d%H%M%S\", time.localtime())\n",
                "network_security_group = f'nsg{suffix}'\n",
                "public_ip_address_name = f'ip{suffix}'\n",
                "iot_hub_name = f'hub{suffix}'\n",
                "iot_hub_sku = 'S1'\n",
                "iot_hub_units = 4\n",
                "iot_device_id = f'vm{suffix}'\n",
                "vm_size = 'Standard_DS3_v2'\n",
                "vnet_name = f'net{suffix}'\n",
                "subnet_name = f'subnet{suffix}'\n",
                "subnet_address_prefix = '10.0.0.0/24'\n",
                "vnet_address_prefix = '10.0.0.0/16'\n",
                "azure_storage_account = f'sa{suffix}'\n",
                "storage_account_container = 'sqldatabasedacpac'\n",
                "sql_lcid = '1033'\n",
                "sql_collation = 'SQL_Latin1_General_CP1_CI_AS'"
            ],
            "metadata": {
                "azdata_cell_guid": "19ebeaf4-94c9-4d2b-bd9f-e3c6bf7f2dda",
                "tags": [
                    "hide_input"
                ]
            },
            "outputs": [],
            "execution_count": null
        },
        {
            "cell_type": "markdown",
            "source": [
                "### Login to Azure"
            ],
            "metadata": {
                "azdata_cell_guid": "84f57c09-5772-4f7a-a270-4039b8d5b081"
            }
        },
        {
            "cell_type": "code",
            "source": [
                "run_command('az login')"
            ],
            "metadata": {
                "azdata_cell_guid": "f9e8ddee-aefa-4951-b767-b318d941d2cd",
                "tags": [
                    "hide_input"
                ]
            },
            "outputs": [],
            "execution_count": null
        },
        {
            "cell_type": "markdown",
            "source": [
                "### Set active Azure subscription"
            ],
            "metadata": {
                "azdata_cell_guid": "59249fa6-f76c-4e5d-bee7-a9ebef6f873e"
            }
        },
        {
            "cell_type": "code",
            "source": [
                "if azure_subscription_id != \"\":\n",
                "    run_command(f'az account set --subscription {azure_subscription_id}')\n",
                "else:\n",
                "    print('Using the default Azure subscription', {azure_subscription_id})\n",
                "run_command(f'az account show')"
            ],
            "metadata": {
                "azdata_cell_guid": "6e085676-2cc5-4af8-819c-fa210244e6c3",
                "tags": [
                    "hide_input"
                ]
            },
            "outputs": [],
            "execution_count": null
        },
        {
            "cell_type": "markdown",
            "source": [
                "### Create resource group"
            ],
            "metadata": {
                "azdata_cell_guid": "67dacbaa-92f4-4d06-90bb-8974964852aa"
            }
        },
        {
            "cell_type": "code",
            "source": [
                "rg_exists = run_command(f'az group exists --name {azure_resource_group}', returnObject=True)\n",
                "\n",
                "if rg_exists:\n",
                "    print(f'resource group \\\"{azure_resource_group}\\\" already exists.')\n",
                "else:\n",
                "    run_command(f'az group create --location {azure_location} --name {azure_resource_group}')"
            ],
            "metadata": {
                "azdata_cell_guid": "f29b439e-cf05-4c35-aa47-1482ccd653bf",
                "tags": [
                    "hide_input"
                ]
            },
            "outputs": [],
            "execution_count": null
        },
        {
            "cell_type": "markdown",
            "source": [
                "### Create storage account and storage account container, then upload the dacpac"
            ],
            "metadata": {
                "azdata_cell_guid": "90ec2b26-0c4a-4aa4-b397-f16b09b454ea"
            }
        },
        {
            "cell_type": "code",
            "source": [
                "if dacpac_path == \"\":\n",
                "    print(f'Dacpac zip file not provided')\n",
                "    blob_sas = ''\n",
                "else:    \n",
                "    dacpac_name = ntpath.basename(dacpac_path)\n",
                "    storage_accounts = run_command(f'az storage account list --resource-group {azure_resource_group} --subscription {azure_subscription_id}', returnObject=True)\n",
                "    storage_accounts = [storage_account for storage_account in storage_accounts if storage_account['name'] == azure_storage_account]\n",
                "    if len(storage_accounts) == 0:\n",
                "        run_command(f'az storage account create -n {azure_storage_account} -g {azure_resource_group} -l {azure_location} --sku Standard_LRS --kind Storage')\n",
                "    else:\n",
                "        print(f'storage account \\\"{azure_storage_account}\\\" already exists.')\n",
                "\n",
                "    storage_account_key = run_command(f'az storage account keys list --account-name {azure_storage_account} --resource-group {azure_resource_group}', returnObject=True)[0]['value']\n",
                "    container_exists = run_command(f'az storage container exists --name {storage_account_container} --account-key {storage_account_key} --account-name {azure_storage_account} --auth-mode key --output json', returnObject=True)['exists']\n",
                "    if container_exists:\n",
                "        print(f'storage account container \\\"{storage_account_container}\\\" already exists.')\n",
                "    else:\n",
                "        run_command(f'az storage container create --name {storage_account_container} --account-key {storage_account_key} --account-name {azure_storage_account} --auth-mode key')\n",
                "\n",
                "    blob_exists = run_command(f'az storage blob exists --container-name {storage_account_container} --name \\\"{dacpac_name}\\\" --account-key {storage_account_key} --account-name {azure_storage_account} --auth-mode key', returnObject=True)['exists']\n",
                "    if blob_exists:\n",
                "        print(f'blob \\\"{dacpac_name}\\\" already exists.')\n",
                "    else:\n",
                "        run_command(f'az storage blob upload --account-name {azure_storage_account} --container-name {storage_account_container} --name {dacpac_name} --file \\\"{dacpac_path}\\\" --account-key {storage_account_key} --auth-mode key')\n",
                "    now = time.localtime()\n",
                "    expiry = f'{(now.tm_year + 1)}-{now.tm_mon}-{now.tm_mday}'\n",
                "    blob_sas = run_command(f'az storage blob generate-sas --container-name {storage_account_container} --name \\\"{dacpac_name}\\\" --account-name {azure_storage_account} --account-key {storage_account_key} --auth-mode key --full-uri --https-only --permissions r --expiry {expiry}', returnObject=True)"
            ],
            "metadata": {
                "azdata_cell_guid": "7ab2b3ec-0832-40b3-98c0-4aa87320e7ce",
                "tags": [
                    "hide_input"
                ]
            },
            "outputs": [],
            "execution_count": null
        },
        {
            "cell_type": "markdown",
            "source": [
                "### Create network security group"
            ],
            "metadata": {
                "azdata_cell_guid": "b308771b-138a-40ce-a9d3-1d15094d537b"
            }
        },
        {
            "cell_type": "code",
            "source": [
                "nsg_list = run_command(f'az network nsg list --resource-group {azure_resource_group}', returnObject=True)\n",
                "nsg_list = [nsg for nsg in nsg_list if nsg['name'] == network_security_group]\n",
                "if len(nsg_list) == 0:\n",
                "    run_command(f'az network nsg create --name {network_security_group} --resource-group {azure_resource_group} --location {azure_location}')\n",
                "    run_command(f'az network nsg rule create --name \\\"SQL\\\" --nsg-name {network_security_group} --priority 100 --resource-group {azure_resource_group} --access Allow --description \\\"Allow SQL\\\" --destination-address-prefixes \\\"*\\\" --destination-port-ranges {sql_port} --direction Inbound --source-address-prefixes Internet --protocol Tcp')\n",
                "else:\n",
                "    print(f'Network security group \\\"{network_security_group}\\\" already exists.')"
            ],
            "metadata": {
                "azdata_cell_guid": "99cbb95c-b109-4b2e-909b-ff71a62754fb",
                "tags": [
                    "hide_input"
                ]
            },
            "outputs": [],
            "execution_count": null
        },
        {
            "cell_type": "markdown",
            "source": [
                "### Create an Edge enabled VM as an Edge device"
            ],
            "metadata": {
                "azdata_cell_guid": "41b10249-cd40-4053-b1b0-b02f562789f7"
            }
        },
        {
            "cell_type": "code",
            "source": [
                "vm_list = run_command(f'az vm list --resource-group {azure_resource_group}', returnObject=True)\n",
                "vm_list = [vm for vm in vm_list if vm['name'] == iot_device_id]\n",
                "if len(vm_list) == 0:\n",
                "    vm_image = run_command(f'az vm image list --all --location {azure_location} --offer iot_edge_vm_ubuntu --publisher microsoft_iot_edge --sku ubuntu_1604_edgeruntimeonly', returnObject=True)\n",
                "    image_urn = vm_image[0]['urn']\n",
                "    run_command(f'az vm image terms accept --urn {image_urn}')\n",
                "    vm_password_placeholder = '<admin_password>'\n",
                "    create_vm_command_template = f'az vm create --name {iot_device_id} --resource-group {azure_resource_group} --admin-username {vm_admin} --admin-password {vm_password_placeholder} --authentication-type password --image {image_urn} --location {azure_location} --nsg {network_security_group} --public-ip-address \\\"{public_ip_address_name}\\\" --public-ip-address-allocation static --public-ip-sku Standard --size {vm_size} --subnet {subnet_name} --subnet-address-prefix \\\"{subnet_address_prefix}\\\" --vnet-name {vnet_name} --vnet-address-prefix \\\"{vnet_address_prefix}\\\"'\n",
                "    run_command(create_vm_command_template.replace(vm_password_placeholder, vm_password), displayCommand=create_vm_command_template.replace(vm_password_placeholder, '******'))\n",
                "else:\n",
                "   print(f'VM \\\"{iot_device_id}\\\" already exists, skipping the vm creation.')\n",
                "ip_address = run_command(f'az vm show -d -g {azure_resource_group} -n {iot_device_id} --query publicIps', returnObject=True)"
            ],
            "metadata": {
                "azdata_cell_guid": "157fc38f-cf2a-40c6-9c9e-88f45cc5c62f",
                "tags": [
                    "hide_input"
                ]
            },
            "outputs": [],
            "execution_count": null
        },
        {
            "cell_type": "markdown",
            "source": [
                "### Create IoT hub"
            ],
            "metadata": {
                "azdata_cell_guid": "e37a04c3-515d-4cb7-99b2-f8bc6167510e"
            }
        },
        {
            "cell_type": "code",
            "source": [
                "hub_list = run_command(f'az iot hub list --resource-group {azure_resource_group}', returnObject=True)\n",
                "hub_list = [hub for hub in hub_list if hub['name'] == iot_hub_name]\n",
                "if len(hub_list) == 0:\n",
                "    run_command(f'az iot hub create --name {iot_hub_name} --resource-group {azure_resource_group} --location {azure_location} --sku {iot_hub_sku} --unit {iot_hub_units}')\n",
                "else:\n",
                "    print(f'IoT hub \\\"{iot_hub_name}\\\" already exists')"
            ],
            "metadata": {
                "azdata_cell_guid": "f9f5e4ec-82a5-45df-a408-ddb0fb21847c",
                "tags": [
                    "hide_input"
                ]
            },
            "outputs": [],
            "execution_count": null
        },
        {
            "cell_type": "markdown",
            "source": [
                "### Add the Edge device to the IoT hub"
            ],
            "metadata": {
                "azdata_cell_guid": "fbc5f4ac-dfe0-4543-ace1-49b796251910"
            }
        },
        {
            "cell_type": "code",
            "source": [
                "device_list = run_command(f'az iot hub device-identity list --edge-enabled true --hub-name {iot_hub_name} --resource-group {azure_resource_group}', returnObject=True)\n",
                "device_list = [device for device in device_list if device['deviceId'] == iot_device_id]\n",
                "if len(device_list) == 0:\n",
                "    run_command(f'az iot hub device-identity create --device-id {iot_device_id} --hub-name {iot_hub_name} --resource-group {azure_resource_group} --edge-enabled true')\n",
                "else:\n",
                "    print(f'Edge device \\\"{iot_device_id}\\\" already exists.')"
            ],
            "metadata": {
                "azdata_cell_guid": "c183c3e3-8699-4f29-993b-07bf848336e3",
                "tags": [
                    "hide_input"
                ]
            },
            "outputs": [],
            "execution_count": null
        },
        {
            "cell_type": "markdown",
            "source": [
                "### Configure Edge on the device"
            ],
            "metadata": {
                "azdata_cell_guid": "069db017-9169-499a-839b-9cd73ea7d01e"
            }
        },
        {
            "cell_type": "code",
            "source": [
                "connection_string = run_command(f'az iot hub device-identity show-connection-string --device-id {iot_device_id} --hub-name {iot_hub_name} --resource-group {azure_resource_group}', returnObject=True)\n",
                "connection_string = connection_string['connectionString']\n",
                "script = f'/etc/iotedge/configedge.sh \\'{connection_string}\\''\n",
                "run_command(f'az vm run-command invoke -g {azure_resource_group} -n {iot_device_id}  --command-id RunShellScript --script \\\"{script}\\\"')"
            ],
            "metadata": {
                "azdata_cell_guid": "9ec1e31a-79aa-49f4-a0e5-16f8d7c2dd21",
                "tags": [
                    "hide_input"
                ]
            },
            "outputs": [],
            "execution_count": null
        },
        {
            "cell_type": "markdown",
            "source": [
                "### Deploy Azure SQL Edge to the device"
            ],
            "metadata": {
                "azdata_cell_guid": "ec46957f-0795-4c75-804d-f8a7ecb26382"
            }
        },
        {
            "cell_type": "code",
            "source": [
                "manifest = '{\\\"modulesContent\\\":{\\\"$edgeAgent\\\":{\\\"properties.desired\\\":{\\\"modules\\\":{\\\"AzureSQLEdge\\\":{\\\"settings\\\":{\\\"image\\\":\\\"mcr.microsoft.com/azure-sql-edge-developer\\\",\\\"createOptions\\\":\\\"{\\\\\\\"HostConfig\\\\\\\":{\\\\\\\"CapAdd\\\\\\\":[\\\\\\\"SYS_PTRACE\\\\\\\"],\\\\\\\"Binds\\\\\\\":[\\\\\\\"sqlvolume:/sqlvolume\\\\\\\"],\\\\\\\"PortBindings\\\\\\\":{\\\\\\\"1433/tcp\\\\\\\":[{\\\\\\\"HostPort\\\\\\\":\\\\\\\"<SQL_Port>\\\\\\\"}]},\\\\\\\"Mounts\\\\\\\":[{\\\\\\\"Type\\\\\\\":\\\\\\\"volume\\\\\\\",\\\\\\\"Source\\\\\\\":\\\\\\\"sqlvolume\\\\\\\",\\\\\\\"Target\\\\\\\":\\\\\\\"/var/opt/mssql\\\\\\\"}]},\\\\\\\"User\\\\\\\":\\\\\\\"0:0\\\\\\\",\\\\\\\"Env\\\\\\\":[\\\\\\\"MSSQL_AGENT_ENABLED=TRUE\\\\\\\",\\\\\\\"ClientTransportType=AMQP_TCP_Only\\\\\\\",\\\\\\\"MSSQL_PID=Developer\\\\\\\"]}\\\"},\\\"type\\\":\\\"docker\\\",\\\"version\\\":\\\"1.0\\\",\\\"env\\\":{\\\"ACCEPT_EULA\\\":{\\\"value\\\":\\\"Y\\\"},\\\"SA_PASSWORD\\\":{\\\"value\\\":\\\"<Default_SQL_SA_Password>\\\"},\\\"MSSQL_LCID\\\":{\\\"value\\\":\\\"<SQL_LCID>\\\"},\\\"MSSQL_COLLATION\\\":{\\\"value\\\":\\\"<SQL_Collation>\\\"}},\\\"status\\\":\\\"running\\\",\\\"restartPolicy\\\":\\\"always\\\"}},\\\"runtime\\\":{\\\"settings\\\":{\\\"minDockerVersion\\\":\\\"v1.25\\\"},\\\"type\\\":\\\"docker\\\"},\\\"schemaVersion\\\":\\\"1.0\\\",\\\"systemModules\\\":{\\\"edgeAgent\\\":{\\\"settings\\\":{\\\"image\\\":\\\"mcr.microsoft.com/azureiotedge-agent:1.0\\\",\\\"createOptions\\\":\\\"\\\"},\\\"type\\\":\\\"docker\\\"},\\\"edgeHub\\\":{\\\"settings\\\":{\\\"image\\\":\\\"mcr.microsoft.com/azureiotedge-hub:1.0\\\",\\\"createOptions\\\":\\\"{\\\\\\\"HostConfig\\\\\\\":{\\\\\\\"PortBindings\\\\\\\":{\\\\\\\"443/tcp\\\\\\\":[{\\\\\\\"HostPort\\\\\\\":\\\\\\\"443\\\\\\\"}],\\\\\\\"5671/tcp\\\\\\\":[{\\\\\\\"HostPort\\\\\\\":\\\\\\\"5671\\\\\\\"}],\\\\\\\"8883/tcp\\\\\\\":[{\\\\\\\"HostPort\\\\\\\":\\\\\\\"8883\\\\\\\"}]}}}\\\"},\\\"type\\\":\\\"docker\\\",\\\"status\\\":\\\"running\\\",\\\"restartPolicy\\\":\\\"always\\\"}}}},\\\"$edgeHub\\\":{\\\"properties.desired\\\":{\\\"routes\\\":{},\\\"schemaVersion\\\":\\\"1.0\\\",\\\"storeAndForwardConfiguration\\\":{\\\"timeToLiveSecs\\\":7200}}},\\\"AzureSQLEdge\\\":{\\\"properties.desired\\\":{\\\"SqlPackage\\\":\\\"<Optional_DACPAC_ZIP_SAS_URL>\\\",\\\"ASAJobInfo\\\":\\\"<Optional_ASA_Job_ZIP_SAS_URL>\\\"}}}}'\n",
                "manifest = manifest.replace('<Optional_DACPAC_ZIP_SAS_URL>', blob_sas).replace('<Default_SQL_SA_Password>',sa_password).replace('<SQL_LCID>',sql_lcid).replace('<SQL_Port>',sql_port).replace('<SQL_Collation>',sql_collation)\n",
                "file_name = f'{uuid.uuid4().hex}.json'\n",
                "manifest_file = open(file_name, 'w')\n",
                "manifest_file.write(manifest)\n",
                "manifest_file.close()\n",
                "run_command(f'az iot edge set-modules --device-id \\\"{iot_device_id}\\\" --hub-name \\\"{iot_hub_name}\\\" --content \\\"{file_name}\\\" --resource-group {azure_resource_group}')\n",
                "os.remove(file_name)"
            ],
            "metadata": {
                "azdata_cell_guid": "81a86ff6-5a83-48be-8be7-654d152eea89",
                "tags": [
                    "hide_input"
                ]
            },
            "outputs": [],
            "execution_count": null
        },
        {
            "cell_type": "markdown",
            "source": [
                "### **Connect to Azure SQL Edge instance in Azure Data Studio**\n",
                "Click the link below to connect to the Azure SQL Edge instance, it might take a couple minutes for the service to start."
            ],
            "metadata": {
                "azdata_cell_guid": "3bdfa537-a749-45c4-b219-57d296c22739"
            }
        },
        {
            "cell_type": "code",
            "source": [
                "from IPython.display import *\n",
                "connectionParameter = '{\"serverName\":\"' + f'{ip_address},{sql_port}' + '\",\"providerName\":\"MSSQL\",\"authenticationType\":\"SqlLogin\",\"userName\": \"sa\",\"password\":' + json.dumps(sa_password) + '}'\n",
                "display(HTML('<br/><a href=\"command:azdata.connect?' + html.escape(connectionParameter)+'\"><font size=\"3\">Click here to connect to the Azure SQL Edge instance</font></a><br/>'))\n",
                "display(HTML('<br/><span style=\"color:red\"><font size=\"2\">NOTE: The Azure SQL Edge instance password is included in this link, you may want to clear the results of this code cell before saving the notebook.</font></span>'))"
            ],
            "metadata": {
                "azdata_cell_guid": "8bc29cce-96a7-4a78-89af-5c73a6431c24",
                "tags": [
                    "hide_input"
                ]
            },
            "outputs": [],
            "execution_count": null
        }
    ]
}