diff --git a/porch/apiserver/pkg/apiserver/apiserver.go b/porch/apiserver/pkg/apiserver/apiserver.go index 9e9b8d0534..9c478b0973 100644 --- a/porch/apiserver/pkg/apiserver/apiserver.go +++ b/porch/apiserver/pkg/apiserver/apiserver.go @@ -168,13 +168,21 @@ func (c completedConfig) New() (*PorchServer, error) { renderer := kpt.NewRenderer() cache := cache.NewCache(c.ExtraConfig.CacheDirectory, credentialResolver) - cad, err := engine.NewCaDEngine( + options := []engine.EngineOption{ engine.WithCache(cache), - engine.WithGRPCFunctionRuntime(c.ExtraConfig.FunctionRunnerAddress), engine.WithCredentialResolver(credentialResolver), engine.WithRenderer(renderer), engine.WithReferenceResolver(referenceResolver), - ) + } + + if c.ExtraConfig.FunctionRunnerAddress != "" { + options = append(options, engine.WithGRPCFunctionRuntime(c.ExtraConfig.FunctionRunnerAddress)) + } else { + ns := "porch-functions-system" + options = append(options, engine.WithKubeFunctionRuntime(coreClient, ns)) + } + + cad, err := engine.NewCaDEngine(options...) if err != nil { return nil, err } diff --git a/porch/config/deploy/5-rbac.yaml b/porch/config/deploy/5-rbac.yaml index eec35d697e..f59b3067b0 100644 --- a/porch/config/deploy/5-rbac.yaml +++ b/porch/config/deploy/5-rbac.yaml @@ -30,3 +30,15 @@ rules: - apiGroups: ["flowcontrol.apiserver.k8s.io"] resources: ["flowschemas", "prioritylevelconfigurations"] verbs: ["get", "watch", "list"] + +--- + +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: porch-function-executor +rules: +# Needed to launch / read function executor pods +- apiGroups: [""] + resources: ["pods"] + verbs: ["get", "watch", "list"] diff --git a/porch/config/deploy/6-rbac-bind.yaml b/porch/config/deploy/6-rbac-bind.yaml index 445c826d8c..dbf7c65906 100644 --- a/porch/config/deploy/6-rbac-bind.yaml +++ b/porch/config/deploy/6-rbac-bind.yaml @@ -24,3 +24,18 @@ subjects: - kind: ServiceAccount name: porch-server namespace: porch-system + +--- + +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: porch-function-executor +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: porch-function-executor +subjects: +- kind: ServiceAccount + name: porch-server + namespace: porch-functions-system diff --git a/porch/engine/go.mod b/porch/engine/go.mod index 336e60e8e3..c0cd9db9be 100644 --- a/porch/engine/go.mod +++ b/porch/engine/go.mod @@ -13,8 +13,10 @@ require ( github.com/go-git/go-git/v5 v5.4.3-0.20220119145113-935af59cf64f github.com/google/go-cmp v0.5.7 google.golang.org/grpc v1.44.0 + k8s.io/api v0.23.2 k8s.io/apimachinery v0.23.2 k8s.io/klog/v2 v2.40.1 + sigs.k8s.io/controller-runtime v0.11.0 sigs.k8s.io/kustomize/kyaml v0.13.3 ) @@ -103,7 +105,6 @@ require ( gopkg.in/warnings.v0 v0.1.2 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect - k8s.io/api v0.23.2 // indirect k8s.io/cli-runtime v0.23.2 // indirect k8s.io/client-go v0.23.2 // indirect k8s.io/component-base v0.23.2 // indirect diff --git a/porch/engine/go.sum b/porch/engine/go.sum index f5754d14e3..73eae40166 100644 --- a/porch/engine/go.sum +++ b/porch/engine/go.sum @@ -133,6 +133,7 @@ github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZx github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= @@ -152,8 +153,10 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 h1:7aWHqerlJ41y6FOsEUvknqgXnGmJyJSbjhAWq5pO4F8= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= @@ -401,6 +404,7 @@ github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTg github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.1 h1:DX7uPQ4WgAWfoh+NGGlbJQswnYIVvz0SRlLS3rPZQDA= github.com/go-logr/logr v1.2.1/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/zapr v1.2.0 h1:n4JnPI1T3Qq1SFEi/F8rwLrZERp2bso19PJZDB9dayk= github.com/go-logr/zapr v1.2.0/go.mod h1:Qa4Bsj2Vb+FAVeAKsLD8RLQ+YRJB8YDmOAKxaBQf7Ro= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= @@ -676,6 +680,7 @@ github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzp github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= @@ -812,11 +817,13 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= @@ -826,6 +833,7 @@ github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+ github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.28.0 h1:vGVfV9KrDTvWt5boZO0I19g2E3CsWfpPPKZM9dt3mEw= github.com/prometheus/common v0.28.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= @@ -837,6 +845,7 @@ github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDa github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= @@ -1001,15 +1010,18 @@ go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5 h1:+FNtrFTmVw0YZGpBGX56XDee33 go.starlark.net v0.0.0-20200306205701-8dd3e2ee1dd5/go.mod h1:nmDLcffg48OtT/PSW0Hg7FvpRQsQh5OSqIylirxKC7o= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI= go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -1364,6 +1376,7 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= @@ -1587,6 +1600,7 @@ k8s.io/api v0.23.0/go.mod h1:8wmDdLBHBNxtOIytwLstXt5E9PddnZb0GaMcqsvDBpg= k8s.io/api v0.23.2 h1:62cpzreV3dCuj0hqPi8r4dyWh48ogMcyh+ga9jEGij4= k8s.io/api v0.23.2/go.mod h1:sYuDb3flCtRPI8ghn6qFrcK5ZBu2mhbElxRE95qpwlI= k8s.io/apiextensions-apiserver v0.23.0/go.mod h1:xIFAEEDlAZgpVBl/1VSjGDmLoXAWRG40+GsWhKhAxY4= +k8s.io/apiextensions-apiserver v0.23.2 h1:N6CIVAhmF0ahgFKUMDdV/AUyckhUb4nIyVPohPtdUPk= k8s.io/apiextensions-apiserver v0.23.2/go.mod h1:9cs7avT6+GfzbB0pambTvH11wcaR85QQg4ovl9s15UU= k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= @@ -1651,6 +1665,7 @@ sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.25/go.mod h1:Mlj9PN sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.27/go.mod h1:tq2nT0Kx7W+/f2JVE+zxYtUhdjuELJkVpNz+x/QN5R4= sigs.k8s.io/cli-utils v0.29.2 h1:SaYo2C1xd0MVv65NQXZ6tIqT1W1iWy8CGmC+VnxQGWs= sigs.k8s.io/cli-utils v0.29.2/go.mod h1:WDVRa5/eQBKntG++uyKdyT+xU7MLdCR4XsgseqL5uX4= +sigs.k8s.io/controller-runtime v0.11.0 h1:DqO+c8mywcZLFJWILq4iktoECTyn30Bkj0CwgqMpZWQ= sigs.k8s.io/controller-runtime v0.11.0/go.mod h1:KKwLiTooNGu+JmLZGn9Sl3Gjmfj66eMbCQznLP5zcqA= sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= sigs.k8s.io/json v0.0.0-20211208200746-9f7c6b3444d2 h1:kDi4JBNAsJWfz1aEXhO8Jg87JJaPNLh5tIzYHgStQ9Y= diff --git a/porch/engine/pkg/engine/k8sruntime.go b/porch/engine/pkg/engine/k8sruntime.go new file mode 100644 index 0000000000..44a00ebaba --- /dev/null +++ b/porch/engine/pkg/engine/k8sruntime.go @@ -0,0 +1,170 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package engine + +import ( + "context" + "fmt" + "io" + "io/ioutil" + "strings" + + v1 "github.com/GoogleContainerTools/kpt/pkg/api/kptfile/v1" + "github.com/GoogleContainerTools/kpt/pkg/fn" + "github.com/GoogleContainerTools/kpt/porch/engine/pkg/kpt" + "github.com/GoogleContainerTools/kpt/porch/func/evaluator" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials/insecure" + corev1 "k8s.io/api/core/v1" + "k8s.io/klog/v2" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func NewKubeFunctionRuntime(kubeClient client.Client, namespace string) (*kubeFunctionRuntime, error) { + return &kubeFunctionRuntime{ + kubeClient: kubeClient, + namespace: namespace, + }, nil +} + +type kubeFunctionRuntime struct { + // kubeClient is the kubernetes client + kubeClient client.Client + + // namespace holds the namespace where the executors run + namespace string +} + +var _ kpt.FunctionRuntime = &kubeFunctionRuntime{} + +func (k *kubeFunctionRuntime) GetRunner(ctx context.Context, fn *v1.Function) (fn.FunctionRunner, error) { + image := fn.Image + + tokens := strings.SplitN(image, ":", 2) + if len(tokens) != 2 { + // TODO: Assume latest? + return nil, fmt.Errorf("expected version in image %q", image) + } + + functionName := lastComponent(tokens[0]) + + // Using labels here is a WIP experiment. + // In theory it allows a pod/image to support multiple functions ... but that raises a discovery/trust issues + // It might be easier simply to look at the image that the pod is running + // However, today this is handy because it lets us run functions from a mirror, i.e. we can + // give them a stable name in the package, but push them to our per-project gcr.io mirror. + // TODO: Review use of labels if/when we start auto-launching pods. + + matchLabels := map[string]string{ + "functions.porch.kpt.dev/" + functionName: "", + } + // Work around limits of annotation/label keys/values + matchAnnotations := map[string]string{ + "functions.porch.kpt.dev/" + functionName: image, + } + + var pods corev1.PodList + + var options []client.ListOption + options = append(options, client.InNamespace(k.namespace)) + options = append(options, client.MatchingLabels(matchLabels)) + if err := k.kubeClient.List(ctx, &pods, options...); err != nil { + return nil, fmt.Errorf("error listing pods: %w", err) + } + + // TODO: we should launch/manage the pods, rather than trusting the labels + for i := range pods.Items { + pod := &pods.Items[i] + if pod.Status.Phase != "Running" { + continue + } + + matchesAnnotations := true + for k, v := range matchAnnotations { + if pod.Annotations[k] != v { + matchesAnnotations = false + break + } + } + if !matchesAnnotations { + break + } + return &kubeFunctionRunner{ + pod: pod, + image: image, + }, nil + } + + return nil, fmt.Errorf("could not find pod to run function %q", image) +} + +func lastComponent(s string) string { + lastSlash := strings.LastIndex(s, "/") + return s[lastSlash+1:] +} + +func (k *kubeFunctionRuntime) Close() error { + return nil +} + +type kubeFunctionRunner struct { + pod *corev1.Pod + image string +} + +var _ fn.FunctionRunner = &kubeFunctionRunner{} + +func (k *kubeFunctionRunner) Run(r io.Reader, w io.Writer) error { + // We shouldn't be putting this into the runner + ctx := context.TODO() + + address := k.pod.Status.PodIP + if address == "" { + return fmt.Errorf("pod did not have podIP") + } + address += ":8888" + + klog.Infof("dialing grpc function runner %q", address) + + // TODO: pool connections + cc, err := grpc.Dial(address, grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return fmt.Errorf("failed to dial grpc function evaluator on %q for pod %s/%s: %w", address, k.pod.Namespace, k.pod.Name, err) + } + defer func() { + if err := cc.Close(); err != nil { + klog.Warningf("failed to close grpc connection: %v", err) + } + }() + + client := evaluator.NewFunctionEvaluatorClient(cc) + + in, err := ioutil.ReadAll(r) + if err != nil { + return fmt.Errorf("failed to read function runner input: %w", err) + } + + res, err := client.EvaluateFunction(ctx, &evaluator.EvaluateFunctionRequest{ + ResourceList: in, + Image: k.image, + }) + if err != nil { + return fmt.Errorf("func eval failed: %w", err) + } + if _, err := w.Write(res.ResourceList); err != nil { + return fmt.Errorf("failed to write function runner output: %w", err) + } + return nil +} diff --git a/porch/engine/pkg/engine/options.go b/porch/engine/pkg/engine/options.go index 81d76dadb5..f4cfbb70cf 100644 --- a/porch/engine/pkg/engine/options.go +++ b/porch/engine/pkg/engine/options.go @@ -25,6 +25,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "k8s.io/klog/v2" + "sigs.k8s.io/controller-runtime/pkg/client" ) type EngineOption interface { @@ -48,7 +49,18 @@ func WithCache(cache *cache.Cache) EngineOption { func WithGRPCFunctionRuntime(address string) EngineOption { return EngineOptionFunc(func(engine *cadEngine) error { - runtime, err := createFunctionRuntime(address) + runtime, err := createGRPCFunctionRuntime(address) + if err != nil { + return fmt.Errorf("failed to create function runtime: %w", err) + } + engine.runtime = runtime + return nil + }) +} + +func WithKubeFunctionRuntime(coreClient client.Client, ns string) EngineOption { + return EngineOptionFunc(func(engine *cadEngine) error { + runtime, err := NewKubeFunctionRuntime(coreClient, ns) if err != nil { return fmt.Errorf("failed to create function runtime: %w", err) } @@ -92,7 +104,7 @@ func WithReferenceResolver(resolver ReferenceResolver) EngineOption { }) } -func createFunctionRuntime(address string) (kpt.FunctionRuntime, error) { +func createGRPCFunctionRuntime(address string) (kpt.FunctionRuntime, error) { if address == "" { return nil, fmt.Errorf("address is required to instantiate gRPC function runtime") }