-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
134 lines (110 loc) · 4.36 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
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() {}