Skip to content
This repository has been archived by the owner on Oct 19, 2023. It is now read-only.

Commit

Permalink
feat: push image to hubble
Browse files Browse the repository at this point in the history
  • Loading branch information
deepankarm committed May 15, 2023
1 parent 70bc62d commit 9b41b74
Show file tree
Hide file tree
Showing 2 changed files with 206 additions and 26 deletions.
174 changes: 162 additions & 12 deletions lcserve/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,17 @@
PDF_QNA_APP_NAME,
deploy_app_on_jcloud,
get_app_status_on_jcloud,
get_gateway_uses,
get_flow_dict,
get_flow_yaml,
get_module_dir,
list_apps_on_jcloud,
load_local_df,
push_app_to_hubble,
remove_app_on_jcloud,
resolve_jcloud_config,
syncify,
update_requirements,
remove_prefix,
)
from .utils import validate_jcloud_config_callback

Expand All @@ -46,10 +47,35 @@ def serve_locally(
# TODO: add local description
f.block()

def _push_app_to_hubble(
module_dir: str,
image_name: str = None,
tag: str = None,
requirements: List[str] = None,
version: str = 'latest',
platform: str = None,
verbose: bool = False,
):
from .flow import push_app_to_hubble

gateway_id = push_app_to_hubble(
module_dir=module_dir,
image_name=image_name,
tag=tag,
requirements=requirements,
version=version,
platform=platform,
verbose=verbose,
)

return gateway_id


async def serve_on_jcloud(
module_str: str = None,
fastapi_app_str: str = None,
app_dir: str = None,
uses: str = None,
name: str = APP_NAME,
requirements: List[str] = None,
app_id: str = None,
Expand All @@ -63,18 +89,24 @@ async def serve_on_jcloud(
from .backend.playground.utils.helper import get_random_tag

module_dir, is_websocket = get_module_dir(
module_str=module_str, fastapi_app_str=fastapi_app_str
module_str=module_str,
fastapi_app_str=fastapi_app_str,
app_dir=app_dir,
)
config = resolve_jcloud_config(config, module_dir)
tag = get_random_tag()
gateway_id_wo_tag = push_app_to_hubble(
module_dir=module_dir,
requirements=requirements,
tag=tag,
version=version,
platform=platform,
verbose=verbose,
)

if uses is not None:
gateway_id = remove_prefix(uses, 'jinahub+docker://')
else:
gateway_id = _push_app_to_hubble(
module_dir=module_dir,
requirements=requirements,
tag=get_random_tag(),
version=version,
platform=platform,
verbose=verbose,
)

app_id, _ = await deploy_app_on_jcloud(
flow_dict=get_flow_dict(
module_str=module_str,
Expand All @@ -84,7 +116,7 @@ async def serve_on_jcloud(
name=name,
timeout=timeout,
app_id=app_id,
gateway_id=gateway_id_wo_tag + ':' + tag,
gateway_id=gateway_id,
is_websocket=is_websocket,
jcloud_config_path=config,
cors=cors,
Expand Down Expand Up @@ -219,6 +251,51 @@ def upload_df_to_jcloud(module: str, name: str):
"Uploaded dataframe with ID " + click.style(df_id, fg="green", bold=True)
)

_hubble_push_options = [
click.option(
'--image-name',
type=str,
required=False,
help='Name of the image to be pushed.',
),
click.option(
'--image-tag',
type=str,
required=False,
help='Tag of the image to be pushed.',
),
click.option(
'--platform',
type=str,
required=False,
help='Platform of Docker image needed for the deployment is built on.',
),
click.option(
'--requirements',
default=None,
type=str,
help='''Pass either
1) multiple requirements or,
2) a path to a requirements.txt/pyproject.toml file or,
3) a directory containing requirements.txt/pyproject.toml file.''',
multiple=True,
),
click.option(
'--version',
type=str,
default='latest',
help='Version of serving gateway to be used.',
show_default=False,
),
click.option(
'--verbose',
is_flag=True,
help='Verbose mode.',
show_default=True,
)
]


_jcloud_shared_options = [
click.option(
Expand Down Expand Up @@ -282,6 +359,12 @@ def upload_df_to_jcloud(module: str, name: str):
]


def hubble_push_options(func):
for option in reversed(_hubble_push_options):
func = option(func)
return func


def jcloud_shared_options(func):
for option in reversed(_jcloud_shared_options):
func = option(func)
Expand All @@ -295,6 +378,57 @@ def serve():
pass


@serve.command(help='Push the app image to Jina AI Cloud.')
@click.argument(
'module_str',
type=str,
required=False,
)
@click.option(
'--app',
type=str,
required=False,
help='FastAPI application to run, in the format "<module>:<attribute>"',
)
@click.option(
'--app-dir',
type=str,
required=False,
help='Base directory to be used for the FastAPI app.',
)
@hubble_push_options
@click.help_option('-h', '--help')
def push(
module_str,
app,
app_dir,
image_name,
image_tag,
platform,
requirements,
version,
verbose,
):
module_dir, _ = get_module_dir(
module_str=module_str,
fastapi_app_str=app,
app_dir=app_dir,
)
gateway_id = _push_app_to_hubble(
module_dir=module_dir,
image_name=image_name,
tag=image_tag,
platform=platform,
requirements=requirements,
version=version,
verbose=verbose,
)

click.echo(
f'Pushed to Hubble. Use {click.style(get_gateway_uses(gateway_id), fg="green")} to deploy.'
)


@serve.group(help='Deploy the app.')
@click.help_option('-h', '--help')
def deploy():
Expand Down Expand Up @@ -336,6 +470,18 @@ def local(module_str, app, port):
required=False,
help='FastAPI application to run, in the format "<module>:<attribute>"',
)
@click.option(
'--app-dir',
type=str,
required=False,
help='Base directory to be used for the FastAPI app.',
)
@click.option(
'--uses',
type=str,
default=None,
help='Pass a pre-existing image that was pushed using `push-only` option.',
)
@click.option(
'--name',
type=str,
Expand All @@ -349,6 +495,8 @@ def local(module_str, app, port):
async def jcloud(
module_str,
app,
app_dir,
uses,
name,
app_id,
requirements,
Expand All @@ -362,6 +510,8 @@ async def jcloud(
await serve_on_jcloud(
module_str=module_str,
fastapi_app_str=app,
app_dir=app_dir,
uses=uses,
name=name,
app_id=app_id,
requirements=requirements,
Expand Down
58 changes: 44 additions & 14 deletions lcserve/flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ def _any_websocket_router_in_module(module: ModuleType) -> bool:


def get_module_dir(
module_str: str = None, fastapi_app_str: str = None
module_str: str = None, fastapi_app_str: str = None, app_dir: str = None
) -> Tuple[str, bool]:
_add_to_path()

Expand All @@ -242,6 +242,10 @@ def get_module_dir(
modname=fastapi_app_str, filename=_module.__file__
)

# if app_dir is not None, return it
if app_dir is not None:
return app_dir, _is_websocket

if not _module.__file__.endswith('.py'):
print(f'Unknown file type for module {module_str}')
sys.exit(1)
Expand Down Expand Up @@ -358,16 +362,36 @@ def _handle_dependencies(reqs: Tuple[str], tmpdir: str):


def _handle_dockerfile(tmpdir: str, version: str):
# Create the Dockerfile
with open(os.path.join(tmpdir, 'Dockerfile'), 'w') as f:
dockerfile = [
f'FROM jinawolf/serving-gateway:{version}',
'COPY . /appdir/',
'RUN if [ -e /appdir/requirements.txt ]; then pip install -r /appdir/requirements.txt; fi',
'RUN if [ -e /appdir/pyproject.toml ]; then pip install poetry && cd /appdir && poetry install; fi',
'ENTRYPOINT [ "jina", "gateway", "--uses", "config.yml" ]',
]
f.write('\n\n'.join(dockerfile))
# if file `lcserve.Dockefile` exists, use it
_lcserve_dockerfile = 'lcserve.Dockerfile'
if os.path.exists(os.path.join(tmpdir, _lcserve_dockerfile)):
shutil.copyfile(
os.path.join(tmpdir, _lcserve_dockerfile),
os.path.join(tmpdir, 'Dockerfile'),
)

# read the Dockerfile and replace the version
with open(os.path.join(tmpdir, 'Dockerfile'), 'r') as f:
dockerfile = f.read()

dockerfile = dockerfile.replace(
'jinawolf/serving-gateway:${version}',
f'jinawolf/serving-gateway:{version}',
)

with open(os.path.join(tmpdir, 'Dockerfile'), 'w') as f:
f.write(dockerfile)

else:
# Create the Dockerfile
with open(os.path.join(tmpdir, 'Dockerfile'), 'w') as f:
dockerfile = [
f'FROM jinawolf/serving-gateway:{version}',
'COPY . /appdir/',
'RUN if [ -e /appdir/requirements.txt ]; then pip install -r /appdir/requirements.txt; fi',
'ENTRYPOINT [ "jina", "gateway", "--uses", "config.yml" ]',
]
f.write('\n\n'.join(dockerfile))


def _handle_config_yaml(tmpdir: str, name: str):
Expand Down Expand Up @@ -417,6 +441,7 @@ def _push_to_hubble(

def push_app_to_hubble(
module_dir: str,
image_name = None,
tag: str = 'latest',
requirements: Tuple[str] = None,
version: str = 'latest',
Expand All @@ -434,11 +459,12 @@ def push_app_to_hubble(
os.path.dirname(__file__), os.path.join(tmpdir, 'lcserve'), dirs_exist_ok=True
)

name = get_random_name()
if image_name is None:
image_name = get_random_name()
_handle_dependencies(requirements, tmpdir)
_handle_dockerfile(tmpdir, version)
_handle_config_yaml(tmpdir, name)
return _push_to_hubble(tmpdir, name, tag, platform, verbose)
_handle_config_yaml(tmpdir, image_name)
return _push_to_hubble(tmpdir, image_name, tag, platform, verbose)


@dataclass
Expand Down Expand Up @@ -856,3 +882,7 @@ def update_requirements(path: str, requirements: List[str]) -> List[str]:
requirements.extend(f.read().splitlines())

return requirements


def remove_prefix(text, prefix):
return text[len(prefix):] if text.startswith(prefix) else text

0 comments on commit 9b41b74

Please sign in to comment.