Merge ~orndorffgrant/ubuntu/+source/ubuntu-advantage-tools:upload-27.7-jammy into ubuntu/+source/ubuntu-advantage-tools:ubuntu/devel

Proposed by Grant Orndorff
Status: Needs review
Proposed branch: ~orndorffgrant/ubuntu/+source/ubuntu-advantage-tools:upload-27.7-jammy
Merge into: ubuntu/+source/ubuntu-advantage-tools:ubuntu/devel
Diff against target: 17891 lines (+8036/-2570)
109 files modified
Jenkinsfile (+36/-0)
Makefile (+1/-0)
README.md (+27/-6)
RELEASES.md (+14/-22)
apt-hook/json-hook-src/json-hook_test.go (+40/-40)
debian/changelog (+37/-0)
debian/ubuntu-advantage-tools.logrotate (+1/-0)
debian/ubuntu-advantage-tools.postinst (+7/-8)
features/_version.feature (+2/-2)
features/attach_invalidtoken.feature (+6/-2)
features/attach_validtoken.feature (+150/-8)
features/attached_commands.feature (+97/-73)
features/attached_enable.feature (+85/-22)
features/attached_status.feature (+2/-13)
features/aws-ids.yaml (+5/-3)
features/azure-ids.yaml (+2/-0)
features/cloud.py (+6/-3)
features/detached_auto_attach.feature (+32/-0)
features/enable_fips_container.feature (+144/-0)
features/enable_fips_vm.feature (+60/-1)
features/install_uninstall.feature (+0/-1)
features/license_check.feature (+3/-3)
features/proxy_config.feature (+24/-5)
features/schemas/ua_operation.json (+76/-0)
features/schemas/ua_security_status.json (+81/-0)
features/schemas/ua_status.json (+247/-0)
features/steps/steps.py (+138/-35)
features/ubuntu_pro.feature (+104/-9)
features/ubuntu_pro_fips.feature (+210/-0)
features/ubuntu_upgrade.feature (+3/-3)
features/ubuntu_upgrade_unattached.feature (+3/-3)
features/unattached_commands.feature (+75/-23)
features/unattached_status.feature (+100/-48)
features/util.py (+7/-0)
integration-requirements.txt (+1/-0)
lib/reboot_cmds.py (+11/-12)
setup.py (+3/-11)
sru/release-27.7/test_world_readable_logs.sh (+66/-0)
tools/create-lp-release-branches.sh (+1/-2)
tools/refresh-aws-pro-ids (+23/-12)
tools/run-integration-tests.py (+3/-4)
tox.ini (+14/-8)
uaclient/actions.py (+60/-10)
uaclient/apt.py (+70/-19)
uaclient/cli.py (+345/-162)
uaclient/clouds/gcp.py (+1/-0)
uaclient/clouds/identity.py (+2/-2)
uaclient/clouds/tests/test_aws.py (+4/-2)
uaclient/clouds/tests/test_identity.py (+9/-11)
uaclient/config.py (+119/-58)
uaclient/conftest.py (+25/-0)
uaclient/contract.py (+97/-109)
uaclient/data_types.py (+196/-0)
uaclient/defaults.py (+1/-0)
uaclient/entitlements/__init__.py (+7/-1)
uaclient/entitlements/base.py (+246/-132)
uaclient/entitlements/cc.py (+2/-3)
uaclient/entitlements/cis.py (+4/-3)
uaclient/entitlements/esm.py (+7/-4)
uaclient/entitlements/fips.py (+148/-62)
uaclient/entitlements/livepatch.py (+54/-36)
uaclient/entitlements/repo.py (+53/-38)
uaclient/entitlements/tests/test_base.py (+247/-39)
uaclient/entitlements/tests/test_cc.py (+2/-2)
uaclient/entitlements/tests/test_entitlements.py (+3/-2)
uaclient/entitlements/tests/test_esm.py (+10/-8)
uaclient/entitlements/tests/test_fips.py (+163/-57)
uaclient/entitlements/tests/test_livepatch.py (+43/-36)
uaclient/entitlements/tests/test_repo.py (+26/-32)
uaclient/event_logger.py (+221/-0)
uaclient/exceptions.py (+251/-26)
uaclient/jobs/metering.py (+2/-9)
uaclient/jobs/update_messaging.py (+21/-22)
uaclient/messages.py (+674/-0)
uaclient/security.py (+123/-86)
uaclient/security_status.py (+55/-34)
uaclient/serviceclient.py (+4/-4)
uaclient/snap.py (+5/-3)
uaclient/status.py (+38/-395)
uaclient/testing/fakes.py (+17/-0)
uaclient/tests/test_actions.py (+15/-15)
uaclient/tests/test_apt.py (+11/-14)
uaclient/tests/test_cli.py (+11/-11)
uaclient/tests/test_cli_attach.py (+413/-27)
uaclient/tests/test_cli_auto_attach.py (+7/-7)
uaclient/tests/test_cli_detach.py (+170/-24)
uaclient/tests/test_cli_disable.py (+310/-47)
uaclient/tests/test_cli_enable.py (+406/-83)
uaclient/tests/test_cli_refresh.py (+10/-10)
uaclient/tests/test_cli_security_status.py (+2/-27)
uaclient/tests/test_cli_status.py (+167/-13)
uaclient/tests/test_config.py (+79/-15)
uaclient/tests/test_contract.py (+93/-93)
uaclient/tests/test_data_types.py (+386/-0)
uaclient/tests/test_event_logger.py (+141/-0)
uaclient/tests/test_gpg.py (+9/-6)
uaclient/tests/test_lock.py (+3/-3)
uaclient/tests/test_reboot_cmds.py (+3/-3)
uaclient/tests/test_security.py (+303/-215)
uaclient/tests/test_security_status.py (+79/-19)
uaclient/tests/test_serviceclient.py (+4/-4)
uaclient/tests/test_snap.py (+7/-4)
uaclient/tests/test_update_messaging.py (+22/-36)
uaclient/tests/test_util.py (+37/-30)
uaclient/tests/test_version.py (+2/-2)
uaclient/types.py (+7/-2)
uaclient/util.py (+23/-67)
uaclient/version.py (+3/-3)
ubuntu-advantage.1 (+11/-1)
Reviewer Review Type Date Requested Status
Paride Legovini (community) Approve
Canonical Server Core Reviewers Pending
Review via email: mp+416678@code.launchpad.net

Description of the change

This is release 27.7 of ubuntu-advantage-tools.

Please review the FFe request here: https://bugs.launchpad.net/ubuntu/+source/ubuntu-advantage-tools/+bug/1964134

And the SRU bug here: https://bugs.launchpad.net/ubuntu/+source/ubuntu-advantage-tools/+bug/1964028

To post a comment you must log in.
bc721e4... by Renan Rodrigo

gcp: add pro license for Jammy

Signed-off-by: Renan Rodrigo <email address hidden>

Revision history for this message
Paride Legovini (paride) wrote :

Looking at this as packaging for a normal new upstream release: this is fine. Packaging changes are small, the only delicate bit is the handling of the log file permissions, which is now partially handled by logrotate. If logrotate isn't installed the logs will stay non-user-readable, which is a good compromise. Lintian complains hard about E: missing-build-dependency-for-dh-addon, but the dh-systemd B-D is present as an "OR" dependency, which lintian doesn't resolve, so this is a false positive. We may want to override this, but it's not a blocker for uploading. The other Lintian warning are due to deliberate choices.

Looking at this upload from the FFe/SRU perspective: this also looks good to me. More specifically the changes are

 - Bug fixes;
 - New functionality, extending existing functionality
   in a non-breaking way;
 - Documentation changes.

A binary debdiff shows that ubuntu-advantage-tools_27.7~22.04.1_amd64.deb ships the following new files wrt 27.6~22.04.1:

Files in second .deb but not in first
-------------------------------------
-rw-r--r-- root/root /usr/lib/python3/dist-packages/uaclient/data_types.py
-rw-r--r-- root/root /usr/lib/python3/dist-packages/uaclient/event_logger.py
-rw-r--r-- root/root /usr/lib/python3/dist-packages/uaclient/messages.py

These are expected and part of the aforementioned new functionality.

I think this is ready for the "second pass" review from a SRU team member.

review: Approve
Revision history for this message
Lucas Albuquerque Medeiros de Moura (lamoura) wrote :

We have found an issue in the current release. We have addressed this problem on this MR:
https://code.launchpad.net/~lamoura/ubuntu/+source/ubuntu-advantage-tools/+git/ubuntu-advantage-tools/+merge/417348

Please do any type of review there instead

Unmerged commits

bc721e4... by Renan Rodrigo

gcp: add pro license for Jammy

Signed-off-by: Renan Rodrigo <email address hidden>

9433db4... by Grant Orndorff

changelog: fix trailer line for 27.4.1

cd08064... by Lucas Albuquerque Medeiros de Moura

fix ubuntu upgrade tests for jammy

5b135d4... by Lucas Albuquerque Medeiros de Moura

fix integration test failures

feff165... by Grant Orndorff

release 27.7 changelog entry

68ca8e6... by Grant Orndorff

chore: bump version to 27.7

cf59d1d... by Renan Rodrigo

chore: bump security-status schema version to 0.1

Signed-off-by: Renan Rodrigo <email address hidden>

7db6f2d... by Renan Rodrigo

cli: include the origin site for security-status updates

Signed-off-by: Renan Rodrigo <email address hidden>

8eb6694... by Renan Rodrigo

cli: include installed ESM package counts in security-status

Signed-off-by: Renan Rodrigo <email address hidden>

90d18b1... by Renan Rodrigo

cli: remove beta flag from the security-status command

Signed-off-by: Renan Rodrigo <email address hidden>

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/Jenkinsfile b/Jenkinsfile
2index f9ca868..9c71624 100644
3--- a/Jenkinsfile
4+++ b/Jenkinsfile
5@@ -141,6 +141,27 @@ pipeline {
6 '''
7 }
8 }
9+ stage ('Package build: 22.04') {
10+ environment {
11+ BUILD_SERIES = "jammy"
12+ SERIES_VERSION = "22.04"
13+ PKG_VERSION = sh(returnStdout: true, script: "dpkg-parsechangelog --show-field Version").trim()
14+ NEW_PKG_VERSION = "${PKG_VERSION}~${SERIES_VERSION}~${JOB_SUFFIX}"
15+ ARTIFACT_DIR = "${TMPDIR}${BUILD_SERIES}"
16+ }
17+ steps {
18+ sh '''
19+ set -x
20+ mkdir ${ARTIFACT_DIR}
21+ cp debian/changelog ${WORKSPACE}/debian/changelog-${SERIES_VERSION}
22+ sed -i "s/${PKG_VERSION}/${NEW_PKG_VERSION}/" ${WORKSPACE}/debian/changelog-${SERIES_VERSION}
23+ dpkg-source -l${WORKSPACE}/debian/changelog-${SERIES_VERSION} -b .
24+ sbuild --resolve-alternatives --nolog --verbose --dist=${BUILD_SERIES} --no-run-lintian --append-to-version=~${SERIES_VERSION} ../ubuntu-advantage-tools*${NEW_PKG_VERSION}*dsc
25+ cp ./ubuntu-advantage-tools*${SERIES_VERSION}*.deb ${ARTIFACT_DIR}/ubuntu-advantage-tools-${BUILD_SERIES}.deb
26+ cp ./ubuntu-advantage-pro*${SERIES_VERSION}*.deb ${ARTIFACT_DIR}/ubuntu-advantage-pro-${BUILD_SERIES}.deb
27+ '''
28+ }
29+ }
30 }
31 }
32 stage ('Integration Tests') {
33@@ -190,6 +211,21 @@ pipeline {
34 '''
35 }
36 }
37+ stage("lxc 22.04") {
38+ environment {
39+ UACLIENT_BEHAVE_DEBS_PATH = "${TMPDIR}jammy/"
40+ UACLIENT_BEHAVE_ARTIFACT_DIR = "artifacts/behave-lxd-22.04"
41+ UACLIENT_BEHAVE_EPHEMERAL_INSTANCE = 1
42+ UACLIENT_BEHAVE_SNAPSHOT_STRATEGY = 1
43+ }
44+ steps {
45+ sh '''
46+ set +x
47+ . $TMPDIR/bin/activate
48+ tox --parallel--safe-build -e behave-lxd-22.04 -- --tags="~slow"
49+ '''
50+ }
51+ }
52 stage("lxc vm 20.04") {
53 environment {
54 UACLIENT_BEHAVE_DEBS_PATH = "${TMPDIR}focal/"
55diff --git a/Makefile b/Makefile
56index e8ce957..87932a8 100644
57--- a/Makefile
58+++ b/Makefile
59@@ -33,6 +33,7 @@ ifneq (,$(findstring trusty,$(TOXENV)))
60 endif
61 pip install tox
62 pip install tox-pip-version
63+ pip install tox-setuptools-version
64
65 travis-deb-install:
66 git fetch --unshallow
67diff --git a/README.md b/README.md
68index a9040e2..8d413c5 100644
69--- a/README.md
70+++ b/README.md
71@@ -33,7 +33,7 @@ Below is a list of platforms and releases ubuntu-advantage-tools supports
72 | Bionic | amd64, arm64, armhf, i386, ppc64el, s390x | Active SRU of all features |
73 | Focal | amd64, arm64, armhf, ppc64el, riscv64, s390x | Active SRU of all features |
74 | Groovy | amd64, arm64, armhf, ppc64el, riscv64, s390x | Last release 27.1 |
75-| Hirsute | amd64, arm64, armhf, ppc64el, riscv64, s390x | Active SRU of all features |
76+| Hirsute | amd64, arm64, armhf, ppc64el, riscv64, s390x | Last release 27.5 |
77 | Impish | amd64, arm64, armhf, ppc64el, riscv64, s390x | Active SRU of all features |
78
79 Note: ppc64el will not have support for APT JSON hook messaging due to insufficient golang packages
80@@ -107,6 +107,27 @@ https://ubuntu.com/advantage.
81 * UA client auto-enables any services defined with
82 `obligations:{enableByDefault: true}`
83
84+#### Attaching with --attach-config
85+Running `ua attach` with the `--attach-config` may be better suited to certain scenarios.
86+
87+When using `--attach-config` the token must be passed in the file rather than on the command line. This is useful in situations where it is preffered to keep the secret token in a file.
88+
89+Optionally, the attach config file can be used to override the services that are automatically enabled as a part of the attach process.
90+
91+An attach config file looks like this:
92+```yaml
93+token: YOUR_TOKEN_HERE # required
94+enable_services: # optional list of service names to auto-enable
95+ - esm-infra
96+ - esm-apps
97+ - cis
98+```
99+
100+And can be passed on the cli like this:
101+```shell
102+sudo ua attach --attach-config /path/to/file.yaml
103+```
104+
105 ### Enabling a service
106 Each service controlled by UA client will have a python module in
107 uaclient/entitlements/\*.py which handles setup and teardown of services when
108@@ -367,9 +388,9 @@ when you should set UACLIENT_BEHAVE_SNAPSHOT_STRATEGY=1
109 The following tox environments allow for testing focal on EC2:
110
111 ```
112- # To test ubuntu-pro-images on EC2
113+ # To test ubuntu-pro-images
114 tox -e behave-awspro-20.04
115- # To test Canonical cloud images (non-ubuntu-pro) on EC2
116+ # To test Canonical cloud images (non-ubuntu-pro)
117 tox -e behave-awsgeneric-20.04
118 ```
119
120@@ -417,9 +438,9 @@ UACLIENT_BEHAVE_REUSE_IMAGE=your-custom-ami tox -e behave-awspro-20.04
121 The following tox environments allow for testing focal on Azure:
122
123 ```
124- # To test ubuntu-pro-images on EC2
125+ # To test ubuntu-pro-images
126 tox -e behave-azurepro-20.04
127- # To test Canonical cloud images (non-ubuntu-pro) on EC2
128+ # To test Canonical cloud images (non-ubuntu-pro)
129 tox -e behave-azuregeneric-20.04
130 ```
131
132@@ -563,7 +584,7 @@ the project, you should install them via `dev-requirements.txt`.)
133 ## Daily Builds
134
135 On Launchpad, there is a [daily build recipe](https://code.launchpad.net/~canonical-server/+recipe/ua-client-daily),
136-which will build the client and place it in the [ua-client-daily PPA](https://code.launchpad.net/~canonical-server/+archive/ubuntu/ua-client-daily).
137+which will build the client and place it in the [ua-client-daily PPA](https://code.launchpad.net/~ua-client/+archive/ubuntu/daily).
138
139 ## Remastering custom golden images based on Ubuntu PRO
140
141diff --git a/RELEASES.md b/RELEASES.md
142index 20096a2..3ea848a 100644
143--- a/RELEASES.md
144+++ b/RELEASES.md
145@@ -226,9 +226,11 @@ If this is your first time releasing ubuntu-advantage-tools, you'll need to do t
146 lxc exec dev-i -- bash /setup_proposed.sh
147 ```
148
149- e. Once [ubuntu-advantage-tools shows up in the pending_sru page](https://people.canonical.com/~ubuntu-archive/pending-sru.html), perform the [Ubuntu-advantage-client SRU verification steps](https://wiki.ubuntu.com/UbuntuAdvantageToolsUpdates). This typically involves running all behave targets with `UACLIENT_BEHAVE_ENABLE_PROPOSED=1 UACLIENT_BEHAVE_CHECK_VERSION=<this-version>` and saving the output.
150+ e. With the package in proposed, perform the steps from `I.3` above but use a `~stableppaX` suffix instead of `~rcX` in the version name, and upload to `ppa:ua-client/stable` instead of staging.
151
152- f. After all tests have passed, tarball all of the output files and upload them to the SRU bug with a message that looks like this:
153+ f. Once [ubuntu-advantage-tools shows up in the pending_sru page](https://people.canonical.com/~ubuntu-archive/pending-sru.html), perform the [Ubuntu-advantage-client SRU verification steps](https://wiki.ubuntu.com/UbuntuAdvantageToolsUpdates). This typically involves running all behave targets with `UACLIENT_BEHAVE_ENABLE_PROPOSED=1 UACLIENT_BEHAVE_CHECK_VERSION=<this-version>` and saving the output.
154+
155+ g. After all tests have passed, tarball all of the output files and upload them to the SRU bug with a message that looks like this:
156 ```
157 We have run the full ubuntu-advantage-tools integration test suite against the version in -proposed. The results are attached. All tests passed (or call out specific explained failures).
158
159@@ -238,33 +240,23 @@ If this is your first time releasing ubuntu-advantage-tools, you'll need to do t
160 ```
161 Change the tags on the bug from `verification-needed` to `verification-done` (including the verification tags for each release).
162
163- g. For any other related Launchpad bugs that are fixed in this release. Perform the verification steps necessary for those bugs and mark them `verification-done` as needed. This will likely involve following the test steps, but instead of adding the staging PPA, enabling -proposed.
164+ h. For any other related Launchpad bugs that are fixed in this release. Perform the verification steps necessary for those bugs and mark them `verification-done` as needed. This will likely involve following the test steps, but instead of adding the staging PPA, enabling -proposed.
165
166- h. Once all SRU bugs are tagged as `verification*-done`, all SRU-bugs should be listed as green in [the pending_sru page](https://people.canonical.com/~ubuntu-archive/pending-sru.html).
167+ i. Once all SRU bugs are tagged as `verification*-done`, all SRU-bugs should be listed as green in [the pending_sru page](https://people.canonical.com/~ubuntu-archive/pending-sru.html).
168
169- i. After the pending sru page says that ubuntu-advantage-tools has been in proposed for 7 days, it is now time to ping the [current SRU vanguard](https://wiki.ubuntu.com/StableReleaseUpdates#Publishing) for acceptance of ubuntu-advantage-tools into -updates.
170+ j. After the pending sru page says that ubuntu-advantage-tools has been in proposed for 7 days, it is now time to ping the [current SRU vanguard](https://wiki.ubuntu.com/StableReleaseUpdates#Publishing) for acceptance of ubuntu-advantage-tools into -updates.
171
172- j. Ping the Ubuntu Server team member who approved the version in step `II.4` to now upload to the devel release.
173+ k. Ping the Ubuntu Server team member who approved the version in step `II.4` to now upload to the devel release.
174
175- k. Check `rmadison ubuntu-advantage-tools` for updated version in devel release
176+ l. Check `rmadison ubuntu-advantage-tools` for updated version in devel release
177
178- l. Confirm availability in <devel-series>-updates pocket via `lxc launch ubuntu-daily:<devel-series> dev-i; lxc exec dev-i -- apt update; lxc exec dev-i -- apt-cache policy ubuntu-advantage-tools`
179+ m. Confirm availability in <devel-series>-updates pocket via `lxc launch ubuntu-daily:<devel-series> dev-i; lxc exec dev-i -- apt update; lxc exec dev-i -- apt-cache policy ubuntu-advantage-tools`
180
181-### III. Final release to team infrastructure
182+### III. Github Repository Post-release Update
183
184 1. Ensure the version tag is correct on github. The `version` git tag should point to the commit that was released as that version to ubuntu -updates. If changes were made in response to feedback during the release process, the tag may have to be moved.
185-2. Perform the steps from `I.3` above but use a `~stableppaX` suffix instead of `~rcX` in the version name, and upload to `ppa:ua-client/stable` instead of staging.
186-3. Bring in any changes that were made to the release branch into `main` via PR (e.g. Changelog edits).
187-
188-## Ubuntu PRO Release Process
189+2. Bring in any changes that were made to the release branch into `main` via PR (e.g. Changelog edits).
190
191-Below is the procedure used to release ubuntu-advantage-tools to Ubuntu PRO images:
192+## Cloud Images Update
193
194- 1. [Open Daily PPA copy-package operation](https://code.launchpad.net/~ua-client/+archive/ubuntu/daily/+copy-packages)
195- 2. Check Xenial, Bionic, Focal, Hirsute, Impish packages
196- 3. Select Destination PPA: UA Client Premium [~ua-client/ubuntu/staging]
197- 4. Select Destination series: The same series
198- 5. Copy options: "Copy existing binaries"
199- 6. Click Copy packages
200- 7. Notify Pro Image creators about expected Premium PPA version (patviafore/powersj)
201- 8. Once new PRO AMIs are publicly available run `./tools/refresh-aws-pro-ids` to update AMIs we test during CI runs
202+After the release process is finished, CPC must be informed. They will be responsible to update the cloud images using the package from the pockets it was released to (whether it is the `stable` PPA or the`-updates` pocket).
203diff --git a/apt-hook/json-hook-src/json-hook_test.go b/apt-hook/json-hook-src/json-hook_test.go
204index 0a2a2eb..695f323 100644
205--- a/apt-hook/json-hook-src/json-hook_test.go
206+++ b/apt-hook/json-hook-src/json-hook_test.go
207@@ -114,9 +114,9 @@ const mockJson = `
208 "pin": 500,
209 "origins": [
210 {
211- "archive": "hirsute-apps-security",
212- "codename": "hirsute",
213- "version": "21.04",
214+ "archive": "focal-apps-security",
215+ "codename": "focal",
216+ "version": "20.04",
217 "origin": "UbuntuESMApps",
218 "label": "Ubuntu",
219 "site": ""
220@@ -130,9 +130,9 @@ const mockJson = `
221 "pin": 500,
222 "origins": [
223 {
224- "archive": "hirsute-apps-security",
225- "codename": "hirsute",
226- "version": "21.04",
227+ "archive": "focal-apps-security",
228+ "codename": "focal",
229+ "version": "20.04",
230 "origin": "UbuntuESMApps",
231 "label": "Ubuntu",
232 "site": ""
233@@ -162,9 +162,9 @@ const mockJson = `
234 "pin": 500,
235 "origins": [
236 {
237- "archive": "hirsute-apps-security",
238- "codename": "hirsute",
239- "version": "21.04",
240+ "archive": "focal-apps-security",
241+ "codename": "focal",
242+ "version": "20.04",
243 "origin": "UbuntuESMApps",
244 "label": "Ubuntu",
245 "site": ""
246@@ -178,9 +178,9 @@ const mockJson = `
247 "pin": 500,
248 "origins": [
249 {
250- "archive": "hirsute-apps-security",
251- "codename": "hirsute",
252- "version": "21.04",
253+ "archive": "focal-apps-security",
254+ "codename": "focal",
255+ "version": "20.04",
256 "origin": "UbuntuESMApps",
257 "label": "Ubuntu",
258 "site": ""
259@@ -210,17 +210,17 @@ const mockJson = `
260 "pin": 500,
261 "origins": [
262 {
263- "archive": "hirsute-infra-security",
264- "codename": "hirsute",
265- "version": "21.04",
266+ "archive": "focal-infra-security",
267+ "codename": "focal",
268+ "version": "20.04",
269 "origin": "UbuntuESM",
270 "label": "Ubuntu",
271 "site": ""
272 },
273 {
274- "archive": "hirsute",
275- "codename": "hirsute",
276- "version": "21.04",
277+ "archive": "focal",
278+ "codename": "focal",
279+ "version": "20.04",
280 "origin": "Ubuntu",
281 "label": "Ubuntu",
282 "site": ""
283@@ -234,17 +234,17 @@ const mockJson = `
284 "pin": 500,
285 "origins": [
286 {
287- "archive": "hirsute-infra-security",
288- "codename": "hirsute",
289- "version": "21.04",
290+ "archive": "focal-infra-security",
291+ "codename": "focal",
292+ "version": "20.04",
293 "origin": "UbuntuESM",
294 "label": "Ubuntu",
295 "site": ""
296 },
297 {
298- "archive": "hirsute",
299- "codename": "hirsute",
300- "version": "21.04",
301+ "archive": "focal",
302+ "codename": "focal",
303+ "version": "20.04",
304 "origin": "Ubuntu",
305 "label": "Ubuntu",
306 "site": ""
307@@ -274,9 +274,9 @@ const mockJson = `
308 "pin": 500,
309 "origins": [
310 {
311- "archive": "hirsute-infra-security",
312- "codename": "hirsute",
313- "version": "21.04",
314+ "archive": "focal-infra-security",
315+ "codename": "focal",
316+ "version": "20.04",
317 "origin": "UbuntuESM",
318 "label": "Ubuntu",
319 "site": ""
320@@ -290,9 +290,9 @@ const mockJson = `
321 "pin": 500,
322 "origins": [
323 {
324- "archive": "hirsute-infra-security",
325- "codename": "hirsute",
326- "version": "21.04",
327+ "archive": "focal-infra-security",
328+ "codename": "focal",
329+ "version": "20.04",
330 "origin": "UbuntuESM",
331 "label": "Ubuntu",
332 "site": ""
333@@ -322,8 +322,8 @@ const mockJson = `
334 "pin": 500,
335 "origins": [
336 {
337- "archive": "hirsute-apps-security",
338- "codename": "hirsute",
339+ "archive": "focal-apps-security",
340+ "codename": "focal",
341 "version": "1.0",
342 "origin": "UbuntuESMApps",
343 "label": "Google",
344@@ -338,8 +338,8 @@ const mockJson = `
345 "pin": 500,
346 "origins": [
347 {
348- "archive": "hirsute-apps-security",
349- "codename": "hirsute",
350+ "archive": "focal-apps-security",
351+ "codename": "focal",
352 "version": "1.0",
353 "origin": "UbuntuESMApps",
354 "label": "Google",
355@@ -370,9 +370,9 @@ const mockJson = `
356 "pin": 500,
357 "origins": [
358 {
359- "archive": "hirsute-security",
360- "codename": "hirsute",
361- "version": "21.04",
362+ "archive": "focal-security",
363+ "codename": "focal",
364+ "version": "20.04",
365 "origin": "Ubuntu",
366 "label": "Ubuntu",
367 "site": ""
368@@ -386,9 +386,9 @@ const mockJson = `
369 "pin": 500,
370 "origins": [
371 {
372- "archive": "hirsute-security",
373- "codename": "hirsute",
374- "version": "21.04",
375+ "archive": "focal-security",
376+ "codename": "focal",
377+ "version": "20.04",
378 "origin": "Ubuntu",
379 "label": "Ubuntu",
380 "site": ""
381diff --git a/debian/changelog b/debian/changelog
382index 6feeb98..60f3d35 100644
383--- a/debian/changelog
384+++ b/debian/changelog
385@@ -1,3 +1,40 @@
386+ubuntu-advantage-tools (27.7~22.04.1) jammy; urgency=medium
387+
388+ * d/changelog:
389+ - fix changelog trailer line for 27.4.1
390+ * d/logrotate:
391+ - make new logs world readable
392+ * d/tools.postinst:
393+ - refactor to catch exception from entitlement_factory
394+ - no longer always set log file to only root readable
395+ - when creating log file for the first time, make world readable
396+ * New upstream release 27.7 (LP: #1964028)
397+ - attach: --attach-config option for customizing auto-enabled services
398+ and supplying token via a file
399+ - auto-attach: fix bug where auto-attach caused a manually attached
400+ machine to detach
401+ - cli:
402+ + support --format=json for attach
403+ + support --format=json for detach
404+ + support --format=json for enable
405+ + support --format=json for disable
406+ - contract: include activity info when updating contract
407+ - detach: no longer contacts contract server on detach
408+ - fips: allow fips on containers
409+ - fix: support USNs that don't have related CVEs
410+ - logs: make all newly created logs world-readable
411+ - security-status:
412+ + show already installed esm package counts
413+ + include APT origin for each potential update
414+ + bump schema version to "0.1"
415+ + remove previously required --beta flag
416+ - status:
417+ + include blocked_by information in service status when format=json
418+ + --simulate-with-token now reports expired tokens as errors
419+ + --simulate-with-token now returns errors in the specified format
420+
421+ -- Grant Orndorff <grant.orndorff@canonical.com> Mon, 07 Mar 2022 13:14:57 -0500
422+
423 ubuntu-advantage-tools (27.6~22.04.1) jammy; urgency=medium
424
425 * New upstream release 27.6 (LP: #1958556)
426diff --git a/debian/ubuntu-advantage-tools.logrotate b/debian/ubuntu-advantage-tools.logrotate
427index 76e6b47..7c64857 100644
428--- a/debian/ubuntu-advantage-tools.logrotate
429+++ b/debian/ubuntu-advantage-tools.logrotate
430@@ -2,6 +2,7 @@
431 # of /var/log/ubuntu-advantage*.log files.
432 /var/log/ubuntu-advantage*.log {
433 su root root
434+ create 0644 root root
435 rotate 6
436 monthly
437 compress
438diff --git a/debian/ubuntu-advantage-tools.postinst b/debian/ubuntu-advantage-tools.postinst
439index 191f0db..014c044 100644
440--- a/debian/ubuntu-advantage-tools.postinst
441+++ b/debian/ubuntu-advantage-tools.postinst
442@@ -119,12 +119,12 @@ check_service_is_beta() {
443 _IS_BETA_SVC=$(/usr/bin/python3 -c "
444 from uaclient.config import UAConfig
445 from uaclient.entitlements import entitlement_factory
446-ent_cls = entitlement_factory('${service_name}')
447-if ent_cls:
448+try:
449+ ent_cls = entitlement_factory('${service_name}')
450 cfg = UAConfig()
451 allow_beta = cfg.features.get('allow_beta', False)
452 print(all([ent_cls.is_beta, not allow_beta]))
453-else:
454+except Exception:
455 print(True)
456 ")
457 if [ "${_IS_BETA_SVC}" = "True" ]; then
458@@ -419,15 +419,14 @@ case "$1" in
459 fi
460 fi
461
462- # We modify permissions for ubuntu-advantage.log because
463- # in a past version of UA, this log file was world readable.
464- # This isn't necessary for the timer/license-check log files,
465- # because they have always been only root-readable.
466+ # log files need to be world-readable
467 if [ ! -f /var/log/ubuntu-advantage.log ]; then
468 touch /var/log/ubuntu-advantage.log
469+ # We are only making new log files world readable
470+ chmod 0644 /var/log/ubuntu-advantage.log
471 fi
472- chmod 0600 /var/log/ubuntu-advantage.log
473 chown root:root /var/log/ubuntu-advantage.log
474+
475 private_dir="/var/lib/ubuntu-advantage/private"
476 if [ -d "$private_dir" ]; then
477 chmod 0700 "$private_dir"
478diff --git a/features/_version.feature b/features/_version.feature
479index e265769..b972500 100644
480--- a/features/_version.feature
481+++ b/features/_version.feature
482@@ -6,8 +6,10 @@ Feature: UA is expected version
483 @uses.config.machine_type.lxd.vm
484 @uses.config.machine_type.aws.generic
485 @uses.config.machine_type.aws.pro
486+ @uses.config.machine_type.aws.pro.fips
487 @uses.config.machine_type.azure.generic
488 @uses.config.machine_type.azure.pro
489+ @uses.config.machine_type.azure.pro.fips
490 @uses.config.machine_type.gcp.generic
491 @uses.config.machine_type.gcp.pro
492 Scenario Outline: Check ua version
493@@ -30,7 +32,6 @@ Feature: UA is expected version
494 | xenial |
495 | bionic |
496 | focal |
497- | hirsute |
498 | impish |
499 | jammy |
500
501@@ -58,5 +59,4 @@ Feature: UA is expected version
502 | xenial |
503 | bionic |
504 | focal |
505- | hirsute |
506 | impish |
507diff --git a/features/attach_invalidtoken.feature b/features/attach_invalidtoken.feature
508index 6ad2d93..59435c2 100644
509--- a/features/attach_invalidtoken.feature
510+++ b/features/attach_invalidtoken.feature
511@@ -15,12 +15,17 @@ Feature: Command behaviour when trying to attach a machine to an Ubuntu
512 """
513 This command must be run as root (try using sudo).
514 """
515+ When I verify that running `ua attach invalid-token --format json` `with sudo` exits `1`
516+ Then I will see the following on stdout:
517+ """
518+ {"_schema_version": "0.1", "errors": [{"message": "Invalid token. See https://ubuntu.com/advantage", "message_code": "attach-invalid-token", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []}
519+ """
520+
521 Examples: ubuntu release
522 | release |
523 | xenial |
524 | bionic |
525 | focal |
526- | hirsute |
527 | impish |
528 | jammy |
529
530@@ -41,6 +46,5 @@ Feature: Command behaviour when trying to attach a machine to an Ubuntu
531 | xenial |
532 | bionic |
533 | focal |
534- | hirsute |
535 | impish |
536 | jammy |
537diff --git a/features/attach_validtoken.feature b/features/attach_validtoken.feature
538index 8045591..25f763b 100644
539--- a/features/attach_validtoken.feature
540+++ b/features/attach_validtoken.feature
541@@ -3,7 +3,6 @@ Feature: Command behaviour when attaching a machine to an Ubuntu Advantage
542 subscription using a valid token
543
544 @series.jammy
545- @series.hirsute
546 @series.impish
547 @uses.config.machine_type.lxd.container
548 Scenario Outline: Attached command in a non-lts ubuntu machine
549@@ -24,7 +23,6 @@ Feature: Command behaviour when attaching a machine to an Ubuntu Advantage
550
551 Examples: ubuntu release
552 | release |
553- | hirsute |
554 | impish |
555 | jammy |
556
557@@ -67,8 +65,8 @@ Feature: Command behaviour when attaching a machine to an Ubuntu Advantage
558 """
559 esm-apps +yes +enabled +UA Apps: Extended Security Maintenance \(ESM\)
560 esm-infra +yes +enabled +UA Infra: Extended Security Maintenance \(ESM\)
561- fips +yes +n/a +NIST-certified core packages
562- fips-updates +yes +n/a +NIST-certified core packages with priority security updates
563+ fips +yes +disabled +NIST-certified core packages
564+ fips-updates +yes +disabled +NIST-certified core packages with priority security updates
565 livepatch +yes +n/a +Canonical Livepatch service
566 """
567 And stdout matches regexp:
568@@ -173,6 +171,115 @@ Feature: Command behaviour when attaching a machine to an Ubuntu Advantage
569 | bionic | libkrad0=1.16-2build1 | disabled | cis |
570 | focal | hello=2.10-2ubuntu2 | n/a | usg |
571
572+ @series.lts
573+ @uses.config.machine_type.lxd.container
574+ Scenario Outline: Attach command with attach config
575+ Given a `<release>` machine with ubuntu-advantage-tools installed
576+ # simplest happy path
577+ When I create the file `/tmp/attach.yaml` with the following
578+ """
579+ token: <contract_token>
580+ """
581+ When I replace `<contract_token>` in `/tmp/attach.yaml` with token `contract_token`
582+ When I run `ua attach --attach-config /tmp/attach.yaml` with sudo
583+ Then stdout matches regexp:
584+ """
585+ esm-apps +yes +enabled
586+ """
587+ And stdout matches regexp:
588+ """
589+ esm-infra +yes +enabled
590+ """
591+ And stdout matches regexp:
592+ """
593+ <cis_or_usg> +yes +disabled
594+ """
595+ When I run `ua detach --assume-yes` with sudo
596+ # don't allow both token on cli and config
597+ Then I verify that running `ua attach TOKEN --attach-config /tmp/attach.yaml` `with sudo` exits `1`
598+ Then stderr matches regexp:
599+ """
600+ Do not pass the TOKEN arg if you are using --attach-config.
601+ Include the token in the attach-config file instead.
602+ """
603+ # happy path with service overrides
604+ When I create the file `/tmp/attach.yaml` with the following
605+ """
606+ token: <contract_token>
607+ enable_services:
608+ - esm-apps
609+ - <cis_or_usg>
610+ """
611+ When I replace `<contract_token>` in `/tmp/attach.yaml` with token `contract_token`
612+ When I run `ua attach --attach-config /tmp/attach.yaml` with sudo
613+ Then stdout matches regexp:
614+ """
615+ esm-apps +yes +enabled
616+ """
617+ And stdout matches regexp:
618+ """
619+ esm-infra +yes +disabled
620+ """
621+ And stdout matches regexp:
622+ """
623+ <cis_or_usg> +yes +enabled
624+ """
625+ When I run `ua detach --assume-yes` with sudo
626+ # missing token
627+ When I create the file `/tmp/attach.yaml` with the following
628+ """
629+ enable_services:
630+ - esm-apps
631+ - <cis_or_usg>
632+ """
633+ Then I verify that running `ua attach --attach-config /tmp/attach.yaml` `with sudo` exits `1`
634+ Then stderr matches regexp:
635+ """
636+ Error while reading /tmp/attach.yaml: Got value with incorrect type for field
637+ "token": Expected value with type StringDataValue but got value: None
638+ """
639+ # other schema error
640+ When I create the file `/tmp/attach.yaml` with the following
641+ """
642+ token: <contract_token>
643+ enable_services: {cis: true}
644+ """
645+ When I replace `<contract_token>` in `/tmp/attach.yaml` with token `contract_token`
646+ Then I verify that running `ua attach --attach-config /tmp/attach.yaml` `with sudo` exits `1`
647+ Then stderr matches regexp:
648+ """
649+ Error while reading /tmp/attach.yaml: Got value with incorrect type for field
650+ "enable_services": Expected value with type list but got value: {\'cis\': True}
651+ """
652+ # invalid service name
653+ When I create the file `/tmp/attach.yaml` with the following
654+ """
655+ token: <contract_token>
656+ enable_services:
657+ - esm-apps
658+ - nonexistent
659+ - nonexistent2
660+ """
661+ When I replace `<contract_token>` in `/tmp/attach.yaml` with token `contract_token`
662+ Then I verify that running `ua attach --attach-config /tmp/attach.yaml` `with sudo` exits `1`
663+ Then stdout matches regexp:
664+ """
665+ esm-apps +yes +enabled
666+ """
667+ And stdout matches regexp:
668+ """
669+ esm-infra +yes +disabled
670+ """
671+ Then stderr matches regexp:
672+ """
673+ Cannot enable unknown service 'nonexistent, nonexistent2'.
674+ """
675+ Examples: ubuntu
676+ | release | cis_or_usg |
677+ | xenial | cis |
678+ | bionic | cis |
679+ | focal | usg |
680+
681 @series.all
682 @uses.config.machine_type.aws.generic
683 Scenario Outline: Attach command in an generic AWS Ubuntu VM
684@@ -231,7 +338,7 @@ Feature: Command behaviour when attaching a machine to an Ubuntu Advantage
685 | release | fips_status |lp_status | lp_desc | cc_status | cis_or_usg |
686 | xenial | disabled |enabled | Canonical Livepatch service | disabled | cis |
687 | bionic | disabled |enabled | Canonical Livepatch service | disabled | cis |
688- | focal | n/a |enabled | Canonical Livepatch service | n/a | usg |
689+ | focal | disabled |enabled | Canonical Livepatch service | n/a | usg |
690
691 @series.all
692 @uses.config.machine_type.azure.generic
693@@ -290,8 +397,8 @@ Feature: Command behaviour when attaching a machine to an Ubuntu Advantage
694 Examples: ubuntu release livepatch status
695 | release | lp_status | fips_status | cc_status | cis_or_usg |
696 | xenial | enabled | n/a | disabled | cis |
697- | bionic | n/a | disabled | disabled | cis |
698- | focal | enabled | n/a | n/a | usg |
699+ | bionic | enabled | disabled | disabled | cis |
700+ | focal | enabled | disabled | n/a | usg |
701
702 @series.all
703 @uses.config.machine_type.gcp.generic
704@@ -351,4 +458,39 @@ Feature: Command behaviour when attaching a machine to an Ubuntu Advantage
705 | release | lp_status | fips_status | cc_status | cis_or_usg |
706 | xenial | n/a | n/a | disabled | cis |
707 | bionic | n/a | disabled | disabled | cis |
708- | focal | enabled | n/a | n/a | usg |
709+ | focal | enabled | disabled | n/a | usg |
710+
711+ @series.all
712+ @uses.config.machine_type.lxd.container
713+ Scenario Outline: Attach command with json output
714+ Given a `<release>` machine with ubuntu-advantage-tools installed
715+ When I verify that running attach `as non-root` with json response exits `1`
716+ Then I will see the following on stdout:
717+ """
718+ {"_schema_version": "0.1", "errors": [{"message": "This command must be run as root (try using sudo).", "message_code": "nonroot-user", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []}
719+ """
720+ When I verify that running attach `with sudo` with json response exits `0`
721+ Then I will see the following on stdout:
722+ """
723+ {"_schema_version": "0.1", "errors": [], "failed_services": [], "needs_reboot": false, "processed_services": ["esm-apps", "esm-infra"], "result": "success", "warnings": []}
724+ """
725+ When I run `ua status` with sudo
726+ Then stdout matches regexp:
727+ """
728+ SERVICE ENTITLED STATUS DESCRIPTION
729+ cc-eal +yes +<cc-eal> +Common Criteria EAL2 Provisioning Packages
730+ """
731+ And stdout matches regexp:
732+ """
733+ esm-apps +yes +enabled +UA Apps: Extended Security Maintenance \(ESM\)
734+ esm-infra +yes +enabled +UA Infra: Extended Security Maintenance \(ESM\)
735+ fips +yes +disabled +NIST-certified core packages
736+ fips-updates +yes +disabled +NIST-certified core packages with priority security updates
737+ livepatch +yes +n/a +Canonical Livepatch service
738+ """
739+
740+ Examples: ubuntu release
741+ | release | cc-eal |
742+ | xenial | disabled |
743+ | bionic | disabled |
744+ | focal | n/a |
745diff --git a/features/attached_commands.feature b/features/attached_commands.feature
746index ca536dd..dd6cf1a 100644
747--- a/features/attached_commands.feature
748+++ b/features/attached_commands.feature
749@@ -38,7 +38,9 @@ Feature: Command behaviour when attached to an UA subscription
750 And I run `sh -c "ls /var/log/ubuntu-advantage* | sort -d"` as non-root
751 Then stdout matches regexp:
752 """
753+ /var/log/ubuntu-advantage.log
754 /var/log/ubuntu-advantage.log.1
755+ /var/log/ubuntu-advantage-timer.log
756 /var/log/ubuntu-advantage-timer.log.1
757 """
758
759@@ -47,79 +49,89 @@ Feature: Command behaviour when attached to an UA subscription
760 | bionic |
761 | focal |
762 | xenial |
763- | hirsute |
764 | impish |
765 | jammy |
766
767- @series.all
768- @uses.config.machine_type.lxd.container
769- Scenario Outline: Attached and detach correctly reach contract endpoint
770- Given a `<release>` machine with ubuntu-advantage-tools installed
771- When I attach `contract_token` with sudo
772- And I run `ua detach --assume-yes` with sudo
773- Then I verify that running `grep "Found new machine-id. Do not call detach on contract backend" /var/log/ubuntu-advantage.log` `with sudo` exits `1`
774-
775- Examples: ubuntu release
776- | release |
777- | bionic |
778- | focal |
779- | xenial |
780- | hirsute |
781- | impish |
782- | jammy |
783
784 @series.all
785 @uses.config.machine_type.lxd.container
786- Scenario Outline: Attached and detach don't reach contract endpoint if machine-id changes
787+ Scenario Outline: Attached disable of an already disabled service in a ubuntu machine
788 Given a `<release>` machine with ubuntu-advantage-tools installed
789 When I attach `contract_token` with sudo
790- And I update contract to use `machineId` as `new-machine-id`
791- And I run `ua detach --assume-yes` with sudo
792- Then stdout matches regexp:
793+ Then I verify that running `ua disable livepatch` `as non-root` exits `1`
794+ And stderr matches regexp:
795 """
796- This machine is now detached.
797+ This command must be run as root \(try using sudo\).
798+ """
799+ And I verify that running `ua disable livepatch` `with sudo` exits `1`
800+ And I will see the following on stdout:
801+ """
802+ Livepatch is not currently enabled
803+ See: sudo ua status
804 """
805- And I verify that running `grep "Found new machine-id. Do not call detach on contract backend" /var/log/ubuntu-advantage.log` `with sudo` exits `0`
806- When I run `ua status` with sudo
807- Then stdout matches regexp:
808- """
809- This machine is not attached to a UA subscription.
810- """
811
812 Examples: ubuntu release
813 | release |
814 | bionic |
815 | focal |
816 | xenial |
817- | hirsute |
818 | impish |
819 | jammy |
820
821- @series.all
822+ @series.lts
823 @uses.config.machine_type.lxd.container
824- Scenario Outline: Attached disable of an already disabled service in a ubuntu machine
825+ Scenario Outline: Attached disable with json format
826 Given a `<release>` machine with ubuntu-advantage-tools installed
827 When I attach `contract_token` with sudo
828- Then I verify that running `ua disable livepatch` `as non-root` exits `1`
829- And stderr matches regexp:
830+ Then I verify that running `ua disable foobar --format json` `as non-root` exits `1`
831+ And stdout is a json matching the `ua_operation` schema
832+ And I will see the following on stdout:
833 """
834- This command must be run as root \(try using sudo\).
835+ {"_schema_version": "0.1", "errors": [{"message": "json formatted response requires --assume-yes flag.", "message_code": "json-format-require-assume-yes", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []}
836 """
837- And I verify that running `ua disable livepatch` `with sudo` exits `1`
838+ Then I verify that running `ua disable foobar --format json` `with sudo` exits `1`
839+ And stdout is a json matching the `ua_operation` schema
840 And I will see the following on stdout:
841 """
842- Livepatch is not currently enabled
843- See: sudo ua status
844+ {"_schema_version": "0.1", "errors": [{"message": "json formatted response requires --assume-yes flag.", "message_code": "json-format-require-assume-yes", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []}
845+ """
846+ Then I verify that running `ua disable foobar --format json --assume-yes` `as non-root` exits `1`
847+ And stdout is a json matching the `ua_operation` schema
848+ And I will see the following on stdout:
849+ """
850+ {"_schema_version": "0.1", "errors": [{"message": "This command must be run as root (try using sudo).", "message_code": "nonroot-user", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []}
851+ """
852+ And I verify that running `ua disable foobar --format json --assume-yes` `with sudo` exits `1`
853+ And stdout is a json matching the `ua_operation` schema
854+ And I will see the following on stdout:
855+ """
856+ {"_schema_version": "0.1", "errors": [{"message": "Cannot disable unknown service 'foobar'.\nTry <valid_services>", "message_code": "invalid-service-or-failure", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []}
857 """
858+ And I verify that running `ua disable livepatch --format json --assume-yes` `with sudo` exits `1`
859+ And stdout is a json matching the `ua_operation` schema
860+ And I will see the following on stdout:
861+ """
862+ {"_schema_version": "0.1", "errors": [{"message": "Livepatch is not currently enabled\nSee: sudo ua status", "message_code": "service-already-disabled", "service": "livepatch", "type": "service"}], "failed_services": ["livepatch"], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []}
863+ """
864+ And I verify that running `ua disable esm-infra esm-apps --format json --assume-yes` `with sudo` exits `0`
865+ And stdout is a json matching the `ua_operation` schema
866+ And I will see the following on stdout:
867+ """
868+ {"_schema_version": "0.1", "errors": [], "failed_services": [], "needs_reboot": false, "processed_services": ["esm-apps", "esm-infra"], "result": "success", "warnings": []}
869+ """
870+ When I run `ua enable esm-infra` with sudo
871+ Then I verify that running `ua disable esm-infra foobar --format json --assume-yes` `with sudo` exits `1`
872+ And stdout is a json matching the `ua_operation` schema
873+ And I will see the following on stdout:
874+ """
875+ {"_schema_version": "0.1", "errors": [{"message": "Cannot disable unknown service 'foobar'.\nTry <valid_services>", "message_code": "invalid-service-or-failure", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": ["esm-infra"], "result": "failure", "warnings": []}
876+ """
877
878 Examples: ubuntu release
879- | release |
880- | bionic |
881- | focal |
882- | xenial |
883- | hirsute |
884- | impish |
885- | jammy |
886+ | release | valid_services |
887+ | xenial | cc-eal, cis, esm-apps, esm-infra, fips, fips-updates, livepatch, ros,\nros-updates. |
888+ | bionic | cc-eal, cis, esm-apps, esm-infra, fips, fips-updates, livepatch, ros,\nros-updates. |
889+ | focal | cc-eal, esm-apps, esm-infra, fips, fips-updates, livepatch, ros,\nros-updates, usg. |
890
891 @series.xenial
892 @series.bionic
893@@ -241,6 +253,31 @@ Feature: Command behaviour when attached to an UA subscription
894 This machine is not attached to a UA subscription.
895 """
896 And I verify that running `apt update` `with sudo` exits `0`
897+ When I attach `contract_token` with sudo
898+ Then I verify that running `ua enable foobar --format json` `as non-root` exits `1`
899+ And stdout is a json matching the `ua_operation` schema
900+ And I will see the following on stdout:
901+ """
902+ {"_schema_version": "0.1", "errors": [{"message": "json formatted response requires --assume-yes flag.", "message_code": "json-format-require-assume-yes", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []}
903+ """
904+ Then I verify that running `ua enable foobar --format json` `with sudo` exits `1`
905+ And stdout is a json matching the `ua_operation` schema
906+ And I will see the following on stdout:
907+ """
908+ {"_schema_version": "0.1", "errors": [{"message": "json formatted response requires --assume-yes flag.", "message_code": "json-format-require-assume-yes", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []}
909+ """
910+ Then I verify that running `ua detach --format json --assume-yes` `as non-root` exits `1`
911+ And stdout is a json matching the `ua_operation` schema
912+ And I will see the following on stdout:
913+ """
914+ {"_schema_version": "0.1", "errors": [{"message": "This command must be run as root (try using sudo).", "message_code": "nonroot-user", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []}
915+ """
916+ When I run `ua detach --format json --assume-yes` with sudo
917+ Then stdout is a json matching the `ua_operation` schema
918+ And I will see the following on stdout:
919+ """
920+ {"_schema_version": "0.1", "errors": [], "failed_services": [], "needs_reboot": false, "processed_services": ["esm-apps", "esm-infra"], "result": "success", "warnings": []}
921+ """
922
923 Examples: ubuntu release
924 | release | esm-apps | cc-eal | cis | fips | fips-update | ros | cis_or_usg |
925@@ -269,7 +306,6 @@ Feature: Command behaviour when attached to an UA subscription
926 | bionic |
927 | focal |
928 | xenial |
929- | hirsute |
930 | impish |
931 | jammy |
932
933@@ -292,7 +328,6 @@ Feature: Command behaviour when attached to an UA subscription
934 | bionic |
935 | focal |
936 | xenial |
937- | hirsute |
938 | impish |
939 | jammy |
940
941@@ -344,7 +379,6 @@ Feature: Command behaviour when attached to an UA subscription
942 | bionic |
943 | focal |
944 | xenial |
945- | hirsute |
946 | impish |
947 | jammy |
948
949@@ -534,7 +568,6 @@ Feature: Command behaviour when attached to an UA subscription
950 | release | infra-status |
951 | bionic | enabled |
952 | xenial | enabled |
953- | hirsute | n/a |
954 | impish | n/a |
955 | jammy | n/a |
956
957@@ -751,7 +784,6 @@ Feature: Command behaviour when attached to an UA subscription
958 | xenial |
959 | bionic |
960 | focal |
961- | hirsute |
962 | impish |
963 | jammy |
964
965@@ -884,14 +916,11 @@ Feature: Command behaviour when attached to an UA subscription
966 """
967 And I run `dpkg-reconfigure ubuntu-advantage-tools` with sudo
968 And I run `apt-get update` with sudo
969- When I run `ua security-status --format json --beta` as non-root
970- Then stdout is formatted as `json` and has keys:
971- """
972- _schema_version summary packages
973- """
974+ When I run `ua security-status --format json` as non-root
975+ Then stdout is a json matching the `ua_security_status` schema
976 And stdout matches regexp:
977 """
978- "_schema_version": "0"
979+ "_schema_version": "0.1"
980 """
981 And stdout matches regexp:
982 """
983@@ -915,13 +944,17 @@ Feature: Command behaviour when attached to an UA subscription
984 """
985 And stdout matches regexp:
986 """
987+ "origin": "esm.ubuntu.com"
988+ """
989+ And stdout matches regexp:
990+ """
991 "status": "pending_attach"
992 """
993 When I attach `contract_token` with sudo
994- And I run `ua security-status --format json --beta` as non-root
995+ And I run `ua security-status --format json` as non-root
996 Then stdout matches regexp:
997 """
998- "_schema_version": "0"
999+ "_schema_version": "0.1"
1000 """
1001 And stdout matches regexp:
1002 """
1003@@ -939,32 +972,23 @@ Feature: Command behaviour when attached to an UA subscription
1004 """
1005 "status": "upgrade_available"
1006 """
1007- When I run `ua security-status --format yaml --beta` as non-root
1008- Then stdout is formatted as `yaml` and has keys:
1009- """
1010- _schema_version summary packages
1011- """
1012+ When I run `ua security-status --format yaml` as non-root
1013+ Then stdout is a yaml matching the `ua_security_status` schema
1014 And stdout matches regexp:
1015 """
1016- _schema_version: '0'
1017- """
1018- When I verify that running `ua security-status --format json` `as non-root` exits `2`
1019- Then I will see the following on stderr:
1020- """
1021- usage: security-status [-h] --format {json,yaml} --beta
1022- the following arguments are required: --beta
1023+ _schema_version: '0.1'
1024 """
1025- When I verify that running `ua security-status --format unsupported --beta` `as non-root` exits `2`
1026+ When I verify that running `ua security-status --format unsupported` `as non-root` exits `2`
1027 Then I will see the following on stderr:
1028 """
1029- usage: security-status [-h] --format {json,yaml} --beta
1030+ usage: security-status [-h] --format {json,yaml}
1031 argument --format: invalid choice: 'unsupported' (choose from 'json', 'yaml')
1032 """
1033 When I verify that running `ua security-status` `as non-root` exits `2`
1034 Then I will see the following on stderr:
1035 """
1036- usage: security-status [-h] --format {json,yaml} --beta
1037- the following arguments are required: --format, --beta
1038+ usage: security-status [-h] --format {json,yaml}
1039+ the following arguments are required: --format
1040 """
1041 Examples: ubuntu release
1042 | release | package | service |
1043diff --git a/features/attached_enable.feature b/features/attached_enable.feature
1044index 2fda0de..90a62cb 100644
1045--- a/features/attached_enable.feature
1046+++ b/features/attached_enable.feature
1047@@ -29,7 +29,6 @@ Feature: Enable command behaviour when attached to an UA subscription
1048 | bionic |
1049
1050 @series.focal
1051- @series.hirsute
1052 @series.impish
1053 @uses.config.machine_type.lxd.container
1054 Scenario Outline: Attached enable Common Criteria service in an ubuntu lxd container
1055@@ -49,11 +48,80 @@ Feature: Enable command behaviour when attached to an UA subscription
1056 Examples: ubuntu release
1057 | release | version | full_name |
1058 | focal | 20.04 LTS | Focal Fossa |
1059- | hirsute | 21.04 | Hirsute Hippo |
1060 | impish | 21.10 | Impish Indri |
1061
1062 @series.xenial
1063 @series.bionic
1064+ @series.focal
1065+ @uses.config.machine_type.lxd.container
1066+ Scenario Outline: Attached enable of different services using json format
1067+ Given a `<release>` machine with ubuntu-advantage-tools installed
1068+ When I attach `contract_token` with sudo
1069+ Then I verify that running `ua enable foobar --format json` `as non-root` exits `1`
1070+ And stdout is a json matching the `ua_operation` schema
1071+ And I will see the following on stdout:
1072+ """
1073+ {"_schema_version": "0.1", "errors": [{"message": "json formatted response requires --assume-yes flag.", "message_code": "json-format-require-assume-yes", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []}
1074+ """
1075+ Then I verify that running `ua enable foobar --format json` `with sudo` exits `1`
1076+ And stdout is a json matching the `ua_operation` schema
1077+ And I will see the following on stdout:
1078+ """
1079+ {"_schema_version": "0.1", "errors": [{"message": "json formatted response requires --assume-yes flag.", "message_code": "json-format-require-assume-yes", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []}
1080+ """
1081+ Then I verify that running `ua enable foobar --format json --assume-yes` `as non-root` exits `1`
1082+ And stdout is a json matching the `ua_operation` schema
1083+ And I will see the following on stdout:
1084+ """
1085+ {"_schema_version": "0.1", "errors": [{"message": "This command must be run as root (try using sudo).", "message_code": "nonroot-user", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []}
1086+ """
1087+ And I verify that running `ua enable foobar --format json --assume-yes` `with sudo` exits `1`
1088+ And stdout is a json matching the `ua_operation` schema
1089+ And I will see the following on stdout:
1090+ """
1091+ {"_schema_version": "0.1", "errors": [{"message": "Cannot enable unknown service 'foobar'.\nTry <valid_services>", "message_code": "invalid-service-or-failure", "service": null, "type": "system"}], "failed_services": ["foobar"], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []}
1092+ """
1093+ And I verify that running `ua enable ros foobar --format json --assume-yes` `with sudo` exits `1`
1094+ And stdout is a json matching the `ua_operation` schema
1095+ And I will see the following on stdout:
1096+ """
1097+ {"_schema_version": "0.1", "errors": [{"message": "Cannot enable unknown service 'foobar, ros'.\nTry <valid_services>", "message_code": "invalid-service-or-failure", "service": null, "type": "system"}], "failed_services": ["foobar", "ros"], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []}
1098+ """
1099+ And I verify that running `ua enable esm-infra --format json --assume-yes` `with sudo` exits `1`
1100+ And stdout is a json matching the `ua_operation` schema
1101+ Then I will see the following on stdout:
1102+ """
1103+ {"_schema_version": "0.1", "errors": [{"message": "UA Infra: ESM is already enabled.\nSee: sudo ua status", "message_code": "service-already-enabled", "service": "esm-infra", "type": "service"}], "failed_services": ["esm-infra"], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []}
1104+ """
1105+ When I run `ua disable esm-infra` with sudo
1106+ And I run `ua enable esm-infra --format json --assume-yes` with sudo
1107+ Then stdout is a json matching the `ua_operation` schema
1108+ And I will see the following on stdout:
1109+ """
1110+ {"_schema_version": "0.1", "errors": [], "failed_services": [], "needs_reboot": false, "processed_services": ["esm-infra"], "result": "success", "warnings": []}
1111+ """
1112+ When I run `ua disable esm-infra` with sudo
1113+ And I verify that running `ua enable esm-infra foobar --format json --assume-yes` `with sudo` exits `1`
1114+ Then stdout is a json matching the `ua_operation` schema
1115+ And I will see the following on stdout:
1116+ """
1117+ {"_schema_version": "0.1", "errors": [{"message": "Cannot enable unknown service 'foobar'.\nTry <valid_services>", "message_code": "invalid-service-or-failure", "service": null, "type": "system"}], "failed_services": ["foobar"], "needs_reboot": false, "processed_services": ["esm-infra"], "result": "failure", "warnings": []}
1118+ """
1119+ When I run `ua disable esm-infra esm-apps` with sudo
1120+ And I run `ua enable esm-infra esm-apps --beta --format json --assume-yes` with sudo
1121+ Then stdout is a json matching the `ua_operation` schema
1122+ And I will see the following on stdout:
1123+ """
1124+ {"_schema_version": "0.1", "errors": [], "failed_services": [], "needs_reboot": false, "processed_services": ["esm-apps", "esm-infra"], "result": "success", "warnings": []}
1125+ """
1126+
1127+ Examples: ubuntu release
1128+ | release | valid_services |
1129+ | xenial | cc-eal, cis, esm-infra, fips, fips-updates, livepatch. |
1130+ | bionic | cc-eal, cis, esm-infra, fips, fips-updates, livepatch. |
1131+ | focal | cc-eal, esm-infra, fips, fips-updates, livepatch, usg. |
1132+
1133+ @series.lts
1134 @uses.config.machine_type.lxd.container
1135 Scenario Outline: Attached enable of a service in a ubuntu machine
1136 Given a `<release>` machine with ubuntu-advantage-tools installed
1137@@ -84,7 +152,7 @@ Feature: Enable command behaviour when attached to an UA subscription
1138 Try cc-eal, cis, esm-infra, fips, fips-updates, livepatch.
1139 """
1140 And I verify that running `ua enable esm-infra` `with sudo` exits `1`
1141- Then I will see the following on stdout:
1142+ And I will see the following on stdout:
1143 """
1144 One moment, checking your subscription first
1145 UA Infra: ESM is already enabled.
1146@@ -176,25 +244,12 @@ Feature: Enable command behaviour when attached to an UA subscription
1147 One moment, checking your subscription first
1148 Cannot install Livepatch on a container.
1149 """
1150- And I verify that running `ua enable fips --assume-yes` `with sudo` exits `1`
1151- And I will see the following on stdout:
1152- """
1153- One moment, checking your subscription first
1154- Cannot install FIPS on a container.
1155- """
1156- And I verify that running `ua enable fips-updates --assume-yes` `with sudo` exits `1`
1157- And I will see the following on stdout:
1158- """
1159- One moment, checking your subscription first
1160- Cannot install FIPS Updates on a container.
1161- """
1162
1163 Examples: Un-supported services in containers
1164 | release |
1165 | bionic |
1166 | focal |
1167 | xenial |
1168- | hirsute |
1169 | impish |
1170 | jammy |
1171
1172@@ -495,7 +550,7 @@ Feature: Enable command behaviour when attached to an UA subscription
1173 """
1174 When I run `ua disable livepatch` with sudo
1175 Then I verify that running `canonical-livepatch status` `with sudo` exits `1`
1176- And stdout matches regexp:
1177+ And stderr matches regexp:
1178 """
1179 Machine is not enabled. Please run 'sudo canonical-livepatch enable' with the
1180 token obtained from https://ubuntu.com/livepatch.
1181@@ -581,6 +636,11 @@ Feature: Enable command behaviour when attached to an UA subscription
1182 One moment, checking your subscription first
1183 Cannot enable Livepatch when FIPS is enabled.
1184 """
1185+ Then I verify that running `ua enable livepatch --format json --assume-yes` `with sudo` exits `1`
1186+ And I will see the following on stdout
1187+ """
1188+ {"_schema_version": "0.1", "errors": [{"message": "Cannot enable Livepatch when FIPS is enabled.", "message_code": "livepatch-error-when-fips-enabled", "service": "livepatch", "type": "service"}], "failed_services": ["livepatch"], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []}
1189+ """
1190
1191 @series.bionic
1192 @uses.config.machine_type.lxd.vm
1193@@ -603,11 +663,14 @@ Feature: Enable command behaviour when attached to an UA subscription
1194 And I will see the following on stdout
1195 """
1196 One moment, checking your subscription first
1197- """
1198- And I will see the following on stderr
1199- """
1200 Cannot enable FIPS when Livepatch is enabled.
1201 """
1202+ Then I verify that running `ua enable fips --assume-yes --format json` `with sudo` exits `1`
1203+ And stdout is a json matching the `ua_operation` schema
1204+ And I will see the following on stdout:
1205+ """
1206+ {"_schema_version": "0.1", "errors": [{"message": "Cannot enable FIPS when Livepatch is enabled.", "service": "fips", "type": "service"}], "failed_services": ["fips"], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []}
1207+ """
1208
1209 @slow
1210 @series.xenial
1211@@ -688,7 +751,8 @@ Feature: Enable command behaviour when attached to an UA subscription
1212 | bionic |
1213 | xenial |
1214
1215- @series.lts
1216+ @series.xenial
1217+ @series.bionic
1218 @uses.config.contract_token
1219 @uses.config.machine_type.lxd.container
1220 Scenario Outline: Attached enable ros on a machine
1221@@ -741,7 +805,6 @@ Feature: Enable command behaviour when attached to an UA subscription
1222 ROS ESM Security Updates cannot be enabled with UA Apps: ESM disabled.
1223 Enable UA Apps: ESM and proceed to enable ROS ESM Security Updates\? \(y\/N\) Cannot enable ROS ESM Security Updates when UA Apps: ESM is disabled.
1224 """
1225-
1226 When I run `ua enable ros --beta` `with sudo` and stdin `y`
1227 Then stdout matches regexp
1228 """
1229diff --git a/features/attached_status.feature b/features/attached_status.feature
1230index 78483ed..59406e7 100644
1231--- a/features/attached_status.feature
1232+++ b/features/attached_status.feature
1233@@ -7,25 +7,14 @@ Feature: Attached status
1234 Given a `<release>` machine with ubuntu-advantage-tools installed
1235 When I attach `contract_token` with sudo
1236 And I run `ua status --format json` as non-root
1237- Then stdout is formatted as `json` and has keys:
1238- """
1239- _doc _schema_version account attached config config_path contract effective
1240- environment_vars execution_details execution_status expires machine_id notices
1241- services version simulated
1242- """
1243+ Then stdout is a json matching the `ua_status` schema
1244 When I run `ua status --format yaml` as non-root
1245- Then stdout is formatted as `yaml` and has keys:
1246- """
1247- _doc _schema_version account attached config config_path contract effective
1248- environment_vars execution_details execution_status expires machine_id notices
1249- services version simulated
1250- """
1251+ Then stdout is a yaml matching the `ua_status` schema
1252
1253 Examples: ubuntu release
1254 | release |
1255 | bionic |
1256 | focal |
1257 | xenial |
1258- | hirsute |
1259 | impish |
1260 | jammy |
1261diff --git a/features/aws-ids.yaml b/features/aws-ids.yaml
1262index e0198a7..f2376ad 100644
1263--- a/features/aws-ids.yaml
1264+++ b/features/aws-ids.yaml
1265@@ -1,3 +1,5 @@
1266-bionic: ami-094e9c95623db463c
1267-focal: ami-0193aa0a9df84a08b
1268-xenial: ami-06e94647aeaed1e5c
1269+bionic: ami-0419d66039473da9d
1270+bionic-fips: ami-03b75f613f80bcff1
1271+focal: ami-0489b8bdbbf3a3b32
1272+xenial: ami-011bcfe2bea365b6a
1273+xenial-fips: ami-077e4c339a098fc9f
1274diff --git a/features/azure-ids.yaml b/features/azure-ids.yaml
1275index 4d43f80..735223f 100644
1276--- a/features/azure-ids.yaml
1277+++ b/features/azure-ids.yaml
1278@@ -1,3 +1,5 @@
1279 bionic: "Canonical:0001-com-ubuntu-pro-bionic:pro-18_04-lts"
1280 focal: "Canonical:0001-com-ubuntu-pro-focal:pro-20_04-lts"
1281 xenial: "Canonical:0001-com-ubuntu-pro-xenial:pro-16_04-lts"
1282+bionic-fips: "Canonical:0001-com-ubuntu-pro-bionic-fips:pro-fips-18_04"
1283+xenial-fips: "Canonical:0001-com-ubuntu-pro-xenial-fips:pro-fips-16_04-private"
1284diff --git a/features/cloud.py b/features/cloud.py
1285index 4938374..b0df204 100644
1286--- a/features/cloud.py
1287+++ b/features/cloud.py
1288@@ -218,7 +218,10 @@ class Cloud:
1289 if "pro" in self.machine_type:
1290 with open(self.pro_ids_path, "r") as stream:
1291 pro_ids = yaml.safe_load(stream.read())
1292- image_name = pro_ids[series]
1293+ if "fips" in self.machine_type:
1294+ image_name = pro_ids[series + "-fips"]
1295+ else:
1296+ image_name = pro_ids[series]
1297 else:
1298 image_name = self.api.daily_image(release=series)
1299
1300@@ -539,7 +542,7 @@ class Azure(Cloud):
1301 List of ports to open for network ingress to the instance
1302
1303 :returns:
1304- An AWS cloud provider instance
1305+ An Azure cloud provider instance
1306 """
1307 if not image_name:
1308 image_name = self.locate_image_name(series)
1309@@ -654,7 +657,7 @@ class GCP(Cloud):
1310 List of ports to open for network ingress to the instance
1311
1312 :returns:
1313- An AWS cloud provider instance
1314+ An GCP cloud provider instance
1315 """
1316 if not image_name:
1317 image_name = self.locate_image_name(series)
1318diff --git a/features/detached_auto_attach.feature b/features/detached_auto_attach.feature
1319new file mode 100644
1320index 0000000..463400a
1321--- /dev/null
1322+++ b/features/detached_auto_attach.feature
1323@@ -0,0 +1,32 @@
1324+@uses.config.contract_token
1325+Feature: Attached cloud does not detach when auto-attaching after manually attaching
1326+
1327+ @series.all
1328+ @uses.config.machine_type.aws.generic
1329+ @uses.config.machine_type.azure.generic
1330+ @uses.config.machine_type.gcp.generic
1331+ Scenario Outline: No detaching on manually attached machine on all clouds
1332+ Given a `<release>` machine with ubuntu-advantage-tools installed
1333+ When I attach `contract_token` with sudo
1334+ And I run `ua refresh` with sudo
1335+ Then I will see the following on stdout:
1336+ """
1337+ Successfully processed your ua configuration.
1338+ Successfully refreshed your subscription.
1339+ """
1340+ When I run `ua auto-attach` with sudo
1341+ Then stderr matches regexp:
1342+ """
1343+ Skipping attach: Instance '[0-9a-z\-]+' is already attached.
1344+ """
1345+ When I run `ua status` with sudo
1346+ Then stdout matches regexp:
1347+ """
1348+ esm-infra +yes +<esm-service> +UA Infra: Extended Security Maintenance \(ESM\)
1349+ """
1350+
1351+ Examples: ubuntu release
1352+ | release | esm-service |
1353+ | bionic | enabled |
1354+ | focal | enabled |
1355+ | xenial | enabled |
1356diff --git a/features/enable_fips_container.feature b/features/enable_fips_container.feature
1357new file mode 100644
1358index 0000000..8519480
1359--- /dev/null
1360+++ b/features/enable_fips_container.feature
1361@@ -0,0 +1,144 @@
1362+
1363+@uses.config.contract_token
1364+Feature: FIPS enablement in lxd containers
1365+
1366+ @series.xenial
1367+ @series.bionic
1368+ @series.focal
1369+ @uses.config.machine_type.lxd.container
1370+ Scenario Outline: Attached enable of FIPS in an ubuntu lxd container
1371+ Given a `<release>` machine with ubuntu-advantage-tools installed
1372+ When I attach `contract_token` with sudo
1373+ And I run `DEBIAN_FRONTEND=noninteractive apt-get install -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" -y openssh-client openssh-server strongswan openssl <libssl> libgcrypt20` with sudo, retrying exit [100]
1374+ And I run `ua enable fips<updates>` `with sudo` and stdin `y`
1375+ Then stdout matches regexp:
1376+ """
1377+ Warning: Enabling <fips-name> in a container.
1378+ This will install the FIPS packages but not the kernel.
1379+ This container must run on a host with <fips-name> enabled to be
1380+ compliant.
1381+ Warning: This action can take some time and cannot be undone.
1382+ """
1383+ And stdout matches regexp:
1384+ """
1385+ Updating package lists
1386+ Installing <fips-name> packages
1387+ <fips-name> enabled
1388+ A reboot is required to complete install.
1389+ Please run `apt upgrade` to ensure all FIPS packages are updated to the correct
1390+ version.
1391+ """
1392+ When I run `ua status --all` with sudo
1393+ Then stdout matches regexp:
1394+ """
1395+ fips<updates> +yes enabled
1396+ """
1397+ And stdout matches regexp:
1398+ """
1399+ FIPS support requires system reboot to complete configuration
1400+ """
1401+ And I verify that running `apt update` `with sudo` exits `0`
1402+ And I verify that `openssh-server` is installed from apt source `https://esm.ubuntu.com/fips<updates>/ubuntu <release><updates>/main`
1403+ And I verify that `openssh-client` is installed from apt source `https://esm.ubuntu.com/fips<updates>/ubuntu <release><updates>/main`
1404+ And I verify that `strongswan` is installed from apt source `https://esm.ubuntu.com/fips<updates>/ubuntu <release><updates>/main`
1405+ And I verify that `strongswan-hmac` is installed from apt source `https://esm.ubuntu.com/fips<updates>/ubuntu <release><updates>/main`
1406+ And I verify that `openssl` is installed from apt source `https://esm.ubuntu.com/fips<updates>/ubuntu <release><updates>/main`
1407+ And I verify that `<libssl>` is installed from apt source `https://esm.ubuntu.com/fips<updates>/ubuntu <release><updates>/main`
1408+ And I verify that `<libssl>-hmac` is installed from apt source `https://esm.ubuntu.com/fips<updates>/ubuntu <release><updates>/main`
1409+ And I verify that `<additional-fips-packages>` are installed from apt source `https://esm.ubuntu.com/fips<updates>/ubuntu <release><updates>/main`
1410+ When I reboot the `<release>` machine
1411+ When I run `ua status --all` with sudo
1412+ Then stdout does not match regexp:
1413+ """
1414+ FIPS support requires system reboot to complete configuration
1415+ """
1416+ When I run `ua disable fips<updates>` `with sudo` and stdin `y`
1417+ Then stdout matches regexp:
1418+ """
1419+ This will disable the FIPS entitlement but the FIPS packages will remain installed.
1420+ """
1421+ And stdout matches regexp:
1422+ """
1423+ Updating package lists
1424+ """
1425+ And stdout does not match regexp:
1426+ """
1427+ A reboot is required to complete disable operation
1428+ """
1429+ When I run `ua status --all` with sudo
1430+ Then stdout matches regexp:
1431+ """
1432+ fips<updates> +yes disabled
1433+ """
1434+ Then stdout does not match regexp:
1435+ """
1436+ Disabling FIPS requires system reboot to complete operation
1437+ """
1438+ When I run `apt-cache policy ubuntu-fips` as non-root
1439+ Then stdout does not match regexp:
1440+ """
1441+ .*Installed: \(none\)
1442+ """
1443+ Then I verify that `openssh-server` installed version matches regexp `fips`
1444+ And I verify that `openssh-client` installed version matches regexp `fips`
1445+ And I verify that `strongswan` installed version matches regexp `fips`
1446+ And I verify that `strongswan-hmac` installed version matches regexp `fips`
1447+ And I verify that `openssl` installed version matches regexp `fips`
1448+ And I verify that `<libssl>` installed version matches regexp `fips`
1449+ And I verify that `<libssl>-hmac` installed version matches regexp `fips`
1450+ And I verify that packages `<additional-fips-packages>` installed versions match regexp `fips`
1451+
1452+ Examples: ubuntu release
1453+ | release | fips-name | updates | libssl | additional-fips-packages |
1454+ | xenial | FIPS | | libssl1.0.0 | openssh-server-hmac openssh-client-hmac |
1455+ | xenial | FIPS Updates | -updates | libssl1.0.0 | openssh-server-hmac openssh-client-hmac |
1456+ | bionic | FIPS | | libssl1.1 | openssh-server-hmac openssh-client-hmac libgcrypt20 libgcrypt20-hmac |
1457+ | bionic | FIPS Updates | -updates | libssl1.1 | openssh-server-hmac openssh-client-hmac libgcrypt20 libgcrypt20-hmac |
1458+ | focal | FIPS | | libssl1.1 | libgcrypt20 libgcrypt20-hmac |
1459+ | focal | FIPS Updates | -updates | libssl1.1 | libgcrypt20 libgcrypt20-hmac |
1460+
1461+ @series.xenial
1462+ @series.bionic
1463+ @series.focal
1464+ @uses.config.machine_type.lxd.container
1465+ Scenario Outline: Try to enable FIPS after FIPS Updates in a lxd container
1466+ Given a `<release>` machine with ubuntu-advantage-tools installed
1467+ When I attach `contract_token` with sudo
1468+ When I run `ua status --all` with sudo
1469+ Then stdout matches regexp:
1470+ """
1471+ fips-updates +yes +disabled
1472+ """
1473+ And stdout matches regexp:
1474+ """
1475+ fips +yes +disabled
1476+ """
1477+ When I run `ua enable fips-updates --assume-yes` with sudo
1478+ When I run `ua status --all` with sudo
1479+ Then stdout matches regexp:
1480+ """
1481+ fips-updates +yes +enabled
1482+ """
1483+ And stdout matches regexp:
1484+ """
1485+ fips +yes +n/a
1486+ """
1487+ When I verify that running `ua enable fips --assume-yes` `with sudo` exits `1`
1488+ Then stdout matches regexp:
1489+ """
1490+ Cannot enable FIPS when FIPS Updates is enabled.
1491+ """
1492+ When I run `ua status --all` with sudo
1493+ Then stdout matches regexp:
1494+ """
1495+ fips-updates +yes +enabled
1496+ """
1497+ And stdout matches regexp:
1498+ """
1499+ fips +yes +n/a
1500+ """
1501+ Examples: ubuntu release
1502+ | release |
1503+ | xenial |
1504+ | bionic |
1505+ | focal |
1506diff --git a/features/enable_fips_vm.feature b/features/enable_fips_vm.feature
1507index b401bf2..a055354 100644
1508--- a/features/enable_fips_vm.feature
1509+++ b/features/enable_fips_vm.feature
1510@@ -8,7 +8,12 @@ Feature: FIPS enablement in lxd VMs
1511 Scenario Outline: Attached enable of FIPS in an ubuntu lxd vm
1512 Given a `<release>` machine with ubuntu-advantage-tools installed
1513 When I attach `contract_token` with sudo
1514- And I run `ua disable livepatch` with sudo
1515+ When I run `ua status --format json` with sudo
1516+ Then stdout contains substring
1517+ """
1518+ {"available": "yes", "blocked_by": [{"name": "livepatch", "reason": "Livepatch cannot be enabled while running the official FIPS certified kernel. If you would like a FIPS compliant kernel with additional bug fixes and security updates, you can use the FIPS Updates service with Livepatch.", "reason_code": "livepatch-invalidates-fips"}], "description": "NIST-certified core packages", "description_override": null, "entitled": "yes", "name": "fips", "status": "disabled", "status_details": "FIPS is not configured"}
1519+ """
1520+ When I run `ua disable livepatch` with sudo
1521 And I run `DEBIAN_FRONTEND=noninteractive apt-get install -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" -y openssh-client openssh-server strongswan` with sudo, retrying exit [100]
1522 And I run `apt-mark hold openssh-client openssh-server strongswan` with sudo
1523 And I run `ua enable <fips-service>` `with sudo` and stdin `y`
1524@@ -40,6 +45,12 @@ Feature: FIPS enablement in lxd VMs
1525 And I verify that `openssh-server-hmac` is installed from apt source `<fips-apt-source>`
1526 And I verify that `openssh-client-hmac` is installed from apt source `<fips-apt-source>`
1527 And I verify that `strongswan-hmac` is installed from apt source `<fips-apt-source>`
1528+ When I run `ua status --format json` with sudo
1529+ Then stdout contains substring:
1530+ """
1531+ {"available": "yes", "blocked_by": [{"name": "fips", "reason": "Livepatch cannot be enabled while running the official FIPS certified kernel. If you would like a FIPS compliant kernel with additional bug fixes and security updates, you can use the FIPS Updates service with Livepatch.", "reason_code": "livepatch-invalidates-fips"}], "description": "Canonical Livepatch service", "description_override": null, "entitled": "yes", "name": "livepatch", "status": "n/a", "status_details": "Cannot enable Livepatch when FIPS is enabled."}
1532+ """
1533+
1534 When I reboot the `<release>` machine
1535 And I run `uname -r` as non-root
1536 Then stdout matches regexp:
1537@@ -99,6 +110,24 @@ Feature: FIPS enablement in lxd VMs
1538 """
1539 Disabling FIPS requires system reboot to complete operation
1540 """
1541+ When I run `ua enable <fips-service> --assume-yes --format json --assume-yes` with sudo
1542+ Then stdout is a json matching the `ua_operation` schema
1543+ And I will see the following on stdout:
1544+ """
1545+ {"_schema_version": "0.1", "errors": [], "failed_services": [], "needs_reboot": true, "processed_services": ["<fips-service>"], "result": "success", "warnings": []}
1546+ """
1547+ When I reboot the `<release>` machine
1548+ And I run `ua disable <fips-service> --assume-yes --format json` with sudo
1549+ Then stdout is a json matching the `ua_operation` schema
1550+ And I will see the following on stdout:
1551+ """
1552+ {"_schema_version": "0.1", "errors": [], "failed_services": [], "needs_reboot": true, "processed_services": ["<fips-service>"], "result": "success", "warnings": []}
1553+ """
1554+ When I run `ua status --all` with sudo
1555+ Then stdout matches regexp:
1556+ """
1557+ <fips-service> +yes disabled
1558+ """
1559
1560 Examples: ubuntu release
1561 | release | fips-name | fips-service |fips-apt-source |
1562@@ -139,6 +168,12 @@ Feature: FIPS enablement in lxd VMs
1563 And I verify that `openssh-server-hmac` is installed from apt source `<fips-apt-source>`
1564 And I verify that `openssh-client-hmac` is installed from apt source `<fips-apt-source>`
1565 And I verify that `strongswan-hmac` is installed from apt source `<fips-apt-source>`
1566+ When I run `ua status --format json` with sudo
1567+ Then stdout contains substring:
1568+ """
1569+ {"available": "yes", "blocked_by": [{"name": "fips-updates", "reason": "FIPS cannot be enabled if FIPS Updates has ever been enabled because FIPS Updates installs security patches that aren't officially certified.", "reason_code": "fips-updates-invalidates-fips"}], "description": "NIST-certified core packages", "description_override": null, "entitled": "yes", "name": "fips", "status": "n/a", "status_details": "Cannot enable FIPS when FIPS Updates is enabled."}
1570+ """
1571+
1572 When I reboot the `<release>` machine
1573 And I run `uname -r` as non-root
1574 Then stdout matches regexp:
1575@@ -207,6 +242,30 @@ Feature: FIPS enablement in lxd VMs
1576 """
1577 livepatch +yes +enabled
1578 """
1579+ When I run `ua status --format json` with sudo
1580+ Then stdout contains substring:
1581+ """
1582+ {"available": "yes", "blocked_by": [{"name": "livepatch", "reason": "Livepatch cannot be enabled while running the official FIPS certified kernel. If you would like a FIPS compliant kernel with additional bug fixes and security updates, you can use the FIPS Updates service with Livepatch.", "reason_code": "livepatch-invalidates-fips"}, {"name": "fips-updates", "reason": "FIPS cannot be enabled if FIPS Updates has ever been enabled because FIPS Updates installs security patches that aren't officially certified.", "reason_code": "fips-updates-invalidates-fips"}], "description": "NIST-certified core packages", "description_override": null, "entitled": "yes", "name": "fips", "status": "n/a", "status_details": "Cannot enable FIPS when FIPS Updates is enabled."}
1583+ """
1584+ When I run `ua disable <fips-service> --assume-yes` with sudo
1585+ And I run `ua enable <fips-service> --assume-yes --format json --assume-yes` with sudo
1586+ Then stdout is a json matching the `ua_operation` schema
1587+ And I will see the following on stdout:
1588+ """
1589+ {"_schema_version": "0.1", "errors": [], "failed_services": [], "needs_reboot": true, "processed_services": ["<fips-service>"], "result": "success", "warnings": []}
1590+ """
1591+ When I reboot the `<release>` machine
1592+ And I run `ua disable <fips-service> --assume-yes --format json` with sudo
1593+ Then stdout is a json matching the `ua_operation` schema
1594+ And I will see the following on stdout:
1595+ """
1596+ {"_schema_version": "0.1", "errors": [], "failed_services": [], "needs_reboot": true, "processed_services": ["<fips-service>"], "result": "success", "warnings": []}
1597+ """
1598+ When I run `ua status --all` with sudo
1599+ Then stdout matches regexp:
1600+ """
1601+ <fips-service> +yes disabled
1602+ """
1603
1604 Examples: ubuntu release
1605 | release | fips-name | fips-service |fips-apt-source |
1606diff --git a/features/install_uninstall.feature b/features/install_uninstall.feature
1607index 66426c8..078b1b5 100644
1608--- a/features/install_uninstall.feature
1609+++ b/features/install_uninstall.feature
1610@@ -12,7 +12,6 @@ Feature: UA Install and Uninstall related tests
1611 | xenial |
1612 | bionic |
1613 | focal |
1614- | hirsute |
1615 | impish |
1616 | jammy |
1617
1618diff --git a/features/license_check.feature b/features/license_check.feature
1619index 01d4425..b368360 100644
1620--- a/features/license_check.feature
1621+++ b/features/license_check.feature
1622@@ -38,8 +38,9 @@ Feature: License check timer only runs in environments where necessary
1623 | xenial |
1624 | bionic |
1625 | focal |
1626+ | jammy |
1627
1628- @series.hirsute
1629+ @series.impish
1630 @uses.config.contract_token
1631 @uses.config.machine_type.gcp.generic
1632 Scenario Outline: license_check is disabled gcp generic non lts
1633@@ -59,7 +60,7 @@ Feature: License check timer only runs in environments where necessary
1634 Then I verify the `ua-license-check` systemd timer is disabled
1635 Examples: version
1636 | release |
1637- | hirsute |
1638+ | impish |
1639
1640 @series.all
1641 @uses.config.contract_token
1642@@ -91,7 +92,6 @@ Feature: License check timer only runs in environments where necessary
1643 | xenial |
1644 | bionic |
1645 | focal |
1646- | hirsute |
1647 | impish |
1648 | jammy |
1649
1650diff --git a/features/proxy_config.feature b/features/proxy_config.feature
1651index 7ee5c5a..30f03cf 100644
1652--- a/features/proxy_config.feature
1653+++ b/features/proxy_config.feature
1654@@ -24,12 +24,23 @@ Feature: Proxy configuration
1655 https_proxy: http://<ci-proxy-ip>:3128
1656 """
1657 And I verify `/var/log/squid/access.log` is empty on `proxy` machine
1658- And I attach `contract_token` with sudo
1659+ # We need this for the route command
1660+ And I run `apt-get install net-tools` with sudo
1661+ # We will guarantee that the machine will only use the proxy when
1662+ # running the ua commands
1663+ And I run `route del default` with sudo
1664+ And I attach `contract_token` with sudo and options `--no-auto-enable`
1665 And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine
1666 Then stdout matches regexp:
1667 """
1668 .*CONNECT contracts.canonical.com.*
1669 """
1670+ When I run `ua status` with sudo
1671+ # Just to verify that the machine is attached
1672+ Then stdout matches regexp:
1673+ """
1674+ esm-infra +yes +disabled +UA Infra: Extended Security Maintenance \(ESM\)
1675+ """
1676 When I run `truncate -s 0 /var/log/squid/access.log` `with sudo` on the `proxy` machine
1677 When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following:
1678 """
1679@@ -139,8 +150,12 @@ Feature: Proxy configuration
1680 And I run `systemctl restart squid.service` `with sudo` on the `proxy` machine
1681 And I run `ua config set http_proxy=http://<ci-proxy-ip>:3128` with sudo
1682 And I run `ua config set https_proxy=http://<ci-proxy-ip>:3128` with sudo
1683- And I verify `/var/log/squid/access.log` is empty on `proxy` machine
1684- And I attach `contract_token` with sudo
1685+ And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine
1686+ Then stdout matches regexp:
1687+ """
1688+ .*CONNECT api.snapcraft.io.*
1689+ """
1690+ When I attach `contract_token` with sudo
1691 Then stdout matches regexp:
1692 """
1693 Setting snap proxy
1694@@ -296,8 +311,12 @@ Feature: Proxy configuration
1695 And I run `systemctl restart squid.service` `with sudo` on the `proxy` machine
1696 And I run `ua config set http_proxy=http://someuser:somepassword@<ci-proxy-ip>:3128` with sudo
1697 And I run `ua config set https_proxy=http://someuser:somepassword@<ci-proxy-ip>:3128` with sudo
1698- And I verify `/var/log/squid/access.log` is empty on `proxy` machine
1699- And I attach `contract_token` with sudo
1700+ And I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine
1701+ Then stdout matches regexp:
1702+ """
1703+ .*CONNECT api.snapcraft.io:443.*
1704+ """
1705+ When I attach `contract_token` with sudo
1706 Then stdout matches regexp:
1707 """
1708 Setting snap proxy
1709diff --git a/features/schemas/ua_operation.json b/features/schemas/ua_operation.json
1710new file mode 100644
1711index 0000000..2afb7c8
1712--- /dev/null
1713+++ b/features/schemas/ua_operation.json
1714@@ -0,0 +1,76 @@
1715+{
1716+ "type": "object",
1717+ "properties": {
1718+ "_schema_version": {
1719+ "type": "string",
1720+ "const": "0.1"
1721+ },
1722+ "result": {
1723+ "type": "string",
1724+ "enum": ["success", "failure"]
1725+ },
1726+ "errors": {
1727+ "type": "array",
1728+ "items": {
1729+ "type": "object",
1730+ "required": [ "message", "service", "type" ],
1731+ "properties": {
1732+ "message": {
1733+ "type": "string"
1734+ },
1735+ "message_code": {
1736+ "type": ["null", "string"]
1737+ },
1738+ "service": {
1739+ "type": ["null", "string"]
1740+ }
1741+ },
1742+ "patternProperties": {
1743+ "^type$": {
1744+ "type": "string",
1745+ "enum": ["service", "system"]
1746+ }
1747+ }
1748+ }
1749+ },
1750+ "warnings": {
1751+ "type": "array",
1752+ "items": {
1753+ "type": "object",
1754+ "required": [ "message", "service", "type" ],
1755+ "properties": {
1756+ "message": {
1757+ "type": "string"
1758+ },
1759+ "message_code": {
1760+ "type": ["null", "string"]
1761+ },
1762+ "service": {
1763+ "type": ["null", "string"]
1764+ }
1765+ },
1766+ "patternProperties": {
1767+ "^type$": {
1768+ "type": "string",
1769+ "enum": ["service", "system"]
1770+ }
1771+ }
1772+ }
1773+ },
1774+ "failed_services": {
1775+ "type": "array",
1776+ "items": {
1777+ "type": "string"
1778+ }
1779+ },
1780+ "processed_services": {
1781+ "type": "array",
1782+ "items": {
1783+ "type": "string"
1784+ }
1785+ },
1786+ "needs_reboot": {
1787+ "type": "boolean"
1788+ }
1789+ }
1790+}
1791diff --git a/features/schemas/ua_security_status.json b/features/schemas/ua_security_status.json
1792new file mode 100644
1793index 0000000..e099976
1794--- /dev/null
1795+++ b/features/schemas/ua_security_status.json
1796@@ -0,0 +1,81 @@
1797+{
1798+ "type": "object",
1799+ "properties": {
1800+ "_schema_version": {
1801+ "type": "string",
1802+ "const": "0.1"
1803+ },
1804+ "summary": {
1805+ "type": "object",
1806+ "properties": {
1807+ "ua": {
1808+ "type": "object",
1809+ "properties": {
1810+ "attached": {
1811+ "type": "boolean"
1812+ },
1813+ "enabled_services": {
1814+ "type": "array",
1815+ "items": {
1816+ "type": "string"
1817+ }
1818+ },
1819+ "entitled_services": {
1820+ "type": "array",
1821+ "items": {
1822+ "type": "string"
1823+ }
1824+ }
1825+ }
1826+ },
1827+ "num_installed_packages": {
1828+ "type": "integer"
1829+ },
1830+ "num_esm_infra_packages": {
1831+ "type": "integer"
1832+ },
1833+ "num_esm_apps_packages": {
1834+ "type": "integer"
1835+ },
1836+ "num_esm_infra_updates": {
1837+ "type": "integer"
1838+ },
1839+ "num_esm_apps_updates": {
1840+ "type": "integer"
1841+ },
1842+ "num_standard_security_updates": {
1843+ "type": "integer"
1844+ }
1845+ }
1846+ },
1847+ "packages": {
1848+ "type": "array",
1849+ "items": {
1850+ "type": "object",
1851+ "properties": {
1852+ "package": {
1853+ "type": "string"
1854+ },
1855+ "version": {
1856+ "type": "string"
1857+ },
1858+ "service_name": {
1859+ "type": "string"
1860+ },
1861+ "origin": {
1862+ "type": "string"
1863+ },
1864+ "status": {
1865+ "type": "string",
1866+ "enum": [
1867+ "upgrade_available",
1868+ "pending_attach",
1869+ "pending_enable",
1870+ "upgrade_unavailable"
1871+ ]
1872+ }
1873+ }
1874+ }
1875+ }
1876+ }
1877+}
1878diff --git a/features/schemas/ua_status.json b/features/schemas/ua_status.json
1879new file mode 100644
1880index 0000000..ae9fd69
1881--- /dev/null
1882+++ b/features/schemas/ua_status.json
1883@@ -0,0 +1,247 @@
1884+{
1885+ "type": "object",
1886+ "properties": {
1887+ "_doc": {
1888+ "type": "string"
1889+ },
1890+ "_schema_version": {
1891+ "type": "string",
1892+ "const": "0.1"
1893+ },
1894+ "version": {
1895+ "type": "string"
1896+ },
1897+ "result": {
1898+ "type": "string",
1899+ "enum": ["success", "failure"]
1900+ },
1901+ "errors": {
1902+ "type": "array",
1903+ "items": {
1904+ "type": "object",
1905+ "required": [ "message", "service", "type" ],
1906+ "properties": {
1907+ "message": {
1908+ "type": "string"
1909+ },
1910+ "service": {
1911+ "type": ["null", "string"]
1912+ }
1913+ },
1914+ "patternProperties": {
1915+ "^type$": {
1916+ "type": "string",
1917+ "enum": ["service", "system"]
1918+ }
1919+ }
1920+ }
1921+ },
1922+ "warnings": {
1923+ "type": "array",
1924+ "items": {
1925+ "type": "object",
1926+ "required": [ "message", "service", "type" ],
1927+ "properties": {
1928+ "message": {
1929+ "type": "string"
1930+ },
1931+ "service": {
1932+ "type": ["null", "string"]
1933+ }
1934+ },
1935+ "patternProperties": {
1936+ "^type$": {
1937+ "type": "string",
1938+ "enum": ["service", "system"]
1939+ }
1940+ }
1941+ }
1942+ },
1943+ "attached": {
1944+ "type": "boolean"
1945+ },
1946+ "machine_id": {
1947+ "type": ["null", "string"]
1948+ },
1949+ "effective": {
1950+ "type": ["null", "string"]
1951+ },
1952+ "expires": {
1953+ "type": ["null", "string"]
1954+ },
1955+ "execution_status": {
1956+ "type": "string",
1957+ "enum": ["active", "inactive", "reboot-required"]
1958+ },
1959+ "execution_details": {
1960+ "type": "string"
1961+ },
1962+ "simulated": {
1963+ "type": "boolean"
1964+ },
1965+ "services": {
1966+ "type": "array",
1967+ "items": {
1968+ "type": "object",
1969+ "properties": {
1970+ "name": {
1971+ "type": "string"
1972+ },
1973+ "description": {
1974+ "type": "string"
1975+ },
1976+ "available": {
1977+ "type": "string",
1978+ "enum": ["yes", "no"]
1979+ },
1980+ "entitled": {
1981+ "type": "string",
1982+ "enum": ["yes", "no"]
1983+ },
1984+ "status": {
1985+ "type": "string",
1986+ "enum": ["enabled", "disabled", "n/a"]
1987+ },
1988+ "status_details": {
1989+ "type": "string"
1990+ },
1991+ "description_override": {
1992+ "type": ["null", "string"]
1993+ },
1994+ "blocked_by": {
1995+ "type": "array",
1996+ "items": {
1997+ "type": "object",
1998+ "properties": {
1999+ "name": {
2000+ "type": "string"
2001+ },
2002+ "reason": {
2003+ "type": "string"
2004+ },
2005+ "reason_code": {
2006+ "type": "string"
2007+ }
2008+ }
2009+ }
2010+ }
2011+ }
2012+ }
2013+ },
2014+ "notices": {
2015+ "type": "array",
2016+ "items": {
2017+ "type": "string"
2018+ }
2019+ },
2020+ "config_path": {
2021+ "type": "string"
2022+ },
2023+ "environment_vars": {
2024+ "type": "array",
2025+ "items": {
2026+ "type": "object",
2027+ "properties": {
2028+ "name": {
2029+ "type": "string"
2030+ },
2031+ "value": {
2032+ "type": "string"
2033+ }
2034+ }
2035+ }
2036+ },
2037+ "contract": {
2038+ "type": "object",
2039+ "properties": {
2040+ "id": {
2041+ "type": "string"
2042+ },
2043+ "name": {
2044+ "type": "string"
2045+ },
2046+ "created_at": {
2047+ "type": "string"
2048+ },
2049+ "products": {
2050+ "type": "array",
2051+ "items": {
2052+ "type": "string"
2053+ }
2054+ },
2055+ "tech_support_level": {
2056+ "type": "string"
2057+ }
2058+ }
2059+ },
2060+ "account": {
2061+ "type": "object",
2062+ "properties": {
2063+ "id": {
2064+ "type": "string"
2065+ },
2066+ "name": {
2067+ "type": "string"
2068+ },
2069+ "created_at": {
2070+ "type": "string"
2071+ },
2072+ "external_account_ids": {
2073+ "type": "array"
2074+ }
2075+ }
2076+ },
2077+ "config": {
2078+ "type": "object",
2079+ "properties": {
2080+ "contract_url": {
2081+ "type": "string"
2082+ },
2083+ "security_url": {
2084+ "type": "string"
2085+ },
2086+ "data_dir": {
2087+ "type": "string"
2088+ },
2089+ "log_level": {
2090+ "type": "string"
2091+ },
2092+ "log_file": {
2093+ "type": "string"
2094+ },
2095+ "timer_log_file": {
2096+ "type": "string"
2097+ },
2098+ "license_check_log_file": {
2099+ "type": "string"
2100+ },
2101+ "ua_config": {
2102+ "type": "object",
2103+ "properties": {
2104+ "apt_http_proxy": {
2105+ "type": ["null", "string"]
2106+ },
2107+ "apt_https_proxy": {
2108+ "type": ["null", "string"]
2109+ },
2110+ "http_proxy": {
2111+ "type": ["null", "string"]
2112+ },
2113+ "https_proxy": {
2114+ "type": ["null", "string"]
2115+ },
2116+ "update_messaging_timer": {
2117+ "type": "integer"
2118+ },
2119+ "update_status_timer": {
2120+ "type": "integer"
2121+ },
2122+ "metering_timer": {
2123+ "type": "integer"
2124+ }
2125+ }
2126+ }
2127+ }
2128+ }
2129+ }
2130+}
2131diff --git a/features/steps/steps.py b/features/steps/steps.py
2132index 2c0d5bf..e63f625 100644
2133--- a/features/steps/steps.py
2134+++ b/features/steps/steps.py
2135@@ -6,6 +6,7 @@ import shlex
2136 import subprocess
2137 import time
2138
2139+import jsonschema # type: ignore
2140 import yaml
2141 from behave import given, then, when
2142 from hamcrest import (
2143@@ -20,7 +21,12 @@ from features.environment import (
2144 capture_container_as_image,
2145 create_instance_with_uat_installed,
2146 )
2147-from features.util import SLOW_CMDS, emit_spinner_on_travis, nullcontext
2148+from features.util import (
2149+ SLOW_CMDS,
2150+ SafeLoaderWithoutDatetime,
2151+ emit_spinner_on_travis,
2152+ nullcontext,
2153+)
2154 from uaclient.defaults import DEFAULT_CONFIG_FILE, DEFAULT_MACHINE_TOKEN_PATH
2155 from uaclient.util import DatetimeAwareJSONDecoder
2156
2157@@ -191,7 +197,7 @@ def when_i_run_command_on_machine(context, command, user_spec, instance_name):
2158
2159 @when("I verify `{file_name}` is empty on `{instance_name}` machine")
2160 def when_i_verify_file_is_empty_on_machine(context, file_name, instance_name):
2161- command = 'sh -c "cat {} | wc -l"'
2162+ command = 'sh -c "cat {} | wc -l"'.format(file_name)
2163 when_i_run_command(
2164 context, command, user_spec="with sudo", instance_name=instance_name
2165 )
2166@@ -230,8 +236,8 @@ def when_i_run_command_with_stdin(
2167
2168
2169 @when("I do a preflight check for `{contract_token}` {user_spec}")
2170-def when_i_preflight(context, contract_token, user_spec):
2171- token = getattr(context.config, contract_token)
2172+def when_i_preflight(context, contract_token, user_spec, verify_return=True):
2173+ token = getattr(context.config, contract_token, "invalid_token")
2174 command = "ua status --simulate-with-token {}".format(token)
2175 if user_spec == "with the all flag":
2176 command += " --all"
2177@@ -239,10 +245,23 @@ def when_i_preflight(context, contract_token, user_spec):
2178 output_format = user_spec.split()[2]
2179 command += " --format {}".format(output_format)
2180 when_i_run_command(
2181- context=context, command=command, user_spec="as non-root"
2182+ context=context,
2183+ command=command,
2184+ user_spec="as non-root",
2185+ verify_return=verify_return,
2186 )
2187
2188
2189+@when(
2190+ "I verify that a preflight check for `{contract_token}` {user_spec} exits {exit_codes}" # noqa
2191+)
2192+def when_i_attempt_preflight(context, contract_token, user_spec, exit_codes):
2193+ when_i_preflight(context, contract_token, user_spec, verify_return=False)
2194+
2195+ expected_codes = exit_codes.split(",")
2196+ assert str(context.process.returncode) in expected_codes
2197+
2198+
2199 @when("I run `{command}` {user_spec}")
2200 def when_i_run_command(
2201 context,
2202@@ -280,6 +299,9 @@ def when_i_run_command(
2203 logging.error("Error executing command: {}".format(command))
2204 logging.error("stdout: {}".format(result.stdout))
2205 logging.error("stderr: {}".format(result.stderr))
2206+ else:
2207+ logging.debug("stdout: {}".format(result.stdout))
2208+ logging.debug("stderr: {}".format(result.stderr))
2209
2210 if verify_return:
2211 assert_that(process.returncode, equal_to(0))
2212@@ -290,11 +312,21 @@ def when_i_run_command(
2213 @when("I fix `{issue}` by attaching to a subscription with `{token_type}`")
2214 def when_i_fix_a_issue_by_attaching(context, issue, token_type):
2215 token = getattr(context.config, token_type)
2216+
2217+ if (
2218+ token_type == "contract_token_staging"
2219+ or token_type == "contract_token_staging_expired"
2220+ ):
2221+ change_contract_endpoint_to_staging(context, user_spec="with sudo")
2222+ else:
2223+ change_contract_endpoint_to_production(context, user_spec="with sudo")
2224+
2225 when_i_run_command(
2226 context=context,
2227 command="ua fix {}".format(issue),
2228 user_spec="with sudo",
2229 stdin="a\n{}\n".format(token),
2230+ verify_return=False,
2231 )
2232
2233
2234@@ -340,23 +372,46 @@ def when_i_update_contract_field_to_new_value(
2235 )
2236
2237
2238+def change_contract_endpoint_to_staging(context, user_spec):
2239+ when_i_run_command(
2240+ context,
2241+ "sed -i 's/contracts.can/contracts.staging.can/' {}".format(
2242+ DEFAULT_CONFIG_FILE
2243+ ),
2244+ user_spec,
2245+ )
2246+
2247+
2248+def change_contract_endpoint_to_production(context, user_spec):
2249+ when_i_run_command(
2250+ context,
2251+ "sed -i 's/contracts.staging.can/contracts.can/' {}".format(
2252+ DEFAULT_CONFIG_FILE
2253+ ),
2254+ user_spec,
2255+ )
2256+
2257+
2258+@when("I attach `{token_type}` {user_spec} and options `{options}`")
2259+def when_i_attach_staging_token_with_options(
2260+ context, token_type, user_spec, options
2261+):
2262+ when_i_attach_staging_token(
2263+ context, token_type, user_spec, options=options
2264+ )
2265+
2266+
2267 @when("I attach `{token_type}` {user_spec}")
2268 def when_i_attach_staging_token(
2269- context, token_type, user_spec, verify_return=True
2270+ context, token_type, user_spec, verify_return=True, options=""
2271 ):
2272 token = getattr(context.config, token_type)
2273 if (
2274 token_type == "contract_token_staging"
2275 or token_type == "contract_token_staging_expired"
2276 ):
2277- when_i_run_command(
2278- context,
2279- "sed -i 's/contracts.can/contracts.staging.can/' {}".format(
2280- DEFAULT_CONFIG_FILE
2281- ),
2282- user_spec,
2283- )
2284- cmd = "ua attach {}".format(token)
2285+ change_contract_endpoint_to_staging(context, user_spec)
2286+ cmd = "ua attach {} {}".format(token, options).strip()
2287 when_i_run_command(context, cmd, user_spec, verify_return=False)
2288
2289 if verify_return:
2290@@ -420,6 +475,25 @@ def when_i_wait(context, seconds):
2291 time.sleep(int(seconds))
2292
2293
2294+@when("I replace `{original}` in `{filename}` with `{new}`")
2295+def when_i_replace_string_in_file(context, original, filename, new):
2296+ when_i_run_command(
2297+ context,
2298+ "sed -i 's/{original}/{new}/' {filename}".format(
2299+ original=original, new=new, filename=filename
2300+ ),
2301+ "with sudo",
2302+ )
2303+
2304+
2305+@when("I replace `{original}` in `{filename}` with token `{token_name}`")
2306+def when_i_replace_string_in_file_with_token(
2307+ context, original, filename, token_name
2308+):
2309+ token = getattr(context.config, token_name)
2310+ when_i_replace_string_in_file(context, original, filename, token)
2311+
2312+
2313 @then("I will see the following on stdout")
2314 def then_i_will_see_on_stdout(context):
2315 assert_that(context.process.stdout.strip(), equal_to(context.text))
2316@@ -439,27 +513,6 @@ def then_conditional_stdout_does_not_match_regexp(context, value1, value2):
2317 then_stream_does_not_match_regexp(context, "stdout")
2318
2319
2320-@then("stdout is formatted as `{output_format}` and has keys")
2321-def then_stdout_is_formatted_and_has_keys(context, output_format):
2322- output = context.process.stdout.strip()
2323- if output_format == "json":
2324- data = json.loads(output)
2325- elif output_format == "yaml":
2326- data = yaml.safe_load(output)
2327-
2328- keys = set(context.text.split())
2329- output_keys = set(data.keys())
2330-
2331- if keys != output_keys:
2332- message = """
2333- Missing keys in output: {}
2334- Extra keys in output: {}
2335- """.format(
2336- keys - output_keys or "", output_keys - keys or ""
2337- )
2338- raise AssertionError(message)
2339-
2340-
2341 @then("{stream} does not match regexp")
2342 def then_stream_does_not_match_regexp(context, stream):
2343 content = getattr(context.process, stream).strip()
2344@@ -472,6 +525,12 @@ def then_stream_matches_regexp(context, stream):
2345 assert_that(content, matches_regexp(context.text))
2346
2347
2348+@then("{stream} contains substring")
2349+def then_stream_contains_substring(context, stream):
2350+ content = getattr(context.process, stream).strip()
2351+ assert_that(content, contains_string(context.text))
2352+
2353+
2354 @then("I will see the following on stderr")
2355 def then_i_will_see_on_stderr(context):
2356 assert_that(context.process.stderr.strip(), equal_to(context.text))
2357@@ -521,6 +580,16 @@ def then_i_verify_that_running_cmd_with_spec_exits_with_codes(
2358
2359
2360 @when(
2361+ "I verify that running attach `{spec}` with json response exits `{exit_codes}`" # noqa
2362+)
2363+def when_i_verify_attach_with_json_response(context, spec, exit_codes):
2364+ cmd = "ua attach {} --format json".format(context.config.contract_token)
2365+ then_i_verify_that_running_cmd_with_spec_exits_with_codes(
2366+ context=context, cmd_name=cmd, spec=spec, exit_codes=exit_codes
2367+ )
2368+
2369+
2370+@when(
2371 "I verify that running `{cmd_name}` `{spec}` and stdin `{stdin}` exits `{exit_codes}`" # noqa
2372 )
2373 def then_i_verify_that_running_cmd_with_spec_and_stdin_exits_with_codes(
2374@@ -581,6 +650,16 @@ def verify_installed_package_matches_version_regexp(context, package, regex):
2375 assert_that(context.process.stdout.strip(), matches_regexp(regex))
2376
2377
2378+@then(
2379+ "I verify that packages `{packages}` installed versions match regexp `{regex}`" # noqa: E501
2380+)
2381+def verify_installed_packages_match_version_regexp(context, packages, regex):
2382+ for package in packages.split(" "):
2383+ verify_installed_package_matches_version_regexp(
2384+ context, package, regex
2385+ )
2386+
2387+
2388 @then("I verify that `{package}` is installed from apt source `{apt_source}`")
2389 def verify_package_is_installed_from_apt_source(context, package, apt_source):
2390 when_i_run_command(
2391@@ -609,6 +688,18 @@ def verify_package_is_installed_from_apt_source(context, package, apt_source):
2392 )
2393
2394
2395+@then(
2396+ "I verify that `{packages}` are installed from apt source `{apt_source}`"
2397+)
2398+def verify_packages_are_installed_from_apt_source(
2399+ context, packages, apt_source
2400+):
2401+ for package in packages.split(" "):
2402+ verify_package_is_installed_from_apt_source(
2403+ context, package, apt_source
2404+ )
2405+
2406+
2407 @then("I verify that the timer interval for `{job}` is `{interval}`")
2408 def verify_timer_interval_for_job(context, job, interval):
2409 when_i_run_command(
2410@@ -779,6 +870,18 @@ def i_restore_the_saved_key_value_on_contract(context, key):
2411 )
2412
2413
2414+@then("stdout is a {output_format} matching the `{schema}` schema")
2415+def stdout_matches_the_json_schema(context, output_format, schema):
2416+ if output_format == "json":
2417+ instance = json.loads(context.process.stdout.strip())
2418+ elif output_format == "yaml":
2419+ instance = yaml.load(
2420+ context.process.stdout.strip(), SafeLoaderWithoutDatetime
2421+ )
2422+ with open("features/schemas/{}.json".format(schema), "r") as schema_file:
2423+ jsonschema.validate(instance=instance, schema=json.load(schema_file))
2424+
2425+
2426 def get_command_prefix_for_user_spec(user_spec):
2427 prefix = []
2428 if user_spec == "with sudo":
2429diff --git a/features/ubuntu_pro.feature b/features/ubuntu_pro.feature
2430index 95d54e1..04ef4fc 100644
2431--- a/features/ubuntu_pro.feature
2432+++ b/features/ubuntu_pro.feature
2433@@ -40,6 +40,22 @@ Feature: Command behaviour when auto-attached in an ubuntu PRO image
2434 """
2435 <cis_or_usg> +yes +<cis-s> +Security compliance and audit tools
2436 """
2437+ When I run `ua enable <cis_or_usg>` with sudo
2438+ And I run `ua status` with sudo
2439+ Then stdout matches regexp:
2440+ """
2441+ <cis_or_usg> +yes +enabled +Security compliance and audit tools
2442+ """
2443+ When I run `ua disable <cis_or_usg>` with sudo
2444+ Then stdout matches regexp:
2445+ """
2446+ Updating package lists
2447+ """
2448+ When I run `ua status` with sudo
2449+ Then stdout matches regexp:
2450+ """
2451+ <cis_or_usg> +yes +disabled +Security compliance and audit tools
2452+ """
2453 When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine
2454 Then stdout matches regexp:
2455 """
2456@@ -53,7 +69,7 @@ Feature: Command behaviour when auto-attached in an ubuntu PRO image
2457 | release | fips-s | cc-eal-s | cis-s | cis_or_usg |
2458 | xenial | disabled | disabled | disabled | cis |
2459 | bionic | disabled | disabled | disabled | cis |
2460- | focal | n/a | n/a | disabled | usg |
2461+ | focal | disabled | n/a | disabled | usg |
2462
2463 @series.lts
2464 @uses.config.machine_type.azure.pro
2465@@ -95,6 +111,22 @@ Feature: Command behaviour when auto-attached in an ubuntu PRO image
2466 """
2467 <cis_or_usg> +yes +<cis-s> +Security compliance and audit tools
2468 """
2469+ When I run `ua enable <cis_or_usg>` with sudo
2470+ And I run `ua status` with sudo
2471+ Then stdout matches regexp:
2472+ """
2473+ <cis_or_usg> +yes +enabled +Security compliance and audit tools
2474+ """
2475+ When I run `ua disable <cis_or_usg>` with sudo
2476+ Then stdout matches regexp:
2477+ """
2478+ Updating package lists
2479+ """
2480+ When I run `ua status` with sudo
2481+ Then stdout matches regexp:
2482+ """
2483+ <cis_or_usg> +yes +disabled +Security compliance and audit tools
2484+ """
2485 When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine
2486 Then stdout matches regexp:
2487 """
2488@@ -107,8 +139,8 @@ Feature: Command behaviour when auto-attached in an ubuntu PRO image
2489 Examples: ubuntu release
2490 | release | fips-s | cc-eal-s | cis-s | livepatch-s | cis_or_usg |
2491 | xenial | n/a | disabled | disabled | enabled | cis |
2492- | bionic | disabled | disabled | disabled | n/a | cis |
2493- | focal | n/a | n/a | disabled | enabled | usg |
2494+ | bionic | disabled | disabled | disabled | enabled | cis |
2495+ | focal | disabled | n/a | disabled | enabled | usg |
2496
2497 @series.lts
2498 @uses.config.machine_type.gcp.pro
2499@@ -150,6 +182,22 @@ Feature: Command behaviour when auto-attached in an ubuntu PRO image
2500 """
2501 <cis_or_usg> +yes +<cis-s> +Security compliance and audit tools
2502 """
2503+ When I run `ua enable <cis_or_usg>` with sudo
2504+ And I run `ua status` with sudo
2505+ Then stdout matches regexp:
2506+ """
2507+ <cis_or_usg> +yes +enabled +Security compliance and audit tools
2508+ """
2509+ When I run `ua disable <cis_or_usg>` with sudo
2510+ Then stdout matches regexp:
2511+ """
2512+ Updating package lists
2513+ """
2514+ When I run `ua status` with sudo
2515+ Then stdout matches regexp:
2516+ """
2517+ <cis_or_usg> +yes +disabled +Security compliance and audit tools
2518+ """
2519 When I run `cat /var/log/squid/access.log` `with sudo` on the `proxy` machine
2520 Then stdout matches regexp:
2521 """
2522@@ -163,7 +211,7 @@ Feature: Command behaviour when auto-attached in an ubuntu PRO image
2523 | release | fips-s | cc-eal-s | cis-s | livepatch-s | cis_or_usg |
2524 | xenial | n/a | disabled | disabled | n/a | cis |
2525 | bionic | disabled | disabled | disabled | n/a | cis |
2526- | focal | n/a | n/a | disabled | enabled | usg |
2527+ | focal | disabled | n/a | disabled | enabled | usg |
2528
2529 @series.lts
2530 @uses.config.machine_type.aws.pro
2531@@ -178,7 +226,6 @@ Feature: Command behaviour when auto-attached in an ubuntu PRO image
2532 """
2533 And I run `ua auto-attach` with sudo
2534 And I run `ua status --wait` as non-root
2535- And I run `ua status` as non-root
2536 Then stdout matches regexp:
2537 """
2538 SERVICE ENTITLED STATUS DESCRIPTION
2539@@ -214,6 +261,22 @@ Feature: Command behaviour when auto-attached in an ubuntu PRO image
2540 """
2541 <cis_or_usg> +yes +<cis-s> +Security compliance and audit tools
2542 """
2543+ When I run `ua enable <cis_or_usg>` with sudo
2544+ And I run `ua status` with sudo
2545+ Then stdout matches regexp:
2546+ """
2547+ <cis_or_usg> +yes +enabled +Security compliance and audit tools
2548+ """
2549+ When I run `ua disable <cis_or_usg>` with sudo
2550+ Then stdout matches regexp:
2551+ """
2552+ Updating package lists
2553+ """
2554+ When I run `ua status` with sudo
2555+ Then stdout matches regexp:
2556+ """
2557+ <cis_or_usg> +yes +disabled +Security compliance and audit tools
2558+ """
2559 When I run `systemctl start ua-auto-attach.service` with sudo
2560 And I verify that running `systemctl status ua-auto-attach.service` `as non-root` exits `0,3`
2561 Then stdout matches regexp:
2562@@ -281,7 +344,7 @@ Feature: Command behaviour when auto-attached in an ubuntu PRO image
2563 | release | fips-s | cc-eal-s | cis-s | infra-pkg | apps-pkg | cis_or_usg |
2564 | xenial | disabled | disabled | disabled | libkrad0 | jq | cis |
2565 | bionic | disabled | disabled | disabled | libkrad0 | bundler | cis |
2566- | focal | n/a | n/a | disabled | hello | ant | usg |
2567+ | focal | disabled | n/a | disabled | hello | ant | usg |
2568
2569 @series.lts
2570 @uses.config.machine_type.azure.pro
2571@@ -332,6 +395,22 @@ Feature: Command behaviour when auto-attached in an ubuntu PRO image
2572 """
2573 <cis_or_usg> +yes +<cis-s> +Security compliance and audit tools
2574 """
2575+ When I run `ua enable <cis_or_usg>` with sudo
2576+ And I run `ua status` with sudo
2577+ Then stdout matches regexp:
2578+ """
2579+ <cis_or_usg> +yes +enabled +Security compliance and audit tools
2580+ """
2581+ When I run `ua disable <cis_or_usg>` with sudo
2582+ Then stdout matches regexp:
2583+ """
2584+ Updating package lists
2585+ """
2586+ When I run `ua status` with sudo
2587+ Then stdout matches regexp:
2588+ """
2589+ <cis_or_usg> +yes +disabled +Security compliance and audit tools
2590+ """
2591 When I run `systemctl start ua-auto-attach.service` with sudo
2592 And I verify that running `systemctl status ua-auto-attach.service` `as non-root` exits `0,3`
2593 Then stdout matches regexp:
2594@@ -398,8 +477,8 @@ Feature: Command behaviour when auto-attached in an ubuntu PRO image
2595 Examples: ubuntu release
2596 | release | fips-s | cc-eal-s | cis-s | infra-pkg | apps-pkg | livepatch | cis_or_usg |
2597 | xenial | n/a | disabled | disabled | libkrad0 | jq | enabled | cis |
2598- | bionic | disabled | disabled | disabled | libkrad0 | bundler | n/a | cis |
2599- | focal | n/a | n/a | disabled | hello | ant | enabled | usg |
2600+ | bionic | disabled | disabled | disabled | libkrad0 | bundler | enabled | cis |
2601+ | focal | disabled | n/a | disabled | hello | ant | enabled | usg |
2602
2603 @series.lts
2604 @uses.config.machine_type.gcp.pro
2605@@ -450,6 +529,22 @@ Feature: Command behaviour when auto-attached in an ubuntu PRO image
2606 """
2607 <cis_or_usg> +yes +<cis-s> +Security compliance and audit tools
2608 """
2609+ When I run `ua enable <cis_or_usg>` with sudo
2610+ And I run `ua status` with sudo
2611+ Then stdout matches regexp:
2612+ """
2613+ <cis_or_usg> +yes +enabled +Security compliance and audit tools
2614+ """
2615+ When I run `ua disable <cis_or_usg>` with sudo
2616+ Then stdout matches regexp:
2617+ """
2618+ Updating package lists
2619+ """
2620+ When I run `ua status` with sudo
2621+ Then stdout matches regexp:
2622+ """
2623+ <cis_or_usg> +yes +disabled +Security compliance and audit tools
2624+ """
2625 When I run `systemctl start ua-auto-attach.service` with sudo
2626 And I verify that running `systemctl status ua-auto-attach.service` `as non-root` exits `0,3`
2627 Then stdout matches regexp:
2628@@ -517,4 +612,4 @@ Feature: Command behaviour when auto-attached in an ubuntu PRO image
2629 | release | fips-s | cc-eal-s | cis-s | infra-pkg | apps-pkg | livepatch | cis_or_usg |
2630 | xenial | n/a | disabled | disabled | libkrad0 | jq | n/a | cis |
2631 | bionic | disabled | disabled | disabled | libkrad0 | bundler | n/a | cis |
2632- | focal | n/a | n/a | disabled | hello | ant | enabled | usg |
2633+ | focal | disabled | n/a | disabled | hello | ant | enabled | usg |
2634diff --git a/features/ubuntu_pro_fips.feature b/features/ubuntu_pro_fips.feature
2635new file mode 100644
2636index 0000000..7a18b03
2637--- /dev/null
2638+++ b/features/ubuntu_pro_fips.feature
2639@@ -0,0 +1,210 @@
2640+Feature: Command behaviour when auto-attached in an ubuntu PRO fips image
2641+
2642+ @series.lts
2643+ @uses.config.machine_type.azure.pro.fips
2644+ Scenario Outline: Check fips is enabled correctly on Ubuntu pro fips Azure machine
2645+ Given a `<release>` machine with ubuntu-advantage-tools installed
2646+ When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following:
2647+ """
2648+ contract_url: 'https://contracts.canonical.com'
2649+ data_dir: /var/lib/ubuntu-advantage
2650+ log_level: debug
2651+ log_file: /var/log/ubuntu-advantage.log
2652+ features:
2653+ allow_xenial_fips_on_cloud: true
2654+ """
2655+ And I run `ua auto-attach` with sudo
2656+ And I run `ua status --wait` as non-root
2657+ And I run `ua status` as non-root
2658+ Then stdout matches regexp:
2659+ """
2660+ esm-apps +yes +enabled +UA Apps: Extended Security Maintenance \(ESM\)
2661+ esm-infra +yes +enabled +UA Infra: Extended Security Maintenance \(ESM\)
2662+ fips +yes +enabled +NIST-certified core packages
2663+ fips-updates +yes +disabled +NIST-certified core packages with priority security updates
2664+ livepatch +yes +n/a +Canonical Livepatch service
2665+ """
2666+ And I verify that running `apt update` `with sudo` exits `0`
2667+ And I verify that running `grep Traceback /var/log/ubuntu-advantage.log` `with sudo` exits `1`
2668+ And I verify that `openssh-server` is installed from apt source `<fips-apt-source>`
2669+ And I verify that `openssh-client` is installed from apt source `<fips-apt-source>`
2670+ And I verify that `strongswan` is installed from apt source `<fips-apt-source>`
2671+ And I verify that `openssh-server-hmac` is installed from apt source `<fips-apt-source>`
2672+ And I verify that `openssh-client-hmac` is installed from apt source `<fips-apt-source>`
2673+ And I verify that `strongswan-hmac` is installed from apt source `<fips-apt-source>`
2674+ When I run `uname -r` as non-root
2675+ Then stdout matches regexp:
2676+ """
2677+ <fips-kernel-version>
2678+ """
2679+ When I run `apt-cache policy ubuntu-azure-fips` as non-root
2680+ Then stdout does not match regexp:
2681+ """
2682+ .*Installed: \(none\)
2683+ """
2684+ When I run `cat /proc/sys/crypto/fips_enabled` with sudo
2685+ Then I will see the following on stdout:
2686+ """
2687+ 1
2688+ """
2689+ When I run `systemctl start ua-auto-attach.service` with sudo
2690+ And I verify that running `systemctl status ua-auto-attach.service` `as non-root` exits `0,3`
2691+ Then stdout matches regexp:
2692+ """
2693+ .*status=0\/SUCCESS.*
2694+ """
2695+ And stdout matches regexp:
2696+ """
2697+ Skipping attach: Instance '[0-9a-z\-]+' is already attached.
2698+ """
2699+ When I run `ua auto-attach` with sudo
2700+ Then stderr matches regexp:
2701+ """
2702+ Skipping attach: Instance '[0-9a-z\-]+' is already attached.
2703+ """
2704+ When I run `apt-cache policy` with sudo
2705+ Then apt-cache policy for the following url has permission `500`
2706+ """
2707+ https://esm.ubuntu.com/infra/ubuntu <release>-infra-updates/main amd64 Packages
2708+ """
2709+ And apt-cache policy for the following url has permission `500`
2710+ """
2711+ https://esm.ubuntu.com/infra/ubuntu <release>-infra-security/main amd64 Packages
2712+ """
2713+ And apt-cache policy for the following url has permission `500`
2714+ """
2715+ https://esm.ubuntu.com/apps/ubuntu <release>-apps-updates/main amd64 Packages
2716+ """
2717+ And apt-cache policy for the following url has permission `500`
2718+ """
2719+ https://esm.ubuntu.com/apps/ubuntu <release>-apps-security/main amd64 Packages
2720+ """
2721+ And I verify that running `apt update` `with sudo` exits `0`
2722+ When I run `apt install -y <infra-pkg>/<release>-infra-security` with sudo, retrying exit [100]
2723+ And I run `apt-cache policy <infra-pkg>` as non-root
2724+ Then stdout matches regexp:
2725+ """
2726+ \s*500 https://esm.ubuntu.com/infra/ubuntu <release>-infra-security/main amd64 Packages
2727+ \s*500 https://esm.ubuntu.com/infra/ubuntu <release>-infra-updates/main amd64 Packages
2728+ """
2729+ And stdout matches regexp:
2730+ """
2731+ Installed: .*[~+]esm
2732+ """
2733+ When I run `apt install -y <apps-pkg>/<release>-apps-security` with sudo, retrying exit [100]
2734+ And I run `apt-cache policy <apps-pkg>` as non-root
2735+ Then stdout matches regexp:
2736+ """
2737+ Version table:
2738+ \s*\*\*\* .* 500
2739+ \s*500 https://esm.ubuntu.com/apps/ubuntu <release>-apps-security/main amd64 Packages
2740+ """
2741+
2742+ Examples: ubuntu release
2743+ | release | infra-pkg | apps-pkg | fips-apt-source | fips-kernel-version |
2744+ | xenial | libkrad0 | jq | https://esm.ubuntu.com/fips/ubuntu xenial/main | fips |
2745+ | bionic | libkrad0 | bundler | https://esm.ubuntu.com/fips/ubuntu bionic/main | azure-fips |
2746+
2747+ @series.lts
2748+ @uses.config.machine_type.aws.pro.fips
2749+ Scenario Outline: Check fips is enabled correctly on Ubuntu pro fips AWS machine
2750+ Given a `<release>` machine with ubuntu-advantage-tools installed
2751+ When I create the file `/etc/ubuntu-advantage/uaclient.conf` with the following:
2752+ """
2753+ contract_url: 'https://contracts.canonical.com'
2754+ data_dir: /var/lib/ubuntu-advantage
2755+ log_level: debug
2756+ log_file: /var/log/ubuntu-advantage.log
2757+ """
2758+ And I run `ua auto-attach` with sudo
2759+ And I run `ua status --wait` as non-root
2760+ And I run `ua status` as non-root
2761+ Then stdout matches regexp:
2762+ """
2763+ esm-apps +yes +enabled +UA Apps: Extended Security Maintenance \(ESM\)
2764+ esm-infra +yes +enabled +UA Infra: Extended Security Maintenance \(ESM\)
2765+ fips +yes +enabled +NIST-certified core packages
2766+ fips-updates +yes +disabled +NIST-certified core packages with priority security updates
2767+ livepatch +yes +n/a +Canonical Livepatch service
2768+ """
2769+ And I verify that running `apt update` `with sudo` exits `0`
2770+ And I verify that running `grep Traceback /var/log/ubuntu-advantage.log` `with sudo` exits `1`
2771+ And I verify that `openssh-server` is installed from apt source `<fips-apt-source>`
2772+ And I verify that `openssh-client` is installed from apt source `<fips-apt-source>`
2773+ And I verify that `strongswan` is installed from apt source `<fips-apt-source>`
2774+ And I verify that `openssh-server-hmac` is installed from apt source `<fips-apt-source>`
2775+ And I verify that `openssh-client-hmac` is installed from apt source `<fips-apt-source>`
2776+ And I verify that `strongswan-hmac` is installed from apt source `<fips-apt-source>`
2777+ When I run `uname -r` as non-root
2778+ Then stdout matches regexp:
2779+ """
2780+ <fips-kernel-version>
2781+ """
2782+ When I run `apt-cache policy ubuntu-aws-fips` as non-root
2783+ Then stdout does not match regexp:
2784+ """
2785+ .*Installed: \(none\)
2786+ """
2787+ When I run `cat /proc/sys/crypto/fips_enabled` with sudo
2788+ Then I will see the following on stdout:
2789+ """
2790+ 1
2791+ """
2792+ When I run `systemctl start ua-auto-attach.service` with sudo
2793+ And I verify that running `systemctl status ua-auto-attach.service` `as non-root` exits `0,3`
2794+ Then stdout matches regexp:
2795+ """
2796+ .*status=0\/SUCCESS.*
2797+ """
2798+ And stdout matches regexp:
2799+ """
2800+ Skipping attach: Instance '[0-9a-z\-]+' is already attached.
2801+ """
2802+ When I run `ua auto-attach` with sudo
2803+ Then stderr matches regexp:
2804+ """
2805+ Skipping attach: Instance '[0-9a-z\-]+' is already attached.
2806+ """
2807+ When I run `apt-cache policy` with sudo
2808+ Then apt-cache policy for the following url has permission `500`
2809+ """
2810+ https://esm.ubuntu.com/infra/ubuntu <release>-infra-updates/main amd64 Packages
2811+ """
2812+ And apt-cache policy for the following url has permission `500`
2813+ """
2814+ https://esm.ubuntu.com/infra/ubuntu <release>-infra-security/main amd64 Packages
2815+ """
2816+ And apt-cache policy for the following url has permission `500`
2817+ """
2818+ https://esm.ubuntu.com/apps/ubuntu <release>-apps-updates/main amd64 Packages
2819+ """
2820+ And apt-cache policy for the following url has permission `500`
2821+ """
2822+ https://esm.ubuntu.com/apps/ubuntu <release>-apps-security/main amd64 Packages
2823+ """
2824+ And I verify that running `apt update` `with sudo` exits `0`
2825+ When I run `apt install -y <infra-pkg>/<release>-infra-security` with sudo, retrying exit [100]
2826+ And I run `apt-cache policy <infra-pkg>` as non-root
2827+ Then stdout matches regexp:
2828+ """
2829+ \s*500 https://esm.ubuntu.com/infra/ubuntu <release>-infra-security/main amd64 Packages
2830+ \s*500 https://esm.ubuntu.com/infra/ubuntu <release>-infra-updates/main amd64 Packages
2831+ """
2832+ And stdout matches regexp:
2833+ """
2834+ Installed: .*[~+]esm
2835+ """
2836+ When I run `apt install -y <apps-pkg>/<release>-apps-security` with sudo, retrying exit [100]
2837+ And I run `apt-cache policy <apps-pkg>` as non-root
2838+ Then stdout matches regexp:
2839+ """
2840+ Version table:
2841+ \s*\*\*\* .* 500
2842+ \s*500 https://esm.ubuntu.com/apps/ubuntu <release>-apps-security/main amd64 Packages
2843+ """
2844+
2845+ Examples: ubuntu release
2846+ | release | infra-pkg | apps-pkg | fips-apt-source | fips-kernel-version |
2847+ | xenial | libkrad0 | jq | https://esm.ubuntu.com/fips/ubuntu xenial/main | fips |
2848+ | bionic | libkrad0 | bundler | https://esm.ubuntu.com/fips/ubuntu bionic/main | aws-fips |
2849+
2850diff --git a/features/ubuntu_upgrade.feature b/features/ubuntu_upgrade.feature
2851index fb506b9..4cf3407 100644
2852--- a/features/ubuntu_upgrade.feature
2853+++ b/features/ubuntu_upgrade.feature
2854@@ -3,7 +3,6 @@ Feature: Upgrade between releases when uaclient is attached
2855
2856 @slow
2857 @series.focal
2858- @series.hirsute
2859 @series.impish
2860 @uses.config.machine_type.lxd.container
2861 @upgrade
2862@@ -11,6 +10,8 @@ Feature: Upgrade between releases when uaclient is attached
2863 Given a `<release>` machine with ubuntu-advantage-tools installed
2864 When I attach `contract_token` with sudo
2865 And I run `apt-get dist-upgrade --assume-yes` with sudo
2866+ # Some packages upgrade may require a reboot
2867+ And I reboot the `<release>` machine
2868 And I create the file `/etc/update-manager/release-upgrades.d/ua-test.cfg` with the following
2869 """
2870 [Sources]
2871@@ -41,8 +42,7 @@ Feature: Upgrade between releases when uaclient is attached
2872
2873 Examples: ubuntu release
2874 | release | next_release | devel_release |
2875- | focal | hirsute | |
2876- | hirsute | impish | |
2877+ | focal | impish | |
2878 | impish | jammy | --devel-release |
2879
2880 @slow
2881diff --git a/features/ubuntu_upgrade_unattached.feature b/features/ubuntu_upgrade_unattached.feature
2882index 3944dba..0856c92 100644
2883--- a/features/ubuntu_upgrade_unattached.feature
2884+++ b/features/ubuntu_upgrade_unattached.feature
2885@@ -3,13 +3,14 @@ Feature: Upgrade between releases when uaclient is unattached
2886
2887 @slow
2888 @series.focal
2889- @series.hirsute
2890 @series.impish
2891 @uses.config.machine_type.lxd.container
2892 @upgrade
2893 Scenario Outline: Unattached upgrade across releases
2894 Given a `<release>` machine with ubuntu-advantage-tools installed
2895 When I run `apt-get dist-upgrade --assume-yes` with sudo
2896+ # Some packages upgrade may require a reboot
2897+ And I reboot the `<release>` machine
2898 And I create the file `/etc/update-manager/release-upgrades.d/ua-test.cfg` with the following
2899 """
2900 [Sources]
2901@@ -40,8 +41,7 @@ Feature: Upgrade between releases when uaclient is unattached
2902
2903 Examples: ubuntu release
2904 | release | next_release | devel_release |
2905- | focal | hirsute | |
2906- | hirsute | impish | |
2907+ | focal | impish | |
2908 | impish | jammy | --devel-release |
2909
2910 @slow
2911diff --git a/features/unattached_commands.feature b/features/unattached_commands.feature
2912index bda7597..7c95ab9 100644
2913--- a/features/unattached_commands.feature
2914+++ b/features/unattached_commands.feature
2915@@ -27,7 +27,6 @@ Feature: Command behaviour when unattached
2916 | bionic |
2917 | focal |
2918 | xenial |
2919- | hirsute |
2920 | impish |
2921 | jammy |
2922
2923@@ -131,8 +130,6 @@ Feature: Command behaviour when unattached
2924 | focal | refresh |
2925 | xenial | detach |
2926 | xenial | refresh |
2927- | hirsute | detach |
2928- | hirsute | refresh |
2929 | impish | detach |
2930 | impish | refresh |
2931 | jammy | detach |
2932@@ -169,10 +166,6 @@ Feature: Command behaviour when unattached
2933 | xenial | disable | livepatch |
2934 | xenial | enable | unknown |
2935 | xenial | disable | unknown |
2936- | hirsute | enable | livepatch |
2937- | hirsute | disable | livepatch |
2938- | hirsute | enable | unknown |
2939- | hirsute | disable | unknown |
2940 | impish | enable | livepatch |
2941 | impish | disable | livepatch |
2942 | impish | enable | unknown |
2943@@ -220,7 +213,6 @@ Feature: Command behaviour when unattached
2944 | bionic | yes |
2945 | focal | yes |
2946 | xenial | yes |
2947- | hirsute | no |
2948 | impish | no |
2949 | jammy | no |
2950
2951@@ -251,7 +243,6 @@ Feature: Command behaviour when unattached
2952 | xenial |
2953 | bionic |
2954 | focal |
2955- | hirsute |
2956 | impish |
2957 | jammy |
2958
2959@@ -288,7 +279,7 @@ Feature: Command behaviour when unattached
2960 USN-4539-1: AWL vulnerability
2961 Found CVEs:
2962 https://ubuntu.com/security/CVE-2020-11728
2963- 1 affected package is installed: awl
2964+ 1 affected source package is installed: awl
2965 \(1/1\) awl:
2966 A fix is available in Ubuntu standard updates.
2967 .*\{ apt update && apt install --only-upgrade -y libawl-php \}.*
2968@@ -299,7 +290,7 @@ Feature: Command behaviour when unattached
2969 """
2970 CVE-2020-28196: Kerberos vulnerability
2971 https://ubuntu.com/security/CVE-2020-28196
2972- 1 affected package is installed: krb5
2973+ 1 affected source package is installed: krb5
2974 \(1/1\) krb5:
2975 A fix is available in Ubuntu standard updates.
2976 The update is already installed.
2977@@ -323,7 +314,7 @@ Feature: Command behaviour when unattached
2978 USN-4539-1: AWL vulnerability
2979 Found CVEs:
2980 https://ubuntu.com/security/CVE-2020-11728
2981- 1 affected package is installed: awl
2982+ 1 affected source package is installed: awl
2983 \(1/1\) awl:
2984 Sorry, no fix is available.
2985 1 package is still affected: awl
2986@@ -334,7 +325,7 @@ Feature: Command behaviour when unattached
2987 """
2988 CVE-2020-15180: MariaDB vulnerabilities
2989 https://ubuntu.com/security/CVE-2020-15180
2990- No affected packages are installed.
2991+ No affected source packages are installed.
2992 .*✔.* CVE-2020-15180 does not affect your system.
2993 """
2994 When I run `ua fix CVE-2020-28196` as non-root
2995@@ -342,7 +333,7 @@ Feature: Command behaviour when unattached
2996 """
2997 CVE-2020-28196: Kerberos vulnerability
2998 https://ubuntu.com/security/CVE-2020-28196
2999- 1 affected package is installed: krb5
3000+ 1 affected source package is installed: krb5
3001 \(1/1\) krb5:
3002 A fix is available in Ubuntu standard updates.
3003 The update is already installed.
3004@@ -354,7 +345,7 @@ Feature: Command behaviour when unattached
3005 """
3006 CVE-2017-9233: Expat vulnerability
3007 https://ubuntu.com/security/CVE-2017-9233
3008- 3 affected packages are installed: expat, matanza, swish-e
3009+ 3 affected source packages are installed: expat, matanza, swish-e
3010 \(1/3, 2/3\) matanza, swish-e:
3011 Sorry, no fix is available.
3012 \(3/3\) expat:
3013@@ -363,6 +354,28 @@ Feature: Command behaviour when unattached
3014 2 packages are still affected: matanza, swish-e
3015 .*✘.* CVE-2017-9233 is not resolved.
3016 """
3017+ When I fix `USN-5079-2` by attaching to a subscription with `contract_token_staging_expired`
3018+ Then stdout matches regexp
3019+ """
3020+ USN-5079-2: curl vulnerabilities
3021+ Found CVEs:
3022+ https://ubuntu.com/security/CVE-2021-22946
3023+ https://ubuntu.com/security/CVE-2021-22947
3024+ 1 affected source package is installed: curl
3025+ \(1/1\) curl:
3026+ A fix is available in UA Infra.
3027+ The update is not installed because this system is not attached to a
3028+ subscription.
3029+
3030+ Choose: \[S\]ubscribe at ubuntu.com \[A\]ttach existing token \[C\]ancel
3031+ > Enter your token \(from https://ubuntu.com/advantage\) to attach this system:
3032+ > .*\{ ua attach .*\}.*
3033+ Attach denied:
3034+ Contract ".*" expired on .*
3035+ Visit https://ubuntu.com/advantage to manage contract tokens.
3036+ 1 package is still affected: curl
3037+ .*✘.* USN-5079-2 is not resolved.
3038+ """
3039 When I fix `USN-5079-2` by attaching to a subscription with `contract_token`
3040 Then stdout matches regexp:
3041 """
3042@@ -370,7 +383,7 @@ Feature: Command behaviour when unattached
3043 Found CVEs:
3044 https://ubuntu.com/security/CVE-2021-22946
3045 https://ubuntu.com/security/CVE-2021-22947
3046- 1 affected package is installed: curl
3047+ 1 affected source package is installed: curl
3048 \(1/1\) curl:
3049 A fix is available in UA Infra.
3050 The update is not installed because this system is not attached to a
3051@@ -395,7 +408,7 @@ Feature: Command behaviour when unattached
3052 USN-5051-2: OpenSSL vulnerability
3053 Found CVEs:
3054 https://ubuntu.com/security/CVE-2021-3712
3055- 1 affected package is installed: openssl
3056+ 1 affected source package is installed: openssl
3057 \(1/1\) openssl:
3058 A fix is available in UA Infra.
3059 .*\{ apt update && apt install --only-upgrade -y libssl1.0.0 openssl \}.*
3060@@ -440,7 +453,7 @@ Feature: Command behaviour when unattached
3061 USN-4539-1: AWL vulnerability
3062 Found CVEs:
3063 https://ubuntu.com/security/CVE-2020-11728
3064- 1 affected package is installed: awl
3065+ 1 affected source package is installed: awl
3066 \(1/1\) awl:
3067 Ubuntu security engineers are investigating this issue.
3068 1 package is still affected: awl
3069@@ -451,7 +464,7 @@ Feature: Command behaviour when unattached
3070 """
3071 CVE-2020-28196: Kerberos vulnerability
3072 https://ubuntu.com/security/CVE-2020-28196
3073- 1 affected package is installed: krb5
3074+ 1 affected source package is installed: krb5
3075 \(1/1\) krb5:
3076 A fix is available in Ubuntu standard updates.
3077 The update is already installed.
3078@@ -463,7 +476,7 @@ Feature: Command behaviour when unattached
3079 """
3080 CVE-2021-27135: xterm vulnerability
3081 https://ubuntu.com/security/CVE-2021-27135
3082- 1 affected package is installed: xterm
3083+ 1 affected source package is installed: xterm
3084 \(1/1\) xterm:
3085 A fix is available in Ubuntu standard updates.
3086 Package fixes cannot be installed.
3087@@ -476,7 +489,7 @@ Feature: Command behaviour when unattached
3088 """
3089 CVE-2021-27135: xterm vulnerability
3090 https://ubuntu.com/security/CVE-2021-27135
3091- 1 affected package is installed: xterm
3092+ 1 affected source package is installed: xterm
3093 \(1/1\) xterm:
3094 A fix is available in Ubuntu standard updates.
3095 .*\{ apt update && apt install --only-upgrade -y xterm \}.*
3096@@ -487,12 +500,26 @@ Feature: Command behaviour when unattached
3097 """
3098 CVE-2021-27135: xterm vulnerability
3099 https://ubuntu.com/security/CVE-2021-27135
3100- 1 affected package is installed: xterm
3101+ 1 affected source package is installed: xterm
3102 \(1/1\) xterm:
3103 A fix is available in Ubuntu standard updates.
3104 The update is already installed.
3105 .*✔.* CVE-2021-27135 is resolved.
3106 """
3107+ When I run `apt-get install libbz2-1.0=1.0.6-8.1 -y --allow-downgrades` with sudo
3108+ And I run `apt-get install bzip2=1.0.6-8.1 -y` with sudo
3109+ And I run `ua fix USN-4038-3` with sudo
3110+ Then stdout matches regexp:
3111+ """
3112+ USN-4038-3: bzip2 regression
3113+ Found Launchpad bugs:
3114+ https://launchpad.net/bugs/1834494
3115+ 1 affected source package is installed: bzip2
3116+ \(1/1\) bzip2:
3117+ A fix is available in Ubuntu standard updates.
3118+ .*\{ apt update && apt install --only-upgrade -y bzip2 libbz2-1.0 \}.*
3119+ .*✔.* USN-4038-3 is resolved.
3120+ """
3121
3122
3123 @series.all
3124@@ -535,6 +562,31 @@ Feature: Command behaviour when unattached
3125 | release |
3126 | bionic |
3127 | focal |
3128- | hirsute |
3129+ | impish |
3130+ | jammy |
3131+
3132+ @series.all
3133+ @uses.config.machine_type.lxd.container
3134+ Scenario Outline: Unattached enable fails in a ubuntu machine
3135+ Given a `<release>` machine with ubuntu-advantage-tools installed
3136+ When I verify that running `ua enable esm-infra` `with sudo` exits `1`
3137+ Then I will see the following on stderr:
3138+ """
3139+ To use 'esm-infra' you need an Ubuntu Advantage subscription
3140+ Personal and community subscriptions are available at no charge
3141+ See https://ubuntu.com/advantage
3142+ """
3143+ When I verify that running `ua enable esm-infra --format json --assume-yes` `with sudo` exits `1`
3144+ Then stdout is a json matching the `ua_operation` schema
3145+ And I will see the following on stdout:
3146+ """
3147+ {"_schema_version": "0.1", "errors": [{"message": "To use 'esm-infra' you need an Ubuntu Advantage subscription\nPersonal and community subscriptions are available at no charge\nSee https://ubuntu.com/advantage", "message_code": "enable-failure-unattached", "service": null, "type": "system"}], "failed_services": [], "needs_reboot": false, "processed_services": [], "result": "failure", "warnings": []}
3148+ """
3149+
3150+ Examples: ubuntu release
3151+ | release |
3152+ | xenial |
3153+ | bionic |
3154+ | focal |
3155 | impish |
3156 | jammy |
3157diff --git a/features/unattached_status.feature b/features/unattached_status.feature
3158index 7b91df3..08538a2 100644
3159--- a/features/unattached_status.feature
3160+++ b/features/unattached_status.feature
3161@@ -5,18 +5,31 @@ Feature: Unattached status
3162 Scenario Outline: Unattached status in a ubuntu machine - formatted
3163 Given a `<release>` machine with ubuntu-advantage-tools installed
3164 When I run `ua status --format json` as non-root
3165- Then stdout is formatted as `json` and has keys:
3166+ Then stdout is a json matching the `ua_status` schema
3167+ When I run `ua status --format yaml` as non-root
3168+ Then stdout is a yaml matching the `ua_status` schema
3169+ When I run `sed -i 's/contracts.can/invalidurl.notcan/' /etc/ubuntu-advantage/uaclient.conf` with sudo
3170+ And I verify that running `ua status --format json` `as non-root` exits `1`
3171+ Then stdout is a json matching the `ua_status` schema
3172+ And I will see the following on stdout:
3173 """
3174- _doc _schema_version account attached config config_path contract effective
3175- environment_vars execution_details execution_status expires machine_id notices
3176- services version simulated
3177+ {"environment_vars": [], "errors": [{"message": "Failed to connect to authentication server\nCheck your Internet connection and try again.", "message_code": "connectivity-error", "service": null, "type": "system"}], "result": "failure", "services": [], "warnings": []}
3178 """
3179- When I run `ua status --format yaml` as non-root
3180- Then stdout is formatted as `yaml` and has keys:
3181+ And I verify that running `ua status --format yaml` `as non-root` exits `1`
3182+ Then stdout is a yaml matching the `ua_status` schema
3183+ And I will see the following on stdout:
3184 """
3185- _doc _schema_version account attached config config_path contract effective
3186- environment_vars execution_details execution_status expires machine_id notices
3187- services version simulated
3188+ environment_vars: []
3189+ errors:
3190+ - message: 'Failed to connect to authentication server
3191+
3192+ Check your Internet connection and try again.'
3193+ message_code: connectivity-error
3194+ service: null
3195+ type: system
3196+ result: failure
3197+ services: []
3198+ warnings: []
3199 """
3200
3201 Examples: ubuntu release
3202@@ -24,7 +37,6 @@ Feature: Unattached status
3203 | bionic |
3204 | focal |
3205 | xenial |
3206- | hirsute |
3207 | impish |
3208 | jammy |
3209
3210@@ -32,7 +44,6 @@ Feature: Unattached status
3211 @uses.config.machine_type.lxd.container
3212 Scenario Outline: Unattached status in a ubuntu machine
3213 Given a `<release>` machine with ubuntu-advantage-tools installed
3214- When I run `sed -i 's/contracts.can/contracts.staging.can/' /etc/ubuntu-advantage/uaclient.conf` with sudo
3215 When I run `ua status` as non-root
3216 Then stdout matches regexp:
3217 """
3218@@ -128,63 +139,104 @@ Feature: Unattached status
3219 | xenial | yes | yes | cis | yes | yes | yes | yes | yes | |
3220 | bionic | yes | yes | cis | yes | yes | yes | yes | yes | |
3221 | focal | yes | no | | yes | yes | yes | no | yes | usg |
3222- | hirsute | no | no | cis | no | no | no | no | no | |
3223 | impish | no | no | cis | no | no | no | no | no | |
3224 | jammy | no | no | cis | no | no | no | no | no | |
3225
3226 @series.all
3227 @uses.config.machine_type.lxd.container
3228 @uses.config.contract_token
3229+ @uses.config.contract_token_staging_expired
3230 Scenario Outline: Simulate status in a ubuntu machine
3231 Given a `<release>` machine with ubuntu-advantage-tools installed
3232 When I do a preflight check for `contract_token` without the all flag
3233 Then stdout matches regexp:
3234- """
3235- SERVICE AVAILABLE ENTITLED AUTO_ENABLED DESCRIPTION
3236- cc-eal <cc-eal> +yes +no +Common Criteria EAL2 Provisioning Packages
3237- ?<cis>( +<cis-available> +yes +no +Security compliance and audit tools)?
3238- ?esm-infra <esm-infra> +yes +yes +UA Infra: Extended Security Maintenance \(ESM\)
3239- fips <fips> +yes +no +NIST-certified core packages
3240- fips-updates <fips> +yes +no +NIST-certified core packages with priority security updates
3241- livepatch <livepatch> +yes +yes +Canonical Livepatch service
3242- ?<usg>( +<cis-available> +yes +no +Security compliance and audit tools)?
3243- """
3244+ """
3245+ SERVICE AVAILABLE ENTITLED AUTO_ENABLED DESCRIPTION
3246+ cc-eal <cc-eal> +yes +no +Common Criteria EAL2 Provisioning Packages
3247+ ?<cis>( +<cis-available> +yes +no +Security compliance and audit tools)?
3248+ ?esm-infra <esm-infra> +yes +yes +UA Infra: Extended Security Maintenance \(ESM\)
3249+ fips <fips> +yes +no +NIST-certified core packages
3250+ fips-updates <fips> +yes +no +NIST-certified core packages with priority security updates
3251+ livepatch <livepatch> +yes +yes +Canonical Livepatch service
3252+ ?<usg>( +<cis-available> +yes +no +Security compliance and audit tools)?
3253+ """
3254 When I do a preflight check for `contract_token` with the all flag
3255 Then stdout matches regexp:
3256- """
3257- SERVICE AVAILABLE ENTITLED AUTO_ENABLED DESCRIPTION
3258- cc-eal <cc-eal> +yes +no +Common Criteria EAL2 Provisioning Packages
3259- ?<cis>( +<cis-available> +yes +no +Security compliance and audit tools)?
3260- ?esm-apps <esm-apps> +yes +yes +UA Apps: Extended Security Maintenance \(ESM\)
3261- esm-infra <esm-infra> +yes +yes +UA Infra: Extended Security Maintenance \(ESM\)
3262- fips <fips> +yes +no +NIST-certified core packages
3263- fips-updates <fips> +yes +no +NIST-certified core packages with priority security updates
3264- livepatch <livepatch> +yes +yes +Canonical Livepatch service
3265- ros <ros> +yes +no +Security Updates for the Robot Operating System
3266- ros-updates <ros> +yes +no +All Updates for the Robot Operating System
3267- ?<usg>( +<cis-available> +yes +no +Security compliance and audit tools)?
3268- """
3269+ """
3270+ SERVICE AVAILABLE ENTITLED AUTO_ENABLED DESCRIPTION
3271+ cc-eal <cc-eal> +yes +no +Common Criteria EAL2 Provisioning Packages
3272+ ?<cis>( +<cis-available> +yes +no +Security compliance and audit tools)?
3273+ ?esm-apps <esm-apps> +yes +yes +UA Apps: Extended Security Maintenance \(ESM\)
3274+ esm-infra <esm-infra> +yes +yes +UA Infra: Extended Security Maintenance \(ESM\)
3275+ fips <fips> +yes +no +NIST-certified core packages
3276+ fips-updates <fips> +yes +no +NIST-certified core packages with priority security updates
3277+ livepatch <livepatch> +yes +yes +Canonical Livepatch service
3278+ ros <ros> +yes +no +Security Updates for the Robot Operating System
3279+ ros-updates <ros> +yes +no +All Updates for the Robot Operating System
3280+ ?<usg>( +<cis-available> +yes +no +Security compliance and audit tools)?
3281+ """
3282 When I do a preflight check for `contract_token` formatted as json
3283- Then stdout is formatted as `json` and has keys:
3284- """
3285- _doc _schema_version account attached config config_path contract effective
3286- environment_vars execution_details execution_status expires machine_id notices
3287- services version simulated
3288- """
3289+ Then stdout is a json matching the `ua_status` schema
3290 When I do a preflight check for `contract_token` formatted as yaml
3291- Then stdout is formatted as `yaml` and has keys:
3292- """
3293- _doc _schema_version account attached config config_path contract effective
3294- environment_vars execution_details execution_status expires machine_id notices
3295- services version simulated
3296- """
3297+ Then stdout is a yaml matching the `ua_status` schema
3298+ When I verify that a preflight check for `invalid_token` formatted as json exits 1
3299+ Then stdout is a json matching the `ua_status` schema
3300+ And I will see the following on stdout:
3301+ """
3302+ {"environment_vars": [], "errors": [{"message": "Invalid token. See https://ubuntu.com/advantage", "message_code": "attach-invalid-token", "service": null, "type": "system"}], "result": "failure", "services": [], "warnings": []}
3303+ """
3304+ When I verify that a preflight check for `invalid_token` formatted as yaml exits 1
3305+ Then stdout is a yaml matching the `ua_status` schema
3306+ And I will see the following on stdout:
3307+ """
3308+ environment_vars: []
3309+ errors:
3310+ - message: Invalid token. See https://ubuntu.com/advantage
3311+ message_code: attach-invalid-token
3312+ service: null
3313+ type: system
3314+ result: failure
3315+ services: []
3316+ warnings: []
3317+ """
3318+ When I run `sed -i 's/contracts.can/contracts.staging.can/' /etc/ubuntu-advantage/uaclient.conf` with sudo
3319+ And I verify that a preflight check for `contract_token_staging_expired` formatted as json exits 1
3320+ Then stdout is a json matching the `ua_status` schema
3321+ And stdout matches regexp:
3322+ """
3323+ \"result\": \"failure\"
3324+ """
3325+ And stdout matches regexp:
3326+ """
3327+ \"message\": \"Contract .* expired on .*\"
3328+ """
3329+ When I verify that a preflight check for `contract_token_staging_expired` formatted as yaml exits 1
3330+ Then stdout is a yaml matching the `ua_status` schema
3331+ Then stdout matches regexp:
3332+ """
3333+ errors:
3334+ - message: Contract .* expired on .*
3335+ """
3336+ When I verify that a preflight check for `contract_token_staging_expired` without the all flag exits 1
3337+ Then stdout matches regexp:
3338+ """
3339+ This token is not valid.
3340+ Contract \".*\" expired on .*
3341
3342+ SERVICE AVAILABLE ENTITLED AUTO_ENABLED DESCRIPTION
3343+ cc-eal <cc-eal> +yes +no +Common Criteria EAL2 Provisioning Packages
3344+ ?<cis>( +<cis-available> +yes +no +Security compliance and audit tools)?
3345+ ?esm-infra <esm-infra> +yes +yes +UA Infra: Extended Security Maintenance \(ESM\)
3346+ fips <fips> +yes +no +NIST-certified core packages
3347+ fips-updates <fips> +yes +no +NIST-certified core packages with priority security updates
3348+ livepatch <livepatch> +yes +yes +Canonical Livepatch service
3349+ ?<usg>( +<cis-available> +yes +no +Security compliance and audit tools)?
3350+ """
3351
3352 Examples: ubuntu release
3353 | release | esm-apps | cc-eal | cis | cis-available | fips | esm-infra | ros | livepatch | usg |
3354 | xenial | yes | yes | cis | yes | yes | yes | yes | yes | |
3355 | bionic | yes | yes | cis | yes | yes | yes | yes | yes | |
3356 | focal | yes | no | | yes | yes | yes | no | yes | usg |
3357- | hirsute | no | no | cis | no | no | no | no | no | |
3358 | impish | no | no | cis | no | no | no | no | no | |
3359 | jammy | no | no | cis | no | no | no | no | no | |
3360diff --git a/features/util.py b/features/util.py
3361index 8bb7dfe..7e00746 100644
3362--- a/features/util.py
3363+++ b/features/util.py
3364@@ -325,3 +325,10 @@ def emit_spinner_on_travis(msg: str = " "):
3365 finally:
3366 print()
3367 dot_process.terminate()
3368+
3369+
3370+class SafeLoaderWithoutDatetime(yaml.SafeLoader):
3371+ yaml_implicit_resolvers = {
3372+ k: [r for r in v if r[0] != "tag:yaml.org,2002:timestamp"]
3373+ for k, v in yaml.SafeLoader.yaml_implicit_resolvers.items()
3374+ }
3375diff --git a/integration-requirements.txt b/integration-requirements.txt
3376index 69471fa..effd7bd 100644
3377--- a/integration-requirements.txt
3378+++ b/integration-requirements.txt
3379@@ -1,5 +1,6 @@
3380 # Integration testing
3381 behave
3382+jsonschema
3383 PyHamcrest
3384 pycloudlib @ git+https://github.com/canonical/pycloudlib.git@756a2c2de044ca60eaa7cdc76653d23a1339dc0a
3385
3386diff --git a/lib/reboot_cmds.py b/lib/reboot_cmds.py
3387index 569b46a..c819dba 100644
3388--- a/lib/reboot_cmds.py
3389+++ b/lib/reboot_cmds.py
3390@@ -18,11 +18,10 @@ import logging
3391 import os
3392 import sys
3393
3394-from uaclient import config, contract, lock, status
3395+from uaclient import config, contract, exceptions, lock, messages
3396 from uaclient.cli import setup_logging
3397 from uaclient.entitlements.fips import FIPSEntitlement
3398-from uaclient.exceptions import LockHeldError, UserFacingError
3399-from uaclient.util import ProcessExecutionError, UrlError, subp
3400+from uaclient.util import subp
3401
3402 # Retry sleep backoff algorithm if lock is held.
3403 # Lock may be held by auto-attach on systems with ubuntu-advantage-pro.
3404@@ -34,7 +33,7 @@ def run_command(cmd, cfg):
3405 try:
3406 out, _ = subp(cmd.split(), capture=True)
3407 logging.debug("Successfully executed cmd: {}".format(cmd))
3408- except ProcessExecutionError as exec_error:
3409+ except exceptions.ProcessExecutionError as exec_error:
3410 msg = (
3411 "Failed running cmd: {}\n"
3412 "Return code: {}\n"
3413@@ -75,7 +74,7 @@ def fix_pro_pkg_holds(cfg):
3414 )
3415 try:
3416 entitlement.install_packages(cleanup_on_failure=False)
3417- except UserFacingError as e:
3418+ except exceptions.UserFacingError as e:
3419 logging.error(e.msg)
3420 logging.warning(
3421 "Failed to install packages at boot: {}".format(
3422@@ -83,22 +82,22 @@ def fix_pro_pkg_holds(cfg):
3423 )
3424 )
3425 sys.exit(1)
3426- cfg.remove_notice("", status.MESSAGE_FIPS_REBOOT_REQUIRED)
3427+ cfg.remove_notice("", messages.FIPS_SYSTEM_REBOOT_REQUIRED.msg)
3428
3429
3430 def refresh_contract(cfg):
3431 try:
3432 contract.request_updated_contract(cfg)
3433- except UrlError as exc:
3434+ except exceptions.UrlError as exc:
3435 logging.exception(exc)
3436- logging.warning(status.MESSAGE_REFRESH_CONTRACT_FAILURE)
3437+ logging.warning(messages.REFRESH_CONTRACT_FAILURE)
3438 sys.exit(1)
3439
3440
3441 def process_remaining_deltas(cfg):
3442 cmd = "/usr/bin/python3 /usr/lib/ubuntu-advantage/upgrade_lts_contract.py"
3443 run_command(cmd=cmd, cfg=cfg)
3444- cfg.remove_notice("", status.MESSAGE_LIVEPATCH_LTS_REBOOT_REQUIRED)
3445+ cfg.remove_notice("", messages.LIVEPATCH_LTS_REBOOT_REQUIRED)
3446
3447
3448 def process_reboot_operations(cfg):
3449@@ -122,13 +121,13 @@ def process_reboot_operations(cfg):
3450 process_remaining_deltas(cfg)
3451
3452 cfg.delete_cache_key("marker-reboot-cmds")
3453- cfg.remove_notice("", status.MESSAGE_REBOOT_SCRIPT_FAILED)
3454+ cfg.remove_notice("", messages.REBOOT_SCRIPT_FAILED)
3455 logging.debug("Successfully ran all commands on reboot.")
3456 except Exception as e:
3457 msg = "Failed running commands on reboot."
3458 msg += str(e)
3459 logging.error(msg)
3460- cfg.add_notice("", status.MESSAGE_REBOOT_SCRIPT_FAILED)
3461+ cfg.add_notice("", messages.REBOOT_SCRIPT_FAILED)
3462
3463
3464 def main(cfg):
3465@@ -145,7 +144,7 @@ def main(cfg):
3466 max_retries=MAX_RETRIES_ON_LOCK_HELD,
3467 ):
3468 process_reboot_operations(cfg=cfg)
3469- except LockHeldError as e:
3470+ except exceptions.LockHeldError as e:
3471 logging.warning("Lock not released. %s", str(e.msg))
3472 sys.exit(1)
3473
3474diff --git a/setup.py b/setup.py
3475index 4c92683..bcce7c5 100644
3476--- a/setup.py
3477+++ b/setup.py
3478@@ -5,7 +5,7 @@ import glob
3479
3480 import setuptools
3481
3482-from uaclient import defaults, util, version
3483+from uaclient import defaults, version
3484
3485 NAME = "ubuntu-advantage-tools"
3486
3487@@ -39,7 +39,7 @@ def _get_version():
3488
3489
3490 def _get_data_files():
3491- data_files = [
3492+ return [
3493 ("/etc/ubuntu-advantage", ["uaclient.conf", "help_data.yaml"]),
3494 ("/etc/update-motd.d", glob.glob("update-motd.d/*")),
3495 ("/usr/lib/ubuntu-advantage", glob.glob("lib/[!_]*")),
3496@@ -49,16 +49,8 @@ def _get_data_files():
3497 ["release-upgrades.d/ubuntu-advantage-upgrades.cfg"],
3498 ),
3499 (defaults.CONFIG_DEFAULTS["data_dir"], []),
3500+ ("/lib/systemd/system", glob.glob("systemd/*")),
3501 ]
3502- rel_major, _rel_minor = util.get_platform_info()["release"].split(".", 1)
3503- if rel_major == "14":
3504- data_files.append(
3505- ("/etc/apt/apt.conf.d", ["apt.conf.d/51ubuntu-advantage-esm"])
3506- )
3507- data_files.append(("/etc/init", glob.glob("upstart/*")))
3508- else:
3509- data_files.append(("/lib/systemd/system", glob.glob("systemd/*")))
3510- return data_files
3511
3512
3513 setuptools.setup(
3514diff --git a/sru/release-27.7/test_world_readable_logs.sh b/sru/release-27.7/test_world_readable_logs.sh
3515new file mode 100644
3516index 0000000..ed687a7
3517--- /dev/null
3518+++ b/sru/release-27.7/test_world_readable_logs.sh
3519@@ -0,0 +1,66 @@
3520+series=$1
3521+deb=$2
3522+
3523+set -eE
3524+
3525+GREEN="\e[32m"
3526+RED="\e[31m"
3527+BLUE="\e[36m"
3528+END_COLOR="\e[0m"
3529+
3530+function cleanup {
3531+ lxc delete test --force
3532+}
3533+
3534+function on_err {
3535+ echo -e "${RED}Test Failed${END_COLOR}"
3536+ cleanup
3537+ exit 1
3538+}
3539+
3540+trap on_err ERR
3541+
3542+function print_and_run_cmd {
3543+ echo -e "${BLUE}Running:${END_COLOR}" "$@"
3544+ echo -e "${BLUE}Output:${END_COLOR}"
3545+ lxc exec test -- sh -c "$@"
3546+ echo
3547+}
3548+
3549+function explanatory_message {
3550+ echo -e "${BLUE}$@${END_COLOR}"
3551+}
3552+
3553+explanatory_message "Starting $series container and updating ubuntu-advantage-tools"
3554+lxc launch ubuntu-daily:$series test >/dev/null 2>&1
3555+sleep 10
3556+
3557+explanatory_message "Check that log is not world readable"
3558+print_and_run_cmd "ua version"
3559+print_and_run_cmd "head /var/log/ubuntu-advantage.log"
3560+print_and_run_cmd "find /var/log/ -name ubuntu-advantage.log -perm 0600 | grep -qz ."
3561+
3562+lxc exec test -- apt-get update >/dev/null
3563+explanatory_message "installing new version of ubuntu-advantage-tools from local copy"
3564+lxc file push $deb test/tmp/ua.deb > /dev/null
3565+print_and_run_cmd "dpkg -i /tmp/ua.deb"
3566+print_and_run_cmd "ua version"
3567+
3568+explanatory_message "Check that log files permissions are still the same"
3569+print_and_run_cmd "find /var/log/ -name ubuntu-advantage.log -perm 0600 | grep -qz ."
3570+
3571+explanatory_message "Check that logrotate command will create world readable files"
3572+print_and_run_cmd "logrotate --force /etc/logrotate.d/ubuntu-advantage-tools"
3573+print_and_run_cmd "find /var/log/ -name ubuntu-advantage.log -perm 0644 | grep -qz ."
3574+print_and_run_cmd "find /var/log/ -name ubuntu-advantage.log.1 -perm 0600 | grep -qz ."
3575+
3576+explanatory_message "Check that running logrotate again will stil make world readable files"
3577+# Just to add new entry to the log
3578+print_and_run_cmd "ua version"
3579+print_and_run_cmd "logrotate --force /etc/logrotate.d/ubuntu-advantage-tools"
3580+print_and_run_cmd "find /var/log/ -name ubuntu-advantage.log -perm 0644 | grep -qz ."
3581+print_and_run_cmd "find /var/log/ -name ubuntu-advantage.log.1 -perm 0644 | grep -qz ."
3582+print_and_run_cmd "find /var/log/ -name ubuntu-advantage.log.2.gz -perm 0600 | grep -qz ."
3583+
3584+echo -e "${GREEN}Test Passed${END_COLOR}"
3585+cleanup
3586diff --git a/tools/create-lp-release-branches.sh b/tools/create-lp-release-branches.sh
3587index 604df74..454af96 100755
3588--- a/tools/create-lp-release-branches.sh
3589+++ b/tools/create-lp-release-branches.sh
3590@@ -32,7 +32,7 @@ else
3591 set -e
3592 fi
3593
3594-for release in xenial bionic focal hirsute impish
3595+for release in xenial bionic focal impish
3596 do
3597 echo
3598 echo $release
3599@@ -48,7 +48,6 @@ do
3600 xenial) version=${UA_VERSION}~16.04.1;;
3601 bionic) version=${UA_VERSION}~18.04.1;;
3602 focal) version=${UA_VERSION}~20.04.1;;
3603- hirsute) version=${UA_VERSION}~21.04.1;;
3604 impish) version=${UA_VERSION}~21.10.1;;
3605 esac
3606 dch_cmd=(dch -v ${version} -D ${release} -b "Backport new upstream release: (LP: #${SRU_BUG}) to $release")
3607diff --git a/tools/refresh-aws-pro-ids b/tools/refresh-aws-pro-ids
3608index 5256a6c..7a89f5a 100755
3609--- a/tools/refresh-aws-pro-ids
3610+++ b/tools/refresh-aws-pro-ids
3611@@ -3,6 +3,7 @@
3612 import glob
3613 import os
3614 import re
3615+
3616 import yaml
3617
3618 from uaclient import util
3619@@ -19,7 +20,7 @@ git push upstream your-branch
3620 Create a new pull request @ https://github.com/canonical/ubuntu-advantage-client/pulls
3621 """
3622
3623-EOL_RELEASES = ("trusty", ) # Releases we no longer test
3624+EOL_RELEASES = ("trusty",) # Releases we no longer test
3625
3626
3627 def main():
3628@@ -33,35 +34,45 @@ def main():
3629 aws_ids = {}
3630 for aws_listing in glob.glob("listing-aws-pro-*"):
3631 m = re.match(
3632- r"^listing-aws-pro-(?P<release>\w+).yaml$", aws_listing
3633+ r"^listing-aws-pro-(fips-)?(?P<release>\w+).yaml$", aws_listing
3634 )
3635 if not m:
3636 print("Skipping unexpected listing file name: ", aws_listing)
3637 continue
3638 elif m.group("release") in EOL_RELEASES:
3639 print(
3640- "Skipping release %s. No longer CI on EOL releases" %
3641- m.group("release")
3642+ "Skipping release %s. No longer CI on EOL releases"
3643+ % m.group("release")
3644 )
3645 continue
3646 listing = yaml.safe_load(open(aws_listing, "r"))
3647- for md in listing['metadata']:
3648+ for md in listing["metadata"]:
3649 if md["key"] == "series":
3650 release = md["value"]
3651+ if "fips" in listing["productID"]:
3652+ release = release + "-fips"
3653 break
3654- for externalID in listing['externalIDs']:
3655- if externalID['origin'] == 'AWS':
3656+ for externalID in listing["externalIDs"]:
3657+ if externalID["origin"] == "AWS":
3658 # TODO(handle multiple IDs)
3659- [marketplace_id] = externalID['IDs']
3660+ [marketplace_id] = externalID["IDs"]
3661 break
3662 marketplace_id = marketplace_id.replace(MARKETPLACE_PREFIX, "")
3663 out, _err = util.subp(
3664- ["aws", "ec2", "describe-images", "--owners", "aws-marketplace",
3665- "--filters", "Name=product-code,Values={}".format(marketplace_id),
3666- "--query", "sort_by(Images, &CreationDate)[-1].ImageId"]
3667+ [
3668+ "aws",
3669+ "ec2",
3670+ "describe-images",
3671+ "--owners",
3672+ "aws-marketplace",
3673+ "--filters",
3674+ "Name=product-code,Values={}".format(marketplace_id),
3675+ "--query",
3676+ "sort_by(Images, &CreationDate)[-1].ImageId",
3677+ ]
3678 )
3679 ami_id = out.strip()
3680- aws_ids[release] = ami_id.replace("\"", "")
3681+ aws_ids[release] = ami_id.replace('"', "")
3682
3683 os.chdir("../..")
3684 with open("features/aws-ids.yaml", "w") as stream:
3685diff --git a/tools/run-integration-tests.py b/tools/run-integration-tests.py
3686index 10b4a55..da55b1c 100644
3687--- a/tools/run-integration-tests.py
3688+++ b/tools/run-integration-tests.py
3689@@ -12,7 +12,6 @@ SERIES_TO_VERSION = {
3690 "xenial": "16.04",
3691 "bionic": "18.04",
3692 "focal": "20.04",
3693- "hirsute": "21.04",
3694 "impish": "21.10",
3695 "jammy": "22.04",
3696 }
3697@@ -28,11 +27,11 @@ PLATFORM_SERIES_TESTS = {
3698 "azurepro": ["xenial", "bionic", "focal"],
3699 "awsgeneric": ["xenial", "bionic", "focal"],
3700 "awspro": ["xenial", "bionic", "focal"],
3701- "gcpgeneric": ["xenial", "bionic", "focal", "hirsute"],
3702+ "gcpgeneric": ["xenial", "bionic", "focal", "impish", "jammy"],
3703 "gcppro": ["xenial", "bionic", "focal"],
3704 "vm": ["xenial", "bionic", "focal"],
3705- "lxd": ["xenial", "bionic", "focal", "hirsute", "impish", "jammy"],
3706- "upgrade": ["xenial", "bionic", "focal", "hirsute", "impish"],
3707+ "lxd": ["xenial", "bionic", "focal", "impish", "jammy"],
3708+ "upgrade": ["xenial", "bionic", "focal", "impish"],
3709 }
3710
3711
3712diff --git a/tox.ini b/tox.ini
3713index 9c98f03..45962e0 100644
3714--- a/tox.ini
3715+++ b/tox.ini
3716@@ -1,20 +1,21 @@
3717 [tox]
3718-envlist = py3, flake8, py3-{xenial,bionic}, flake8-{trusty,xenial,bionic}, mypy, black, isort
3719-
3720-[testenv:flake8-trusty]
3721-pip_version = 9.0.2
3722+envlist = py3, flake8, py3-{xenial,bionic}, flake8-{xenial,bionic}, mypy, black, isort
3723
3724 [testenv:flake8-bionic]
3725 pip_version = 9.0.2
3726+setuptools_version = 59.8.0
3727
3728 [testenv:flake8-xenial]
3729 pip_version = 9.0.2
3730+setuptools_version = 59.8.0
3731
3732 [testenv:py3-xenial]
3733 pip_version = 9.0.2
3734+setuptools_version = 59.8.0
3735
3736 [testenv:py3-bionic]
3737 pip_version = 9.0.2
3738+setuptools_version = 59.8.0
3739
3740 [testenv]
3741 deps =
3742@@ -35,8 +36,10 @@ passenv =
3743 setenv =
3744 awsgeneric: UACLIENT_BEHAVE_MACHINE_TYPE = aws.generic
3745 awspro: UACLIENT_BEHAVE_MACHINE_TYPE = aws.pro
3746+ awspro-fips: UACLIENT_BEHAVE_MACHINE_TYPE = aws.pro.fips
3747 azuregeneric: UACLIENT_BEHAVE_MACHINE_TYPE = azure.generic
3748 azurepro: UACLIENT_BEHAVE_MACHINE_TYPE = azure.pro
3749+ azurepro-fips: UACLIENT_BEHAVE_MACHINE_TYPE = azure.pro.fips
3750 gcpgeneric: UACLIENT_BEHAVE_MACHINE_TYPE = gcp.generic
3751 gcppro: UACLIENT_BEHAVE_MACHINE_TYPE = gcp.pro
3752 vm: UACLIENT_BEHAVE_MACHINE_TYPE = lxd.vm
3753@@ -52,16 +55,14 @@ commands =
3754 behave-lxd-16.04: behave -v {posargs} --tags="uses.config.machine_type.lxd.container" --tags="series.xenial,series.lts,series.all" --tags="~upgrade"
3755 behave-lxd-18.04: behave -v {posargs} --tags="uses.config.machine_type.lxd.container" --tags="series.bionic,series.lts,series.all" --tags="~upgrade"
3756 behave-lxd-20.04: behave -v {posargs} --tags="uses.config.machine_type.lxd.container" --tags="series.focal,series.lts,series.all" --tags="~upgrade"
3757- behave-lxd-21.04: behave -v {posargs} --tags="uses.config.machine_type.lxd.container" --tags="series.hirsute,series.all" --tags="~upgrade"
3758 behave-lxd-21.10: behave -v {posargs} --tags="uses.config.machine_type.lxd.container" --tags="series.impish,series.all" --tags="~upgrade"
3759- behave-lxd-22.04: behave -v {posargs} --tags="uses.config.machine_type.lxd.container" --tags="series.jammy,series.all" --tags="~upgrade"
3760+ behave-lxd-22.04: behave -v {posargs} --tags="uses.config.machine_type.lxd.container" --tags="series.jammy,series.lts,series.all" --tags="~upgrade"
3761 behave-vm-16.04: behave -v {posargs} --tags="uses.config.machine_type.lxd.vm" --tags="series.xenial,series.all,series.lts" --tags="~upgrade"
3762 behave-vm-18.04: behave -v {posargs} --tags="uses.config.machine_type.lxd.vm" --tags="series.bionic,series.all,series.lts" --tags="~upgrade"
3763 behave-vm-20.04: behave -v {posargs} --tags="uses.config.machine_type.lxd.vm" --tags="series.focal,series.all,series.lts" --tags="~upgrade"
3764 behave-upgrade-16.04: behave -v {posargs} --tags="upgrade" --tags="series.xenial,series.all"
3765 behave-upgrade-18.04: behave -v {posargs} --tags="upgrade" --tags="series.bionic,series.all"
3766 behave-upgrade-20.04: behave -v {posargs} --tags="upgrade" --tags="series.focal,series.all"
3767- behave-upgrade-21.04: behave -v {posargs} --tags="upgrade" --tags="series.hirsute,series.all"
3768 behave-upgrade-21.10: behave -v {posargs} --tags="upgrade" --tags="series.impish,series.all"
3769 behave-awsgeneric-16.04: behave -v {posargs} --tags="uses.config.machine_type.aws.generic" --tags="series.xenial,series.lts,series.all" --tags="~upgrade"
3770 behave-awsgeneric-18.04: behave -v {posargs} --tags="uses.config.machine_type.aws.generic" --tags="series.bionic,series.lts,series.all" --tags="~upgrade"
3771@@ -69,16 +70,21 @@ commands =
3772 behave-awspro-16.04: behave -v {posargs} --tags="uses.config.machine_type.aws.pro" --tags="series.xenial,series.lts,series.all"
3773 behave-awspro-18.04: behave -v {posargs} --tags="uses.config.machine_type.aws.pro" --tags="series.bionic,series.lts,series.all"
3774 behave-awspro-20.04: behave -v {posargs} --tags="uses.config.machine_type.aws.pro" --tags="series.focal,series.lts,series.all"
3775+ behave-awspro-fips-16.04: behave -v {posargs} --tags="uses.config.machine_type.aws.pro.fips" --tags="series.xenial,series.lts,series.all"
3776+ behave-awspro-fips-18.04: behave -v {posargs} --tags="uses.config.machine_type.aws.pro.fips" --tags="series.bionic,series.lts,series.all"
3777 behave-azuregeneric-16.04: behave -v {posargs} --tags="uses.config.machine_type.azure.generic" --tags="series.xenial,series.lts,series.all" --tags="~upgrade"
3778 behave-azuregeneric-18.04: behave -v {posargs} --tags="uses.config.machine_type.azure.generic" --tags="series.bionic,series.lts,series.all" --tags="~upgrade"
3779 behave-azuregeneric-20.04: behave -v {posargs} --tags="uses.config.machine_type.azure.generic" --tags="series.focal,series.lts,series.all" --tags="~upgrade"
3780 behave-azurepro-16.04: behave -v {posargs} --tags="uses.config.machine_type.azure.pro" --tags="series.xenial,series.lts,series.all"
3781 behave-azurepro-18.04: behave -v {posargs} --tags="uses.config.machine_type.azure.pro" --tags="series.bionic,series.lts,series.all"
3782 behave-azurepro-20.04: behave -v {posargs} --tags="uses.config.machine_type.azure.pro" --tags="series.focal,series.lts,series.all"
3783+ behave-azurepro-fips-16.04: behave -v {posargs} --tags="uses.config.machine_type.azure.pro.fips" --tags="series.xenial,series.lts,series.all"
3784+ behave-azurepro-fips-18.04: behave -v {posargs} --tags="uses.config.machine_type.azure.pro.fips" --tags="series.bionic,series.lts,series.all"
3785 behave-gcpgeneric-16.04: behave -v {posargs} --tags="uses.config.machine_type.gcp.generic" --tags="series.xenial,series.lts,series.all" --tags="~upgrade"
3786 behave-gcpgeneric-18.04: behave -v {posargs} --tags="uses.config.machine_type.gcp.generic" --tags="series.bionic,series.lts,series.all" --tags="~upgrade"
3787 behave-gcpgeneric-20.04: behave -v {posargs} --tags="uses.config.machine_type.gcp.generic" --tags="series.focal,series.lts,series.all" --tags="~upgrade"
3788- behave-gcpgeneric-21.04: behave -v {posargs} --tags="uses.config.machine_type.gcp.generic" --tags="series.hirsute,series.all" --tags="~upgrade"
3789+ behave-gcpgeneric-21.10: behave -v {posargs} --tags="uses.config.machine_type.gcp.generic" --tags="series.impish,series.all" --tags="~upgrade"
3790+ behave-gcpgeneric-22.04: behave -v {posargs} --tags="uses.config.machine_type.gcp.generic" --tags="series.jammy,series.lts,series.all" --tags="~upgrade"
3791 behave-gcppro-16.04: behave -v {posargs} --tags="uses.config.machine_type.gcp.pro" --tags="series.xenial,series.lts,series.all" --tags="~upgrade"
3792 behave-gcppro-18.04: behave -v {posargs} --tags="uses.config.machine_type.gcp.pro" --tags="series.bionic,series.lts,series.all" --tags="~upgrade"
3793 behave-gcppro-20.04: behave -v {posargs} --tags="uses.config.machine_type.gcp.pro" --tags="series.focal,series.lts,series.all" --tags="~upgrade"
3794diff --git a/uaclient/actions.py b/uaclient/actions.py
3795index ec8706f..792c5a7 100644
3796--- a/uaclient/actions.py
3797+++ b/uaclient/actions.py
3798@@ -1,9 +1,20 @@
3799 import logging
3800+import sys
3801+from typing import Optional # noqa: F401
3802
3803-from uaclient import clouds, config, contract, exceptions, status, util
3804+from uaclient import (
3805+ clouds,
3806+ config,
3807+ contract,
3808+ entitlements,
3809+ event_logger,
3810+ exceptions,
3811+ messages,
3812+)
3813 from uaclient.clouds import identity
3814
3815 LOG = logging.getLogger("ua.actions")
3816+event = event_logger.get_event_logger()
3817
3818
3819 def attach_with_token(
3820@@ -22,18 +33,20 @@ def attach_with_token(
3821 contract.request_updated_contract(
3822 cfg, token, allow_enable=allow_enable
3823 )
3824- except util.UrlError as exc:
3825- with util.disable_log_to_console():
3826- LOG.exception(exc)
3827+ except exceptions.UrlError as exc:
3828 cfg.status() # Persist updated status in the event of partial attach
3829 update_apt_and_motd_messages(cfg)
3830 raise exc
3831 except exceptions.UserFacingError as exc:
3832- LOG.warning(exc.msg)
3833+ event.info(exc.msg, file_type=sys.stderr)
3834 cfg.status() # Persist updated status in the event of partial attach
3835 update_apt_and_motd_messages(cfg)
3836 raise exc
3837
3838+ current_iid = identity.get_instance_id()
3839+ if current_iid:
3840+ cfg.write_cache("instance-id", current_iid)
3841+
3842 update_apt_and_motd_messages(cfg)
3843
3844
3845@@ -53,16 +66,53 @@ def auto_attach(
3846 tokenResponse = contract_client.request_auto_attach_contract_token(
3847 instance=cloud
3848 )
3849- except contract.ContractAPIError as e:
3850+ except exceptions.ContractAPIError as e:
3851 if e.code and 400 <= e.code < 500:
3852 raise exceptions.NonAutoAttachImageError(
3853- status.MESSAGE_UNSUPPORTED_AUTO_ATTACH
3854+ messages.UNSUPPORTED_AUTO_ATTACH
3855 )
3856 raise e
3857- current_iid = identity.get_instance_id()
3858- if current_iid:
3859- cfg.write_cache("instance-id", current_iid)
3860
3861 token = tokenResponse["contractToken"]
3862
3863 attach_with_token(cfg, token=token, allow_enable=True)
3864+
3865+
3866+def enable_entitlement_by_name(
3867+ cfg: config.UAConfig,
3868+ name: str,
3869+ *,
3870+ assume_yes: bool = False,
3871+ allow_beta: bool = False
3872+):
3873+ """
3874+ Constructs an entitlement based on the name provided. Passes kwargs onto
3875+ the entitlement constructor.
3876+ :raise EntitlementNotFoundError: If no entitlement with the given name is
3877+ found, then raises this error.
3878+ """
3879+ ent_cls = entitlements.entitlement_factory(name)
3880+ entitlement = ent_cls(
3881+ cfg, assume_yes=assume_yes, allow_beta=allow_beta, called_name=name
3882+ )
3883+ return entitlement.enable()
3884+
3885+
3886+def status(
3887+ cfg: config.UAConfig,
3888+ *,
3889+ simulate_with_token: Optional[str] = None,
3890+ show_beta: bool = False
3891+):
3892+ """
3893+ Construct the current UA status dictionary.
3894+ """
3895+ if simulate_with_token:
3896+ status, ret = cfg.simulate_status(
3897+ token=simulate_with_token, show_beta=show_beta
3898+ )
3899+ else:
3900+ status = cfg.status(show_beta=show_beta)
3901+ ret = 0
3902+
3903+ return status, ret
3904diff --git a/uaclient/apt.py b/uaclient/apt.py
3905index f6e86a7..e84d3cf 100644
3906--- a/uaclient/apt.py
3907+++ b/uaclient/apt.py
3908@@ -6,7 +6,7 @@ import subprocess
3909 import tempfile
3910 from typing import Dict, List, Optional
3911
3912-from uaclient import exceptions, gpg, status, util
3913+from uaclient import event_logger, exceptions, gpg, messages, util
3914
3915 APT_HELPER_TIMEOUT = 60.0 # 60 second timeout used for apt-helper call
3916 APT_AUTH_COMMENT = " # ubuntu-advantage-tools"
3917@@ -27,6 +27,8 @@ APT_PROXY_CONF_FILE = "/etc/apt/apt.conf.d/90ubuntu-advantage-aptproxy"
3918 # Hope for an optimal first try.
3919 APT_RETRIES = [1.0, 5.0, 10.0]
3920
3921+event = event_logger.get_event_logger()
3922+
3923
3924 def assert_valid_apt_credentials(repo_url, username, password):
3925 """Validate apt credentials for a PPA.
3926@@ -55,7 +57,7 @@ def assert_valid_apt_credentials(repo_url, username, password):
3927 timeout=APT_HELPER_TIMEOUT,
3928 retry_sleeps=APT_RETRIES,
3929 )
3930- except util.ProcessExecutionError as e:
3931+ except exceptions.ProcessExecutionError as e:
3932 if e.exit_code == 100:
3933 stderr = str(e.stderr).lower()
3934 if re.search(r"401\s+unauthorized|httperror401", stderr):
3935@@ -80,7 +82,9 @@ def assert_valid_apt_credentials(repo_url, username, password):
3936 )
3937
3938
3939-def _parse_apt_update_for_invalid_apt_config(apt_error: str) -> str:
3940+def _parse_apt_update_for_invalid_apt_config(
3941+ apt_error: str
3942+) -> Optional[messages.NamedMessage]:
3943 """Parse apt update errors for invalid apt config in user machine.
3944
3945 This functions parses apt update errors regarding the presence of
3946@@ -96,9 +100,9 @@ def _parse_apt_update_for_invalid_apt_config(apt_error: str) -> str:
3947 message.
3948
3949 :param apt_error: The apt error string
3950- :return: a string containing the parsed error message.
3951+ :return: a NamedMessage containing the error message
3952 """
3953- error_msg = ""
3954+ error_msg = None
3955 failed_repos = set()
3956
3957 for line in apt_error.strip().split("\n"):
3958@@ -115,17 +119,18 @@ def _parse_apt_update_for_invalid_apt_config(apt_error: str) -> str:
3959 failed_repos.add(repo_url_match)
3960
3961 if failed_repos:
3962- error_msg += "\n"
3963- error_msg += status.MESSAGE_APT_UPDATE_INVALID_URL_CONFIG.format(
3964- "s" if len(failed_repos) > 1 else "",
3965- "\n".join(sorted(failed_repos)),
3966+ error_msg = messages.APT_UPDATE_INVALID_URL_CONFIG.format(
3967+ plural="s" if len(failed_repos) > 1 else "",
3968+ failed_repos="\n".join(sorted(failed_repos)),
3969 )
3970
3971 return error_msg
3972
3973
3974 def run_apt_command(
3975- cmd: List[str], error_msg: str, env: Optional[Dict[str, str]] = {}
3976+ cmd: List[str],
3977+ error_msg: Optional[str] = None,
3978+ env: Optional[Dict[str, str]] = {},
3979 ) -> str:
3980 """Run an apt command, retrying upon failure APT_RETRIES times.
3981
3982@@ -141,18 +146,66 @@ def run_apt_command(
3983 out, _err = util.subp(
3984 cmd, capture=True, retry_sleeps=APT_RETRIES, env=env
3985 )
3986- except util.ProcessExecutionError as e:
3987+ except exceptions.ProcessExecutionError as e:
3988 if "Could not get lock /var/lib/dpkg/lock" in str(e.stderr):
3989- error_msg += " Another process is running APT."
3990+ raise exceptions.APTProcessConflictError()
3991 else:
3992 """
3993 Treat errors where one of the APT repositories
3994 is invalid or unreachable. In that situation, we alert
3995 which repository is causing the error
3996 """
3997- error_msg += _parse_apt_update_for_invalid_apt_config(e.stderr)
3998+ repo_error_msg = _parse_apt_update_for_invalid_apt_config(e.stderr)
3999+ if repo_error_msg:
4000+ raise exceptions.APTInvalidRepoError(
4001+ error_msg=repo_error_msg.msg
4002+ )
4003+
4004+ msg = error_msg if error_msg else str(e)
4005+ raise exceptions.UserFacingError(msg)
4006+ return out
4007+
4008+
4009+def run_apt_update_command(env: Optional[Dict[str, str]] = {}) -> str:
4010+ try:
4011+ out = run_apt_command(cmd=["apt-get", "update"], env=env)
4012+ except exceptions.APTProcessConflictError:
4013+ raise exceptions.APTUpdateProcessConflictError()
4014+ except exceptions.APTInvalidRepoError as e:
4015+ raise exceptions.APTUpdateInvalidRepoError(repo_msg=e.msg)
4016+ except exceptions.UserFacingError as e:
4017+ raise exceptions.UserFacingError(
4018+ msg=messages.APT_UPDATE_FAILED.msg + "\n" + e.msg,
4019+ msg_code=messages.APT_UPDATE_FAILED.name,
4020+ )
4021+
4022+ return out
4023+
4024+
4025+def run_apt_install_command(
4026+ packages: List[str],
4027+ apt_options: Optional[List[str]] = None,
4028+ error_msg: Optional[str] = None,
4029+ env: Optional[Dict[str, str]] = {},
4030+) -> str:
4031+ if apt_options is None:
4032+ apt_options = []
4033+
4034+ try:
4035+ out = run_apt_command(
4036+ cmd=["apt-get", "install", "--assume-yes"]
4037+ + apt_options
4038+ + packages,
4039+ error_msg=error_msg,
4040+ env=env,
4041+ )
4042+ except exceptions.APTProcessConflictError:
4043+ raise exceptions.APTInstallProcessConflictError(header_msg=error_msg)
4044+ except exceptions.APTInvalidRepoError as e:
4045+ raise exceptions.APTInstallInvalidRepoError(
4046+ repo_msg=e.msg, header_msg=error_msg
4047+ )
4048
4049- raise exceptions.UserFacingError(error_msg)
4050 return out
4051
4052
4053@@ -181,7 +234,7 @@ def add_auth_apt_repo(
4054 # Does this system have updates suite enabled?
4055 updates_enabled = False
4056 policy = run_apt_command(
4057- ["apt-cache", "policy"], status.MESSAGE_APT_POLICY_FAILED
4058+ ["apt-cache", "policy"], messages.APT_POLICY_FAILED.msg
4059 )
4060 for line in policy.splitlines():
4061 # We only care about $suite-updates lines
4062@@ -403,7 +456,7 @@ def setup_apt_proxy(
4063 :return: None
4064 """
4065 if http_proxy or https_proxy:
4066- print(status.MESSAGE_SETTING_SERVICE_PROXY.format(service="APT"))
4067+ event.info(messages.SETTING_SERVICE_PROXY.format(service="APT"))
4068
4069 apt_proxy_config = ""
4070 if http_proxy:
4071@@ -413,9 +466,7 @@ def setup_apt_proxy(
4072 proxy_url=https_proxy
4073 )
4074 if apt_proxy_config != "":
4075- apt_proxy_config = (
4076- status.MESSAGE_APT_PROXY_CONFIG_HEADER + apt_proxy_config
4077- )
4078+ apt_proxy_config = messages.APT_PROXY_CONFIG_HEADER + apt_proxy_config
4079
4080 if apt_proxy_config == "":
4081 util.remove_file(APT_PROXY_CONF_FILE)
4082diff --git a/uaclient/cli.py b/uaclient/cli.py
4083index 340516c..6af7e74 100644
4084--- a/uaclient/cli.py
4085+++ b/uaclient/cli.py
4086@@ -15,8 +15,7 @@ import tempfile
4087 import textwrap
4088 import time
4089 from functools import wraps
4090-from typing import Optional # noqa: F401
4091-from typing import List, Tuple
4092+from typing import List, Optional, Tuple # noqa
4093
4094 import yaml
4095
4096@@ -25,9 +24,11 @@ from uaclient import (
4097 config,
4098 contract,
4099 entitlements,
4100+ event_logger,
4101 exceptions,
4102 jobs,
4103 lock,
4104+ messages,
4105 security,
4106 security_status,
4107 )
4108@@ -35,6 +36,7 @@ from uaclient import status as ua_status
4109 from uaclient import util, version
4110 from uaclient.clouds import AutoAttachCloudInstance # noqa: F401
4111 from uaclient.clouds import identity
4112+from uaclient.data_types import AttachActionsConfigFile, IncorrectTypeError
4113 from uaclient.defaults import (
4114 CLOUD_BUILD_INFO,
4115 CONFIG_FIELD_ENVVAR_ALLOWLIST,
4116@@ -82,6 +84,8 @@ UA_SERVICES = (
4117 "ua-license-check.timer",
4118 )
4119
4120+event = event_logger.get_event_logger()
4121+
4122
4123 class UAArgumentParser(argparse.ArgumentParser):
4124 def __init__(
4125@@ -128,31 +132,31 @@ class UAArgumentParser(argparse.ArgumentParser):
4126
4127 resources = contract.get_available_resources(config.UAConfig())
4128 for resource in resources:
4129- ent_cls = entitlements.entitlement_factory(resource["name"])
4130- if ent_cls:
4131- # Because we don't know the presentation name if unattached
4132- presentation_name = resource.get(
4133- "presentedAs", resource["name"]
4134- )
4135- if ent_cls.help_doc_url:
4136- url = " ({})".format(ent_cls.help_doc_url)
4137- else:
4138- url = ""
4139- service_info = textwrap.fill(
4140- service_info_tmpl.format(
4141- name=presentation_name,
4142- description=ent_cls.description,
4143- url=url,
4144- ),
4145- width=PRINT_WRAP_WIDTH,
4146- subsequent_indent=" ",
4147- break_long_words=False,
4148- break_on_hyphens=False,
4149- )
4150- if ent_cls.is_beta:
4151- beta_services_desc.append(service_info)
4152- else:
4153- non_beta_services_desc.append(service_info)
4154+ try:
4155+ ent_cls = entitlements.entitlement_factory(resource["name"])
4156+ except exceptions.EntitlementNotFoundError:
4157+ continue
4158+ # Because we don't know the presentation name if unattached
4159+ presentation_name = resource.get("presentedAs", resource["name"])
4160+ if ent_cls.help_doc_url:
4161+ url = " ({})".format(ent_cls.help_doc_url)
4162+ else:
4163+ url = ""
4164+ service_info = textwrap.fill(
4165+ service_info_tmpl.format(
4166+ name=presentation_name,
4167+ description=ent_cls.description,
4168+ url=url,
4169+ ),
4170+ width=PRINT_WRAP_WIDTH,
4171+ subsequent_indent=" ",
4172+ break_long_words=False,
4173+ break_on_hyphens=False,
4174+ )
4175+ if ent_cls.is_beta:
4176+ beta_services_desc.append(service_info)
4177+ else:
4178+ non_beta_services_desc.append(service_info)
4179
4180 return (non_beta_services_desc, beta_services_desc)
4181
4182@@ -179,7 +183,25 @@ def assert_root(f):
4183 def new_f(*args, **kwargs):
4184 if os.getuid() != 0:
4185 raise exceptions.NonRootUserError()
4186- return f(*args, **kwargs)
4187+ else:
4188+ return f(*args, **kwargs)
4189+
4190+ return new_f
4191+
4192+
4193+def verify_json_format_args(f):
4194+ """Decorator to verify if correct params are used for json format"""
4195+
4196+ @wraps(f)
4197+ def new_f(cmd_args, *args, **kwargs):
4198+ if not cmd_args:
4199+ return f(cmd_args, *args, **kwargs)
4200+
4201+ if cmd_args.format == "json" and not cmd_args.assume_yes:
4202+ msg = messages.JSON_FORMAT_REQUIRE_ASSUME_YES
4203+ raise exceptions.UserFacingError(msg=msg.msg, msg_code=msg.name)
4204+ else:
4205+ return f(cmd_args, *args, **kwargs)
4206
4207 return new_f
4208
4209@@ -352,6 +374,21 @@ def attach_parser(parser):
4210 dest="auto_enable",
4211 help="do not enable any recommended services automatically",
4212 )
4213+ parser.add_argument(
4214+ "--attach-config",
4215+ type=argparse.FileType("r"),
4216+ help=(
4217+ "use the provided attach config file instead of passing the token"
4218+ " on the cli"
4219+ ),
4220+ )
4221+ parser.add_argument(
4222+ "--format",
4223+ action="store",
4224+ choices=["cli", "json"],
4225+ default="cli",
4226+ help=("output enable in the specified format (default: cli)"),
4227+ )
4228 return parser
4229
4230
4231@@ -389,15 +426,6 @@ def security_status_parser(parser):
4232 choices=("json", "yaml"),
4233 required=True,
4234 )
4235- parser.add_argument(
4236- "--beta",
4237- help=(
4238- "Acknowledge that this output is not final and may change in the"
4239- " next version"
4240- ),
4241- action="store_true",
4242- required=True,
4243- )
4244 return parser
4245
4246
4247@@ -465,6 +493,13 @@ def detach_parser(parser):
4248 action="store_true",
4249 help="do not prompt for confirmation before performing the detach",
4250 )
4251+ parser.add_argument(
4252+ "--format",
4253+ action="store",
4254+ choices=["cli", "json"],
4255+ default="cli",
4256+ help=("output enable in the specified format (default: cli)"),
4257+ )
4258 return parser
4259
4260
4261@@ -534,6 +569,13 @@ def enable_parser(parser):
4262 parser.add_argument(
4263 "--beta", action="store_true", help="allow beta service to be enabled"
4264 )
4265+ parser.add_argument(
4266+ "--format",
4267+ action="store",
4268+ choices=["cli", "json"],
4269+ default="cli",
4270+ help=("output enable in the specified format (default: cli)"),
4271+ )
4272 return parser
4273
4274
4275@@ -561,6 +603,13 @@ def disable_parser(parser):
4276 action="store_true",
4277 help="do not prompt for confirmation before performing the disable",
4278 )
4279+ parser.add_argument(
4280+ "--format",
4281+ action="store",
4282+ choices=["cli", "json"],
4283+ default="cli",
4284+ help=("output disable in the specified format (default: cli)"),
4285+ )
4286 return parser
4287
4288
4289@@ -643,7 +692,7 @@ def status_parser(parser):
4290 return parser
4291
4292
4293-def _perform_disable(entitlement_name, cfg, *, assume_yes):
4294+def _perform_disable(entitlement, cfg, *, assume_yes, update_status=True):
4295 """Perform the disable action on a named entitlement.
4296
4297 :param entitlement_name: the name of the entitlement to enable
4298@@ -653,10 +702,27 @@ def _perform_disable(entitlement_name, cfg, *, assume_yes):
4299
4300 @return: True on success, False otherwise
4301 """
4302- ent_cls = entitlements.entitlement_factory(entitlement_name)
4303- entitlement = ent_cls(cfg, assume_yes=assume_yes)
4304- ret = entitlement.disable()
4305- cfg.status() # Update the status cache
4306+ ret, reason = entitlement.disable()
4307+
4308+ if not ret:
4309+ event.service_failed(entitlement.name)
4310+
4311+ if reason is not None and isinstance(
4312+ reason, ua_status.CanDisableFailure
4313+ ):
4314+ if reason.message is not None:
4315+ event.info(reason.message.msg)
4316+ event.error(
4317+ error_msg=reason.message.msg,
4318+ error_code=reason.message.name,
4319+ service=entitlement.name,
4320+ )
4321+ else:
4322+ event.service_processed(entitlement.name)
4323+
4324+ if update_status:
4325+ cfg.status() # Update the status cache
4326+
4327 return ret
4328
4329
4330@@ -709,7 +775,7 @@ def action_config_show(args, *, cfg, **kwargs):
4331 raise exceptions.UserFacingError(
4332 textwrap.fill(
4333 msg,
4334- width=ua_status.PRINT_WRAP_WIDTH,
4335+ width=PRINT_WRAP_WIDTH,
4336 subsequent_indent=" " * indent_position,
4337 )
4338 )
4339@@ -844,8 +910,9 @@ def action_config_unset(args, *, cfg, **kwargs):
4340 return 0
4341
4342
4343+@verify_json_format_args
4344 @assert_root
4345-@assert_attached(ua_status.MESSAGE_ENABLE_FAILURE_UNATTACHED_TMPL)
4346+@assert_attached(messages.ENABLE_FAILURE_UNATTACHED)
4347 @assert_lock_file("ua disable")
4348 def action_disable(args, *, cfg, **kwargs):
4349 """Perform the disable action on a list of entitlements.
4350@@ -856,11 +923,13 @@ def action_disable(args, *, cfg, **kwargs):
4351 entitlements_found, entitlements_not_found = get_valid_entitlement_names(
4352 names
4353 )
4354- tmpl = ua_status.MESSAGE_INVALID_SERVICE_OP_FAILURE_TMPL
4355 ret = True
4356
4357- for entitlement in entitlements_found:
4358- ret &= _perform_disable(entitlement, cfg, assume_yes=args.assume_yes)
4359+ for ent_name in entitlements_found:
4360+ ent_cls = entitlements.entitlement_factory(ent_name)
4361+ ent = ent_cls(cfg, assume_yes=args.assume_yes)
4362+
4363+ ret &= _perform_disable(ent, cfg, assume_yes=args.assume_yes)
4364
4365 if entitlements_not_found:
4366 valid_names = (
4367@@ -876,50 +945,68 @@ def action_disable(args, *, cfg, **kwargs):
4368 break_on_hyphens=False,
4369 )
4370 )
4371- raise exceptions.UserFacingError(
4372- tmpl.format(
4373- operation="disable",
4374- name=", ".join(entitlements_not_found),
4375- service_msg=service_msg,
4376- )
4377+ raise exceptions.InvalidServiceToDisableError(
4378+ operation="disable",
4379+ name=", ".join(entitlements_not_found),
4380+ service_msg=service_msg,
4381 )
4382
4383+ event.process_events()
4384 return 0 if ret else 1
4385
4386
4387+def _create_enable_entitlements_not_found_message(
4388+ entitlements_not_found, *, allow_beta: bool
4389+) -> messages.NamedMessage:
4390+ """
4391+ Constructs the MESSAGE_INVALID_SERVICE_OP_FAILURE message
4392+ based on the attempted services and valid services.
4393+ """
4394+ valid_services_names = entitlements.valid_services(allow_beta=allow_beta)
4395+ valid_names = ", ".join(valid_services_names)
4396+ service_msg = "\n".join(
4397+ textwrap.wrap(
4398+ "Try " + valid_names + ".",
4399+ width=80,
4400+ break_long_words=False,
4401+ break_on_hyphens=False,
4402+ )
4403+ )
4404+
4405+ return messages.INVALID_SERVICE_OP_FAILURE.format(
4406+ operation="enable",
4407+ name=", ".join(entitlements_not_found),
4408+ service_msg=service_msg,
4409+ )
4410+
4411+
4412+@verify_json_format_args
4413 @assert_root
4414-@assert_attached(ua_status.MESSAGE_ENABLE_FAILURE_UNATTACHED_TMPL)
4415+@assert_attached(messages.ENABLE_FAILURE_UNATTACHED)
4416 @assert_lock_file("ua enable")
4417 def action_enable(args, *, cfg, **kwargs):
4418 """Perform the enable action on a named entitlement.
4419
4420 @return: 0 on success, 1 otherwise
4421 """
4422- print(ua_status.MESSAGE_REFRESH_CONTRACT_ENABLE)
4423+ event.info(messages.REFRESH_CONTRACT_ENABLE)
4424 try:
4425 contract.request_updated_contract(cfg)
4426- except (util.UrlError, exceptions.UserFacingError):
4427+ except (exceptions.UrlError, exceptions.UserFacingError):
4428 # Inability to refresh is not a critical issue during enable
4429- logging.debug(
4430- ua_status.MESSAGE_REFRESH_CONTRACT_FAILURE, exc_info=True
4431- )
4432+ logging.debug(messages.REFRESH_CONTRACT_FAILURE, exc_info=True)
4433+ event.warning(warning_msg=messages.REFRESH_CONTRACT_FAILURE)
4434
4435 names = getattr(args, "service", [])
4436 entitlements_found, entitlements_not_found = get_valid_entitlement_names(
4437 names
4438 )
4439- valid_services_names = entitlements.valid_services(allow_beta=args.beta)
4440 ret = True
4441 for ent_name in entitlements_found:
4442 try:
4443- ent_cls = entitlements.entitlement_factory(ent_name)
4444- entitlement = ent_cls(
4445- cfg,
4446- assume_yes=args.assume_yes,
4447- allow_beta=args.beta,
4448- called_name=ent_name,
4449+ ent_ret, reason = actions.enable_entitlement_by_name(
4450+ cfg, ent_name, assume_yes=args.assume_yes, allow_beta=args.beta
4451 )
4452- ent_ret, reason = entitlement.enable()
4453 cfg.status() # Update the status cache
4454
4455 if (
4456@@ -928,39 +1015,41 @@ def action_enable(args, *, cfg, **kwargs):
4457 and isinstance(reason, ua_status.CanEnableFailure)
4458 ):
4459 if reason.message is not None:
4460- print(reason.message)
4461+ event.info(reason.message.msg)
4462+ event.error(
4463+ error_msg=reason.message.msg,
4464+ error_code=reason.message.name,
4465+ service=ent_name,
4466+ )
4467 if reason.reason == ua_status.CanEnableFailureReason.IS_BETA:
4468 # if we failed because ent is in beta and there was no
4469 # allow_beta flag/config, pretend it doesn't exist
4470 entitlements_not_found.append(ent_name)
4471+ elif ent_ret:
4472+ event.service_processed(service=ent_name)
4473+ elif not ent_ret and reason is None:
4474+ event.service_failed(service=ent_name)
4475
4476 ret &= ent_ret
4477 except exceptions.UserFacingError as e:
4478- print(e)
4479+ event.info(e.msg)
4480+ event.error(
4481+ error_msg=e.msg, error_code=e.msg_code, service=ent_name
4482+ )
4483 ret = False
4484
4485 if entitlements_not_found:
4486- valid_names = ", ".join(valid_services_names)
4487- service_msg = "\n".join(
4488- textwrap.wrap(
4489- "Try " + valid_names + ".",
4490- width=80,
4491- break_long_words=False,
4492- break_on_hyphens=False,
4493- )
4494- )
4495- tmpl = ua_status.MESSAGE_INVALID_SERVICE_OP_FAILURE_TMPL
4496- raise exceptions.UserFacingError(
4497- tmpl.format(
4498- operation="enable",
4499- name=", ".join(entitlements_not_found),
4500- service_msg=service_msg,
4501- )
4502+ msg = _create_enable_entitlements_not_found_message(
4503+ entitlements_not_found, allow_beta=args.beta
4504 )
4505+ event.services_failed(entitlements_not_found)
4506+ raise exceptions.UserFacingError(msg=msg.msg, msg_code=msg.name)
4507
4508+ event.process_events()
4509 return 0 if ret else 1
4510
4511
4512+@verify_json_format_args
4513 @assert_root
4514 @assert_attached()
4515 @assert_lock_file("ua detach")
4516@@ -985,11 +1074,15 @@ def _detach(cfg: config.UAConfig, assume_yes: bool) -> int:
4517 to_disable = []
4518 for ent_cls in entitlements.ENTITLEMENT_CLASSES:
4519 ent = ent_cls(cfg=cfg, assume_yes=assume_yes)
4520- if ent.can_disable(silent=True):
4521+ # For detach, we should not consider that a service
4522+ # cannot be disabled because of dependent services,
4523+ # since we are going to disable all of them anyway
4524+ ret, _ = ent.can_disable(ignore_dependent_services=True)
4525+ if ret:
4526 to_disable.append(ent)
4527
4528 """
4529- We will nake sure that services without dependencies are disabled first
4530+ We will make sure that services without dependencies are disabled first
4531 PS: This will only work because we have only three services with reverse
4532 dependencies:
4533 * ros: ros-updates
4534@@ -1007,40 +1100,47 @@ def _detach(cfg: config.UAConfig, assume_yes: bool) -> int:
4535
4536 if to_disable:
4537 suffix = "s" if len(to_disable) > 1 else ""
4538- print("Detach will disable the following service{}:".format(suffix))
4539+ event.info(
4540+ "Detach will disable the following service{}:".format(suffix)
4541+ )
4542 for ent in to_disable:
4543- print(" {}".format(ent.name))
4544+ event.info(" {}".format(ent.name))
4545 if not util.prompt_for_confirmation(assume_yes=assume_yes):
4546 return 1
4547 for ent in to_disable:
4548- ent.disable(silent=False)
4549- contract_client = contract.UAContractClient(cfg)
4550- machine_token = cfg.machine_token["machineToken"]
4551- contract_id = cfg.machine_token["machineTokenInfo"]["contractInfo"]["id"]
4552- contract_client.detach_machine_from_contract(machine_token, contract_id)
4553+ _perform_disable(ent, cfg, assume_yes=assume_yes, update_status=False)
4554+
4555 cfg.delete_cache()
4556 jobs.enable_license_check_if_applicable(cfg)
4557 update_apt_and_motd_messages(cfg)
4558- print(ua_status.MESSAGE_DETACH_SUCCESS)
4559+ event.info(messages.DETACH_SUCCESS)
4560+ event.process_events()
4561 return 0
4562
4563
4564 def _post_cli_attach(cfg: config.UAConfig) -> None:
4565- contract_name = cfg.machine_token["machineTokenInfo"]["contractInfo"][
4566- "name"
4567- ]
4568+ contract_name = None
4569+
4570+ if cfg.machine_token:
4571+ contract_name = (
4572+ cfg.machine_token.get("machineTokenInfo", {})
4573+ .get("contractInfo", {})
4574+ .get("name")
4575+ )
4576
4577 if contract_name:
4578- print(
4579- ua_status.MESSAGE_ATTACH_SUCCESS_TMPL.format(
4580- contract_name=contract_name
4581- )
4582+ event.info(
4583+ messages.ATTACH_SUCCESS_TMPL.format(contract_name=contract_name)
4584 )
4585 else:
4586- print(ua_status.MESSAGE_ATTACH_SUCCESS_NO_CONTRACT_NAME)
4587+ event.info(messages.ATTACH_SUCCESS_NO_CONTRACT_NAME)
4588
4589 jobs.disable_license_check_if_applicable(cfg)
4590- action_status(args=None, cfg=cfg)
4591+
4592+ status, _ret = actions.status(cfg)
4593+ output = ua_status.format_tabular(status)
4594+ event.info(util.handle_unicode_characters(output))
4595+ event.process_events()
4596
4597
4598 @assert_root
4599@@ -1064,27 +1164,25 @@ def action_auto_attach(args, *, cfg):
4600 raise exceptions.AlreadyAttachedError(cfg)
4601 if isinstance(e, exceptions.CloudFactoryNoCloudError):
4602 raise exceptions.UserFacingError(
4603- ua_status.MESSAGE_UNABLE_TO_DETERMINE_CLOUD_TYPE
4604+ messages.UNABLE_TO_DETERMINE_CLOUD_TYPE
4605 )
4606 if isinstance(e, exceptions.CloudFactoryNonViableCloudError):
4607- raise exceptions.UserFacingError(
4608- ua_status.MESSAGE_UNSUPPORTED_AUTO_ATTACH
4609- )
4610+ raise exceptions.UserFacingError(messages.UNSUPPORTED_AUTO_ATTACH)
4611 if isinstance(e, exceptions.CloudFactoryUnsupportedCloudError):
4612 raise exceptions.NonAutoAttachImageError(
4613- ua_status.MESSAGE_UNSUPPORTED_AUTO_ATTACH_CLOUD_TYPE.format(
4614+ messages.UNSUPPORTED_AUTO_ATTACH_CLOUD_TYPE.format(
4615 cloud_type=e.cloud_type
4616 )
4617 )
4618 # we shouldn't get here, but this is a reasonable default just in case
4619 raise exceptions.UserFacingError(
4620- ua_status.MESSAGE_UNABLE_TO_DETERMINE_CLOUD_TYPE
4621+ messages.UNABLE_TO_DETERMINE_CLOUD_TYPE
4622 )
4623
4624 if not instance:
4625 # we shouldn't get here, but this is a reasonable default just in case
4626 raise exceptions.UserFacingError(
4627- ua_status.MESSAGE_UNABLE_TO_DETERMINE_CLOUD_TYPE
4628+ messages.UNABLE_TO_DETERMINE_CLOUD_TYPE
4629 )
4630
4631 current_iid = identity.get_instance_id()
4632@@ -1095,13 +1193,13 @@ def action_auto_attach(args, *, cfg):
4633 print("Re-attaching Ubuntu Advantage subscription on new instance")
4634 if _detach(cfg, assume_yes=True) != 0:
4635 raise exceptions.UserFacingError(
4636- ua_status.MESSAGE_DETACH_AUTOMATION_FAILURE
4637+ messages.DETACH_AUTOMATION_FAILURE
4638 )
4639
4640 try:
4641 actions.auto_attach(cfg, instance)
4642- except util.UrlError:
4643- print(ua_status.MESSAGE_ATTACH_FAILURE)
4644+ except exceptions.UrlError:
4645+ event.info(messages.ATTACH_FAILURE)
4646 return 1
4647 except exceptions.UserFacingError:
4648 return 1
4649@@ -1114,22 +1212,83 @@ def action_auto_attach(args, *, cfg):
4650 @assert_root
4651 @assert_lock_file("ua attach")
4652 def action_attach(args, *, cfg):
4653- if not args.token:
4654+ if not args.token and not args.attach_config:
4655 raise exceptions.UserFacingError(
4656- ua_status.MESSAGE_ATTACH_REQUIRES_TOKEN
4657+ msg=messages.ATTACH_REQUIRES_TOKEN.msg,
4658+ msg_code=messages.ATTACH_REQUIRES_TOKEN.name,
4659 )
4660- try:
4661- actions.attach_with_token(
4662- cfg, token=args.token, allow_enable=args.auto_enable
4663+ if args.token and args.attach_config:
4664+ raise exceptions.UserFacingError(
4665+ msg=messages.ATTACH_TOKEN_ARG_XOR_CONFIG.msg,
4666+ msg_code=messages.ATTACH_TOKEN_ARG_XOR_CONFIG.name,
4667 )
4668- except util.UrlError:
4669- print(ua_status.MESSAGE_ATTACH_FAILURE)
4670+
4671+ if args.token:
4672+ token = args.token
4673+ enable_services_override = None
4674+ else:
4675+ try:
4676+ attach_config = AttachActionsConfigFile.from_dict(
4677+ yaml.safe_load(args.attach_config)
4678+ )
4679+ except IncorrectTypeError as e:
4680+ raise exceptions.AttachInvalidConfigFileError(
4681+ config_name=args.attach_config.name, error=e.msg
4682+ )
4683+
4684+ token = attach_config.token
4685+ enable_services_override = attach_config.enable_services
4686+
4687+ allow_enable = args.auto_enable and enable_services_override is None
4688+
4689+ try:
4690+ actions.attach_with_token(cfg, token=token, allow_enable=allow_enable)
4691+ except exceptions.UrlError:
4692+ msg = messages.ATTACH_FAILURE
4693+ event.info(msg.msg)
4694+ event.error(error_msg=msg.msg, error_code=msg.name)
4695+ event.process_events()
4696 return 1
4697- except exceptions.UserFacingError:
4698+ except exceptions.UserFacingError as exc:
4699+ event.info(exc.msg)
4700+ event.error(error_msg=exc.msg, error_code=exc.msg_code)
4701+ event.process_events()
4702 return 1
4703 else:
4704+ ret = 0
4705+ if enable_services_override is not None and args.auto_enable:
4706+ found, not_found = get_valid_entitlement_names(
4707+ enable_services_override
4708+ )
4709+ for name in found:
4710+ ent_ret, reason = actions.enable_entitlement_by_name(
4711+ cfg, name, assume_yes=True, allow_beta=True
4712+ )
4713+ if not ent_ret:
4714+ ret = 1
4715+ if (
4716+ reason is not None
4717+ and isinstance(reason, ua_status.CanEnableFailure)
4718+ and reason.message is not None
4719+ ):
4720+ event.info(reason.message.msg)
4721+ event.error(
4722+ error_msg=reason.message.msg,
4723+ error_code=reason.message.name,
4724+ service=name,
4725+ )
4726+ else:
4727+ event.service_processed(name)
4728+
4729+ if not_found:
4730+ msg = _create_enable_entitlements_not_found_message(
4731+ not_found, allow_beta=True
4732+ )
4733+ event.info(msg.msg, file_type=sys.stderr)
4734+ event.error(error_msg=msg.msg, error_code=msg.name)
4735+ ret = 1
4736 _post_cli_attach(cfg)
4737- return 0
4738+ return ret
4739
4740
4741 def _write_command_output_to_file(
4742@@ -1138,7 +1297,7 @@ def _write_command_output_to_file(
4743 """Helper which runs a command and writes output or error to filename."""
4744 try:
4745 out, _ = util.subp(cmd.split(), rcs=return_codes)
4746- except util.ProcessExecutionError as e:
4747+ except exceptions.ProcessExecutionError as e:
4748 util.write_file("{}-error".format(filename), str(e))
4749 else:
4750 util.write_file(filename, out)
4751@@ -1326,26 +1485,27 @@ def action_status(args, *, cfg):
4752 cfg = config.UAConfig()
4753 show_beta = args.all if args else False
4754 token = args.simulate_with_token if args else None
4755- if token:
4756- status = cfg.simulate_status(token=token, show_beta=show_beta)
4757- else:
4758- status = cfg.status(show_beta=show_beta)
4759 active_value = ua_status.UserFacingConfigStatus.ACTIVE.value
4760+
4761+ status, ret = actions.status(
4762+ cfg, simulate_with_token=token, show_beta=show_beta
4763+ )
4764 config_active = bool(status["execution_status"] == active_value)
4765+
4766 if args and args.wait and config_active:
4767 while status["execution_status"] == active_value:
4768- print(".", end="")
4769+ event.info(".", end="")
4770 time.sleep(1)
4771- status = cfg.status(show_beta=show_beta)
4772- print("")
4773- if args and args.format == "json":
4774- print(ua_status.format_json_status(status))
4775- elif args and args.format == "yaml":
4776- print(ua_status.format_yaml_status(status))
4777- else:
4778- output = ua_status.format_tabular(status)
4779- print(util.handle_unicode_characters(output))
4780- return 0
4781+ status, ret = actions.status(
4782+ cfg, simulate_with_token=token, show_beta=show_beta
4783+ )
4784+ event.info("")
4785+
4786+ event.set_output_content(status)
4787+ output = ua_status.format_tabular(status)
4788+ event.info(util.handle_unicode_characters(output))
4789+ event.process_events()
4790+ return ret
4791
4792
4793 def get_version(_args=None, _cfg=None):
4794@@ -1365,23 +1525,19 @@ def _action_refresh_config(args, cfg: config.UAConfig):
4795 except RuntimeError as exc:
4796 with util.disable_log_to_console():
4797 logging.exception(exc)
4798- raise exceptions.UserFacingError(
4799- ua_status.MESSAGE_REFRESH_CONFIG_FAILURE
4800- )
4801- print(ua_status.MESSAGE_REFRESH_CONFIG_SUCCESS)
4802+ raise exceptions.UserFacingError(messages.REFRESH_CONFIG_FAILURE)
4803+ print(messages.REFRESH_CONFIG_SUCCESS)
4804
4805
4806 @assert_attached()
4807 def _action_refresh_contract(_args, cfg: config.UAConfig):
4808 try:
4809 contract.request_updated_contract(cfg)
4810- except util.UrlError as exc:
4811+ except exceptions.UrlError as exc:
4812 with util.disable_log_to_console():
4813 logging.exception(exc)
4814- raise exceptions.UserFacingError(
4815- ua_status.MESSAGE_REFRESH_CONTRACT_FAILURE
4816- )
4817- print(ua_status.MESSAGE_REFRESH_CONTRACT_SUCCESS)
4818+ raise exceptions.UserFacingError(messages.REFRESH_CONTRACT_FAILURE)
4819+ print(messages.REFRESH_CONTRACT_SUCCESS)
4820
4821
4822 @assert_root
4823@@ -1444,8 +1600,10 @@ def setup_logging(console_level, log_level, log_file=None, logger=None):
4824 if os.getuid() == 0:
4825 # Setup readable-by-root-only debug file logging if running as root
4826 log_file_path = pathlib.Path(log_file)
4827- log_file_path.touch()
4828- log_file_path.chmod(0o600)
4829+
4830+ if not log_file_path.exists():
4831+ log_file_path.touch()
4832+ log_file_path.chmod(0o644)
4833
4834 file_handler = logging.FileHandler(log_file)
4835 file_handler.setLevel(log_level)
4836@@ -1454,6 +1612,17 @@ def setup_logging(console_level, log_level, log_file=None, logger=None):
4837 logger.addHandler(file_handler)
4838
4839
4840+def set_event_mode(cmd_args):
4841+ """Set the right event mode based on the args provided"""
4842+ if cmd_args.command in ("attach", "detach", "enable", "disable", "status"):
4843+ event.set_command(cmd_args.command)
4844+ if hasattr(cmd_args, "format"):
4845+ if cmd_args.format == "json":
4846+ event.set_event_mode(event_logger.EventLoggerMode.JSON)
4847+ if cmd_args.format == "yaml":
4848+ event.set_event_mode(event_logger.EventLoggerMode.YAML)
4849+
4850+
4851 def main_error_handler(func):
4852 def wrapper(*args, **kwargs):
4853 try:
4854@@ -1464,40 +1633,53 @@ def main_error_handler(func):
4855 print("Interrupt received; exiting.", file=sys.stderr)
4856 lock.clear_lock_file_if_present()
4857 sys.exit(1)
4858- except util.UrlError as exc:
4859+ except exceptions.UrlError as exc:
4860 if "CERTIFICATE_VERIFY_FAILED" in str(exc):
4861- tmpl = ua_status.MESSAGE_SSL_VERIFICATION_ERROR_CA_CERTIFICATES
4862+ tmpl = messages.SSL_VERIFICATION_ERROR_CA_CERTIFICATES
4863 if util.is_installed("ca-certificates"):
4864- tmpl = (
4865- ua_status.MESSAGE_SSL_VERIFICATION_ERROR_OPENSSL_CONFIG
4866- )
4867- print(tmpl.format(url=exc.url), file=sys.stderr)
4868+ tmpl = messages.SSL_VERIFICATION_ERROR_OPENSSL_CONFIG
4869+ msg = tmpl.format(url=exc.url)
4870+ event.error(error_msg=msg.msg, error_code=msg.name)
4871+ event.info(info_msg=msg.msg, file_type=sys.stderr)
4872 else:
4873 with util.disable_log_to_console():
4874 msg_args = {"url": exc.url, "error": exc}
4875 if exc.url:
4876 msg_tmpl = (
4877- ua_status.LOG_CONNECTIVITY_ERROR_WITH_URL_TMPL
4878+ messages.LOG_CONNECTIVITY_ERROR_WITH_URL_TMPL
4879 )
4880 else:
4881- msg_tmpl = ua_status.LOG_CONNECTIVITY_ERROR_TMPL
4882+ msg_tmpl = messages.LOG_CONNECTIVITY_ERROR_TMPL
4883 logging.exception(msg_tmpl.format(**msg_args))
4884- print(ua_status.MESSAGE_CONNECTIVITY_ERROR, file=sys.stderr)
4885+
4886+ msg = messages.CONNECTIVITY_ERROR
4887+ event.error(error_msg=msg.msg, error_code=msg.name)
4888+ event.info(info_msg=msg.msg, file_type=sys.stderr)
4889+
4890 lock.clear_lock_file_if_present()
4891+ event.process_events()
4892 sys.exit(1)
4893 except exceptions.UserFacingError as exc:
4894 with util.disable_log_to_console():
4895 logging.error(exc.msg)
4896- print("{}".format(exc.msg), file=sys.stderr)
4897+ event.error(error_msg=exc.msg, error_code=exc.msg_code)
4898+ event.info(info_msg="{}".format(exc.msg), file_type=sys.stderr)
4899 if not isinstance(exc, exceptions.LockHeldError):
4900 # Only clear the lock if it is ours.
4901 lock.clear_lock_file_if_present()
4902+ event.process_events()
4903 sys.exit(exc.exit_code)
4904- except Exception:
4905+ except Exception as e:
4906 with util.disable_log_to_console():
4907 logging.exception("Unhandled exception, please file a bug")
4908 lock.clear_lock_file_if_present()
4909- print(ua_status.MESSAGE_UNEXPECTED_ERROR, file=sys.stderr)
4910+ event.info(
4911+ info_msg=messages.UNEXPECTED_ERROR.msg, file_type=sys.stderr
4912+ )
4913+ event.error(
4914+ error_msg=getattr(e, "msg", str(e)), error_type="exception"
4915+ )
4916+ event.process_events()
4917 sys.exit(1)
4918
4919 return wrapper
4920@@ -1514,6 +1696,7 @@ def main(sys_argv=None):
4921 print("Try 'ua --help' for more information.")
4922 sys.exit(1)
4923 args = parser.parse_args(args=cli_arguments)
4924+ set_event_mode(args)
4925 cfg = config.UAConfig()
4926
4927 http_proxy = cfg.http_proxy
4928diff --git a/uaclient/clouds/gcp.py b/uaclient/clouds/gcp.py
4929index 55f518c..c520d47 100644
4930--- a/uaclient/clouds/gcp.py
4931+++ b/uaclient/clouds/gcp.py
4932@@ -20,6 +20,7 @@ GCP_LICENSES = {
4933 "xenial": "8045211386737108299",
4934 "bionic": "6022427724719891830",
4935 "focal": "599959289349842382",
4936+ "jammy": "2592866803419978320",
4937 }
4938
4939
4940diff --git a/uaclient/clouds/identity.py b/uaclient/clouds/identity.py
4941index cd8f7b4..7542d35 100644
4942--- a/uaclient/clouds/identity.py
4943+++ b/uaclient/clouds/identity.py
4944@@ -30,7 +30,7 @@ def get_instance_id() -> Optional[str]:
4945 # Present in cloud-init on >= Xenial
4946 out, _err = util.subp(["cloud-init", "query", "instance_id"])
4947 return out.strip()
4948- except util.ProcessExecutionError:
4949+ except exceptions.ProcessExecutionError:
4950 pass
4951 logging.warning("Unable to determine current instance-id")
4952 return None
4953@@ -43,7 +43,7 @@ def get_cloud_type() -> Tuple[Optional[str], Optional[NoCloudTypeReason]]:
4954 try:
4955 out, _err = util.subp(["cloud-id"])
4956 return (out.strip(), None)
4957- except util.ProcessExecutionError:
4958+ except exceptions.ProcessExecutionError:
4959 return (None, NoCloudTypeReason.CLOUD_ID_ERROR)
4960 # If no cloud-id command, assume not on cloud
4961 return (None, NoCloudTypeReason.NO_CLOUD_DETECTED)
4962diff --git a/uaclient/clouds/tests/test_aws.py b/uaclient/clouds/tests/test_aws.py
4963index d319427..9063994 100644
4964--- a/uaclient/clouds/tests/test_aws.py
4965+++ b/uaclient/clouds/tests/test_aws.py
4966@@ -6,6 +6,7 @@ from urllib.error import HTTPError
4967 import mock
4968 import pytest
4969
4970+from uaclient import exceptions
4971 from uaclient.clouds.aws import (
4972 AWS_TOKEN_PUT_HEADER,
4973 AWS_TOKEN_REQ_HEADER,
4974@@ -16,7 +17,6 @@ from uaclient.clouds.aws import (
4975 IMDS_V2_TOKEN_URL,
4976 UAAutoAttachAWSInstance,
4977 )
4978-from uaclient.exceptions import UserFacingError
4979
4980 M_PATH = "uaclient.clouds.aws."
4981
4982@@ -274,7 +274,9 @@ class TestUAAutoAttachAWSInstance:
4983 "No valid AWS IMDS endpoint discovered at "
4984 "addresses: {}, {}".format(IMDS_IPV4_ADDRESS, IMDS_IPV6_ADDRESS)
4985 )
4986- with pytest.raises(UserFacingError, match=re.escape(expected_error)):
4987+ with pytest.raises(
4988+ exceptions.UserFacingError, match=re.escape(expected_error)
4989+ ):
4990 instance.identity_doc
4991
4992 expected = [
4993diff --git a/uaclient/clouds/tests/test_identity.py b/uaclient/clouds/tests/test_identity.py
4994index f223f80..a21efee 100644
4995--- a/uaclient/clouds/tests/test_identity.py
4996+++ b/uaclient/clouds/tests/test_identity.py
4997@@ -1,18 +1,13 @@
4998 import mock
4999 import pytest
5000
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to status/vote changes: