Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ImageConfig should contain the image from sandbox.config #1106

Merged
merged 4 commits into from
Jul 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions flytekit/clis/sdk_in_container/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import os
import pathlib
import typing
from dataclasses import dataclass
from dataclasses import dataclass, replace
from typing import cast

import click
Expand All @@ -15,7 +15,7 @@
from typing_extensions import get_args

from flytekit import BlobType, Literal, Scalar
from flytekit.clis.sdk_in_container.constants import CTX_DOMAIN, CTX_PROJECT
from flytekit.clis.sdk_in_container.constants import CTX_CONFIG_FILE, CTX_DOMAIN, CTX_PROJECT
from flytekit.clis.sdk_in_container.helpers import FLYTE_REMOTE_INSTANCE_KEY, get_and_save_remote_with_click_context
from flytekit.configuration import ImageConfig
from flytekit.configuration.default_images import DefaultImages
Expand Down Expand Up @@ -512,12 +512,35 @@ def _run(*args, **kwargs):
return

remote = ctx.obj[FLYTE_REMOTE_INSTANCE_KEY]
config_file = ctx.obj.get(CTX_CONFIG_FILE)
Copy link
Collaborator

@eapolinario eapolinario Jul 27, 2022

Choose a reason for hiding this comment

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

just for own understanding, what's the level that config is applied? As in, can you pass the --config flag to the run subcommand (e.g. pyflyte run --config <file.yaml> --remote ...)? Or it has to come before the run subcommand?

Copy link
Member Author

Choose a reason for hiding this comment

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

We still have to use --config before the run. like pyflyte --config flyte.yaml run ....


# Images come from three places:
# * The default flytekit images, which are already supplied by the base run_level_params.
# * The images provided by the user on the command line.
# * The images provided by the user via the config file, if there is one. (Images on the command line should
# override all).
#
# However, the run_level_params already contains both the default flytekit images (lowest priority), as well
# as the images from the command line (highest priority). So when we read from the config file, we only
# want to add in the images that are missing, including the default, if that's also missing.
image_config_from_parent_cmd = run_level_params.get("image_config", None)
additional_image_names = set([v.name for v in image_config_from_parent_cmd.images])
new_additional_images = [v for v in image_config_from_parent_cmd.images]
new_default = image_config_from_parent_cmd.default_image
if config_file:
cfg_ic = ImageConfig.auto(config_file=config_file)
new_default = new_default or cfg_ic.default_image
for addl in cfg_ic.images:
if addl.name not in additional_image_names:
new_additional_images.append(addl)

image_config = replace(image_config_from_parent_cmd, default_image=new_default, images=new_additional_images)

remote_entity = remote.register_script(
entity,
project=project,
domain=domain,
image_config=run_level_params.get("image_config", None),
image_config=image_config,
destination_dir=run_level_params.get("destination_dir"),
)

Expand Down
22 changes: 11 additions & 11 deletions flytekit/configuration/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,25 +230,25 @@ def create_from(
return ImageConfig(default_image=default_image, images=all_images)

@classmethod
def auto(cls, config_file: typing.Union[str, ConfigFile] = None, img_name: Optional[str] = None) -> ImageConfig:
def auto(
cls, config_file: typing.Union[str, ConfigFile, None] = None, img_name: Optional[str] = None
) -> ImageConfig:
"""
Reads from config file or from img_name
Note that this function does not take into account the flytekit default images (see the Dockerfiles at the
base of this repo). To pick those up, see the auto_default_image function..

:param config_file:
:param img_name:
:return:
"""
if config_file is None and img_name is None:
raise ValueError("Either an image or a config with a default image should be provided")
wild-endeavor marked this conversation as resolved.
Show resolved Hide resolved

default_img = Image.look_up_image_info("default", img_name) if img_name else None
wild-endeavor marked this conversation as resolved.
Show resolved Hide resolved

other_images = []
if config_file:
config_file = get_config_file(config_file)
other_images = [
Image.look_up_image_info(k, tag=v, optional_tag=True)
for k, v in _internal.Images.get_specified_images(config_file).items()
]
config_file = get_config_file(config_file)
other_images = [
Image.look_up_image_info(k, tag=v, optional_tag=True)
for k, v in _internal.Images.get_specified_images(config_file).items()
]
return cls.create_from(default_img, other_images)

@classmethod
Expand Down
24 changes: 14 additions & 10 deletions flytekit/configuration/internal.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

class Images(object):
@staticmethod
def get_specified_images(cfg: ConfigFile) -> typing.Dict[str, str]:
def get_specified_images(cfg: typing.Optional[ConfigFile]) -> typing.Dict[str, str]:
"""
This section should contain options, where the option name is the friendly name of the image and the corresponding
value is actual FQN of the image. Example of how the section is structured
Expand All @@ -19,16 +19,20 @@ def get_specified_images(cfg: ConfigFile) -> typing.Dict[str, str]:
:returns a dictionary of name: image<fqn+version> Version is optional
"""
images: typing.Dict[str, str] = {}
if cfg is None or not cfg.legacy_config:
if cfg is None:
return images
try:
image_names = cfg.legacy_config.options("images")
except configparser.NoSectionError:
image_names = None
if image_names:
for i in image_names:
images[str(i)] = cfg.legacy_config.get("images", i)
return images

if cfg.legacy_config:
try:
image_names = cfg.legacy_config.options("images")
except configparser.NoSectionError:
return {}
if image_names:
for i in image_names:
images[str(i)] = cfg.legacy_config.get("images", i)
return images
if cfg.yaml_config:
return cfg.yaml_config.get("images", images)


class Deck(object):
Expand Down
13 changes: 13 additions & 0 deletions tests/flytekit/unit/configuration/configs/no_images.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
admin:
# For GRPC endpoints you might want to use dns:///flyte.myexample.com
endpoint: dns:///flyte.mycorp.io
authType: Pkce
insecure: true
clientId: propeller
scopes:
- all
storage:
connection:
access-key: minio
endpoint: http://localhost:30084
secret-key: miniostorage
3 changes: 3 additions & 0 deletions tests/flytekit/unit/configuration/configs/sample.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ storage:
access-key: minio
endpoint: http://localhost:30084
secret-key: miniostorage
images:
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this is supported at all on the flytectl side (and might never be since it's mostly responsible for registration time settings) but that's ok. We can still support this.

xyz: docker.io/xyz:latest
abc: docker.io/abc
8 changes: 8 additions & 0 deletions tests/flytekit/unit/configuration/test_image_config.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
import sys

import mock
Expand Down Expand Up @@ -38,6 +39,13 @@ def test_image_config_auto():
assert x.images[0].full == f"ghcr.io/flyteorg/flytekit:py{version_str}-latest"


def test_image_from_flytectl_config():
os.environ["FLYTECTL_CONFIG"] = os.path.join(os.path.dirname(os.path.realpath(__file__)), "configs/sample.yaml")
image_config = ImageConfig.auto(config_file=None)
assert image_config.images[0].full == "docker.io/xyz:latest"
assert image_config.images[1].full == "docker.io/abc:None"


@mock.patch("flytekit.configuration.default_images.sys")
def test_not_version(mock_sys):
mock_sys.version_info.major.return_value = 2
Expand Down
4 changes: 4 additions & 0 deletions tests/flytekit/unit/configuration/test_internal.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ def test_load_images():
imgs = Images.get_specified_images(cfg)
assert imgs == {"abc": "docker.io/abc", "xyz": "docker.io/xyz:latest"}

cfg = get_config_file(os.path.join(os.path.dirname(os.path.realpath(__file__)), "configs/sample.yaml"))
imgs = Images.get_specified_images(cfg)
assert imgs == {"abc": "docker.io/abc", "xyz": "docker.io/xyz:latest"}


def test_no_images():
cfg = get_config_file(os.path.join(os.path.dirname(os.path.realpath(__file__)), "configs/good.config"))
Expand Down
10 changes: 9 additions & 1 deletion tests/flytekit/unit/configuration/test_yaml_file.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from flytekit.configuration import ConfigEntry, get_config_file
from flytekit.configuration.file import LegacyConfigEntry, YamlConfigEntry
from flytekit.configuration.internal import AWS, Credentials, Platform
from flytekit.configuration.internal import AWS, Credentials, Images, Platform


def test_config_entry_file():
Expand All @@ -20,6 +20,14 @@ def test_config_entry_file():
assert c.read(cfg) is None


def test_config_entry_file_normal():
# Most yaml config files will not have images, make sure that a normal one without an image section doesn't
# return None
cfg = get_config_file(os.path.join(os.path.dirname(os.path.realpath(__file__)), "configs/no_images.yaml"))
images_dict = Images.get_specified_images(cfg)
assert images_dict == {}


@mock.patch("flytekit.configuration.file.getenv")
def test_config_entry_file_2(mock_get):
# Test reading of the environment variable that flytectl asks users to set.
Expand Down