This commit is contained in:
Marvin Buss 2020-03-23 01:10:14 +01:00
Родитель a4e4151283
Коммит 5cddb813c0
12 изменённых файлов: 387 добавлений и 0 удалений

13
.github/workflows/integration.yml поставляемый Normal file
Просмотреть файл

@ -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

23
.github/workflows/python.yml поставляемый Normal file
Просмотреть файл

@ -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

5
.gitignore поставляемый Normal file
Просмотреть файл

@ -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
}

Просмотреть файл

16
.ml/.azure/deploy.json Normal file
Просмотреть файл

@ -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>"
}

7
Dockerfile Normal file
Просмотреть файл

@ -0,0 +1,7 @@
FROM marvinbuss/aml-docker:latest
LABEL maintainer="azure/gh_aml"
COPY /code /code
ENTRYPOINT ["/code/entrypoint.sh"]

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

17
action.yml Normal file
Просмотреть файл

@ -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"

6
code/entrypoint.sh Normal file
Просмотреть файл

@ -0,0 +1,6 @@
#!/bin/sh
set -e
ls -la
python /code/main.py

180
code/main.py Normal file
Просмотреть файл

@ -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()

13
code/utils.py Normal file
Просмотреть файл

@ -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}")