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  }