Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions commands/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import (
trigger "github.com/fnproject/cli/objects/trigger"
v2Client "github.com/fnproject/fn_go/clientv2"
models "github.com/fnproject/fn_go/modelsv2"
fnprovider "github.com/fnproject/fn_go/provider"
"github.com/oracle/oci-go-sdk/v65/artifacts"
ociCommon "github.com/oracle/oci-go-sdk/v65/common"
"github.com/oracle/oci-go-sdk/v65/keymanagement"
Expand Down Expand Up @@ -109,6 +110,7 @@ func DeployCommand() cli.Command {

type deploycmd struct {
clientV2 *v2Client.Fn
provider fnprovider.Provider

appName string
createApp bool
Expand Down Expand Up @@ -349,6 +351,7 @@ func (p *deploycmd) deployFuncV20180708(c *cli.Context, app *models.App, funcfil
if funcfile.Name == "" {
funcfile.Name = filepath.Base(filepath.Dir(funcfilePath)) // todo: should probably make a copy of ff before changing it
}
common.WarnIfOCIManagedFunctionSettingsUnsupported(os.Stderr, p.provider, funcfile.Name, funcfile)

oracleProvider, _ := getOracleProvider()
if oracleProvider != nil && oracleProvider.ImageCompartmentID != "" {
Expand Down
72 changes: 72 additions & 0 deletions common/funcfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,36 @@ type Expects struct {
Config []inputVar `yaml:"config" json:"config"`
}

// OCIDestination represents an OCI destination reference stored in func.yaml.
type OCIDestination struct {
Type string `yaml:"type,omitempty" json:"type,omitempty"`
OCID string `yaml:"ocid,omitempty" json:"ocid,omitempty"`
}

// OCIDetachedModeConfig stores detached mode settings for OCI Functions.
type OCIDetachedModeConfig struct {
Timeout string `yaml:"timeout,omitempty" json:"timeout,omitempty"`
OnSuccess *OCIDestination `yaml:"on_success,omitempty" json:"on_success,omitempty"`
OnFailure *OCIDestination `yaml:"on_failure,omitempty" json:"on_failure,omitempty"`
}

// OCIProvisionedConcurrencyConfig stores provisioned concurrency settings for OCI Functions.
type OCIProvisionedConcurrencyConfig struct {
Strategy string `yaml:"strategy,omitempty" json:"strategy,omitempty"`
Count *int `yaml:"count,omitempty" json:"count,omitempty"`
}

// OCIFunctionDeployConfig stores OCI-specific deploy configuration for a function.
type OCIFunctionDeployConfig struct {
ProvisionedConcurrency *OCIProvisionedConcurrencyConfig `yaml:"provisioned_concurrency,omitempty" json:"provisioned_concurrency,omitempty"`
DetachedMode *OCIDetachedModeConfig `yaml:"detached_mode,omitempty" json:"detached_mode,omitempty"`
}

// FuncDeployConfig stores deploy-time configuration sections in func.yaml.
type FuncDeployConfig struct {
OCI *OCIFunctionDeployConfig `yaml:"oci,omitempty" json:"oci,omitempty"`
}

// FuncFile defines the internal structure of a func.yaml/json/yml
type FuncFile struct {
// just for posterity, this won't be set on old files, but we can check that
Expand Down Expand Up @@ -96,6 +126,7 @@ type FuncFile struct {
Config map[string]string `yaml:"config,omitempty" json:"config,omitempty"`
IDLETimeout *int32 `yaml:"idle_timeout,omitempty" json:"idle_timeout,omitempty"`
Annotations map[string]interface{} `yaml:"annotations,omitempty" json:"annotations,omitempty"`
Deploy *FuncDeployConfig `yaml:"deploy,omitempty" json:"deploy,omitempty"`

// Run/test
Expects Expects `yaml:"expects,omitempty" json:"expects,omitempty"`
Expand Down Expand Up @@ -128,6 +159,7 @@ type FuncFileV20180708 struct {

Config map[string]string `yaml:"config,omitempty" json:"config,omitempty"`
Annotations map[string]interface{} `yaml:"annotations,omitempty" json:"annotations,omitempty"`
Deploy *FuncDeployConfig `yaml:"deploy,omitempty" json:"deploy,omitempty"`

SigningDetails SigningDetails `yaml:"signing_details,omitempty" json:"signing_details,omitempty""`

Expand Down Expand Up @@ -388,6 +420,43 @@ func (ff *FuncFileV20180708) ImageNameV20180708() string {
return fname
}

// HasOCIManagedFunctionSettings reports whether the func.yaml contains OCI-specific
// managed function settings that require OCI provider support.
func (ff *FuncFileV20180708) HasOCIManagedFunctionSettings() bool {
if ff == nil || ff.Deploy == nil || ff.Deploy.OCI == nil {
return false
}

oci := ff.Deploy.OCI
if oci.ProvisionedConcurrency != nil && (oci.ProvisionedConcurrency.Strategy != "" || oci.ProvisionedConcurrency.Count != nil) {
return true
}
if oci.DetachedMode != nil && (oci.DetachedMode.Timeout != "" || oci.DetachedMode.OnSuccess != nil || oci.DetachedMode.OnFailure != nil) {
return true
}

return false
}

// OCIManagedFunctionSettingNames returns the OCI-managed function setting groups
// present in func.yaml in a stable order for warnings and diagnostics.
func (ff *FuncFileV20180708) OCIManagedFunctionSettingNames() []string {
if ff == nil || ff.Deploy == nil || ff.Deploy.OCI == nil {
return nil
}

var settings []string
oci := ff.Deploy.OCI
if oci.ProvisionedConcurrency != nil && (oci.ProvisionedConcurrency.Strategy != "" || oci.ProvisionedConcurrency.Count != nil) {
settings = append(settings, "provisioned_concurrency")
}
if oci.DetachedMode != nil && (oci.DetachedMode.Timeout != "" || oci.DetachedMode.OnSuccess != nil || oci.DetachedMode.OnFailure != nil) {
settings = append(settings, "detached_mode")
}

return settings
}

// Merge the func.init.yaml from the initImage with a.ff
//
// write out the new func file
Expand All @@ -409,5 +478,8 @@ func MergeFuncFileInitYAML(path string, ff *FuncFileV20180708) error {
ff.Expects = initFf.Expects
ff.Run_image = initFf.RunImage
ff.Runtime = initFf.Runtime
if initFf.Deploy != nil {
ff.Deploy = initFf.Deploy
}
return nil
}
57 changes: 57 additions & 0 deletions common/funcfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,63 @@ entrypoint: ./func
}
}

func TestMergeFuncFileInitYAMLCopiesDeploySection(t *testing.T) {
ff := FuncFileV20180708{Name: "hello"}
initYAML := `
schema_version: 20180708
runtime: go
deploy:
oci:
provisioned_concurrency:
strategy: CONSTANT
count: 3
detached_mode:
timeout: 20m
on_success:
type: stream
ocid: ocid1.stream.oc1..example
`
folder, filePath := createInitYAML(initYAML)
defer os.RemoveAll(folder)

if err := MergeFuncFileInitYAML(filePath, &ff); err != nil {
t.Fatalf("MergeFuncFileInitYAML() error = %v", err)
}
if ff.Deploy == nil || ff.Deploy.OCI == nil || ff.Deploy.OCI.ProvisionedConcurrency == nil {
t.Fatalf("expected deploy.oci.provisioned_concurrency to be copied from init yaml")
}
if ff.Deploy.OCI.ProvisionedConcurrency.Strategy != "CONSTANT" {
t.Fatalf("expected provisioned concurrency strategy CONSTANT, got %q", ff.Deploy.OCI.ProvisionedConcurrency.Strategy)
}
if ff.Deploy.OCI.ProvisionedConcurrency.Count == nil || *ff.Deploy.OCI.ProvisionedConcurrency.Count != 3 {
t.Fatalf("expected provisioned concurrency count 3, got %#v", ff.Deploy.OCI.ProvisionedConcurrency.Count)
}
if ff.Deploy.OCI.DetachedMode == nil || ff.Deploy.OCI.DetachedMode.Timeout != "20m" {
t.Fatalf("expected detached mode timeout to be copied, got %#v", ff.Deploy.OCI.DetachedMode)
}
}

func TestFuncFileV20180708OCIManagedFunctionSettingsHelpers(t *testing.T) {
count := 5
ff := &FuncFileV20180708{
Deploy: &FuncDeployConfig{
OCI: &OCIFunctionDeployConfig{
ProvisionedConcurrency: &OCIProvisionedConcurrencyConfig{Strategy: "CONSTANT", Count: &count},
DetachedMode: &OCIDetachedModeConfig{Timeout: "20m"},
},
},
}

if !ff.HasOCIManagedFunctionSettings() {
t.Fatal("expected HasOCIManagedFunctionSettings to return true")
}
want := []string{"provisioned_concurrency", "detached_mode"}
got := ff.OCIManagedFunctionSettingNames()
if !reflect.DeepEqual(got, want) {
t.Fatalf("expected OCIManagedFunctionSettingNames %v, got %v", want, got)
}
}

func createInitYAML(contents string) (string, string) {
folder, err := ioutil.TempDir(os.TempDir(), "fn-tests")
if err != nil {
Expand Down
51 changes: 51 additions & 0 deletions common/oci_provider_support.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package common

import (
"fmt"
"io"
"reflect"
"strings"

fnprovider "github.com/fnproject/fn_go/provider"
)

// IsOracleProvider reports whether the current provider is the OCI Functions provider.
func IsOracleProvider(p fnprovider.Provider) bool {
if p == nil {
return false
}
typ := reflect.TypeOf(p)
if typ == nil {
return false
}
if typ.Kind() == reflect.Ptr {
typ = typ.Elem()
}
return typ.PkgPath() == "github.com/fnproject/fn_go/provider/oracle" && typ.Name() == "OracleProvider"
}

// WarnIfOCIManagedFunctionSettingsUnsupported emits a warning when func.yaml
// contains OCI-specific managed function settings but the active provider does
// not support OCI-managed function features.
func WarnIfOCIManagedFunctionSettingsUnsupported(w io.Writer, p fnprovider.Provider, fnName string, ff *FuncFileV20180708) bool {
if w == nil || ff == nil || !ff.HasOCIManagedFunctionSettings() || IsOracleProvider(p) {
return false
}

settings := ff.OCIManagedFunctionSettingNames()
if len(settings) == 0 {
return false
}

if fnName == "" {
fnName = ff.Name
}

_, _ = fmt.Fprintf(
w,
"Warning: function %s contains OCI-specific deploy settings (%s), but the current provider does not support OCI managed function features. These settings will be ignored.\n",
fnName,
strings.Join(settings, ", "),
)
return true
}
35 changes: 35 additions & 0 deletions common/oci_provider_support_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package common

import (
"bytes"
"strings"
"testing"
)

func TestWarnIfOCIManagedFunctionSettingsUnsupported(t *testing.T) {
count := 2
ff := &FuncFileV20180708{
Name: "hello",
Deploy: &FuncDeployConfig{
OCI: &OCIFunctionDeployConfig{
ProvisionedConcurrency: &OCIProvisionedConcurrencyConfig{
Strategy: "CONSTANT",
Count: &count,
},
},
},
}

var stderr bytes.Buffer
warned := WarnIfOCIManagedFunctionSettingsUnsupported(&stderr, nil, ff.Name, ff)
if !warned {
t.Fatal("expected warning helper to report that a warning was emitted")
}
output := stderr.String()
if !strings.Contains(output, "OCI-specific deploy settings") {
t.Fatalf("expected warning output to mention OCI-specific deploy settings, got %q", output)
}
if !strings.Contains(output, "provisioned_concurrency") {
t.Fatalf("expected warning output to include setting name, got %q", output)
}
}
51 changes: 51 additions & 0 deletions common/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,57 @@ const V20180708Schema = `{
"config": {
"type": "object"
},
"deploy": {
"type": "object",
"properties": {
"oci": {
"type": "object",
"properties": {
"provisioned_concurrency": {
"type": "object",
"properties": {
"strategy": {
"type": "string"
},
"count": {
"type": "integer"
}
}
},
"detached_mode": {
"type": "object",
"properties": {
"timeout": {
"type": "string"
},
"on_success": {
"type": "object",
"properties": {
"type": {
"type": "string"
},
"ocid": {
"type": "string"
}
}
},
"on_failure": {
"type": "object",
"properties": {
"type": {
"type": "string"
},
"ocid": {
"type": "string"
}
}
}
}
}
}
}
}
},
"triggers": {
"type": "array",
"properties": {
Expand Down
50 changes: 50 additions & 0 deletions common/schema_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package common

import (
"os"
"path/filepath"
"testing"
)

func TestValidateFileAgainstSchemaAcceptsOCIDeployConfig(t *testing.T) {
tmpDir := t.TempDir()
oldWd, err := os.Getwd()
if err != nil {
t.Fatalf("failed to get working directory: %v", err)
}
defer func() { _ = os.Chdir(oldWd) }()
if err := os.Chdir(tmpDir); err != nil {
t.Fatalf("failed to change working directory: %v", err)
}

jsonFile := filepath.Join(tmpDir, "temp.json")
content := `{
"schema_version": 20180708,
"name": "hello",
"version": "0.0.1",
"runtime": "go",
"entrypoint": "./func",
"deploy": {
"oci": {
"provisioned_concurrency": {
"strategy": "CONSTANT",
"count": 5
},
"detached_mode": {
"timeout": "20m",
"on_success": {
"type": "stream",
"ocid": "ocid1.stream.oc1..example"
}
}
}
}
}`
if err := os.WriteFile(jsonFile, []byte(content), 0644); err != nil {
t.Fatalf("failed to write temp schema file: %v", err)
}

if err := ValidateFileAgainstSchema("temp.json", V20180708Schema); err != nil {
t.Fatalf("ValidateFileAgainstSchema() error = %v", err)
}
}
Loading