зеркало из https://github.com/Azure/iotedgedev.git
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:
Родитель
ea831ddf71
Коммит
271e28ebe1
10
.env.tmp
10
.env.tmp
|
@ -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
|
||||
|
Загрузка…
Ссылка в новой задаче