Last active
July 26, 2024 18:48
-
-
Save afflom/13bb25d287c45f182913b380aef2302c to your computer and use it in GitHub Desktop.
ORAS and afero in WASM
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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") | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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