{Cloud Native Community Blog}

Einfache Berechnung der Azure-Kosten mit einer Deno CLI-Anwendung, entwickelt in einem VS Code Dev Container

Mittwoch, 23. Dezember 2020

Einfache Berechnung der Azure-Kosten mit einer Deno CLI-Anwendung, entwickelt in einem VS Code Dev Container

Für ein anstehendes Service-Angebot musste ich die zu erwartenden Kosten für die Azure-Dienste kalkulieren, die wir nutzen werden und ich wollte Deno schon länger mal ausprobieren sowie in einem VS Code dev container mit WSL2 entwickeln. Daher wird dies ein zweiteiliger Blog-Beitrag mit dem ersten Teil über die Azure-Pricing-API, technisch nicht sehr interessant, aber meine Erfahrungen sind vielleicht nützlich, wenn Sie schnell damit anfangen wollen. Der zweite Teil wird sich mit der Implementierung meines Tools in Deno beschäftigen. Wenn Sie nur an letzterem interessiert sind, möchten Sie vielleicht trotzdem das TL;DR für den ersten Teil lesen (direkt folgend) und dann zum zweiten Teil springen .

Das TL;DR für Teil 1: Die Azure-Preisberechnungs-REST-API

Den Azure-Preiskalkulator für eine einzelne, sehr übersichtliche Konfiguration zu verwenden, funktioniert gut, aber wenn man viele Konfigurationen (in meinem Fall 8) in verschiedenen Varianten (in meinem Fall 24) berechnen will, wird es sehr schnell umständlich und wenn man Änderungen vornehmen muss, ist es ziemlich nervig und fehleranfällig, zumindest für mich. Glücklicherweise wurde im September 2020 die Retail Rates Prices API veröffentlicht, mit der Sie die Abfragen automatisieren können. Man erhält sehr schnell erste Ergebnisse, aber die folgenden vier Themen haben mich etwas mehr Zeit gekostet:

  • Sie können nach regulärem Verbrauch ("prod" pay-as-you-go Preis), "dev/test" Verbrauch und reservierten Instanzen für "prod" abfragen. Aber Sie können nicht nach reservierten Instanzen für "prod" abfragen. Stattdessen müssen Sie den Preis der virtuellen Maschine abfragen und die Lizenz dazu addieren.
  • Damit verbunden: Einige (die meisten?) Ressourcen sind nur im Verbrauchsmodus verfügbar, da es keine speziellen Reservierungspreise gibt.
  • Und dann gibt es Ressourcen, die eine "globale" Region haben, wie z. B. IP-Adressen, was zwar einen gewissen Sinn ergibt, aber etwas schwierig zu bedienen ist. Und dann gibt es einige, die eine leere Region haben wie z.B. Windows Server Lizenzkosten, was zumindest aus meiner Sicht nicht sehr konsistent ist.
  • Wenn Sie den genauen Produktnamen nicht kennen, ist der beste Weg, den ich gefunden habe, die Namen der Zähler oder Dienstnamen mit Filtern wie $filter=startswith(tolower(menterName), 'static public') zu betrachten. Es ist nicht direkt auf der API-Seite dokumentiert, aber diese Standard-OData-Filteroperationen funktionieren und machen es einfacher, das zu finden, wonach Sie suchen.
  • Die Preise sind immer in USD und Microsoft verwendet feste Wechselkurse für andere Währungen, die anscheinend einmal pro Monat aktualisiert werden. Eine Übersicht erhalten Sie hier.

Die Details zum ersten Teil: Die Azure Pricing REST API

Die grundlegende Nutzung ist recht einfach und in der Dokumentation gut erklärt. Sie machen etwas wie

GET https://prices.azure.com/api/retail/prices?$filter=productName eq 'Virtual Machines Edsv4 Series' and skuName eq 'E4ds v4'

um Preise für die E4ds v4 VM-Größe zu erhalten. Wie Sie sehen können, erlaubt die API anonyme Anfragen und Sie müssen Standard-OData-Filter verwenden, um die gewünschten Ergebnisse zu erhalten, in diesem Fall Preise für VMs der Edsv4-Serie und insbesondere die E4ds v4-Größe. Wenn der skuName eindeutig ist, könnten Sie auch den productName-Filter entfernen, aber während meiner Tests habe ich oft mehr Ergebnisse als erwartet nur mit dem skuName erhalten, daher würde ich sagen, dass es eine gute Praxis ist, den productName-Filter immer mit zu verwenden. Um die Suche weiter auf Ihre Region und Ihr Preismodell einzugrenzen, könnten Sie etwas wie folgt tun

GET https://prices.azure.com/api/retail/prices?$filter=productName eq 'Virtual Machines Edsv4 Series' and skuName eq 'E4ds v4' and armRegionName eq 'westeurope' and priceType eq 'Consumption'

Das führt zu einem Ergebnis in etwa wie folgt:

{
  "BillingCurrency": "USD",
  "CustomerEntityId": "Default",
  "CustomerEntityType": "Retail",
  "Items": [
    {
      "currencyCode": "USD",
      "tierMinimumUnits": 0.0,
      "retailPrice": 0.346,
      "unitPrice": 0.346,
      "armRegionName": "westeurope",
      "location": "EU West",
      "effectiveStartDate": "2020-06-01T00:00:00Z",
      "meterId": "fa405127-11f9-5d0d-ac47-53511c8d2888",
      "meterName": "E4ds v4",
      "productId": "DZH318Z0CSHK",
      "skuId": "DZH318Z0CSHK/00JW",
      "productName": "Virtual Machines Edsv4 Series",
      "skuName": "E4ds v4",
      "serviceName": "Virtual Machines",
      "serviceId": "DZH313Z7MMC8",
      "serviceFamily": "Compute",
      "unitOfMeasure": "1 Hour",
      "type": "Consumption",
      "isPrimaryMeterRegion": true,
      "armSkuName": "Standard_E4ds_v4"
    }
  ],
  "NextPageLink": null,
  "Count": 1
}

Wie Sie sehen können, erhalten Sie genau einen Preis, der in diesem Fall die Kosten pro Stunde (Zeile 23) und immer in USD, so dass Sie die Kosten z. B. für einen Monat berechnen müssen, indem Sie mit 730 für die Stunden multiplizieren und dann mit dem Wechselkurs Ihrer lokalen Währung, falls erforderlich. Sie können den Link im TL;DR oben verwenden, um den aktuellen Wert dafür zu erhalten. Wenn Sie dies nun mit dem Preis für eine reservierte Instanz vergleichen wollen, ändern Sie den priceType auf reservation und müssen auch die Reservierungsdauer (1 Jahr oder 3 Jahre) hinzufügen:

GET https://prices.azure.com/api/retail/prices?$filter=productName eq 'Virtual Machines Edsv4 Series' and skuName eq 'E4ds v4' and armRegionName eq 'westeurope' and priceType eq 'Reservation' and reservationTerm eq '1 Year'

Das Ergebnis sieht sehr ähnlich aus:

{
  "BillingCurrency": "USD",
  "CustomerEntityId": "Default",
  "CustomerEntityType": "Retail",
  "Items": [
    {
      "currencyCode": "USD",
      "tierMinimumUnits": 0.0,
      "reservationTerm": "1 Year",
      "retailPrice": 1788.0,
      "unitPrice": 1788.0,
      "armRegionName": "westeurope",
      "location": "EU West",
      "effectiveStartDate": "2020-06-01T00:00:00Z",
      "meterId": "fa405127-11f9-5d0d-ac47-53511c8d2888",
      "meterName": "E4ds v4",
      "productId": "DZH318Z0CSHK",
      "skuId": "DZH318Z0CSHK/02WV",
      "productName": "Virtual Machines Edsv4 Series",
      "skuName": "E4ds v4",
      "serviceName": "Virtual Machines",
      "serviceId": "DZH313Z7MMC8",
      "serviceFamily": "Compute",
      "unitOfMeasure": "1 Hour",
      "type": "Reservation",
      "isPrimaryMeterRegion": true,
      "armSkuName": "Standard_E4ds_v4"
    }
  ],
  "NextPageLink": null,
  "Count": 1
}

Der große Unterschied sind die Zeilen 9-11: Sie sehen, dass es sich um eine Reservierungsdauer von 1 Jahr handelt, und folglich erhalten Sie den Preis für 1 Jahr. Wenn Sie also mit dem obigen Verbrauchspreis vergleichen wollen, müssen Sie entsprechend dividieren oder multiplizieren. Nun zum letzten priceType, dem Dev/Test-Verbrauch: Die "intuitive" (soweit eine REST-API intuitiv sein kann) Option wäre meiner Meinung nach, den priceType einfach in DevTestConsumption zu ändern:

GET https://prices.azure.com/api/retail/prices?$filter=productName eq 'Virtual Machines Edsv4 Series' and skuName eq 'E4ds v4' and armRegionName eq 'westeurope' and priceType eq 'DevTestConsumption'

Als Antwort kommt aber folgendes zurücl:

{
  "BillingCurrency": "USD",
  "CustomerEntityId": "Default",
  "CustomerEntityType": "Retail",
  "Items": [],
  "NextPageLink": null,
  "Count": 0
}

Der Grund dafür ist (so vermute ich), dass es keinen unterschiedlichen Preis nur für die VM gibt. Stattdessen müssen Sie den Produktnamen in Virtual Machines Edsv4 Series Windows ändern, wobei es tatsächlich einen Unterschied zwischen Consumption und DevTestConsupmtion gibt: Entweder Sie zahlen für die Lizenz oder nicht. Aber wie ich bereits erwähnt habe, gibt es keine Option DevTestReservation, also müssen Sie für dieses Szenario einen anderen Ansatz wählen: Sie erhalten den Preis für die VM mit Reservierung wie oben gesehen und addieren dann die Lizenzkosten. Wenn Sie herausfinden, dass der Produktname dafür Windows Server und der SKU-Name z.B. 4 vCPU VM ist, dann würden Sie eine Anfrage wie diese erstellen:

GET https://prices.azure.com/api/retail/prices?$filter=productName eq 'Windows Server' and skuName eq '4 vCPU VM' and armRegionName eq 'westeurope' and priceType eq 'Reservation'

Aber wieder bekommen Sie dafür eine leere Antwort. Dieses Mal gibt es sogar zwei Gründe:

  1. Die Lizenz ist nur mit dem Preistyp Consumption verfügbar, wahrscheinlich aus dem Grund, dass man für die meisten Ressourcen, einschließlich der Lizenz, tatsächlich keine Reservierung anlegen kann.
  2. Die Lizenz ist auch nur verfügbar, wenn Sie den armRegionName nicht hinzufügen oder ihn leer lassen. Wir können es also z.B. so machen, damit es funktioniert:
GET https://prices.azure.com/api/retail/prices?$filter=productName eq 'Windows Server' and skuName eq '4 vCPU VM' and armRegionName eq '' and priceType eq 'Consumption'

Damit bekommen wir jetzt ein besseres Ergebnis:

{
  "BillingCurrency": "USD",
  "CustomerEntityId": "Default",
  "CustomerEntityType": "Retail",
  "Items": [
    {
      "currencyCode": "USD",
      "tierMinimumUnits": 0.0,
      "retailPrice": 0.184,
      "unitPrice": 0.184,
      "armRegionName": "",
      "location": "",
      "effectiveStartDate": "2017-08-01T00:00:00Z",
      "meterId": "1cb88381-0905-4843-9ba2-7914066aabe5",
      "meterName": "4 vCPU VM License",
      "productId": "DZH318Z0BJS5",
      "skuId": "DZH318Z0BJS5/0009",
      "productName": "Windows Server",
      "skuName": "4 vCPU VM",
      "serviceName": "Virtual Machines Licenses",
      "serviceId": "DZH317WPTGV0",
      "serviceFamily": "Compute",
      "unitOfMeasure": "1 Hour",
      "type": "Consumption",
      "isPrimaryMeterRegion": true,
      "armSkuName": ""
    }
  ],
  "NextPageLink": null,
  "Count": 1
}

Das gleiche Problem mit dem armRegionName gilt z.B. für die öffentlichen IP-Adressen, die mit einem armRegionName eq 'Global' Filter abgefragt werden müssen. Ich habe keine Ahnung, warum es Ressourcen mit Global und Ressourcen mit leerem armRegionName gibt, das habe ich schlicht durch Ausprobieren herausgefunden.

Eine letzte Sache, die nützlich ist, wenn Sie nicht sicher sind, was der genaue Produktname und die Sku für eine Ressource sind: Sie können mit OData-Filterausdrücken wie startswith oder tolower abfragen, wenn Sie die Schreibweise nicht genau kennen. Als ich z. B. nach den IP-Adressen suchte, habe ich dies zur Eingrenzung verwendet:

GET https://prices.azure.com/api/retail/prices?$filter=startswith(tolower(meterName), 'static public')

Dies gab mir eine Liste von Ergebnissen, aber es zeigte mir zumindest den korrekten productName (Zeile 18), skuName (Zeile 19) und armRegionName (Zeile 11), so dass ich es weiter eingrenzen konnte:

{
    "BillingCurrency": "USD",
    "CustomerEntityId": "Default",
    "CustomerEntityType": "Retail",
    "Items": [
        {
            "currencyCode": "USD",
            "tierMinimumUnits": 0.0,
            "retailPrice": 0.0036,
            "unitPrice": 0.0036,
            "armRegionName": "Global",
            "location": "Global",
            "effectiveStartDate": "2014-08-01T00:00:00Z",
            "meterId": "26ce34b7-67b3-480d-9d1b-54a7fb80f67a",
            "meterName": "Static Public IP",
            "productId": "DZH318Z0BNXN",
            "skuId": "DZH318Z0BNXN/0032",
            "productName": "IP Addresses",
            "skuName": "Basic",
            "serviceName": "Virtual Network",
            "serviceId": "DZH314HC0WV9",
            "serviceFamily": "Networking",
            "unitOfMeasure": "1 Hour",
            "type": "DevTestConsumption",
            "isPrimaryMeterRegion": true,
            "armSkuName": ""
        },
        ...
    ],
    "NextPageLink": null,
    "Count": 3
}

Dies sollte Ihnen hoffentlich eine bessere Vorstellung davon geben, wie die Preis-API funktioniert und es Ihnen ermöglichen, die Probleme, die ich am Anfang hatte, zu überspringen. Danach habe ich mir ein kleines Tool gebaut, um effizienter mit der API arbeiten zu können:

Das TL;DR für Teil 2: Entwickeln einer Deno-CLI-Anwendung in einem VS-Code-Dev-Container

Mit dem Tool können Sie die gewünschten Ressourcen und Konfigurationen in einer recht einfachen JSON-Datei angeben. Sie beschreiben die Konfiguration wie Preistyp, Region und Reservierungsdauer. Dann beschreiben Sie die Ressourcen-Konfigurationen mit ihren Ressourcen und Eigenschaften wie Produktname, Sku-Name, Menge oder Zählername. Um Ihnen eine Idee zu geben, könnte ein Beispiel wie folgt aussehen:

{
  "priceConfigs": [
    {
      "priceType": "Reservation",
      "reservationTerm": "1 Year",
      "description": "Reserved",
      "armRegionName": "westeurope"
    },
    {
      "priceType": "Consumption",
      "description": "pay as you go",
      "armRegionName": "westeurope"
    }
  ],
  "hourFactor": 730,
  "exchangeRate": 0.843,
  "resourceConfigs": [
    {
      "description": "VMs",
      "resources": [
        {
          "productName": "Virtual Machines Edsv4 Series",
          "skuName": "E4ds v4",
          "amount": 2,
          "description": "Base scale set"
        },
        {
          "productName": "Premium SSD Managed Disks",
          "skuName": "P10 LRS",
          "amount": 2,
          "meterName": "P10 Disks",
          "priceType": "Consumption",
          "description": "OS Disks"
        },
        {
          "productName": "Premium SSD Managed Disks",
          "skuName": "P20 LRS",
          "amount": 2,
          "meterName": "P20 Disks",
          "priceType": "Consumption",
          "description": "Data Disk"
        },
        {
          "productName": "Windows Server",
          "skuName": "4 vCPU VM",
          "amount": 2,
          "optional": true,
          "priceType": "Consumption",
          "armRegionName": "",
          "description": "Windows License"
        }
      ]
    },
    {
      "description": "Storage and Public IP",
      "resources": [
        {
          "productName": "Standard HDD Managed Disks",
          "skuName": "S10 LRS",
          "amount": 1,
          "meterName": "S10 Disks",
          "priceType": "Consumption",
          "description": "Shared Storage"
        },
        {
          "productName": "IP Addresses",
          "skuName": "Basic",
          "armRegionName": "Global",
          "amount": 1,
          "priceType": "Consumption",
          "meterName": "Static Public IP",
          "description": "Public IP"
        }
      ]
    }
  ]
}

Wenn Sie das Tool gegen diese Konfiguration laufen lassen, liest und parst es die Daten, macht die notwendigen Aufrufe gegen die Pricing-API und gibt dann eine Datei aus, die die verschiedenen Ressourcenkonfigurationen mit den verschiedenen Preiskonfigurationen enthält, und um den Vergleich zu erleichtern, alles mit monatlichen Preisen. Da wir zwei Preiskonfigurationen und zwei Ressourcenkonfigurationen haben, erhalten wir vier Ergebnisobjekte, die eine Beschreibung, die Ressourcen mit Preisen und Summen inklusive und exklusive optionaler Ressourcen enthalten. Das können Sie dann in Excel bringen, mit Power Query transformieren und damit haben Sie alle Daten, die Sie von der Pricing-API brauchen. Es ist Excel, also wird es nie wirklich schön sein, aber Sie können damit arbeiten :)

calc

Das Wichtigste für mich ist, dass ich wenn ich z.B. einen Vergleich mit 3-Jahres-Kosten machen möchte, einfach eine neue Preiskonfiguration hinzufüge und alles ist erledigt. Oder das Hinzufügen und Entfernen einer Ressource muss nur einmal gemacht werden, nicht für jede mögliche Kombination von Konfiguration und Ressource, wie es der Fall ist, wenn man die Kalkulator-Website benutzt.

Da dies unter Linux einwandfrei läuft und auch mein WSL2-Subsystem gut funktioniert, habe ich mich entschlossen, dies in einem dev-container zu entwickeln. Dazu kann ich nur sagen, dass es extrem einfach einzurichten ist und grundsolide funktioniert, einfach gut.

Die Details zum zweiten Teil: Entwickeln einer Deno-CLI-Anwendung in einem VS-Code-Dev-Container

Mit dem Wissen, wie die Pricing-API funktioniert, konnte ich die Entscheidung über mein Tool treffen. Da ich erwartete, hauptsächlich JSON-basierte Daten zu bearbeiten, war JavaScript der klare Favorit, und da ich beim Programmieren manchmal in die Falle der Faulheit tappe, ist eine typisierte Sprache immer meine erste Wahl, also entschied ich mich für TypeScript. Seit der Einführung von Deno wollte ich es schon immer mal ausprobieren und habe die Gelegenheit genutzt. Ich werde Sie nicht mit einem "ist Deno besser als Node"-Vergleich langweilen, denn davon gibt es schon mehr als genug, sondern stattdessen zeige ich Ihnen einfach, was ich erstellt habe:

Ich habe deps.ts für meine Abhängigkeiten (ich verwende nur https://deno.land/std/flags/mod.ts) und types.d.ts / classes.ts für meine Typen und Klassen. Dann habe ich api.ts für die eigentlichen API-Aufrufe und mod.ts für die Hauptfunktionalität. Der Code selbst ist ziemlich trivial, wenn Sie also verstehen wollen, wie er funktioniert, sollten Sie das sehr schnell tun können, indem Sie sich api.ts und mod.ts ansehen. Die einzige kleine "Design-Entscheidung", die ich getroffen habe, war es keine "magischen" Transformation für die obigen Spezialfälle in den Code einzubauen, sondern dafür zu sorgen, dass ich sie so konfigurieren kann, wie Sie es unten sehen werden. Meiner Erfahrung nach funktioniert diese Art von "Magie" gut und ist am Anfang bequem, aber wenn Sie Monate oder sogar Jahre später wieder mit Ihrem Code in Berührung kommen, ist es unglaublich schwer zu verstehen oder sich zu erinnern, wie und wo Sie das implementiert haben.

Die Konfigurationsdateien haben neben der im TL;DR erläuterten Grundstruktur einige kleinere Spezialitäten, auf die ich hinweisen möchte: Wie ich oben erwähnt habe, gibt es einige Sonderfälle, wie z.B. Ressourcen, die nur in einer globalen Region oder bei Verbrauchspreisen verfügbar sind, daher habe ich einige Optionen, um die "Standardwerte" in den Preiskonfigurationen mit spezifischeren Werten in den Ressourcen-Konfigurationen zu überschreiben. Nehmen Sie zum Beispiel die IP-Adressen: Sie sind nur im Verbrauch und in der globalen Region verfügbar, also habe ich zwar verschiedene Optionen in den Preiskonfigurationen (Zeilen 4 und 7), aber diese beiden Eigenschaften werden in den Ressourcenkonfigurationen überschrieben (Zeilen 22 und 24).

{
    "priceConfigs": [
        {
            "priceType": "Reservation",
            "reservationTerm": "1 Year",
            "description": "Reserved",
            "armRegionName": "westeurope"
        },
        ...
    ],
    "hourFactor": 730,
    "exchangeRate": 0.843,
    "resourceConfigs": [
        ...
        {
            "description": "Storage and Public IP",
            "resources": [
                ...
                {
                    "productName": "IP Addresses",
                    "skuName": "Basic",
                    "armRegionName": "Global",
                    "amount": 1,
                    "priceType": "Consumption",
                    "meterName": "Static Public IP",
                    "description": "Public IP"
                }
            ]
        }
    ]
}

Sie können auch in Zeile 12 sehen, wie Sie die USD-Preise in Ihre Landeswährung umrechnen können. Und wir haben optionale Ressourcen wie die Windows Server-Lizenzen (siehe Zeile 12 unten), denn wie ich oben erwähnt habe, ist die separate Berechnung der Lizenzkosten und das Hinzufügen nur an den benötigten Stellen meiner Meinung nach der einzige gute Weg, um zu reservierten Dev/Test-Preisen zu kommen:

{
    ..."resourceConfigs": [
        {
            "description": "VMs",
            "resources": [
                ...
                {
                    "productName": "Windows Server",
                    "skuName": "4 vCPU VM",
                    "amount": 2,
                    "optional": true,
                    "priceType": "Consumption",
                    "armRegionName": "",
                    "description": "Windows License"
                }
            ]
        },
        ...
    ]
}

Die Excel-Konvertierung hängt sehr stark von Ihrem Bedarf und Ihren Vorlieben ab, daher möchte ich nur erwähnen, dass Sie die Aktion Daten > Daten holen > Aus Datei > Aus JSON verwenden, um den Import zu starten. Der Rest war ein bisschen Rätselraten, aber Sie werden es wahrscheinlich schnell verstehen, wenn Sie es ausprobieren. Der Artikel, der mir bei den ersten Schritten geholfen hat, ist hier und die offizielle Dokumentation ist auch recht gut und umfassend.

Der letzte zu erwähnende Teil ist der dev-container, den ich verwendet habe. Meine devcontainer.json-Datei, die den Container selbst und die darin zu installierenden VC-Code-Erweiterungen beschreibt, entspricht ziemlich genau dem Deno-Standard:

{
  "name": "Deno",
  "dockerFile": "Dockerfile",
  // Set *default* container specific settings.json values on container create.
  "settings": {
    "terminal.integrated.shell.linux": "/bin/bash"
  },
  // Add the IDs of extensions you want installed when the container is created.
  "extensions": [
    "denoland.vscode-deno",
    "eamodio.gitlens",
    "humao.rest-client"
  ],
  // Use 'forwardPorts' to make a list of ports inside the container available locally.
  // "forwardPorts": [],
  // Uncomment to use the Docker CLI from inside the container. See https://aka.ms/vscode-remote/samples/docker-from-docker.
  // "mounts": [ "source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind" ],
  // Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
  "remoteUser":"vscode"
}

Ich habe nur GitLens hinzugefügt, weil es einfach toll ist, und den REST Client, weil ich immer zuerst versucht habe die Pricing API direkt aufzurufen, bevor ich Code dafür geschrieben habe. Das Dockerfile, das beschreibt, wie der Dev-Container gebaut werden soll, ist ebenfalls recht einfach und unverändert gegenüber der von Microsoft bereitgestellten Standardvorlage:

FROM mcr.microsoft.com/vscode/devcontainers/base:debian-10

ENV DENO_INSTALL=/deno
RUN mkdir -p /deno \
    && curl -fsSL https://deno.land/x/install/install.sh | sh \
    && chown -R vscode /deno

ENV PATH=${DENO_INSTALL}/bin:${PATH} \
    DENO_DIR=${DENO_INSTALL}/.cache/deno

Das Erstaunliche dort war, wie einfach es einzurichten war und wie schnell es startet und dann sehr gut funktioniert. Ich werde das in Zukunft wahrscheinlich noch viel öfter benutzen (insbesondere wenn es dann hoffentlich bald Windows-Support gibt).

Wenn Sie sich das alles ansehen wollen, schauen Sie bitte auf https://github.com/cosmoconsult/azure-calculator. Der einfachste Weg, damit anzufangen, ist wahrscheinlich, es einfach in Visual Studio Code zu klonen und F5 zu drücken. Das wird die Beispielkonfiguration aus dem configs-Ordner ausführen. Wenn das funktioniert, können Sie beginnen, die Konfiguration an Ihre Bedürfnisse anzupassen und auch neue Konfigurationen zu erstellen. Viel Spaß damit!

Hinweis: Dieser Post ist ursprünglich auf Englisch auf dem Blog des Authors erschienen.