@@ -16,81 +16,23 @@ package plugins
1616
1717import (
1818 "context"
19- "encoding/json"
2019 "fmt"
21- "time "
20+ "strings "
2221
2322 apiv2beta1 "github.com/kubeflow/pipelines/backend/api/v2beta1/go_client"
24- "github.com/kubeflow/pipelines/backend/src/apiserver/model"
2523 commonplugins "github.com/kubeflow/pipelines/backend/src/common/plugins"
2624 "github.com/kubeflow/pipelines/backend/src/common/util"
27- "google.golang.org/protobuf/encoding/protojson "
25+ corev1 "k8s.io/api/core/v1 "
2826 apierrors "k8s.io/apimachinery/pkg/api/errors"
2927 v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3028 "k8s.io/client-go/kubernetes"
3129)
3230
33- type PluginsOutputEnvelope struct {
34- Plugins map [string ]json.RawMessage
35- }
36-
37- func (e * PluginsOutputEnvelope ) UnmarshalJSON (data []byte ) error {
38- var all map [string ]json.RawMessage
39- if err := json .Unmarshal (data , & all ); err != nil {
40- return err
41- }
42- if len (all ) > 0 {
43- e .Plugins = all
44- }
45- return nil
46- }
47-
48- func (e PluginsOutputEnvelope ) MarshalJSON () ([]byte , error ) {
49- if len (e .Plugins ) == 0 {
50- return []byte ("{}" ), nil
51- }
52- return json .Marshal (e .Plugins )
53- }
54-
55- // set stores a plugin entry by name.
56- func (e * PluginsOutputEnvelope ) set (name string , data json.RawMessage ) {
57- if e .Plugins == nil {
58- e .Plugins = make (map [string ]json.RawMessage )
59- }
60- e .Plugins [name ] = data
61- }
62-
63- func (e * PluginsOutputEnvelope ) forEachEntry (fn func (name string , payload json.RawMessage )) {
64- for name , payload := range e .Plugins {
65- fn (name , payload )
66- }
67- }
68-
69- const (
70- EntryExperimentName = "experiment_name"
71- EntryExperimentID = "experiment_id"
72- EntryRootRunID = "root_run_id"
73- EntryRunURL = "run_url"
74- EntryEndpoint = "endpoint"
75- )
76-
7731const (
7832 LauncherConfigMapName = "kfp-launcher"
7933 LauncherConfigKeyPrefix = "plugins."
8034)
8135
82- const (
83- // DefaultTimeout is the default HTTP request timeout for the plugin client.
84- DefaultTimeout = "30s"
85- )
86-
87- type RunSyncMode string
88-
89- const (
90- RunSyncModeTerminal RunSyncMode = "terminal"
91- RunSyncModeRetry RunSyncMode = "retry"
92- )
93-
9436// KubeClientProvider abstracts Kubernetes clientset access.
9537type KubeClientProvider interface {
9638 GetClientSet () kubernetes.Interface
@@ -105,20 +47,20 @@ type PluginConfig struct {
10547
10648// InjectPluginRuntimeEnv upserts plugin-provided environment variables into the
10749// driver and launcher containers of the execution spec.
108- func InjectPluginRuntimeEnv (executionSpec util.ExecutionSpec , env map [ string ] string ) error {
109- if len (env ) == 0 || executionSpec == nil {
50+ func InjectPluginRuntimeEnv (executionSpec util.ExecutionSpec , envVars []corev1. EnvVar ) error {
51+ if len (envVars ) == 0 || executionSpec == nil {
11052 return nil
11153 }
112- return executionSpec .UpsertRuntimeEnvVars (env ,
54+ return executionSpec .UpsertRuntimeEnvVars (envVars ,
11355 util .ExecutionRuntimeRoleDriver ,
11456 util .ExecutionRuntimeRoleLauncher ,
11557 )
11658}
11759
118- // GetNamespacePluginConfig reads the namespace-level Plugin configuration
119- // from the kfp-launcher ConfigMap. Returns nil (no error) when the ConfigMap
120- // or key is absent .
121- func GetNamespacePluginConfig (ctx context.Context , clientSet kubernetes.Interface , pluginName , namespace string ) (* PluginConfig , error ) {
60+ // GetLauncherNamespacePluginConfigsMap retrieves a map of plugin configurations from a Kubernetes ConfigMap in a given namespace.
61+ // It requires a non-empty namespace and a valid Kubernetes clientset to access the ConfigMap.
62+ // Returns a map of plugin configurations or an error if the ConfigMap retrieval or parsing fails .
63+ func GetLauncherNamespacePluginConfigsMap (ctx context.Context , clientSet kubernetes.Interface , namespace string ) (map [ string ] string , error ) {
12264 if namespace == "" {
12365 return nil , util .NewInternalServerError (fmt .Errorf ("namespace is empty" ), "namespace must be specified when reading Plugin config" )
12466 }
@@ -130,129 +72,16 @@ func GetNamespacePluginConfig(ctx context.Context, clientSet kubernetes.Interfac
13072 if apierrors .IsNotFound (err ) {
13173 return nil , nil
13274 }
133- return nil , util .NewInternalServerError (err , "failed to read %s plugin namespace config from configmap %q in namespace %q" , pluginName , LauncherConfigMapName , namespace )
134- }
135- launcherConfigKey := LauncherConfigKeyPrefix + pluginName
136- raw , ok := cm .Data [launcherConfigKey ]
137- if ! ok || raw == "" {
138- return nil , nil
139- }
140- var cfg PluginConfig
141- if err := json .Unmarshal ([]byte (raw ), & cfg ); err != nil {
142- return nil , util .NewInternalServerError (err , "failed to parse %s plugin config from key %q in configmap %q/%q" , pluginName , launcherConfigKey , namespace , LauncherConfigMapName )
75+ return nil , util .NewInternalServerError (err , "failed to read MLflow namespace config from configmap %q in namespace %q" , LauncherConfigMapName , namespace )
14376 }
144- return & cfg , nil
145- }
14677
147- // SetPendingRunPluginOutput serializes the given PluginOutput into PendingRun.PluginsOutput.
148- func SetPendingRunPluginOutput (run * PendingRun , pluginName string , output * apiv2beta1.PluginOutput ) error {
149- if run == nil || output == nil || pluginName == "" {
150- return nil
151- }
152- result , err := upsertPluginOutput (run .PluginsOutput , pluginName , output )
153- if err != nil {
154- return err
155- }
156- run .PluginsOutput = & result
157- return nil
158- }
159-
160- // upsertPluginOutput merges a single plugin's output into an existing
161- // plugins_output JSON string, returning the updated JSON.
162- func upsertPluginOutput (existing * string , pluginName string , output * apiv2beta1.PluginOutput ) (string , error ) {
163- marshaledOutput , err := protojson .Marshal (output )
164- if err != nil {
165- return "" , fmt .Errorf ("failed to marshal plugin output for %q: %w" , pluginName , err )
166- }
167- var envelope PluginsOutputEnvelope
168- if existing != nil && * existing != "" {
169- if err := json .Unmarshal ([]byte (* existing ), & envelope ); err != nil {
170- return "" , fmt .Errorf ("failed to unmarshal existing plugins_output: %w" , err )
78+ cfgMap := make (map [string ]string )
79+ for key , value := range cm .Data {
80+ if strings .HasPrefix (key , LauncherConfigKeyPrefix ) {
81+ cfgMap [strings .TrimPrefix (key , LauncherConfigKeyPrefix )] = value
17182 }
17283 }
173- envelope .set (pluginName , marshaledOutput )
174- marshaledMap , err := json .Marshal (envelope )
175- if err != nil {
176- return "" , fmt .Errorf ("failed to marshal plugins_output map: %w" , err )
177- }
178- return string (marshaledMap ), nil
179- }
180-
181- func GetParentRunID (output * apiv2beta1.PluginOutput ) string {
182- return GetStringEntry (output , EntryRootRunID )
183- }
184-
185- func GetStringEntry (output * apiv2beta1.PluginOutput , key string ) string {
186- if output == nil || output .Entries == nil || key == "" {
187- return ""
188- }
189- entry , ok := output .Entries [key ]
190- if ! ok || entry == nil || entry .Value == nil {
191- return ""
192- }
193- return entry .Value .GetStringValue ()
194- }
195-
196- // PersistPluginsOutput serializes the PersistedRun's PluginsOutput and writes
197- // it to the database via the given store.
198- func PersistPluginsOutput (run * PersistedRun , store RunPluginOutputStore ) error {
199- lt , err := SerializePluginsOutput (run .PluginsOutput )
200- if err != nil {
201- return fmt .Errorf ("failed to serialize plugins_output for run %q: %w" , run .RunID , err )
202- }
203- return store .UpdateRunPluginsOutput (run .RunID , lt )
204- }
205-
206- func SerializePluginsOutput (outputs map [string ]* apiv2beta1.PluginOutput ) (* model.LargeText , error ) {
207- if len (outputs ) == 0 {
208- return nil , nil
209- }
210- var envelope PluginsOutputEnvelope
211- for key , output := range outputs {
212- marshaledOutput , err := protojson .Marshal (output )
213- if err != nil {
214- return nil , fmt .Errorf ("failed to marshal plugin output for %q: %w" , key , err )
215- }
216- envelope .set (key , marshaledOutput )
217- }
218- marshaledMap , err := json .Marshal (envelope )
219- if err != nil {
220- return nil , fmt .Errorf ("failed to marshal plugins_output map: %w" , err )
221- }
222- lt := model .LargeText (string (marshaledMap ))
223- return & lt , nil
224- }
225-
226- // ResolvePluginRequestConfig builds the effective PluginConfig for a single
227- // request by merging namespace-level overrides on top of the global config.
228- // Returns (nil, nil) when neither source provides configuration, signaling
229- // that the plugin is unconfigured rather than broken.
230- func ResolvePluginRequestConfig (ctx context.Context , clientSet kubernetes.Interface , handler RunPluginHandler , namespace string ) (* PluginConfig , error ) {
231- if handler == nil {
232- return nil , fmt .Errorf ("handler is nil" )
233- }
234-
235- globalCfg , err := handler .GetGlobalPluginConfig ()
236- if err != nil {
237- return nil , err
238- }
239-
240- namespaceCfg , err := GetNamespacePluginConfig (ctx , clientSet , handler .Name (), namespace )
241- if err != nil {
242- return nil , err
243- }
244-
245- merged , err := MergePluginConfig (namespaceCfg , globalCfg )
246- if err != nil {
247- return nil , err
248- }
249- if merged == nil {
250- return nil , nil
251- }
252- if merged .Timeout == "" {
253- merged .Timeout = DefaultTimeout
254- }
255- return merged , nil
84+ return cfgMap , nil
25685}
25786
25887// MergePluginConfig merges namespace-level overrides into the global config.
@@ -298,47 +127,6 @@ func mergeSettings(ns, global map[string]interface{}) map[string]interface{} {
298127 return merged
299128}
300129
301- // ModelToPersistedRun converts a model.Run to a PersistedRun for the
302- // post-run plugin hooks (OnRunEnd, OnRunRetry).
303- func ModelToPersistedRun (m * model.Run , namespace string ) (* PersistedRun , error ) {
304- if m == nil {
305- return nil , fmt .Errorf ("model.Run is nil" )
306- }
307- pluginsOutput , err := DeserializePluginsOutput (m .PluginsOutputString )
308- if err != nil {
309- return nil , fmt .Errorf ("failed to deserialize plugins_output for run %q: %w" , m .UUID , err )
310- }
311- pr := & PersistedRun {
312- RunID : m .UUID ,
313- Namespace : namespace ,
314- State : string (m .RunDetails .State ), //nolint:staticcheck // QF1008
315- PluginsOutput : pluginsOutput ,
316- }
317- if m .RunDetails .FinishedAtInSec > 0 { //nolint:staticcheck // QF1008
318- t := time .Unix (m .RunDetails .FinishedAtInSec , 0 ) //nolint:staticcheck // QF1008
319- pr .FinishedAt = & t
320- }
321- return pr , nil
322- }
323-
324- func DeserializePluginsOutput (raw * model.LargeText ) (map [string ]* apiv2beta1.PluginOutput , error ) {
325- result := make (map [string ]* apiv2beta1.PluginOutput )
326- if raw == nil || * raw == "" {
327- return result , nil
328- }
329- var envelope PluginsOutputEnvelope
330- if err := json .Unmarshal ([]byte (* raw ), & envelope ); err != nil {
331- return nil , fmt .Errorf ("failed to unmarshal plugins_output: %w" , err )
332- }
333- envelope .forEachEntry (func (name string , payload json.RawMessage ) {
334- output := & apiv2beta1.PluginOutput {}
335- if err := protojson .Unmarshal (payload , output ); err == nil {
336- result [name ] = output
337- }
338- })
339- return result , nil
340- }
341-
342130func SetPluginOutputState (output * apiv2beta1.PluginOutput , state apiv2beta1.PluginState , stateMessage string ) {
343131 if output == nil {
344132 return
0 commit comments