From 51d6d19810a230c38fd5f9134a12320d7e263d39 Mon Sep 17 00:00:00 2001 From: MercierCorentin Date: Thu, 20 May 2021 13:29:04 +0200 Subject: [PATCH] Add first version of set domain provider --- data/actionsmap/yunohost.yml | 24 ++ data/bash-completion.d/yunohost | 117 ++++++++- .../{providers.yml => providers_list.yml} | 138 +++++------ src/yunohost/app.py | 1 + src/yunohost/domain.py | 225 +++++++++++------- 5 files changed, 344 insertions(+), 161 deletions(-) rename data/other/{providers.yml => providers_list.yml} (79%) diff --git a/data/actionsmap/yunohost.yml b/data/actionsmap/yunohost.yml index 58a48c87f..87dfcf026 100644 --- a/data/actionsmap/yunohost.yml +++ b/data/actionsmap/yunohost.yml @@ -582,6 +582,30 @@ domain: full: --delete help: Delete the key action: store_true + subcategories: + registrar: + subcategory_help: Manage domains registrars + actions: + ### domain_registrar_set() + set: + action_help: Set domain registrar + api: POST /domains/registrar + arguments: + domain: + help: Domain name + registrar: + help: registrar_key, see yunohost domain registrar list + -a: + full: --args + help: Serialized arguments for app script (i.e. "domain=domain.tld&path=/path") + ### domain_registrar_set() + get: + action_help: Get domain registrar + api: GET /domains/registrar + ### domain_registrar_list() + list: + action_help: List available registrars + api: GET /domains/registrar/list ############################# # App # diff --git a/data/bash-completion.d/yunohost b/data/bash-completion.d/yunohost index 2572a391d..1be522db2 100644 --- a/data/bash-completion.d/yunohost +++ b/data/bash-completion.d/yunohost @@ -1,3 +1,114 @@ -# This file is automatically generated -# during Debian's package build by the script -# data/actionsmap/yunohost_completion.py +# +# completion for yunohost +# automatically generated from the actionsmap +# + +_yunohost() +{ + local cur prev opts narg + COMPREPLY=() + + # the number of words already typed + narg=${#COMP_WORDS[@]} + + # the current word being typed + cur="${COMP_WORDS[COMP_CWORD]}" + + # If one is currently typing a category, + # match with categorys + if [[ $narg == 2 ]]; then + opts="user domain app backup settings service firewall dyndns tools hook log diagnosis" + fi + + # If one already typed a category, + # match the actions or the subcategories of that category + if [[ $narg == 3 ]]; then + # the category typed + category="${COMP_WORDS[1]}" + + if [[ $category == "user" ]]; then + opts="list create delete update info group permission ssh" + fi + if [[ $category == "domain" ]]; then + opts="list add registrar push_config remove dns-conf main-domain cert-status cert-install cert-renew url-available setting " + fi + if [[ $category == "app" ]]; then + opts="catalog search manifest fetchlist list info map install remove upgrade change-url setting register-url makedefault ssowatconf change-label addaccess removeaccess clearaccess action config" + fi + if [[ $category == "backup" ]]; then + opts="create restore list info download delete " + fi + if [[ $category == "settings" ]]; then + opts="list get set reset-all reset " + fi + if [[ $category == "service" ]]; then + opts="add remove start stop reload restart reload_or_restart enable disable status log regen-conf " + fi + if [[ $category == "firewall" ]]; then + opts="list allow disallow upnp reload stop " + fi + if [[ $category == "dyndns" ]]; then + opts="subscribe update installcron removecron " + fi + if [[ $category == "tools" ]]; then + opts="adminpw maindomain postinstall update upgrade shell shutdown reboot regen-conf versions migrations" + fi + if [[ $category == "hook" ]]; then + opts="add remove info list callback exec " + fi + if [[ $category == "log" ]]; then + opts="list show share " + fi + if [[ $category == "diagnosis" ]]; then + opts="list show get run ignore unignore " + fi + fi + + # If one already typed an action or a subcategory, + # match the actions of that subcategory + if [[ $narg == 4 ]]; then + # the category typed + category="${COMP_WORDS[1]}" + + # the action or the subcategory typed + action_or_subcategory="${COMP_WORDS[2]}" + + if [[ $category == "user" ]]; then + if [[ $action_or_subcategory == "group" ]]; then + opts="list create delete info add remove" + fi + if [[ $action_or_subcategory == "permission" ]]; then + opts="list info update add remove reset" + fi + if [[ $action_or_subcategory == "ssh" ]]; then + opts="list-keys add-key remove-key" + fi + fi + if [[ $category == "app" ]]; then + if [[ $action_or_subcategory == "action" ]]; then + opts="list run" + fi + if [[ $action_or_subcategory == "config" ]]; then + opts="show-panel apply" + fi + fi + if [[ $category == "tools" ]]; then + if [[ $action_or_subcategory == "migrations" ]]; then + opts="list run state" + fi + fi + fi + + # If no options were found propose --help + if [ -z "$opts" ]; then + prev="${COMP_WORDS[COMP_CWORD-1]}" + + if [[ $prev != "--help" ]]; then + opts=( --help ) + fi + fi + COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) + return 0 +} + +complete -F _yunohost yunohost \ No newline at end of file diff --git a/data/other/providers.yml b/data/other/providers_list.yml similarity index 79% rename from data/other/providers.yml rename to data/other/providers_list.yml index 4ba69d97b..a006bd272 100644 --- a/data/other/providers.yml +++ b/data/other/providers_list.yml @@ -1,218 +1,218 @@ -- aliyun +aliyun: - auth_key_id - auth_secret -- aurora +aurora: - auth_api_key - auth_secret_key -- azure +azure: - auth_client_id - auth_client_secret - auth_tenant_id - auth_subscription_id - resource_group -- cloudflare +cloudflare: - auth_username - auth_token - zone_id -- cloudns +cloudns: - auth_id - auth_subid - auth_subuser - auth_password - weight - port -- cloudxns +cloudxns: - auth_username - auth_token -- conoha +conoha: - auth_region - auth_token - auth_username - auth_password - auth_tenant_id -- constellix +constellix: - auth_username - auth_token -- digitalocean +digitalocean: - auth_token -- dinahosting +dinahosting: - auth_username - auth_password -- directadmin +directadmin: - auth_password - auth_username - endpoint -- dnsimple +dnsimple: - auth_token - auth_username - auth_password - auth_2fa -- dnsmadeeasy +dnsmadeeasy: - auth_username - auth_token -- dnspark +dnspark: - auth_username - auth_token -- dnspod +dnspod: - auth_username - auth_token -- dreamhost +dreamhost: - auth_token -- dynu +dynu: - auth_token -- easydns +easydns: - auth_username - auth_token -- easyname +easyname: - auth_username - auth_password -- euserv +euserv: - auth_username - auth_password -- exoscale +exoscale: - auth_key - auth_secret -- gandi +gandi: - auth_token - api_protocol -- gehirn +gehirn: - auth_token - auth_secret -- glesys +glesys: - auth_username - auth_token -- godaddy +godaddy: - auth_key - auth_secret -- googleclouddns +googleclouddns: - auth_service_account_info -- gransy +gransy: - auth_username - auth_password -- gratisdns +gratisdns: - auth_username - auth_password -- henet +henet: - auth_username - auth_password -- hetzner +hetzner: - auth_token -- hostingde +hostingde: - auth_token -- hover +hover: - auth_username - auth_password -- infoblox +infoblox: - auth_user - auth_psw - ib_view - ib_host -- infomaniak +infomaniak: - auth_token -- internetbs +internetbs: - auth_key - auth_password -- inwx +inwx: - auth_username - auth_password -- joker +joker: - auth_token -- linode +linode: - auth_token -- linode4 +linode4: - auth_token -- localzone +localzone: - filename -- luadns +luadns: - auth_username - auth_token -- memset +memset: - auth_token -- mythicbeasts +mythicbeasts: - auth_username - auth_password - auth_token -- namecheap +namecheap: - auth_token - auth_username - auth_client_ip - auth_sandbox -- namesilo +namesilo: - auth_token -- netcup +netcup: - auth_customer_id - auth_api_key - auth_api_password -- nfsn +nfsn: - auth_username - auth_token -- njalla +njalla: - auth_token -- nsone +nsone: - auth_token -- onapp +onapp: - auth_username - auth_token - auth_server -- online +online: - auth_token -- ovh +ovh: - auth_entrypoint - auth_application_key - auth_application_secret - auth_consumer_key -- plesk +plesk: - auth_username - auth_password - plesk_server -- pointhq +pointhq: - auth_username - auth_token -- powerdns +powerdns: - auth_token - pdns_server - pdns_server_id - pdns_disable_notify -- rackspace +rackspace: - auth_account - auth_username - auth_api_key - auth_token - sleep_time -- rage4 +rage4: - auth_username - auth_token -- rcodezero +rcodezero: - auth_token -- route53 +route53: - auth_access_key - auth_access_secret - private_zone - auth_username - auth_token -- safedns +safedns: - auth_token -- sakuracloud +sakuracloud: - auth_token - auth_secret -- softlayer +softlayer: - auth_username - auth_api_key -- transip +transip: - auth_username - auth_api_key -- ultradns +ultradns: - auth_token - auth_username - auth_password -- vultr +vultr: - auth_token -- yandex +yandex: - auth_token -- zeit +zeit: - auth_token -- zilore +zilore: - auth_key -- zonomi +zonomi: - auth_token - auth_entrypoint diff --git a/src/yunohost/app.py b/src/yunohost/app.py index c048ca5ea..b9a7e634b 100644 --- a/src/yunohost/app.py +++ b/src/yunohost/app.py @@ -2977,6 +2977,7 @@ ARGUMENTS_TYPE_PARSERS = { } + def _parse_args_in_yunohost_format(user_answers, argument_questions): """Parse arguments store in either manifest.json or actions.json or from a config panel against the user answers when they are present. diff --git a/src/yunohost/domain.py b/src/yunohost/domain.py index 05f2a16ae..6180616bf 100644 --- a/src/yunohost/domain.py +++ b/src/yunohost/domain.py @@ -29,8 +29,8 @@ import sys import yaml import functools -from lexicon.config import ConfigResolver -from lexicon.client import Client +# from lexicon.config import ConfigResolver +# from lexicon.client import Client from moulinette import m18n, msettings, msignals from moulinette.core import MoulinetteError @@ -43,6 +43,7 @@ from yunohost.app import ( _installed_apps, _get_app_settings, _get_conflicting_apps, + _parse_args_in_yunohost_format ) from yunohost.regenconf import regen_conf, _force_clear_hashes, _process_regen_conf from yunohost.utils.network import get_public_ip @@ -53,6 +54,7 @@ from yunohost.hook import hook_callback logger = getActionLogger("yunohost.domain") DOMAIN_SETTINGS_PATH = "/etc/yunohost/domains.yml" +REGISTRAR_LIST_PATH = "/usr/share/yunohost/other/providers_list.yml" def domain_list(exclude_subdomains=False): """ @@ -825,105 +827,150 @@ def _set_domain_settings(domain, domain_settings): with open(DOMAIN_SETTINGS_PATH, 'w') as file: yaml.dump(domains, file, default_flow_style=False) +# def domain_get_registrar(): +def domain_registrar_set(domain, registrar, args): + + domains = _load_domain_settings() + if not domain in domains.keys(): + raise YunohostError("domain_name_unknown", domain=domain) -def domain_push_config(domain): - """ - Send DNS records to the previously-configured registrar of the domain. - """ - # Generate the records - if domain not in domain_list()["domains"]: - raise YunohostValidationError("domain_name_unknown", domain=domain) + registrars = yaml.load(open(REGISTRAR_LIST_PATH, "r+")) + if not registrar in registrars.keys(): + # FIXME créer l'erreur + raise YunohostError("registrar_unknown") + + parameters = registrars[registrar] + ask_args = [] + for parameter in parameters: + ask_args.append({ + 'name' : parameter, + 'type': 'string', + 'example': '', + 'default': '', + }) + args_dict = ( + {} if not args else dict(urllib.parse.parse_qsl(args, keep_blank_values=True)) + ) + parsed_answer_dict = _parse_args_in_yunohost_format(args_dict, ask_args) - domains_settings = _get_domain_settings(domain, True) - - dns_conf = _build_dns_conf(domains_settings) - - # Flatten the DNS conf - flatten_dns_conf = [] - for key in dns_conf: - list_of_records = dns_conf[key] - for record in list_of_records: - # FIXME Lexicon does not support CAA records - # See https://github.com/AnalogJ/lexicon/issues/282 and https://github.com/AnalogJ/lexicon/pull/371 - # They say it's trivial to implement it! - # And yet, it is still not done/merged - if record["type"] != "CAA": - # Add .domain.tdl to the name entry - record["name"] = "{}.{}".format(record["name"], domain) - flatten_dns_conf.append(record) - - # Get provider info - # TODO - provider = { - "name": "gandi", - "options": { - "api_protocol": "rest", - "auth_token": "vhcIALuRJKtoZiZyxfDYWLom" + domain_provider = { + 'name': registrar, + 'options': { + } } + for arg_name, arg_value_and_type in parsed_answer_dict.items(): + domain_provider['options'][arg_name] = arg_value_and_type[0] + + domain_settings = domains[domain] + domain_settings["provider"] = domain_provider - # Construct the base data structure to use lexicon's API. - base_config = { - "provider_name": provider["name"], - "domain": domain, # domain name - } - base_config[provider["name"]] = provider["options"] + # Save the settings to the .yaml file + with open(DOMAIN_SETTINGS_PATH, 'w') as file: + yaml.dump(domains, file, default_flow_style=False) + - # Get types present in the generated records - types = set() - for record in flatten_dns_conf: - types.add(record["type"]) - # Fetch all types present in the generated records - distant_records = {} - for key in types: - record_config = { - "action": "list", - "type": key, - } - final_lexicon = ConfigResolver().with_dict(dict_object=base_config).with_dict(dict_object=record_config) - # print('final_lexicon:', final_lexicon); - client = Client(final_lexicon) - distant_records[key] = client.execute() +# def domain_push_config(domain): +# """ +# Send DNS records to the previously-configured registrar of the domain. +# """ +# # Generate the records +# if domain not in domain_list()["domains"]: +# raise YunohostValidationError("domain_name_unknown", domain=domain) - for key in types: - for distant_record in distant_records[key]: - print('distant_record:', distant_record); - for local_record in flatten_dns_conf: - print('local_record:', local_record); +# domains_settings = _get_domain_settings(domain, True) - # Push the records - for record in flatten_dns_conf: - # For each record, first check if one record exists for the same (type, name) couple - it_exists = False - # TODO do not push if local and distant records are exactly the same ? - # is_the_same_record = False +# dns_conf = _build_dns_conf(domains_settings) - for distant_record in distant_records[record["type"]]: - if distant_record["type"] == record["type"] and distant_record["name"] == record["name"]: - it_exists = True - # previous TODO - # if distant_record["ttl"] = ... and distant_record["name"] ... - # is_the_same_record = True +# # Flatten the DNS conf +# flatten_dns_conf = [] +# for key in dns_conf: +# list_of_records = dns_conf[key] +# for record in list_of_records: +# # FIXME Lexicon does not support CAA records +# # See https://github.com/AnalogJ/lexicon/issues/282 and https://github.com/AnalogJ/lexicon/pull/371 +# # They say it's trivial to implement it! +# # And yet, it is still not done/merged +# if record["type"] != "CAA": +# # Add .domain.tdl to the name entry +# record["name"] = "{}.{}".format(record["name"], domain) +# flatten_dns_conf.append(record) - # Finally, push the new record or update the existing one - record_config = { - "action": "update" if it_exists else "create", # create, list, update, delete - "type": record["type"], # specify a type for record filtering, case sensitive in some cases. - "name": record["name"], - "content": record["value"], - # FIXME Delte TTL, doesn't work with Gandi. - # See https://github.com/AnalogJ/lexicon/issues/726 (similar issue) - # But I think there is another issue with Gandi. Or I'm misusing the API... - # "ttl": record["ttl"], - } - final_lexicon = ConfigResolver().with_dict(dict_object=base_config).with_dict(dict_object=record_config) - client = Client(final_lexicon) - print('pushed_record:', record_config, "→", end=' ') - results = client.execute() - print('results:', results); - # print("Failed" if results == False else "Ok") +# # Get provider info +# # TODO +# provider = { +# "name": "gandi", +# "options": { +# "api_protocol": "rest", +# "auth_token": "vhcIALuRJKtoZiZyxfDYWLom" +# } +# } + +# # Construct the base data structure to use lexicon's API. +# base_config = { +# "provider_name": provider["name"], +# "domain": domain, # domain name +# } +# base_config[provider["name"]] = provider["options"] + +# # Get types present in the generated records +# types = set() + +# for record in flatten_dns_conf: +# types.add(record["type"]) + +# # Fetch all types present in the generated records +# distant_records = {} + +# for key in types: +# record_config = { +# "action": "list", +# "type": key, +# } +# final_lexicon = ConfigResolver().with_dict(dict_object=base_config).with_dict(dict_object=record_config) +# # print('final_lexicon:', final_lexicon); +# client = Client(final_lexicon) +# distant_records[key] = client.execute() + +# for key in types: +# for distant_record in distant_records[key]: +# print('distant_record:', distant_record); +# for local_record in flatten_dns_conf: +# print('local_record:', local_record); + +# # Push the records +# for record in flatten_dns_conf: +# # For each record, first check if one record exists for the same (type, name) couple +# it_exists = False +# # TODO do not push if local and distant records are exactly the same ? +# # is_the_same_record = False + +# for distant_record in distant_records[record["type"]]: +# if distant_record["type"] == record["type"] and distant_record["name"] == record["name"]: +# it_exists = True +# # previous TODO +# # if distant_record["ttl"] = ... and distant_record["name"] ... +# # is_the_same_record = True + +# # Finally, push the new record or update the existing one +# record_config = { +# "action": "update" if it_exists else "create", # create, list, update, delete +# "type": record["type"], # specify a type for record filtering, case sensitive in some cases. +# "name": record["name"], +# "content": record["value"], +# # FIXME Delte TTL, doesn't work with Gandi. +# # See https://github.com/AnalogJ/lexicon/issues/726 (similar issue) +# # But I think there is another issue with Gandi. Or I'm misusing the API... +# # "ttl": record["ttl"], +# } +# final_lexicon = ConfigResolver().with_dict(dict_object=base_config).with_dict(dict_object=record_config) +# client = Client(final_lexicon) +# print('pushed_record:', record_config, "→", end=' ') +# results = client.execute() +# print('results:', results); +# # print("Failed" if results == False else "Ok") # def domain_config_fetch(domain, key, value):