Support for multiple registries (#193)

* envvar parsing of multiple container registries

* rename value, support for pushing modules based on module.json

* string comparison code clean up

* modify envvars with better values & refactor dockercls and .env.tmp

* modified variable names, minor fixes, added envvar testing specific to container registry

* add tests for additional cr, comments to explain code, fix merge conflict

* add additional testing for mutliple registries, fix logic around given/expected env vars

* fix env load in tests

* Tell travis to use DOTENV_FILE
This commit is contained in:
Kat Ngov 2018-08-11 09:59:53 -07:00 коммит произвёл Jon Gallant
Родитель ea831ddf71
Коммит 271e28ebe1
7 изменённых файлов: 214 добавлений и 43 удалений

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

@ -9,7 +9,7 @@ DEVICE_CONNECTION_STRING=""
#
# CONTAINER REGISTRY
#
# Settings for your container registry, set CONTAINER_REGISTRY_SERVER to the following:
# Settings for your container registry, set CONTAINER_REGISTRY_SERVER to the following as the default registry:
# Local Registry: "localhost:5000" - USERNAME/PASSWORD not required.
# Azure Container Registry: "jong.azurecr.io", Also set USERNAME/PASSWORD
# Docker Hub: "jongallant" - Your Docker hub username. Enter your Docker hub username into the CONTAINER_REGISTRY_USERNAME setting. Also set the PASSWORD.
@ -18,6 +18,14 @@ CONTAINER_REGISTRY_SERVER="localhost:5000"
CONTAINER_REGISTRY_USERNAME=""
CONTAINER_REGISTRY_PASSWORD=""
# To specify additional container registries ensure the prefix is CONTAINER_REGISTRY_SERVER, CONTAINER_REGISTRY_USERNAME, CONTAINER_REGISTRY_PASSWORD
# And the token following the prefix uniquely associates the SERVER/USERNAME/PASSWORD
# Token can be any string of alphanumeric characters
# CONTAINER_REGISTRY_SERVER2=""
# CONTAINER_REGISTRY_USERNAME2=""
# CONTAINER_REGISTRY_PASSWORD2=""
#
# HOST
#

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

@ -7,4 +7,6 @@ python:
install:
- pip install -r requirements_travis.txt
script:
- pytest -m unit # & pylint iotedgedev # or py.test for Python versions 3.5 and below
- pytest -m unit # & pylint iotedgedev # or py.test for Python versions 3.5 and below
env:
- DOTENV_FILE=".env.tmp"

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

@ -0,0 +1,7 @@
class ContainerRegistry:
def __init__(self, server, username, password):
self.server = server
self.username = username
self.password = password

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

@ -28,20 +28,22 @@ class Docker:
def init_registry(self):
self.output.header("INITIALIZING CONTAINER REGISTRY")
self.output.info("REGISTRY: " + self.envvars.CONTAINER_REGISTRY_SERVER)
for registry in self.envvars.CONTAINER_REGISTRY_MAP.values():
self.output.header("INITIALIZING CONTAINER REGISTRY")
self.output.info("REGISTRY: " + registry.server)
if "localhost" in self.envvars.CONTAINER_REGISTRY_SERVER:
self.init_local_registry()
if "localhost" in registry.server:
self.init_local_registry(registry.server)
self.output.line()
def init_local_registry(self):
def init_local_registry(self, local_server):
parts = self.envvars.CONTAINER_REGISTRY_SERVER.split(":")
parts = local_server.split(":")
if len(parts) < 2:
self.output.error("You must specific a port for your local registry server. Expected: 'localhost:5000'. Found: " + self.envvars.CONTAINER_REGISTRY_SERVER)
self.output.error("You must specific a port for your local registry server. Expected: 'localhost:5000'. Found: " +
local_server)
sys.exit()
port = parts[1]
@ -66,36 +68,12 @@ class Docker:
self.output.info("Running registry container")
self.docker_client.containers.run("registry:2", detach=True, name="registry", ports=ports, restart_policy={"Name": "always"})
def login_registry(self):
try:
if "localhost" in self.envvars.CONTAINER_REGISTRY_SERVER:
client_login_status = self.docker_client.login(self.envvars.CONTAINER_REGISTRY_SERVER)
api_login_status = self.docker_api.login(self.envvars.CONTAINER_REGISTRY_SERVER)
else:
client_login_status = self.docker_client.login(registry=self.envvars.CONTAINER_REGISTRY_SERVER,
username=self.envvars.CONTAINER_REGISTRY_USERNAME,
password=self.envvars.CONTAINER_REGISTRY_PASSWORD)
api_login_status = self.docker_api.login(registry=self.envvars.CONTAINER_REGISTRY_SERVER,
username=self.envvars.CONTAINER_REGISTRY_USERNAME,
password=self.envvars.CONTAINER_REGISTRY_PASSWORD)
self.output.info("Successfully logged into container registry: " + self.envvars.CONTAINER_REGISTRY_SERVER)
except Exception as ex:
self.output.error(
"Could not login to Container Registry. 1. Make sure Docker is running locally. 2. Verify your credentials in CONTAINER_REGISTRY_ environment variables. "
"3. If you are using WSL, then please set DOCKER_HOST Environment Variable. See the Azure IoT Edge Dev readme at https://aka.ms/iotedgedev for full instructions.")
self.output.error(str(ex))
sys.exit(-1)
def setup_registry(self):
self.output.header("SETTING UP CONTAINER REGISTRY")
self.init_registry()
self.output.info("PUSHING EDGE IMAGES TO CONTAINER REGISTRY")
image_names = ["azureiotedge-agent", "azureiotedge-hub", "azureiotedge-simulated-temperature-sensor"]
default_cr = self.envvars.CONTAINER_REGISTRY_MAP['']
for image_name in image_names:
@ -103,7 +81,7 @@ class Docker:
image_name, self.envvars.RUNTIME_TAG)
container_registry_image_name = "{0}/{1}:{2}".format(
self.envvars.CONTAINER_REGISTRY_SERVER, image_name, self.envvars.RUNTIME_TAG)
default_cr.server, image_name, self.envvars.RUNTIME_TAG)
# Pull image from Microsoft Docker Hub
try:
@ -134,7 +112,7 @@ class Docker:
container_registry_image_name))
response = self.docker_client.images.push(repository=container_registry_image_name, tag=self.envvars.RUNTIME_TAG, stream=True, auth_config={
"username": self.envvars.CONTAINER_REGISTRY_USERNAME, "password": self.envvars.CONTAINER_REGISTRY_PASSWORD})
"username": default_cr.username, "password": default_cr.password})
self.process_api_response(response)
self.output.info("SUCCESSFULLY PUSHED IMAGE: '{0}'".format(
container_registry_image_name))

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

@ -10,6 +10,7 @@ from fstrings import f
from .args import Args
from .connectionstring import DeviceConnectionString, IoTHubConnectionString
from .containerregistry import ContainerRegistry
class EnvVars:
@ -120,6 +121,8 @@ class EnvVars:
self.output.error("Unable to parse DEVICE_CONNECTION_STRING Environment Variable. Please ensure that you have the right connection string set.")
self.output.error(str(ex))
sys.exit(-1)
self.get_registries()
self.RUNTIME_HOST_NAME = self.get_envvar("RUNTIME_HOST_NAME", default=".")
if self.RUNTIME_HOST_NAME == ".":
@ -134,9 +137,6 @@ class EnvVars:
self.set_envvar("RUNTIME_CONFIG_DIR", self.get_runtime_config_dir())
self.BYPASS_MODULES = self.get_envvar("BYPASS_MODULES")
self.ACTIVE_DOCKER_PLATFORMS = self.get_envvar("ACTIVE_DOCKER_PLATFORMS", altkeys=["ACTIVE_DOCKER_ARCH"])
self.CONTAINER_REGISTRY_SERVER = self.get_envvar("CONTAINER_REGISTRY_SERVER")
self.CONTAINER_REGISTRY_USERNAME = self.get_envvar("CONTAINER_REGISTRY_USERNAME")
self.CONTAINER_REGISTRY_PASSWORD = self.get_envvar("CONTAINER_REGISTRY_PASSWORD")
self.CONTAINER_TAG = self.get_envvar("CONTAINER_TAG")
self.RUNTIME_TAG = self.get_envvar("RUNTIME_TAG")
self.RUNTIME_VERBOSITY = self.get_envvar("RUNTIME_VERBOSITY")
@ -235,6 +235,35 @@ class EnvVars:
self.output.error(f("Could not update the environment variable {key} in file {dotenv_path}"))
sys.exit(-1)
def get_registries(self):
registries = {}
self.CONTAINER_REGISTRY_MAP = {}
length_container_registry_server = len('container_registry_server')
length_container_registry_username_or_password = len('container_registry_username')
length_container_registry = len('container_registry_')
# loops through .env file for key matching container_registry_server, container_registry_username, container_registry_password
for key in os.environ:
key = key.upper()
# get token for container_registry_server key
if key.startswith('CONTAINER_REGISTRY_SERVER'):
token = key[length_container_registry_server:]
# if the token doesn't already exist as an item in the dictionary, add it. if it does, add the server value
if token not in registries:
registries[token] = {'username': '', 'password': ''}
registries[token]['server'] = self.get_envvar(key, required=True)
# get token for container_registry_username or container_registry_password key and get subkey (username or password)
elif key.startswith(('CONTAINER_REGISTRY_USERNAME', 'CONTAINER_REGISTRY_PASSWORD')):
token = key[length_container_registry_username_or_password:]
subkey = key[length_container_registry:length_container_registry_username_or_password]
# if the token doesn't already exist as an item in the dictionary, add it. if it does, add the subkey(username/password) value
if token not in registries:
registries[token] = {'username': '', 'password': ''}
registries[token][subkey] = self.get_envvar(key)
# store parsed values as a dicitonary of containerregistry objects
for key, value in registries.items():
self.CONTAINER_REGISTRY_MAP[key] = ContainerRegistry(value['server'], value['username'], value['password'])
def get_runtime_home_dir(self):
if self.is_posix():
return "/var/lib/azure-iot-edge"

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

@ -138,10 +138,19 @@ class Modules:
if not no_push:
# PUSH TO CONTAINER REGISTRY
self.output.info("PUSHING DOCKER IMAGE: " + tag)
registry_key = None
for key, registry in self.envvars.CONTAINER_REGISTRY_MAP.items():
#Split the repository tag in the module.json (ex: Localhost:5000/filtermodule)
if registry.server.lower() == tag.split('/')[0].lower():
registry_key = key
break
if registry_key is None:
self.output.error("Could not find registry server with name {0}. Please make sure your envvar is set.".format(tag.split('/')[0].lower()))
self.output.info("module json reading {0}".format(tag))
response = self.dock.docker_client.images.push(repository=tag, stream=True, auth_config={
"username": self.envvars.CONTAINER_REGISTRY_USERNAME,
"password": self.envvars.CONTAINER_REGISTRY_PASSWORD})
"username": self.envvars.CONTAINER_REGISTRY_MAP[registry_key].username,
"password": self.envvars.CONTAINER_REGISTRY_MAP[registry_key].password})
self.dock.process_api_response(response)
self.output.footer("BUILD COMPLETE", suppress=no_build)
self.output.footer("PUSH COMPLETE", suppress=no_push)

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

@ -59,7 +59,6 @@ def test_set_envvar():
assert setlevel == "debug"
envvars.set_envvar("RUNTIME_LOG_LEVEL", loglevel)
def test_envvar_clean():
output = Output()
envvars = EnvVars(output)
@ -71,7 +70,6 @@ def test_envvar_clean():
if PY2:
assert isinstance(os.environ[envvar_clean_name], str)
def test_in_command_list_true_1():
output = Output()
envvars = EnvVars(output)
@ -148,3 +146,143 @@ def test_is_terse_command_empty():
output = Output()
envvars = EnvVars(output)
assert envvars.is_terse_command("")
def test_default_container_registry_server_key_exists():
output = Output()
envvars = EnvVars(output)
envvars.load()
assert "CONTAINER_REGISTRY_SERVER" in os.environ
def test_default_container_registry_server_value_exists():
output = Output()
envvars = EnvVars(output)
server = envvars.get_envvar("CONTAINER_REGISTRY_SERVER")
assert server is not None
def test_default_container_registry_username_value_exists_or_returns_empty_string():
output = Output()
envvars = EnvVars(output)
username = envvars.get_envvar("CONTAINER_REGISTRY_USERNAME")
assert username is not None
def test_default_container_registry_password_value_exists_or_returns_empty_string():
output = Output()
envvars = EnvVars(output)
password = envvars.get_envvar("CONTAINER_REGISTRY_PASSWORD")
assert password is not None
def test_container_registry_server_key_missing_sys_exit():
with pytest.raises(SystemExit):
output = Output()
envvars = EnvVars(output)
envvars.get_envvar("CONTAINER_REGISTRY_SERVERUNITTEST", required=True)
@pytest.fixture
def setup_test_env(request):
output = Output()
envvars = EnvVars(output)
envvars.set_envvar("CONTAINER_REGISTRY_SERVERUNITTEST", '')
def clean():
os.environ.pop("CONTAINER_REGISTRY_SERVERUNITTEST")
request.addfinalizer(clean)
return
def test_container_registry_server_value_missing_sys_exit(setup_test_env):
with pytest.raises(SystemExit):
output = Output()
envvars = EnvVars(output)
envvars.get_envvar("CONTAINER_REGISTRY_SERVERUNITTEST", required=True)
def test_unique_container_registry_server_tokens():
unique = set()
length_container_registry_server = len('container_registry_server')
is_unique = True
output = Output()
envvars = EnvVars(output)
envvars.load()
for key in os.environ:
key = key.lower()
if key.startswith('container_registry_server'):
token = key[length_container_registry_server:]
if token not in unique:
unique.add(token)
else:
is_unique = False
assert is_unique
def test_unique_container_registry_username_tokens():
unique = set()
length_container_registry_username = len('container_registry_username')
is_unique = True
output = Output()
envvars = EnvVars(output)
envvars.load()
for key in os.environ:
key = key.lower()
if key.startswith('container_registry_username'):
token = key[length_container_registry_username:]
if token not in unique:
unique.add(token)
else:
is_unique = False
assert is_unique
def test_unique_container_registry_password_tokens():
unique = set()
length_container_registry_password = len('container_registry_password')
is_unique = True
output = Output()
envvars = EnvVars(output)
envvars.load()
for key in os.environ:
key = key.lower()
if key.startswith('container_registry_password'):
token = key[length_container_registry_password:]
if token not in unique:
unique.add(token)
else:
is_unique = False
assert is_unique
def test_container_registry_map_has_val():
output = Output()
envvars = EnvVars(output)
envvars.load()
result = envvars.verify_envvar_has_val("CONTAINER_REGISTRY_MAP", envvars.CONTAINER_REGISTRY_MAP)
assert not result
def test_additional_container_registry_server_has_val():
output = Output()
envvars = EnvVars(output)
envvars.load()
if len(envvars.CONTAINER_REGISTRY_MAP) > 1:
keys = envvars.CONTAINER_REGISTRY_MAP.keys()
for key in keys:
if key != '':
token = key
assert envvars.CONTAINER_REGISTRY_MAP[token].server is not None
def test_additional_container_registry_username_has_val():
output = Output()
envvars = EnvVars(output)
envvars.load()
if len(envvars.CONTAINER_REGISTRY_MAP) > 1:
keys = envvars.CONTAINER_REGISTRY_MAP.keys()
for key in keys:
if key != '':
token = key
assert envvars.CONTAINER_REGISTRY_MAP[token].username is not None
def test_additional_container_registry_password_has_val():
output = Output()
envvars = EnvVars(output)
envvars.load()
if len(envvars.CONTAINER_REGISTRY_MAP) > 1:
keys = envvars.CONTAINER_REGISTRY_MAP.keys()
for key in keys:
if key != '':
token = key
assert envvars.CONTAINER_REGISTRY_MAP[token].password is not None