Skip to content

Instantly share code, notes, and snippets.

@afflom
Last active July 26, 2024 18:48
Show Gist options
  • Save afflom/13bb25d287c45f182913b380aef2302c to your computer and use it in GitHub Desktop.
Save afflom/13bb25d287c45f182913b380aef2302c to your computer and use it in GitHub Desktop.
ORAS and afero in WASM
//go:build js && wasm
// +build js,wasm
package main
import (
"context"
"io"
"strings"
"syscall/js"
"time"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/spf13/afero"
"oras.land/oras-go/v2"
"oras.land/oras-go/v2/content"
"oras.land/oras-go/v2/content/memory"
"oras.land/oras-go/v2/registry/remote"
"oras.land/oras-go/v2/registry/remote/auth"
"oras.land/oras-go/v2/registry/remote/retry"
"github.com/opencontainers/go-digest"
)
var fs afero.Fs
func main() {
fs = afero.NewMemMapFs()
// Expose the writeFile function to JavaScript
js.Global().Set("writeFile", js.FuncOf(writeToFile))
// Keep the program running to interact with the JavaScript environment
select {}
}
func writeToFile(this js.Value, args []js.Value) interface{} {
go func() {
if len(args) != 2 {
logError("Invalid number of arguments")
return
}
uploadedData := args[0].String()
file, err := fs.Create("/example.txt")
if err != nil {
logError("Error creating file: " + err.Error())
return
}
_, err = file.WriteString(uploadedData)
if err != nil {
logError("Error writing to file: " + err.Error())
return
}
file.Close()
registry := args[1].String()
ufile, err := fs.Open("/example.txt")
if err != nil {
logError("Error opening file: " + err.Error())
return
}
data, err := io.ReadAll(ufile)
if err != nil {
logError("Error reading file: " + err.Error())
return
}
desc := ocispec.Descriptor{
MediaType: "application/vnd.example+type",
Digest: digest.FromBytes(data),
Size: int64(len(data)),
ArtifactType: "application/vnd.example.art+type",
}
store := memory.New()
opts := oras.PackManifestOptions{
Layers: []ocispec.Descriptor{
desc,
},
ManifestAnnotations: map[string]string{
"uor.module": "hello",
},
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
artifactType := "application/vnd.example.wasm+type"
manifestDesc, err := oras.PackManifest(ctx, store, oras.PackManifestVersion1_1, artifactType, opts)
if err != nil {
logError("Error packing manifest: " + err.Error())
return
}
logInfo("Manifest descriptor: " + manifestDesc.Digest.String())
// Verify the packed manifest
manifestData, err := content.FetchAll(ctx, store, manifestDesc)
if err != nil {
logError("Error fetching manifest content: " + err.Error())
return
}
logInfo("Manifest content: " + string(manifestData))
logInfo("Registry: " + registry)
tag := "latest"
if err = store.Tag(ctx, manifestDesc, tag); err != nil {
logError("Unable to tag manifest: " + err.Error())
return
}
logInfo("Tagged manifest as: " + tag)
// Connect to a remote repository
repo, err := remote.NewRepository(registry + "/myrepo")
if err != nil {
logError("Unable to connect to registry: " + err.Error())
return
}
logInfo("Connected to: " + repo.Reference.Repository)
repo.Client = &auth.Client{
Client: retry.DefaultClient,
Cache: auth.NewCache(),
}
const maxRetries = 3
for retries := 0; retries < maxRetries; retries++ {
logInfo("Attempting to push artifact, attempt: " + string(retries+1))
_, err = oras.Copy(ctx, store, tag, repo, tag, oras.DefaultCopyOptions)
if err != nil {
if isNotFoundError(err) {
logInfo("Ignoring 404 error: " + err.Error())
break
} else {
logError("Error pushing artifact, retrying: " + err.Error())
time.Sleep(2 * time.Second)
continue
}
}
logInfo("Successfully pushed artifact")
break // Break out of the loop if successful
}
if err != nil {
logError("Failed to push artifact after retries: " + err.Error())
}
}()
return nil
}
// logError logs an error message to the JavaScript console
func logError(message string) {
js.Global().Get("console").Call("log", message)
}
// logInfo logs an informational message to the JavaScript console
func logInfo(message string) {
js.Global().Get("console").Call("log", message)
}
// isNotFoundError checks if the error indicates a 404 Not Found status
func isNotFoundError(err error) bool {
if err == nil {
return false
}
// This check assumes the error message contains "404", "NAME_UNKNOWN", or "BLOB_UNKNOWN"
return strings.Contains(err.Error(), "404") || strings.Contains(err.Error(), "NAME_UNKNOWN") || strings.Contains(err.Error(), "BLOB_UNKNOWN")
}
package main
import (
"fmt"
"log"
"net"
"net/http"
"time"
"github.com/google/go-containerregistry/pkg/registry"
)
var port = 5000
// CORS middleware function
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Set CORS headers
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, Accept, X-Requested-With")
w.Header().Set("Access-Control-Expose-Headers", "Location")
// Handle preflight requests
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusOK)
return
}
// Call the next handler
next.ServeHTTP(w, r)
})
}
func main() {
listener, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", port))
if err != nil {
log.Fatal(err)
}
porti := listener.Addr().(*net.TCPAddr).Port
log.Printf("serving on port %d", porti)
s := &http.Server{
ReadHeaderTimeout: 5 * time.Second, // prevent slowloris, quiet linter
Handler: corsMiddleware(registry.New(
registry.WithWarning(.01, "Congratulations! You've won a lifetime's supply of free image pulls from this in-memory registry!"),
)),
}
go func() {
log.Fatal(s.Serve(listener))
}()
fs := http.FileServer(http.Dir("."))
corsHandler := corsMiddleware(fs)
http.Handle("/", corsHandler)
go func() {
err = http.ListenAndServe(":8080", nil)
if err != nil {
log.Fatal(err)
}
}()
select {}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment