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

Proposed by Lucas Albuquerque Medeiros de Moura
Status: Needs review
Proposed branch: ~lamoura/ubuntu/+source/ubuntu-advantage-tools:upload-27.7-jammy
Merge into: ubuntu/+source/ubuntu-advantage-tools:ubuntu/devel
Diff against target: 17950 lines (+8046/-2577)
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 (+38/-0)
debian/ubuntu-advantage-tools.logrotate (+1/-0)
debian/ubuntu-advantage-tools.postinst (+15/-15)
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 (+675/-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+417348@code.launchpad.net

Description of the change

This MR represents release 27.7 of ubuntu-advantage-tools. We are creating this new MR because we have identified an issue in the release that needed a fix.

Originally, the MR was made through this branch:
https://code.launchpad.net/~orndorffgrant/ubuntu/+source/ubuntu-advantage-tools/+git/ubuntu-advantage-tools/+merge/416678

We have a FFE ready for this release on Jammy:
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.
Revision history for this message
Paride Legovini (paride) wrote :

The bugfix commit (afc280d) LGTM. I verified the correspondence between the message strings names as called by postinst and the actual definitions.

I checked the commit hashes to make sure that the new commits are applied cleanly on the previously-reviewed branch, without rebasing. As this is the case, the new change can be considered alone, and given that it's a bugfix-only change I believe this doesn't require another FFe approval (virtually we could have uploaded the FFe approved version, followed by a bugfix-only upload, not requiring approval).

My review comment to https://code.launchpad.net/~orndorffgrant/ubuntu/+source/ubuntu-advantage-tools/+git/ubuntu-advantage-tools/+merge/416678 still applies, I'm reporting it here.

---

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
Chris Halse Rogers (raof) wrote :

SRU pass: there are a lot of changes here, but the changes to the packaging are OK and the others look like they're sensible. SRU pre-ack.

Revision history for this message
Chris Halse Rogers (raof) :

Unmerged commits

05f6b3a... by Lucas Albuquerque Medeiros de Moura

changelog: add line for postinst messages change

afc280d... by Lucas Albuquerque Medeiros de Moura

postinst: Adapt script for new messages module

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>

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

Subscribers

People subscribed via source and target branches

to status/vote changes: