github.com/wmuizelaar/kpt@v0.0.0-20221018115725-bd564717b2ed/internal/fnruntime/wasm.go (about) 1 // Copyright 2022 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package fnruntime 16 17 import ( 18 "context" 19 "fmt" 20 "io" 21 "os" 22 "path/filepath" 23 24 "github.com/GoogleContainerTools/kpt/pkg/wasm" 25 ) 26 27 type WasmRuntime string 28 29 const ( 30 Wasmtime WasmRuntime = "wasmtime" 31 Nodejs WasmRuntime = "nodejs" 32 33 WasmRuntimeEnv = "KPT_FN_WASM_RUNTIME" 34 35 jsEntrypointFunction = "processResourceList" 36 ) 37 38 type WasmFn struct { 39 runtimeType WasmRuntime 40 41 wasmtime *WasmtimeFn 42 nodejs *WasmNodejsFn 43 } 44 45 func NewWasmFn(loader WasmLoader) (*WasmFn, error) { 46 switch os.Getenv(WasmRuntimeEnv) { 47 case string(Nodejs): 48 nf, err := NewNodejsFn(loader) 49 if err != nil { 50 return nil, err 51 } 52 return &WasmFn{ 53 runtimeType: Nodejs, 54 nodejs: nf, 55 }, nil 56 case "": 57 fallthrough 58 case string(Wasmtime): 59 wf, err := NewWasmtimeFn(loader) 60 if err != nil { 61 return nil, err 62 } 63 return &WasmFn{ 64 runtimeType: Wasmtime, 65 wasmtime: wf, 66 }, nil 67 default: 68 return nil, fmt.Errorf("unsupported wasm runtime: %v", os.Getenv(WasmRuntimeEnv)) 69 } 70 } 71 72 func (f *WasmFn) Run(r io.Reader, w io.Writer) error { 73 switch f.runtimeType { 74 case Nodejs: 75 return f.nodejs.Run(r, w) 76 case Wasmtime: 77 return f.wasmtime.Run(r, w) 78 default: 79 return fmt.Errorf("unknown wasm runtime type: %q", f.runtimeType) 80 } 81 } 82 83 type WasmLoader interface { 84 // getReadCloser returns an io.ReadCloser to read the wasm contents. 85 getReadCloser() (io.ReadCloser, error) 86 // getFilePath returns a path to the wasm file. 87 getFilePath() (string, error) 88 // cleanup remove temporary directory or files. 89 cleanup() error 90 } 91 92 type OciLoader struct { 93 cacheDir string 94 image string 95 tempDir string 96 } 97 98 var _ WasmLoader = &OciLoader{} 99 100 func NewOciLoader(cacheDir, image string) *OciLoader { 101 return &OciLoader{ 102 cacheDir: cacheDir, 103 image: image, 104 } 105 } 106 107 func (o *OciLoader) getReadCloser() (io.ReadCloser, error) { 108 storage, err := wasm.NewClient(o.cacheDir) 109 if err != nil { 110 return nil, fmt.Errorf("unable to create a storage client: %w", err) 111 } 112 rc, err := storage.LoadWasm(context.TODO(), o.image) 113 if err != nil { 114 return nil, fmt.Errorf("unable to load image from %v: %w", o.image, err) 115 } 116 return rc, nil 117 } 118 119 func (o *OciLoader) getFilePath() (string, error) { 120 rc, err := o.getReadCloser() 121 if err != nil { 122 return "", fmt.Errorf("unable to get reader from OCI image: %w", err) 123 } 124 defer rc.Close() 125 data, err := io.ReadAll(rc) 126 if err != nil { 127 return "", fmt.Errorf("unable to read wasm content from reader: %w", err) 128 } 129 o.tempDir, err = os.MkdirTemp(o.cacheDir, "oci-loader-") 130 if err != nil { 131 return "", fmt.Errorf("unable to create temp dir in %v: %w", o.cacheDir, err) 132 } 133 wasmFile := filepath.Join(o.tempDir, "fn.wasm") 134 err = os.WriteFile(wasmFile, data, 0644) 135 if err != nil { 136 return "", fmt.Errorf("unable to write wasm content to %v: %w", wasmFile, err) 137 } 138 return wasmFile, nil 139 } 140 141 func (o *OciLoader) cleanup() error { 142 if o.tempDir != "" { 143 return os.RemoveAll(o.tempDir) 144 } 145 return nil 146 } 147 148 type FsLoader struct { 149 Filename string 150 } 151 152 var _ WasmLoader = &FsLoader{} 153 154 func (f *FsLoader) getFilePath() (string, error) { 155 return f.Filename, nil 156 } 157 158 func (f *FsLoader) getReadCloser() (io.ReadCloser, error) { 159 fi, err := os.Open(f.Filename) 160 if err != nil { 161 return nil, fmt.Errorf("failed to open file %v: %w", f.Filename, err) 162 } 163 return fi, nil 164 } 165 166 func (f *FsLoader) cleanup() error { 167 return nil 168 }