Skip to content
4 changes: 2 additions & 2 deletions kale/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
from kale import __version__ as KALE_VERSION
from kale.common import graphutils, kfputils, utils
from kale.common.imports import get_packages_to_install
from kale.pipeline import DEFAULT_BASE_IMAGE, Pipeline, PipelineParam, Step
from kale.pipeline import Pipeline, PipelineParam, Step

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -204,7 +204,7 @@ def _encode_source(s):
step_inputs=step_inputs,
step_outputs=step_outputs,
kfp_dsl_artifact_imports=KFP_DSL_ARTIFACT_IMPORTS,
default_base_image=DEFAULT_BASE_IMAGE,
default_base_image=self.pipeline.config.base_image,
**self.pipeline.config.to_dict(),
)
return autopep8.fix_code(fn_code)
Expand Down
24 changes: 12 additions & 12 deletions kale/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
import logging
import os

from kubernetes.client.rest import ApiException
from kubernetes.config import ConfigException
import networkx as nx

from kale.common import graphutils, podutils, utils
Expand Down Expand Up @@ -134,16 +132,18 @@ def _randomize_pipeline_name(self):
self.pipeline_name = f"{self.pipeline_name}-{utils.random_string()}"

def _set_base_image(self):
if not self.base_image:
try:
self.base_image = podutils.get_docker_base_image()
except (ConfigException, RuntimeError, FileNotFoundError, ApiException):
# * ConfigException: no K8s config found
# * RuntimeError, FileNotFoundError: this is not running in a
# pod
# * ApiException: K8s call to read pod raised exception;
# Use kfp default image
self.base_image = DEFAULT_BASE_IMAGE
# Priority 1: Settings (already in self.base_image)
if self.base_image:
return

# Priority 2: Environment variable
env_image = os.getenv("DEFAULT_BASE_IMAGE")
if env_image:
self.base_image = env_image
return

# Priority 3: Default
self.base_image = DEFAULT_BASE_IMAGE

def _set_volume_storage_class(self):
if not self.storage_class_name:
Expand Down
2 changes: 1 addition & 1 deletion kale/processors/nbprocessor.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ def parse_notebook(self):
limits=tags.get("limits", {}),
labels=tags.get("labels", {}),
annotations=tags.get("annotations", {}),
base_image=tags.get("base_image", ""),
base_image=tags.get("base_image") or self.pipeline.config.base_image,
enable_caching=tags.get("enable_caching"),
)
self.pipeline.add_step(step)
Expand Down
13 changes: 13 additions & 0 deletions labextension/schema/deploymentPanel.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"title": "Kale Deployment Panel Settings",
"description": "Settings for default base image configuration",
"type": "object",
"properties": {
"defaultBaseImage": {
"type": "string",
"title": "Default Base Image",
"description": "Default Docker image used for pipeline steps",
"default": "python:3.12"
}
}
}
113 changes: 41 additions & 72 deletions labextension/src/widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
// 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.

import {
JupyterFrontEnd,
JupyterFrontEndPlugin,
Expand Down Expand Up @@ -55,10 +54,6 @@ export const KALE_PANEL_ID = 'jupyterlab-kubeflow-kale/kubeflowDeployment';

const id = 'jupyterlab-kubeflow-kale:deploymentPanel';

const KALE_SETTINGS_PLUGIN_ID = 'jupyterlab-kubeflow-kale:kale-settings';
const ENABLE_KALE_BY_DEFAULT_KEY = 'enableKaleByDefault';
const AUTO_SAVE_ON_COMPILE_OR_RUN_KEY = 'autoSaveOnCompileOrRun';

const kaleIcon = new LabIcon({ name: 'kale:logo', svgstr: kaleIconSvg });
let kalePanelWidget: ReactWidget | undefined;

Expand Down Expand Up @@ -109,6 +104,31 @@ async function activate(
// TODO: backend can become an Enum that indicates the type of
// env we are in (like Local Laptop, MiniKF, GCP, UI without Kale, ...)
const backend = await getBackend(kernel);
const settings = await settingRegistry.load(
'jupyterlab-kubeflow-kale:deploymentPanel',
);

let defaultBaseImage =
(settings.get('default_base_image').composite as string) || 'python:3.12';

settings.changed.connect(() => {
defaultBaseImage =
(settings.get('default_base_image').composite as string) || '';

if (kalePanelWidget) {
const newWidget = createPanel(defaultBaseImage);

newWidget.id = KALE_PANEL_ID;
newWidget.title.icon = kaleIcon;
newWidget.title.caption = 'Kubeflow Pipelines Deployment Panel';
newWidget.node.classList.add('kale-panel');

labShell.add(newWidget, 'left');

kalePanelWidget = newWidget;
}
});

if (backend) {
try {
await executeRpc(kernel, 'log.setup_logging');
Expand All @@ -118,71 +138,6 @@ async function activate(
}
}

// Load and react to Kale JupyterLab settings
const SettingsAwareLeftPanel = () => {
const [kaleSettings, setKaleSettings] = React.useState({
enableKaleByDefault: false,
autoSaveOnCompileOrRun: false,
});

React.useEffect(() => {
let disposed = false;
let setting: any | null = null;
let onSettingChanged: (() => void) | null = null;

settingRegistry
.load(KALE_SETTINGS_PLUGIN_ID)
.then(loadedSetting => {
setting = loadedSetting;

const read = () => ({
enableKaleByDefault:
(loadedSetting.get(ENABLE_KALE_BY_DEFAULT_KEY).composite as
| boolean
| undefined) ?? false,
autoSaveOnCompileOrRun:
(loadedSetting.get(AUTO_SAVE_ON_COMPILE_OR_RUN_KEY).composite as
| boolean
| undefined) ?? false,
});

const update = () => {
if (disposed) {
return;
}
setKaleSettings(read());
};

update();
onSettingChanged = () => update();
(loadedSetting.changed as any).connect(onSettingChanged);
})
.catch(reason => {
console.error('Failed to load Kale settings:', reason);
});

return () => {
disposed = true;
if (setting && onSettingChanged) {
(setting.changed as any).disconnect(onSettingChanged);
}
};
}, []);

return (
<KubeflowKaleLeftPanel
ref={ref => setLeftPanelRef(ref)}
lab={lab}
tracker={tracker}
docManager={docManager}
backend={backend}
kernel={kernel}
enableKaleByDefault={kaleSettings.enableKaleByDefault}
autoSaveOnCompileOrRun={kaleSettings.autoSaveOnCompileOrRun}
/>
);
};

async function loadPanel() {
let reveal_widget = undefined;
if (backend) {
Expand All @@ -208,12 +163,26 @@ async function activate(
kalePanelWidget.activate();
}
}

function createPanel(defaultBaseImage: string) {
return ReactWidget.create(
<KubeflowKaleLeftPanel
ref={ref => setLeftPanelRef(ref)}
lab={lab}
tracker={tracker}
docManager={docManager}
backend={backend}
kernel={kernel}
enableKaleByDefault={false}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will ignore the JupyterLab settings for enableKalebyDefault and auto-save when you hardcode the false value

autoSaveOnCompileOrRun={false}
defaultBaseImage={defaultBaseImage}
/>,
);
}
// Creates the left side bar widget once the app has fully started
lab.started.then(() => {
// show list of commands in the commandRegistry
// console.log(lab.commands.listCommands());
kalePanelWidget = ReactWidget.create(<SettingsAwareLeftPanel />);
kalePanelWidget = createPanel(defaultBaseImage);
kalePanelWidget.id = KALE_PANEL_ID;
kalePanelWidget!.title.icon = kaleIcon;
kalePanelWidget!.title.caption = 'Kubeflow Pipelines Deployment Panel';
Expand Down
11 changes: 11 additions & 0 deletions labextension/src/widgets/LeftPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ interface IProps {
kernel: Kernel.IKernelConnection;
enableKaleByDefault: boolean;
autoSaveOnCompileOrRun: boolean;
defaultBaseImage?: string;
}

interface IState {
Expand Down Expand Up @@ -789,6 +790,16 @@ export class KubeflowKaleLeftPanel extends React.Component<IProps, IState> {
{pipeline_desc_input}
{enable_caching_toggle}
</div>
<Input
variant="standard"
label={'Default Base Image'}
value={this.state.defaultBaseImage}
placeholder="e.g. python:3.12"
updateValue={(v: string) => {
this.setState({ defaultBaseImage: v });
this.updateDockerImage(v);
}}
/>
</div>

<div
Expand Down
16 changes: 10 additions & 6 deletions labextension/src/widgets/cell-metadata/CellMetadataEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -666,21 +666,25 @@ export class CellMetadataEditor extends React.Component<IProps, IState> {
<DialogTitle>Base Image for Step</DialogTitle>
<DialogContent>
<p style={{ margin: '8px 0' }}>
Default:{' '}
System Default:{' '}
<strong>
{this.props.defaultBaseImage || DEFAULT_BASE_IMAGE}
</strong>
</p>

<p style={{ margin: '8px 0' }}>
Pipeline Default:{' '}
<strong>
{this.props.pipelineBaseImage ||
'Not set (uses system default)'}
</strong>
</p>
<Input
variant="outlined"
label="Custom Base Image"
value={this.props.baseImage || ''}
updateValue={(v: string) => this.updateBaseImage(v)}
placeholder={
this.props.pipelineBaseImage ||
this.props.defaultBaseImage ||
DEFAULT_BASE_IMAGE
}
placeholder="e.g. python:3.12"
style={{ width: '100%', marginTop: '8px' }}
/>
</DialogContent>
Expand Down
Loading