Open mfeyx opened 2 years ago
Thanks for the feedback! We are routing this to the appropriate team for follow-up. cc @armleads-azure.
Author: | mfeyx |
---|---|
Assignees: | - |
Labels: | `Service Attention`, `ARM` |
Milestone: | - |
route to service team
Is there any progress here? Providing a parameters file with unused variables shouldn't be a problem. I right now have to parse out the correct parameters of my parameters.json file just to not run into a
Invalid Template: Deployment template validation failed: 'The template parameters 'a,b,c' in the parameters file are not valid; they are not present in the original template and can therefore not be provided at deployment time. The only supported parameters for this template are 'a,b'
error.
In this case I would expect the parameter c
to just be ignored.
It would be very useful to have this feature implemented as it would allow to have a centralized parameter file with all the values in there. That could be passed in a yml pipeline and sent to the deployment file where only the needed params would be loaded and the rest ignored.
This would be very usefull for me also. I have a deployment that uses three different bicep templates, which are called with a single parameter file. The templates have some of the same parameters, but few different also.
To work around this limitation I have created dummy parameters for the bicep templates. This means everytime I want to add a new parameter for any of the templates, I also need to add the Dummy params for the other two.
Could the az deployment command just have a flag --ignore-unused-parameters=true ?
Hi,
maybe this is not the best solution to this request but I have written a script for the bicep deployment.
.
├── RG1
│ ├── main.bicep
│ └── modules
├── RG2
│ ├── main.bicep
│ ├── modules
│ ├── params.json
│ └── params.secret.json
├── config.json
├── deploy.py
└── params.json
{
"location": "northeurope",
"subscriptionId": "000e0000-e00b-00d0-a000-000000000000"
}
⚠️ put the params.secret.json in .gitignore
{
"id": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"title": "Parameters",
"description": "Azure deployment parameter file",
"type": "object",
"parameters": {
"tags": {
"value": {
"key": "value"
}
}
}
}
The script...
params.json
from the root folder;params.json
file in the resource group folder, if available, both were merged: {**global, **project}
;main.bicep
file is checked for params. The deploy will run if all params are found (a params.deployment.json
file is created and deleted afterwards. It will only contain the params from main.bicep
)python deploy.py --help
import os
import re
import json
import time
import argparse
import hashlib
from string import Template
# ---------------------------------------------------------------------------- #
# UTIL FUNCTIONS #
# ---------------------------------------------------------------------------- #
def walk_folder(folder: str) -> tuple:
top_dir = list(os.walk(folder))
root_dir = top_dir[0]
return root_dir
def _print(msg: str or list) -> None:
if not type(msg) == list:
msg = [msg]
for m in msg:
print(m)
def xprint(msg: str | list, code=1) -> None:
_print(msg)
exit(code)
def nprint(msg: str or list, new_line="\n") -> None:
if new_line:
print(new_line)
_print(msg)
def padding(val):
val = str(val)
if len(val) == 2:
return val
return f" {val}"
# ---------------------------------------------------------------------------- #
# DEFAULT VALUES #
# ---------------------------------------------------------------------------- #
# script default values
SEP = os.sep
ENV_ARG = "env"
ENCODING = "utf-8"
DEFAULT_VALUE = {"value": None}
# configuration files used for deploy.py
CONFIG_JSON = "config.json"
CONFIG_SECRET_JSON = "config.secret.json"
# configuration for bicep files
PARAMS_JSON = "params.json"
PARAMS_SECRET_JSON = "params.secret.json"
# tmp file
# will be generated by this script, deleted afterwards
# only used for deployment
PARAMS_DEPLOYMENT = "params.deploy.json"
# possible env values
DEV = "dev"
PROD = "prd"
# ---------------------------------------------------------------------------- #
# STRING TEMPLATES #
# ---------------------------------------------------------------------------- #
param_json_template = Template("""{
"id": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"title": "Parameters",
"description": "An Azure deployment parameter file",
"type": "object",
"parameters": $parameters
}""".strip())
command_template = Template("""
az deployment sub create --name $name --subscription $subscription_id -l $location -f $bicep_file
""".strip())
info_template = Template("""
DEPLOYMENT COMMAND
---------------------------
$command
""")
# ---------------------------------------------------------------------------- #
# ARGUMENT PARSER #
# ---------------------------------------------------------------------------- #
parser = argparse.ArgumentParser(
description="Deploy Infrastructure to Azure Cloud.")
parser.add_argument("-e", f"--{ENV_ARG}",
help="Choose Deployment Environment", default=None)
parser.add_argument("-f", "--file",
help="Name of bicep file", default="main.bicep")
parser.add_argument("-g", "--group",
help="Resource Group folder for deployment, relative path", default=None)
parser.add_argument("-s", "--skip-preview", help="Run Deployment without Preview",
action=argparse.BooleanOptionalAction, default=False)
args = vars(parser.parse_args())
# print(args)
# ---------------------------------------------------------------------------- #
# CHECK FOR PROJECT #
# ---------------------------------------------------------------------------- #
global_root, global_folders, global_files = list(walk_folder('.'))
ROOT = args.get("group")
if not ROOT:
try:
project_folders = {}
i = 0
print("RESOURCE GROUPS")
print("===============")
for project in global_folders:
i += 1
print(f"{padding(i)} --> {project}")
project_folders[f"{i}"] = project
while not ROOT:
project = str(input("\nSelect Resource Group Number: "))
ROOT = project_folders.get(project)
except KeyboardInterrupt:
xprint(["None", "Abort Deployment."])
ROOT_PATH = f".{SEP}{ROOT}{SEP}"
print(f"Running Deployment for: {ROOT}")
# ---------------------------------------------------------------------------- #
# HANDLE CONFIG FILES #
# ---------------------------------------------------------------------------- #
project_root, project_folders, project_files = list(walk_folder(ROOT))
if not re.search(r"bicep", "|".join(project_files)):
xprint("No Bicep File found")
config_global_json = f"{CONFIG_JSON}"
global_config = {}
if config_global_json in global_files:
with open(config_global_json, "r") as f:
global_config = json.load(f)
config_json = f"{ROOT_PATH}{CONFIG_JSON}"
project_config = {}
if CONFIG_JSON in project_files:
with open(config_json, "r", encoding=ENCODING) as f:
project_config = json.load(f)
# project overrides global
config = {**global_config, **project_config}
# ---------------------------------------------------------------------------- #
# INPUT VALIDATION #
# ---------------------------------------------------------------------------- #
# mandatory files: config.json, *.bicep file with resources
# mandatory fields in config.json: subscriptionId, location
# ------------------------------- CONFIGURATION ------------------------------ #
if not config:
xprint(["No `config.json` found, or config is emtpy!",
"Fields: [subscriptionId, location] are mandatory!"])
# ------------------------------ SUBSCRIPTION ID ----------------------------- #
subscription_id = config.get("subscriptionId")
if not subscription_id:
xprint([
"Missing Subscription ID.",
"Make sure `config.json` exists with `subscriptionId` field."
])
# --------------------------------- LOCATION --------------------------------- #
location = config.get("location")
if not location:
xprint("No Location specified. Add `location` to your `config.json` file.")
# -------------------------------- BICEP FILE -------------------------------- #
bicep_file = args.get('file')
if not bicep_file in project_files:
xprint(f"Bicep file `{bicep_file}` not found in { project_files }")
# ---------------------------------------------------------------------------- #
# BUILDING DEPLOYMENT PARAMETERS #
# ---------------------------------------------------------------------------- #
bicep_file = f"{ROOT_PATH}{bicep_file}"
with open(bicep_file, "r") as b:
# line := param <name> type = <default value>
params = [line.strip().split(" ")[1] for line in b.readlines()
if line.startswith("param") and len(line.strip().split(" ")) == 3]
deployment_json = None
if params:
print(f"Params in Bicep: {params}")
print("--> Building Deployment Parameters")
deploy_parameters = {}
# global params
for json_file in [PARAMS_JSON, PARAMS_SECRET_JSON]:
if json_file in global_files:
params_json = json_file
with open(params_json, "r") as f:
params_content = json.load(f)
parameters = params_content.get("parameters")
for param in params:
p = parameters.get(param)
if p:
deploy_parameters[param] = p
# project params
for json_file in [PARAMS_JSON, PARAMS_SECRET_JSON]:
if json_file in project_files:
params_json = f"{ROOT_PATH}{json_file}"
with open(params_json, "r") as f:
params_content = json.load(f)
parameters = params_content.get("parameters")
for param in params:
p = parameters.get(param)
if p:
deploy_parameters[param] = p
env_value = args.get(ENV_ARG)
if env_value and (ENV_ARG in params):
print("found env: {}".format(env_value))
env_value = env_value.lower()
is_production = re.search(r"pr.?d.*", env_value)
env = PROD if is_production else DEV
deploy_parameters[ENV_ARG] = {"value": env}
# ---------------------------- VALIDATE PARAMS --------------------------- #
missing_params = [
param for param in params
if param not in deploy_parameters.keys()
]
if missing_params:
if "env" in missing_params:
env_value = input(
"Choose Deployment Environment -> [d]ev, [p]rod: ")
is_production = str(env_value).lower().startswith("p")
env = PROD if is_production else DEV
deploy_parameters[ENV_ARG] = {"value": env}
else:
xprint(f"--> Missing Parameters: {missing_params}")
# ------------- WRITE DEPLOYMENT PARAMS FILE AFTER VALIDATION ------------ #
nprint("Writing Deployment Config")
deploy_parameters_str = json.dumps(deploy_parameters)
parameters_deployment = param_json_template.safe_substitute(
parameters=deploy_parameters_str)
deployment_json = f"{ROOT_PATH}{PARAMS_DEPLOYMENT}"
with open(deployment_json, "w", encoding=ENCODING) as f:
f.write(parameters_deployment)
# ---------------------------------------------------------------------------- #
# BUILD THE COMMAND #
# ---------------------------------------------------------------------------- #
print("Building Deployment Command")
# base command
command = command_template.safe_substitute({
"name": hashlib.md5(bytes(ROOT, encoding="utf-8")).hexdigest(),
"subscription_id": subscription_id,
"location": location,
"bicep_file": bicep_file,
})
# add parameters argument to command
if deployment_json and os.path.isfile(deployment_json):
command += f" -p {deployment_json}"
# skip preview or not?
skip_preview = args.get("skip_preview")
if not skip_preview:
command += " --confirm-with-what-if"
# ---------------------------------------------------------------------------- #
# RUN THE COMMAND #
# ---------------------------------------------------------------------------- #
info = info_template.safe_substitute(command=command)
print(info)
time.sleep(1)
# ? Run Forest -> RUN!
os.system(command)
if os.path.isfile(deployment_json):
os.remove(deployment_json)
Hope it helps! Cheers
Is there an update on this?
It would be very useful to have this feature implemented. please consider such useful improvisations.
Is your feature request related to a problem? Please describe
I want to deploy resources based on a
params.json
file. If unused parameters are present the deployment currently fails:main.bicep
params.json
Describe the solution you'd like
Basically, I want to deploy resources without having to delete parameters in the parameters file. In other words, I want to provide as many parameters in my configuration as I want, but use only a few of them in a specific deployment. Hence, I want to be able to define parameters even though they might not be used in a deployment. When I deploy a resource group, for instance, I will use my
rg_tags
values from the parameters, but not the normaltags
that might be used in other deployments.Additional context
This is the command I use:
Hope this is the right place for the suggestion. I already opened an issue here, but this place seems to be a better fit.
Kind regards :)