Skip to content

Commit

Permalink
Feat: support cue printer & add support for protocol https (#55)
Browse files Browse the repository at this point in the history
* Feat: support cue printer & add support for protocol https & external call with subpath

Signed-off-by: Yin Da <yd219913@alibaba-inc.com>

* Feat: support absolute crd path in test bootstrap

Signed-off-by: Yin Da <yd219913@alibaba-inc.com>

* Feat: support passing do key in http header

Signed-off-by: Yin Da <yd219913@alibaba-inc.com>

---------

Signed-off-by: Yin Da <yd219913@alibaba-inc.com>
  • Loading branch information
Somefive committed Feb 23, 2023
1 parent 1ab9b99 commit 3db6aa6
Show file tree
Hide file tree
Showing 10 changed files with 273 additions and 50 deletions.
2 changes: 2 additions & 0 deletions apis/cue/v1alpha1/package_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ const (
ProtocolGRPC ProviderProtocol = "grpc"
// ProtocolHTTP protocol type http for external Provider
ProtocolHTTP ProviderProtocol = "http"
// ProtocolHTTPS protocol type https for external Provider
ProtocolHTTPS ProviderProtocol = "https"
)

// Provider the external Provider in Package for cuex to run functions
Expand Down
1 change: 1 addition & 0 deletions cue/cuex/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ var (
func AddFlags(set *pflag.FlagSet) {
set.BoolVarP(&EnableExternalPackageForDefaultCompiler, "enable-external-cue-package", "", EnableExternalPackageForDefaultCompiler, "enable load external package for cuex default compiler")
set.BoolVarP(&EnableExternalPackageWatchForDefaultCompiler, "list-watch-external-cue-package", "", EnableExternalPackageWatchForDefaultCompiler, "enable watch external package changes for cuex default compiler")
set.BoolVarP(&cuexruntime.DefaultClientInsecureSkipVerify, "cuex-external-provider-insecure-skip-verify", "", cuexruntime.DefaultClientInsecureSkipVerify, "Set if the default external provider client of cuex should skip insecure verify")
}

// CompileString use cuex default compiler to compile cue string
Expand Down
6 changes: 4 additions & 2 deletions cue/cuex/runtime/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,10 @@ func (in *externalPackage) GetProviderFn(do string) ProviderFn {
if in.src.Spec.Provider == nil {
return nil
}
fn := ExternalProviderFn(*in.src.Spec.Provider)
return &fn
return &ExternalProviderFn{
Provider: *in.src.Spec.Provider,
Fn: do,
}
}

func (in *externalPackage) GetName() string {
Expand Down
34 changes: 29 additions & 5 deletions cue/cuex/runtime/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package runtime
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"fmt"
"io"
Expand All @@ -28,6 +29,8 @@ import (
"k8s.io/apimachinery/pkg/runtime"

"github.com/kubevela/pkg/apis/cue/v1alpha1"
"github.com/kubevela/pkg/cue/cuex/providers"
"github.com/kubevela/pkg/util/singleton"
)

var _ ProviderFn = GenericProviderFn[any, any](nil)
Expand Down Expand Up @@ -56,23 +59,44 @@ func (fn GenericProviderFn[T, U]) Call(ctx context.Context, value cue.Value) (cu
var _ ProviderFn = (*ExternalProviderFn)(nil)

// ExternalProviderFn external provider that implements ProviderFn interface
type ExternalProviderFn v1alpha1.Provider
type ExternalProviderFn struct {
v1alpha1.Provider
Fn string
}

// DefaultClientInsecureSkipVerify set if the default external provider client
// use insecure-skip-verify
var DefaultClientInsecureSkipVerify = true

// DefaultClient client for dealing requests
var DefaultClient = singleton.NewSingleton(func() *http.Client {
return &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: DefaultClientInsecureSkipVerify},
},
}
})

// FunctionHeaderKey http header for recording cuex provider function
const FunctionHeaderKey = "CueX-External-Provider-Function"

// Call dial external endpoints by passing the json data of the input parameter,
// then fill back returned values
func (in *ExternalProviderFn) Call(ctx context.Context, value cue.Value) (cue.Value, error) {
bs, err := value.MarshalJSON()
params := value.LookupPath(cue.ParsePath(providers.ParamsKey))
bs, err := params.MarshalJSON()
if err != nil {
return value, err
}
switch in.Protocol {
case v1alpha1.ProtocolHTTP:
case v1alpha1.ProtocolHTTP, v1alpha1.ProtocolHTTPS:
req, err := http.NewRequest(http.MethodPost, in.Endpoint, bytes.NewReader(bs))
if err != nil {
return value, err
}
req.Header.Set("Content-Type", runtime.ContentTypeJSON)
resp, err := http.DefaultClient.Do(req.WithContext(ctx))
req.Header.Set(FunctionHeaderKey, in.Fn)
resp, err := DefaultClient.Get().Do(req.WithContext(ctx))
if err != nil {
return value, err
}
Expand All @@ -89,7 +113,7 @@ func (in *ExternalProviderFn) Call(ctx context.Context, value cue.Value) (cue.Va
if err = json.Unmarshal(bs, ret); err != nil {
return value, err
}
return value.FillPath(cue.ParsePath(""), ret), nil
return value.FillPath(cue.ParsePath(providers.ReturnsKey), ret), nil
}

var _ ProviderFn = NativeProviderFn(nil)
Expand Down
43 changes: 29 additions & 14 deletions cue/cuex/runtime/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,18 +105,23 @@ func TestExternalProviderFn(t *testing.T) {

// test normal
prd := runtime.ExternalProviderFn{
Protocol: v1alpha1.ProtocolHTTP,
Endpoint: server.URL,
Provider: v1alpha1.Provider{
Protocol: v1alpha1.ProtocolHTTP,
Endpoint: server.URL,
},
}
v := cuecontext.New().CompileString(`{
input: "value"
output?: string
$params: input: "value"
$returns?: {
output?: string
...
}
}`)
out, err := prd.Call(context.Background(), v)
require.NoError(t, err)
_v := &value{}
require.NoError(t, out.Decode(_v))
require.Equal(t, _v.Output, "VALUE")
require.NoError(t, out.LookupPath(cue.ParsePath(providers.ReturnsKey)).Decode(_v))
require.Equal(t, "VALUE", _v.Output)

// test invalid input
badInput := cuecontext.New().CompileString(`what?`)
Expand All @@ -125,32 +130,42 @@ func TestExternalProviderFn(t *testing.T) {

// test invalid output
badOutput := cuecontext.New().CompileString(`{
input: "?"
output?: string
$params: input: "?"
$returns?: {
output?: string
...
}
}`)
_, err = prd.Call(context.Background(), badOutput)
require.Error(t, err)

// test bad response
badResp := cuecontext.New().CompileString(`{
input: "-"
output?: string
$params: input: "-"
$returns?: {
output?: string
...
}
}`)
_, err = prd.Call(context.Background(), badResp)
require.Error(t, err)

// test invalid protocol
prd = runtime.ExternalProviderFn{
Protocol: "-",
Endpoint: server.URL,
Provider: v1alpha1.Provider{
Protocol: "-",
Endpoint: server.URL,
},
}
_, err = prd.Call(context.Background(), v)
require.Error(t, fmt.Errorf("protocol - not supported yet"), err)

// test bad endpoint
prd = runtime.ExternalProviderFn{
Protocol: v1alpha1.ProtocolHTTP,
Endpoint: "?",
Provider: v1alpha1.Provider{
Protocol: v1alpha1.ProtocolHTTP,
Endpoint: "?",
},
}
_, err = prd.Call(context.Background(), v)
require.Error(t, err)
Expand Down
23 changes: 13 additions & 10 deletions cue/cuex/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,11 @@ func TestCuex(t *testing.T) {

var _ = bootstrap.InitKubeBuilderForTest(bootstrap.WithCRDPath("../../crds/cue.oam.dev_packages.yaml"))

type toUpperVar struct {
Input string `json:"input"`
type toUpperIn struct {
Input string `json:"input"`
}

type toUpperOut struct {
Output string `json:"output"`
}

Expand All @@ -61,13 +64,13 @@ var _ = Describe("Test Cuex Compiler", func() {
writer.WriteHeader(400)
return
}
v := &toUpperVar{}
if err := json.Unmarshal(bs, v); err != nil {
in := &toUpperIn{}
if err := json.Unmarshal(bs, in); err != nil {
writer.WriteHeader(400)
return
}
v.Output = strings.ToUpper(v.Input)
if bs, err = json.Marshal(v); err != nil {
out := &toUpperOut{Output: strings.ToUpper(in.Input)}
if bs, err = json.Marshal(out); err != nil {
writer.WriteHeader(500)
return
}
Expand All @@ -90,8 +93,8 @@ var _ = Describe("Test Cuex Compiler", func() {
#ToUpper: {
#do: "toUpper"
#provider: "string-util"
input: string
output?: string
$params: input: string
$returns?: output: string
}
`,
},
Expand Down Expand Up @@ -127,10 +130,10 @@ var _ = Describe("Test Cuex Compiler", func() {
}
toUpper: sutil.#ToUpper & {
input: decode.$returns
$params: input: decode.$returns
}
output: toUpper.output
output: toUpper.$returns.output
`)
Ω(err).To(Succeed())
s, err := v.LookupPath(cue.ParsePath("output")).String()
Expand Down
29 changes: 11 additions & 18 deletions cue/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ import (

"cuelang.org/go/cue"
"github.com/emicklei/go-restful/v3"
"k8s.io/klog/v2"
"sigs.k8s.io/yaml"

"github.com/kubevela/pkg/cue/util"
)
Expand Down Expand Up @@ -53,41 +51,36 @@ func NewCompileServer(fn CompileFn) *CompileServer {
func (in *CompileServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
bs, err := io.ReadAll(r.Body)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
_, _ = fmt.Fprintf(w, "read request body error: %s", err.Error())
http.Error(w, fmt.Sprintf("read request body error: %s", err.Error()), http.StatusBadRequest)
return
}
val, err := in.fn(r.Context(), string(bs))
if err != nil {
w.WriteHeader(http.StatusBadRequest)
_, _ = fmt.Fprintf(w, "compile cue error: %s", err.Error())
http.Error(w, fmt.Sprintf("compile cue error: %s", err.Error()), http.StatusBadRequest)
return
}
var options []util.PrintOption
if path := r.URL.Query().Get(paramKeyPath); len(path) > 0 {
val = val.LookupPath(cue.ParsePath(path))
options = append(options, util.WithPath(path))
}
switch r.Header.Get(restful.HEADER_Accept) {
case mimeCue:
w.Header().Set(restful.HEADER_ContentEncoding, mimeCue)
s, e := util.ToString(val)
bs, err = []byte(s), e
options = append(options, util.WithFormat(util.PrintFormatCue))
case mimeYaml:
w.Header().Set(restful.HEADER_ContentEncoding, mimeYaml)
if bs, err = val.MarshalJSON(); err == nil {
bs, err = yaml.JSONToYAML(bs)
}
options = append(options, util.WithFormat(util.PrintFormatYaml))
default:
w.Header().Set(restful.HEADER_ContentEncoding, restful.MIME_JSON)
bs, err = val.MarshalJSON()
options = append(options, util.WithFormat(util.PrintFormatJson))
}
bs, err = util.Print(val, options...)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
_, _ = fmt.Fprintf(w, "content encode error: %s", err.Error())
http.Error(w, fmt.Sprintf("content encode error: %s", err.Error()), http.StatusBadRequest)
return
}
if _, err = fmt.Fprint(w, string(bs)); err != nil {
w.WriteHeader(http.StatusInternalServerError)
klog.Errorf("unexpected error when writing response: %s", err.Error())
if _, err = w.Write(bs); err != nil {
http.Error(w, fmt.Sprintf("unexpected error when writing response: %s", err.Error()), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
Expand Down
Loading

0 comments on commit 3db6aa6

Please sign in to comment.