diff --git a/debian/po/pt_BR.po b/debian/po/pt_BR.po index 230d0132dd..ec24958c91 100644 --- a/debian/po/pt_BR.po +++ b/debian/po/pt_BR.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-01-05 13:05-0300\n" +"POT-Creation-Date: 2024-01-08 11:51-0500\n" "PO-Revision-Date: 2023-09-25 12:29-0400\n" "Last-Translator: Lucas Moura \n" "Language-Team: Brazilian Portuguese for interval must be a positive integer." @@ -3249,17 +3264,17 @@ msgstr "" "Não foi possível associar {key} a {value}: precisa ser um inteiro " "positivo." -#: ../../uaclient/messages/__init__.py:2342 +#: ../../uaclient/messages/__init__.py:2362 #, python-brace-format msgid "Invalid url in config. {key}: {value}" msgstr "url inválida no arquivo de configuração. {key}: {value}" -#: ../../uaclient/messages/__init__.py:2347 +#: ../../uaclient/messages/__init__.py:2367 #, python-brace-format msgid "Could not find yaml file: {filepath}" msgstr "Não foi possível encontrar o arquivo yaml: {filepath}" -#: ../../uaclient/messages/__init__.py:2353 +#: ../../uaclient/messages/__init__.py:2373 msgid "" "Error: Setting global apt proxy and pro scoped apt proxy\n" "at the same time is unsupported.\n" @@ -3270,27 +3285,27 @@ msgstr "" "pro de apt ao mesmo tempo não é suportado.\n" "Cancelando o processo de configuração.\n" -#: ../../uaclient/messages/__init__.py:2363 +#: ../../uaclient/messages/__init__.py:2383 msgid "Can't load the distro-info database." msgstr "Não foi possível carregar o banco de dados do distro-info." -#: ../../uaclient/messages/__init__.py:2368 +#: ../../uaclient/messages/__init__.py:2388 #, python-brace-format msgid "Can't find series {series} in the distro-info database." msgstr "" "Não foi possível encontrar a série {series} na banco de dados do distro-info." -#: ../../uaclient/messages/__init__.py:2373 +#: ../../uaclient/messages/__init__.py:2393 #, python-brace-format msgid "Error: Cannot use {option1} together with {option2}." msgstr "Erro: não é possível usar {option1} junto com {option2}" -#: ../../uaclient/messages/__init__.py:2377 +#: ../../uaclient/messages/__init__.py:2397 #, python-brace-format msgid "No help available for '{name}'" msgstr "Ajuda não disponível para '{name}'" -#: ../../uaclient/messages/__init__.py:2383 +#: ../../uaclient/messages/__init__.py:2403 #, python-brace-format msgid "" "Error: issue \"{issue}\" is not recognized.\n" @@ -3299,34 +3314,34 @@ msgstr "" "Erro: problema de segurnça \"{issue}\" não foi reconhecido.\n" "Use: \"pro fix CVE-yyyy-nnnn\" ou \"pro fix USN-nnnn\"" -#: ../../uaclient/messages/__init__.py:2389 +#: ../../uaclient/messages/__init__.py:2409 #, python-brace-format msgid "{arg} must be one of: {choices}" msgstr "{arg} precisa ser um de: {choices}" -#: ../../uaclient/messages/__init__.py:2394 +#: ../../uaclient/messages/__init__.py:2414 #, python-brace-format msgid "Expected {expected} but found: {actual}" msgstr "Esperava {expected} mas encontrou: {actual}" -#: ../../uaclient/messages/__init__.py:2398 +#: ../../uaclient/messages/__init__.py:2418 msgid "Unable to process uaclient.conf" msgstr "Falha ao processar uaclient.conf" -#: ../../uaclient/messages/__init__.py:2403 +#: ../../uaclient/messages/__init__.py:2423 msgid "Unable to refresh your subscription" msgstr "Falha ao atualizar sua assinatura" -#: ../../uaclient/messages/__init__.py:2408 +#: ../../uaclient/messages/__init__.py:2428 msgid "Unable to update Ubuntu Pro related APT and MOTD messages." msgstr "" "Falha ao atualizar as mensagens de APT e MOTD relacioandas ao Ubuntu Pro" -#: ../../uaclient/messages/__init__.py:2414 +#: ../../uaclient/messages/__init__.py:2434 msgid "json formatted response requires --assume-yes flag." msgstr "resposta formatada em json necessita do paramêtro --assume-yes" -#: ../../uaclient/messages/__init__.py:2422 +#: ../../uaclient/messages/__init__.py:2442 msgid "" "Do not pass the TOKEN arg if you are using --attach-config.\n" "Include the token in the attach-config file instead.\n" @@ -3336,50 +3351,50 @@ msgstr "" "Ao invés disso, inclua o token no arquivo de attach-config.\n" " " -#: ../../uaclient/messages/__init__.py:2431 +#: ../../uaclient/messages/__init__.py:2451 msgid "Cannot provide both --args and --data at the same time" msgstr "Não é possível usar --args e --data ao mesmo tempo" -#: ../../uaclient/messages/__init__.py:2437 +#: ../../uaclient/messages/__init__.py:2457 #, python-brace-format msgid "Unable to perform: {lock_request}.\n" msgstr "Falha ao executar: {lock_request}.\n" -#: ../../uaclient/messages/__init__.py:2446 +#: ../../uaclient/messages/__init__.py:2466 msgid "This command must be run as root (try using sudo)." msgstr "Esse comando precisa ser executado como root (tente usando sudo)." -#: ../../uaclient/messages/__init__.py:2451 +#: ../../uaclient/messages/__init__.py:2471 #, python-brace-format msgid "Metadata for {issue} is invalid. Error: {error_msg}." msgstr "Metadados para {issue} não são válidos. Erro: {error_msg}." -#: ../../uaclient/messages/__init__.py:2458 +#: ../../uaclient/messages/__init__.py:2478 #, python-brace-format msgid "Error: {issue_id} not found." msgstr "Erro: {issue_id} não encontrada." -#: ../../uaclient/messages/__init__.py:2462 +#: ../../uaclient/messages/__init__.py:2482 #, python-brace-format msgid "GPG key '{keyfile}' not found." msgstr "chave GPG '{keyfile}' não foi encontrada" -#: ../../uaclient/messages/__init__.py:2467 +#: ../../uaclient/messages/__init__.py:2487 #, python-brace-format msgid "'{endpoint}' is not a valid endpoint" msgstr "'{endpoint}' não é um endpoint válido" -#: ../../uaclient/messages/__init__.py:2472 +#: ../../uaclient/messages/__init__.py:2492 #, python-brace-format msgid "Missing argument '{arg}' for endpoint {endpoint}" msgstr "'{arg}' está faltando para endpoint {endpoint}" -#: ../../uaclient/messages/__init__.py:2477 +#: ../../uaclient/messages/__init__.py:2497 #, python-brace-format msgid "{endpoint} accepts no arguments" msgstr "{endpoint} não aceita paramêtros" -#: ../../uaclient/messages/__init__.py:2482 +#: ../../uaclient/messages/__init__.py:2502 #, python-brace-format msgid "" "Error parsing API json data parameter:\n" @@ -3388,32 +3403,32 @@ msgstr "" "Error ao analisar paramêtro data para API json:\n" "{data}" -#: ../../uaclient/messages/__init__.py:2487 +#: ../../uaclient/messages/__init__.py:2507 #, python-brace-format msgid "'{arg}' is not formatted as 'key=value'" msgstr "'{arg}' não está formatado como 'chave=valor'" -#: ../../uaclient/messages/__init__.py:2492 +#: ../../uaclient/messages/__init__.py:2512 #, python-brace-format msgid "Unable to determine version: {error_msg}" msgstr "Não foi possível determinar a versão: {error_msg}" -#: ../../uaclient/messages/__init__.py:2497 +#: ../../uaclient/messages/__init__.py:2517 msgid "features.disable_auto_attach set in config" msgstr "features.disable_auto_attach definida na configuração" -#: ../../uaclient/messages/__init__.py:2502 +#: ../../uaclient/messages/__init__.py:2522 #, python-brace-format msgid "Unable to determine unattended-upgrades status: {error_msg}" msgstr "" "Não foi possível determinar o status do unattended-upgrades: {error_msg}" -#: ../../uaclient/messages/__init__.py:2508 +#: ../../uaclient/messages/__init__.py:2528 #, python-brace-format msgid "Expected value with type {expected_type} but got type: {got_type}" msgstr "Esperava valor com tipo {expected_type}, mas recebeu tipo {got_type}" -#: ../../uaclient/messages/__init__.py:2514 +#: ../../uaclient/messages/__init__.py:2534 #, python-brace-format msgid "" "Got value with incorrect type at index {index}:\n" @@ -3422,7 +3437,7 @@ msgstr "" "Valor com tipo incorreto na posição {index}:\n" "{nested_msg}" -#: ../../uaclient/messages/__init__.py:2520 +#: ../../uaclient/messages/__init__.py:2540 #, python-brace-format msgid "" "Got value with incorrect type for field \"{key}\":\n" @@ -3431,13 +3446,17 @@ msgstr "" "Valor com tipo incorreto para o campo \"{key}\":\n" "{nested_msg}" -#: ../../uaclient/messages/__init__.py:2527 +#: ../../uaclient/messages/__init__.py:2547 #, python-brace-format msgid "Value provided was not found in {enum_class}'s allowed: value: {values}" msgstr "" "Valor fornecido não está presente nos valores permitidos de {enum_class}: " "{values}" +#: ../../uaclient/messages/__init__.py:2558 +msgid "The operation is not supported" +msgstr "A operação não é suportada" + #~ msgid "Detach this machine from Ubuntu Pro services." #~ msgstr "Desvincule esta máquina de uma assinatura Ubuntu Pro" diff --git a/debian/po/ubuntu-pro.pot b/debian/po/ubuntu-pro.pot index 85d77e4dc8..399064c372 100644 --- a/debian/po/ubuntu-pro.pot +++ b/debian/po/ubuntu-pro.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2024-01-05 13:05-0300\n" +"POT-Creation-Date: 2024-01-08 11:51-0500\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -316,103 +316,107 @@ msgid "" "to get the latest bug fixes and new features." msgstr "" +#: ../../uaclient/messages/__init__.py:112 +msgid "an unknown error" +msgstr "" + #. ############################################################################## #. GENERIC SYSTEM OPERATIONS # #. ############################################################################## -#: ../../uaclient/messages/__init__.py:118 +#: ../../uaclient/messages/__init__.py:119 #, python-brace-format msgid "Executing `{command}`" msgstr "" -#: ../../uaclient/messages/__init__.py:119 +#: ../../uaclient/messages/__init__.py:120 #, python-brace-format msgid "Executing `{command}` failed." msgstr "" -#: ../../uaclient/messages/__init__.py:120 +#: ../../uaclient/messages/__init__.py:121 #, python-brace-format msgid "Invalid command specified '{cmd}'." msgstr "" -#: ../../uaclient/messages/__init__.py:122 +#: ../../uaclient/messages/__init__.py:123 #, python-brace-format msgid "Failed running command '{cmd}' [exit({exit_code})]. Message: {stderr}" msgstr "" -#: ../../uaclient/messages/__init__.py:125 +#: ../../uaclient/messages/__init__.py:126 #, python-brace-format msgid "Installing {packages}" msgstr "" -#: ../../uaclient/messages/__init__.py:126 +#: ../../uaclient/messages/__init__.py:127 #, python-brace-format msgid "Installing {title} packages" msgstr "" -#: ../../uaclient/messages/__init__.py:127 +#: ../../uaclient/messages/__init__.py:128 msgid "Installing required snaps" msgstr "" -#: ../../uaclient/messages/__init__.py:129 +#: ../../uaclient/messages/__init__.py:130 #, python-brace-format msgid "Installing required snap: {snap}" msgstr "" -#: ../../uaclient/messages/__init__.py:132 +#: ../../uaclient/messages/__init__.py:133 #, python-brace-format msgid "Skipping installing packages: {packages}" msgstr "" -#: ../../uaclient/messages/__init__.py:134 +#: ../../uaclient/messages/__init__.py:135 #, python-brace-format msgid "Uninstalling {packages}" msgstr "" -#: ../../uaclient/messages/__init__.py:136 +#: ../../uaclient/messages/__init__.py:137 #, python-brace-format msgid "Failure when uninstalling {packages}" msgstr "" -#: ../../uaclient/messages/__init__.py:139 +#: ../../uaclient/messages/__init__.py:140 #, python-brace-format msgid "Cannot install package {package} version {version}" msgstr "" -#: ../../uaclient/messages/__init__.py:142 +#: ../../uaclient/messages/__init__.py:143 msgid "Failure checking APT policy." msgstr "" -#: ../../uaclient/messages/__init__.py:143 +#: ../../uaclient/messages/__init__.py:144 msgid "Updating package lists" msgstr "" -#: ../../uaclient/messages/__init__.py:144 +#: ../../uaclient/messages/__init__.py:145 #, python-brace-format msgid "Updating {name} package lists" msgstr "" -#: ../../uaclient/messages/__init__.py:145 +#: ../../uaclient/messages/__init__.py:146 msgid "APT update failed." msgstr "" -#: ../../uaclient/messages/__init__.py:146 +#: ../../uaclient/messages/__init__.py:147 msgid "APT install failed." msgstr "" -#: ../../uaclient/messages/__init__.py:148 +#: ../../uaclient/messages/__init__.py:149 #, python-brace-format msgid "Backing up {original} as {backup}" msgstr "" -#: ../../uaclient/messages/__init__.py:149 +#: ../../uaclient/messages/__init__.py:150 msgid "The following package(s) will be REMOVED:" msgstr "" -#: ../../uaclient/messages/__init__.py:151 +#: ../../uaclient/messages/__init__.py:152 msgid "The following package(s) will be reinstalled from the archive:" msgstr "" -#: ../../uaclient/messages/__init__.py:162 +#: ../../uaclient/messages/__init__.py:163 #, python-brace-format msgid "" "*Your Ubuntu Pro subscription has EXPIRED*\n" @@ -427,7 +431,7 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: ../../uaclient/messages/__init__.py:175 +#: ../../uaclient/messages/__init__.py:176 #, python-brace-format msgid "" "CAUTION: Your Ubuntu Pro subscription will expire in {{remaining_days}} " @@ -442,7 +446,7 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: ../../uaclient/messages/__init__.py:188 +#: ../../uaclient/messages/__init__.py:189 #, python-brace-format msgid "" "CAUTION: Your Ubuntu Pro subscription expired on {{expired_date}}.\n" @@ -457,7 +461,7 @@ msgid_plural "" msgstr[0] "" msgstr[1] "" -#: ../../uaclient/messages/__init__.py:202 +#: ../../uaclient/messages/__init__.py:203 #, python-brace-format msgid "" "*Your Ubuntu Pro subscription has EXPIRED*\n" @@ -467,36 +471,36 @@ msgstr "" #. ############################################################################## #. CONFIGURATION # #. ############################################################################## -#: ../../uaclient/messages/__init__.py:213 +#: ../../uaclient/messages/__init__.py:214 #, python-brace-format msgid "Setting {service} proxy" msgstr "" -#: ../../uaclient/messages/__init__.py:215 +#: ../../uaclient/messages/__init__.py:216 #, python-brace-format msgid "" "No proxy set in config; however, proxy is configured for: {{services}}.\n" "See {url} for more information on pro proxy configuration.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:220 +#: ../../uaclient/messages/__init__.py:221 #, python-brace-format msgid "Setting {scope} APT proxy" msgstr "" -#: ../../uaclient/messages/__init__.py:222 +#: ../../uaclient/messages/__init__.py:223 msgid "" "\n" "Error: Setting global apt proxy and pro scoped apt proxy at the same time is " "unsupported. No apt proxy is set." msgstr "" -#: ../../uaclient/messages/__init__.py:226 +#: ../../uaclient/messages/__init__.py:227 #, python-brace-format msgid "Warning: {old} has been renamed to {new}." msgstr "" -#: ../../uaclient/messages/__init__.py:230 +#: ../../uaclient/messages/__init__.py:231 #, python-brace-format msgid "" "Warning: Setting the {current_proxy} proxy will overwrite the " @@ -504,71 +508,71 @@ msgid "" "proxy previously set via `pro config`.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:236 +#: ../../uaclient/messages/__init__.py:237 #, python-brace-format msgid "" "Using deprecated \"{old}\" config field.\n" "Please migrate to using \"{new}\"\n" msgstr "" -#: ../../uaclient/messages/__init__.py:243 +#: ../../uaclient/messages/__init__.py:244 msgid "Migrating /etc/ubuntu-advantage/uaclient.conf" msgstr "" -#: ../../uaclient/messages/__init__.py:246 +#: ../../uaclient/messages/__init__.py:247 msgid "" "Warning: Failed to load /etc/ubuntu-advantage/uaclient.conf.preinst-backup\n" " No automatic migration will occur.\n" " You may need to use \"pro config set\" to re-set your settings." msgstr "" -#: ../../uaclient/messages/__init__.py:253 +#: ../../uaclient/messages/__init__.py:254 msgid "" "Warning: Failed to migrate user_config from /etc/ubuntu-advantage/uaclient." "conf\n" " Please run the following to keep your custom settings:" msgstr "" -#: ../../uaclient/messages/__init__.py:259 +#: ../../uaclient/messages/__init__.py:260 msgid "" "Warning: Failed to migrate /etc/ubuntu-advantage/uaclient.conf\n" " Please add following to uaclient.conf to keep your config:" msgstr "" -#: ../../uaclient/messages/__init__.py:272 +#: ../../uaclient/messages/__init__.py:273 msgid "" "Currently attempting to automatically attach this machine to an Ubuntu Pro " "subscription" msgstr "" -#: ../../uaclient/messages/__init__.py:276 +#: ../../uaclient/messages/__init__.py:277 #, python-brace-format msgid "This machine is now attached to '{contract_name}'\n" msgstr "" -#: ../../uaclient/messages/__init__.py:281 +#: ../../uaclient/messages/__init__.py:282 msgid "This machine is now successfully attached'\n" msgstr "" -#: ../../uaclient/messages/__init__.py:285 +#: ../../uaclient/messages/__init__.py:286 #, python-brace-format msgid "Enabling default service {name}" msgstr "" -#: ../../uaclient/messages/__init__.py:287 +#: ../../uaclient/messages/__init__.py:288 #, python-brace-format msgid "Service {name} is recommended by default. Run: sudo pro enable {name}" msgstr "" -#: ../../uaclient/messages/__init__.py:290 +#: ../../uaclient/messages/__init__.py:291 msgid "Initiating attach operation..." msgstr "" -#: ../../uaclient/messages/__init__.py:291 +#: ../../uaclient/messages/__init__.py:292 msgid "Failed to perform attach..." msgstr "" -#: ../../uaclient/messages/__init__.py:293 +#: ../../uaclient/messages/__init__.py:294 #, python-brace-format msgid "" "Please sign in to your Ubuntu Pro account at this link:\n" @@ -576,40 +580,40 @@ msgid "" "And provide the following code: {bold}{{user_code}}{end_bold}" msgstr "" -#: ../../uaclient/messages/__init__.py:302 +#: ../../uaclient/messages/__init__.py:303 msgid "Attaching the machine..." msgstr "" -#: ../../uaclient/messages/__init__.py:307 +#: ../../uaclient/messages/__init__.py:308 msgid "Detach will disable the following service:" msgid_plural "Detach will disable the following services:" msgstr[0] "" msgstr[1] "" -#: ../../uaclient/messages/__init__.py:312 +#: ../../uaclient/messages/__init__.py:313 msgid "This machine is now detached." msgstr "" -#: ../../uaclient/messages/__init__.py:316 +#: ../../uaclient/messages/__init__.py:317 msgid "One moment, checking your subscription first" msgstr "" -#: ../../uaclient/messages/__init__.py:318 +#: ../../uaclient/messages/__init__.py:319 #, python-brace-format msgid "{title} enabled" msgstr "" -#: ../../uaclient/messages/__init__.py:319 +#: ../../uaclient/messages/__init__.py:320 #, python-brace-format msgid "{title} access enabled" msgstr "" -#: ../../uaclient/messages/__init__.py:320 +#: ../../uaclient/messages/__init__.py:321 #, python-brace-format msgid "Could not enable {title}." msgstr "" -#: ../../uaclient/messages/__init__.py:322 +#: ../../uaclient/messages/__init__.py:323 #, python-brace-format msgid "" "{service_being_enabled} cannot be enabled with {incompatible_service}.\n" @@ -617,12 +621,12 @@ msgid "" "{service_being_enabled}? (y/N) " msgstr "" -#: ../../uaclient/messages/__init__.py:328 +#: ../../uaclient/messages/__init__.py:329 #, python-brace-format msgid "Disabling incompatible service: {service}" msgstr "" -#: ../../uaclient/messages/__init__.py:331 +#: ../../uaclient/messages/__init__.py:332 #, python-brace-format msgid "" "{service_being_enabled} cannot be enabled with {required_service} disabled.\n" @@ -630,23 +634,23 @@ msgid "" "N) " msgstr "" -#: ../../uaclient/messages/__init__.py:336 +#: ../../uaclient/messages/__init__.py:337 #, python-brace-format msgid "Enabling required service: {service}" msgstr "" -#: ../../uaclient/messages/__init__.py:338 +#: ../../uaclient/messages/__init__.py:339 #, python-brace-format msgid "A reboot is required to complete {operation}." msgstr "" #. DISABLE -#: ../../uaclient/messages/__init__.py:343 +#: ../../uaclient/messages/__init__.py:344 #, python-brace-format msgid "Could not disable {title}." msgstr "" -#: ../../uaclient/messages/__init__.py:345 +#: ../../uaclient/messages/__init__.py:346 #, python-brace-format msgid "" "{dependent_service} depends on {service_being_disabled}.\n" @@ -654,50 +658,50 @@ msgid "" "(y/N) " msgstr "" -#: ../../uaclient/messages/__init__.py:351 +#: ../../uaclient/messages/__init__.py:352 #, python-brace-format msgid "Disabling dependent service: {required_service}" msgstr "" -#: ../../uaclient/messages/__init__.py:354 +#: ../../uaclient/messages/__init__.py:355 #, python-brace-format msgid "Removing apt source file: {filename}" msgstr "" -#: ../../uaclient/messages/__init__.py:356 +#: ../../uaclient/messages/__init__.py:357 #, python-brace-format msgid "Removing apt preferences file: {filename}" msgstr "" -#: ../../uaclient/messages/__init__.py:361 +#: ../../uaclient/messages/__init__.py:362 msgid "(The --purge flag is still experimental - use with caution)" msgstr "" -#: ../../uaclient/messages/__init__.py:364 +#: ../../uaclient/messages/__init__.py:365 #, python-brace-format msgid "Purging the {service} packages would uninstall the following kernel(s):" msgstr "" -#: ../../uaclient/messages/__init__.py:367 +#: ../../uaclient/messages/__init__.py:368 #, python-brace-format msgid "{kernel_version} is the current running kernel." msgstr "" -#: ../../uaclient/messages/__init__.py:370 +#: ../../uaclient/messages/__init__.py:371 msgid "" "No other valid Ubuntu kernel was found in the system.\n" "Removing the package would potentially make the system unbootable.\n" "Aborting.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:377 +#: ../../uaclient/messages/__init__.py:378 msgid "" "If you cannot guarantee that other kernels in this system are bootable and\n" "working properly, *do not proceed*. You may end up with an unbootable " "system.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:386 +#: ../../uaclient/messages/__init__.py:387 #, python-brace-format msgid "" "Failed to automatically attach to an Ubuntu Pro subscription {num_attempts} " @@ -707,7 +711,7 @@ msgid "" "You can try manually with `sudo pro auto-attach`." msgstr "" -#: ../../uaclient/messages/__init__.py:394 +#: ../../uaclient/messages/__init__.py:395 #, python-brace-format msgid "" "Failed to automatically attach to an Ubuntu Pro subscription {num_attempts} " @@ -718,39 +722,35 @@ msgid "" "You can try manually with `sudo pro auto-attach`." msgstr "" -#: ../../uaclient/messages/__init__.py:402 +#: ../../uaclient/messages/__init__.py:403 #, python-brace-format msgid "" "Canonical servers did not recognize this machine as Ubuntu Pro: \"{detail}\"" msgstr "" -#: ../../uaclient/messages/__init__.py:406 +#: ../../uaclient/messages/__init__.py:407 msgid "Canonical servers did not recognize this image as Ubuntu Pro" msgstr "" -#: ../../uaclient/messages/__init__.py:408 +#: ../../uaclient/messages/__init__.py:409 #, python-brace-format msgid "the pro lock was held by pid {pid}" msgstr "" -#: ../../uaclient/messages/__init__.py:410 +#: ../../uaclient/messages/__init__.py:411 #, python-brace-format msgid "an error from Canonical servers: \"{error_msg}\"" msgstr "" -#: ../../uaclient/messages/__init__.py:412 +#: ../../uaclient/messages/__init__.py:413 msgid "a connectivity error" msgstr "" -#: ../../uaclient/messages/__init__.py:413 +#: ../../uaclient/messages/__init__.py:414 #, python-brace-format msgid "an error while reaching {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:414 -msgid "an unknown error" -msgstr "" - #: ../../uaclient/messages/__init__.py:418 #, python-brace-format msgid "Due to contract refresh, '{service}' is now disabled." @@ -2573,28 +2573,28 @@ msgid "" "USNs should follow the pattern USN-nnnn." msgstr "" -#: ../../uaclient/messages/__init__.py:1923 +#: ../../uaclient/messages/__init__.py:1928 msgid "Another process is running APT." msgstr "" -#: ../../uaclient/messages/__init__.py:1929 +#: ../../uaclient/messages/__init__.py:1934 #, python-brace-format msgid "" "APT update failed to read APT config for the following:\n" "{failed_repos}" msgstr "" -#: ../../uaclient/messages/__init__.py:1959 +#: ../../uaclient/messages/__init__.py:1964 #, python-brace-format msgid "Invalid APT credentials provided for {repo}" msgstr "" -#: ../../uaclient/messages/__init__.py:1964 +#: ../../uaclient/messages/__init__.py:1969 #, python-brace-format msgid "Timeout trying to access APT repository at {repo}" msgstr "" -#: ../../uaclient/messages/__init__.py:1970 +#: ../../uaclient/messages/__init__.py:1975 #, python-brace-format msgid "" "Unexpected APT error.\n" @@ -2602,107 +2602,107 @@ msgid "" "See /var/log/ubuntu-advantage.log" msgstr "" -#: ../../uaclient/messages/__init__.py:1980 +#: ../../uaclient/messages/__init__.py:1985 #, python-brace-format msgid "" "Cannot validate credentials for APT repo. Timeout after {seconds} seconds " "trying to reach {repo}." msgstr "" -#: ../../uaclient/messages/__init__.py:1987 +#: ../../uaclient/messages/__init__.py:1992 #, python-brace-format msgid "snap {snap} is not installed or doesn't exist" msgstr "" -#: ../../uaclient/messages/__init__.py:1992 +#: ../../uaclient/messages/__init__.py:1997 #, python-brace-format msgid "" "Unexpected SNAPD API error\n" "{error}" msgstr "" -#: ../../uaclient/messages/__init__.py:1996 +#: ../../uaclient/messages/__init__.py:2001 msgid "Could not reach the SNAPD API" msgstr "" -#: ../../uaclient/messages/__init__.py:2000 +#: ../../uaclient/messages/__init__.py:2005 msgid "Failed to install snapd on the system" msgstr "" -#: ../../uaclient/messages/__init__.py:2005 +#: ../../uaclient/messages/__init__.py:2010 #, python-brace-format msgid "Unable to install Livepatch client: {error_msg}" msgstr "" -#: ../../uaclient/messages/__init__.py:2010 +#: ../../uaclient/messages/__init__.py:2015 #, python-brace-format msgid "\"{proxy}\" is not working. Not setting as proxy." msgstr "" -#: ../../uaclient/messages/__init__.py:2015 +#: ../../uaclient/messages/__init__.py:2020 #, python-brace-format msgid "\"{proxy}\" is not a valid url. Not setting as proxy." msgstr "" -#: ../../uaclient/messages/__init__.py:2021 +#: ../../uaclient/messages/__init__.py:2026 msgid "" "To use an HTTPS proxy for HTTPS connections, please install pycurl with `apt " "install python3-pycurl`" msgstr "" -#: ../../uaclient/messages/__init__.py:2027 +#: ../../uaclient/messages/__init__.py:2032 #, python-brace-format msgid "PycURL Error: {e}" msgstr "" -#: ../../uaclient/messages/__init__.py:2031 +#: ../../uaclient/messages/__init__.py:2036 msgid "Proxy authentication failed" msgstr "" -#: ../../uaclient/messages/__init__.py:2037 +#: ../../uaclient/messages/__init__.py:2042 #, python-brace-format msgid "" "Failed to connect to {url}\n" "{cause_error}\n" msgstr "" -#: ../../uaclient/messages/__init__.py:2045 +#: ../../uaclient/messages/__init__.py:2050 #, python-brace-format msgid "Error connecting to {url}: {code} {body}" msgstr "" -#: ../../uaclient/messages/__init__.py:2051 +#: ../../uaclient/messages/__init__.py:2056 #, python-brace-format msgid "" "Cannot {operation} unknown service '{invalid_service}'.\n" "{service_msg}" msgstr "" -#: ../../uaclient/messages/__init__.py:2060 +#: ../../uaclient/messages/__init__.py:2065 #, python-brace-format msgid "" "This machine is already attached to '{account_name}'\n" "To use a different subscription first run: sudo pro detach." msgstr "" -#: ../../uaclient/messages/__init__.py:2067 +#: ../../uaclient/messages/__init__.py:2072 #, python-brace-format msgid "Failed to attach machine. See {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:2074 +#: ../../uaclient/messages/__init__.py:2079 #, python-brace-format msgid "" "Error while reading {config_name}:\n" "{error}" msgstr "" -#: ../../uaclient/messages/__init__.py:2079 +#: ../../uaclient/messages/__init__.py:2084 #, python-brace-format msgid "Invalid token. See {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:2085 +#: ../../uaclient/messages/__init__.py:2090 #, python-brace-format msgid "" "Attach denied:\n" @@ -2710,7 +2710,7 @@ msgid "" "Visit {url} to manage contract tokens." msgstr "" -#: ../../uaclient/messages/__init__.py:2095 +#: ../../uaclient/messages/__init__.py:2100 #, python-brace-format msgid "" "Attach denied:\n" @@ -2718,7 +2718,7 @@ msgid "" "Visit {url} to manage contract tokens." msgstr "" -#: ../../uaclient/messages/__init__.py:2105 +#: ../../uaclient/messages/__init__.py:2110 #, python-brace-format msgid "" "Attach denied:\n" @@ -2726,41 +2726,41 @@ msgid "" "Visit {url} to manage contract tokens." msgstr "" -#: ../../uaclient/messages/__init__.py:2115 +#: ../../uaclient/messages/__init__.py:2120 #, python-brace-format msgid "Expired token or contract. To obtain a new token visit: {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:2122 +#: ../../uaclient/messages/__init__.py:2127 msgid "The magic attach token is already activated." msgstr "" -#: ../../uaclient/messages/__init__.py:2128 +#: ../../uaclient/messages/__init__.py:2133 msgid "The magic attach token is invalid, has expired or never existed" msgstr "" -#: ../../uaclient/messages/__init__.py:2134 +#: ../../uaclient/messages/__init__.py:2139 msgid "Service unavailable, please try again later." msgstr "" -#: ../../uaclient/messages/__init__.py:2139 +#: ../../uaclient/messages/__init__.py:2144 #, python-brace-format msgid "This attach flow does not support {param} with value: {value}" msgstr "" -#: ../../uaclient/messages/__init__.py:2145 +#: ../../uaclient/messages/__init__.py:2150 #, python-brace-format msgid "Ubuntu Pro server provided no aptURL directive for {entitlement_name}" msgstr "" -#: ../../uaclient/messages/__init__.py:2153 +#: ../../uaclient/messages/__init__.py:2158 #, python-brace-format msgid "" "This machine is not attached to an Ubuntu Pro subscription.\n" "See {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:2162 +#: ../../uaclient/messages/__init__.py:2167 #, python-brace-format msgid "" "To use '{{valid_service}}' you need an Ubuntu Pro subscription\n" @@ -2768,59 +2768,74 @@ msgid "" "See {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:2178 +#: ../../uaclient/messages/__init__.py:2183 #, python-brace-format msgid "could not find entitlement named \"{entitlement_name}\"" msgstr "" -#: ../../uaclient/messages/__init__.py:2183 +#: ../../uaclient/messages/__init__.py:2188 msgid "failed to enable some services" msgstr "" -#: ../../uaclient/messages/__init__.py:2189 +#: ../../uaclient/messages/__init__.py:2193 +#, python-brace-format +msgid "failed to enable {service}" +msgstr "" + +#: ../../uaclient/messages/__init__.py:2199 msgid "Failed to enable default services, check: sudo pro status" msgstr "" -#: ../../uaclient/messages/__init__.py:2197 +#: ../../uaclient/messages/__init__.py:2207 msgid "Something went wrong during the attach process. Check the logs." msgstr "" -#: ../../uaclient/messages/__init__.py:2205 +#: ../../uaclient/messages/__init__.py:2215 #, python-brace-format msgid "Ubuntu Pro server provided no aptKey directive for {entitlement_name}" msgstr "" -#: ../../uaclient/messages/__init__.py:2212 +#: ../../uaclient/messages/__init__.py:2222 #, python-brace-format msgid "Ubuntu Pro server provided no suites directive for {entitlement_name}" msgstr "" -#: ../../uaclient/messages/__init__.py:2219 +#: ../../uaclient/messages/__init__.py:2229 #, python-brace-format msgid "" "Cannot setup apt pin. Empty apt repo origin value for {entitlement_name}" msgstr "" -#: ../../uaclient/messages/__init__.py:2228 +#: ../../uaclient/messages/__init__.py:2238 #, python-brace-format msgid "Could not determine contract delta service type {orig} {new}" msgstr "" -#: ../../uaclient/messages/__init__.py:2232 +#: ../../uaclient/messages/__init__.py:2243 +#, python-brace-format +msgid "Could not enable {target} because {incompatible} is enabled" +msgstr "" + +#: ../../uaclient/messages/__init__.py:2248 +#, python-brace-format +msgid "Could not enable {target} because {required} is not enabled" +msgstr "" + +#: ../../uaclient/messages/__init__.py:2252 #, python-brace-format msgid "" "Error on Pro Image:\n" "{error_msg}" msgstr "" -#: ../../uaclient/messages/__init__.py:2238 +#: ../../uaclient/messages/__init__.py:2258 #, python-brace-format msgid "" "An error occurred while talking the the cloud metadata service: {code} - " "{body}" msgstr "" -#: ../../uaclient/messages/__init__.py:2245 +#: ../../uaclient/messages/__init__.py:2265 #, python-brace-format msgid "" "Failed to attach machine\n" @@ -2828,41 +2843,41 @@ msgid "" "For more information, see {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:2255 +#: ../../uaclient/messages/__init__.py:2275 #, python-brace-format msgid "No valid AWS IMDS endpoint discovered at addresses: {addresses}" msgstr "" -#: ../../uaclient/messages/__init__.py:2262 +#: ../../uaclient/messages/__init__.py:2282 msgid "Unable to determine cloud platform." msgstr "" -#: ../../uaclient/messages/__init__.py:2270 +#: ../../uaclient/messages/__init__.py:2290 #, python-brace-format msgid "" "Auto-attach image support is not available on this image\n" "See: {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:2279 +#: ../../uaclient/messages/__init__.py:2299 #, python-brace-format msgid "" "Auto-attach image support is not available on {{cloud_type}}\n" "See: {url}" msgstr "" -#: ../../uaclient/messages/__init__.py:2287 +#: ../../uaclient/messages/__init__.py:2307 #, python-brace-format msgid "{file_name} is not valid {file_format}" msgstr "" -#: ../../uaclient/messages/__init__.py:2293 +#: ../../uaclient/messages/__init__.py:2313 #, python-brace-format msgid "" "Could not parse /etc/os-release VERSION: {orig_ver} (modified to {mod_ver})" msgstr "" -#: ../../uaclient/messages/__init__.py:2301 +#: ../../uaclient/messages/__init__.py:2321 #, python-brace-format msgid "" "Could not extract series information from /etc/os-release.\n" @@ -2870,7 +2885,7 @@ msgid "" "and the VERSION_CODENAME information is not present" msgstr "" -#: ../../uaclient/messages/__init__.py:2311 +#: ../../uaclient/messages/__init__.py:2331 #, python-brace-format msgid "" "There is a corrupted lock file in the system. To continue, please remove it\n" @@ -2879,189 +2894,193 @@ msgid "" "$ sudo rm {lock_file_path}" msgstr "" -#: ../../uaclient/messages/__init__.py:2320 +#: ../../uaclient/messages/__init__.py:2340 #, python-brace-format msgid "{source} returned invalid json: {out}" msgstr "" -#: ../../uaclient/messages/__init__.py:2326 +#: ../../uaclient/messages/__init__.py:2346 #, python-brace-format msgid "" "Invalid value for {path_to_value} in /etc/ubuntu-advantage/uaclient.conf. " "Expected {expected_value}, found {value}." msgstr "" -#: ../../uaclient/messages/__init__.py:2335 +#: ../../uaclient/messages/__init__.py:2355 #, python-brace-format msgid "" "Cannot set {key} to {value}: for interval must be a positive integer." msgstr "" -#: ../../uaclient/messages/__init__.py:2342 +#: ../../uaclient/messages/__init__.py:2362 #, python-brace-format msgid "Invalid url in config. {key}: {value}" msgstr "" -#: ../../uaclient/messages/__init__.py:2347 +#: ../../uaclient/messages/__init__.py:2367 #, python-brace-format msgid "Could not find yaml file: {filepath}" msgstr "" -#: ../../uaclient/messages/__init__.py:2353 +#: ../../uaclient/messages/__init__.py:2373 msgid "" "Error: Setting global apt proxy and pro scoped apt proxy\n" "at the same time is unsupported.\n" "Cancelling config process operation.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:2363 +#: ../../uaclient/messages/__init__.py:2383 msgid "Can't load the distro-info database." msgstr "" -#: ../../uaclient/messages/__init__.py:2368 +#: ../../uaclient/messages/__init__.py:2388 #, python-brace-format msgid "Can't find series {series} in the distro-info database." msgstr "" -#: ../../uaclient/messages/__init__.py:2373 +#: ../../uaclient/messages/__init__.py:2393 #, python-brace-format msgid "Error: Cannot use {option1} together with {option2}." msgstr "" -#: ../../uaclient/messages/__init__.py:2377 +#: ../../uaclient/messages/__init__.py:2397 #, python-brace-format msgid "No help available for '{name}'" msgstr "" -#: ../../uaclient/messages/__init__.py:2383 +#: ../../uaclient/messages/__init__.py:2403 #, python-brace-format msgid "" "Error: issue \"{issue}\" is not recognized.\n" "Usage: \"pro fix CVE-yyyy-nnnn\" or \"pro fix USN-nnnn\"" msgstr "" -#: ../../uaclient/messages/__init__.py:2389 +#: ../../uaclient/messages/__init__.py:2409 #, python-brace-format msgid "{arg} must be one of: {choices}" msgstr "" -#: ../../uaclient/messages/__init__.py:2394 +#: ../../uaclient/messages/__init__.py:2414 #, python-brace-format msgid "Expected {expected} but found: {actual}" msgstr "" -#: ../../uaclient/messages/__init__.py:2398 +#: ../../uaclient/messages/__init__.py:2418 msgid "Unable to process uaclient.conf" msgstr "" -#: ../../uaclient/messages/__init__.py:2403 +#: ../../uaclient/messages/__init__.py:2423 msgid "Unable to refresh your subscription" msgstr "" -#: ../../uaclient/messages/__init__.py:2408 +#: ../../uaclient/messages/__init__.py:2428 msgid "Unable to update Ubuntu Pro related APT and MOTD messages." msgstr "" -#: ../../uaclient/messages/__init__.py:2414 +#: ../../uaclient/messages/__init__.py:2434 msgid "json formatted response requires --assume-yes flag." msgstr "" -#: ../../uaclient/messages/__init__.py:2422 +#: ../../uaclient/messages/__init__.py:2442 msgid "" "Do not pass the TOKEN arg if you are using --attach-config.\n" "Include the token in the attach-config file instead.\n" " " msgstr "" -#: ../../uaclient/messages/__init__.py:2431 +#: ../../uaclient/messages/__init__.py:2451 msgid "Cannot provide both --args and --data at the same time" msgstr "" -#: ../../uaclient/messages/__init__.py:2437 +#: ../../uaclient/messages/__init__.py:2457 #, python-brace-format msgid "Unable to perform: {lock_request}.\n" msgstr "" -#: ../../uaclient/messages/__init__.py:2446 +#: ../../uaclient/messages/__init__.py:2466 msgid "This command must be run as root (try using sudo)." msgstr "" -#: ../../uaclient/messages/__init__.py:2451 +#: ../../uaclient/messages/__init__.py:2471 #, python-brace-format msgid "Metadata for {issue} is invalid. Error: {error_msg}." msgstr "" -#: ../../uaclient/messages/__init__.py:2458 +#: ../../uaclient/messages/__init__.py:2478 #, python-brace-format msgid "Error: {issue_id} not found." msgstr "" -#: ../../uaclient/messages/__init__.py:2462 +#: ../../uaclient/messages/__init__.py:2482 #, python-brace-format msgid "GPG key '{keyfile}' not found." msgstr "" -#: ../../uaclient/messages/__init__.py:2467 +#: ../../uaclient/messages/__init__.py:2487 #, python-brace-format msgid "'{endpoint}' is not a valid endpoint" msgstr "" -#: ../../uaclient/messages/__init__.py:2472 +#: ../../uaclient/messages/__init__.py:2492 #, python-brace-format msgid "Missing argument '{arg}' for endpoint {endpoint}" msgstr "" -#: ../../uaclient/messages/__init__.py:2477 +#: ../../uaclient/messages/__init__.py:2497 #, python-brace-format msgid "{endpoint} accepts no arguments" msgstr "" -#: ../../uaclient/messages/__init__.py:2482 +#: ../../uaclient/messages/__init__.py:2502 #, python-brace-format msgid "" "Error parsing API json data parameter:\n" "{data}" msgstr "" -#: ../../uaclient/messages/__init__.py:2487 +#: ../../uaclient/messages/__init__.py:2507 #, python-brace-format msgid "'{arg}' is not formatted as 'key=value'" msgstr "" -#: ../../uaclient/messages/__init__.py:2492 +#: ../../uaclient/messages/__init__.py:2512 #, python-brace-format msgid "Unable to determine version: {error_msg}" msgstr "" -#: ../../uaclient/messages/__init__.py:2497 +#: ../../uaclient/messages/__init__.py:2517 msgid "features.disable_auto_attach set in config" msgstr "" -#: ../../uaclient/messages/__init__.py:2502 +#: ../../uaclient/messages/__init__.py:2522 #, python-brace-format msgid "Unable to determine unattended-upgrades status: {error_msg}" msgstr "" -#: ../../uaclient/messages/__init__.py:2508 +#: ../../uaclient/messages/__init__.py:2528 #, python-brace-format msgid "Expected value with type {expected_type} but got type: {got_type}" msgstr "" -#: ../../uaclient/messages/__init__.py:2514 +#: ../../uaclient/messages/__init__.py:2534 #, python-brace-format msgid "" "Got value with incorrect type at index {index}:\n" "{nested_msg}" msgstr "" -#: ../../uaclient/messages/__init__.py:2520 +#: ../../uaclient/messages/__init__.py:2540 #, python-brace-format msgid "" "Got value with incorrect type for field \"{key}\":\n" "{nested_msg}" msgstr "" -#: ../../uaclient/messages/__init__.py:2527 +#: ../../uaclient/messages/__init__.py:2547 #, python-brace-format msgid "Value provided was not found in {enum_class}'s allowed: value: {values}" msgstr "" + +#: ../../uaclient/messages/__init__.py:2558 +msgid "The operation is not supported" +msgstr "" diff --git a/features/api_enable.feature b/features/api_enable.feature new file mode 100644 index 0000000000..3388939802 --- /dev/null +++ b/features/api_enable.feature @@ -0,0 +1,209 @@ +Feature: u.pro.services.enable + + Scenario Outline: u.pro.services.enable.v1 container services + Given a `` `` machine with ubuntu-advantage-tools installed + When I apt update + And I apt install `jq` + + # Requires attach + When I verify that running `pro api u.pro.services.enable.v1 --args service=esm-infra` `with sudo` exits `1` + When I run shell command `pro api u.pro.services.enable.v1 --args service=esm-infra | jq .errors[0]` with sudo + Then I will see the following on stdout: + """ + { + "code": "unattached", + "meta": {}, + "title": "This machine is not attached to an Ubuntu Pro subscription.\nSee https://ubuntu.com/pro" + } + """ + + When I attach `contract_token` with sudo and options `--no-auto-enable` + + # Requires root + When I verify that running `pro api u.pro.services.enable.v1 --args service=esm-infra` `as non-root` exits `1` + When I run shell command `pro api u.pro.services.enable.v1 --args service=esm-infra | jq .errors[0]` as non-root + Then I will see the following on stdout: + """ + { + "code": "nonroot-user", + "meta": {}, + "title": "This command must be run as root (try using sudo)." + } + """ + + # Basic enable + When I run shell command `pro api u.pro.services.enable.v1 --args service=esm-infra | jq .data.attributes` with sudo + Then I will see the following on stdout: + """ + { + "disabled": [], + "enabled": [ + "esm-infra" + ], + "messages": [], + "reboot_required": false + } + """ + Then I verify that `esm-infra` is enabled + # Enable already enabled service succeeds + When I run shell command `pro api u.pro.services.enable.v1 --args service=esm-infra | jq .data.attributes` with sudo + Then I will see the following on stdout: + """ + { + "disabled": [], + "enabled": [], + "messages": [], + "reboot_required": false + } + """ + # Disallowing required services when required causes error + When I verify that running `pro api u.pro.services.enable.v1 --data '{"service": "ros", "enable_required_services": false}'` `with sudo` exits `1` + When I run shell command `pro api u.pro.services.enable.v1 --data \"{\\\"service\\\": \\\"ros\\\", \\\"enable_required_services\\\": false}\" | jq .errors[0]` with sudo + Then I will see the following on stdout: + """ + { + "code": "enable-blocked-required-service", + "meta": { + "required": "esm-apps", + "target": "ros" + }, + "title": "Could not enable ros because esm-apps is not enabled" + } + """ + # Default allows enabling required services + When I run shell command `pro api u.pro.services.enable.v1 --args service=ros | jq .data.attributes` with sudo + Then I will see the following on stdout: + """ + { + "disabled": [], + "enabled": [ + "esm-apps", + "ros" + ], + "messages": [], + "reboot_required": false + } + """ + # Access only works and post enable messages work + When I run shell command `pro api u.pro.services.enable.v1 --data \"{\\\"service\\\": \\\"cis\\\", \\\"access_only\\\": true}\" | jq .data.attributes` with sudo + Then I will see the following on stdout: + """ + { + "disabled": [], + "enabled": [ + "cis" + ], + "messages": [ + "Visit https://ubuntu.com/security/cis to learn how to use CIS" + ], + "reboot_required": false + } + """ + When I run `apt-cache policy usg-common` as non-root + Then stdout contains substring: + """ + Installed: (none) + """ + # Access only on service that doesn't support it fails + When I verify that running `pro api u.pro.services.enable.v1 --data '{"service": "ros-updates", "access_only": true}'` `with sudo` exits `1` + When I run shell command `pro api u.pro.services.enable.v1 --data \"{\\\"service\\\": \\\"ros-updates\\\", \\\"access_only\\\": true}\" | jq .errors[0]` with sudo + Then I will see the following on stdout: + """ + { + "code": "entitlement-not-enabled", + "meta": { + "reason": { + "code": "enable-access-only-not-supported", + "title": "ROS ESM All Updates does not support being enabled with --access-only" + } + }, + "title": "failed to enable ros-updates" + } + """ + Examples: + | release | machine_type | + | xenial | lxd-container | + | bionic | lxd-container | + + Scenario Outline: u.pro.services.enable.v1 landscape + Given a `` `` machine with ubuntu-advantage-tools installed + When I apt update + And I apt install `jq` + And I attach `contract_token` with sudo and options `--no-auto-enable` + + When I verify that running `pro api u.pro.services.enable.v1 --args service=landscape` `with sudo` exits `1` + And I run shell command `pro api u.pro.services.enable.v1 --args service=landscape | jq .errors[0]` with sudo + Then I will see the following on stdout: + """ + { + "code": "not-supported", + "meta": {}, + "title": "The operation is not supported" + } + """ + Examples: + | release | machine_type | + | mantic | lxd-container | + + Scenario Outline: u.pro.services.enable.v1 vm services + Given a `` `` machine with ubuntu-advantage-tools installed + When I apt update + And I apt install `jq` + And I attach `contract_token` with sudo and options `--no-auto-enable` + + # Basic enable + And I run shell command `pro api u.pro.services.enable.v1 --args service=livepatch | jq .data.attributes` with sudo + Then I will see the following on stdout: + """ + { + "disabled": [], + "enabled": [ + "livepatch" + ], + "messages": [], + "reboot_required": false + } + """ + # Enable without disable_incompatible fails + When I verify that running `pro api u.pro.services.enable.v1 --data '{"service": "realtime-kernel", "disable_incompatible_services": false}'` `with sudo` exits `1` + When I run shell command `pro api u.pro.services.enable.v1 --data \"{\\\"service\\\": \\\"realtime-kernel\\\", \\\"disable_incompatible_services\\\": false}\" | jq .errors[0]` with sudo + Then I will see the following on stdout: + """ + { + "code": "enable-blocked-incompatible-service", + "meta": { + "incompatible": "livepatch", + "target": "realtime-kernel" + }, + "title": "Could not enable realtime-kernel because livepatch is enabled" + } + """ + # Enable with disable_incompatible works and variant works + When I run shell command `pro api u.pro.services.enable.v1 --data \"{\\\"service\\\": \\\"realtime-kernel\\\", \\\"variant\\\": \\\"intel-iotg\\\"}\" | jq .data.attributes` with sudo + Then I will see the following on stdout: + """ + { + "disabled": [ + "livepatch" + ], + "enabled": [ + "realtime-kernel" + ], + "messages": [], + "reboot_required": true + } + """ + When I run shell command `pro api u.pro.status.enabled_services.v1 | jq ".data.attributes.enabled_services | select(.name==\"realtime-kernel\")" ` with sudo + Then I will see the following on stdout: + """ + [ + { + "name": "realtime-kernel", + "variant_enabled": true, + "variant_name": "intel-iotg" + } + ] + """ + Examples: + | release | machine_type | + | jammy | lxd-vm | diff --git a/uaclient/api/api.py b/uaclient/api/api.py index a03247aaec..785ee43413 100644 --- a/uaclient/api/api.py +++ b/uaclient/api/api.py @@ -24,6 +24,7 @@ "u.pro.security.fix.usn.plan.v1", "u.pro.security.status.livepatch_cves.v1", "u.pro.security.status.reboot_required.v1", + "u.pro.services.enable.v1", "u.pro.status.enabled_services.v1", "u.pro.status.is_attached.v1", "u.pro.version.v1", @@ -74,9 +75,6 @@ def _process_data( raise errors.APIJSONDataFormatError(data=data) for k, v in json_data.items(): - if not k or not v: - raise errors.APIBadArgsFormat(arg="{}:{}".format(k, v)) - if k not in fields: warnings.append( ErrorWarningObject( diff --git a/uaclient/api/exceptions.py b/uaclient/api/exceptions.py index 589c361d44..3799a6c79b 100644 --- a/uaclient/api/exceptions.py +++ b/uaclient/api/exceptions.py @@ -4,12 +4,17 @@ AlreadyAttachedError, ConnectivityError, ContractAPIError, + EnableBlockedByIncompatibleService, + EnableBlockedByRequiredService, + EntitlementNotEnabledError, EntitlementNotFoundError, EntitlementsNotEnabledError, InvalidProImage, LockHeldError, NonAutoAttachImageError, + NonRootUserError, UbuntuProError, + UnattachedError, UrlError, UserFacingError, ) @@ -22,10 +27,15 @@ "InvalidProImage", "LockHeldError", "NonAutoAttachImageError", + "NonRootUserError", "UbuntuProError", + "UnattachedError", "UrlError", "UserFacingError", "EntitlementsNotEnabledError", + "EntitlementNotEnabledError", + "EnableBlockedByIncompatibleService", + "EnableBlockedByRequiredService", ] @@ -35,3 +45,7 @@ class AutoAttachDisabledError(UbuntuProError): class UnattendedUpgradesError(APIError): _formatted_msg = messages.E_UNATTENDED_UPGRADES_ERROR + + +class NotSupported(UbuntuProError): + _msg = messages.E_NOT_SUPPORTED diff --git a/uaclient/api/tests/test_api_u_pro_services_enable_v1.py b/uaclient/api/tests/test_api_u_pro_services_enable_v1.py new file mode 100644 index 0000000000..5efe708358 --- /dev/null +++ b/uaclient/api/tests/test_api_u_pro_services_enable_v1.py @@ -0,0 +1,205 @@ +import mock +import pytest + +from uaclient.api import exceptions +from uaclient.api.u.pro.services.enable.v1 import ( + EnableOptions, + EnableResult, + _enable, +) +from uaclient.testing.helpers import does_not_raise + +M_PATH = "uaclient.api.u.pro.services.enable.v1." + + +class TestEnable: + @pytest.mark.parametrize( + [ + "options", + "is_attached", + "enabled_services_names_before", + "enabled_services_names_after", + "enable_result", + "messaging_ops", + "reboot_required", + "expected_raises", + "expected_result", + ], + [ + ( + EnableOptions(service="s1"), + False, + None, + None, + None, + None, + None, + pytest.raises(exceptions.UnattachedError), + None, + ), + ( + EnableOptions(service="s1"), + True, + [], + None, + (False, None), + None, + None, + pytest.raises(exceptions.EntitlementNotEnabledError), + None, + ), + ( + EnableOptions(service="s1"), + True, + [], + ["s1"], + (True, None), + {}, + False, + does_not_raise(), + EnableResult( + enabled=["s1"], + disabled=[], + reboot_required=False, + messages=[], + ), + ), + ( + EnableOptions(service="s1"), + True, + [], + ["s1"], + (True, None), + {}, + True, + does_not_raise(), + EnableResult( + enabled=["s1"], + disabled=[], + reboot_required=True, + messages=[], + ), + ), + ( + EnableOptions(service="s1"), + True, + [], + ["s1"], + (True, None), + {"post_enable": ["one", "two", 3]}, + False, + does_not_raise(), + EnableResult( + enabled=["s1"], + disabled=[], + reboot_required=False, + messages=["one", "two"], + ), + ), + ( + EnableOptions(service="s1"), + True, + ["s2"], + ["s1", "s3"], + (True, None), + {}, + False, + does_not_raise(), + EnableResult( + enabled=["s1", "s3"], + disabled=["s2"], + reboot_required=False, + messages=[], + ), + ), + ( + EnableOptions( + service="s1", + enable_required_services=False, + disable_incompatible_services=False, + access_only=True, + ), + True, + [], + ["s1"], + (True, None), + {}, + False, + does_not_raise(), + EnableResult( + enabled=["s1"], + disabled=[], + reboot_required=False, + messages=[], + ), + ), + ], + ) + @mock.patch(M_PATH + "lock.clear_lock_file_if_present") + @mock.patch(M_PATH + "lock.SpinLock") + @mock.patch(M_PATH + "entitlements.entitlement_factory") + @mock.patch(M_PATH + "_enabled_services_names") + @mock.patch(M_PATH + "_is_attached") + @mock.patch(M_PATH + "event.set_event_mode") + def test_enable( + self, + m_set_event_mode, + m_is_attached, + m_enabled_services_names, + m_entitlement_factory, + m_spin_lock, + m_clear_lock_file_if_present, + options, + is_attached, + enabled_services_names_before, + enabled_services_names_after, + enable_result, + messaging_ops, + reboot_required, + expected_raises, + expected_result, + FakeConfig, + ): + m_is_attached.return_value = mock.MagicMock(is_attached=is_attached) + m_enabled_services_names.side_effect = [ + enabled_services_names_before, + enabled_services_names_after, + ] + m_ent_class = m_entitlement_factory.return_value + m_ent = m_ent_class.return_value + m_ent.enable.return_value = enable_result + m_ent.messaging = messaging_ops + m_ent._check_for_reboot.return_value = reboot_required + + cfg = FakeConfig() + + actual_result = None + with expected_raises: + actual_result = _enable(options, cfg) + + assert actual_result == expected_result + + if expected_result is not None: + assert m_entitlement_factory.call_args_list == [ + mock.call( + cfg=cfg, + name=options.service, + variant=options.variant or "", + ) + ] + assert m_ent_class.call_args_list == [ + mock.call( + cfg, + assume_yes=True, + allow_beta=True, + called_name=options.service, + access_only=options.access_only, + ) + ] + assert m_ent.enable.call_args_list == [ + mock.call( + enable_required_services=options.enable_required_services, + disable_incompatible_services=options.disable_incompatible_services, # noqa: E501 + api=True, + ) + ] diff --git a/uaclient/api/u/pro/services/__init__.py b/uaclient/api/u/pro/services/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/uaclient/api/u/pro/services/enable/__init__.py b/uaclient/api/u/pro/services/enable/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/uaclient/api/u/pro/services/enable/v1.py b/uaclient/api/u/pro/services/enable/v1.py new file mode 100644 index 0000000000..05abc3c45f --- /dev/null +++ b/uaclient/api/u/pro/services/enable/v1.py @@ -0,0 +1,173 @@ +import logging +from typing import List, Optional + +from uaclient import entitlements, event_logger, lock, messages, util +from uaclient.api import exceptions +from uaclient.api.api import APIEndpoint +from uaclient.api.data_types import AdditionalInfo +from uaclient.api.u.pro.status.enabled_services.v1 import _enabled_services +from uaclient.api.u.pro.status.is_attached.v1 import _is_attached +from uaclient.config import UAConfig +from uaclient.data_types import ( + BoolDataValue, + DataObject, + Field, + StringDataValue, + data_list, +) + +event = event_logger.get_event_logger() +LOG = logging.getLogger(util.replace_top_level_logger_name(__name__)) + + +class EnableOptions(DataObject): + fields = [ + Field("service", StringDataValue), + Field("variant", StringDataValue, False), + Field("enable_required_services", BoolDataValue, False), + Field("disable_incompatible_services", BoolDataValue, False), + Field("access_only", BoolDataValue, False), + ] + + def __init__( + self, + *, + service: str, + variant: Optional[str] = None, + enable_required_services: bool = True, + disable_incompatible_services: bool = True, + access_only: bool = False + ): + self.service = service + self.variant = variant + self.enable_required_services = enable_required_services + self.disable_incompatible_services = disable_incompatible_services + self.access_only = access_only + + +class EnableResult(DataObject, AdditionalInfo): + fields = [ + Field("enabled", data_list(StringDataValue)), + Field("disabled", data_list(StringDataValue)), + Field("reboot_required", BoolDataValue), + Field("messages", data_list(StringDataValue)), + ] + + def __init__( + self, + *, + enabled: List[str], + disabled: List[str], + reboot_required: bool, + messages: List[str] + ): + self.enabled = enabled + self.disabled = disabled + self.reboot_required = reboot_required + self.messages = messages + + +def _enabled_services_names(cfg: UAConfig) -> List[str]: + return [s.name for s in _enabled_services(cfg).enabled_services] + + +def enable(options: EnableOptions) -> EnableResult: + return _enable(options, UAConfig()) + + +def _enable( + options: EnableOptions, + cfg: UAConfig, +) -> EnableResult: + event.set_event_mode(event_logger.EventLoggerMode.JSON) + + if not util.we_are_currently_root(): + raise exceptions.NonRootUserError() + + if not _is_attached(cfg).is_attached: + raise exceptions.UnattachedError() + + if options.service == "landscape": + raise exceptions.NotSupported() + + enabled_services_before = _enabled_services_names(cfg) + if options.service in enabled_services_before: + # nothing to do + return EnableResult( + enabled=[], + disabled=[], + reboot_required=False, + messages=[], + ) + + ent_cls = entitlements.entitlement_factory( + cfg=cfg, name=options.service, variant=options.variant or "" + ) + entitlement = ent_cls( + cfg, + assume_yes=True, + allow_beta=True, + called_name=options.service, + access_only=options.access_only, + ) + + success = False + fail_reason = None + + try: + with lock.SpinLock( + cfg=cfg, + lock_holder="u.pro.services.enable.v1", + ): + success, fail_reason = entitlement.enable( + enable_required_services=options.enable_required_services, + disable_incompatible_services=options.disable_incompatible_services, # noqa: E501 + api=True, + ) + except Exception as e: + lock.clear_lock_file_if_present() + raise e + + if not success: + if fail_reason is not None: + reason = fail_reason.message + else: + reason = messages.GENERIC_UNKNOWN_ISSUE + raise exceptions.EntitlementNotEnabledError( + service=options.service, reason=reason + ) + + enabled_services_after = _enabled_services_names(cfg) + + post_enable_messages = [ + msg + for msg in entitlement.messaging.get("post_enable", []) + if isinstance(msg, str) + ] + + return EnableResult( + enabled=sorted( + list( + set(enabled_services_after).difference( + set(enabled_services_before) + ) + ) + ), + disabled=sorted( + list( + set(enabled_services_before).difference( + set(enabled_services_after) + ) + ) + ), + reboot_required=entitlement._check_for_reboot(), + messages=post_enable_messages, + ) + + +endpoint = APIEndpoint( + version="v1", + name="EnableService", + fn=_enable, + options_cls=EnableOptions, +) diff --git a/uaclient/daemon/retry_auto_attach.py b/uaclient/daemon/retry_auto_attach.py index 3960cf9de6..e6fe96a582 100644 --- a/uaclient/daemon/retry_auto_attach.py +++ b/uaclient/daemon/retry_auto_attach.py @@ -59,7 +59,7 @@ def full_auto_attach_exception_to_failure_reason(e: Exception) -> str: return '"{}"'.format(e.msg) else: LOG.error("Unexpected exception", exc_info=e) - return str(e) or messages.RETRY_ERROR_DETAIL_UNKNOWN + return str(e) or messages.UNKNOWN_ERROR def cleanup(cfg: UAConfig): @@ -103,7 +103,7 @@ def retry_auto_attach(cfg: UAConfig) -> None: ) msg_reason = failure_reason if msg_reason is None: - msg_reason = messages.RETRY_ERROR_DETAIL_UNKNOWN + msg_reason = messages.UNKNOWN_ERROR try: next_attempt = next_attempt.astimezone() except Exception: @@ -169,7 +169,7 @@ def retry_auto_attach(cfg: UAConfig) -> None: ) msg_reason = failure_reason if msg_reason is None: - msg_reason = messages.RETRY_ERROR_DETAIL_UNKNOWN + msg_reason = messages.UNKNOWN_ERROR auto_attach_status_msg = ( messages.AUTO_ATTACH_RETRY_TOTAL_FAILURE_NOTICE.format( num_attempts=len(RETRY_INTERVALS) + 1, reason=msg_reason diff --git a/uaclient/entitlements/base.py b/uaclient/entitlements/base.py index 15ab765ccf..d42e03a5dd 100644 --- a/uaclient/entitlements/base.py +++ b/uaclient/entitlements/base.py @@ -413,9 +413,17 @@ def can_enable(self) -> Tuple[bool, Optional[CanEnableFailure]]: # using Union instead of Optional here to signal that it may expand to # support additional reason types in the future. + # TODO: currently enable is a function designed for use in the CLI + # and we are re-using it for the API by passing through an `api` flag. + # This will be inverted in the next release by redesigning this function + # to be used by the API (including potentially breaking it into smaller + # functions). Then the CLI enable will be refactored to use the API. def enable( self, silent: bool = False, + enable_required_services: Optional[bool] = None, + disable_incompatible_services: Optional[bool] = None, + api: bool = False, ) -> Tuple[bool, Union[None, CanEnableFailure]]: """Enable specific entitlement. @@ -426,9 +434,15 @@ def enable( include other types of reasons in the future. """ - msg_ops = self.messaging.get("pre_can_enable", []) - if not util.handle_message_operations(msg_ops): - return False, None + if enable_required_services is None: + enable_required_services = self.assume_yes + if disable_incompatible_services is None: + disable_incompatible_services = self.assume_yes + + if not api: + msg_ops = self.messaging.get("pre_can_enable", []) + if not util.handle_message_operations(msg_ops): + return False, None can_enable, fail = self.can_enable() if not can_enable: @@ -437,7 +451,9 @@ def enable( return False, None elif fail.reason == CanEnableFailureReason.INCOMPATIBLE_SERVICE: # Try to disable those services before proceeding with enable - incompat_ret, error = self.handle_incompatible_services() + incompat_ret, error = self.handle_incompatible_services( + api=api, allow=disable_incompatible_services + ) if not incompat_ret: fail.message = error return False, fail @@ -446,7 +462,9 @@ def enable( == CanEnableFailureReason.INACTIVE_REQUIRED_SERVICES ): # Try to enable those services before proceeding with enable - req_ret, error = self._enable_required_services() + req_ret, error = self._enable_required_services( + api=api, allow=enable_required_services + ) if not req_ret: fail.message = error return False, fail @@ -454,9 +472,10 @@ def enable( # every other reason means we can't continue return False, fail - msg_ops = self.messaging.get("pre_enable", []) - if not util.handle_message_operations(msg_ops): - return False, None + if not api: + msg_ops = self.messaging.get("pre_enable", []) + if not util.handle_message_operations(msg_ops): + return False, None # TODO: Move all logic from RepoEntitlement that # handles the additionalPackages and APT directives @@ -472,9 +491,10 @@ def enable( if not ret: return False, None - msg_ops = self.messaging.get("post_enable", []) - if not util.handle_message_operations(msg_ops): - return False, None + if not api: + msg_ops = self.messaging.get("post_enable", []) + if not util.handle_message_operations(msg_ops): + return False, None return True, None @@ -703,6 +723,8 @@ def detect_incompatible_services(self) -> bool: def handle_incompatible_services( self, + api: bool, + allow: bool, ) -> Tuple[bool, Optional[messages.NamedMessage]]: """ Prompt user when incompatible services are found during enable. @@ -738,8 +760,13 @@ def handle_incompatible_services( if cfg_block_disable_on_enable: return False, e_msg + if api and not allow: + raise exceptions.EnableBlockedByIncompatibleService( + target=self.name, incompatible=ent.name + ) + if not util.prompt_for_confirmation( - msg=user_msg, assume_yes=self.assume_yes + msg=user_msg, assume_yes=allow ): return False, e_msg @@ -757,6 +784,8 @@ def handle_incompatible_services( def _enable_required_services( self, + api: bool, + allow: bool, ) -> Tuple[bool, Optional[messages.NamedMessage]]: """ Prompt user when required services are found during enable. @@ -783,8 +812,13 @@ def _enable_required_services( required_service=ent.title, ) + if api and not allow: + raise exceptions.EnableBlockedByRequiredService( + target=self.name, required=ent.name + ) + if not util.prompt_for_confirmation( - msg=user_msg, assume_yes=self.assume_yes + msg=user_msg, assume_yes=allow ): return False, e_msg diff --git a/uaclient/exceptions.py b/uaclient/exceptions.py index 9a250a974c..fac9685db0 100644 --- a/uaclient/exceptions.py +++ b/uaclient/exceptions.py @@ -351,6 +351,18 @@ def __init__( ) +class EntitlementNotEnabledError(UbuntuProError): + _formatted_msg = messages.E_ENTITLEMENT_NOT_ENABLED_ERROR + + def __init__(self, service: str, reason: messages.NamedMessage): + super().__init__( + service=service, + additional_info={ + "reason": {"code": reason.name, "title": reason.msg} + }, + ) + + class AttachFailureDefaultServices(EntitlementsNotEnabledError): _msg = messages.E_ATTACH_FAILURE_DEFAULT_SERVICES @@ -375,6 +387,14 @@ class InvalidContractDeltasServiceType(UbuntuProError): _formatted_msg = messages.E_INVALID_CONTRACT_DELTAS_SERVICE_TYPE +class EnableBlockedByIncompatibleService(UbuntuProError): + _formatted_msg = messages.E_ENABLE_BLOCKED_BY_INCOMPATIBLE_SERVICE + + +class EnableBlockedByRequiredService(UbuntuProError): + _formatted_msg = messages.E_ENABLE_BLOCKED_BY_REQUIRED_SERVICE + + ############################################################################### # CLOUD PRO # ############################################################################### diff --git a/uaclient/messages/__init__.py b/uaclient/messages/__init__.py index 39eb54f9e6..fa86b64ec2 100644 --- a/uaclient/messages/__init__.py +++ b/uaclient/messages/__init__.py @@ -109,6 +109,7 @@ class TxtColor: ) ) +UNKNOWN_ERROR = t.gettext("an unknown error") ############################################################################### # GENERIC SYSTEM OPERATIONS # @@ -411,7 +412,6 @@ class TxtColor: ) RETRY_ERROR_DETAIL_CONNECTIVITY_ERROR = t.gettext("a connectivity error") RETRY_ERROR_DETAIL_URL_ERROR_URL = t.gettext("an error while reaching {url}") -RETRY_ERROR_DETAIL_UNKNOWN = t.gettext("an unknown error") # These are related messages but actually occur during a "refresh" DISABLE_DURING_CONTRACT_REFRESH = t.gettext( @@ -1913,6 +1913,11 @@ def __repr__(self): ), ) +GENERIC_UNKNOWN_ISSUE = NamedMessage( + "unknown-issue", + UNKNOWN_ERROR, +) + ############################################################################### # ERROR MESSAGES # @@ -2183,6 +2188,11 @@ def __repr__(self): t.gettext("failed to enable some services"), ) +E_ENTITLEMENT_NOT_ENABLED_ERROR = FormattedNamedMessage( + "entitlement-not-enabled", + t.gettext("failed to enable {service}"), +) + E_ATTACH_FAILURE_DEFAULT_SERVICES = NamedMessage( "attach-failure-default-service", t.gettext( @@ -2228,6 +2238,16 @@ def __repr__(self): t.gettext("Could not determine contract delta service type {orig} {new}"), ) +E_ENABLE_BLOCKED_BY_INCOMPATIBLE_SERVICE = FormattedNamedMessage( + "enable-blocked-incompatible-service", + t.gettext("Could not enable {target} because {incompatible} is enabled"), +) + +E_ENABLE_BLOCKED_BY_REQUIRED_SERVICE = FormattedNamedMessage( + "enable-blocked-required-service", + t.gettext("Could not enable {target} because {required} is not enabled"), +) + E_INVALID_PRO_IMAGE = FormattedNamedMessage( name="invalid-pro-image", msg=t.gettext("Error on Pro Image:\n{error_msg}") ) @@ -2532,3 +2552,8 @@ def __repr__(self): E_PYCURL_CA_CERTIFICATES = NamedMessage( "pycurl-ca-certificates-error", "Problem reading SSL CA certificates" ) + +E_NOT_SUPPORTED = NamedMessage( + "not-supported", + t.gettext("The operation is not supported"), +)