From 70e4d3371f2ee7d51bd86a4cffc408042cb057d5 Mon Sep 17 00:00:00 2001 From: Andrew Steurer Date: Thu, 18 Jul 2024 08:44:10 -0600 Subject: [PATCH] deleting code for the purpose of creating a more-readable PR Signed-off-by: Andrew Steurer --- .github/workflows/build-package.yaml | 97 ------------- Makefile | 3 - README.md | 199 --------------------------- azure/azure.go | 144 ------------------- azure/azure_test.go | 160 --------------------- go.mod | 7 - go.sum | 4 - infra/README.md | 28 ---- infra/main.tf | 47 ------- infra/outputs.tf | 16 --- infra/variables.tf | 13 -- main.go | 134 ------------------ spin.toml | 18 --- 13 files changed, 870 deletions(-) delete mode 100644 .github/workflows/build-package.yaml delete mode 100644 Makefile delete mode 100644 README.md delete mode 100644 azure/azure.go delete mode 100644 azure/azure_test.go delete mode 100644 go.mod delete mode 100644 go.sum delete mode 100644 infra/README.md delete mode 100644 infra/main.tf delete mode 100644 infra/outputs.tf delete mode 100644 infra/variables.tf delete mode 100644 main.go delete mode 100644 spin.toml diff --git a/.github/workflows/build-package.yaml b/.github/workflows/build-package.yaml deleted file mode 100644 index 845de07..0000000 --- a/.github/workflows/build-package.yaml +++ /dev/null @@ -1,97 +0,0 @@ -name: Build and Push Azure Client Package -on: - push: - branches: - - main - tags: - - "v*" - paths: - - 'main.go' - - 'go.mod' - - 'go.sum' - - 'spin.toml' - - 'azure/**' - - '.github/workflows/**' - pull_request: - branches: - - main - -jobs: - test-on-pull-request: - if: github.event_name == 'pull_request' - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: install go - uses: actions/setup-go@v3 - with: - go-version: "1.22" - - - name: run unit tests - run: make test - - build-on-push: - if: github.event_name == 'push' - runs-on: ubuntu-latest - steps: - - - uses: actions/checkout@v3 - - - name: install spin - uses: fermyon/actions/spin/setup@v1 - with: - github_token: ${{ github.token }} - - - name: install go - uses: actions/setup-go@v3 - with: - go-version: "1.22" - - - name: install tinygo - uses: rajatjindal/setup-actions/tinygo@v0.0.1 - with: - version: "v0.32.0" - - - name: run unit tests - run: make test - - - name: cofiguring rust toolchain version - run: rustup toolchain install stable --profile minimal - - - name: cache cargo registry and git index - id: cache-cargo-registry - uses: actions/cache@v3 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('install-script.sh') }} - restore-keys: | - ${{ runner.os }}-cargo-registry- - - - name: cache cargo binaries - id: cache-cargo-bin - uses: actions/cache@v3 - with: - path: ~/.cargo/bin - key: ${{ runner.os }}-cargo-bin-${{ hashFiles('install-script.sh') }} - restore-keys: | - ${{ runner.os }}-cargo-bin- - - - name: install wkg - run: cargo install --git https://github.com/bytecodealliance/wasm-pkg-tools wkg --locked --config net.git-fetch-with-cli=true --force - if: steps.cache-cargo-bin.outputs.cache-hit != 'true' - - - name: build spinapp - run: spin build - - - name: login to GitHub container registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: push package - run: RELEASE_VERSION=$(yq '.application.version' spin.toml) && wkg oci push ghcr.io/fermyon/wasm-pkg/fermyon-experimental/azure-client:$RELEASE_VERSION main.wasm \ No newline at end of file diff --git a/Makefile b/Makefile deleted file mode 100644 index 45442b5..0000000 --- a/Makefile +++ /dev/null @@ -1,3 +0,0 @@ -.PHONY: test -test: - go test -v ./azure \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 153ad10..0000000 --- a/README.md +++ /dev/null @@ -1,199 +0,0 @@ -# Overview - -If you have code running as an Azure Function, this guide will show you how to transform it into a portable, WebAssembly-powered application with sub-millisecond start up time that connects to your existing Azure resources (using the Spin developer tool). - -This repository contains the source code for the [WebAssembly component](https://github.com/orgs/fermyon/packages/container/package/wasm-pkg%2Ffermyon-experimental%2Fazure-client) we created which formats and signs HTTP calls in the way Azure requires. We have also provided Terraform that will deploy an example Storage Container and Queue instance in Azure. - -### How to get help - -We understand that integrating a new technology can be challenging. If you have questions or feedback, reach out to us on our [Discord channel](https://www.fermyon.com/blog/fermyon-discord). - -### Prerequisites - -We are assuming that you have familiarity with the topics below. If you are not familiar, we have included links to helpful resources. - -- Interacting progarmmatically with Azure [Blob Storage](https://learn.microsoft.com/en-us/azure/storage/blobs/) and/or [Queue Storage](https://learn.microsoft.com/en-us/azure/storage/queues/) services -- [Writing](https://developer.fermyon.com/spin/v2/writing-apps), [building](https://developer.fermyon.com/spin/v2/build) and running a Spin application with [environment variables](https://developer.fermyon.com/spin/v2/writing-apps#adding-environment-variables-to-components) -- [Integrating a pre-built WebAssembly component into a Spin application](https://developer.fermyon.com/spin/v2/spin-application-structure) -- Making HTTP requests using [`curl`](https://curl.se/docs/tutorial.html) - -### Background - -As it currently stands, Spin applications written in Go, Python, and JavaScript are not able to use their respective [Azure](https://github.com/fermyon/spin/issues/2623) SDKs. As a workaround, we have built Spin components that make HTTP calls directly to Azure's API endpoints for object storage and queue services, which can be integrated into a Spin app and accessed via internal HTTP calls. - -The component was written to interact with Azure's blob and queue storage services. See Azure's [documentation](https://learn.microsoft.com/en-us/rest/api/azure/) for API information on other Azure services. - -### Benefits of migrating from serverless to Spin - -While function-as-a-service products (FaaS) like Azure Functions offer remarkable scalability and simplicity, they are not without their issues. Three significant pain points for FaaS customers are cost, cold-start times (how long it takes to have the code ready for execution), and vendor lock-in (you are forced to use a specific cloud provider because switching will cost too much money and/or time). Spin solves these problems by offering [sub-millisecond](https://www.fermyon.com/serverless-guide/speed-and-execution-time) cold-start times, a significant reduction in cost if deployed using [Fermyon Platform for Kubernetes](https://www.fermyon.com/platform), and cloud-agnostic portability, meaning your applications can be run on any Kubernetes cluster running [SpinKube](https://www.spinkube.dev/). - -### When is Spin not a good alternative to serverless? - -Although Spin offers some amazing features, there are some situations for which it may not be a good fit. For example, if FaaS is not severely impacting the cost to run your applications, or if cold-start times are not meaningfully affecting the performance of your applications. In these cases, the work required to migrate existing infrastructure to Spin may not be justified by the relatively small improvements in cost and performance. Another situation where Spin may not be a good fit is if your applications rely heavily on libraries which Spin doesn't yet support. It's not impossible to find workarounds (as we have with the Azure SDK); however, there are some libraries for which we have not been able to create a workaround (see our [language guides](https://developer.fermyon.com/spin/v2/language-support-overview) for more information). - -# Using the WebAssembly component - -In the `spin.toml` file of the Spin application to which you want to add the Azure component, you'll need to tell Spin that you want the component to be part of your app, and you'll need to give your application permission to make HTTP calls to the Azure component: - -```toml -# Don't forget that the main application needs to have permission to access the Azure client component, so don't forget to add either 'http://localhost:3000' or 'http://name-of-azure-component.spin.internal' as an allowed outbound host (see https://developer.fermyon.com/spin/v2/http-outbound#local-service-chaining for more details) - -[variables] -az_account_name = { required = true, secret = true } -az_shared_key = { required = true, secret = true } - -[[trigger.http]] -# For defining a custom route, see article on structuring Spin applications: https://developer.fermyon.com/spin/v2/spin-application-structure -route = "/..." -component = "name-of-azure-component" - -[component.name-of-azure-component] -# Be sure to use the current version of the package. -source = { registry = "fermyon.com", package = "fermyon-experimental:azure-client", version = " 0.1.0" } -# If the app needs to access multiple storage accounts, use "https://*.{{blob|queue}}.core.windows.net" -allowed_outbound_hosts = [ - "https://{{ az_account_name }}.blob.core.windows.net", - "https://{{ az_account_name }}.queue.core.windows.net", -] - -[component.name-of-azure-component.variables] -az_account_name = "{{ az_account_name }}" -az_shared_key = "{{ az_shared_key }}" -``` - -Once these entries have been added to the `spin.toml` file, you can run `spin build`. - -# Building from source - -### Requirements - -- Latest version of [Spin](https://developer.fermyon.com/spin/v2/install) -- Latest version of [Go](https://go.dev/doc/install) -- Latest version of [TinyGo](https://tinygo.org/getting-started/install/) - - -### Building the component: - -Navigate to the directory containing the codefiles, then run the below commands: - -```bash -# Installing dependencies -go mod download -# Building the component -spin build -``` - -# Running the application - -### Export environment variables - -In your terminal, export the below variables: - -```bash -export SPIN_VARIABLE_AZ_ACCOUNT_NAME=YOUR_ACCOUNT_NAME -export SPIN_VARIABLE_AZ_SHARED_KEY=YOUR_SHARED_KEY -``` - -Notice that the environment variables are formatted `SPIN_VARIABLE_UPPERCASE_VARIABLE_NAME`. This is the format required by Spin to read environment variables properly. As can be seen in the `spin.toml` file, the Spin application accesses the variables as `lowercase_variable_name`. - -Once the environment variables have been exported, you can run `spin up`. - -# Interacting with the application: - -The curl request examples below are for standalone Azure components. If trying to interact with the Azure component from within Spin, the commands will look a little different: - -```golang -// Place blob -method := "PUT" -endpoint := "http://name-of-azure-component.spin.internal/container-name/path/to/your/blob" -bodyData := []byte("Hello, Azure!") - -req, err := http.NewRequest(method, endpoint, bytes.NewReader(bodyData)) -if err != nil { - panic(err) -} - -req.Header.Set("x-az-service", "blob") - -resp, err := spinhttp.Send(req) -``` - -## List blobs: - -```bash -curl \ - -H 'x-az-service: blob' \ - "http://127.0.0.1:3000/container-name?restype=container&comp=list" -``` - -## Get blob: - -```bash -curl \ - -o file_name.extension \ - -H 'x-az-service: blob' \ - http://127.0.0.1:3000/container-name/path/to/your/blob -``` - -## Delete blob: - -```bash -curl \ - --request DELETE \ - -H 'x-az-service: blob' \ - http://127.0.0.1:3000/container-name/path/to/your/blob -``` - -## Place blob: - -```bash -curl \ - --request PUT \ - -H 'x-az-service: blob' \ - --data-binary @/path/to/file \ - http://127.0.0.1:3000/container-name/path/to/your/blob -```- Latest version of [Go](https://go.dev/doc/install) - - "http://127.0.0.1:3000?comp=list" -``` -## Get queue messages: - -```bash -curl \ - -H 'x-az-service: queue' \ - http://127.0.0.1:3000/your-queue-name/messages -``` - -## Delete queue message: - -```bash -# The message-id and pop-receipt string values can be retrieved via getting messages from the queue. -curl \ - --request DELETE \ - -H 'x-az-service: queue' \ - "http://127.0.0.1:3000/your-queue-name/messages/your-message-id?popreceipt=your-pop-receipt-value" -``` - -## Place queue message: - -```bash -# Per their documentation, the request body needs to be formatted using the XML as follows: -# -# YourMessageHere -# -curl \ - --request POST \ - -H 'x-az-service: queue' \ - --data-binary @path/to/your/xml/message \ - http://127.0.0.1:3000/your-queue-name/messages -``` - -# Testing - -### Requirements - -- Latest version of [Go](https://go.dev/doc/install) - -### Running the tests - -In your terminal, in the root directory of the code files, run `make test`. \ No newline at end of file diff --git a/azure/azure.go b/azure/azure.go deleted file mode 100644 index 1c92dda..0000000 --- a/azure/azure.go +++ /dev/null @@ -1,144 +0,0 @@ -package azure - -import ( - "bytes" - "crypto/hmac" - "crypto/sha256" - "encoding/base64" - "fmt" - "net/http" - "net/url" - "sort" - "strings" -) - -type AZCredentials struct { - AccountName string - AccountKey []byte - Service string -} - -func ParseAZCredentials(accountName, accountKey, service string) (*AZCredentials, error) { - decodedKey, err := base64.StdEncoding.DecodeString(accountKey) - if err != nil { - return nil, fmt.Errorf("decode account key: %v", err) - } - return &AZCredentials{AccountName: accountName, AccountKey: decodedKey, Service: service}, nil -} - -func ComputeHMACSHA256(c *AZCredentials, message string) (string, error) { - h := hmac.New(sha256.New, c.AccountKey) - _, err := h.Write([]byte(message)) - return base64.StdEncoding.EncodeToString(h.Sum(nil)), err -} - -func BuildStringToSign(c *AZCredentials, req *http.Request) (string, error) { - // Returns a blank value if the header value doesn't exist - getHeader := func(key string, headers http.Header) string { - if headers == nil { - return "" - } - if v, ok := headers[key]; ok { - if len(v) > 0 { - return v[0] - } - } - return "" - } - - headers := req.Header - - // Per the documentation, the Content-Length field must be an empty string if the content length of the request is zero. - contentLength := getHeader("Content-Length", headers) - if contentLength == "0" { - contentLength = "" - } - - canonicalizedResource, err := buildCanonicalizedResource(c, req.URL) - if err != nil { - return "", err - } - - stringToSign := strings.Join([]string{ - req.Method, - getHeader("Content-Encoding", headers), - getHeader("Content-Language", headers), - contentLength, - getHeader("Content-MD5", headers), - getHeader("Content-Type", headers), - "", // Empty date because x-ms-date is expected - getHeader("If-Modified-Since", headers), - getHeader("If-Match", headers), - getHeader("If-None-Match", headers), - getHeader("If-Unmodified-Since", headers), - getHeader("Range", headers), - buildCanonicalizedHeader(headers), - canonicalizedResource, - }, "\n") - - return stringToSign, nil -} - -// buildCanonicalizedHeader retrieves all headers which start with 'x-ms-' and creates a lexicographically sorted string: -// x-ms-header-a:foo,\nx-ms-header-b:bar,\nx-ms-header-0:baz\n -func buildCanonicalizedHeader(headers http.Header) string { - cm := map[string][]string{} - for k, v := range headers { - headerName := strings.TrimSpace(strings.ToLower(k)) - if strings.HasPrefix(headerName, "x-ms-") { - cm[headerName] = v - } - } - if len(cm) == 0 { - return "" - } - - keys := make([]string, 0, len(cm)) - for key := range cm { - keys = append(keys, key) - } - sort.Strings(keys) // Canonicalized headers must be in lexicographical order - - ch := bytes.NewBufferString("") - for i, key := range keys { - if i > 0 { - ch.WriteRune('\n') - } - ch.WriteString(key) - ch.WriteRune(':') - ch.WriteString(strings.Join(cm[key], ",")) - } - return ch.String() -} - -func buildCanonicalizedResource(c *AZCredentials, u *url.URL) (string, error) { - cr := bytes.NewBufferString("/") - cr.WriteString(c.AccountName) - - if len(u.Path) > 0 { - cr.WriteString(u.EscapedPath()) - } else { - cr.WriteString("/") - } - - params, err := url.ParseQuery(u.RawQuery) - if err != nil { - return "", fmt.Errorf("failed to parse query params: %v", err) - } - - if len(params) > 0 { - var paramNames []string - for paramName := range params { - paramNames = append(paramNames, paramName) - } - sort.Strings(paramNames) - - for _, paramName := range paramNames { - paramValues := params[paramName] - sort.Strings(paramValues) - cr.WriteString("\n" + strings.ToLower(paramName) + ":" + strings.Join(paramValues, ",")) - } - } - - return cr.String(), nil -} diff --git a/azure/azure_test.go b/azure/azure_test.go deleted file mode 100644 index 6c149ab..0000000 --- a/azure/azure_test.go +++ /dev/null @@ -1,160 +0,0 @@ -package azure - -import ( - "io" - "net/http" - "testing" -) - -func TestParseAZCredentials(t *testing.T) { - goodCreds := AZCredentials{ - AccountName: "testaccount", - AccountKey: []byte("testkey"), - Service: "testservice", - } - - tests := []struct { - name string - accountName string - accountKey string - service string - expectedCreds *AZCredentials - expectedError bool - }{{ - name: "properly base64 encoded account key", - accountName: "testaccount", - accountKey: "dGVzdGtleQ==", - service: "testservice", - expectedCreds: &goodCreds, - expectedError: false, - }, { - name: "non-base64 encoded account key", - accountName: "testaccount", - accountKey: "T3=$stK3y@", // Contains characters not in (A-Z, a-z, 0-9, +, /), and the length of the string is not a multiple of four, and there's a misplaced padding (=) character. - service: "testservice", - expectedCreds: nil, - expectedError: true, - }} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - creds, err := ParseAZCredentials(tt.accountName, tt.accountKey, tt.service) - - if (tt.expectedError && err == nil) || (!tt.expectedError && err != nil) { - t.Errorf("got: %v, want: %v", err, tt.expectedError) - } else if !tt.expectedError { - if string(creds.AccountKey) != string(tt.expectedCreds.AccountKey) { - t.Errorf("got: %v, want: %v", string(creds.AccountKey), string(tt.expectedCreds.AccountKey)) - } - - if creds.AccountName != tt.expectedCreds.AccountName { - t.Errorf("got: %v, want: %v", creds.AccountName, tt.expectedCreds.AccountName) - } - - if creds.Service != tt.expectedCreds.Service { - t.Errorf("got: %v, want: %v", creds.Service, tt.expectedCreds.Service) - } - } - }) - } -} - -func TestComputeHMACSHA256(t *testing.T) { - testCreds := AZCredentials{ - AccountKey: []byte("testkey"), - } - - tests := []struct { - name string - creds *AZCredentials - message string - expectedString string - expectedError bool - }{{ - name: "initial test", - creds: &testCreds, - message: "Hello, world!", - expectedString: "JcFgXrIZPVbHKy1GWjQzi/KN+3hFaA/g2+Dn9JV7UAM=", - expectedError: false, - }} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - str, err := ComputeHMACSHA256(tt.creds, tt.message) - - if str != tt.expectedString { - t.Errorf("got: %v, want: %v", str, tt.expectedString) - } - - if (tt.expectedError && err == nil) || (!tt.expectedError && err != nil) { - t.Errorf("got: %v, want: %v", err, tt.expectedError) - } - }) - } -} - -func TestBuildStringToSign(t *testing.T) { - buildReq := func(method, endpoint string, payload io.Reader, headers map[string]string) (*http.Request, error) { - req, err := http.NewRequest(method, endpoint, payload) - if err != nil { - return nil, err - } - - for key, value := range headers { - req.Header.Set(key, value) - } - - return req, nil - } - - testCreds := AZCredentials{ - AccountName: "testaccount", - AccountKey: []byte("testkey"), - Service: "testservice", - } - - goodReq, err := buildReq("GET", "https://localhost:3000/testpath?foo=bar&bar=baz", nil, map[string]string{ - "x-ms-header-a": "foo", - "x-ms-header-b": "bar", - "x-ms-header-0": "baz", - "Content-Type": "test/type", - }) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - - emptyReq, err := buildReq("GET", "https://localhost:3000", nil, map[string]string{}) - if err != nil { - t.Errorf("unexpected error: %v", err) - } - - tests := []struct { - name string - creds *AZCredentials - req *http.Request - expectedString string - expectedError bool - }{{ - name: "normal request", - creds: &testCreds, - req: goodReq, - expectedString: "GET\n\n\n\n\ntest/type\n\n\n\n\n\n\nx-ms-header-0:baz\nx-ms-header-a:foo\nx-ms-header-b:bar\n/testaccount/testpath\nbar:baz\nfoo:bar", - expectedError: false, - }, { - name: "empty headers", - creds: &testCreds, - req: emptyReq, - expectedString: "GET\n\n\n\n\n\n\n\n\n\n\n\n\n/testaccount/", - expectedError: false, - }} - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - str, err := BuildStringToSign(tt.creds, tt.req) - if str != tt.expectedString { - t.Errorf("got: %v, want: %v", str, tt.expectedString) - } - - if (tt.expectedError && err == nil) || (!tt.expectedError && err != nil) { - t.Errorf("got: %v, want: %v", err, tt.expectedError) - } - }) - } -} diff --git a/go.mod b/go.mod deleted file mode 100644 index 2138b7a..0000000 --- a/go.mod +++ /dev/null @@ -1,7 +0,0 @@ -module github.com/fermyon/experimental-azure-client - -go 1.22 - -require github.com/fermyon/spin/sdk/go/v2 v2.2.0 - -require github.com/julienschmidt/httprouter v1.3.0 // indirect diff --git a/go.sum b/go.sum deleted file mode 100644 index c283acc..0000000 --- a/go.sum +++ /dev/null @@ -1,4 +0,0 @@ -github.com/fermyon/spin/sdk/go/v2 v2.2.0 h1:zHZdIqjbUwyxiwdygHItnM+vUUNSZ3CX43jbIUemBI4= -github.com/fermyon/spin/sdk/go/v2 v2.2.0/go.mod h1:kfJ+gdf/xIaKrsC6JHCUDYMv2Bzib1ohFIYUzvP+SCw= -github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= diff --git a/infra/README.md b/infra/README.md deleted file mode 100644 index c5b4041..0000000 --- a/infra/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# Overview - -# Requirements - -- Latest version of [Terraform](https://developer.hashicorp.com/terraform/install) - - For configuration information, visit [Hashicorp's documentation](https://developer.hashicorp.com/terraform/tutorials/azure-get-started), or [Microsoft's documentation](https://learn.microsoft.com/en-us/azure/developer/terraform/) - -# Usage - -### Variables - -In the directory with the Terraform files, create a `terraform.tfvars` file, and define the `prefix`(required) and any `tags`(optional). - -### Deploying - -To deploy the Azure infrastructure defined in the `main.tf` file, navigate in your terminal to the directory containing the Terraform files and run the below commands: - -```bash -terraform init - -terraform apply --auto-approve -``` - -To view the outputs defined in the `outputs.tf` file, run the below command: - -```bash -terraform output -``` \ No newline at end of file diff --git a/infra/main.tf b/infra/main.tf deleted file mode 100644 index ff6798d..0000000 --- a/infra/main.tf +++ /dev/null @@ -1,47 +0,0 @@ -terraform { - required_version = ">=1.0" - - required_providers { - azurerm = { - source = "hashicorp/azurerm" - version = "3.99.0" - } - } -} - -provider "azurerm" { - features {} -} - -resource "random_id" "demo" { - byte_length = 4 -} - -locals { - base_name = "${var.prefix}${random_id.demo.hex }" - tags =var.tags -} - -resource "azurerm_resource_group" "demo" { - name = "rg-${local.base_name}" - location = var.location -} - -resource "azurerm_storage_account" "demo" { - name = "${local.base_name}" - resource_group_name = azurerm_resource_group.demo.name - location = azurerm_resource_group.demo.location - account_tier = "Standard" - account_replication_type = "LRS" -} - -resource "azurerm_storage_container" "demo" { - name = "container-${local.base_name}" - storage_account_name = azurerm_storage_account.demo.name - container_access_type = "private" -} - -resource "azurerm_storage_queue" "demo" { - name = "queue-${local.base_name}" - storage_account_name = azurerm_storage_account.demo.name -} \ No newline at end of file diff --git a/infra/outputs.tf b/infra/outputs.tf deleted file mode 100644 index 1a6fb84..0000000 --- a/infra/outputs.tf +++ /dev/null @@ -1,16 +0,0 @@ -output "storage_account_primary_access_key" { - value = azurerm_storage_account.demo.primary_access_key - sensitive = true -} - -output "storage_account_name" { - value = azurerm_storage_account.demo.name -} - -output "container_name" { - value = azurerm_storage_container.demo.name -} - -output "queue_name" { - value = azurerm_storage_queue.demo.name -} \ No newline at end of file diff --git a/infra/variables.tf b/infra/variables.tf deleted file mode 100644 index 3c9eab4..0000000 --- a/infra/variables.tf +++ /dev/null @@ -1,13 +0,0 @@ -variable "prefix" { - type = string -} - -variable "location" { - type = string -} - -variable "tags" { - description = "Map of extra tags to attach to items which accept them" - type = map(string) - default = {} -} \ No newline at end of file diff --git a/main.go b/main.go deleted file mode 100644 index c18ef23..0000000 --- a/main.go +++ /dev/null @@ -1,134 +0,0 @@ -package main - -// This was built using the Azure API documentation: https://learn.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key - -import ( - "bytes" - "fmt" - "io" - "net/http" - "time" - - "github.com/fermyon/experimental-azure-client/azure" - spinhttp "github.com/fermyon/spin/sdk/go/v2/http" - "github.com/fermyon/spin/sdk/go/v2/variables" -) - -func init() { - spinhttp.Handle(func(w http.ResponseWriter, r *http.Request) { - accountName, err := variables.Get("az_account_name") - if err != nil { - http.Error(w, "Error retrieving Azure account name", http.StatusInternalServerError) - return - } - - sharedKey, err := variables.Get("az_shared_key") - if err != nil { - http.Error(w, "Error retrieving Azure shared_key", http.StatusInternalServerError) - return - } - - service := r.Header.Get("x-az-service") - if service == "" { - http.Error(w, "ERROR: You must include the 'x-az-service' header in your request", http.StatusBadRequest) - return - } - - // This gets the vital portions of the uri path, while excluding the route path defined in the spin.toml file - //See https://developer.fermyon.com/spin/v2/http-trigger#additional-request-information - uriPath := r.Header.Get("spin-path-info") - queryString := r.URL.RawQuery - endpoint := fmt.Sprintf("https://%s.%s.core.windows.net", accountName, service) - - if len(queryString) == 0 { - if uriPath == "/" { - http.Error(w, fmt.Sprint("If you are not including a query string, you must have a more specific URI path (i.e. /containerName/path/to/object)"), http.StatusBadRequest) - return - } else { - endpoint += uriPath - } - } else { - endpoint += uriPath + "?" + queryString - } - - now := time.Now().UTC() - - bodyData, err := io.ReadAll(r.Body) - if err != nil { - http.Error(w, fmt.Sprintf("Failed to read request body: %s", err.Error()), http.StatusInternalServerError) - return - } - r.Body.Close() - - req, err := http.NewRequest(r.Method, endpoint, bytes.NewReader(bodyData)) - if err != nil { - http.Error(w, fmt.Sprintf("Failed to create http request: %s", err.Error()), http.StatusInternalServerError) - } - - resp, err := sendAzureRequest(req, now, accountName, sharedKey, service) - if err != nil { - http.Error(w, fmt.Sprintf("Failed to execute outbound http request: %s", err.Error()), http.StatusInternalServerError) - return - } - - body, err := io.ReadAll(resp.Body) - if err != nil { - http.Error(w, fmt.Sprintf("Failed to read outbound http response: %s", err.Error()), http.StatusInternalServerError) - return - } - resp.Body.Close() - - if resp.StatusCode < 200 || resp.StatusCode >= 300 { - http.Error(w, fmt.Sprintf("Response from outbound http request is not OK:\n%v\n%v", resp.Status, string(body)), http.StatusBadRequest) - return - } - - w.WriteHeader(resp.StatusCode) - - if len(body) == 0 { - w.Write([]byte("Response from Azure: " + resp.Status)) - } else { - w.Write(body) - } - }) -} - -// sendAzureRequest was built to interact with the Blob Storage and Storage Queue services. Please see Microsoft's documentation for other Azure services: https://learn.microsoft.com/en-us/rest/api/azure/ -func sendAzureRequest(req *http.Request, now time.Time, accountName, sharedKey, service string) (*http.Response, error) { - cred, err := azure.ParseAZCredentials(accountName, sharedKey, service) - if err != nil { - fmt.Println("Error creating credential:", err) - return nil, err - } - - // Setting universally required headers - req.Header.Set("x-ms-date", now.Format(http.TimeFormat)) - req.Header.Set("x-ms-version", "2024-08-04") // Although not technically required, we strongly recommend specifying the latest Azure Storage API version: https://learn.microsoft.com/en-us/rest/api/storageservices/versioning-for-the-azure-storage-services - - // Setting method and service-specific headers - if req.Method == "PUT" || req.Method == "POST" { - req.Header.Set("content-length", fmt.Sprintf("%d", req.ContentLength)) - - if service == "blob" { - req.Header.Set("x-ms-blob-type", "BlockBlob") - } - } - - stringToSign, err := azure.BuildStringToSign(cred, req) - if err != nil { - fmt.Println("Error building string to sign:", err) - return nil, err - } - - signature, err := azure.ComputeHMACSHA256(cred, stringToSign) - if err != nil { - fmt.Println("Error computing signature:", err) - return nil, err - } - authHeader := fmt.Sprintf("SharedKey %s:%s", accountName, signature) - req.Header.Set("authorization", authHeader) - - return spinhttp.Send(req) -} - -func main() {} diff --git a/spin.toml b/spin.toml deleted file mode 100644 index f785318..0000000 --- a/spin.toml +++ /dev/null @@ -1,18 +0,0 @@ -spin_manifest_version = 2 - -[application] -name = "Go Azure" -# Update with latest version number -version = "0.1.0" -authors = ["Andrew Steurer "] - -[[trigger.http]] -route = "/..." -component = "go-azure" - -[component.go-azure] -source = "main.wasm" - -[component.go-azure.build] -command = "tinygo build -target=wasi -gc=leaking -no-debug -o main.wasm main.go" -watch = ["**/*.go", "go.mod"] \ No newline at end of file