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
99 changes: 99 additions & 0 deletions commands/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,20 @@
package commands

import (
"context"
"errors"
"fmt"
"net/url"
"os"
"path/filepath"
"strings"

"github.com/fnproject/cli/client"
"github.com/fnproject/cli/common"
"github.com/fnproject/cli/config"
fnprovider "github.com/fnproject/fn_go/provider/oracle"
"github.com/oracle/oci-go-sdk/v65/objectstorage"
"github.com/spf13/viper"
"github.com/urfave/cli"
)

Expand Down Expand Up @@ -75,6 +85,15 @@ func (p *pushcmd) push(c *cli.Context) error {
return err
}

if ff.Code_only {
objectName, err := pushCodeOnlyArchive(ff)
if err != nil {
return err
}
fmt.Printf("Code-only archive uploaded successfully as %s\n", objectName)
return nil
}

fmt.Println("pushing", ff.ImageNameV20180708())

if err := common.PushV20180708(ff); err != nil {
Expand Down Expand Up @@ -103,3 +122,83 @@ func (p *pushcmd) push(c *cli.Context) error {
fmt.Printf("Function %v pushed successfully to the registry.\n", ff.ImageName())
return nil
}

func pushCodeOnlyArchive(ff *common.FuncFileV20180708) (string, error) {
contextName := viper.GetString(config.CurrentContext)
contextPath := filepath.Join(config.GetHomeDir(), ".fn", "contexts", contextName+".yaml")
ctxFile, err := config.NewContextFile(contextPath)
if err != nil {
return "", err
}
if strings.TrimSpace(ctxFile.ObjectStorageBucketName) == "" || strings.TrimSpace(ctxFile.ObjectStorageNamespace) == "" {
return "", errors.New("code-only Object Storage target is not configured in the current context")
}
archivePath := fmt.Sprintf("%s.%s.zip", ff.Name, ff.Version)
if _, err := os.Stat(archivePath); err != nil {
return "", fmt.Errorf("built archive not found at %s: %w", archivePath, err)
}
provider, err := client.CurrentProvider()
if err != nil {
return "", err
}
ociProvider, ok := provider.(*fnprovider.OracleProvider)
if !ok || ociProvider == nil {
return "", errors.New("code-only archive push requires an oracle provider")
}
client, err := objectstorage.NewObjectStorageClientWithConfigurationProvider(ociProvider.ConfigurationProvider)
if err != nil {
return "", err
}
region, err := ociProvider.ConfigurationProvider.Region()
if err == nil {
client.SetRegion(region)
}
if client.Host == "" || strings.Contains(client.Host, "objectstorage..") {
if ociProvider.FnApiUrl != nil {
objectStorageHost, hostErr := objectStorageHostFromFnAPIURL(ociProvider.FnApiUrl)
if hostErr != nil {
return "", hostErr
}
client.Host = objectStorageHost
}
}
file, err := os.Open(archivePath)
if err != nil {
return "", err
}
defer file.Close()
info, err := file.Stat()
if err != nil {
return "", err
}
objectName := archivePath
contentLength := info.Size()
request := objectstorage.PutObjectRequest{
NamespaceName: &ctxFile.ObjectStorageNamespace,
BucketName: &ctxFile.ObjectStorageBucketName,
ObjectName: &objectName,
PutObjectBody: file,
ContentLength: &contentLength,
}
_, err = client.PutObject(context.Background(), request)
if err != nil {
return "", fmt.Errorf("failed to upload archive to Object Storage: %w", err)
}
return objectName, nil
}

func objectStorageHostFromFnAPIURL(fnAPIURL *url.URL) (string, error) {
if fnAPIURL == nil {
return "", errors.New("unable to derive Object Storage host from nil Functions API URL")
}
hostParts := strings.Split(fnAPIURL.Host, ".")
if len(hostParts) < 4 {
return "", fmt.Errorf("unable to derive Object Storage host from Functions API host %s", fnAPIURL.Host)
}
region := hostParts[1]
domain := strings.Join(hostParts[3:], ".")
if region == "" || domain == "" {
return "", fmt.Errorf("unable to derive Object Storage host from Functions API host %s", fnAPIURL.Host)
}
return fmt.Sprintf("https://objectstorage.%s.%s", region, domain), nil
}
52 changes: 52 additions & 0 deletions test/cli_code_only_push_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package test

import (
"fmt"
"testing"

"github.com/fnproject/cli/common"
"github.com/fnproject/cli/testharness"
)

func TestCodeOnlyPushValidation(t *testing.T) {
t.Run("code-only push should fail when Object Storage target is not configured in current context", func(t *testing.T) {
t.Parallel()
h := testharness.Create(t)
defer h.Cleanup()

appName := h.NewAppName()
funcName := h.NewFuncName(appName)
dirName := funcName + "_dir"
h.Fn("init", "--code-only", "--runtime-name", "python311.ol9", "--runtime-config-type", "function-update", "--name", funcName, dirName).AssertSuccess()

h.Cd(dirName)
h.Fn("build").AssertSuccess()
h.Fn("push").AssertFailed().AssertStderrContains("code-only Object Storage target is not configured in the current context")
})

t.Run("code-only push should fail when built archive is missing even if context has bucket and namespace", func(t *testing.T) {
t.Parallel()
h := testharness.Create(t)
defer h.Cleanup()

contextName := h.NewContextName()
h.Fn("create", "context", "--api-url", "http://localhost:8080", contextName).AssertSuccess()
h.Fn("use", "context", contextName).AssertSuccess()
h.Fn("update", "context", "object_storage_bucket_name", "code-only-test-files").AssertSuccess()
h.Fn("update", "context", "namespace", "oraclefunctionsdevelopm").AssertSuccess()

h.MkDir("hello")
h.Cd("hello")
h.WithFile("func.yaml", fmt.Sprintf(`schema_version: %d
name: hello
version: 0.0.1
code_only: true
runtime_config:
type: function-update
runtime_name: python311.ol9
handler: hello_world.handler
`, common.LatestYamlVersion), 0644)

h.Fn("push").AssertFailed().AssertStderrContains("built archive not found at hello.0.0.1.zip")
})
}