Skip to content

Commit a3e3586

Browse files
committed
Rebase final plugin arch
Signed-off-by: Alyssa Goins <agoins@redhat.com>
1 parent 974b915 commit a3e3586

25 files changed

Lines changed: 6143 additions & 1756 deletions

backend/src/apiserver/plugins/config.go

Lines changed: 15 additions & 227 deletions
Original file line numberDiff line numberDiff line change
@@ -16,81 +16,23 @@ package plugins
1616

1717
import (
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-
7731
const (
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.
9537
type 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-
342130
func SetPluginOutputState(output *apiv2beta1.PluginOutput, state apiv2beta1.PluginState, stateMessage string) {
343131
if output == nil {
344132
return

0 commit comments

Comments
 (0)