initial push
This commit is contained in:
Родитель
a4e4151283
Коммит
5cddb813c0
|
@ -0,0 +1,13 @@
|
|||
name: Integration Test
|
||||
on: [push]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check Out Repository
|
||||
id: checkout_repository
|
||||
uses: actions/checkout@master
|
||||
|
||||
- name: Self test
|
||||
id: selftest
|
||||
uses: azure/aml-deploy@master
|
|
@ -0,0 +1,23 @@
|
|||
name: Lint
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Python 3.7
|
||||
id: python_setup
|
||||
uses: actions/setup-python@v1
|
||||
with:
|
||||
python-version: "3.7"
|
||||
|
||||
- name: Check Out Repository
|
||||
id: checkout_repository
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Lint
|
||||
id: python_linting
|
||||
run: |
|
||||
pip install flake8
|
||||
flake8 --ignore E501 code/main.py
|
||||
flake8 --ignore E501 code/utils.py
|
|
@ -0,0 +1,5 @@
|
|||
################################################################################
|
||||
# This .gitignore file was automatically created by Microsoft(R) Visual Studio.
|
||||
################################################################################
|
||||
|
||||
/.vs
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "<your-webservice-name>",
|
||||
"service_type": "aci",
|
||||
"authentication_enabled": true
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "<your-webservice-name>",
|
||||
"deployment_target": "<your-deployment-target-name>",
|
||||
"inference_source_directory": "<your-source-directory>",
|
||||
"inference_entry_script": "<your-entry-script>",
|
||||
"conda_file": "<your-conda-environment-file-path>",
|
||||
"extra_docker_file_steps": "<your-extra-docker-steps-file-path>",
|
||||
"enable_gpu": false,
|
||||
"cuda_version": "<your-cuda-version>",
|
||||
"runtime": "<'python' or 'spark-py'>",
|
||||
"custom_base_image": "<your-custom-docker-base-image>",
|
||||
"custom_container_registry_address": "<your-custom-container-registry-address>",
|
||||
"tags": {"<your-webservice-tag-key>": "<your-webservice-tag-value>"},
|
||||
"properties": {"<your-webservice-property-key>": "<your-webservice-property-value>"},
|
||||
"description": "<your-webservice-description>"
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
|
||||
FROM marvinbuss/aml-docker:latest
|
||||
|
||||
LABEL maintainer="azure/gh_aml"
|
||||
|
||||
COPY /code /code
|
||||
ENTRYPOINT ["/code/entrypoint.sh"]
|
102
README.md
102
README.md
|
@ -1,3 +1,105 @@
|
|||
# Azure Machine Learning Deploy Action
|
||||
|
||||
https://docs.microsoft.com/en-us/azure/templates/Microsoft.ContainerService/2020-02-01/managedClusters?toc=%2Fen-us%2Fazure%2Fazure-resource-manager%2Ftoc.json&bc=%2Fen-us%2Fazure%2Fbread%2Ftoc.json#managedclusteragentpoolprofile-object
|
||||
|
||||
## Usage
|
||||
|
||||
Description.
|
||||
|
||||
### Example workflow
|
||||
|
||||
```yaml
|
||||
name: My Workflow
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Run action
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Run action
|
||||
|
||||
# Put your action repo here
|
||||
uses: me/myaction@master
|
||||
|
||||
# Put an example of your mandatory inputs here
|
||||
with:
|
||||
myInput: world
|
||||
```
|
||||
|
||||
### Inputs
|
||||
|
||||
| Input | Description |
|
||||
|------------------------------------------------------|-----------------------------------------------|
|
||||
| `myInput` | An example mandatory input |
|
||||
| `anotherInput` _(optional)_ | An example optional input |
|
||||
|
||||
#### Parameter File
|
||||
|
||||
A sample file can be found in this repository in the folder `.aml`. The action expects a similar parameter file in your repository in the `.aml folder`.
|
||||
|
||||
| Parameter Name | Required | Allowed Values | Description |
|
||||
| ------------------- | -------- | ------------------------------------ | ----------- |
|
||||
| createWorkspace | x | bool: true, false | Create Workspace if it could not be loaded |
|
||||
| name | x | str | For more details please read [here](https://docs.microsoft.com/en-us/python/api/azureml-core/azureml.core.workspace.workspace?view=azure-ml-py#create-name--auth-none--subscription-id-none--resource-group-none--location-none--create-resource-group-true--sku--basic---friendly-name-none--storage-account-none--key-vault-none--app-insights-none--container-registry-none--cmk-keyvault-none--resource-cmk-uri-none--hbi-workspace-false--default-cpu-compute-target-none--default-gpu-compute-target-none--exist-ok-false--show-output-true-) |
|
||||
| friendlyName | | str |
|
||||
| createResourceGroup | | bool: true, false |
|
||||
| location | | str: [supported region](https://azure.microsoft.com/global-infrastructure/services/?products=machine-learning-service) |
|
||||
| sku | | str: "basic", "enterprise" |
|
||||
| storageAccount | | str: Azure resource ID format |
|
||||
| keyVault | | str: Azure resource ID format |
|
||||
| appInsights | | str: Azure resource ID format |
|
||||
| containerRegistry | | str: Azure resource ID format |
|
||||
| cmkKeyVault | | str: Azure resource ID format |
|
||||
| resourceCmkUri | | str: URI of the customer managed key |
|
||||
| hbiWorkspace | | bool: true, false |
|
||||
|
||||
|
||||
### Outputs
|
||||
|
||||
| Output | Description |
|
||||
|------------------------------------------------------|-----------------------------------------------|
|
||||
| `myOutput` | An example output (returns 'Hello world') |
|
||||
|
||||
## Examples
|
||||
|
||||
|
||||
|
||||
### Using the optional input
|
||||
|
||||
This is how to use the optional input.
|
||||
|
||||
```yaml
|
||||
with:
|
||||
myInput: world
|
||||
anotherInput: optional
|
||||
```
|
||||
|
||||
### Using outputs
|
||||
|
||||
Show people how to use your outputs in another action.
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- name: Run action
|
||||
id: myaction
|
||||
|
||||
# Put your action name here
|
||||
uses: me/myaction@master
|
||||
|
||||
# Put an example of your mandatory arguments here
|
||||
with:
|
||||
myInput: world
|
||||
|
||||
# Put an example of using your outputs here
|
||||
- name: Check outputs
|
||||
run: |
|
||||
echo "Outputs - ${{ steps.myaction.outputs.myOutput }}"
|
||||
```
|
||||
|
||||
# Contributing
|
||||
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
name: "Azure Machine Learning Deploy Action"
|
||||
description: "Deploy a registered model in your Azure Machine Learning Workspace with this GitHub Action"
|
||||
author: "azure/gh-aml"
|
||||
inputs:
|
||||
azureCredentials:
|
||||
description: "Paste output of `az ad sp create-for-rbac --name <your-sp-name> --role contributor --scopes /subscriptions/<your-subscriptionId>/resourceGroups/<your-rg> --sdk-auth` as value of secret variable: AZURE_CREDENTIALS"
|
||||
required: true
|
||||
parametersFile:
|
||||
description: "JSON file including the parameters for deployment."
|
||||
required: true
|
||||
default: "deploy.json"
|
||||
branding:
|
||||
icon: "chevron-up"
|
||||
color: "blue"
|
||||
runs:
|
||||
using: "docker"
|
||||
image: "Dockerfile"
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
ls -la
|
||||
python /code/main.py
|
|
@ -0,0 +1,180 @@
|
|||
import os
|
||||
import json
|
||||
|
||||
from azureml.core import Workspace, Model, ContainerRegistry
|
||||
from azureml.core.compute import ComputeTarget, AksCompute
|
||||
from azureml.core.model import InferenceConfig
|
||||
from azureml.core.webservice import AksWebservice, AciWebservice
|
||||
from azureml.exceptions import ComputeTargetException, AuthenticationException, ProjectSystemException
|
||||
from azureml.core.authentication import ServicePrincipalAuthentication
|
||||
from adal.adal_error import AdalError
|
||||
from msrest.exceptions import AuthenticationError
|
||||
from json import JSONDecodeError
|
||||
from utils import AMLConfigurationException, required_parameters_provided
|
||||
|
||||
|
||||
def main():
|
||||
# Loading input values
|
||||
print("::debug::Loading input values")
|
||||
parameters_file = os.environ.get("INPUT_PARAMETERSFILE", default="compute.json")
|
||||
azure_credentials = os.environ.get("INPUT_AZURECREDENTIALS", default="{}")
|
||||
model_name = os.environ.get("INPUT_MODELNAME", default=None)
|
||||
model_version = os.environ.get("INPUT_MODELVERSION", default=None)
|
||||
try:
|
||||
azure_credentials = json.loads(azure_credentials)
|
||||
except JSONDecodeError:
|
||||
print("::error::Please paste output of `az ad sp create-for-rbac --name <your-sp-name> --role contributor --scopes /subscriptions/<your-subscriptionId>/resourceGroups/<your-rg> --sdk-auth` as value of secret variable: AZURE_CREDENTIALS")
|
||||
raise AMLConfigurationException(f"Incorrect or poorly formed output from azure credentials saved in AZURE_CREDENTIALS secret. See setup in https://github.com/Azure/aml-compute/blob/master/README.md")
|
||||
|
||||
# Checking provided parameters
|
||||
print("::debug::Checking provided parameters")
|
||||
required_parameters_provided(
|
||||
parameters=azure_credentials,
|
||||
keys=["tenantId", "clientId", "clientSecret"],
|
||||
message="Required parameter(s) not found in your azure credentials saved in AZURE_CREDENTIALS secret for logging in to the workspace. Please provide a value for the following key(s): "
|
||||
)
|
||||
|
||||
# Loading parameters file
|
||||
print("::debug::Loading parameters file")
|
||||
parameters_file_path = os.path.join(".ml", ".azure", parameters_file)
|
||||
try:
|
||||
with open(parameters_file_path) as f:
|
||||
parameters = json.load(f)
|
||||
except FileNotFoundError:
|
||||
print(f"::error::Could not find parameter file in {parameters_file_path}. Please provide a parameter file in your repository (e.g. .ml/.azure/workspace.json).")
|
||||
raise AMLConfigurationException(f"Could not find parameter file in {parameters_file_path}. Please provide a parameter file in your repository (e.g. .ml/.azure/workspace.json).")
|
||||
|
||||
# Loading Workspace
|
||||
print("::debug::Loading AML Workspace")
|
||||
sp_auth = ServicePrincipalAuthentication(
|
||||
tenant_id=azure_credentials.get("tenantId", ""),
|
||||
service_principal_id=azure_credentials.get("clientId", ""),
|
||||
service_principal_password=azure_credentials.get("clientSecret", "")
|
||||
)
|
||||
config_file_path = os.environ.get("GITHUB_WORKSPACE", default=".ml/.azure")
|
||||
config_file_name = "aml_arm_config.json"
|
||||
try:
|
||||
ws = Workspace.from_config(
|
||||
path=config_file_path,
|
||||
_file_name=config_file_name,
|
||||
auth=sp_auth
|
||||
)
|
||||
except AuthenticationException as exception:
|
||||
print(f"::error::Could not retrieve user token. Please paste output of `az ad sp create-for-rbac --name <your-sp-name> --role contributor --scopes /subscriptions/<your-subscriptionId>/resourceGroups/<your-rg> --sdk-auth` as value of secret variable: AZURE_CREDENTIALS: {exception}")
|
||||
raise AuthenticationException
|
||||
except AuthenticationError as exception:
|
||||
print(f"::error::Microsoft REST Authentication Error: {exception}")
|
||||
raise AuthenticationError
|
||||
except AdalError as exception:
|
||||
print(f"::error::Active Directory Authentication Library Error: {exception}")
|
||||
raise AdalError
|
||||
except ProjectSystemException as exception:
|
||||
print(f"::error::Workspace authorizationfailed: {exception}")
|
||||
raise ProjectSystemException
|
||||
|
||||
# Loading deployment target
|
||||
print("::debug::Loading deployment target")
|
||||
try:
|
||||
deployment_target = ComputeTarget(
|
||||
workspace=ws,
|
||||
name=parameters.get("deployment_target", "")
|
||||
)
|
||||
except ComputeTargetException:
|
||||
deployment_target = None
|
||||
|
||||
# Creating inference config
|
||||
print("::debug::Creating inference config")
|
||||
if parameters.get("custom_container_registry_address", None) is not None:
|
||||
container_registry = ContainerRegistry()
|
||||
container_registry.address = parameters.get("custom_container_registry_address", None)
|
||||
container_registry.username = os.environ.get("custom_container_registry_username", None)
|
||||
container_registry.password = os.environ.get("custom_container_registry_password", None)
|
||||
else:
|
||||
container_registry = None
|
||||
|
||||
inference_config = InferenceConfig(
|
||||
entry_script=parameters.get("inference_entry_script", None),
|
||||
runtime=parameters.get("runtime", None),
|
||||
conda_file=parameters.get("conda_file", None),
|
||||
extra_docker_file_steps=parameters.get("extra_docker_file_steps", None),
|
||||
source_directory=parameters.get("inference_source_directory", None),
|
||||
enable_gpu=parameters.get("enable_gpu", None),
|
||||
description=parameters.get("description", None),
|
||||
base_image=parameters.get("base_image", None),
|
||||
base_image_registry=container_registry,
|
||||
cuda_version=parameters.get("cuda_version", None)
|
||||
)
|
||||
|
||||
# Creating deployment config
|
||||
print("::debug::Creating deployment config")
|
||||
if type(deployment_target) is AksCompute:
|
||||
deployment_config = AksWebservice.deploy_configuration(
|
||||
autoscale_enabled="",
|
||||
autoscale_min_replicas="",
|
||||
autoscale_max_replicas="",
|
||||
autoscale_refresh_seconds="",
|
||||
autoscale_target_utilization="",
|
||||
collect_model_data="",
|
||||
auth_enabled="",
|
||||
cpu_cores="",
|
||||
memory_gb="",
|
||||
enable_app_insights="",
|
||||
scoring_timeout_ms="",
|
||||
replica_max_concurrent_requests="",
|
||||
max_request_wait_time="",
|
||||
num_replicas="",
|
||||
primary_key="",
|
||||
secondary_key="",
|
||||
tags=parameters.get("tags", None),
|
||||
properties=parameters.get("properties", None),
|
||||
description=parameters.get("description", None),
|
||||
gpu_cores="",
|
||||
period_seconds="",
|
||||
initial_delay_seconds="",
|
||||
timeout_seconds="",
|
||||
success_threshold="",
|
||||
failure_threshold="",
|
||||
namespace="",
|
||||
token_auth_enabled=""
|
||||
)
|
||||
|
||||
else:
|
||||
deployment_config = AciWebservice.deploy_configuration(
|
||||
cpu_cores="",
|
||||
memory_gb="",
|
||||
tags=parameters.get("tags", None),
|
||||
properties=parameters.get("properties", None),
|
||||
description=parameters.get("description", None),
|
||||
location="",
|
||||
auth_enabled="",
|
||||
ssl_enabled="",
|
||||
enable_app_insights="",
|
||||
ssl_cert_pem_file="",
|
||||
ssl_key_pem_file="",
|
||||
ssl_cname="",
|
||||
dns_name_label="",
|
||||
primary_key="",
|
||||
secondary_key="",
|
||||
collect_model_data="",
|
||||
cmk_vault_base_url="",
|
||||
cmk_key_name="",
|
||||
cmk_key_version=""
|
||||
)
|
||||
|
||||
try:
|
||||
service = Model.deploy(
|
||||
workspace=ws,
|
||||
name=parameters.get("name", None),
|
||||
models=[],
|
||||
inference_config=inference_config,
|
||||
deployment_config=deployment_config,
|
||||
deployment_target=deployment_target,
|
||||
overwrite=True
|
||||
)
|
||||
except expression as identifier:
|
||||
pass
|
||||
print("::debug::Successfully finished Azure Machine Learning Deploy Action")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -0,0 +1,13 @@
|
|||
class AMLConfigurationException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def required_parameters_provided(parameters, keys, message="Required parameter(s) not found in your parameters file. Please provide a value for the following key(s): "):
|
||||
missing_keys = []
|
||||
for key in keys:
|
||||
if key not in parameters:
|
||||
err_msg = f"{message} {key}"
|
||||
print(f"::error::{err_msg}")
|
||||
missing_keys.append(key)
|
||||
if len(missing_keys) > 0:
|
||||
raise AMLConfigurationException(f"{message} {missing_keys}")
|
Загрузка…
Ссылка в новой задаче