Merge ~lamoura/ubuntu/+source/ubuntu-advantage-tools:hirsute-devel-release-27 into ubuntu/+source/ubuntu-advantage-tools:ubuntu/hirsute-devel

Proposed by Lucas Albuquerque Medeiros de Moura
Status: Needs review
Proposed branch: ~lamoura/ubuntu/+source/ubuntu-advantage-tools:hirsute-devel-release-27
Merge into: ubuntu/+source/ubuntu-advantage-tools:ubuntu/hirsute-devel
Diff against target: 12025 lines (+8488/-712)
87 files modified
Jenkinsfile (+28/-35)
README.md (+29/-12)
RELEASES.md (+63/-4)
apt-hook/20apt-esm-hook.conf (+10/-2)
apt-hook/Makefile (+11/-4)
apt-hook/hook.cc (+264/-74)
apt-hook/json-hook-src/go.mod (+3/-0)
apt-hook/json-hook-src/json-hook.go (+253/-0)
apt-hook/json-hook-src/json-hook_test.go (+410/-0)
debian/changelog (+158/-1)
debian/control (+8/-2)
debian/lintian-overrides (+5/-0)
debian/po/templates.pot (+2/-2)
debian/rules (+10/-2)
debian/ubuntu-advantage-tools.config (+14/-0)
debian/ubuntu-advantage-tools.postinst (+150/-40)
debian/ubuntu-advantage-tools.templates (+1/-1)
dev/null (+0/-0)
features/attach_invalidtoken.feature (+1/-1)
features/attach_validtoken.feature (+63/-28)
features/attached_commands.feature (+12/-12)
features/attached_enable.feature (+62/-118)
features/cloud.py (+0/-12)
features/environment.py (+27/-17)
features/gcp-ids.yaml (+3/-3)
features/staging_commands.feature (+50/-5)
features/steps/steps.py (+75/-3)
features/ubuntu_pro.feature (+39/-9)
features/unattached_commands.feature (+388/-3)
features/util.py (+0/-81)
integration-requirements.txt (+1/-1)
lib/reboot_cmds.py (+12/-8)
lib/ua_update_messaging.py (+302/-0)
setup.py (+1/-0)
systemd/ua-messaging.service (+8/-0)
systemd/ua-messaging.timer (+11/-0)
tools/test_xenial_upgrade.sh (+224/-0)
tools/tox-lxd-runner (+2/-2)
tox.ini (+2/-2)
uaclient-devel.conf (+1/-0)
uaclient.conf (+1/-0)
uaclient/apt.py (+1/-0)
uaclient/cli.py (+87/-18)
uaclient/clouds/identity.py (+12/-0)
uaclient/clouds/tests/test_identity.py (+31/-0)
uaclient/config.py (+136/-8)
uaclient/contract.py (+1/-2)
uaclient/defaults.py (+10/-1)
uaclient/entitlements/esm.py (+53/-7)
uaclient/entitlements/fips.py (+9/-3)
uaclient/entitlements/livepatch.py (+3/-3)
uaclient/entitlements/repo.py (+3/-3)
uaclient/entitlements/tests/test_base.py (+2/-2)
uaclient/entitlements/tests/test_cc.py (+1/-1)
uaclient/entitlements/tests/test_esm.py (+145/-34)
uaclient/entitlements/tests/test_fips.py (+38/-1)
uaclient/entitlements/tests/test_livepatch.py (+8/-6)
uaclient/entitlements/tests/test_repo.py (+1/-1)
uaclient/exceptions.py (+12/-0)
uaclient/gpg.py (+1/-1)
uaclient/security.py (+1179/-0)
uaclient/serviceclient.py (+19/-2)
uaclient/status.py (+237/-37)
uaclient/testing/fakes.py (+4/-1)
uaclient/tests/test_apt.py (+5/-1)
uaclient/tests/test_cli.py (+2/-0)
uaclient/tests/test_cli_attach.py (+33/-3)
uaclient/tests/test_cli_auto_attach.py (+28/-0)
uaclient/tests/test_cli_detach.py (+31/-2)
uaclient/tests/test_cli_disable.py (+27/-3)
uaclient/tests/test_cli_enable.py (+31/-7)
uaclient/tests/test_cli_fix.py (+78/-0)
uaclient/tests/test_cli_refresh.py (+20/-1)
uaclient/tests/test_cli_status.py (+81/-7)
uaclient/tests/test_config.py (+162/-31)
uaclient/tests/test_contract.py (+1/-8)
uaclient/tests/test_reboot_cmds.py (+48/-1)
uaclient/tests/test_security.py (+2344/-0)
uaclient/tests/test_serviceclient.py (+24/-0)
uaclient/tests/test_status.py (+49/-1)
uaclient/tests/test_ua_update_messaging.py (+469/-0)
uaclient/tests/test_util.py (+237/-9)
uaclient/tests/test_version.py (+10/-3)
uaclient/util.py (+101/-18)
uaclient/version.py (+2/-2)
update-motd.d/88-esm-announce (+4/-0)
update-motd.d/91-contract-ua-esm-status (+4/-0)
Reviewer Review Type Date Requested Status
Bryce Harrington (community) Approve
Review via email: mp+401650@code.launchpad.net

Description of the change

This is a branch used only as a base for reviewing release 27 of uaclient. It brings all the functionality that we are going to release across ubuntu releases (xenial to hirsute).

To reproduce it:

1. Add the main project as upstream:
git remote add upstream <email address hidden>:canonical/ubuntu-advantage-client.git

2. git fetch upstream
3. Manually cherry-pick those commits:

140d0ee657d70e22109f17de02f9cc3a3e8df529
80b4172fa4dcac4355ab72842d8a9fe082d488fb
e134dd489aed8ec3cb5f8a5510aaf8eeff1091de
985f8d1eb74512c535bbe0f03cd30c3570d6ba2b
bad262200eb38d23a2885168ad1676503ec538ef
c76dcf249adb6f300d750408275d9129c5d00a7b
c6ceeedc536c7144fb5d10463dab5692f2970ef0
bb3d8928acd4ef7e6091ba2ecf69091b733c2774

4. git cherry-pick b2de092ebeb9480c837048c7551bc50fb3a25377..bb623147c73c2984f75b35e3947595c6027350ce

Skip all of merge commits and solve the conflicts on debian/changelog and debian/control

5. Add a commit bumping the project version to 27.0

To post a comment you must log in.
7a474e8... by Chad Smith

config: avoid tracebacks on invalid features value in uaclient.conf

If /etc/ubuntu-advantage/uaclient.conf contains unexpected values
in features: settings log a warning and return an empty dict.

Avoid tracing on every ua subcommand call.

Fixes: #1564

2d4d78c... by Lucas Albuquerque Medeiros de Moura

Remove redundant messaging from uaclient

Currently, uaclient is advertising esm services by allowing
motd to show messages saying that additional packages could
be installed if esm service was enabled. We don't need those
type of messages because update-notifier will handle them.

Also, we are updating the code to always show expired message
for esm-infra services, even if the distro is not ESM

851036e... by Grant Orndorff

test: uncomment additional xenial upgrade tests

3b3e646... by Chad Smith

apt-hook: avoid reporting and counting duplicate package names

Only track unique named package upgrades in ESM.
Both esm -updates and esm-security pockets can contain same
packages. Avoid counting duplicates.

Fixes: #1578

a0dc53a... by Chad Smith

messages: add optional (s) to apt messaging to include singular/plural pkgs

5aa8c38... by Grant Orndorff

fix: dont say reboot required when unnecessary (#1577)

Before, we were assuming that if the system reboot
flag was set, then it was because of our fix operation.
Now, we are carving out a case where we can safely know
that the reboot is not required because of our fix.
Specifically, if our fix operation didn't actually install
anything, then it couldn't have caused the reboot to be
required.

LP: #1926183

bba3fd7... by Lucas Albuquerque Medeiros de Moura

changelog for release 27.0

Revision history for this message
Chad Smith (chad.smith) wrote :

Note that functional equivalent bits have been uploaded to https://launchpad.net/~orndorffgrant/+archive/ubuntu/uaclient-staging-27 for hirsute and impish for testing purposes

Revision history for this message
Chad Smith (chad.smith) wrote :

Since we are now actualy SRUing into Hirsute we have to follow the SRU exception process for ua-tools @ https://wiki.ubuntu.com/UbuntuAdvantageToolsUpdates

So the debian/changelog needs to reflect our SRU process bug (and we won't link any other LP bugs in that SRU)

diff --git a/debian/changelog b/debian/changelog
index 9db7b0de..2bae3d2b 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,11 +1,11 @@
 ubuntu-advantage-tools (27.0~21.04.1) hirsute; urgency=medium

- * New upstream release 27.0:
+ * New upstream release 27.0: (LP: #1926361)
     - messages: add optional (s) to apt messaging to include
       singular/plural pkgs
     - apt-hook: avoid reporting and counting duplicate package
       names (GH: #1578)
- - fix: dont say reboot required when unnecessary (LP: #1926183)
+ - fix: dont say reboot required when unnecessary
     - test: uncomment additional xenial upgrade tests

  -- Lucas Moura <email address hidden> Tue, 27 Apr 2021 15:31:06 -0300

21e6c1e... by Lucas Albuquerque Medeiros de Moura

fix changelog

Revision history for this message
Bryce Harrington (bryce) wrote :

Given that this is essentially a new upstream release, for review purposes I'm going to treat this as an upstream package merge. In other words, rather than a detailed code review I'm mainly going to focus on the packaging changes and integration testing, and assume the main codebase changes have been adequately reviewed and tested already. (The short timeframe we have really doesn't permit a full review anyway.)

However, I did do a cursory review through the 10 commits just to familiarize myself and make sure there's nothing scary. Didn't find anything notable, (other than that this impressively hits four different programming languages!)

I ran into a test failure on initial build which went away after subsequent builds. I'm ignoring that since I couldn't reproduce it, but am noting it in case it comes up again. The issue seemed to involve not recognizing one of the files in lib/ as a python module since there is not a lib/__init__.py. If that crops up anywhere else, it may need adjustment for how it imports the module.

Other than that, the build and test cases all worked fine, and the package installed and upgraded/downgraded without issue.

I did run into a problem trying to use the test case from the SRU bug to reproduce the issue. See
https://pastebin.ubuntu.com/p/WYmK6467NF/. I also tested the libcaca0 package from the original bug report, and that gave similar results. Chad suggested testing against ruby2.7=2.7.2-4ubuntu1 with `ua fix USN-4922-2`, and that worked ok. I gather the metadata provided for CVEs isn't as complete as the metadata provided with USN's. However, it seems like it may be hard to justify this SRU if the original issue is not seen to be fixed by the change... I would suggest changing the test case to one like ruby2.7 that properly demonstrates the bug + fix, and adding a discussion in the SRU text at how this will also work for CVEs once the metadata issue is resolved.

Since the SRU process isn't required for devel versions, I'll go ahead and mark this approved for impish and proceed with uploading.

review: Approve
Revision history for this message
Bryce Harrington (bryce) wrote :

I also uploaded this hirsute package since the package itself is good to go. The SRU bug will need some attention though.

Unmerged commits

21e6c1e... by Lucas Albuquerque Medeiros de Moura

fix changelog

bba3fd7... by Lucas Albuquerque Medeiros de Moura

changelog for release 27.0

a0dc53a... by Chad Smith

messages: add optional (s) to apt messaging to include singular/plural pkgs

3b3e646... by Chad Smith

apt-hook: avoid reporting and counting duplicate package names

Only track unique named package upgrades in ESM.
Both esm -updates and esm-security pockets can contain same
packages. Avoid counting duplicates.

Fixes: #1578

5aa8c38... by Grant Orndorff

fix: dont say reboot required when unnecessary (#1577)

Before, we were assuming that if the system reboot
flag was set, then it was because of our fix operation.
Now, we are carving out a case where we can safely know
that the reboot is not required because of our fix.
Specifically, if our fix operation didn't actually install
anything, then it couldn't have caused the reboot to be
required.

LP: #1926183

851036e... by Grant Orndorff

test: uncomment additional xenial upgrade tests

2d4d78c... by Lucas Albuquerque Medeiros de Moura

Remove redundant messaging from uaclient

Currently, uaclient is advertising esm services by allowing
motd to show messages saying that additional packages could
be installed if esm service was enabled. We don't need those
type of messages because update-notifier will handle them.

Also, we are updating the code to always show expired message
for esm-infra services, even if the distro is not ESM

5ea5a1a... by Grant Orndorff

apt-hook: new json hook for security update counts

This new hook takes advantage of the newly created
"stats" json hooj in apt to print a message about the
number of security updates to be installed on
apt upgrade.

7a474e8... by Chad Smith

config: avoid tracebacks on invalid features value in uaclient.conf

If /etc/ubuntu-advantage/uaclient.conf contains unexpected values
in features: settings log a warning and return an empty dict.

Avoid tracing on every ua subcommand call.

Fixes: #1564

ede5523... by Lucas Albuquerque Medeiros de Moura

Bump version to 27

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/Jenkinsfile b/Jenkinsfile
index ebee6f9..2082e19 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -37,35 +37,28 @@ pipeline {
37 '''37 '''
38 }38 }
39 }39 }
40 stage ('Lint and Style') {40 stage("flake8") {
41 parallel {41 steps {
42 stage("flake8") {42 sh '''
43 steps {43 set +x
44 sh '''44 tox -e flake8
45 set +x45 '''
46 . $TMPDIR/bin/activate46 }
47 tox --parallel--safe-build -e flake847 }
48 '''48 stage("style") {
49 }49 steps {
50 }50 sh '''
51 stage("style") {51 set +x
52 steps {52 tox -e black
53 sh '''53 '''
54 set +x54 }
55 . $TMPDIR/bin/activate55 }
56 tox --parallel--safe-build -e black56 stage("mypy") {
57 '''57 steps {
58 }58 sh '''
59 }59 set +x
60 stage("mypy") {60 tox -e mypy
61 steps {61 '''
62 sh '''
63 set +x
64 . $TMPDIR/bin/activate
65 tox --parallel--safe-build -e mypy
66 '''
67 }
68 }
69 }62 }
70 }63 }
71 stage ('Unit Tests') {64 stage ('Unit Tests') {
@@ -170,7 +163,7 @@ pipeline {
170 stage("lxc 14.04") {163 stage("lxc 14.04") {
171 environment {164 environment {
172 UACLIENT_BEHAVE_DEBS_PATH = "${TMPDIR}trusty/"165 UACLIENT_BEHAVE_DEBS_PATH = "${TMPDIR}trusty/"
173 UACLIENT_BEHAVE_ARTIFACT_DIR = "${TMPDIR}artifacts/behave-lxd-14.04"166 UACLIENT_BEHAVE_ARTIFACT_DIR = "artifacts/behave-lxd-14.04"
174 }167 }
175 steps {168 steps {
176 sh '''169 sh '''
@@ -183,7 +176,7 @@ pipeline {
183 stage("lxc 16.04") {176 stage("lxc 16.04") {
184 environment {177 environment {
185 UACLIENT_BEHAVE_DEBS_PATH = "${TMPDIR}xenial/"178 UACLIENT_BEHAVE_DEBS_PATH = "${TMPDIR}xenial/"
186 UACLIENT_BEHAVE_ARTIFACT_DIR = "${TMPDIR}artifacts/behave-lxd-16.04"179 UACLIENT_BEHAVE_ARTIFACT_DIR = "artifacts/behave-lxd-16.04"
187 }180 }
188 steps {181 steps {
189 sh '''182 sh '''
@@ -196,7 +189,7 @@ pipeline {
196 stage("lxc 18.04") {189 stage("lxc 18.04") {
197 environment {190 environment {
198 UACLIENT_BEHAVE_DEBS_PATH = "${TMPDIR}bionic/"191 UACLIENT_BEHAVE_DEBS_PATH = "${TMPDIR}bionic/"
199 UACLIENT_BEHAVE_ARTIFACT_DIR = "${TMPDIR}artifacts/behave-lxd-18.04"192 UACLIENT_BEHAVE_ARTIFACT_DIR = "artifacts/behave-lxd-18.04"
200 }193 }
201 steps {194 steps {
202 sh '''195 sh '''
@@ -209,7 +202,7 @@ pipeline {
209 stage("lxc vm 20.04") {202 stage("lxc vm 20.04") {
210 environment {203 environment {
211 UACLIENT_BEHAVE_DEBS_PATH = "${TMPDIR}focal/"204 UACLIENT_BEHAVE_DEBS_PATH = "${TMPDIR}focal/"
212 UACLIENT_BEHAVE_ARTIFACT_DIR = "${TMPDIR}artifacts/behave-vm-20.04"205 UACLIENT_BEHAVE_ARTIFACT_DIR = "artifacts/behave-vm-20.04"
213 }206 }
214 steps {207 steps {
215 sh '''208 sh '''
@@ -222,7 +215,7 @@ pipeline {
222 stage("awspro 18.04") {215 stage("awspro 18.04") {
223 environment {216 environment {
224 UACLIENT_BEHAVE_DEBS_PATH = "${TMPDIR}bionic/"217 UACLIENT_BEHAVE_DEBS_PATH = "${TMPDIR}bionic/"
225 UACLIENT_BEHAVE_ARTIFACT_DIR = "${TMPDIR}artifacts/behave-awspro-18.04"218 UACLIENT_BEHAVE_ARTIFACT_DIR = "artifacts/behave-awspro-18.04"
226 }219 }
227 steps {220 steps {
228 sh '''221 sh '''
@@ -253,7 +246,7 @@ pipeline {
253 currentBuild.result = 'UNSTABLE'246 currentBuild.result = 'UNSTABLE'
254 }247 }
255 try {248 try {
256 archiveArtifacts "/tmp/${BUILD_TAG}/artifacts/**/*"249 archiveArtifacts "artifacts/**/**/*"
257 } catch (Exception e) {250 } catch (Exception e) {
258 echo "No integration test artifacts found. Presume success."251 echo "No integration test artifacts found. Presume success."
259 }252 }
diff --git a/README.md b/README.md
index c45c58b..7e140c0 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,15 @@ The client comes pre-installed on all Ubuntu systems in the debian packages
20images will also contain `ubuntu-advantage-pro` which automates machine attach20images will also contain `ubuntu-advantage-pro` which automates machine attach
21on custom AWS and Azure images.21on custom AWS and Azure images.
2222
23Additionally, there are 3 PPAs with different release channels of the Ubuntu Advantage Client:
24
251. Stable: This contains stable builds only.
26 - add with `sudo add-apt-repository ppa:ua-client/stable`
272. Staging: This contains builds that are being prepared for release to stable.
28 - add with `sudo add-apt-repository ppa:ua-client/staging`
293. Daily: This PPA is updated every day with the latest changes.
30 - add with `sudo add-apt-repository ppa:ua-client/daily`
31
23Users can manually run the `ua` command to learn more or view the manpage.32Users can manually run the `ua` command to learn more or view the manpage.
2433
25## Terminology34## Terminology
@@ -37,7 +46,7 @@ Ubuntu Advantage Client performs:
3746
3847
39## Architecture48## Architecture
40Ubuntu Advantage client, hereafter "UA client", is python3-based command line49Ubuntu Advantage client, hereafter "UA client", is a python3-based command line
41utility. It provides a CLI to attach, detach, enable,50utility. It provides a CLI to attach, detach, enable,
42disable and check status of support related services.51disable and check status of support related services.
4352
@@ -83,7 +92,7 @@ enabled or disabled.
8392
84If a contract entitles a machine to a service, `root` user can enable the93If a contract entitles a machine to a service, `root` user can enable the
85service with `ua enable <service>`. If a service can be disabled94service with `ua enable <service>`. If a service can be disabled
86`ua disabled <service>` will be permitted.95`ua disable <service>` will be permitted.
8796
88The goal of the UA client is to remain simple and flexible and let the97The goal of the UA client is to remain simple and flexible and let the
89contracts backend drive dynamic changes in contract offerings and constraints.98contracts backend drive dynamic changes in contract offerings and constraints.
@@ -120,7 +129,15 @@ The following describes the intent of UA client related directories:
120129
121## Testing130## Testing
122131
123All unit and lint tests are run using tox:132All unit and lint tests are run using `tox`. We also use `tox-pip-version` to specify an older pip version as a workaround: we have some required dependencies that can't meet the strict compatibility checks of current pip versions.
133
134First, install `tox` and `tox-pip-version` - you'll only have to do this once.
135
136```shell
137make testdeps
138```
139
140Then you can run the unit and lint tests:
124141
125```shell142```shell
126tox143tox
@@ -144,7 +161,7 @@ logic for those tests.
144161
145By default, integration tests will do the folowing on a given cloud platform:162By default, integration tests will do the folowing on a given cloud platform:
146 * Launch an instance running latest daily image of the target Ubuntu release163 * Launch an instance running latest daily image of the target Ubuntu release
147 * Add the Ubuntu advantage client daily build PPA: [ppa:canonical-server/ua-client-daily](https://code.launchpad.net/~canonical-server/+archive/ubuntu/ua-client-daily)164 * Add the Ubuntu advantage client daily build PPA: [ppa:ua-client/daily](https://code.launchpad.net/~ua-client/+archive/ubuntu/daily)
148 * Install the appropriate ubuntu-advantage-tools and ubuntu-advantage-pro deb165 * Install the appropriate ubuntu-advantage-tools and ubuntu-advantage-pro deb
149 * Stop the instance and snapshot it creating an updated bootable image for166 * Stop the instance and snapshot it creating an updated bootable image for
150 test runs167 test runs
@@ -181,9 +198,9 @@ Furthermore, when developing/debugging a new scenario:
181198
182 1. Add a `@wip` tag decorator on the scenario199 1. Add a `@wip` tag decorator on the scenario
183 2. To only run @wip scenarios run: `tox -e behave-20.04 -- -w`200 2. To only run @wip scenarios run: `tox -e behave-20.04 -- -w`
184 4. If you want to use a debugger:201 3. If you want to use a debugger:
185 a. Add ipdb to integration-requirements.txt202 1. Add ipdb to integration-requirements.txt
186 b. Add ipdb.set_trace() in the code block you wish to debug203 2. Add ipdb.set_trace() in the code block you wish to debug
187204
188(If you're getting started with behave, we recommend at least reading205(If you're getting started with behave, we recommend at least reading
189through [the behave206through [the behave
@@ -271,7 +288,7 @@ To update `features/aws-ids.yaml`, run `./tools/refresh-aws-pro-ids` and put up
271a pull request against this repo to updated that content from the ua-contracts288a pull request against this repo to updated that content from the ua-contracts
272marketplace definitions.289marketplace definitions.
273290
274* To manually run EC2 integration tests using packages from `ppa:canonical-server/ua-client-daily` provide the following environment vars:291* To manually run EC2 integration tests using packages from `ppa:ua-client/daily` provide the following environment vars:
275292
276```sh293```sh
277UACLIENT_BEHAVE_AWS_ACCESS_KEY_ID=<blah> UACLIENT_BEHAVE_AWS_SECRET_KEY=<blah2> tox -e behave-awspro-20.04294UACLIENT_BEHAVE_AWS_ACCESS_KEY_ID=<blah> UACLIENT_BEHAVE_AWS_SECRET_KEY=<blah2> tox -e behave-awspro-20.04
@@ -295,13 +312,13 @@ The following tox environments allow for testing focal on Azure:
295```312```
296313
297To run the test for a different release, just update the release version string. For example,314To run the test for a different release, just update the release version string. For example,
298to run AWS pro xenial tests, you can run:315to run Azure pro xenial tests, you can run:
299316
300```317```
301tox -e behave-azurepro-16.04318tox -e behave-azurepro-16.04
302```319```
303320
304In order to run EC2 tests the following environment variables are required:321In order to run Azure tests the following environment variables are required:
305 - UACLIENT_BEHAVE_AZ_CLIENT_ID322 - UACLIENT_BEHAVE_AZ_CLIENT_ID
306 - UACLIENT_BEHAVE_AZ_CLIENT_SECRET323 - UACLIENT_BEHAVE_AZ_CLIENT_SECRET
307 - UACLIENT_BEHAVE_AZ_SUBSCRIPTION_ID324 - UACLIENT_BEHAVE_AZ_SUBSCRIPTION_ID
@@ -312,7 +329,7 @@ To specifically run non-ubuntu pro tests using canonical cloud-images an
312additional token obtained from https://ubuntu.com/advantage needs to be set:329additional token obtained from https://ubuntu.com/advantage needs to be set:
313 - UACLIENT_BEHAVE_CONTRACT_TOKEN=<your_token>330 - UACLIENT_BEHAVE_CONTRACT_TOKEN=<your_token>
314331
315* To manually run Azure integration tests using packages from `ppa:canonical-server/ua-client-daily` provide the following environment vars:332* To manually run Azure integration tests using packages from `ppa:ua-client/daily` provide the following environment vars:
316333
317```sh334```sh
318UACLIENT_BEHAVE_AZ_CLIENT_ID=<blah> UACLIENT_BEHAVE_AZ_CLIENT_SECRET=<blah2> UACLIENT_BEHAVE_AZ_SUBSCRIPTION_ID=<blah3> UACLIENT_BEHAVE_AZ_TENANT_ID=<blah4> tox -e behave-azurepro-20.04335UACLIENT_BEHAVE_AZ_CLIENT_ID=<blah> UACLIENT_BEHAVE_AZ_CLIENT_SECRET=<blah2> UACLIENT_BEHAVE_AZ_SUBSCRIPTION_ID=<blah3> UACLIENT_BEHAVE_AZ_TENANT_ID=<blah4> tox -e behave-azurepro-20.04
@@ -337,7 +354,7 @@ dpkg-buildpackage -us -uc
337```354```
338355
339**Note** It will build the package with dependencies for the Ubuntu release on356**Note** It will build the package with dependencies for the Ubuntu release on
340which you are building, so it's best to build in a container of kvm for the357which you are building, so it's best to build in a container or kvm for the
341release you are targeting.358release you are targeting.
342359
343OR, if you want to build for a target release other than the release360OR, if you want to build for a target release other than the release
diff --git a/RELEASES.md b/RELEASES.md
index e31dc37..98e8aaf 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -54,10 +54,69 @@ Previous release bugs:
5454
55Manually perform a binary package copy from Daily PPA to Premium PPA and notify image creators55Manually perform a binary package copy from Daily PPA to Premium PPA and notify image creators
5656
57 1. [Open Daily PPA copy-package operation](https://code.launchpad.net/~canonical-server/+archive/ubuntu/ua-client-daily/+copy-packages)57 1. [Open Daily PPA copy-package operation](https://code.launchpad.net/~ua-client/+archive/ubuntu/daily/+copy-packages)
58 2. Check Trusty, Xenial, Bionic package58 2. Check Trusty, Xenial, Bionic package
59 3. Select Destination PPA: UA Client Premium [~canonical-server/ubuntu/ua-client-premium]59 3. Select Destination PPA: UA Client Premium [~ua-client/ubuntu/staging]
60 4. Select Destination series: The same series60 4. Select Destination series: The same series
61 5. Copy options: "Copy existing binaries61 5. Copy options: "Copy existing binaries"
62 6. Click Copy packages62 6. Click Copy packages
63 7. Notify Pro Image creatros about expected Premium PPA version (patviafore/rcj)63 7. Notify Pro Image creators about expected Premium PPA version (patviafore/rcj/powersj)
64
65
66## Release to PPA
67
68We manually upload the packages to our staging/stable PPAs. If you want to cut a new release and
69upload to one of these PPAs, follow these steps:
70
71 1. Do a `git cherry-pick` on the commits that should be included in the release
72 2. Update the debian/changelog file:
73 * Create a new entry in the `debian/changelog` file:
74 * You can do that by running ` dch --newversion <version-name>`
75 * Remember to update the release from `UNRELEASED` to the most recently supported
76 ubuntu release
77 * Populate `debian/changelog` with the commits you have cherry-picked
78 * You can do that by running `git log <first-cherry-pick-commit>..<last-cherry-pick-commit> | log2dch`
79 * This will generate a list of commits that could be included in the changelog. If you don't
80 have `log2dch`, you can get it from the [uss-tableflip](https://github.com/canonical/uss-tableflip)
81 * You don't need to include all of the commits generated. Remember that the changelog should
82 be read by the user to understand the new features/modifications in the package. If you
83 think a commit will not add that much to the user experience, you can drop it from the
84 changelog
85 * To structure the changelog you can use the other entries as example. But we basically try
86 keep this order: debian changes, new features/modifications, testing
87 3. Start building the package:
88 * *WARNING* Build the package in a clean environment. The reason for that is because the package
89 will contain everything that it is present in the folder. If you are storing credentials or
90 other sensible development information in your folder, they will be uploaded too when we send
91 the package to the ppa. A clean environment is the safest way to perform this.
92 * Build the necessary artifacts that allow building the package
93 * Guarantee that you have a gpg key in the system. You will use that gpg key to sign the
94 package.
95 * If you don't yet have a gpg key set up, follow the instructions
96 [here](https://help.launchpad.net/YourAccount/ImportingYourPGPKey) to create a key,
97 publish it to `hkp://keyserver.ubuntu.com`, and import it into Launchpad.
98 * We can achieve that by running the `build-package` command when your current folder is the
99 top of source tree. This script is also found on the [uss-tableflip](https://github.com/canonical/uss-tableflip) repo.
100 * This script will generate all the package artifacts in the parent directory as `../out`.
101 * Verify if we can build the package:
102 * We can achieve that by running the `sbuild-it` command. This script is also found on the
103 [uss-tableflip](https://github.com/canonical/uss-tableflip) repo.
104 * Before you run `sbuild-it` for the first time, you'll need to set up a chroot for each Ubuntu release.
105 Follow [these instructions](https://gist.github.com/smoser/14df5f0cd621e10d2282d7c90345e322#new-sbuild-creation)
106 to create a chroot for each supported release.
107 * To use it, you can just run `sbuild-it ../out/<package_name>.dsc
108 * If the package was built sucessfully, you can move to the next step.
109 4. Repeat that for older ubuntu releases:
110 * Currently, we test this build process for `Trusty(14.04)`, `Xenial(16.04)`, `Bionic(18.04)`,
111 `Focal(20.10)`, `Groovy(20.10)` and `Hirsute(21.04)`
112 * To test this other releases, just change the changelog to target those releases.
113 PS: remember to also change the version number on the changelog. For example, suppose
114 the new version is `1.1~20.04.1`. If you want to test Bionic now, change it to
115 `1.1~18.04.1`.
116 * Commit those changes and perform the `build-package` and `sbuild-it` steps for the release.
117 * These commits are just local commits for this build process - do not push them to remote repository.
118 5. After all of the releases are tested, we can start uploading to the ppa. For each release, run
119 the command `dput ppa:ua-client/stable ../out/<package_name>_source.changes`
120 * Run this command for each release you are going to upload
121 * Remember to have launchpad already properly configured in your system to allow you uploading
122 packages to the ppa.
diff --git a/apt-hook/20apt-esm-hook.conf b/apt-hook/20apt-esm-hook.conf
index d6d1ad1..c4e4ebb 100644
--- a/apt-hook/20apt-esm-hook.conf
+++ b/apt-hook/20apt-esm-hook.conf
@@ -1,7 +1,15 @@
1APT::Update::Post-Invoke-Stats {1APT::Update::Post-Invoke-Stats {
2 "[ ! -f /usr/lib/ubuntu-advantage/apt-esm-hook ] || /usr/lib/ubuntu-advantage/apt-esm-hook";2 "[ ! -f /usr/lib/ubuntu-advantage/apt-esm-hook ] || /usr/lib/ubuntu-advantage/apt-esm-hook post-invoke-stats";
3};3};
44
5APT::Install::Post-Invoke-Success {5APT::Install::Post-Invoke-Success {
6 "[ ! -f /usr/lib/ubuntu-advantage/apt-esm-hook ] || /usr/lib/ubuntu-advantage/apt-esm-hook";6 "[ ! -f /usr/lib/ubuntu-advantage/apt-esm-hook ] || /usr/lib/ubuntu-advantage/apt-esm-hook post-invoke-success";
7}; 7};
8
9APT::Install::Pre-Invoke {
10 "[ ! -f /usr/lib/ubuntu-advantage/apt-esm-hook ] || /usr/lib/ubuntu-advantage/apt-esm-hook pre-invoke";
11}
12
13AptCli::Hooks::Upgrade {
14 "[ ! -f /usr/lib/ubuntu-advantage/apt-esm-json-hook ] || /usr/lib/ubuntu-advantage/apt-esm-json-hook";
15}
diff --git a/apt-hook/Makefile b/apt-hook/Makefile
index 8eeae10..15c4856 100644
--- a/apt-hook/Makefile
+++ b/apt-hook/Makefile
@@ -1,16 +1,23 @@
1all: build1all: build
22
3build: hook ubuntu-advantage.pot3build: hook ubuntu-advantage.pot json-hook
44
5ubuntu-advantage.pot: hook.cc5ubuntu-advantage.pot: hook.cc
6 xgettext hook.cc -o ubuntu-advantage.pot6 xgettext hook.cc -o ubuntu-advantage.pot
77
8hook: hook.cc8hook: hook.cc
9 $(CXX) -Wall -Wextra -pedantic $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) -g -o hook hook.cc -lapt-pkg $(LDLIBS)9 $(CXX) -Wall -Wextra -pedantic -std=c++11 $(CXXFLAGS) $(CPPFLAGS) $(LDFLAGS) -g -o hook hook.cc -lapt-pkg $(LDLIBS)
1010
11install: hook11json-hook:
12 cd json-hook-src && GOCACHE=/tmp/ go build json-hook.go
13
14install: hook json-hook
12 install -D -m 644 20apt-esm-hook.conf $(DESTDIR)/etc/apt/apt.conf.d/20apt-esm-hook.conf15 install -D -m 644 20apt-esm-hook.conf $(DESTDIR)/etc/apt/apt.conf.d/20apt-esm-hook.conf
13 install -D -m 755 hook $(DESTDIR)/usr/lib/ubuntu-advantage/apt-esm-hook16 install -D -m 755 hook $(DESTDIR)/usr/lib/ubuntu-advantage/apt-esm-hook
17 install -D -m 755 json-hook-src/json-hook $(DESTDIR)/usr/lib/ubuntu-advantage/apt-esm-json-hook
1418
15clean:19clean:
16 rm -f hook ubuntu-advantage.pot20 rm -f hook ubuntu-advantage.pot json-hook-src/json-hook
21
22test:
23 cd json-hook-src && go test
diff --git a/apt-hook/hook.cc b/apt-hook/hook.cc
index ed009de..5aed078 100644
--- a/apt-hook/hook.cc
+++ b/apt-hook/hook.cc
@@ -23,20 +23,50 @@
23#include <apt-pkg/policy.h>23#include <apt-pkg/policy.h>
24#include <apt-pkg/strutl.h>24#include <apt-pkg/strutl.h>
2525
26#include <algorithm>
26#include <fstream>27#include <fstream>
28#include <iostream>
27#include <sstream>29#include <sstream>
28#include <string>30#include <string>
31#include <vector>
2932
30#include <assert.h>33#include <assert.h>
31#include <sys/stat.h>34#include <sys/stat.h>
32#include <libintl.h>35#include <libintl.h>
33#include <locale.h>36#include <locale.h>
3437
38#define MOTD_ESM_SERVICE_STATUS_MESSAGE_STATIC_PATH "/var/lib/ubuntu-advantage/messages/motd-esm-service-status"
39#define MOTD_APPS_NO_PKGS_TEMPLATE_PATH "/var/lib/ubuntu-advantage/messages/motd-no-packages-apps.tmpl"
40#define MOTD_INFRA_NO_PKGS_TEMPLATE_PATH "/var/lib/ubuntu-advantage/messages/motd-no-packages-infra.tmpl"
41#define MOTD_APPS_PKGS_TEMPLATE_PATH "/var/lib/ubuntu-advantage/messages/motd-packages-apps.tmpl"
42#define MOTD_INFRA_PKGS_TEMPLATE_PATH "/var/lib/ubuntu-advantage/messages/motd-packages-infra.tmpl"
43#define MOTD_APPS_PKGS_STATIC_PATH "/var/lib/ubuntu-advantage/messages/motd-packages-apps"
44#define MOTD_INFRA_PKGS_STATIC_PATH "/var/lib/ubuntu-advantage/messages/motd-packages-infra"
45#define APT_PRE_INVOKE_APPS_NO_PKGS_TEMPLATE_PATH "/var/lib/ubuntu-advantage/messages/apt-pre-invoke-no-packages-apps.tmpl"
46#define APT_PRE_INVOKE_INFRA_NO_PKGS_TEMPLATE_PATH "/var/lib/ubuntu-advantage/messages/apt-pre-invoke-no-packages-infra.tmpl"
47#define APT_PRE_INVOKE_APPS_PKGS_TEMPLATE_PATH "/var/lib/ubuntu-advantage/messages/apt-pre-invoke-packages-apps.tmpl"
48#define APT_PRE_INVOKE_APPS_PKGS_STATIC_PATH "/var/lib/ubuntu-advantage/messages/apt-pre-invoke-packages-apps"
49#define APT_PRE_INVOKE_INFRA_PKGS_TEMPLATE_PATH "/var/lib/ubuntu-advantage/messages/apt-pre-invoke-packages-infra.tmpl"
50#define APT_PRE_INVOKE_INFRA_PKGS_STATIC_PATH "/var/lib/ubuntu-advantage/messages/apt-pre-invoke-packages-infra"
51#define APT_PRE_INVOKE_MESSAGE_STATIC_PATH "/var/lib/ubuntu-advantage/messages/apt-pre-invoke-esm-service-status"
52#define UBUNTU_NO_WARRANTY_STATIC_PATH "/var/lib/ubuntu-advantage/messages/ubuntu-no-warranty"
53
54
55#define ESM_APPS_PKGS_COUNT_TEMPLATE_VAR "{ESM_APPS_PKG_COUNT}"
56#define ESM_APPS_PACKAGES_TEMPLATE_VAR "{ESM_APPS_PACKAGES}"
57#define ESM_INFRA_PKGS_COUNT_TEMPLATE_VAR "{ESM_INFRA_PKG_COUNT}"
58#define ESM_INFRA_PACKAGES_TEMPLATE_VAR "{ESM_INFRA_PACKAGES}"
59
60enum Subcommand { PreInvoke, PostInvokeStats, PostInvokeSuccess, ProcessTemplates };
61
35struct result {62struct result {
36 int enabled_esms_i;63 int enabled_esms_i;
37 int disabled_esms_i;64 int disabled_esms_i;
65 std::vector<std::string> esm_i_packages;
66
38 int enabled_esms_a;67 int enabled_esms_a;
39 int disabled_esms_a;68 int disabled_esms_a;
69 std::vector<std::string> esm_a_packages;
40};70};
4171
42// Return parent pid of specified pid, using /proc (pid might be self)72// Return parent pid of specified pid, using /proc (pid might be self)
@@ -56,7 +86,7 @@ static std::string getppid_of(std::string pid)
56 getline(stream, line);86 getline(stream, line);
5787
58 if (line.find("PPid:") != 0)88 if (line.find("PPid:") != 0)
59 continue;89 continue;
6090
61 // Erase everything before a number91 // Erase everything before a number
62 line.erase(0, line.find_first_of("0123456789"));92 line.erase(0, line.find_first_of("0123456789"));
@@ -121,28 +151,38 @@ static void check_esm_upgrade(pkgCache::PkgIterator pkg, pkgPolicy *policy, resu
121 {151 {
122 for (pkgCache::VerFileIterator pf = ver.FileList(); !pf.end(); pf++)152 for (pkgCache::VerFileIterator pf = ver.FileList(); !pf.end(); pf++)
123 {153 {
124 // TODO: Just look at the origin, not pinning.154 if (pf.File().Archive() != 0 && pf.File().Origin() == std::string("UbuntuESM"))
125 if (pf.File().Archive() != 0 && pf.File().Origin() == std::string("UbuntuESM"))155 {
126 {156 if (std::find(res.esm_i_packages.begin(), res.esm_i_packages.end(), pkg.Name()) == res.esm_i_packages.end()) {
127 // Xenial and later should not be advertising unauthenticated ESM Infra apt repos157 res.esm_i_packages.push_back(pkg.Name());
128 if (policy->GetPriority(pf.File()) == -32768)158
129 res.disabled_esms_i++;159 // Pin-Priority: never unauthenticated APT repos == -32768
130 else160 if (policy->GetPriority(pf.File()) == -32768)
131 res.enabled_esms_i++;161 {
132162 res.disabled_esms_i++;
133 return;163 }
134 }164 else
135165 {
136 if (pf.File().Archive() != 0 && pf.File().Origin() == std::string("UbuntuESMApps"))166 res.enabled_esms_i++;
137 {167 }
138 // Xenial and later should not be advertising unauthenticated ESM Apps apt repos168 }
139 if (policy->GetPriority(pf.File()) == -32768)169 }
140 res.disabled_esms_a++;170 if (pf.File().Archive() != 0 && pf.File().Origin() == std::string("UbuntuESMApps"))
141 else171 {
142 res.enabled_esms_a++;172 if (std::find(res.esm_a_packages.begin(), res.esm_a_packages.end(), pkg.Name()) == res.esm_a_packages.end()) {
143173 res.esm_a_packages.push_back(pkg.Name());
144 return;174
145 }175 // Pin-Priority: never unauthenticated APT repos == -32768
176 if (policy->GetPriority(pf.File()) == -32768)
177 {
178 res.disabled_esms_a++;
179 }
180 else
181 {
182 res.enabled_esms_a++;
183 }
184 }
185 }
146 }186 }
147 }187 }
148}188}
@@ -172,6 +212,155 @@ static int get_update_count(result &res)
172 return count;212 return count;
173}213}
174214
215
216static void process_template_file(
217 std::string template_file_name,
218 std::string static_file_name,
219 std::string esm_a_pkgs_count,
220 std::string esm_a_pkgs,
221 std::string esm_i_pkgs_count,
222 std::string esm_i_pkgs
223) {
224 std::ifstream message_tmpl_file(template_file_name.c_str());
225 if (message_tmpl_file.is_open()) {
226 // This line loads the whole file contents into a string
227 std::string message_tmpl((std::istreambuf_iterator<char>(message_tmpl_file)), (std::istreambuf_iterator<char>()));
228
229 message_tmpl_file.close();
230
231 // Process all template variables
232 std::array<std::string, 4> tmpl_var_names = {
233 ESM_APPS_PKGS_COUNT_TEMPLATE_VAR,
234 ESM_APPS_PACKAGES_TEMPLATE_VAR,
235 ESM_INFRA_PKGS_COUNT_TEMPLATE_VAR,
236 ESM_INFRA_PACKAGES_TEMPLATE_VAR
237 };
238 std::array<std::string, 4> tmpl_var_vals = {
239 esm_a_pkgs_count,
240 esm_a_pkgs,
241 esm_i_pkgs_count,
242 esm_i_pkgs
243 };
244 for (uint i = 0; i < tmpl_var_names.size(); i++) {
245 size_t pos = message_tmpl.find(tmpl_var_names[i]);
246 if (pos != std::string::npos) {
247 message_tmpl.replace(pos, tmpl_var_names[i].size(), tmpl_var_vals[i]);
248 }
249 }
250
251 std::ofstream message_static_file(static_file_name.c_str());
252 if (message_static_file.is_open()) {
253 message_static_file << message_tmpl;
254 message_static_file.close();
255 }
256 } else {
257 remove(static_file_name.c_str());
258 }
259}
260
261static void process_all_templates(
262 int esm_a_pkgs_count,
263 std::string esm_a_pkgs,
264 int esm_i_pkgs_count,
265 std::string esm_i_pkgs
266) {
267 int bytes_written;
268 std::array<std::string, 4> static_file_names = {
269 APT_PRE_INVOKE_APPS_PKGS_STATIC_PATH,
270 MOTD_APPS_PKGS_STATIC_PATH,
271 APT_PRE_INVOKE_INFRA_PKGS_STATIC_PATH,
272 MOTD_INFRA_PKGS_STATIC_PATH,
273 };
274 std::array<std::string, 3> apt_static_files = {
275 APT_PRE_INVOKE_APPS_PKGS_STATIC_PATH,
276 APT_PRE_INVOKE_INFRA_PKGS_STATIC_PATH,
277 UBUNTU_NO_WARRANTY_STATIC_PATH
278 };
279 std::array<std::string, 3> motd_static_files = {
280 MOTD_APPS_PKGS_STATIC_PATH,
281 MOTD_INFRA_PKGS_STATIC_PATH,
282 UBUNTU_NO_WARRANTY_STATIC_PATH
283 };
284
285 std::vector<std::string> template_file_names;
286 if (esm_a_pkgs_count > 0) {
287 template_file_names.push_back(APT_PRE_INVOKE_APPS_PKGS_TEMPLATE_PATH);
288 template_file_names.push_back(MOTD_APPS_PKGS_TEMPLATE_PATH);
289 } else {
290 template_file_names.push_back(APT_PRE_INVOKE_APPS_NO_PKGS_TEMPLATE_PATH);
291 template_file_names.push_back(MOTD_APPS_NO_PKGS_TEMPLATE_PATH);
292 }
293 if (esm_i_pkgs_count > 0) {
294 template_file_names.push_back(APT_PRE_INVOKE_INFRA_PKGS_TEMPLATE_PATH);
295 template_file_names.push_back(MOTD_INFRA_PKGS_TEMPLATE_PATH);
296 } else {
297 template_file_names.push_back(APT_PRE_INVOKE_INFRA_NO_PKGS_TEMPLATE_PATH);
298 template_file_names.push_back(MOTD_INFRA_NO_PKGS_TEMPLATE_PATH);
299 }
300 for (uint i = 0; i < template_file_names.size(); i++) {
301 process_template_file(
302 template_file_names[i],
303 static_file_names[i],
304 std::to_string(esm_a_pkgs_count),
305 esm_a_pkgs,
306 std::to_string(esm_i_pkgs_count),
307 esm_i_pkgs
308 );
309 }
310
311 std::ofstream apt_pre_invoke_msg;
312 apt_pre_invoke_msg.open(APT_PRE_INVOKE_MESSAGE_STATIC_PATH);
313 for (uint i = 0; i < apt_static_files.size(); i++) {
314 std::ifstream message_file(apt_static_files[i]);
315 if (message_file.is_open()) {
316 apt_pre_invoke_msg << std::endl;
317 apt_pre_invoke_msg << message_file.rdbuf();
318 message_file.close();
319 };
320 }
321 bytes_written = apt_pre_invoke_msg.tellp();
322 if (bytes_written > 0) {
323 // Then we wrote some content add trailing newline
324 apt_pre_invoke_msg << std::endl;
325 }
326 apt_pre_invoke_msg.close();
327 if (bytes_written == 0) {
328 // We added nothing. Remove the file
329 remove(APT_PRE_INVOKE_MESSAGE_STATIC_PATH);
330 }
331
332 std::ofstream motd_msg;
333 motd_msg.open(MOTD_ESM_SERVICE_STATUS_MESSAGE_STATIC_PATH);
334 for (uint i = 0; i < motd_static_files.size(); i++) {
335 std::ifstream message_file(motd_static_files[i]);
336 if (message_file.is_open()) {
337 if ( i > 0 ) {
338 motd_msg << std::endl;
339 }
340 motd_msg << message_file.rdbuf();
341 message_file.close();
342 };
343 }
344 bytes_written = motd_msg.tellp();
345 if (bytes_written > 0) {
346 // Then we wrote some content add trailing newline
347 motd_msg << std::endl;
348 }
349 motd_msg.close();
350 if (bytes_written == 0) {
351 // We added nothing. Remove the file
352 remove(MOTD_ESM_SERVICE_STATUS_MESSAGE_STATIC_PATH);
353 }
354}
355
356static void output_file_if_present(std::string file_name) {
357 std::ifstream message_file(file_name);
358 if (message_file.is_open()) {
359 std::cout << message_file.rdbuf();
360 message_file.close();
361 }
362}
363
175// Preserves \0 bytes in a string literal364// Preserves \0 bytes in a string literal
176template<std::size_t n>365template<std::size_t n>
177std::string make_cmdline(const char (&s)[n])366std::string make_cmdline(const char (&s)[n])
@@ -191,6 +380,9 @@ bool has_arg(char **argv, const char *arg)
191int main(int argc, char *argv[])380int main(int argc, char *argv[])
192{381{
193 (void) argc; // unused382 (void) argc; // unused
383 Subcommand subcommand = ProcessTemplates;
384 bool test_run = false;
385
194 setlocale(LC_ALL, "");386 setlocale(LC_ALL, "");
195 textdomain("ubuntu-advantage");387 textdomain("ubuntu-advantage");
196 // Self testing388 // Self testing
@@ -206,69 +398,67 @@ int main(int argc, char *argv[])
206 assert(cmdline_eligible(make_cmdline("aptitude\0update\0")));398 assert(cmdline_eligible(make_cmdline("aptitude\0update\0")));
207 command_used = "";399 command_used = "";
208400
209 result res = {0, 0, 0, 0};401 if (has_arg(argv, "test")) {
402 // useful for testing
403 test_run = true;
404 command_used = "upgrade";
405 }
406 if (has_arg(argv, "pre-invoke")) {
407 subcommand = PreInvoke;
408 } else if (has_arg(argv, "post-invoke-stats")) {
409 subcommand = PostInvokeStats;
410 } else if (has_arg(argv, "post-invoke-success")) {
411 subcommand = PostInvokeSuccess;
412 } else if (has_arg(argv, "process-templates")) {
413 subcommand = ProcessTemplates;
414 }
210415
211 // useful for testing416 if (!test_run && subcommand != ProcessTemplates && !cmdline_eligible(getcmdline(getppid_of(getppid_of("self"))))) {
212 if (has_arg(argv, "test"))417 // Only run on valid apt commands, or when being used to process templates
213 command_used = "update";418 return 0;
419 }
214420
215 if (has_arg(argv, "test") || cmdline_eligible(getcmdline(getppid_of(getppid_of("self")))))421 // Iterate over apt cache looking for esm packages
216 get_update_count(res);422 result res = {0, 0, std::vector<std::string>(), 0, 0, std::vector<std::string>()};
423 get_update_count(res);
217 if (_error->PendingError())424 if (_error->PendingError())
218 {425 {
219 _error->DumpErrors();426 _error->DumpErrors();
220 return 1;427 return 1;
221 }428 }
222429
223 if (command_used == "update")430 // Compute all strings necessary to fill in templates
224 {431 std::string space_separated_esm_i_packages = "";
225 if (res.enabled_esms_i > 0)432 if (res.esm_i_packages.size() > 0) {
226 {433 for (uint i = 0; i < res.esm_i_packages.size() - 1; i++) {
227 ioprintf(std::cout,434 space_separated_esm_i_packages.append(res.esm_i_packages[i]);
228 ngettext("%d of the updates is from UA Infra: ESM.",435 space_separated_esm_i_packages.append(" ");
229 "%d of the updates are from UA Infra: ESM.",
230 res.enabled_esms_i),
231 res.enabled_esms_i);
232 ioprintf(std::cout, "\n");
233 }
234 if (res.enabled_esms_a > 0)
235 {
236 ioprintf(std::cout,
237 ngettext("%d of the updates is from UA Apps: ESM.",
238 "%d of the updates are from UA Apps: ESM.",
239 res.enabled_esms_a),
240 res.enabled_esms_a);
241 ioprintf(std::cout, "\n");
242 }436 }
437 space_separated_esm_i_packages.append(res.esm_i_packages[res.esm_i_packages.size() - 1]);
243 }438 }
244439 std::string space_separated_esm_a_packages = "";
245 if (res.disabled_esms_i > 0 || res.disabled_esms_a > 0)440 if (res.esm_a_packages.size() > 0) {
246 {441 for (uint i = 0; i < res.esm_a_packages.size() - 1; i++) {
247 if (command_used != "update")442 space_separated_esm_a_packages.append(res.esm_a_packages[i]);
248 std::cout << std::endl;443 space_separated_esm_a_packages.append(" ");
249 if (res.disabled_esms_i > 0)
250 {
251 ioprintf(std::cout,
252 ngettext("%d additional update is available with UA Infra: ESM.",
253 "%d additional updates are available with UA Infra: ESM.",
254 res.disabled_esms_i),
255 res.disabled_esms_i);
256 ioprintf(std::cout, "\n");
257 }444 }
258 if (res.disabled_esms_a > 0)445 space_separated_esm_a_packages.append(res.esm_a_packages[res.esm_a_packages.size() - 1]);
259 {446 }
260 ioprintf(std::cout,447 std::string esm_i_packages_count = std::to_string(res.esm_i_packages.size());
261 ngettext("%d additional update is available with UA Apps: ESM.",448 std::string esm_a_packages_count = std::to_string(res.esm_a_packages.size());
262 "%d additional updates are available with UA Apps ESM.",449
263 res.disabled_esms_a),450 process_all_templates(
264 res.disabled_esms_a);451 res.esm_a_packages.size(),
265 ioprintf(std::cout, "\n");452 space_separated_esm_a_packages,
453 res.esm_i_packages.size(),
454 space_separated_esm_i_packages
455 );
456
457 // Execute specified subcommand
458 if (subcommand == PreInvoke) {
459 if (command_used == "upgrade" || command_used == "dist-upgrade") {
460 output_file_if_present(APT_PRE_INVOKE_MESSAGE_STATIC_PATH);
266 }461 }
267
268 ioprintf(std::cout, gettext("To see these additional updates run: apt list --upgradable"));
269 ioprintf(std::cout, "\n");
270 ioprintf(std::cout, gettext("See https://ubuntu.com/advantage or run: sudo ua status"));
271 ioprintf(std::cout, "\n");
272 }462 }
273463
274 return 0;464 return 0;
diff --git a/apt-hook/json-hook-src/go.mod b/apt-hook/json-hook-src/go.mod
275new file mode 100644465new file mode 100644
index 0000000..66726af
--- /dev/null
+++ b/apt-hook/json-hook-src/go.mod
@@ -0,0 +1,3 @@
1module json-hook
2
3go 1.2
diff --git a/apt-hook/json-hook-src/json-hook.go b/apt-hook/json-hook-src/json-hook.go
0new file mode 1006444new file mode 100644
index 0000000..0f2e3eb
--- /dev/null
+++ b/apt-hook/json-hook-src/json-hook.go
@@ -0,0 +1,253 @@
1package main
2
3import (
4 "bufio"
5 "encoding/json"
6 "fmt"
7 "io"
8 "net"
9 "os"
10 "strconv"
11)
12
13type jsonRPCPackageVersion struct {
14 Id int `json:"id"`
15 Version string `json:"version"`
16 Architecture string `json:"architecture"`
17 Pin int `json:"pin"`
18 Origins []struct {
19 Archive string `json:"archive"`
20 Codename string `json:"codename"`
21 Version string `json:"version"`
22 Origin string `json:"origin"`
23 Label string `json:"label"`
24 Site string `json:"site"`
25 } `json:"origins"`
26}
27type jsonRPC struct {
28 JsonRPC string `json:"jsonrpc"`
29 Method string `json:"method"`
30 Params struct {
31 Command string `json:"command"`
32 UnknownPackages []string `json:"unknown-packages"`
33 Packages []struct {
34 Id int `json:"id"`
35 Name string `json:"name"`
36 Architecture string `json:"architecture"`
37 Mode string `json:"mode"`
38 Automatic bool `json:"automatic"`
39 Versions struct {
40 Candidate jsonRPCPackageVersion `json:"candidate"`
41 Install jsonRPCPackageVersion `json:"install"`
42 Current jsonRPCPackageVersion `json:"current"`
43 } `json:"versions"`
44 } `json:"packages"`
45 } `json:"params"`
46}
47
48func createUpdateMessage(standardSecurityCount int, esmInfraCount int, esmAppsCount int) string {
49 displayStandard := true
50 displayEsmInfra := true
51 displayEsmApps := true
52 if standardSecurityCount == 0 {
53 displayStandard = false
54 }
55 if esmInfraCount == 0 {
56 displayEsmInfra = false
57 }
58 if esmAppsCount == 0 {
59 displayEsmApps = false
60 }
61
62 if !displayStandard && !displayEsmInfra && !displayEsmApps {
63 return ""
64 }
65
66 esmInfraFirst := false
67 esmAppsFirst := false
68 if !displayStandard && displayEsmInfra {
69 esmInfraFirst = true
70 } else if !displayStandard && !displayEsmInfra && displayEsmApps {
71 esmAppsFirst = true
72 }
73
74 standardUpdates := ""
75 esmInfraUpdates := ""
76 esmAppsUpdates := ""
77 if displayStandard {
78 standardUpdates = fmt.Sprintf("%d standard security ", standardSecurityCount)
79 if standardSecurityCount > 1 {
80 standardUpdates += "updates"
81 } else {
82 standardUpdates += "update"
83 }
84 if displayEsmInfra && displayEsmApps {
85 standardUpdates += ","
86 }
87 if displayEsmInfra || displayEsmApps {
88 standardUpdates += " "
89 }
90 if (displayEsmInfra && !displayEsmApps) || (!displayEsmInfra && displayEsmApps) {
91 standardUpdates += "and "
92 }
93 }
94 if displayEsmInfra {
95 esmInfraUpdates = fmt.Sprintf("%d esm-infra ", esmInfraCount)
96 if esmInfraFirst {
97 esmInfraUpdates += "security "
98 }
99 if esmInfraCount > 1 {
100 esmInfraUpdates += "updates"
101 } else {
102 esmInfraUpdates += "update"
103 }
104 if displayEsmApps {
105 esmInfraUpdates += " and "
106 }
107 }
108 if displayEsmApps {
109 esmAppsUpdates = fmt.Sprintf("%d esm-apps ", esmAppsCount)
110 if esmAppsFirst {
111 esmAppsUpdates += "security "
112 }
113 if esmAppsCount > 1 {
114 esmAppsUpdates += "updates"
115 } else {
116 esmAppsUpdates += "update"
117 }
118 }
119
120 return standardUpdates + esmInfraUpdates + esmAppsUpdates
121}
122
123func fromOriginAndArchive(pkgVersion jsonRPCPackageVersion, origin string, archive string) bool {
124 for _, pkgOrigin := range pkgVersion.Origins {
125 if pkgOrigin.Origin == origin && pkgOrigin.Archive == archive {
126 return true
127 }
128 }
129 return false
130}
131
132func distroFromPackageOrigin(rpc *jsonRPC) string {
133 for _, pkg := range rpc.Params.Packages {
134 for _, origin := range pkg.Versions.Candidate.Origins {
135 if origin.Codename != "" {
136 return origin.Codename
137 }
138 }
139 }
140 return ""
141}
142
143func countSecurityUpdates(rpc *jsonRPC) (int, int, int) {
144 esmAppsCount := 0
145 esmInfraCount := 0
146 standardSecurityCount := 0
147 distro := distroFromPackageOrigin(rpc)
148 for _, pkg := range rpc.Params.Packages {
149 if pkg.Mode == "upgrade" {
150 if fromOriginAndArchive(pkg.Versions.Install, "UbuntuESMApps", fmt.Sprintf("%s-apps-security", distro)) {
151 esmAppsCount++
152 } else if fromOriginAndArchive(pkg.Versions.Install, "UbuntuESM", fmt.Sprintf("%s-infra-security", distro)) {
153 esmInfraCount++
154 } else if fromOriginAndArchive(pkg.Versions.Install, "Ubuntu", fmt.Sprintf("%s-security", distro)) {
155 standardSecurityCount++
156 }
157 }
158 }
159 return standardSecurityCount, esmInfraCount, esmAppsCount
160}
161
162// readRpc reads a apt json rpc protocol 0.2 message as described in
163// https://salsa.debian.org/apt-team/apt/blob/main/doc/json-hooks-protocol.md#wire-protocol
164func readRpc(r *bufio.Reader) (*jsonRPC, error) {
165 line, err := r.ReadBytes('\n')
166 if err != nil && err != io.EOF {
167 return nil, fmt.Errorf("cannot read json-rpc: %v", err)
168 }
169
170 var rpc jsonRPC
171 if err := json.Unmarshal(line, &rpc); err != nil {
172 return nil, err
173 }
174 // empty \n
175 emptyNL, _, err := r.ReadLine()
176 if err != nil {
177 return nil, err
178 }
179 if string(emptyNL) != "" {
180 return nil, fmt.Errorf("unexpected line: %q (empty)", emptyNL)
181 }
182
183 return &rpc, nil
184}
185
186func printEsmUpgrades() error {
187 sockFd := os.Getenv("APT_HOOK_SOCKET")
188 if sockFd == "" {
189 return fmt.Errorf("cannot find APT_HOOK_SOCKET env")
190 }
191
192 fd, err := strconv.Atoi(sockFd)
193 if err != nil {
194 return fmt.Errorf("expected APT_HOOK_SOCKET to be a decimal integer, found %q", sockFd)
195 }
196
197 f := os.NewFile(uintptr(fd), "apt-hook-socket")
198 if f == nil {
199 return fmt.Errorf("cannot open file descriptor %v", fd)
200 }
201 defer f.Close()
202
203 conn, err := net.FileConn(f)
204 if err != nil {
205 return fmt.Errorf("cannot connect to %v: %v", fd, err)
206 }
207 defer conn.Close()
208
209 r := bufio.NewReader(conn)
210
211 // handshake
212 rpc, err := readRpc(r)
213 if err != nil {
214 return err
215 }
216 if rpc.Method != "org.debian.apt.hooks.hello" {
217 return fmt.Errorf("expected 'hello' method, got: %v", rpc.Method)
218 }
219 if _, err := conn.Write([]byte(`{"jsonrpc":"2.0","id":0,"result":{"version":"0.2"}}` + "\n\n")); err != nil {
220 return err
221 }
222
223 // payload
224 rpc, err = readRpc(r)
225 if err != nil {
226 return err
227 }
228 if rpc.Method == "org.debian.apt.hooks.install.statistics" {
229 standardSecurityCount, esmInfraCount, esmAppsCount := countSecurityUpdates(rpc)
230 msg := createUpdateMessage(standardSecurityCount, esmInfraCount, esmAppsCount)
231 if msg != "" {
232 fmt.Println(msg)
233 }
234 }
235
236 // bye
237 rpc, err = readRpc(r)
238 if err != nil {
239 return err
240 }
241 if rpc.Method != "org.debian.apt.hooks.bye" {
242 return fmt.Errorf("expected 'bye' method, got: %v", rpc.Method)
243 }
244
245 return nil
246}
247
248func main() {
249 err := printEsmUpgrades()
250 if err != nil {
251 println(err.Error())
252 }
253}
diff --git a/apt-hook/json-hook-src/json-hook_test.go b/apt-hook/json-hook-src/json-hook_test.go
0new file mode 100644254new file mode 100644
index 0000000..cfc1788
--- /dev/null
+++ b/apt-hook/json-hook-src/json-hook_test.go
@@ -0,0 +1,410 @@
1package main
2
3import (
4 "encoding/json"
5 "fmt"
6 "testing"
7)
8
9func TestCreateUpdateMessages(t *testing.T) {
10 type params struct {
11 standardSecurityCount int
12 esmInfraCount int
13 esmAppsCount int
14 expectedMessage string
15 }
16 testParamsList := []params{
17 params{0, 0, 0, ""},
18 params{0, 0, 1, "1 esm-apps security update"},
19 params{0, 0, 2, "2 esm-apps security updates"},
20 params{0, 1, 0, "1 esm-infra security update"},
21 params{0, 1, 1, "1 esm-infra security update and 1 esm-apps update"},
22 params{0, 1, 2, "1 esm-infra security update and 2 esm-apps updates"},
23 params{0, 2, 0, "2 esm-infra security updates"},
24 params{0, 2, 1, "2 esm-infra security updates and 1 esm-apps update"},
25 params{0, 2, 2, "2 esm-infra security updates and 2 esm-apps updates"},
26 params{1, 0, 0, "1 standard security update"},
27 params{1, 0, 1, "1 standard security update and 1 esm-apps update"},
28 params{1, 0, 2, "1 standard security update and 2 esm-apps updates"},
29 params{1, 1, 0, "1 standard security update and 1 esm-infra update"},
30 params{1, 1, 1, "1 standard security update, 1 esm-infra update and 1 esm-apps update"},
31 params{1, 1, 2, "1 standard security update, 1 esm-infra update and 2 esm-apps updates"},
32 params{1, 2, 0, "1 standard security update and 2 esm-infra updates"},
33 params{1, 2, 1, "1 standard security update, 2 esm-infra updates and 1 esm-apps update"},
34 params{1, 2, 2, "1 standard security update, 2 esm-infra updates and 2 esm-apps updates"},
35 params{2, 0, 0, "2 standard security updates"},
36 params{2, 0, 1, "2 standard security updates and 1 esm-apps update"},
37 params{2, 0, 2, "2 standard security updates and 2 esm-apps updates"},
38 params{2, 1, 0, "2 standard security updates and 1 esm-infra update"},
39 params{2, 1, 1, "2 standard security updates, 1 esm-infra update and 1 esm-apps update"},
40 params{2, 1, 2, "2 standard security updates, 1 esm-infra update and 2 esm-apps updates"},
41 params{2, 2, 0, "2 standard security updates and 2 esm-infra updates"},
42 params{2, 2, 1, "2 standard security updates, 2 esm-infra updates and 1 esm-apps update"},
43 params{2, 2, 2, "2 standard security updates, 2 esm-infra updates and 2 esm-apps updates"},
44 }
45
46 for i, testParams := range testParamsList {
47 t.Run(fmt.Sprintf("Case %d", i), func(t *testing.T) {
48 actual := createUpdateMessage(testParams.standardSecurityCount, testParams.esmInfraCount, testParams.esmAppsCount)
49 if actual != testParams.expectedMessage {
50 t.Logf("expected: \"%s\", got: \"%s\"", testParams.expectedMessage, actual)
51 t.Fail()
52 }
53 })
54 }
55}
56
57func TestCountSecurityUpdates(t *testing.T) {
58 type params struct {
59 rpc string
60 expectedStandardSecurityCount int
61 expectedEsmInfraCount int
62 expectedEsmAppsCount int
63 }
64 testParamsList := []params{
65 params{mockJson, 1, 2, 3},
66 }
67
68 for i, testParams := range testParamsList {
69 t.Run(fmt.Sprintf("Case %d", i), func(t *testing.T) {
70 rpc := &jsonRPC{}
71 if err := json.Unmarshal([]byte(mockJson), rpc); err != nil {
72 t.Error(err)
73 }
74
75 actualStandard, actualInfra, actualApps := countSecurityUpdates(rpc)
76 if actualStandard != testParams.expectedStandardSecurityCount {
77 t.Logf("expected: %d, got: %d", testParams.expectedStandardSecurityCount, actualStandard)
78 t.Fail()
79 }
80 if actualInfra != testParams.expectedEsmInfraCount {
81 t.Logf("expected: %d, got: %d", testParams.expectedEsmInfraCount, actualInfra)
82 t.Fail()
83 }
84 if actualApps != testParams.expectedEsmAppsCount {
85 t.Logf("expected: %d, got: %d", testParams.expectedEsmAppsCount, actualApps)
86 t.Fail()
87 }
88 })
89 }
90}
91
92const mockJson = `
93{
94 "jsonrpc": "2.0",
95 "method": "org.debian.apt.hooks.install.statistics",
96 "params": {
97 "command": "install",
98 "search-terms": [
99 "~U"
100 ],
101 "unknown-packages": [],
102 "packages": [
103 {
104 "id": 418,
105 "name": "base-files",
106 "architecture": "amd64",
107 "mode": "upgrade",
108 "automatic": true,
109 "versions": {
110 "candidate": {
111 "id": 86,
112 "version": "11ubuntu19",
113 "architecture": "amd64",
114 "pin": 500,
115 "origins": [
116 {
117 "archive": "hirsute-apps-security",
118 "codename": "hirsute",
119 "version": "21.04",
120 "origin": "UbuntuESMApps",
121 "label": "Ubuntu",
122 "site": ""
123 }
124 ]
125 },
126 "install": {
127 "id": 86,
128 "version": "11ubuntu19",
129 "architecture": "amd64",
130 "pin": 500,
131 "origins": [
132 {
133 "archive": "hirsute-apps-security",
134 "codename": "hirsute",
135 "version": "21.04",
136 "origin": "UbuntuESMApps",
137 "label": "Ubuntu",
138 "site": ""
139 }
140 ]
141 },
142 "current": {
143 "id": 95463,
144 "version": "11ubuntu18",
145 "architecture": "amd64",
146 "pin": 100,
147 "origins": []
148 }
149 }
150 },
151 {
152 "id": 1085,
153 "name": "elfutils",
154 "architecture": "amd64",
155 "mode": "upgrade",
156 "automatic": true,
157 "versions": {
158 "candidate": {
159 "id": 371,
160 "version": "0.183-8",
161 "architecture": "amd64",
162 "pin": 500,
163 "origins": [
164 {
165 "archive": "hirsute-apps-security",
166 "codename": "hirsute",
167 "version": "21.04",
168 "origin": "UbuntuESMApps",
169 "label": "Ubuntu",
170 "site": ""
171 }
172 ]
173 },
174 "install": {
175 "id": 371,
176 "version": "0.183-8",
177 "architecture": "amd64",
178 "pin": 500,
179 "origins": [
180 {
181 "archive": "hirsute-apps-security",
182 "codename": "hirsute",
183 "version": "21.04",
184 "origin": "UbuntuESMApps",
185 "label": "Ubuntu",
186 "site": ""
187 }
188 ]
189 },
190 "current": {
191 "id": 95472,
192 "version": "0.183-6",
193 "architecture": "amd64",
194 "pin": 100,
195 "origins": []
196 }
197 }
198 },
199 {
200 "id": 24709,
201 "name": "fdroidserver",
202 "architecture": "amd64",
203 "mode": "upgrade",
204 "automatic": false,
205 "versions": {
206 "candidate": {
207 "id": 14186,
208 "version": "2.0-1",
209 "architecture": "all",
210 "pin": 500,
211 "origins": [
212 {
213 "archive": "hirsute-infra-security",
214 "codename": "hirsute",
215 "version": "21.04",
216 "origin": "UbuntuESM",
217 "label": "Ubuntu",
218 "site": ""
219 },
220 {
221 "archive": "hirsute",
222 "codename": "hirsute",
223 "version": "21.04",
224 "origin": "Ubuntu",
225 "label": "Ubuntu",
226 "site": ""
227 }
228 ]
229 },
230 "install": {
231 "id": 14186,
232 "version": "2.0-1",
233 "architecture": "all",
234 "pin": 500,
235 "origins": [
236 {
237 "archive": "hirsute-infra-security",
238 "codename": "hirsute",
239 "version": "21.04",
240 "origin": "UbuntuESM",
241 "label": "Ubuntu",
242 "site": ""
243 },
244 {
245 "archive": "hirsute",
246 "codename": "hirsute",
247 "version": "21.04",
248 "origin": "Ubuntu",
249 "label": "Ubuntu",
250 "site": ""
251 }
252 ]
253 },
254 "current": {
255 "id": 95474,
256 "version": "1.1.9-1",
257 "architecture": "all",
258 "pin": 100,
259 "origins": []
260 }
261 }
262 },
263 {
264 "id": 238,
265 "name": "gdb",
266 "architecture": "amd64",
267 "mode": "upgrade",
268 "automatic": true,
269 "versions": {
270 "candidate": {
271 "id": 705,
272 "version": "10.1-2ubuntu2",
273 "architecture": "amd64",
274 "pin": 500,
275 "origins": [
276 {
277 "archive": "hirsute-infra-security",
278 "codename": "hirsute",
279 "version": "21.04",
280 "origin": "UbuntuESM",
281 "label": "Ubuntu",
282 "site": ""
283 }
284 ]
285 },
286 "install": {
287 "id": 705,
288 "version": "10.1-2ubuntu2",
289 "architecture": "amd64",
290 "pin": 500,
291 "origins": [
292 {
293 "archive": "hirsute-infra-security",
294 "codename": "hirsute",
295 "version": "21.04",
296 "origin": "UbuntuESM",
297 "label": "Ubuntu",
298 "site": ""
299 }
300 ]
301 },
302 "current": {
303 "id": 95475,
304 "version": "10.1-2ubuntu1",
305 "architecture": "amd64",
306 "pin": 100,
307 "origins": []
308 }
309 }
310 },
311 {
312 "id": 126271,
313 "name": "google-chrome-stable",
314 "architecture": "amd64",
315 "mode": "upgrade",
316 "automatic": true,
317 "versions": {
318 "candidate": {
319 "id": 95416,
320 "version": "90.0.4430.85-1",
321 "architecture": "amd64",
322 "pin": 500,
323 "origins": [
324 {
325 "archive": "hirsute-apps-security",
326 "codename": "hirsute",
327 "version": "1.0",
328 "origin": "UbuntuESMApps",
329 "label": "Google",
330 "site": "dl.google.com"
331 }
332 ]
333 },
334 "install": {
335 "id": 95416,
336 "version": "90.0.4430.85-1",
337 "architecture": "amd64",
338 "pin": 500,
339 "origins": [
340 {
341 "archive": "hirsute-apps-security",
342 "codename": "hirsute",
343 "version": "1.0",
344 "origin": "UbuntuESMApps",
345 "label": "Google",
346 "site": "dl.google.com"
347 }
348 ]
349 },
350 "current": {
351 "id": 95477,
352 "version": "90.0.4430.72-1",
353 "architecture": "amd64",
354 "pin": 100,
355 "origins": []
356 }
357 }
358 },
359 {
360 "id": 1499,
361 "name": "libasm1",
362 "architecture": "amd64",
363 "mode": "upgrade",
364 "automatic": true,
365 "versions": {
366 "candidate": {
367 "id": 1763,
368 "version": "0.183-8",
369 "architecture": "amd64",
370 "pin": 500,
371 "origins": [
372 {
373 "archive": "hirsute-security",
374 "codename": "hirsute",
375 "version": "21.04",
376 "origin": "Ubuntu",
377 "label": "Ubuntu",
378 "site": ""
379 }
380 ]
381 },
382 "install": {
383 "id": 1763,
384 "version": "0.183-8",
385 "architecture": "amd64",
386 "pin": 500,
387 "origins": [
388 {
389 "archive": "hirsute-security",
390 "codename": "hirsute",
391 "version": "21.04",
392 "origin": "Ubuntu",
393 "label": "Ubuntu",
394 "site": ""
395 }
396 ]
397 },
398 "current": {
399 "id": 95482,
400 "version": "0.183-6",
401 "architecture": "amd64",
402 "pin": 100,
403 "origins": []
404 }
405 }
406 }
407 ]
408 }
409}
410`
diff --git a/debian/changelog b/debian/changelog
index ee90158..3f0715e 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,10 +1,167 @@
1ubuntu-advantage-tools (27.0~21.04.1) hirsute; urgency=medium
2
3 * New upstream release 27.0: (LP: #1926361)
4 - messages: add optional (s) to apt messaging to include
5 singular/plural pkgs
6 - apt-hook: avoid reporting and counting duplicate package
7 names (GH: #1578)
8 - fix: dont say reboot required when unnecessary
9 - test: uncomment additional xenial upgrade tests
10
11 -- Lucas Moura <lucas.moura@canonical.com> Tue, 27 Apr 2021 15:31:06 -0300
12
13ubuntu-advantage-tools (27.0~21.04.1~beta3) hirsute; urgency=medium
14
15 * New upstream beta3 release:
16 - config: avoid tracebacks on invalid features value in uaclient.conf
17 (GH: #1564)
18 - apt-hook: new json hook for security update counts
19 - Remove redundant messaging from uaclient
20
21 -- Chad Smith <chad.smith@canonical.com> Fri, 23 Apr 2021 15:28:44 -0600
22
23ubuntu-advantage-tools (27.0~21.04.1~beta2) hirsute; urgency=medium
24
25 * d/control:
26 - add distro-info dependency
27 - add new debianutils dependency
28 - add optional dh-systemd | debhelper (>= 13.3) to fallback on hirsute
29 and later when dh-systemd is not present
30 * d/rules: enable and start ua-messaging.timer on package install
31 * d/postinst:
32 - configure esm on any LTS release avoid beta services
33 - configure esm-infra when is_active_esm and apps on LTS
34 - xenial enable unauthenticated apt source for apps/infra
35 * New upstream release 27.0~beta:
36 - apt-hook:
37 + adapt hook to process separate message templates
38 + esm-apps and esm-infra pkg counts not mutually-exclusive
39 + print static messages on apt upgrade/dist-upgrade (GH: #1546)
40 - config: create settings_overrides on config (GH: #1507)
41 - docs: add entry for uploading new version to ppa
42 - esm:
43 + add pin never when disabling esm-infra/apps on xenial
44 + enable infra when EOL LTS and apps on all LTS (GH: #1558)
45 - fips: add notice when installing over old fips
46 - fix:
47 + add links to ubuntu.com/gcp/aws in messaging when on non-PRO
48 + add notice to reboot operation on ua fix
49 + do not prompt user for beta services (GH: #1544)
50 + notify users if reboot is required (GH: #1476)
51 + update how the expired token logic works
52 + wrap output greater than 80 chars (GH: #1487)
53 - lib: fix notice handling on reboot script
54 - messages
55 + provide static message files for use in APT and MOTD
56 + update_ua_messages on attach/detach/disable
57 - mypy: add lib/ dir for coverage
58 - status: do not remove notices on non-root call (GH: #1518)
59 - subp: separate % format strings when logging (GH: #1520)
60 - systemd: add ua-messaging.timer to update ua MOTD and APT msgs
61 - update-motd.d: add conditional hooks for motd to source ua messages
62 - util: add is_lts and is_active_esm funtions to support ESM
63 - test
64 + add integration tests asserting esm-apps setup due to postinst
65 + manual test script for xenial upgrade
66 + trusty and xenial infra and apps disabled in pkg install
67 - behave: use unaltered cloud images unsetting UACLIENT_BEHAVE_PPA
68 - jenkins: make lint and style stage run sequentially
69
70 -- Lucas Moura <lucas.moura@canonical.com> Thu, 22 Apr 2021 14:16:26 -0300
71
72ubuntu-advantage-tools (27.0~21.04.1~beta) hirsute; urgency=medium
73
74 * d/*: prefix all the debhelper conf files with the package name
75 * d/control:
76 - add Rules-Requires-Root: no
77 - bump Standards-Version to 4.5.1
78 - make ubuntu-advantage-pro Architecture: all
79 * d/lintian-overrides:
80 - override maintainer-script-calls-service
81 - package-supports-alternative-init-but-no-init.d-script
82 * d/postinst: move the u-a-pro note to a config script
83 * d/ubuntu-advantage-tools.templates: suggest the use of apt
84 * New upstream release 27.0~beta:
85 - apt: add retry for apt-helper command (GH: #1431)
86 - cli: drop subcommand repeated help output, fix enable & refresh
87 (GH: #1440)
88 - config:
89 + allow parsing yaml delivered from env values
90 + environment variable support for feature overrides (GH: #1395)
91 + create config to add extra params to security url
92 - docs:
93 + add ppas and fix typos
94 + use Ubuntu Pro not Ubuntu PRO
95 + add stop "." punctuation to messages (GH: #1320)
96 - fips: fix FIPS message when disable operation fails
97 - fix:
98 + add basic UASecurityClient to which queries CVE and USNs
99 + add security_url to config
100 + check if service is enabled during ua fix (GH: #1462)
101 + closer representation of cve and usn responses
102 + filter usns by cve details (GH: #1470)
103 + fix regex to be more permissive and strict
104 + get_cve_affected_source_packages_status won't list not-affected
105 (GH: #1467)
106 + handle other package status when running ua fix (GH: #1435)
107 + improve error message for ua fix (GH: #1420)
108 + install pkg fixes when they are on standard pocket (GH: #1401)
109 + move timeout and retries to security client only
110 + only prompt for subscription attach for UA-related pkg updates
111 + parse all related USNS to a given CVE when fixing
112 + parse full API responses for related CVEs and USNs
113 + prefer USN.release_packages binary pkg versions to CVE src ver
114 (GH: #1436)
115 + prompt for new ua token when expired one is used (GH: #1475)
116 + prompt to emit pro suggestion on pro_clouds if unattached (GH: #1386)
117 + prompt to enable service during ua fix (GH: #1455)
118 + provide related CVE URLs instead of USNs (GH: #1456)
119 + raise errors when source_link is null or unexpected format
120 + show packages that were not fixed in the output
121 + update output for released packages in ua fix (GH: #1438)
122 + update message for invalid issue in ua fix (GH: #1433)
123 + use pocket values from USNs (GH: #1439)
124 - logs: emit error response on API errors and redact sensitive logs
125 (GH: #1424)
126 - serviceclient: add 10 second timeout and two retries to API calls
127 (GH: #1374)
128 - util:
129 + add error prompts on invalid selection
130 + add timeout to readurl
131 - tests:
132 + Add disable_auto_attach config to all test PRO vms
133 + add merge_usn_released_binary_package_versions tests
134 + add unittest coverage for override_usn_release_package_status
135 + drop traceback checks on fips integration tests
136 + refactor integration tests for ua fix cmd
137 + run status wait before detach in PRO tests
138 + use ssh to run commands on lxd containers
139 - jenkins: archiveArtifacts can only reference paths within workspace
140
141 -- Lucas Moura <lucas.moura@canonical.com> Tue, 30 Mar 2021 14:16:03 -0300
142
143ubuntu-advantage-tools (26.3~21.04.1) hirsute; urgency=medium
144
145 * d/control: add new debianutils dependency
146 * New upstream release 26.3
147 - util: improve is_container check for chroot
148 - cli: pass assume_yes param to services on detach (GH: #1530)
149
150 -- Grant Orndorff <grant.orndorff@canonical.com> Tue, 06 Apr 2021 14:26:20 -0300
151
1ubuntu-advantage-tools (26.2) hirsute; urgency=medium152ubuntu-advantage-tools (26.2) hirsute; urgency=medium
2153
3 * Drop dh-systemd build dependency.154 * Drop dh-systemd build dependency.
4155
5 -- Matthias Klose <doko@ubuntu.com> Wed, 10 Mar 2021 16:54:12 +0100156 -- Matthias Klose <doko@ubuntu.com> Wed, 10 Mar 2021 16:54:12 +0100
6157
7ubuntu-advantage-tools (26.1) hirsute; urgency=medium158ubuntu-advantage-tools (26.2~21.04.1) hirsute; urgency=medium
159
160 * status: show beta services in status if enabled (GH: #1410)
161
162 -- Lucas Moura <lucas.moura@canonical.com> Tue, 02 Mar 2021 10:11:53 -0300
163
164ubuntu-advantage-tools (26.1~21.04.1) hirsute; urgency=medium
8165
9 * New upstream release 26.1166 * New upstream release 26.1
10 - contract: block detach call to contract if machine-id change167 - contract: block detach call to contract if machine-id change
diff --git a/debian/control b/debian/control
index 87595b8..cd28a1f 100644
--- a/debian/control
+++ b/debian/control
@@ -4,27 +4,33 @@ Priority: important
4Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>4Maintainer: Ubuntu Developers <ubuntu-devel-discuss@lists.ubuntu.com>
5Build-Depends: bash-completion,5Build-Depends: bash-completion,
6 debhelper (>=9),6 debhelper (>=9),
7 debianutils,
7 dh-python,8 dh-python,
9 dh-systemd | debhelper (>= 13.3),
8 gettext,10 gettext,
9 git,11 git,
12 golang,
10 libapt-pkg-dev,13 libapt-pkg-dev,
11 po-debconf,14 po-debconf,
12 python3 (>= 3.4),15 python3 (>= 3.4),
16 distro-info,
13 python3-flake8,17 python3-flake8,
14 python3-mock,18 python3-mock,
15 python3-pytest,19 python3-pytest,
16 python3-setuptools,20 python3-setuptools,
17 python3-yaml21 python3-yaml
18Standards-Version: 4.3.022Standards-Version: 4.5.1
19Homepage: https://buy.ubuntu.com23Homepage: https://buy.ubuntu.com
20Vcs-Git: https://github.com/CanonicalLtd/ubuntu-advantage-script.git24Vcs-Git: https://github.com/CanonicalLtd/ubuntu-advantage-script.git
21Vcs-Browser: https://github.com/CanonicalLtd/ubuntu-advantage-script25Vcs-Browser: https://github.com/CanonicalLtd/ubuntu-advantage-script
26Rules-Requires-Root: no
2227
23Package: ubuntu-advantage-tools28Package: ubuntu-advantage-tools
24Architecture: any29Architecture: any
25Depends: ${misc:Depends},30Depends: ${misc:Depends},
26 ${python3:Depends},31 ${python3:Depends},
27 ${shlibs:Depends},32 ${shlibs:Depends},
33 distro-info,
28 python3-pkg-resources,34 python3-pkg-resources,
29 ${extra:Depends},35 ${extra:Depends},
30Description: management tools for Ubuntu Advantage36Description: management tools for Ubuntu Advantage
@@ -36,7 +42,7 @@ Description: management tools for Ubuntu Advantage
36 services in this package.42 services in this package.
3743
38Package: ubuntu-advantage-pro44Package: ubuntu-advantage-pro
39Architecture: any45Architecture: all
40Depends: ${misc:Depends}, ubuntu-advantage-tools (>=20.2)46Depends: ${misc:Depends}, ubuntu-advantage-tools (>=20.2)
41Replaces: ubuntu-advantage-tools (<<20.2)47Replaces: ubuntu-advantage-tools (<<20.2)
42Breaks: ubuntu-advantage-tools (<<20.2)48Breaks: ubuntu-advantage-tools (<<20.2)
diff --git a/debian/lintian-overrides b/debian/lintian-overrides
43new file mode 10064449new file mode 100644
index 0000000..0d8ba20
--- /dev/null
+++ b/debian/lintian-overrides
@@ -0,0 +1,5 @@
1# False positive
2ubuntu-advantage-tools: maintainer-script-calls-service postinst:*
3
4# Ubuntu doesn't require init.d scripts
5ubuntu-advantage-tools: package-supports-alternative-init-but-no-init.d-script
diff --git a/debian/po/templates.pot b/debian/po/templates.pot
index 9dd5145..ffd2763 100644
--- a/debian/po/templates.pot
+++ b/debian/po/templates.pot
@@ -8,7 +8,7 @@ msgid ""
8msgstr ""8msgstr ""
9"Project-Id-Version: ubuntu-advantage-tools\n"9"Project-Id-Version: ubuntu-advantage-tools\n"
10"Report-Msgid-Bugs-To: ubuntu-advantage-tools@packages.debian.org\n"10"Report-Msgid-Bugs-To: ubuntu-advantage-tools@packages.debian.org\n"
11"POT-Creation-Date: 2020-10-02 15:07-0600\n"11"POT-Creation-Date: 2021-02-26 16:29+0100\n"
12"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"12"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"13"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14"Language-Team: LANGUAGE <LL@li.org>\n"14"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -32,5 +32,5 @@ msgstr ""
32#. Type: note32#. Type: note
33#. Description33#. Description
34#: ../ubuntu-advantage-tools.templates:100134#: ../ubuntu-advantage-tools.templates:1001
35msgid "sudo apt-get install ubuntu-advantage-pro"35msgid "sudo apt install ubuntu-advantage-pro"
36msgstr ""36msgstr ""
diff --git a/debian/rules b/debian/rules
index 888dcde..9bea522 100755
--- a/debian/rules
+++ b/debian/rules
@@ -19,8 +19,7 @@ APT_PKG_DEPS="apt (>= 1.8.1), apt-utils (>= 1.8.1), libapt-pkg5.90 (>= 1.8.1)"
19endif19endif
2020
21%:21%:
22 dh $@ --with python3,bash-completion,systemd --buildsystem=pybuild \22 dh $@ --with python3,bash-completion,systemd --buildsystem=pybuild
23 --no-start
2423
25override_dh_auto_build:24override_dh_auto_build:
26 dh_auto_build25 dh_auto_build
@@ -42,6 +41,15 @@ override_dh_gencontrol:
42 echo extra:Depends=$(APT_PKG_DEPS) >> debian/ubuntu-advantage-tools.substvars41 echo extra:Depends=$(APT_PKG_DEPS) >> debian/ubuntu-advantage-tools.substvars
43 dh_gencontrol42 dh_gencontrol
4443
44override_dh_systemd_enable:
45 dh_systemd_enable -pubuntu-advantage-pro ua-auto-attach.service
46 dh_systemd_enable -pubuntu-advantage-tools ua-reboot-cmds.service
47 dh_systemd_enable -pubuntu-advantage-tools ua-messaging.timer
48 dh_systemd_enable -pubuntu-advantage-tools ua-messaging.service
49
50override_dh_systemd_start:
51 dh_systemd_start -pubuntu-advantage-tools ua-messaging.timer
52
45override_dh_auto_install:53override_dh_auto_install:
46 dh_auto_install --destdir=debian/ubuntu-advantage-tools54 dh_auto_install --destdir=debian/ubuntu-advantage-tools
47 flist=$$(find $(CURDIR)/debian/ -type f -name version.py) && sed -i 's,@@PACKAGED_VERSION@@,$(DEB_VERSION),' $${flist:-did-not-find-version-py-for-replacement}55 flist=$$(find $(CURDIR)/debian/ -type f -name version.py) && sed -i 's,@@PACKAGED_VERSION@@,$(DEB_VERSION),' $${flist:-did-not-find-version-py-for-replacement}
diff --git a/debian/ubuntu-advantage-tools.config b/debian/ubuntu-advantage-tools.config
48new file mode 10064456new file mode 100644
index 0000000..92f4ee2
--- /dev/null
+++ b/debian/ubuntu-advantage-tools.config
@@ -0,0 +1,14 @@
1#!/bin/sh
2
3set -e
4
5. /usr/share/debconf/confmodule
6
7if dpkg --compare-versions "$PREVIOUS_PKG_VER" lt-nl "20.2~"; then
8 if dpkg --compare-versions "$PREVIOUS_PKG_VER" ge-nl "19.7~"; then
9 # Use debconf to alert the user to the additional
10 # ubuntu-advantage-pro package that should be installed
11 db_input high ubuntu-advantage-tools/suggest_pro_pkg || true
12 db_go || true
13 fi
14fi
diff --git a/debian/links b/debian/ubuntu-advantage-tools.links
0similarity index 100%15similarity index 100%
1rename from debian/links16rename from debian/links
2rename to debian/ubuntu-advantage-tools.links17rename to debian/ubuntu-advantage-tools.links
diff --git a/debian/manpages b/debian/ubuntu-advantage-tools.manpages
3similarity index 100%18similarity index 100%
4rename from debian/manpages19rename from debian/manpages
5rename to debian/ubuntu-advantage-tools.manpages20rename to debian/ubuntu-advantage-tools.manpages
diff --git a/debian/postinst b/debian/ubuntu-advantage-tools.postinst
6similarity index 50%21similarity index 50%
7rename from debian/postinst22rename from debian/postinst
8rename to debian/ubuntu-advantage-tools.postinst23rename to debian/ubuntu-advantage-tools.postinst
index 5d942f9..75581e3 100644
--- a/debian/postinst
+++ b/debian/ubuntu-advantage-tools.postinst
@@ -4,22 +4,39 @@ set -e
44
5. /etc/os-release # For VERSION_ID5. /etc/os-release # For VERSION_ID
66
7# Since UBUNTU_CODENAME isn't on trusty set it set a default if unknown
8if [ "" = "${UBUNTU_CODENAME}" ]; then
9 case "$VERSION_ID" in
10 14.04) UBUNTU_CODENAME="trusty";;
11 *) UBUNTU_CODENAME="NO-UBUNTU_CODENAME-$VERSION_ID";;
12 esac
13fi
14
15# Needed even if this script doesn't call debconf, see:
16# https://lintian.debian.org/tags/postinst-does-not-load-confmodule.html
17. /usr/share/debconf/confmodule
718
8APT_TRUSTED_KEY_DIR="/etc/apt/trusted.gpg.d"19APT_TRUSTED_KEY_DIR="/etc/apt/trusted.gpg.d"
9UA_KEYRING_DIR="/usr/share/keyrings/"20UA_KEYRING_DIR="/usr/share/keyrings/"
1021
11ESM_INFRA_KEY_TRUSTY="ubuntu-advantage-esm-infra-trusty.gpg"22ESM_INFRA_KEY_TRUSTY="ubuntu-advantage-esm-infra-trusty.gpg"
23ESM_APPS_KEY="ubuntu-advantage-esm-apps.gpg"
1224
13APT_SRC_DIR="/etc/apt/sources.list.d"25APT_SRC_DIR="/etc/apt/sources.list.d"
14APT_PREFERENCES_DIR="/etc/apt/preferences.d"26APT_PREFERENCES_DIR="/etc/apt/preferences.d"
15ESM_APT_SOURCE_FILE_PRECISE="$APT_SRC_DIR/ubuntu-esm-precise.list"27ESM_APT_SOURCE_FILE_PRECISE="$APT_SRC_DIR/ubuntu-esm-precise.list"
16ESM_APT_SOURCE_FILE_TRUSTY="$APT_SRC_DIR/ubuntu-esm-trusty.list"28ESM_APT_SOURCE_FILE_TRUSTY="$APT_SRC_DIR/ubuntu-esm-trusty.list"
17ESM_INFRA_OLD_APT_SOURCE_FILE_TRUSTY="$APT_SRC_DIR/ubuntu-esm-infra-trusty.list"29ESM_INFRA_OLD_APT_SOURCE_FILE_TRUSTY="$APT_SRC_DIR/ubuntu-esm-infra-trusty.list"
18ESM_INFRA_APT_SOURCE_FILE_TRUSTY="$APT_SRC_DIR/ubuntu-esm-infra.list"30ESM_INFRA_APT_SOURCE_FILE="$APT_SRC_DIR/ubuntu-esm-infra.list"
31ESM_APPS_APT_SOURCE_FILE="$APT_SRC_DIR/ubuntu-esm-apps.list"
32FIPS_APT_SOURCE_FILE="$APT_SRC_DIR/ubuntu-fips.list"
1933
20ESM_APT_PREF_FILE_TRUSTY="/etc/apt/preferences.d/ubuntu-esm-trusty"34OLD_CLIENT_FIPS_PPA="private-ppa.launchpad.net/ubuntu-advantage/fips/ubuntu"
21ESM_INFRA_OLD_APT_PREF_FILE_TRUSTY="/etc/apt/preferences.d/ubuntu-esm-infra-trusty"35
22ESM_INFRA_APT_PREF_FILE_TRUSTY="/etc/apt/preferences.d/ubuntu-esm-infra"36ESM_APT_PREF_FILE_TRUSTY="$APT_PREFERENCES_DIR/ubuntu-esm-trusty"
37ESM_INFRA_OLD_APT_PREF_FILE_TRUSTY="$APT_PREFERENCES_DIR/ubuntu-esm-infra-trusty"
38ESM_INFRA_APT_PREF_FILE="$APT_PREFERENCES_DIR/ubuntu-esm-infra"
39ESM_APPS_APT_PREF_FILE="$APT_PREFERENCES_DIR/ubuntu-esm-apps"
2340
24MYARCH="$(dpkg --print-architecture)"41MYARCH="$(dpkg --print-architecture)"
25ESM_SUPPORTED_ARCHS="i386 amd64"42ESM_SUPPORTED_ARCHS="i386 amd64"
@@ -68,6 +85,50 @@ print(*ENTITLEMENT_CLASS_BY_NAME.keys(), sep=' ')
68}85}
6986
7087
88# Ubuntu LTS release all support-esm
89check_is_lts() {
90 release_name=$1
91 ubuntu-distro-info --supported-esm | grep -q "${release_name}"
92}
93
94
95# Check whether this series is under active ESM
96check_is_active_esm() {
97 release_name=$1
98 # Trusty doesn't support --series param
99 if [ "${release_name}" = "trusty" ]; then
100 return 0
101 else
102 _DAYS_UNTIL_ESM=$(ubuntu-distro-info --series "${release_name}" -yeol)
103 if [ "${_DAYS_UNTIL_ESM}" -lt "1" ]; then
104 return 0
105 fi
106 fi
107 return 1
108}
109
110# Check whether a given service is beta
111check_service_is_beta() {
112 service_name=$1
113 _IS_BETA_SVC=$(python3 -c "
114from uaclient.config import UAConfig
115from uaclient.entitlements import ENTITLEMENT_CLASS_BY_NAME
116ent_cls = ENTITLEMENT_CLASS_BY_NAME.get('${service_name}')
117if ent_cls:
118 cfg = UAConfig()
119 allow_beta = cfg.features.get('allow_beta', False)
120 print(all([ent_cls.is_beta, not allow_beta]))
121else:
122 print(True)
123")
124if [ "${_IS_BETA_SVC}" = "True" ]; then
125 return 0
126else
127 return 1
128fi
129}
130
131
71# Check cached service status from status.json and return 0 if enabled else 1132# Check cached service status from status.json and return 0 if enabled else 1
72check_service_is_enabled() {133check_service_is_enabled() {
73 service_name=$1134 service_name=$1
@@ -92,47 +153,90 @@ if status:
92153
93unconfigure_esm() {154unconfigure_esm() {
94 if ! check_service_is_enabled esm-infra; then155 if ! check_service_is_enabled esm-infra; then
95 rm -f $APT_TRUSTED_KEY_DIR/ubuntu-esm*gpg # Remove previous esm keys156 rm -f "$APT_TRUSTED_KEY_DIR/ubuntu-esm*gpg" # Remove previous esm keys
96 rm -f $APT_TRUSTED_KEY_DIR/$ESM_INFRA_KEY_TRUSTY157 rm -f "$APT_TRUSTED_KEY_DIR/$ESM_INFRA_KEY_TRUSTY"
97 rm -f $ESM_INFRA_APT_SOURCE_FILE_TRUSTY158 rm -f "$ESM_INFRA_APT_SOURCE_FILE"
98 rm -f $ESM_INFRA_OLD_APT_SOURCE_FILE_TRUSTY159 rm -f "$ESM_INFRA_OLD_APT_SOURCE_FILE_TRUSTY"
99 rm -f $ESM_APT_PREF_FILE_TRUSTY $ESM_INFRA_OLD_APT_PREF_FILE_TRUSTY160 rm -f "$ESM_APT_PREF_FILE_TRUSTY" "$ESM_INFRA_OLD_APT_PREF_FILE_TRUSTY"
100 rm -f $ESM_INFRA_APT_PREF_FILE_TRUSTY161 rm -f "$ESM_INFRA_APT_PREF_FILE"
162 fi
163 if ! check_service_is_enabled esm-apps; then
164 rm -f "$APT_TRUSTED_KEY_DIR/$ESM_APPS_KEY"
165 rm -f "$ESM_APPS_APT_SOURCE_FILE"
166 rm -f "$ESM_APPS_APT_PREF_FILE"
101 fi167 fi
102}168}
103169
104170
105configure_esm() {171# Add visibility to a disabled ESM APT source by installing a GPG key and
106 rm -f $APT_TRUSTED_KEY_DIR/ubuntu-esm*gpg # Remove previous esm keys172# preferences file to Pin never so packages won't get installed by apt update.
107 if [ ! -f "$APT_TRUSTED_KEY_DIR/$ESM_INFRA_KEY_TRUSTY" ]; then173install_esm_apt_key_and_source() {
108 cp $UA_KEYRING_DIR/$ESM_INFRA_KEY_TRUSTY $APT_TRUSTED_KEY_DIR174 service=$1 release=$2
175 apt_suite="${release}-${service}";
176 case "${service}" in
177 apps)
178 apt_origin="UbuntuESMApps"
179 apt_pref_file=${ESM_APPS_APT_PREF_FILE};
180 apt_source_file=${ESM_APPS_APT_SOURCE_FILE};
181 apt_key=${ESM_APPS_KEY};
182 ;;
183 infra)
184 apt_origin="UbuntuESM"
185 apt_pref_file=${ESM_INFRA_APT_PREF_FILE};
186 apt_source_file=${ESM_INFRA_APT_SOURCE_FILE};
187 apt_key=${ESM_INFRA_KEY_TRUSTY};
188 ;;
189 esac
190
191 # GPG key setup to avoid apt gpg key warnings
192 if [ ! -f "$APT_TRUSTED_KEY_DIR/$apt_key" ]; then
193 cp $UA_KEYRING_DIR/$apt_key $APT_TRUSTED_KEY_DIR
109 fi194 fi
110195
111 if [ -e "$ESM_APT_SOURCE_FILE_TRUSTY" ]; then196 # Migrate trusty legacy source list and preference file names
112 mv $ESM_APT_SOURCE_FILE_TRUSTY $ESM_INFRA_APT_SOURCE_FILE_TRUSTY197 if [ "14.04" = "$VERSION_ID" ]; then
113 fi198 if [ -e "$ESM_APT_SOURCE_FILE_TRUSTY" ]; then
114 if [ -e "$ESM_APT_PREF_FILE_TRUSTY" ]; then199 mv $ESM_APT_SOURCE_FILE_TRUSTY $ESM_INFRA_APT_SOURCE_FILE
115 mv $ESM_APT_PREF_FILE_TRUSTY $ESM_INFRA_APT_PREF_FILE_TRUSTY200 fi
201 if [ -e "$ESM_APT_PREF_FILE_TRUSTY" ]; then
202 mv "$ESM_APT_PREF_FILE_TRUSTY" "$ESM_INFRA_APT_PREF_FILE"
203 fi
116 fi204 fi
117 if [ ! -e "$ESM_INFRA_APT_SOURCE_FILE_TRUSTY" ]; then205 # If preference file doesn't already exist, we aren't attached.
118 cat > $ESM_INFRA_APT_SOURCE_FILE_TRUSTY <<EOF206 # Setup unauthenticated apt source list file and never-pin preference
207 if [ ! -e "${apt_source_file}" ]; then
208 # Unconfigured repo, so set it up as never-pinned
209 cat > ${apt_source_file} <<EOF
119# Written by ubuntu-advantage-tools210# Written by ubuntu-advantage-tools
120deb https://esm.ubuntu.com/ubuntu trusty-infra-security main211deb https://esm.ubuntu.com/${service}/ubuntu ${apt_suite}-security main
121# deb-src https://esm.ubuntu.com/ubuntu trusty-infra-security main212# deb-src https://esm.ubuntu.com/${service}/ubuntu ${apt_suite}-security main
122213
123deb https://esm.ubuntu.com/ubuntu trusty-infra-updates main214deb https://esm.ubuntu.com/${service}/ubuntu ${apt_suite}-updates main
124# deb-src https://esm.ubuntu.com/ubuntu trusty-infra-updates main215# deb-src https://esm.ubuntu.com/${service}/ubuntu ${apt_suite}-updates main
125EOF216EOF
217
126 # Automatically disable esm sources via apt preferences until enabled218 # Automatically disable esm sources via apt preferences until enabled
127 cat > $ESM_INFRA_APT_PREF_FILE_TRUSTY <<EOF219 cat > "${apt_pref_file}" <<EOF
128# Written by ubuntu-advantage-tools220# Written by ubuntu-advantage-tools
129Package: *221Package: *
130Pin: release o=UbuntuESM, n=trusty222Pin: release o=${apt_origin}, n=${release}
131Pin-Priority: never223Pin-Priority: never
132EOF224EOF
133 fi225 fi
134}226}
135227
228configure_esm() {
229 rm -f $APT_TRUSTED_KEY_DIR/ubuntu-esm*gpg # Remove legacy esm keys
230 if check_is_active_esm "${UBUNTU_CODENAME}"; then
231 install_esm_apt_key_and_source "infra" "$UBUNTU_CODENAME"
232 fi
233 if ! check_service_is_beta esm-apps; then
234 if [ "${UBUNTU_CODENAME}" != "trusty" ]; then
235 install_esm_apt_key_and_source "apps" "$UBUNTU_CODENAME"
236 fi
237 fi
238}
239
136240
137# If held fips packages exist, we are on a FIPS PRO machine with FIPS enabled241# If held fips packages exist, we are on a FIPS PRO machine with FIPS enabled
138mark_reboot_for_fips_pro() {242mark_reboot_for_fips_pro() {
@@ -143,11 +247,8 @@ mark_reboot_for_fips_pro() {
143}247}
144248
145249
146mark_reboot_cmds_as_needed() {250add_notice() {
147 msg_name=$1251 msg_name=$1
148 if [ ! -f "$REBOOT_CMD_MARKER_FILE" ]; then
149 touch $REBOOT_CMD_MARKER_FILE
150 fi
151 python3 -c "252 python3 -c "
152from uaclient.config import UAConfig253from uaclient.config import UAConfig
153from uaclient.status import ${msg_name}254from uaclient.status import ${msg_name}
@@ -156,6 +257,14 @@ cfg.add_notice(label='', description=${msg_name})
156"257"
157}258}
158259
260mark_reboot_cmds_as_needed() {
261 msg_name=$1
262 if [ ! -f "$REBOOT_CMD_MARKER_FILE" ]; then
263 touch $REBOOT_CMD_MARKER_FILE
264 fi
265 add_notice "$msg_name"
266}
267
159case "$1" in268case "$1" in
160 configure)269 configure)
161 PREVIOUS_PKG_VER=$2270 PREVIOUS_PKG_VER=$2
@@ -165,7 +274,7 @@ case "$1" in
165 # https://github.com/CanonicalLtd/ubuntu-advantage-client/issues/693274 # https://github.com/CanonicalLtd/ubuntu-advantage-client/issues/693
166 if [ -e "$ESM_APT_SOURCE_FILE_PRECISE" ]; then275 if [ -e "$ESM_APT_SOURCE_FILE_PRECISE" ]; then
167 mv $ESM_APT_SOURCE_FILE_PRECISE \276 mv $ESM_APT_SOURCE_FILE_PRECISE \
168 $ESM_INFRA_APT_SOURCE_FILE_TRUSTY277 $ESM_INFRA_APT_SOURCE_FILE
169 fi278 fi
170279
171 # We changed the way we store public files in 19.5280 # We changed the way we store public files in 19.5
@@ -182,11 +291,6 @@ case "$1" in
182 rm -f $SYSTEMD_WANTS_AUTO_ATTACH_LINK291 rm -f $SYSTEMD_WANTS_AUTO_ATTACH_LINK
183 rm -f $SYSTEMD_HELPER_ENABLED_AUTO_ATTACH_DSH292 rm -f $SYSTEMD_HELPER_ENABLED_AUTO_ATTACH_DSH
184 rm -f $SYSTEMD_HELPER_ENABLED_WANTS_LINK293 rm -f $SYSTEMD_HELPER_ENABLED_WANTS_LINK
185 # Use debconf to alert the user to the additional
186 # ubuntu-advantage-pro package that should be installed
187 . /usr/share/debconf/confmodule
188 db_input high ubuntu-advantage-tools/suggest_pro_pkg || true
189 db_go || true
190 fi294 fi
191 fi295 fi
192296
@@ -195,17 +299,23 @@ case "$1" in
195 redact_ubuntu_release_from_ua_apt_filenames $APT_SRC_DIR299 redact_ubuntu_release_from_ua_apt_filenames $APT_SRC_DIR
196 redact_ubuntu_release_from_ua_apt_filenames $APT_PREFERENCES_DIR300 redact_ubuntu_release_from_ua_apt_filenames $APT_PREFERENCES_DIR
197301
302 # Repo for FIPS packages changed from old client
303 if [ -f $FIPS_APT_SOURCE_FILE ]; then
304 if grep -q $OLD_CLIENT_FIPS_PPA $FIPS_APT_SOURCE_FILE; then
305 add_notice MESSAGE_FIPS_INSTALL_OUT_OF_DATE
306 fi
307 fi
308
198 # CACHE_DIR is no longer present or used since 19.1309 # CACHE_DIR is no longer present or used since 19.1
199 rm -rf /var/cache/ubuntu-advantage-tools310 rm -rf /var/cache/ubuntu-advantage-tools
200 # machine-access cache files no longer present or used since 20.1311 # machine-access cache files no longer present or used since 20.1
201 rm -f /var/lib/ubuntu-advantage/private/machine-access-*.json312 rm -f /var/lib/ubuntu-advantage/private/machine-access-*.json
202313
203 if [ "14.04" = "$VERSION_ID" ]; then314 if check_is_lts "${UBUNTU_CODENAME}"; then
204 if echo "$ESM_SUPPORTED_ARCHS" | grep -qw "$MYARCH"; then315 if echo "$ESM_SUPPORTED_ARCHS" | grep -qw "$MYARCH"; then
205 # 14.04 and supported arch
206 configure_esm316 configure_esm
207 else317 else
208 # 14.04 and unsupported arch318 # ESM supported release but unsupported arch
209 unconfigure_esm319 unconfigure_esm
210 fi320 fi
211 fi321 fi
diff --git a/debian/postrm b/debian/ubuntu-advantage-tools.postrm
212similarity index 100%322similarity index 100%
213rename from debian/postrm323rename from debian/postrm
214rename to debian/ubuntu-advantage-tools.postrm324rename to debian/ubuntu-advantage-tools.postrm
diff --git a/debian/prerm b/debian/ubuntu-advantage-tools.prerm
215similarity index 100%325similarity index 100%
216rename from debian/prerm326rename from debian/prerm
217rename to debian/ubuntu-advantage-tools.prerm327rename to debian/ubuntu-advantage-tools.prerm
diff --git a/debian/ubuntu-advantage-tools.templates b/debian/ubuntu-advantage-tools.templates
index 1b5c6f8..ce1a66a 100644
--- a/debian/ubuntu-advantage-tools.templates
+++ b/debian/ubuntu-advantage-tools.templates
@@ -3,4 +3,4 @@ Type: note
3_Description: Ubuntu Pro support now requires ubuntu-advantage-pro3_Description: Ubuntu Pro support now requires ubuntu-advantage-pro
4 To install run the following:4 To install run the following:
5 .5 .
6 sudo apt-get install ubuntu-advantage-pro6 sudo apt install ubuntu-advantage-pro
diff --git a/features/attach_invalidtoken.feature b/features/attach_invalidtoken.feature
index 4f3fc9a..6b8db52 100644
--- a/features/attach_invalidtoken.feature
+++ b/features/attach_invalidtoken.feature
@@ -12,7 +12,7 @@ Feature: Command behaviour when trying to attach a machine to an Ubuntu
12 When I verify that running `ua attach INVALID_TOKEN` `as non-root` exits `1`12 When I verify that running `ua attach INVALID_TOKEN` `as non-root` exits `1`
13 Then I will see the following on stderr:13 Then I will see the following on stderr:
14 """14 """
15 This command must be run as root (try using sudo)15 This command must be run as root (try using sudo).
16 """16 """
17 Examples: ubuntu release17 Examples: ubuntu release
18 | release |18 | release |
diff --git a/features/attach_validtoken.feature b/features/attach_validtoken.feature
index a25234f..d0e9612 100644
--- a/features/attach_validtoken.feature
+++ b/features/attach_validtoken.feature
@@ -2,26 +2,15 @@
2Feature: Command behaviour when attaching a machine to an Ubuntu Advantage2Feature: Command behaviour when attaching a machine to an Ubuntu Advantage
3 subscription using a valid token3 subscription using a valid token
44
5 @series.all5 @series.xenial
6 @series.bionic
7 @series.focal
6 @uses.config.machine_type.lxd.container8 @uses.config.machine_type.lxd.container
7 Scenario Outline: Attach command in a ubuntu lxd container9 Scenario Outline: Attach command in a ubuntu lxd container
8 Given a `<release>` machine with ubuntu-advantage-tools installed10 Given a `<release>` machine with ubuntu-advantage-tools installed
9 When I run `apt-get update` with sudo, retrying exit [100]11 When I run `apt-get update` with sudo, retrying exit [100]
10 And I run `apt-get install -y <downrev_pkg>` with sudo, retrying exit [100]12 And I run `apt-get install -y <downrev_pkg>` with sudo, retrying exit [100]
11 When I verify that running ` --assume-yes --beta` `with sudo` exits `1`13 And I run `run-parts /etc/update-motd.d/` with sudo
12 And I run `/usr/lib/update-notifier/apt-check --human-readable` as non-root
13 Then if `<release>` in `trusty` and stdout matches regexp:
14 """
15 UA Infrastructure Extended Security Maintenance \(ESM\) is not enabled.
16
17 \d+ update(s)? can be installed immediately.
18 \d+ of these updates (is a|are) security update(s)?.
19 """
20 Then if `<release>` in `trusty` and stdout matches regexp:
21 """
22 Enable UA Infrastructure ESM to receive \d+ additional security update(s)?.
23 See https://ubuntu.com/advantage or run: sudo ua status
24 """
25 Then if `<release>` in `xenial or bionic` and stdout matches regexp:14 Then if `<release>` in `xenial or bionic` and stdout matches regexp:
26 """15 """
27 \d+ package(s)? can be updated.16 \d+ package(s)? can be updated.
@@ -35,7 +24,7 @@ Feature: Command behaviour when attaching a machine to an Ubuntu Advantage
35 When I attach `contract_token` with sudo24 When I attach `contract_token` with sudo
36 Then stdout matches regexp:25 Then stdout matches regexp:
37 """26 """
38 ESM Infra enabled27 UA Infra: ESM enabled
39 """28 """
40 And stdout matches regexp:29 And stdout matches regexp:
41 """30 """
@@ -53,28 +42,74 @@ Feature: Command behaviour when attaching a machine to an Ubuntu Advantage
53 """42 """
54 Enabling default service esm-infra43 Enabling default service esm-infra
55 """44 """
56 When I run `/usr/lib/update-notifier/apt-check --human-readable` as non-root45 When I append the following on uaclient config:
57 Then if `<release>` in `trusty` and stdout matches regexp:46 """
47 features:
48 allow_beta: true
49 """
50 And I run `apt update` with sudo
51 And I run `python3 /usr/lib/ubuntu-advantage/ua_update_messaging.py` with sudo
52 And I run `apt install update-motd` with sudo, retrying exit [100]
53 And I run `update-motd` with sudo
54 Then if `<release>` in `focal` and stdout matches regexp:
58 """55 """
56 \* Introducing Extended Security Maintenance for Applications.
57 +Receive updates to over 30,000 software packages with your
58 +Ubuntu Advantage subscription. Free for personal use.
59
60 +https:\/\/ubuntu.com\/esm
61
59 UA (Infra:|Infrastructure) Extended Security Maintenance \(ESM\) is enabled.62 UA (Infra:|Infrastructure) Extended Security Maintenance \(ESM\) is enabled.
6063
61 \d+ update(s)? can be installed immediately.64 \d+ update(s)? can be installed immediately.
62 \d+ of these updates (is|are) (fixed|provided) through UA (Infra:|Infrastructure) ESM.65 \d+ of these updates (is|are) (fixed|provided) through UA (Infra:|Infrastructure) ESM.
63 \d+ of these updates (is a|are) security update(s)?.66 \d+ of these updates (is a|are) security update(s)?.
67 To see these additional updates run: apt list --upgradable
64 """68 """
65 Then if `<release>` in `focal` and stdout matches regexp:69 Then if `<release>` in `xenial or bionic` and stdout matches regexp:
66 """70 """
67 UA (Infra:|Infrastructure) Extended Security Maintenance \(ESM\) is enabled.71 \* Introducing Extended Security Maintenance for Applications.
72 +Receive updates to over 30,000 software packages with your
73 +Ubuntu Advantage subscription. Free for personal use.
6874
69 \d+ update(s)? can be installed immediately.75 +https:\/\/ubuntu.com\/esm
70 \d+ of these updates (is|are) (fixed|provided) through UA (Infra:|Infrastructure) ESM.76
77 UA Infra: Extended Security Maintenance \(ESM\) is enabled.
78
79 \d+ package(s)? can be updated.
71 \d+ of these updates (is a|are) security update(s)?.80 \d+ of these updates (is a|are) security update(s)?.
72 To see these additional updates run: apt list --upgradable81 To see these additional updates run: apt list --upgradable
73 """82 """
83 When I update contract to use `effectiveTo` as `days=-20`
84 And I run `python3 /usr/lib/ubuntu-advantage/ua_update_messaging.py` with sudo
85 And I run `update-motd` with sudo
74 Then if `<release>` in `xenial or bionic` and stdout matches regexp:86 Then if `<release>` in `xenial or bionic` and stdout matches regexp:
75 """87 """
76 \d+ package(s)? can be updated.88
77 \d+ of these updates (is a|are) security update(s)?.89 \*Your UA Infra: ESM subscription has EXPIRED\*
90
91 \d+ additional security update\(s\) could have been applied via UA Infra: ESM.
92
93 Renew your UA services at https:\/\/ubuntu.com\/esm
94
95 """
96 Then if `<release>` in `xenial` and stdout matches regexp:
97 """
98
99 Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
100 applicable law.
101
102 """
103 When I run `apt upgrade --dry-run` with sudo
104 Then if `<release>` in `xenial` and stdout matches regexp:
105 """
106 \*Your UA Infra: ESM subscription has EXPIRED\*
107 Enabling UA Infra: ESM service would provide security updates for following
108 packages:
109 libkrad0
110 1 esm-infra security update\(s\) NOT APPLIED. Renew your UA services at
111 https:\/\/ubuntu.com\/advantage
112
78 """113 """
79 Examples: ubuntu release packages114 Examples: ubuntu release packages
80 | release | downrev_pkg |115 | release | downrev_pkg |
@@ -85,12 +120,12 @@ Feature: Command behaviour when attaching a machine to an Ubuntu Advantage
85120
86 @series.all121 @series.all
87 @uses.config.machine_type.aws.generic122 @uses.config.machine_type.aws.generic
88 Scenario Outline: Attach command in a ubuntu lxd container123 Scenario Outline: Attach command in an generic AWS Ubuntu VM
89 Given a `<release>` machine with ubuntu-advantage-tools installed124 Given a `<release>` machine with ubuntu-advantage-tools installed
90 When I attach `contract_token` with sudo125 When I attach `contract_token` with sudo
91 Then stdout matches regexp:126 Then stdout matches regexp:
92 """127 """
93 ESM Infra enabled128 UA Infra: ESM enabled
94 """129 """
95 And stdout matches regexp:130 And stdout matches regexp:
96 """131 """
@@ -123,7 +158,7 @@ Feature: Command behaviour when attaching a machine to an Ubuntu Advantage
123 When I attach `contract_token` with sudo158 When I attach `contract_token` with sudo
124 Then stdout matches regexp:159 Then stdout matches regexp:
125 """160 """
126 ESM Infra enabled161 UA Infra: ESM enabled
127 """162 """
128 And stdout matches regexp:163 And stdout matches regexp:
129 """164 """
@@ -156,7 +191,7 @@ Feature: Command behaviour when attaching a machine to an Ubuntu Advantage
156 When I attach `contract_token` with sudo191 When I attach `contract_token` with sudo
157 Then stdout matches regexp:192 Then stdout matches regexp:
158 """193 """
159 ESM Infra enabled194 UA Infra: ESM enabled
160 """195 """
161 And stdout matches regexp:196 And stdout matches regexp:
162 """197 """
diff --git a/features/attached_commands.feature b/features/attached_commands.feature
index 4b48f76..48b69eb 100644
--- a/features/attached_commands.feature
+++ b/features/attached_commands.feature
@@ -8,12 +8,12 @@ Feature: Command behaviour when attached to an UA subscription
8 Then I verify that running `ua refresh` `as non-root` exits `1`8 Then I verify that running `ua refresh` `as non-root` exits `1`
9 And stderr matches regexp:9 And stderr matches regexp:
10 """10 """
11 This command must be run as root \(try using sudo\)11 This command must be run as root \(try using sudo\).
12 """12 """
13 When I run `ua refresh` with sudo13 When I run `ua refresh` with sudo
14 Then I will see the following on stdout:14 Then I will see the following on stdout:
15 """15 """
16 Successfully refreshed your subscription16 Successfully refreshed your subscription.
17 """17 """
1818
19 Examples: ubuntu release19 Examples: ubuntu release
@@ -30,7 +30,7 @@ Feature: Command behaviour when attached to an UA subscription
30 Then I verify that running `ua disable livepatch` `as non-root` exits `1`30 Then I verify that running `ua disable livepatch` `as non-root` exits `1`
31 And stderr matches regexp:31 And stderr matches regexp:
32 """32 """
33 This command must be run as root \(try using sudo\)33 This command must be run as root \(try using sudo\).
34 """34 """
35 And I verify that running `ua disable livepatch` `with sudo` exits `1`35 And I verify that running `ua disable livepatch` `with sudo` exits `1`
36 And I will see the following on stdout:36 And I will see the following on stdout:
@@ -53,18 +53,18 @@ Feature: Command behaviour when attached to an UA subscription
53 Then I verify that running `ua disable foobar` `as non-root` exits `1`53 Then I verify that running `ua disable foobar` `as non-root` exits `1`
54 And stderr matches regexp:54 And stderr matches regexp:
55 """55 """
56 This command must be run as root \(try using sudo\)56 This command must be run as root \(try using sudo\).
57 """57 """
58 And I verify that running `ua disable foobar` `with sudo` exits `1`58 And I verify that running `ua disable foobar` `with sudo` exits `1`
59 And stderr matches regexp:59 And stderr matches regexp:
60 """60 """
61 Cannot disable unknown service 'foobar'.61 Cannot disable unknown service 'foobar'.
62 Try cc-eal, cis, esm-apps, esm-infra, fips, fips-updates, livepatch62 Try cc-eal, cis, esm-apps, esm-infra, fips, fips-updates, livepatch.
63 """63 """
64 And I verify that running `ua disable esm-infra` `as non-root` exits `1`64 And I verify that running `ua disable esm-infra` `as non-root` exits `1`
65 And stderr matches regexp:65 And stderr matches regexp:
66 """66 """
67 This command must be run as root \(try using sudo\)67 This command must be run as root \(try using sudo\).
68 """68 """
69 When I run `ua disable esm-infra` with sudo69 When I run `ua disable esm-infra` with sudo
70 Then I will see the following on stdout:70 Then I will see the following on stdout:
@@ -91,7 +91,7 @@ Feature: Command behaviour when attached to an UA subscription
91 Then I verify that running `ua detach` `as non-root` exits `1`91 Then I verify that running `ua detach` `as non-root` exits `1`
92 And stderr matches regexp:92 And stderr matches regexp:
93 """93 """
94 This command must be run as root \(try using sudo\)94 This command must be run as root \(try using sudo\).
95 """95 """
96 When I run `ua detach --assume-yes` with sudo96 When I run `ua detach --assume-yes` with sudo
97 Then I will see the following on stdout:97 Then I will see the following on stdout:
@@ -99,7 +99,7 @@ Feature: Command behaviour when attached to an UA subscription
99 Detach will disable the following service:99 Detach will disable the following service:
100 esm-infra100 esm-infra
101 Updating package lists101 Updating package lists
102 This machine is now detached102 This machine is now detached.
103 """103 """
104 When I run `ua status --all` as non-root104 When I run `ua status --all` as non-root
105 Then stdout matches regexp:105 Then stdout matches regexp:
@@ -133,7 +133,7 @@ Feature: Command behaviour when attached to an UA subscription
133 Then I verify that running `ua auto-attach` `as non-root` exits `1`133 Then I verify that running `ua auto-attach` `as non-root` exits `1`
134 And stderr matches regexp:134 And stderr matches regexp:
135 """135 """
136 This command must be run as root \(try using sudo\)136 This command must be run as root \(try using sudo\).
137 """137 """
138 When I run `ua auto-attach` with sudo138 When I run `ua auto-attach` with sudo
139 Then stderr matches regexp:139 Then stderr matches regexp:
@@ -236,7 +236,7 @@ Feature: Command behaviour when attached to an UA subscription
236 And stderr matches regexp:236 And stderr matches regexp:
237 """237 """
238 Cannot disable unknown service 'foobar'.238 Cannot disable unknown service 'foobar'.
239 Try cc-eal, cis, esm-apps, esm-infra, fips, fips-updates, livepatch239 Try cc-eal, cis, esm-apps, esm-infra, fips, fips-updates, livepatch.
240 """240 """
241 When I run `ua status` with sudo241 When I run `ua status` with sudo
242 Then stdout matches regexp:242 Then stdout matches regexp:
@@ -258,13 +258,13 @@ Feature: Command behaviour when attached to an UA subscription
258 Then I verify that running `ua disable foobar` `as non-root` exits `1`258 Then I verify that running `ua disable foobar` `as non-root` exits `1`
259 And stderr matches regexp:259 And stderr matches regexp:
260 """260 """
261 This command must be run as root \(try using sudo\)261 This command must be run as root \(try using sudo\).
262 """262 """
263 And I verify that running `ua disable foobar` `with sudo` exits `1`263 And I verify that running `ua disable foobar` `with sudo` exits `1`
264 And stderr matches regexp:264 And stderr matches regexp:
265 """265 """
266 Cannot disable unknown service 'foobar'.266 Cannot disable unknown service 'foobar'.
267 Try cc-eal, cis, esm-apps, esm-infra, fips, fips-updates, livepatch267 Try cc-eal, cis, esm-apps, esm-infra, fips, fips-updates, livepatch.
268 """268 """
269 And I verify that running `ua disable esm-infra` `as non-root` exits `1`269 And I verify that running `ua disable esm-infra` `as non-root` exits `1`
270 And stderr matches regexp:270 And stderr matches regexp:
diff --git a/features/attached_enable.feature b/features/attached_enable.feature
index 8a36fce..f6f0ec9 100644
--- a/features/attached_enable.feature
+++ b/features/attached_enable.feature
@@ -8,7 +8,7 @@ Feature: Enable command behaviour when attached to an UA subscription
8 Then I verify that running `ua enable cc-eal` `as non-root` exits `1`8 Then I verify that running `ua enable cc-eal` `as non-root` exits `1`
9 And I will see the following on stderr:9 And I will see the following on stderr:
10 """10 """
11 This command must be run as root (try using sudo)11 This command must be run as root (try using sudo).
12 """12 """
13 And I verify that running `ua enable cc-eal --beta` `with sudo` exits `1`13 And I verify that running `ua enable cc-eal --beta` `with sudo` exits `1`
14 And I will see the following on stdout14 And I will see the following on stdout
@@ -24,7 +24,7 @@ Feature: Enable command behaviour when attached to an UA subscription
24 And stderr matches regexp:24 And stderr matches regexp:
25 """25 """
26 Cannot enable unknown service 'cc-eal'.26 Cannot enable unknown service 'cc-eal'.
27 Try esm-infra, fips, fips-updates, livepatch27 Try esm-infra, fips, fips-updates, livepatch.
28 """28 """
2929
30 Examples: ubuntu release30 Examples: ubuntu release
@@ -34,73 +34,39 @@ Feature: Enable command behaviour when attached to an UA subscription
34 | trusty | CC EAL2 is not available for Ubuntu 14.04 LTS (Trusty Tahr). |34 | trusty | CC EAL2 is not available for Ubuntu 14.04 LTS (Trusty Tahr). |
3535
36 @series.all36 @series.all
37 Scenario Outline: Attached enable a disabled beta service and unknown service in a ubuntu machine37 Scenario Outline: Attached enable of a service in a ubuntu machine
38 Given a `<release>` machine with ubuntu-advantage-tools installed38 Given a `<release>` machine with ubuntu-advantage-tools installed
39 When I attach `contract_token` with sudo39 When I attach `contract_token` with sudo
40 Then I verify that running `ua enable cc-eal foobar` `as non-root` exits `1`40 Then I verify that running `ua enable foobar` `as non-root` exits `1`
41 And I will see the following on stderr:41 And I will see the following on stderr:
42 """42 """
43 This command must be run as root (try using sudo)43 This command must be run as root (try using sudo).
44 """44 """
45 And I verify that running `ua enable cc-eal foobar` `with sudo` exits `1`45 And I verify that running `ua enable foobar` `with sudo` exits `1`
46 And I will see the following on stdout:46 And I will see the following on stdout:
47 """47 """
48 One moment, checking your subscription first48 One moment, checking your subscription first
49 """49 """
50 And stderr matches regexp:50 And stderr matches regexp:
51 """51 """
52 Cannot enable unknown service 'foobar, cc-eal'.52 Cannot enable unknown service 'foobar'.
53 Try esm-infra, fips, fips-updates, livepatch53 Try esm-infra, fips, fips-updates, livepatch.
54 """
55
56 Examples: ubuntu release
57 | release |
58 | bionic |
59 | focal |
60 | trusty |
61 | xenial |
62
63 @series.all
64 Scenario Outline: Attached enable of an unknown service in a ubuntu machine
65 Given a `<release>` machine with ubuntu-advantage-tools installed
66 When I attach `contract_token` with sudo
67 Then I verify that running `ua enable foobar` `as non-root` exits `1`
68 And I will see the following on stderr:
69 """
70 This command must be run as root (try using sudo)
71 """54 """
72 And I verify that running `ua enable foobar` `with sudo` exits `1`55 And I verify that running `ua enable cc-eal foobar` `with sudo` exits `1`
73 And I will see the following on stdout:56 And I will see the following on stdout:
74 """57 """
75 One moment, checking your subscription first58 One moment, checking your subscription first
76 """59 """
77 And stderr matches regexp:60 And stderr matches regexp:
78 """61 """
79 Cannot enable unknown service 'foobar'.62 Cannot enable unknown service 'foobar, cc-eal'.
80 Try esm-infra, fips, fips-updates, livepatch63 Try esm-infra, fips, fips-updates, livepatch.
81 """
82
83 Examples: ubuntu release
84 | release |
85 | bionic |
86 | focal |
87 | trusty |
88 | xenial |
89
90 @series.all
91 Scenario Outline: Attached enable of a known service already enabled (UA Infra) in a ubuntu machine
92 Given a `<release>` machine with ubuntu-advantage-tools installed
93 When I attach `contract_token` with sudo
94 Then I verify that running `ua enable esm-infra` `as non-root` exits `1`
95 And I will see the following on stderr:
96 """
97 This command must be run as root (try using sudo)
98 """64 """
99 And I verify that running `ua enable esm-infra` `with sudo` exits `1`65 And I verify that running `ua enable esm-infra` `with sudo` exits `1`
100 Then I will see the following on stdout:66 Then I will see the following on stdout:
101 """67 """
102 One moment, checking your subscription first68 One moment, checking your subscription first
103 ESM Infra is already enabled.69 UA Infra: ESM is already enabled.
104 See: sudo ua status70 See: sudo ua status
105 """71 """
106 When I run `apt-cache policy` with sudo72 When I run `apt-cache policy` with sudo
@@ -124,69 +90,41 @@ Feature: Enable command behaviour when attached to an UA subscription
124 | trusty | libgit2-0 | https://esm.ubuntu.com/ubuntu/ |90 | trusty | libgit2-0 | https://esm.ubuntu.com/ubuntu/ |
125 | xenial | libkrad0 | https://esm.ubuntu.com/infra/ubuntu |91 | xenial | libkrad0 | https://esm.ubuntu.com/infra/ubuntu |
12692
127 @series.xenial
128 @series.bionic
129 @series.focal
130 Scenario Outline: Attached enable of a know service shows update in a ubuntu machine
131 Given a `<release>` machine with ubuntu-advantage-tools installed
132 When I attach `contract_token` with sudo
133 Then I verify that running `ua enable esm-infra` `with sudo` exits `1`
134 And I will see the following on stdout:
135 """
136 One moment, checking your subscription first
137 ESM Infra is already enabled.
138 See: sudo ua status
139 """
140 When I run `apt install -y <pkg-version>` with sudo, retrying exit [100]
141 And I run `apt update` with sudo
142 Then stdout matches regexp
143 """
144 \d+ of the updates (is|are) from UA Infra: ESM
145 """
146 When I run `ua disable esm-infra` with sudo
147 And I run `apt update` with sudo
148 Then stdout does not match regexp
149 """
150 \d+ of the updates (is|are) from UA Infra: ESM
151 """
152
153 Examples: ubuntu release
154 | release | pkg-version |
155 | bionic | libkrad0=1.16-2build1 |
156 | focal | hello=2.10-2ubuntu2 |
157 | xenial | libkrad0=1.13.2+dfsg-5 |
158
159 @series.all93 @series.all
160 @uses.config.machine_type.lxd.container94 @uses.config.machine_type.lxd.container
161 Scenario Outline: Attached enable of non-container services in a ubuntu lxd container95 Scenario Outline: Attached enable of non-container services in a ubuntu lxd container
162 Given a `<release>` machine with ubuntu-advantage-tools installed96 Given a `<release>` machine with ubuntu-advantage-tools installed
163 When I attach `contract_token` with sudo97 When I attach `contract_token` with sudo
164 Then I verify that running `ua enable <service> <flag>` `as non-root` exits `1`98 Then I verify that running `ua enable livepatch` `as non-root` exits `1`
165 And I will see the following on stderr:99 And I will see the following on stderr:
166 """100 """
167 This command must be run as root (try using sudo)101 This command must be run as root (try using sudo).
102 """
103 And I verify that running `ua enable livepatch` `with sudo` exits `1`
104 And I will see the following on stdout:
105 """
106 One moment, checking your subscription first
107 Cannot install Livepatch on a container.
108 """
109 And I verify that running `ua enable fips --assume-yes` `with sudo` exits `1`
110 And I will see the following on stdout:
111 """
112 One moment, checking your subscription first
113 Cannot install FIPS on a container.
168 """114 """
169 And I verify that running `ua enable <service> <flag>` `with sudo` exits `1`115 And I verify that running `ua enable fips-updates --assume-yes` `with sudo` exits `1`
170 And I will see the following on stdout:116 And I will see the following on stdout:
171 """117 """
172 One moment, checking your subscription first118 One moment, checking your subscription first
173 Cannot install <title> on a container119 Cannot install FIPS Updates on a container.
174 """120 """
175121
176 Examples: Un-supported services in containers122 Examples: Un-supported services in containers
177 | release | service | title | flag |123 | release |
178 | bionic | livepatch | Livepatch | |124 | bionic |
179 | bionic | fips | FIPS | --assume-yes |125 | focal |
180 | bionic | fips-updates | FIPS Updates | --assume-yes |126 | trusty |
181 | focal | livepatch | Livepatch | |127 | xenial |
182 | focal | fips | FIPS | --assume-yes |
183 | focal | fips-updates | FIPS Updates | --assume-yes |
184 | trusty | livepatch | Livepatch | |
185 | trusty | fips | FIPS | --assume-yes |
186 | trusty | fips-updates | FIPS Updates | --assume-yes |
187 | xenial | livepatch | Livepatch | |
188 | xenial | fips | FIPS | --assume-yes |
189 | xenial | fips-updates | FIPS Updates | --assume-yes |
190128
191 @series.all129 @series.all
192 Scenario Outline: Attached enable not entitled service in a ubuntu machine130 Scenario Outline: Attached enable not entitled service in a ubuntu machine
@@ -195,26 +133,29 @@ Feature: Enable command behaviour when attached to an UA subscription
195 Then I verify that running `ua enable <service>` `as non-root` exits `1`133 Then I verify that running `ua enable <service>` `as non-root` exits `1`
196 And I will see the following on stderr:134 And I will see the following on stderr:
197 """135 """
198 This command must be run as root (try using sudo)136 This command must be run as root (try using sudo).
137 """
138 And I verify that running `ua enable cis --beta` `with sudo` exits `1`
139 And I will see the following on stdout:
140 """
141 One moment, checking your subscription first
142 This subscription is not entitled to CIS Audit
143 For more information see: https://ubuntu.com/advantage.
199 """144 """
200 And I verify that running `ua enable <service> --beta` `with sudo` exits `1`145 And I verify that running `ua enable esm-apps --beta` `with sudo` exits `1`
201 And I will see the following on stdout:146 And I will see the following on stdout:
202 """147 """
203 One moment, checking your subscription first148 One moment, checking your subscription first
204 This subscription is not entitled to <title>.149 This subscription is not entitled to UA Apps: ESM
205 For more information see: https://ubuntu.com/advantage150 For more information see: https://ubuntu.com/advantage.
206 """151 """
207152
208 Examples: not entitled services153 Examples: not entitled services
209 | release | service | title |154 | release |
210 | bionic | cis | CIS Audit |155 | bionic |
211 | bionic | esm-apps | ESM Apps |156 | focal |
212 | focal | cis | CIS Audit |157 | trusty |
213 | focal | esm-apps | ESM Apps |158 | xenial |
214 | trusty | cis | CIS Audit |
215 | trusty | esm-apps | ESM Apps |
216 | xenial | cis | CIS Audit |
217 | xenial | esm-apps | ESM Apps |
218159
219 @series.focal160 @series.focal
220 @uses.config.machine_type.lxd.vm161 @uses.config.machine_type.lxd.vm
@@ -309,7 +250,7 @@ Feature: Enable command behaviour when attached to an UA subscription
309 Then stdout matches regexp:250 Then stdout matches regexp:
310 """251 """
311 Updating package lists252 Updating package lists
312 ESM Infra enabled253 UA Infra: ESM enabled
313 Installing canonical-livepatch snap254 Installing canonical-livepatch snap
314 Canonical livepatch enabled255 Canonical livepatch enabled
315 """256 """
@@ -321,7 +262,7 @@ Feature: Enable command behaviour when attached to an UA subscription
321 Updating package lists262 Updating package lists
322 Installing FIPS packages263 Installing FIPS packages
323 FIPS enabled264 FIPS enabled
324 A reboot is required to complete install265 A reboot is required to complete install.
325 """266 """
326 When I append the following on uaclient config:267 When I append the following on uaclient config:
327 """268 """
@@ -332,7 +273,7 @@ Feature: Enable command behaviour when attached to an UA subscription
332 And I will see the following on stdout273 And I will see the following on stdout
333 """274 """
334 One moment, checking your subscription first275 One moment, checking your subscription first
335 Cannot enable Livepatch when FIPS is enabled276 Cannot enable Livepatch when FIPS is enabled.
336 """277 """
337278
338 @series.bionic279 @series.bionic
@@ -343,7 +284,7 @@ Feature: Enable command behaviour when attached to an UA subscription
343 Then stdout matches regexp:284 Then stdout matches regexp:
344 """285 """
345 Updating package lists286 Updating package lists
346 ESM Infra enabled287 UA Infra: ESM enabled
347 Installing canonical-livepatch snap288 Installing canonical-livepatch snap
348 Canonical livepatch enabled289 Canonical livepatch enabled
349 """290 """
@@ -356,7 +297,10 @@ Feature: Enable command behaviour when attached to an UA subscription
356 And I will see the following on stdout297 And I will see the following on stdout
357 """298 """
358 One moment, checking your subscription first299 One moment, checking your subscription first
359 Cannot enable FIPS when Livepatch is enabled300 """
301 And I will see the following on stderr
302 """
303 Cannot enable FIPS when Livepatch is enabled.
360 """304 """
361305
362 @series.xenial306 @series.xenial
@@ -368,7 +312,7 @@ Feature: Enable command behaviour when attached to an UA subscription
368 Then stdout matches regexp:312 Then stdout matches regexp:
369 """313 """
370 Updating package lists314 Updating package lists
371 ESM Infra enabled315 UA Infra: ESM enabled
372 """316 """
373 And stdout matches regexp:317 And stdout matches regexp:
374 """318 """
@@ -382,7 +326,7 @@ Feature: Enable command behaviour when attached to an UA subscription
382 Updating package lists326 Updating package lists
383 Installing FIPS packages327 Installing FIPS packages
384 FIPS enabled328 FIPS enabled
385 A reboot is required to complete install329 A reboot is required to complete install.
386 """330 """
387 When I run `ua status` with sudo331 When I run `ua status` with sudo
388 Then stdout matches regexp:332 Then stdout matches regexp:
@@ -407,7 +351,7 @@ Feature: Enable command behaviour when attached to an UA subscription
407 When I attach `contract_token` with sudo351 When I attach `contract_token` with sudo
408 Then stdout matches regexp:352 Then stdout matches regexp:
409 """353 """
410 ESM Infra enabled354 UA Infra: ESM enabled
411 """355 """
412 And stdout matches regexp:356 And stdout matches regexp:
413 """357 """
@@ -422,13 +366,13 @@ Feature: Enable command behaviour when attached to an UA subscription
422 Updating package lists366 Updating package lists
423 Installing FIPS Updates packages367 Installing FIPS Updates packages
424 FIPS Updates enabled368 FIPS Updates enabled
425 A reboot is required to complete install369 A reboot is required to complete install.
426 """370 """
427 When I verify that running `ua enable fips --assume-yes` `with sudo` exits `1`371 When I verify that running `ua enable fips --assume-yes` `with sudo` exits `1`
428 Then I will see the following on stdout372 Then I will see the following on stdout
429 """373 """
430 One moment, checking your subscription first374 One moment, checking your subscription first
431 Cannot enable FIPS when FIPS Updates is enabled375 Cannot enable FIPS when FIPS Updates is enabled.
432 """376 """
433377
434 Examples: ubuntu release378 Examples: ubuntu release
diff --git a/features/cloud.py b/features/cloud.py
index 4443345..13d3ea4 100644
--- a/features/cloud.py
+++ b/features/cloud.py
@@ -730,15 +730,3 @@ class LXDContainer(_LXD):
730 def pycloudlib_cls(self):730 def pycloudlib_cls(self):
731 """Return the pycloudlib cls to be used as an api."""731 """Return the pycloudlib cls to be used as an api."""
732 return pycloudlib.LXDContainer732 return pycloudlib.LXDContainer
733
734 def manage_ssh_key(self, private_key_path: "Optional[str]" = None) -> None:
735 """Create and manage ssh key pairs to be used in the cloud provider.
736
737 On a LXD container, we do not use ssh keys to communicate with the
738 instance. Therefore, this method should not be used.
739
740 :param private_key_path:
741 Location of the private key path to use. If None, the location
742 will be a default location.
743 """
744 pass
diff --git a/features/environment.py b/features/environment.py
index 9a53933..0df3632 100644
--- a/features/environment.py
+++ b/features/environment.py
@@ -1,5 +1,4 @@
1import datetime1import datetime
2import errno
3import os2import os
4import itertools3import itertools
5import tempfile4import tempfile
@@ -23,8 +22,8 @@ from features.util import emit_spinner_on_travis, lxc_get_property, build_debs
2322
24ALL_SUPPORTED_SERIES = ["bionic", "focal", "trusty", "xenial"]23ALL_SUPPORTED_SERIES = ["bionic", "focal", "trusty", "xenial"]
2524
26DAILY_PPA = "http://ppa.launchpad.net/canonical-server/ua-client-daily/ubuntu"25DAILY_PPA = "http://ppa.launchpad.net/ua-client/daily/ubuntu"
27DAILY_PPA_KEYID = "8A295C4FB8B190B7"26DAILY_PPA_KEYID = "6E34E7116C0BC933"
2827
29USERDATA_BLOCK_AUTO_ATTACH_IMG = """\28USERDATA_BLOCK_AUTO_ATTACH_IMG = """\
30#cloud-config29#cloud-config
@@ -500,18 +499,14 @@ def after_step(context, step):
500 )499 )
501 print("-- pull instance:{} {}".format(log_file, artifact_file))500 print("-- pull instance:{} {}".format(log_file, artifact_file))
502 try:501 try:
503 context.instance.pull_file(log_file, artifact_file)502 result = context.instance.execute(
504 except IOError as e:503 ["cat", log_file], use_sudo=True
505 if e.errno == errno.EACCES:504 )
506 result = context.instance.execute(505 content = result.stdout if result.ok else ""
507 ["cat", log_file], use_sudo=True
508 )
509 with open(artifact_file, "w") as stream:
510 stream.write(result.stdout)
511 except RuntimeError:506 except RuntimeError:
512 # File did not exist507 content = ""
513 with open(artifact_file, "w") as stream:508 with open(artifact_file, "w") as stream:
514 stream.write("")509 stream.write(content)
515 for artifact_file, cmd in FAILURE_CMDS.items():510 for artifact_file, cmd in FAILURE_CMDS.items():
516 result = context.instance.execute(cmd, use_sudo=True)511 result = context.instance.execute(cmd, use_sudo=True)
517 artifact_file = os.path.join(artifacts_dir, artifact_file)512 artifact_file = os.path.join(artifacts_dir, artifact_file)
@@ -520,7 +515,9 @@ def after_step(context, step):
520515
521516
522def after_all(context):517def after_all(context):
523 if context.config.image_clean:518 if context.config.ppa == "":
519 print(" No custom images to delete. UACLIENT_BEHAVE_PPA is unset.")
520 elif context.config.image_clean:
524 for key, image in context.series_image_name.items():521 for key, image in context.series_image_name.items():
525 if key == context.series_reuse_image:522 if key == context.series_reuse_image:
526 print(523 print(
@@ -635,6 +632,16 @@ def create_uat_image(context: Context, series: str) -> None:
635 context.reuse_container[series],632 context.reuse_container[series],
636 )633 )
637 return634 return
635 ppa = context.config.ppa
636 if ppa == "":
637 image_name = context.config.cloud_manager.locate_image_name(series)
638 print(
639 "--- Unset UACLIENT_BEHAVE_PPA. Using ubuntu-advantage-tools "
640 + "from image: {}".format(image_name)
641 )
642 context.series_image_name[series] = image_name
643 return
644
638 time_suffix = datetime.datetime.now().strftime("%s%f")645 time_suffix = datetime.datetime.now().strftime("%s%f")
639 deb_paths = []646 deb_paths = []
640 if context.config.build_pr:647 if context.config.build_pr:
@@ -741,11 +748,14 @@ def _install_uat_in_container(
741 if "pro" in config.machine_type:748 if "pro" in config.machine_type:
742 features = "features:\n disable_auto_attach: true\n"749 features = "features:\n disable_auto_attach: true\n"
743 conf_path = "/etc/ubuntu-advantage/uaclient.conf"750 conf_path = "/etc/ubuntu-advantage/uaclient.conf"
744 cmd = "printf '{}' > /tmp/uaclient.conf".format(features)751 cmd = "printf '{}' > /var/tmp/uaclient.conf".format(features)
745 cmds.append('sh -c "{}"'.format(cmd))752 cmds.append('sh -c "{}"'.format(cmd))
746 cmds.append(753 cmds.append(
747 'sudo -- sh -c "cat /tmp/uaclient.conf >> {}"'.format(conf_path)754 'sudo -- sh -c "cat /var/tmp/uaclient.conf >> {}"'.format(
755 conf_path
756 )
748 )757 )
758 cmds.append("sudo ua status --wait")
749 cmds.append("sudo ua detach --assume-yes")759 cmds.append("sudo ua detach --assume-yes")
750760
751 cmds.append(["ua", "version"])761 cmds.append(["ua", "version"])
diff --git a/features/gcp-ids.yaml b/features/gcp-ids.yaml
index a3ca5bd..fe37cfb 100644
--- a/features/gcp-ids.yaml
+++ b/features/gcp-ids.yaml
@@ -1,3 +1,3 @@
1xenial: "projects/ubuntu-os-cloud-image-proposed/global/images/testing-ubuntu-pro-1604-xenial-v20210205"1xenial: "projects/ubuntu-os-pro-cloud/global/images/ubuntu-pro-1604-xenial-v20210329"
2bionic: "projects/ubuntu-os-cloud-image-proposed/global/images/testing-ubuntu-pro-1804-bionic-v20210205"2bionic: "projects/ubuntu-os-pro-cloud/global/images/ubuntu-pro-1804-bionic-v20210329"
3focal: "projects/ubuntu-os-cloud-image-proposed/global/images/testing-ubuntu-pro-2004-focal-v20210205"3focal: "projects/ubuntu-os-pro-cloud/global/images/ubuntu-pro-2004-focal-v20210329"
diff --git a/features/staging_commands.feature b/features/staging_commands.feature
index 90c212f..8946977 100644
--- a/features/staging_commands.feature
+++ b/features/staging_commands.feature
@@ -8,14 +8,15 @@ Feature: Enable command behaviour when attached to an UA staging subscription
8 Then I verify that running `ua enable cc-eal` `as non-root` exits `1`8 Then I verify that running `ua enable cc-eal` `as non-root` exits `1`
9 And I will see the following on stderr:9 And I will see the following on stderr:
10 """10 """
11 This command must be run as root (try using sudo)11 This command must be run as root (try using sudo).
12 """12 """
13 When I run `ua enable cc-eal --beta` with sudo13 When I run `ua enable cc-eal --beta` with sudo
14 Then I will see the following on stdout:14 Then I will see the following on stdout:
15 """15 """
16 One moment, checking your subscription first16 One moment, checking your subscription first
17 GPG key '/usr/share/keyrings/ubuntu-cc-keyring.gpg' not found17 GPG key '/usr/share/keyrings/ubuntu-cc-keyring.gpg' not found.
18 """ 18 """
19
19 @series.xenial20 @series.xenial
20 @series.bionic21 @series.bionic
21 @series.focal22 @series.focal
@@ -46,6 +47,53 @@ Feature: Enable command behaviour when attached to an UA staging subscription
46 \s*\*\*\* .* 50047 \s*\*\*\* .* 500
47 \s*500 https://esm.staging.ubuntu.com/apps/ubuntu <release>-apps-security/main amd64 Packages48 \s*500 https://esm.staging.ubuntu.com/apps/ubuntu <release>-apps-security/main amd64 Packages
48 """49 """
50 When I run `mkdir -p /var/lib/ubuntu-advantage/messages` with sudo
51 When I create the file `/var/lib/ubuntu-advantage/messages/apt-pre-invoke-no-packages-infra.tmpl` with the following
52 """
53 esm-infra-no {ESM_INFRA_PKG_COUNT}:{ESM_INFRA_PACKAGES}
54 """
55 When I create the file `/var/lib/ubuntu-advantage/messages/apt-pre-invoke-packages-infra.tmpl` with the following
56 """
57 esm-infra {ESM_INFRA_PKG_COUNT}:{ESM_INFRA_PACKAGES}
58 """
59 When I create the file `/var/lib/ubuntu-advantage/messages/apt-pre-invoke-packages-apps.tmpl` with the following
60 """
61 esm-apps {ESM_APPS_PKG_COUNT}:{ESM_APPS_PACKAGES}
62 """
63 When I create the file `/var/lib/ubuntu-advantage/messages/apt-pre-invoke-no-packages-apps.tmpl` with the following
64 """
65 esm-apps-no {ESM_APPS_PKG_COUNT}:{ESM_APPS_PACKAGES}
66 """
67 When I run `/usr/lib/ubuntu-advantage/apt-esm-hook process-templates` with sudo
68 When I run `cat /var/lib/ubuntu-advantage/messages/apt-pre-invoke-packages-apps` with sudo
69 Then stdout matches regexp:
70 """
71 esm-apps(-no)? \d+:(.*)?
72 """
73 When I run `cat /var/lib/ubuntu-advantage/messages/apt-pre-invoke-packages-infra` with sudo
74 Then stdout matches regexp:
75 """
76 esm-infra(-no)? \d+:(.*)?
77 """
78 When I create the file `/var/lib/ubuntu-advantage/messages/apt-pre-invoke-packages-infra.tmpl` with the following
79 """
80 esm-infra {ESM_INFRA_PKG_COUNT} {ESM_INFRA_PACKAGES}
81 """
82 When I create the file `/var/lib/ubuntu-advantage/messages/apt-pre-invoke-no-packages-infra.tmpl` with the following
83 """
84 esm-infra-no {ESM_INFRA_PKG_COUNT} {ESM_INFRA_PACKAGES}
85 """
86 When I create the file `/var/lib/ubuntu-advantage/messages/apt-pre-invoke-packages-apps.tmpl` with the following
87 """
88 esm-apps {ESM_APPS_PKG_COUNT} {ESM_APPS_PACKAGES}
89 """
90 When I run `apt upgrade --dry-run` with sudo
91 Then stdout matches regexp:
92 """
93 esm-apps(-no)? \d+.*
94
95 esm-infra(-no)? \d+.*
96 """
4997
50 Examples: ubuntu release98 Examples: ubuntu release
51 | release | apps-pkg |99 | release | apps-pkg |
@@ -81,7 +129,6 @@ Feature: Enable command behaviour when attached to an UA staging subscription
81 FIPS support requires system reboot to complete configuration129 FIPS support requires system reboot to complete configuration
82 """130 """
83 And I verify that running `apt update` `with sudo` exits `0`131 And I verify that running `apt update` `with sudo` exits `0`
84 And I verify that running `grep Traceback /var/log/ubuntu-advantage.log` `with sudo` exits `1`
85 And I verify that `openssh-server` is installed from apt source `<fips-apt-source>`132 And I verify that `openssh-server` is installed from apt source `<fips-apt-source>`
86 And I verify that `openssh-client` is installed from apt source `<fips-apt-source>`133 And I verify that `openssh-client` is installed from apt source `<fips-apt-source>`
87 And I verify that `strongswan` is installed from apt source `<fips-apt-source>`134 And I verify that `strongswan` is installed from apt source `<fips-apt-source>`
@@ -171,7 +218,6 @@ Feature: Enable command behaviour when attached to an UA staging subscription
171 <fips-service> +yes enabled218 <fips-service> +yes enabled
172 """219 """
173 And I verify that running `apt update` `with sudo` exits `0`220 And I verify that running `apt update` `with sudo` exits `0`
174 And I verify that running `grep Traceback /var/log/ubuntu-advantage.log` `with sudo` exits `1`
175 And I verify that `openssh-server` is installed from apt source `<fips-apt-source>`221 And I verify that `openssh-server` is installed from apt source `<fips-apt-source>`
176 And I verify that `openssh-client` is installed from apt source `<fips-apt-source>`222 And I verify that `openssh-client` is installed from apt source `<fips-apt-source>`
177 And I verify that `strongswan` is installed from apt source `<fips-apt-source>`223 And I verify that `strongswan` is installed from apt source `<fips-apt-source>`
@@ -370,7 +416,6 @@ Feature: Enable command behaviour when attached to an UA staging subscription
370 """416 """
371 When I reboot the `<release>` machine417 When I reboot the `<release>` machine
372 Then I verify that running `apt update` `with sudo` exits `0`418 Then I verify that running `apt update` `with sudo` exits `0`
373 And I verify that running `grep Traceback /var/log/ubuntu-advantage.log` `with sudo` exits `1`
374 And I verify that `ubuntu-fips` is installed from apt source `<fips-apt-source>`419 And I verify that `ubuntu-fips` is installed from apt source `<fips-apt-source>`
375 And I verify that `openssh-server` is installed from apt source `<fips-apt-source>`420 And I verify that `openssh-server` is installed from apt source `<fips-apt-source>`
376 And I verify that `openssh-client` is installed from apt source `<fips-apt-source>`421 And I verify that `openssh-client` is installed from apt source `<fips-apt-source>`
diff --git a/features/steps/steps.py b/features/steps/steps.py
index 928cfd7..8555ad6 100644
--- a/features/steps/steps.py
+++ b/features/steps/steps.py
@@ -18,7 +18,7 @@ from hamcrest import (
18from features.environment import create_uat_image18from features.environment import create_uat_image
19from features.util import SLOW_CMDS, emit_spinner_on_travis, nullcontext19from features.util import SLOW_CMDS, emit_spinner_on_travis, nullcontext
2020
21from uaclient.defaults import DEFAULT_CONFIG_FILE21from uaclient.defaults import DEFAULT_CONFIG_FILE, DEFAULT_MACHINE_TOKEN_PATH
2222
2323
24CONTAINER_PREFIX = "ubuntu-behave-test-"24CONTAINER_PREFIX = "ubuntu-behave-test-"
@@ -74,6 +74,16 @@ def given_a_machine(context, series):
74 context.instance74 context.instance
75 )75 )
7676
77 if series == "trusty":
78 # On trusty, the distro-info package is not directly
79 # installed when we install the ubuntu-advantage-client
80 # deb. This is fixing that problem on trusty
81 when_i_run_command(
82 context=context,
83 command="apt-get install -f -y",
84 user_spec="with sudo",
85 )
86
77 def cleanup_instance() -> None:87 def cleanup_instance() -> None:
78 if not context.config.destroy_instances:88 if not context.config.destroy_instances:
79 print(89 print(
@@ -118,7 +128,9 @@ def when_i_retry_run_command(context, command, user_spec, exit_codes):
118128
119129
120@when("I run `{command}` {user_spec}")130@when("I run `{command}` {user_spec}")
121def when_i_run_command(context, command, user_spec, verify_return=True):131def when_i_run_command(
132 context, command, user_spec, verify_return=True, stdin=None
133):
122 prefix = get_command_prefix_for_user_spec(user_spec)134 prefix = get_command_prefix_for_user_spec(user_spec)
123 slow_cmd_spinner = nullcontext135 slow_cmd_spinner = nullcontext
124 for slow_cmd in SLOW_CMDS:136 for slow_cmd in SLOW_CMDS:
@@ -128,7 +140,7 @@ def when_i_run_command(context, command, user_spec, verify_return=True):
128140
129 full_cmd = prefix + shlex.split(command)141 full_cmd = prefix + shlex.split(command)
130 with slow_cmd_spinner():142 with slow_cmd_spinner():
131 result = context.instance.execute(full_cmd)143 result = context.instance.execute(full_cmd, stdin=stdin)
132144
133 process = subprocess.CompletedProcess(145 process = subprocess.CompletedProcess(
134 args=full_cmd,146 args=full_cmd,
@@ -148,6 +160,59 @@ def when_i_run_command(context, command, user_spec, verify_return=True):
148 context.process = process160 context.process = process
149161
150162
163@when("I fix `{issue}` by attaching to a subscription with `{token_type}`")
164def when_i_fix_a_issue_by_attaching(context, issue, token_type):
165 token = getattr(context.config, token_type)
166 when_i_run_command(
167 context=context,
168 command="ua fix {}".format(issue),
169 user_spec="with sudo",
170 stdin="a\n{}\n".format(token),
171 )
172
173
174@when("I fix `{issue}` by enabling required service")
175def when_i_fix_a_issue_by_enabling_service(context, issue):
176 when_i_run_command(
177 context=context,
178 command="ua fix {}".format(issue),
179 user_spec="with sudo",
180 stdin="e\n",
181 )
182
183
184@when("I fix `{issue}` by updating expired token")
185def when_i_fix_a_issue_by_updating_expired_token(context, issue):
186 token = getattr(context.config, "contract_token")
187 when_i_run_command(
188 context=context,
189 command="ua fix {}".format(issue),
190 user_spec="with sudo",
191 stdin="r\n{}\n".format(token),
192 )
193
194
195@when("I update contract to use `{contract_field}` as `{new_value}`")
196def when_i_update_contract_field_to_new_value(
197 context, contract_field, new_value
198):
199 if contract_field == "effectiveTo":
200 if "days=" in new_value: # Set timedelta offset from current day
201 now = datetime.datetime.utcnow()
202 contract_expiry = now + datetime.timedelta(days=int(new_value[5:]))
203 new_value = contract_expiry.strftime("%Y-%m-%dT00:00:00Z")
204 when_i_run_command(
205 context,
206 'sed -i \'s/"{}": "[^"]*"/"{}": "{}"/g\' {}'.format(
207 contract_field,
208 contract_field,
209 new_value,
210 DEFAULT_MACHINE_TOKEN_PATH,
211 ),
212 user_spec="with sudo",
213 )
214
215
151@when("I attach `{token_type}` {user_spec}")216@when("I attach `{token_type}` {user_spec}")
152def when_i_attach_staging_token(context, token_type, user_spec):217def when_i_attach_staging_token(context, token_type, user_spec):
153 token = getattr(context.config, token_type)218 token = getattr(context.config, token_type)
@@ -225,6 +290,13 @@ def then_conditional_stdout_matches_regexp(context, value1, value2):
225 then_stdout_matches_regexp(context)290 then_stdout_matches_regexp(context)
226291
227292
293@then("if `{value1}` in `{value2}` and stdout does not match regexp")
294def then_conditional_stdout_does_not_match_regexp(context, value1, value2):
295 """Only apply regex assertion if value1 in value2."""
296 if value1 in value2.split(" or "):
297 then_stdout_does_not_match_regexp(context)
298
299
228@then("stdout matches regexp")300@then("stdout matches regexp")
229def then_stdout_matches_regexp(context):301def then_stdout_matches_regexp(context):
230 assert_that(context.process.stdout.strip(), matches_regexp(context.text))302 assert_that(context.process.stdout.strip(), matches_regexp(context.text))
diff --git a/features/ubuntu_pro.feature b/features/ubuntu_pro.feature
index 5dd0dbb..522781e 100644
--- a/features/ubuntu_pro.feature
+++ b/features/ubuntu_pro.feature
@@ -15,7 +15,17 @@ Feature: Command behaviour when attached to an UA subscription
15 """15 """
16 And I run `ua auto-attach` with sudo16 And I run `ua auto-attach` with sudo
17 And I run `ua status --wait` as non-root17 And I run `ua status --wait` as non-root
18 And I run `ua status --all` as non-root18 And I run `ua status` as non-root
19 Then stdout matches regexp:
20 """
21 SERVICE ENTITLED STATUS DESCRIPTION
22 esm-apps +yes +enabled +UA Apps: Extended Security Maintenance \(ESM\)
23 esm-infra +yes +enabled +UA Infra: Extended Security Maintenance \(ESM\)
24 fips +yes +<fips-s> +NIST-certified FIPS modules
25 fips-updates +yes +<fips-s> +Uncertified security updates to FIPS modules
26 livepatch +yes +enabled +Canonical Livepatch service
27 """
28 When I run `ua status --all` as non-root
19 Then stdout matches regexp:29 Then stdout matches regexp:
20 """30 """
21 SERVICE ENTITLED STATUS DESCRIPTION31 SERVICE ENTITLED STATUS DESCRIPTION
@@ -96,7 +106,17 @@ Feature: Command behaviour when attached to an UA subscription
96 """106 """
97 And I run `ua auto-attach` with sudo107 And I run `ua auto-attach` with sudo
98 And I run `ua status --wait` as non-root108 And I run `ua status --wait` as non-root
99 And I run `ua status --all` as non-root109 And I run `ua status` as non-root
110 Then stdout matches regexp:
111 """
112 SERVICE ENTITLED STATUS DESCRIPTION
113 esm-apps +yes +enabled +UA Apps: Extended Security Maintenance \(ESM\)
114 esm-infra +yes +enabled +UA Infra: Extended Security Maintenance \(ESM\)
115 fips +yes +<fips-s> +NIST-certified FIPS modules
116 fips-updates +yes +<fips-s> +Uncertified security updates to FIPS modules
117 livepatch +yes +enabled +Canonical Livepatch service
118 """
119 When I run `ua status --all` as non-root
100 Then stdout matches regexp:120 Then stdout matches regexp:
101 """121 """
102 SERVICE ENTITLED STATUS DESCRIPTION122 SERVICE ENTITLED STATUS DESCRIPTION
@@ -177,7 +197,17 @@ Feature: Command behaviour when attached to an UA subscription
177 """197 """
178 And I run `ua auto-attach` with sudo198 And I run `ua auto-attach` with sudo
179 And I run `ua status --wait` as non-root199 And I run `ua status --wait` as non-root
180 And I run `ua status --all` as non-root200 And I run `ua status` as non-root
201 Then stdout matches regexp:
202 """
203 SERVICE ENTITLED STATUS DESCRIPTION
204 esm-apps +yes +enabled +UA Apps: Extended Security Maintenance \(ESM\)
205 esm-infra +yes +enabled +UA Infra: Extended Security Maintenance \(ESM\)
206 fips +yes +<fips-s> +NIST-certified FIPS modules
207 fips-updates +yes +<fips-s> +Uncertified security updates to FIPS modules
208 livepatch +yes +enabled +Canonical Livepatch service
209 """
210 When I run `ua status --all` as non-root
181 Then stdout matches regexp:211 Then stdout matches regexp:
182 """212 """
183 SERVICE ENTITLED STATUS DESCRIPTION213 SERVICE ENTITLED STATUS DESCRIPTION
@@ -207,7 +237,7 @@ Feature: Command behaviour when attached to an UA subscription
207 https://esm.ubuntu.com/apps/ubuntu <release>-apps-security/main amd64 Packages237 https://esm.ubuntu.com/apps/ubuntu <release>-apps-security/main amd64 Packages
208 """238 """
209 And I verify that running `apt update` `with sudo` exits `0`239 And I verify that running `apt update` `with sudo` exits `0`
210 When I run `apt install -y <infra-pkg>/<release>-infra-security>` with sudo, retrying exit [100]240 When I run `apt install -y <infra-pkg>/<release>-infra-security` with sudo, retrying exit [100]
211 And I run `apt-cache policy <infra-pkg>` as non-root241 And I run `apt-cache policy <infra-pkg>` as non-root
212 Then stdout matches regexp:242 Then stdout matches regexp:
213 """243 """
@@ -218,7 +248,7 @@ Feature: Command behaviour when attached to an UA subscription
218 """248 """
219 Installed: .*[~+]esm249 Installed: .*[~+]esm
220 """250 """
221 When I run `apt install -y <apps-pkg>/<release>-apps-security>` with sudo, retrying exit [100]251 When I run `apt install -y <apps-pkg>/<release>-apps-security` with sudo, retrying exit [100]
222 And I run `apt-cache policy <apps-pkg>` as non-root252 And I run `apt-cache policy <apps-pkg>` as non-root
223 Then stdout matches regexp:253 Then stdout matches regexp:
224 """254 """
@@ -238,10 +268,10 @@ Feature: Command behaviour when attached to an UA subscription
238 """268 """
239269
240 Examples: ubuntu release270 Examples: ubuntu release
241 | release | cc-eal-s | cis-s | infra-pkg | apps-pkg |271 | release | fips-s | cc-eal-s | cis-s | infra-pkg | apps-pkg |
242 | xenial | disabled | disabled | libkrad0 | jq |272 | xenial | n/a | disabled | disabled | libkrad0 | jq |
243 | bionic | n/a | disabled | libkrad0 | bundler |273 | bionic | n/a | n/a | disabled | libkrad0 | bundler |
244 | focal | n/a | n/a | hello | ant |274 | focal | n/a | n/a | n/a | hello | ant |
245275
246 @series.trusty276 @series.trusty
247 @uses.config.machine_type.aws.pro277 @uses.config.machine_type.aws.pro
diff --git a/features/unattached_commands.feature b/features/unattached_commands.feature
index 0f73393..e2877ba 100644
--- a/features/unattached_commands.feature
+++ b/features/unattached_commands.feature
@@ -6,7 +6,7 @@ Feature: Command behaviour when unattached
6 When I verify that running `ua auto-attach` `as non-root` exits `1`6 When I verify that running `ua auto-attach` `as non-root` exits `1`
7 Then stderr matches regexp:7 Then stderr matches regexp:
8 """8 """
9 This command must be run as root \(try using sudo\)9 This command must be run as root \(try using sudo\).
10 """10 """
11 When I run `ua auto-attach` with sudo11 When I run `ua auto-attach` with sudo
12 Then stderr matches regexp:12 Then stderr matches regexp:
@@ -22,13 +22,93 @@ Feature: Command behaviour when unattached
22 | trusty | nocloudnet |22 | trusty | nocloudnet |
23 | xenial | lxd |23 | xenial | lxd |
2424
25 @series.trusty
26 @series.xenial
27 Scenario Outline: Disabled unattached APT policy apt-hook for infra and apps
28 Given a `<release>` machine with ubuntu-advantage-tools installed
29 When I run `apt update` with sudo
30 When I run `apt-cache policy` with sudo
31 Then if `<release>` in `trusty` and stdout matches regexp:
32 """
33 -32768 <esm-infra-url> <release>-infra-security/main amd64 Packages
34 """
35 Then if `<release>` in `xenial` and stdout matches regexp:
36 """
37 -32768 <esm-infra-url> <release>-infra-updates/main amd64 Packages
38 """
39 Then if `<release>` in `trusty or xenial` and stdout does not match regexp:
40 """
41 -32768 <esm-apps-url> <release>-apps-updates/main amd64 Packages
42 """
43 Then if `<release>` in `trusty or xenial` and stdout does not match regexp:
44 """
45 -32768 <esm-apps-url> <release>-apps-security/main amd64 Packages
46 """
47 When I append the following on uaclient config:
48 """
49 features:
50 allow_beta: true
51 """
52 And I run `dpkg-reconfigure ubuntu-advantage-tools` with sudo
53 And I run `apt-get update` with sudo
54 When I run `apt-cache policy` with sudo
55 Then if `<release>` in `trusty` and stdout does not match regexp:
56 """
57 -32768 <esm-apps-url> <release>-apps-updates/main amd64 Packages
58 """
59 Then if `<release>` in `trusty` and stdout does not match regexp:
60 """
61 -32768 <esm-apps-url> <release>-apps-security/main amd64 Packages
62 """
63 Then if `<release>` in `xenial` and stdout matches regexp:
64 """
65 -32768 <esm-apps-url> <release>-apps-updates/main amd64 Packages
66 """
67 Then if `<release>` in `xenial` and stdout matches regexp:
68 """
69 -32768 <esm-apps-url> <release>-apps-security/main amd64 Packages
70 """
71 When I append the following on uaclient config:
72 """
73 features:
74 allow_beta: true
75 """
76 And I run `python3 /usr/lib/ubuntu-advantage/ua_update_messaging.py` with sudo
77 And I run `run-parts /etc/update-motd.d/` with sudo
78 Then if `<release>` in `xenial` and stdout matches regexp:
79 """
80 \* Introducing Extended Security Maintenance for Applications.
81 +Receive updates to over 30,000 software packages with your
82 +Ubuntu Advantage subscription. Free for personal use.
83
84 +https:\/\/ubuntu.com\/esm
85
86 UA Infra: Extended Security Maintenance \(ESM\) is not enabled.
87 """
88 # Check that json hook is installed properly
89 When I run `ls /usr/lib/ubuntu-advantage` with sudo
90 Then stdout matches regexp:
91 """
92 apt-esm-json-hook
93 """
94 When I run `cat /etc/apt/apt.conf.d/20apt-esm-hook.conf` with sudo
95 Then stdout matches regexp:
96 """
97 apt-esm-json-hook
98 """
99
100 Examples: ubuntu release
101 | release | esm-infra-url | esm-apps-url |
102 | trusty | https://esm.ubuntu.com/ubuntu/ | NOTTESTED |
103 | xenial | https://esm.ubuntu.com/infra/ubuntu | https://esm.ubuntu.com/apps/ubuntu |
104
25 @series.all105 @series.all
26 Scenario Outline: Unattached commands that requires enabled user in a ubuntu machine106 Scenario Outline: Unattached commands that requires enabled user in a ubuntu machine
27 Given a `<release>` machine with ubuntu-advantage-tools installed107 Given a `<release>` machine with ubuntu-advantage-tools installed
28 When I verify that running `ua <command>` `as non-root` exits `1`108 When I verify that running `ua <command>` `as non-root` exits `1`
29 Then I will see the following on stderr:109 Then I will see the following on stderr:
30 """110 """
31 This command must be run as root (try using sudo)111 This command must be run as root (try using sudo).
32 """112 """
33 When I verify that running `ua <command>` `with sudo` exits `1`113 When I verify that running `ua <command>` `with sudo` exits `1`
34 Then stderr matches regexp:114 Then stderr matches regexp:
@@ -54,7 +134,7 @@ Feature: Command behaviour when unattached
54 When I verify that running `ua <command> <service>` `as non-root` exits `1`134 When I verify that running `ua <command> <service>` `as non-root` exits `1`
55 Then I will see the following on stderr:135 Then I will see the following on stderr:
56 """136 """
57 This command must be run as root (try using sudo)137 This command must be run as root (try using sudo).
58 """138 """
59 When I verify that running `ua <command> <service>` `with sudo` exits `1`139 When I verify that running `ua <command> <service>` `with sudo` exits `1`
60 Then stderr matches regexp:140 Then stderr matches regexp:
@@ -121,3 +201,308 @@ Feature: Command behaviour when unattached
121 | focal |201 | focal |
122 | trusty |202 | trusty |
123 | xenial |203 | xenial |
204
205 @series.focal
206 Scenario Outline: Fix command on an unattached machine
207 Given a `<release>` machine with ubuntu-advantage-tools installed
208 When I verify that running `ua fix CVE-1800-123456` `as non-root` exits `1`
209 Then I will see the following on stderr:
210 """
211 Error: CVE-1800-123456 not found.
212 """
213 When I verify that running `ua fix USN-12345-12` `as non-root` exits `1`
214 Then I will see the following on stderr:
215 """
216 Error: USN-12345-12 not found.
217 """
218 When I verify that running `ua fix CVE-12345678-12` `as non-root` exits `1`
219 Then I will see the following on stderr:
220 """
221 Error: issue "CVE-12345678-12" is not recognized.
222 Usage: "ua fix CVE-yyyy-nnnn" or "ua fix USN-nnnn"
223 """
224 When I verify that running `ua fix USN-12345678-12` `as non-root` exits `1`
225 Then I will see the following on stderr:
226 """
227 Error: issue "USN-12345678-12" is not recognized.
228 Usage: "ua fix CVE-yyyy-nnnn" or "ua fix USN-nnnn"
229 """
230 When I run `apt install -y libawl-php=0.60-1 --allow-downgrades` with sudo
231 And I run `ua fix USN-4539-1` with sudo
232 Then stdout matches regexp:
233 """
234 USN-4539-1: AWL vulnerability
235 Found CVEs:
236 https://ubuntu.com/security/CVE-2020-11728
237 1 affected package is installed: awl
238 \(1/1\) awl:
239 A fix is available in Ubuntu standard updates.
240 .*\{ apt update && apt install --only-upgrade -y libawl-php \}.*
241 .*✔.* USN-4539-1 is resolved.
242 """
243 When I run `ua fix CVE-2020-28196` as non-root
244 Then stdout matches regexp:
245 """
246 CVE-2020-28196: Kerberos vulnerability
247 https://ubuntu.com/security/CVE-2020-28196
248 1 affected package is installed: krb5
249 \(1/1\) krb5:
250 A fix is available in Ubuntu standard updates.
251 The update is already installed.
252 .*✔.* CVE-2020-28196 is resolved.
253 """
254
255 Examples: ubuntu release details
256 | release |
257 | focal |
258
259 @series.xenial
260 Scenario Outline: Fix command on an unattached machine
261 Given a `<release>` machine with ubuntu-advantage-tools installed
262 When I run `apt install -y libawl-php` with sudo
263 And I run `ua fix USN-4539-1` as non-root
264 Then stdout matches regexp:
265 """
266 USN-4539-1: AWL vulnerability
267 Found CVEs:
268 https://ubuntu.com/security/CVE-2020-11728
269 1 affected package is installed: awl
270 \(1/1\) awl:
271 Ubuntu security engineers are investigating this issue.
272 1 package is still affected: awl
273 .*✘.* USN-4539-1 is not resolved.
274 """
275 When I run `ua fix CVE-2020-28196` as non-root
276 Then stdout matches regexp:
277 """
278 CVE-2020-28196: Kerberos vulnerability
279 https://ubuntu.com/security/CVE-2020-28196
280 1 affected package is installed: krb5
281 \(1/1\) krb5:
282 A fix is available in Ubuntu standard updates.
283 The update is already installed.
284 .*✔.* CVE-2020-28196 is resolved.
285 """
286 When I run `DEBIAN_FRONTEND=noninteractive apt-get install -y expat=2.1.0-7 swish-e matanza ghostscript` with sudo
287 And I verify that running `ua fix CVE-2017-9233` `with sudo` exits `1`
288 Then stdout matches regexp:
289 """
290 CVE-2017-9233: Expat vulnerability
291 https://ubuntu.com/security/CVE-2017-9233
292 3 affected packages are installed: expat, matanza, swish-e
293 \(1/3, 2/3\) matanza, swish-e:
294 Ubuntu security engineers are investigating this issue.
295 """
296 And stderr matches regexp:
297 """
298 Error: CVE-2017-9233 metadata defines no fixed version for expat.
299 3 packages are still affected: expat, matanza, swish-e
300 .*✘.* CVE-2017-9233 is not resolved.
301 """
302
303 Examples: ubuntu release details
304 | release |
305 | xenial |
306
307 @uses.config.contract_token
308 @series.trusty
309 Scenario Outline: Fix command on an unattached machine
310 Given a `<release>` machine with ubuntu-advantage-tools installed
311 When I run `ua fix USN-4539-1` as non-root
312 Then stdout matches regexp:
313 """
314 USN-4539-1: AWL vulnerability
315 Found CVEs:
316 https://ubuntu.com/security/CVE-2020-11728
317 No affected packages are installed.
318 .*✔.* USN-4539-1 does not affect your system.
319 """
320 When I run `ua fix CVE-2020-15180` as non-root
321 Then stdout matches regexp:
322 """
323 CVE-2020-15180: MariaDB vulnerabilities
324 https://ubuntu.com/security/CVE-2020-15180
325 No affected packages are installed.
326 .*✔.* CVE-2020-15180 does not affect your system.
327 """
328 When I run `ua fix CVE-2020-28196` as non-root
329 Then stdout matches regexp:
330 """
331 CVE-2020-28196: Kerberos vulnerability
332 https://ubuntu.com/security/CVE-2020-28196
333 1 affected package is installed: krb5
334 \(1/1\) krb5:
335 A fix is available in UA Infra.
336 Package fixes cannot be installed.
337 To install them, run this command as root \(try using sudo\)
338 1 package is still affected: krb5
339 .*✘.* CVE-2020-28196 is not resolved.
340 """
341 When I fix `USN-4747-2` by attaching to a subscription with `contract_token`
342 Then stdout matches regexp:
343 """
344 USN-4747-2: GNU Screen vulnerability
345 Found CVEs:
346 https://ubuntu.com/security/CVE-2021-26937
347 1 affected package is installed: screen
348 \(1/1\) screen:
349 A fix is available in UA Infra.
350 The update is not installed because this system is not attached to a
351 subscription.
352
353 Choose: \[S\]ubscribe at ubuntu.com \[A\]ttach existing token \[C\]ancel
354 > Enter your token \(from https://ubuntu.com/advantage\) to attach this system:
355 > .*\{ ua attach .*\}.*
356 Updating package lists
357 UA Infra: ESM enabled
358 """
359 And stdout matches regexp:
360 """
361 .*\{ apt update && apt install --only-upgrade -y screen \}.*
362 .*✔.* USN-4747-2 is resolved.
363 """
364 When I run `apt-get install -y screen=4.1.0~20120320gitdb59704-9 --force-yes` with sudo
365 And I run `ua disable esm-infra` with sudo
366 And I fix `USN-4747-2` by enabling required service
367 Then stdout matches regexp:
368 """
369 USN-4747-2: GNU Screen vulnerability
370 Found CVEs:
371 https://ubuntu.com/security/CVE-2021-26937
372 1 affected package is installed: screen
373 \(1/1\) screen:
374 A fix is available in UA Infra.
375 The update is not installed because this system does not have
376 esm-infra enabled.
377
378 Choose: \[E\]nable esm-infra \[C\]ancel
379 > .*\{ ua enable esm-infra \}.*
380 One moment, checking your subscription first
381 Updating package lists
382 UA Infra: ESM enabled
383 """
384 And stdout matches regexp:
385 """
386 .*\{ apt update && apt install --only-upgrade -y screen \}.*
387 .*✔.* USN-4747-2 is resolved.
388 """
389 When I run `apt-get install -y screen=4.1.0~20120320gitdb59704-9 --force-yes` with sudo
390 And I update contract to use `effectiveTo` as `1999-12-01T00:00:00Z`
391 And I fix `USN-4747-2` by updating expired token
392 Then stdout matches regexp:
393 """
394 USN-4747-2: GNU Screen vulnerability
395 Found CVEs:
396 https://ubuntu.com/security/CVE-2021-26937
397 1 affected package is installed: screen
398 \(1/1\) screen:
399 A fix is available in UA Infra.
400 The update is not installed because this system is attached to an
401 expired subscription.
402
403 Choose: \[R\]enew your subscription \(at https://ubuntu.com/advantage\) \[C\]ancel
404 > Enter your new token to renew UA subscription on this system:
405 > .*\{ ua detach \}.*
406 Detach will disable the following service:
407 esm-infra
408 Updating package lists
409 This machine is now detached.
410 .*\{ ua attach .* \}.*
411 Updating package lists
412 UA Infra: ESM enabled
413 """
414 And stdout matches regexp:
415 """
416 .*\{ apt update && apt install --only-upgrade -y screen \}.*
417 .*✔.* USN-4747-2 is resolved.
418 """
419
420 Examples: ubuntu release
421 | release |
422 | trusty |
423
424 @series.bionic
425 Scenario: Fix command on an unattached machine
426 Given a `bionic` machine with ubuntu-advantage-tools installed
427 When I verify that running `ua fix CVE-1800-123456` `as non-root` exits `1`
428 Then I will see the following on stderr:
429 """
430 Error: CVE-1800-123456 not found.
431 """
432 When I verify that running `ua fix USN-12345-12` `as non-root` exits `1`
433 Then I will see the following on stderr:
434 """
435 Error: USN-12345-12 not found.
436 """
437 When I verify that running `ua fix CVE-12345678-12` `as non-root` exits `1`
438 Then I will see the following on stderr:
439 """
440 Error: issue "CVE-12345678-12" is not recognized.
441 Usage: "ua fix CVE-yyyy-nnnn" or "ua fix USN-nnnn"
442 """
443 When I verify that running `ua fix USN-12345678-12` `as non-root` exits `1`
444 Then I will see the following on stderr:
445 """
446 Error: issue "USN-12345678-12" is not recognized.
447 Usage: "ua fix CVE-yyyy-nnnn" or "ua fix USN-nnnn"
448 """
449 When I run `apt install -y libawl-php` with sudo
450 And I run `ua fix USN-4539-1` as non-root
451 Then stdout matches regexp:
452 """
453 USN-4539-1: AWL vulnerability
454 Found CVEs:
455 https://ubuntu.com/security/CVE-2020-11728
456 1 affected package is installed: awl
457 \(1/1\) awl:
458 Ubuntu security engineers are investigating this issue.
459 1 package is still affected: awl
460 .*✘.* USN-4539-1 is not resolved.
461 """
462 When I run `ua fix CVE-2020-28196` as non-root
463 Then stdout matches regexp:
464 """
465 CVE-2020-28196: Kerberos vulnerability
466 https://ubuntu.com/security/CVE-2020-28196
467 1 affected package is installed: krb5
468 \(1/1\) krb5:
469 A fix is available in Ubuntu standard updates.
470 The update is already installed.
471 .*✔.* CVE-2020-28196 is resolved.
472 """
473 When I run `apt-get install xterm=330-1ubuntu2 -y` with sudo
474 And I run `ua fix CVE-2021-27135` as non-root
475 Then stdout matches regexp:
476 """
477 CVE-2021-27135: xterm vulnerability
478 https://ubuntu.com/security/CVE-2021-27135
479 1 affected package is installed: xterm
480 \(1/1\) xterm:
481 A fix is available in Ubuntu standard updates.
482 Package fixes cannot be installed.
483 To install them, run this command as root \(try using sudo\)
484 1 package is still affected: xterm
485 .*✘.* CVE-2021-27135 is not resolved.
486 """
487 When I run `ua fix CVE-2021-27135` with sudo
488 Then stdout matches regexp:
489 """
490 CVE-2021-27135: xterm vulnerability
491 https://ubuntu.com/security/CVE-2021-27135
492 1 affected package is installed: xterm
493 \(1/1\) xterm:
494 A fix is available in Ubuntu standard updates.
495 .*\{ apt update && apt install --only-upgrade -y xterm \}.*
496 .*✔.* CVE-2021-27135 is resolved.
497 """
498 When I run `ua fix CVE-2021-27135` with sudo
499 Then stdout matches regexp:
500 """
501 CVE-2021-27135: xterm vulnerability
502 https://ubuntu.com/security/CVE-2021-27135
503 1 affected package is installed: xterm
504 \(1/1\) xterm:
505 A fix is available in Ubuntu standard updates.
506 The update is already installed.
507 .*✔.* CVE-2021-27135 is resolved.
508 """
diff --git a/features/util.py b/features/util.py
index 8af945c..cacd992 100644
--- a/features/util.py
+++ b/features/util.py
@@ -18,33 +18,6 @@ LXC_PROPERTY_MAP = {
18SLOW_CMDS = ["do-release-upgrade"] # Commands which will emit dots on travis18SLOW_CMDS = ["do-release-upgrade"] # Commands which will emit dots on travis
19SOURCE_PR_TGZ = os.path.join(tempfile.gettempdir(), "pr_source.tar.gz")19SOURCE_PR_TGZ = os.path.join(tempfile.gettempdir(), "pr_source.tar.gz")
20UA_DEBS = frozenset({"ubuntu-advantage-tools.deb", "ubuntu-advantage-pro.deb"})20UA_DEBS = frozenset({"ubuntu-advantage-tools.deb", "ubuntu-advantage-pro.deb"})
21VM_PROFILE_TMPL = "behave-{}"
22
23
24# For Xenial and Bionic vendor-data required to setup lxd-agent
25# Additionally xenial needs to launch images:ubuntu/16.04/cloud
26# because it contains the HWE kernel which has vhost-vsock support
27LXC_SETUP_VENDORDATA = textwrap.dedent(
28 """\
29 config:
30 user.vendor-data: |
31 #cloud-config
32 {custom_cfg}
33 write_files:
34 - path: /var/lib/cloud/scripts/per-once/setup-lxc.sh
35 encoding: b64
36 permissions: '0755'
37 owner: root:root
38 content: |
39 IyEvYmluL3NoCmlmICEgZ3JlcCBseGRfY29uZmlnIC9wcm9jL21vdW50czsgdGhlbgogICAgbWtk
40 aXIgLXAgL3J1bi9seGRhZ2VudAogICAgbW91bnQgLXQgOXAgY29uZmlnIC9ydW4vbHhkYWdlbnQK
41 ICAgIFZJUlQ9JChzeXN0ZW1kLWRldGVjdC12aXJ0KQogICAgY2FzZSAkVklSVCBpbgogICAgICAg
42 IHFlbXV8a3ZtKQogICAgICAgICAgICAoY2QgL3J1bi9seGRhZ2VudC8gJiYgLi9pbnN0YWxsLnNo
43 KQogICAgICAgICAgICB1bW91bnQgL3J1bi9seGRhZ2VudAogICAgICAgICAgICBzeXN0ZW1jdGwg
44 c3RhcnQgbHhkLWFnZW50LTlwIGx4ZC1hZ2VudAogICAgICAgICAgICA7OwogICAgICAgICopCiAg
45 ICBlc2FjCmZpCg==
46 """
47)
4821
4922
50BUILD_FROM_TGZ = textwrap.dedent(23BUILD_FROM_TGZ = textwrap.dedent(
@@ -65,60 +38,6 @@ BUILD_FROM_TGZ = textwrap.dedent(
65)38)
6639
6740
68def lxc_create_vm_profile(series: str):
69 """Create a vm profile to enable launching kvm instances"""
70
71 content_tmpl = textwrap.dedent(
72 """\
73 {vendordata}
74 description: Default LXD profile for {series} VMs
75 devices:
76 config:
77 source: cloud-init:config
78 type: disk
79 eth0:
80 name: eth0
81 network: lxdbr0
82 type: nic
83 root:
84 path: /
85 pool: default
86 type: disk
87 name: vm
88 """
89 )
90 if series == "xenial":
91 # FIXME: Xenial images from images:ubuntu/16.04/cloud have HWE kernel
92 # but no openssh-server (which fips testing would expect)
93 # Work with CPC to get vhost-vsock support if possible to use
94 # ubuntu-daily:xenial images
95 content = content_tmpl.format(
96 vendordata=LXC_SETUP_VENDORDATA.format(
97 custom_cfg="packages: [openssh-server]"
98 ),
99 series=series,
100 )
101 elif series == "bionic":
102 content = content_tmpl.format(
103 vendordata=LXC_SETUP_VENDORDATA.format(custom_cfg=""),
104 series=series,
105 )
106 elif series == "focal":
107 content = content_tmpl.format(vendordata="config: {}", series=series)
108 else:
109 raise RuntimeError(
110 "===No lxc mv support for series {}====".format(series)
111 )
112 output = subprocess.check_output(["lxc", "profile", "list"])
113 profile_name = VM_PROFILE_TMPL.format(series)
114 if " {} ".format(profile_name) not in output.decode("utf-8"):
115 subprocess.run(["lxc", "profile", "create", profile_name])
116 proc = subprocess.Popen(
117 ["lxc", "profile", "edit", profile_name], stdin=subprocess.PIPE
118 )
119 proc.communicate(content.encode())
120
121
122def lxc_get_property(name: str, property_name: str, image: bool = False):41def lxc_get_property(name: str, property_name: str, image: bool = False):
123 """Check series name of either an image or a container.42 """Check series name of either an image or a container.
12443
diff --git a/integration-requirements.txt b/integration-requirements.txt
index e29790e..b4e36d7 100644
--- a/integration-requirements.txt
+++ b/integration-requirements.txt
@@ -1,7 +1,7 @@
1# Integration testing1# Integration testing
2behave2behave
3PyHamcrest3PyHamcrest
4pycloudlib @ git+https://github.com/canonical/pycloudlib.git@1ac9d4c82fdfd5cb1407f70b8a2b17e02953569d4pycloudlib @ git+https://github.com/canonical/pycloudlib.git@cab5db70bdd5588aabcf7217e655ff90d2dee487
55
66
7# Simplestreams is not found on PyPi so pull from repo directly7# Simplestreams is not found on PyPi so pull from repo directly
diff --git a/lib/__init__.py b/lib/__init__.py
8deleted file mode 1006448deleted file mode 100644
index e69de29..0000000
--- a/lib/__init__.py
+++ /dev/null
diff --git a/lib/reboot_cmds.py b/lib/reboot_cmds.py
index 6a753d5..7cc5dfd 100644
--- a/lib/reboot_cmds.py
+++ b/lib/reboot_cmds.py
@@ -120,15 +120,19 @@ def process_reboot_operations(args, cfg):
120 if os.path.exists(reboot_cmd_marker_file):120 if os.path.exists(reboot_cmd_marker_file):
121 logging.debug("Running process contract deltas on reboot ...")121 logging.debug("Running process contract deltas on reboot ...")
122122
123 fix_pro_pkg_holds(cfg)123 try:
124 refresh_contract(cfg)124 fix_pro_pkg_holds(cfg)
125 process_remaining_deltas(cfg)125 refresh_contract(cfg)
126126 process_remaining_deltas(cfg)
127 cfg.delete_cache_key("marker-reboot-cmds")
128127
129 logging.debug(128 cfg.delete_cache_key("marker-reboot-cmds")
130 "Completed running process contract deltas on reboot ..."129 cfg.remove_notice("", status.MESSAGE_REBOOT_SCRIPT_FAILED)
131 )130 logging.debug("Successfully ran all commands on reboot.")
131 except Exception as e:
132 msg = "Failed running commands on reboot."
133 msg += str(e)
134 logging.error(msg)
135 cfg.add_notice("", status.MESSAGE_REBOOT_SCRIPT_FAILED)
132136
133137
134def main(cfg):138def main(cfg):
diff --git a/lib/ua_update_messaging.py b/lib/ua_update_messaging.py
135new file mode 100644139new file mode 100644
index 0000000..d8e645e
--- /dev/null
+++ b/lib/ua_update_messaging.py
@@ -0,0 +1,302 @@
1#!/usr/bin/env python3
2
3"""
4Update messaging text for use in MOTD and APT custom Ubuntu Advantage messages.
5
6Messaging files will be emitted to /var/lib/ubuntu-advantage/message-* which
7will be sourced by apt-hook/hook.cc and various /etc/update-motd.d/ hooks to
8present updated text about Ubuntu Advantage service and token state.
9"""
10
11import enum
12import logging
13import os
14
15try:
16 from typing import Dict, List, Optional, Tuple # noqa
17except ImportError:
18 # typing isn't available on trusty, so ignore its absence
19 pass
20
21from uaclient.cli import setup_logging
22from uaclient import config
23from uaclient import entitlements
24from uaclient import defaults
25from uaclient.status import (
26 MESSAGE_ANNOUNCE_ESM,
27 MESSAGE_CONTRACT_EXPIRED_APT_NO_PKGS_TMPL,
28 MESSAGE_CONTRACT_EXPIRED_APT_PKGS_TMPL,
29 MESSAGE_CONTRACT_EXPIRED_GRACE_PERIOD_TMPL,
30 MESSAGE_CONTRACT_EXPIRED_MOTD_PKGS_TMPL,
31 MESSAGE_CONTRACT_EXPIRED_SOON_TMPL,
32 MESSAGE_DISABLED_MOTD_NO_PKGS_TMPL,
33 MESSAGE_DISABLED_APT_PKGS_TMPL,
34 MESSAGE_UBUNTU_NO_WARRANTY,
35 ApplicationStatus,
36)
37from uaclient import util
38
39
40@enum.unique
41class ContractExpiryStatus(enum.Enum):
42 NONE = 0
43 ACTIVE = 1
44 ACTIVE_EXPIRED_SOON = 2
45 EXPIRED_GRACE_PERIOD = 3
46 EXPIRED = 4
47
48
49# Type of message file used for external messaging (APT and MOTD)
50@enum.unique
51class ExternalMessage(enum.Enum):
52 MOTD_APPS_NO_PKGS = "motd-no-packages-apps.tmpl"
53 MOTD_INFRA_NO_PKGS = "motd-no-packages-infra.tmpl"
54 MOTD_APPS_PKGS = "motd-packages-apps.tmpl"
55 MOTD_INFRA_PKGS = "motd-packages-infra.tmpl"
56 APT_PRE_INVOKE_APPS_NO_PKGS = "apt-pre-invoke-no-packages-apps.tmpl"
57 APT_PRE_INVOKE_INFRA_NO_PKGS = "apt-pre-invoke-no-packages-infra.tmpl"
58 APT_PRE_INVOKE_APPS_PKGS = "apt-pre-invoke-packages-apps.tmpl"
59 APT_PRE_INVOKE_INFRA_PKGS = "apt-pre-invoke-packages-infra.tmpl"
60 APT_PRE_INVOKE_SERVICE_STATUS = "apt-pre-invoke-esm-service-status"
61 MOTD_ESM_SERVICE_STATUS = "motd-esm-service-status"
62 ESM_ANNOUNCE = "motd-esm-announce"
63 UBUNTU_NO_WARRANTY = "ubuntu-no-warranty"
64
65
66def get_contract_expiry_status(
67 cfg: config.UAConfig
68) -> "Tuple[ContractExpiryStatus, int]":
69 """Return a tuple [ContractExpiryStatus, num_days]"""
70 if not cfg.is_attached:
71 return ContractExpiryStatus.NONE, 0
72
73 grace_period = defaults.CONTRACT_EXPIRY_GRACE_PERIOD_DAYS
74 pending_expiry = defaults.CONTRACT_EXPIRY_PENDING_DAYS
75 remaining_days = cfg.contract_remaining_days
76 if 0 <= remaining_days <= pending_expiry:
77 return ContractExpiryStatus.ACTIVE_EXPIRED_SOON, remaining_days
78 elif -grace_period <= remaining_days < 0:
79 return ContractExpiryStatus.EXPIRED_GRACE_PERIOD, remaining_days
80 elif remaining_days < -grace_period:
81 return ContractExpiryStatus.EXPIRED, remaining_days
82 return ContractExpiryStatus.ACTIVE, remaining_days
83
84
85def _write_template_or_remove(msg: str, tmpl_file: str):
86 """Write a template to tmpl_file.
87
88 When msg is empty, remove both tmpl_file and the generated msg.
89 """
90 if msg:
91 util.write_file(tmpl_file, msg)
92 else:
93 util.remove_file(tmpl_file)
94 if tmpl_file.endswith(".tmpl"):
95 util.remove_file(tmpl_file.replace(".tmpl", ""))
96
97
98def _write_esm_service_msg_templates(
99 cfg: config.UAConfig,
100 ent: entitlements.base.UAEntitlement,
101 expiry_status: ContractExpiryStatus,
102 remaining_days: int,
103 pkgs_file: str,
104 no_pkgs_file: str,
105 motd_pkgs_file: str,
106 motd_no_pkgs_file: str,
107 no_warranty_file: str,
108):
109
110 pkgs_msg = no_pkgs_msg = motd_pkgs_msg = motd_no_pkgs_msg = ""
111 no_warranty_msg = ""
112 tmpl_prefix = ent.name.upper().replace("-", "_")
113 tmpl_pkg_count_var = "{{{}_PKG_COUNT}}".format(tmpl_prefix)
114 tmpl_pkg_names_var = "{{{}_PACKAGES}}".format(tmpl_prefix)
115 if ent.application_status()[0] == ApplicationStatus.ENABLED:
116 if expiry_status == ContractExpiryStatus.ACTIVE_EXPIRED_SOON:
117 pkgs_msg = MESSAGE_CONTRACT_EXPIRED_SOON_TMPL.format(
118 title=ent.title,
119 remaining_days=remaining_days,
120 url=defaults.BASE_UA_URL,
121 )
122 # Same cautionary message when contract is about to expire
123 motd_pkgs_msg = motd_no_pkgs_msg = no_pkgs_msg = pkgs_msg
124 elif expiry_status == ContractExpiryStatus.EXPIRED_GRACE_PERIOD:
125 grace_period_remaining = (
126 defaults.CONTRACT_EXPIRY_GRACE_PERIOD_DAYS + remaining_days
127 )
128 pkgs_msg = MESSAGE_CONTRACT_EXPIRED_GRACE_PERIOD_TMPL.format(
129 title=ent.title,
130 expired_date=cfg.contract_expiry_datetime.strftime("%d %b %Y"),
131 remaining_days=grace_period_remaining,
132 url=defaults.BASE_UA_URL,
133 )
134 # Same cautionary message when in grace period
135 motd_pkgs_msg = motd_no_pkgs_msg = no_pkgs_msg = pkgs_msg
136 elif expiry_status == ContractExpiryStatus.EXPIRED:
137 if util.is_active_esm(util.get_platform_info()["series"]):
138 no_warranty_msg = MESSAGE_UBUNTU_NO_WARRANTY
139 pkgs_msg = MESSAGE_CONTRACT_EXPIRED_APT_PKGS_TMPL.format(
140 pkg_num=tmpl_pkg_count_var,
141 pkg_names=tmpl_pkg_names_var,
142 title=ent.title,
143 name=ent.name,
144 url=defaults.BASE_UA_URL,
145 )
146 no_pkgs_msg = MESSAGE_CONTRACT_EXPIRED_APT_NO_PKGS_TMPL.format(
147 title=ent.title, url=defaults.BASE_ESM_URL
148 )
149 motd_no_pkgs_msg = no_pkgs_msg
150 motd_pkgs_msg = MESSAGE_CONTRACT_EXPIRED_MOTD_PKGS_TMPL.format(
151 title=ent.title,
152 pkg_num=tmpl_pkg_count_var,
153 url=defaults.BASE_ESM_URL,
154 )
155 elif expiry_status != ContractExpiryStatus.EXPIRED: # Service not enabled
156 pkgs_msg = MESSAGE_DISABLED_APT_PKGS_TMPL.format(
157 title=ent.title,
158 pkg_num=tmpl_pkg_count_var,
159 pkg_names=tmpl_pkg_names_var,
160 url=defaults.BASE_ESM_URL,
161 )
162 no_pkgs_msg = MESSAGE_DISABLED_MOTD_NO_PKGS_TMPL.format(
163 title=ent.title, url=defaults.BASE_ESM_URL
164 )
165
166 msg_dir = os.path.join(cfg.data_dir, "messages")
167 _write_template_or_remove(
168 no_warranty_msg, os.path.join(msg_dir, no_warranty_file)
169 )
170 _write_template_or_remove(no_pkgs_msg, os.path.join(msg_dir, no_pkgs_file))
171 _write_template_or_remove(pkgs_msg, os.path.join(msg_dir, pkgs_file))
172 _write_template_or_remove(
173 motd_no_pkgs_msg, os.path.join(msg_dir, motd_no_pkgs_file)
174 )
175 _write_template_or_remove(
176 motd_pkgs_msg, os.path.join(msg_dir, motd_pkgs_file)
177 )
178
179
180def write_apt_and_motd_templates(cfg: config.UAConfig, series: str) -> None:
181 """Write messaging templates about available esm packages.
182
183 :param cfg: UAConfig instance for this environment.
184 :param series: string of Ubuntu release series: 'xenial'.
185 """
186 apps_no_pkg_file = ExternalMessage.APT_PRE_INVOKE_APPS_NO_PKGS.value
187 apps_pkg_file = ExternalMessage.APT_PRE_INVOKE_APPS_PKGS.value
188 infra_no_pkg_file = ExternalMessage.APT_PRE_INVOKE_INFRA_NO_PKGS.value
189 infra_pkg_file = ExternalMessage.APT_PRE_INVOKE_INFRA_PKGS.value
190 motd_apps_no_pkg_file = ExternalMessage.MOTD_APPS_NO_PKGS.value
191 motd_apps_pkg_file = ExternalMessage.MOTD_APPS_PKGS.value
192 motd_infra_no_pkg_file = ExternalMessage.MOTD_INFRA_NO_PKGS.value
193 motd_infra_pkg_file = ExternalMessage.MOTD_INFRA_PKGS.value
194 no_warranty_file = ExternalMessage.UBUNTU_NO_WARRANTY.value
195
196 apps_cls = entitlements.ENTITLEMENT_CLASS_BY_NAME["esm-apps"]
197 apps_inst = apps_cls(cfg)
198 config_allow_beta = util.is_config_value_true(
199 config=cfg.cfg, path_to_value="features.allow_beta"
200 )
201 apps_not_beta = bool(config_allow_beta or not apps_cls.is_beta)
202 infra_cls = entitlements.ENTITLEMENT_CLASS_BY_NAME["esm-infra"]
203 infra_inst = infra_cls(cfg)
204
205 expiry_status, remaining_days = get_contract_expiry_status(cfg)
206
207 if series != "trusty" and apps_not_beta:
208 _write_esm_service_msg_templates(
209 cfg,
210 apps_inst,
211 expiry_status,
212 remaining_days,
213 apps_pkg_file,
214 apps_no_pkg_file,
215 motd_apps_pkg_file,
216 motd_apps_no_pkg_file,
217 no_warranty_file,
218 )
219
220 # We only have esm-infra apt alerts for esm distros.
221 # However, if we have expired credentials, we will
222 # produce esm-infra message showing that the contract is
223 # expiring/expired.
224 infra_status, _ = infra_inst.application_status()
225 is_infra_enabled = infra_status == ApplicationStatus.ENABLED
226 if is_infra_enabled or util.is_active_esm(series):
227 _write_esm_service_msg_templates(
228 cfg,
229 infra_inst,
230 expiry_status,
231 remaining_days,
232 infra_pkg_file,
233 infra_no_pkg_file,
234 motd_infra_pkg_file,
235 motd_infra_no_pkg_file,
236 no_warranty_file,
237 )
238
239
240def write_esm_announcement_message(cfg: config.UAConfig, series: str) -> None:
241 """Write human-readable messages if ESM is offered on this LTS release.
242
243 Do not write ESM announcements on trusty, esm-apps is enable or beta.
244
245 :param cfg: UAConfig instance for this environment.
246 :param series: string of Ubuntu release series: 'xenial'.
247 """
248 apps_cls = entitlements.ENTITLEMENT_CLASS_BY_NAME["esm-apps"]
249 apps_inst = apps_cls(cfg)
250 enabled_status = ApplicationStatus.ENABLED
251 apps_not_enabled = apps_inst.application_status()[0] != enabled_status
252 config_allow_beta = util.is_config_value_true(
253 config=cfg.cfg, path_to_value="features.allow_beta"
254 )
255 apps_not_beta = bool(config_allow_beta or not apps_cls.is_beta)
256
257 msg_dir = os.path.join(cfg.data_dir, "messages")
258 esm_news_file = os.path.join(msg_dir, ExternalMessage.ESM_ANNOUNCE.value)
259 if all([series != "trusty", apps_not_beta, apps_not_enabled]):
260 util.write_file(esm_news_file, "\n" + MESSAGE_ANNOUNCE_ESM)
261 else:
262 util.remove_file(esm_news_file)
263
264
265def update_apt_and_motd_messages(cfg: config.UAConfig) -> None:
266 """Emit templates and human-readable status messages in msg_dir.
267
268 These structured messages will be sourced by both /etc/update.motd.d
269 and APT UA-configured hooks. APT hook content will orginate from
270 apt-hook/hook.cc
271
272 Call esm-apt-hook process-templates to render final human-readable
273 messages.
274
275 :param cfg: UAConfig instance for this environment.
276 """
277 setup_logging(logging.INFO, logging.DEBUG)
278 logging.debug("Updating UA messages for APT and MOTD.")
279 msg_dir = os.path.join(cfg.data_dir, "messages")
280 if not os.path.exists(msg_dir):
281 os.makedirs(msg_dir)
282
283 series = util.get_platform_info()["series"]
284 if not util.is_lts(series):
285 # ESM is only on LTS releases. Remove all messages and templates.
286 for msg_enum in ExternalMessage:
287 msg_path = os.path.join(msg_dir, msg_enum.value)
288 util.remove_file(msg_path)
289 if msg_path.endswith(".tmpl"):
290 util.remove_file(msg_path.replace(".tmpl", ""))
291 return
292
293 # Announce ESM availabilty on active ESM LTS releases
294 write_esm_announcement_message(cfg, series)
295 write_apt_and_motd_templates(cfg, series)
296 # Now that we've setup/cleanedup templates render them with apt-hook
297 util.subp(["/usr/lib/ubuntu-advantage/apt-esm-hook", "process-templates"])
298
299
300if __name__ == "__main__":
301 cfg = config.UAConfig()
302 update_apt_and_motd_messages(cfg=cfg)
diff --git a/setup.py b/setup.py
index a8a597c..0702d03 100644
--- a/setup.py
+++ b/setup.py
@@ -40,6 +40,7 @@ def _get_version():
40def _get_data_files():40def _get_data_files():
41 data_files = [41 data_files = [
42 ("/etc/ubuntu-advantage", ["uaclient.conf", "help_data.yaml"]),42 ("/etc/ubuntu-advantage", ["uaclient.conf", "help_data.yaml"]),
43 ("/etc/update-motd.d", glob.glob("update-motd.d/*")),
43 ("/usr/lib/ubuntu-advantage", glob.glob("lib/[!_]*")),44 ("/usr/lib/ubuntu-advantage", glob.glob("lib/[!_]*")),
44 ("/usr/share/keyrings", glob.glob("keyrings/*")),45 ("/usr/share/keyrings", glob.glob("keyrings/*")),
45 (46 (
diff --git a/systemd/ua-messaging.service b/systemd/ua-messaging.service
46new file mode 10064447new file mode 100644
index 0000000..7bc57d2
--- /dev/null
+++ b/systemd/ua-messaging.service
@@ -0,0 +1,8 @@
1[Unit]
2Description=Ubuntu Advantage APT and MOTD Messages
3After=network.target network-online.target systemd-networkd.service ua-auto-attach.service
4Wants=ua-auto-attach.service
5
6[Service]
7Type=oneshot
8ExecStart=/usr/bin/python3 /usr/lib/ubuntu-advantage/ua_update_messaging.py
diff --git a/systemd/ua-messaging.timer b/systemd/ua-messaging.timer
0new file mode 1006449new file mode 100644
index 0000000..fee91b7
--- /dev/null
+++ b/systemd/ua-messaging.timer
@@ -0,0 +1,11 @@
1[Unit]
2Description=Ubuntu Advantage update messaging
3
4[Timer]
5OnCalendar=*-*-* 3,15:00
6RandomizedDelaySec=1h
7Persistent=true
8OnStartupSec=1min
9
10[Install]
11WantedBy=timers.target
diff --git a/tools/test_xenial_upgrade.sh b/tools/test_xenial_upgrade.sh
0new file mode 10064412new file mode 100644
index 0000000..691581b
--- /dev/null
+++ b/tools/test_xenial_upgrade.sh
@@ -0,0 +1,224 @@
1#!/usr/bin/bash
2
3set -x
4set -e
5name=test-xenial-ua-upgrade
6
7function h1() {
8 set +x
9 echo ""
10 echo ""
11 echo ""
12 echo "############################################################################"
13 echo "## $1"
14 echo "############################################################################"
15 echo ""
16 set -x
17}
18function h2() {
19 set +x
20 echo ""
21 echo ""
22 echo "-> $1"
23 echo "----------------------------------------------------------------------------"
24 echo ""
25 set -x
26}
27
28
29function setup() {
30 tool=$1
31
32 h2 "Make sure we're up to date"
33 $tool exec $name -- sudo apt update
34 $tool exec $name -- sudo apt upgrade -y
35 $tool exec $name -- sudo apt install ubuntu-advantage-tools -y
36
37 h2 "Initial State"
38 $tool exec $name -- apt-cache policy ubuntu-advantage-tools
39 $tool exec $name -- sudo ubuntu-advantage status
40 $tool exec $name -- dpkg-query -L ubuntu-advantage-tools
41}
42function setup_container() {
43 h1 "Setting up fresh container"
44 lxc delete --force $name || true
45 lxc launch ubuntu-daily:xenial $name
46 sleep 10
47
48 setup lxc
49}
50function setup_vm() {
51 h1 "Setting up fresh vm"
52 multipass delete -p $name || true
53 multipass launch -n $name xenial
54 sleep 10
55
56 setup multipass
57}
58function teardown_container() {
59 lxc delete --force $name || true
60}
61function teardown_vm() {
62 multipass delete -p $name || true
63}
64
65function install_new_ua() {
66 tool=$1
67 h2 "Set up to use daily ppa"
68 $tool exec $name -- sudo add-apt-repository ppa:ua-client/daily -y
69 $tool exec $name -- sudo apt update
70
71 h2 "Actually install - verify there are no errors"
72 $tool exec $name -- sudo apt install ubuntu-advantage-tools -y
73}
74
75function attach_ua() {
76 tool=$1
77 set +x
78 echo "+ $tool exec $name -- sudo ua attach \$UACLIENT_BEHAVE_CONTRACT_TOKEN"
79 $tool exec $name -- sudo ua attach $UACLIENT_BEHAVE_CONTRACT_TOKEN
80 set -x
81}
82
83
84
85
86function test_upgrade_in_container() {
87 setup_container
88
89 h1 "Upgrade to UA 27 while unattached"
90
91 install_new_ua lxc
92
93 h2 "Check for leftover files from old version - verify nothing unexpected is left behind"
94 lxc exec $name -- ls -l /etc/update-motd.d/99-esm /usr/share/keyrings/ubuntu-esm-keyring.gpg /usr/share/keyrings/ubuntu-fips-keyring.gpg /usr/share/man/man1/ubuntu-advantage.1.gz /usr/share/doc/ubuntu-advantage-tools/copyright /usr/share/doc/ubuntu-advantage-tools/changelog.gz /usr/bin/ubuntu-advantage || true
95
96 h2 "New Status - verify esm-infra available but not enabled; esm-apps not visible"
97 lxc exec $name -- ua status
98
99 h2 "Attach - verify esm-infra automatically enabled; esm-apps not visible"
100 attach_ua lxc
101
102 h2 "Detaching before destruction"
103 lxc exec $name -- ua detach --assume-yes
104
105 teardown_container
106}
107
108
109function test_upgrade_with_livepatch_in_vm() {
110
111 setup_vm
112
113 h1 "Upgrade to UA 27 while old version has livepatch enabled"
114
115 h2 "Enable livepatch on old version"
116 set +x
117 echo "+ multipass exec $name -- ubuntu-advantage enable-livepatch \$LIVEPATCH_TOKEN"
118 multipass exec $name -- sudo ubuntu-advantage enable-livepatch $LIVEPATCH_TOKEN
119 set -x
120
121 h2 "Status before - old UA and livepatch say enabled"
122 multipass exec $name -- sudo canonical-livepatch status
123 multipass exec $name -- sudo ubuntu-advantage status
124
125 install_new_ua multipass
126
127 h2 "Status after upgrade - livepatch still enabled but new UA doesn't report it"
128 multipass exec $name -- sudo canonical-livepatch status
129 multipass exec $name -- sudo ua status
130
131 h2 "Attach - verify that livepatch is disabled and re-enabled"
132 attach_ua multipass
133
134 h2 "Status after attach - both livepatch and UA should say enabled"
135 multipass exec $name -- sudo canonical-livepatch status
136 multipass exec $name -- sudo ua status
137
138 h2 "Detaching before destruction"
139 multipass exec $name -- sudo ua detach --assume-yes
140
141 teardown_vm
142}
143
144function test_upgrade_with_fips_in_vm() {
145
146 setup_vm
147
148 h1 "Upgrade to UA 27 while old version has fips enabled"
149
150 h2 "Manual fips check says disabled (file doesn't exist)"
151 multipass exec $name -- sudo cat /proc/sys/crypto/fips_enabled || true
152
153 h2 "Enable fips on old version"
154 set +x
155 echo "+ multipass exec $name -- ubuntu-advantage enable-fips \$FIPS_CREDS"
156 multipass exec $name -- sudo ubuntu-advantage enable-fips $FIPS_CREDS
157 set -x
158
159 h2 "Reboot to finish fips activation"
160 multipass exec $name -- sudo reboot || true
161 sleep 20
162
163 h2 "Status before upgrade - old UA says fips is enabled, manual check agrees"
164 multipass exec $name -- sudo ubuntu-advantage status
165 multipass exec $name -- sudo cat /proc/sys/crypto/fips_enabled
166
167 h2 "Source added by old client is present"
168 multipass exec $name -- sudo ls /etc/apt/sources.list.d
169 multipass exec $name -- sudo grep -o private-ppa.launchpad.net/ubuntu-advantage/fips/ubuntu /etc/apt/sources.list.d/ubuntu-fips-xenial.list
170
171 install_new_ua multipass
172
173 h2 "Status after upgrade - new UA won't say anything is enabled, but a manual check still says fips is enabled"
174 multipass exec $name -- sudo ua status
175 multipass exec $name -- sudo cat /proc/sys/crypto/fips_enabled
176
177 h2 "Source file added by old client is renamed but contents left unchanged"
178 multipass exec $name -- sudo ls /etc/apt/sources.list.d
179 multipass exec $name -- sudo grep -o private-ppa.launchpad.net/ubuntu-advantage/fips/ubuntu /etc/apt/sources.list.d/ubuntu-fips.list
180
181 h2 "Attach - only esm-infra will be auto-enabled"
182 attach_ua multipass
183
184 h2 "Status after attach - new UA will say fips is disabled, livepatch is n/a, and there is a notice to enable fips"
185 multipass exec $name -- sudo ua status
186 multipass exec $name -- sudo cat /proc/sys/crypto/fips_enabled
187
188 h2 "Enable fips on new UA - This will re-install fips packages and ask to reboot again"
189 multipass exec $name -- sudo ua enable fips --assume-yes
190
191
192 h2 "Status after enabled but before reboot - UA says fips enabled, notice to enable fips is gone"
193 multipass exec $name -- sudo ua status
194 multipass exec $name -- sudo cat /proc/sys/crypto/fips_enabled
195
196 h2 "Source added by old client is replaced with new source"
197 set +x
198 echo "multipass exec $name -- sudo grep \$FIPS_CREDS /etc/apt/sources.list.d/ubuntu-fips.list && echo \"FAIL: found oldclient FIPS creds\" || echo \"SUCCESS: Migrated to new client creds\""
199 multipass exec $name -- sudo grep $FIPS_CREDS /etc/apt/sources.list.d/ubuntu-fips.list && echo "FAIL: found oldclient FIPS creds" || echo "SUCCESS: Migrated to new client creds"
200 set -x
201 multipass exec $name -- sudo ls /etc/apt/sources.list.d
202 multipass exec $name -- sudo cat /etc/apt/sources.list.d/ubuntu-fips.list
203 multipass exec $name -- sudo apt update
204
205 h2 "Check to make sure we have a valid ubuntu-*-fips metapackage installed"
206 multipass exec $name -- sudo grep "install" /var/log/ubuntu-advantage.log
207
208 h2 "Reboot to finish second fips activation"
209 multipass exec $name -- sudo reboot || true
210 sleep 20
211
212 h2 "Status after reboot - new UA will say fips is enabled, manual check agrees"
213 multipass exec $name -- sudo ua status
214 multipass exec $name -- sudo cat /proc/sys/crypto/fips_enabled
215
216 h2 "Detaching before destruction"
217 multipass exec $name -- sudo ua detach --assume-yes
218
219 teardown_vm
220}
221
222test_upgrade_in_container
223test_upgrade_with_livepatch_in_vm
224test_upgrade_with_fips_in_vm
diff --git a/tools/tox-lxd-runner b/tools/tox-lxd-runner
index 40c5e48..cd80af0 100755
--- a/tools/tox-lxd-runner
+++ b/tools/tox-lxd-runner
@@ -57,9 +57,9 @@ python_minor=$(lxc exec "$container" -- python3 -c 'import sys; print(sys.versio
57if ((python_minor > 5)); then57if ((python_minor > 5)); then
58 get_pip_url="https://bootstrap.pypa.io/get-pip.py"58 get_pip_url="https://bootstrap.pypa.io/get-pip.py"
59elif ((python_minor == 5)); then59elif ((python_minor == 5)); then
60 get_pip_url="https://bootstrap.pypa.io/3.5/get-pip.py"60 get_pip_url="https://bootstrap.pypa.io/pip/3.5/get-pip.py"
61elif ((python_minor == 4)); then61elif ((python_minor == 4)); then
62 get_pip_url="https://bootstrap.pypa.io/3.4/get-pip.py"62 get_pip_url="https://bootstrap.pypa.io/pip/3.4/get-pip.py"
63else63else
64 echo "Unsupported Python version (3.$python_minor)"64 echo "Unsupported Python version (3.$python_minor)"
65 exit 165 exit 1
diff --git a/tox.ini b/tox.ini
index fa53bb5..19a5b56 100644
--- a/tox.ini
+++ b/tox.ini
@@ -46,8 +46,8 @@ commands =
46 flake8-bionic: flake8 features46 flake8-bionic: flake8 features
47 mypy: mypy --python-version 3.4 uaclient/47 mypy: mypy --python-version 3.4 uaclient/
48 mypy: mypy --python-version 3.5 uaclient/48 mypy: mypy --python-version 3.5 uaclient/
49 mypy: mypy --python-version 3.6 uaclient/ features/49 mypy: mypy --python-version 3.6 uaclient/ features/ lib/
50 mypy-focal: mypy --python-version 3.7 uaclient/ features/50 mypy-focal: mypy --python-version 3.7 uaclient/ features/ lib/
51 black: black --check --diff uaclient/ features/ lib/ setup.py51 black: black --check --diff uaclient/ features/ lib/ setup.py
52 behave-lxd-14.04: behave -v {posargs} --tags="series.trusty,series.all" --tags="~upgrade"52 behave-lxd-14.04: behave -v {posargs} --tags="series.trusty,series.all" --tags="~upgrade"
53 behave-lxd-16.04: behave -v {posargs} --tags="series.xenial,series.all" --tags="~upgrade"53 behave-lxd-16.04: behave -v {posargs} --tags="series.xenial,series.all" --tags="~upgrade"
diff --git a/uaclient-devel.conf b/uaclient-devel.conf
index e21d64d..c8386c8 100644
--- a/uaclient-devel.conf
+++ b/uaclient-devel.conf
@@ -1,5 +1,6 @@
1# Development UA Client config file. YAML1# Development UA Client config file. YAML
2contract_url: 'https://contracts.staging.canonical.com'2contract_url: 'https://contracts.staging.canonical.com'
3security_url: 'https://ubuntu.com/security'
3data_dir: /var/tmp/uaclient4data_dir: /var/tmp/uaclient
4log_level: debug5log_level: debug
5log_file: ubuntu-advantage-devel.log6log_file: ubuntu-advantage-devel.log
diff --git a/uaclient.conf b/uaclient.conf
index 8dc2f1a..9e5def8 100644
--- a/uaclient.conf
+++ b/uaclient.conf
@@ -1,5 +1,6 @@
1# Ubuntu-Advantage client config file.1# Ubuntu-Advantage client config file.
2contract_url: 'https://contracts.canonical.com'2contract_url: 'https://contracts.canonical.com'
3security_url: 'https://ubuntu.com/security'
3data_dir: /var/lib/ubuntu-advantage4data_dir: /var/lib/ubuntu-advantage
4log_level: debug5log_level: debug
5log_file: /var/log/ubuntu-advantage.log6log_file: /var/log/ubuntu-advantage.log
diff --git a/uaclient/apt.py b/uaclient/apt.py
index 93630b4..7b80d87 100644
--- a/uaclient/apt.py
+++ b/uaclient/apt.py
@@ -58,6 +58,7 @@ def assert_valid_apt_credentials(repo_url, username, password):
58 os.path.join(tmpd, "apt-helper-output"),58 os.path.join(tmpd, "apt-helper-output"),
59 ],59 ],
60 timeout=APT_HELPER_TIMEOUT,60 timeout=APT_HELPER_TIMEOUT,
61 retry_sleeps=APT_RETRIES,
61 )62 )
62 except util.ProcessExecutionError as e:63 except util.ProcessExecutionError as e:
63 if e.exit_code == 100:64 if e.exit_code == 100:
diff --git a/uaclient/cli.py b/uaclient/cli.py
index 87dc767..b6a4ed5 100644
--- a/uaclient/cli.py
+++ b/uaclient/cli.py
@@ -8,6 +8,7 @@ import json
8import logging8import logging
9import os9import os
10import pathlib10import pathlib
11import re
11import sys12import sys
12import textwrap13import textwrap
13import time14import time
@@ -23,6 +24,7 @@ from uaclient import config
23from uaclient import contract24from uaclient import contract
24from uaclient import entitlements25from uaclient import entitlements
25from uaclient import exceptions26from uaclient import exceptions
27from uaclient import security
26from uaclient import status as ua_status28from uaclient import status as ua_status
27from uaclient import util29from uaclient import util
28from uaclient import version30from uaclient import version
@@ -88,19 +90,11 @@ class UAArgumentParser(argparse.ArgumentParser):
88 self.non_beta_services_desc,90 self.non_beta_services_desc,
89 self.beta_services_desc,91 self.beta_services_desc,
90 ]92 ]
9193 if any(desc_vars):
92 if all([desc_var is None for desc_var in desc_vars]):94 services = sorted(self.non_beta_services_desc)
93 super().print_help(file=file)95 if show_all:
94 elif show_all:96 services = sorted(services + self.beta_services_desc)
95 self.description = "\n".join(97 self.description = "\n".join([self.base_desc] + services)
96 [self.base_desc]
97 + sorted(self.non_beta_services_desc + self.beta_services_desc)
98 )
99 else:
100 self.description = "\n".join(
101 [self.base_desc] + sorted(self.non_beta_services_desc)
102 )
103
104 super().print_help(file=file)98 super().print_help(file=file)
10599
106100
@@ -195,6 +189,10 @@ def assert_not_attached(f):
195def auto_attach_parser(parser):189def auto_attach_parser(parser):
196 """Build or extend an arg parser for auto-attach subcommand."""190 """Build or extend an arg parser for auto-attach subcommand."""
197 parser.prog = "auto-attach"191 parser.prog = "auto-attach"
192 parser.description = (
193 "Automatically attach an Ubuntu Advantage token on Ubuntu Pro"
194 " images."
195 )
198 parser.usage = USAGE_TMPL.format(name=NAME, command=parser.prog)196 parser.usage = USAGE_TMPL.format(name=NAME, command=parser.prog)
199 parser._optionals.title = "Flags"197 parser._optionals.title = "Flags"
200 return parser198 return parser
@@ -204,6 +202,10 @@ def attach_parser(parser):
204 """Build or extend an arg parser for attach subcommand."""202 """Build or extend an arg parser for attach subcommand."""
205 parser.usage = USAGE_TMPL.format(name=NAME, command="attach <token>")203 parser.usage = USAGE_TMPL.format(name=NAME, command="attach <token>")
206 parser.prog = "attach"204 parser.prog = "attach"
205 parser.description = (
206 "Attach this machine to Ubuntu Advantage with a token obtained"
207 " from https://ubuntu.com/advantage"
208 )
207 parser._optionals.title = "Flags"209 parser._optionals.title = "Flags"
208 parser.add_argument(210 parser.add_argument(
209 "token",211 "token",
@@ -221,11 +223,56 @@ def attach_parser(parser):
221 return parser223 return parser
222224
223225
226def fix_parser(parser):
227 """Build or extend an arg parser for fix subcommand."""
228 parser.usage = USAGE_TMPL.format(
229 name=NAME, command="fix <CVE-yyyy-nnnn+>|<USN-nnnn-d+>"
230 )
231 parser.prog = "fix"
232 parser.description = (
233 "Inspect and resolve CVEs and USNs (Ubuntu Security Notices) on this"
234 " machine."
235 )
236 parser._optionals.title = "Flags"
237 parser.add_argument(
238 "security_issue",
239 help=(
240 "Security vulnerability ID to inspect and resolve on this system."
241 " Format: CVE-yyyy-nnnn, CVE-yyyy-nnnnnnn or USN-nnnn-dd"
242 ),
243 )
244 return parser
245
246
247def refresh_parser(parser):
248 """Build or extend an arg parser for refresh subcommand."""
249 parser.prog = "refresh"
250 parser.description = (
251 "Refresh existing Ubuntu Advantage contract and update services."
252 )
253 parser.usage = USAGE_TMPL.format(name=NAME, command=parser.prog)
254 parser._optionals.title = "Flags"
255 return parser
256
257
258def action_fix(args, cfg, **kwargs):
259 if not re.match(security.CVE_OR_USN_REGEX, args.security_issue):
260 msg = (
261 'Error: issue "{}" is not recognized.\n'
262 'Usage: "ua fix CVE-yyyy-nnnn" or "ua fix USN-nnnn"'
263 ).format(args.security_issue)
264 raise exceptions.UserFacingError(msg)
265
266 security.fix_security_issue_id(cfg, args.security_issue)
267 return 0
268
269
224def detach_parser(parser):270def detach_parser(parser):
225 """Build or extend an arg parser for detach subcommand."""271 """Build or extend an arg parser for detach subcommand."""
226 usage = USAGE_TMPL.format(name=NAME, command="detach")272 usage = USAGE_TMPL.format(name=NAME, command="detach")
227 parser.usage = usage273 parser.usage = usage
228 parser.prog = "detach"274 parser.prog = "detach"
275 parser.description = "Detach this machine from Ubuntu Advantage services."
229 parser._optionals.title = "Flags"276 parser._optionals.title = "Flags"
230 parser.add_argument(277 parser.add_argument(
231 "--assume-yes",278 "--assume-yes",
@@ -240,6 +287,9 @@ def help_parser(parser):
240 usage = USAGE_TMPL.format(name=NAME, command="help [service]")287 usage = USAGE_TMPL.format(name=NAME, command="help [service]")
241 parser.usage = usage288 parser.usage = usage
242 parser.prog = "help"289 parser.prog = "help"
290 parser.description = (
291 "Provide detailed information about Ubuntu Advantage services."
292 )
243 parser._positionals.title = "Arguments"293 parser._positionals.title = "Arguments"
244 parser.add_argument(294 parser.add_argument(
245 "service",295 "service",
@@ -276,6 +326,7 @@ def enable_parser(parser):
276 usage = USAGE_TMPL.format(326 usage = USAGE_TMPL.format(
277 name=NAME, command="enable <service> [<service>]"327 name=NAME, command="enable <service> [<service>]"
278 )328 )
329 parser.description = "Enable an Ubuntu Advantage service."
279 parser.usage = usage330 parser.usage = usage
280 parser.prog = "enable"331 parser.prog = "enable"
281 parser._positionals.title = "Arguments"332 parser._positionals.title = "Arguments"
@@ -286,7 +337,7 @@ def enable_parser(parser):
286 nargs="+",337 nargs="+",
287 help=(338 help=(
288 "the name(s) of the Ubuntu Advantage services to enable."339 "the name(s) of the Ubuntu Advantage services to enable."
289 " One of: {}".format(entitlements.RELEASED_ENTITLEMENTS_STR),340 " One of: {}".format(entitlements.RELEASED_ENTITLEMENTS_STR)
290 ),341 ),
291 )342 )
292 parser.add_argument(343 parser.add_argument(
@@ -305,6 +356,7 @@ def disable_parser(parser):
305 usage = USAGE_TMPL.format(356 usage = USAGE_TMPL.format(
306 name=NAME, command="disable <service> [<service>]"357 name=NAME, command="disable <service> [<service>]"
307 )358 )
359 parser.description = "Disable an Ubuntu Advantage service."
308 parser.usage = usage360 parser.usage = usage
309 parser.prog = "disable"361 parser.prog = "disable"
310 parser._positionals.title = "Arguments"362 parser._positionals.title = "Arguments"
@@ -330,6 +382,9 @@ def status_parser(parser):
330 """Build or extend an arg parser for status subcommand."""382 """Build or extend an arg parser for status subcommand."""
331 usage = USAGE_TMPL.format(name=NAME, command="status")383 usage = USAGE_TMPL.format(name=NAME, command="status")
332 parser.usage = usage384 parser.usage = usage
385 parser.description = (
386 "Output the status information for Ubuntu Advantage services."
387 )
333 parser.prog = "status"388 parser.prog = "status"
334 # This formatter_class ensures that our formatting below isn't lost389 # This formatter_class ensures that our formatting below isn't lost
335 parser.formatter_class = argparse.RawDescriptionHelpFormatter390 parser.formatter_class = argparse.RawDescriptionHelpFormatter
@@ -441,7 +496,7 @@ def action_disable(args, cfg, **kwargs):
441 ret &= _perform_disable(entitlement, cfg, assume_yes=args.assume_yes)496 ret &= _perform_disable(entitlement, cfg, assume_yes=args.assume_yes)
442497
443 if entitlements_not_found:498 if entitlements_not_found:
444 valid_names = "Try " + entitlements.ALL_ENTITLEMENTS_STR499 valid_names = "Try " + entitlements.ALL_ENTITLEMENTS_STR + "."
445 service_msg = "\n".join(500 service_msg = "\n".join(
446 textwrap.wrap(valid_names, width=80, break_long_words=False)501 textwrap.wrap(valid_names, width=80, break_long_words=False)
447 )502 )
@@ -542,7 +597,7 @@ def action_enable(args, cfg, **kwargs):
542 valid_names = entitlements.RELEASED_ENTITLEMENTS_STR597 valid_names = entitlements.RELEASED_ENTITLEMENTS_STR
543 service_msg = "\n".join(598 service_msg = "\n".join(
544 textwrap.wrap(599 textwrap.wrap(
545 "Try " + valid_names, width=80, break_long_words=False600 "Try " + valid_names + ".", width=80, break_long_words=False
546 )601 )
547 )602 )
548 tmpl = ua_status.MESSAGE_INVALID_SERVICE_OP_FAILURE_TMPL603 tmpl = ua_status.MESSAGE_INVALID_SERVICE_OP_FAILURE_TMPL
@@ -580,7 +635,7 @@ def _detach(cfg: config.UAConfig, assume_yes: bool) -> int:
580 """635 """
581 to_disable = []636 to_disable = []
582 for ent_cls in entitlements.ENTITLEMENT_CLASSES:637 for ent_cls in entitlements.ENTITLEMENT_CLASSES:
583 ent = ent_cls(cfg)638 ent = ent_cls(cfg=cfg, assume_yes=assume_yes)
584 if ent.can_disable(silent=True):639 if ent.can_disable(silent=True):
585 to_disable.append(ent)640 to_disable.append(ent)
586 if to_disable:641 if to_disable:
@@ -597,6 +652,7 @@ def _detach(cfg: config.UAConfig, assume_yes: bool) -> int:
597 contract_id = cfg.machine_token["machineTokenInfo"]["contractInfo"]["id"]652 contract_id = cfg.machine_token["machineTokenInfo"]["contractInfo"]["id"]
598 contract_client.detach_machine_from_contract(machine_token, contract_id)653 contract_client.detach_machine_from_contract(machine_token, contract_id)
599 cfg.delete_cache()654 cfg.delete_cache()
655 config.update_ua_messages(cfg)
600 print(ua_status.MESSAGE_DETACH_SUCCESS)656 print(ua_status.MESSAGE_DETACH_SUCCESS)
601 return 0657 return 0
602658
@@ -614,10 +670,12 @@ def _attach_with_token(
614 logging.exception(exc)670 logging.exception(exc)
615 print(ua_status.MESSAGE_ATTACH_FAILURE)671 print(ua_status.MESSAGE_ATTACH_FAILURE)
616 cfg.status() # Persist updated status in the event of partial attach672 cfg.status() # Persist updated status in the event of partial attach
673 config.update_ua_messages(cfg)
617 return 1674 return 1
618 except exceptions.UserFacingError as exc:675 except exceptions.UserFacingError as exc:
619 logging.warning(exc.msg)676 logging.warning(exc.msg)
620 cfg.status() # Persist updated status in the event of partial attach677 cfg.status() # Persist updated status in the event of partial attach
678 config.update_ua_messages(cfg)
621 return 1679 return 1
622 contract_name = cfg.machine_token["machineTokenInfo"]["contractInfo"][680 contract_name = cfg.machine_token["machineTokenInfo"]["contractInfo"][
623 "name"681 "name"
@@ -628,6 +686,7 @@ def _attach_with_token(
628 )686 )
629 )687 )
630688
689 config.update_ua_messages(cfg)
631 action_status(args=None, cfg=cfg)690 action_status(args=None, cfg=cfg)
632 return 0691 return 0
633692
@@ -804,6 +863,13 @@ def get_parser():
804 help="refresh Ubuntu Advantage services from contracts server",863 help="refresh Ubuntu Advantage services from contracts server",
805 )864 )
806 parser_refresh.set_defaults(action=action_refresh)865 parser_refresh.set_defaults(action=action_refresh)
866 refresh_parser(parser_refresh)
867 parser_fix = subparsers.add_parser(
868 "fix",
869 help="check for and mitigate the impact of a CVE/USN on this system",
870 )
871 parser_fix.set_defaults(action=action_fix)
872 fix_parser(parser_fix)
807 parser_version = subparsers.add_parser(873 parser_version = subparsers.add_parser(
808 "version", help="show version of {}".format(NAME)874 "version", help="show version of {}".format(NAME)
809 )875 )
@@ -869,6 +935,7 @@ def action_refresh(args, cfg):
869 with util.disable_log_to_console():935 with util.disable_log_to_console():
870 logging.exception(exc)936 logging.exception(exc)
871 raise exceptions.UserFacingError(ua_status.MESSAGE_REFRESH_FAILURE)937 raise exceptions.UserFacingError(ua_status.MESSAGE_REFRESH_FAILURE)
938
872 print(ua_status.MESSAGE_REFRESH_SUCCESS)939 print(ua_status.MESSAGE_REFRESH_SUCCESS)
873 return 0940 return 0
874941
@@ -989,7 +1056,9 @@ def main(sys_argv=None):
989 log_level = cfg.log_level1056 log_level = cfg.log_level
990 console_level = logging.DEBUG if args.debug else logging.INFO1057 console_level = logging.DEBUG if args.debug else logging.INFO
991 setup_logging(console_level, log_level, cfg.log_file)1058 setup_logging(console_level, log_level, cfg.log_file)
992 logging.debug("Executed with sys.argv: %r", sys_argv)1059 logging.debug(
1060 util.redact_sensitive_logs("Executed with sys.argv: %r" % sys_argv)
1061 )
993 return args.action(args, cfg)1062 return args.action(args, cfg)
9941063
9951064
diff --git a/uaclient/clouds/identity.py b/uaclient/clouds/identity.py
index fa24c36..95f9a19 100644
--- a/uaclient/clouds/identity.py
+++ b/uaclient/clouds/identity.py
@@ -6,6 +6,7 @@ from uaclient import exceptions
6from uaclient import clouds6from uaclient import clouds
7from uaclient import status7from uaclient import status
8from uaclient import util8from uaclient import util
9from uaclient.config import apply_config_settings_override
910
10try:11try:
11 from typing import Dict, Optional, Type # noqa: F40112 from typing import Dict, Optional, Type # noqa: F401
@@ -21,6 +22,16 @@ CLOUDINIT_INSTANCE_ID_FILE = "/var/lib/cloud/data/instance-id"
21# Mapping of datasource names to cloud-id responses. Trusty compat with Xenial+22# Mapping of datasource names to cloud-id responses. Trusty compat with Xenial+
22DATASOURCE_TO_CLOUD_ID = {"azurenet": "azure", "ec2": "aws", "gce": "gcp"}23DATASOURCE_TO_CLOUD_ID = {"azurenet": "azure", "ec2": "aws", "gce": "gcp"}
2324
25CLOUD_TYPE_TO_TITLE = {
26 "aws": "AWS",
27 "aws-china": "AWS China",
28 "aws-gov": "AWS Gov",
29 "azure": "Azure",
30 "gcp": "GCP",
31}
32
33PRO_CLOUDS = ["aws", "azure", "gcp"]
34
2435
25def get_instance_id(36def get_instance_id(
26 _iid_file: str = CLOUDINIT_INSTANCE_ID_FILE37 _iid_file: str = CLOUDINIT_INSTANCE_ID_FILE
@@ -47,6 +58,7 @@ def get_cloud_type_from_result_file(
47 return DATASOURCE_TO_CLOUD_ID.get(dsname, dsname)58 return DATASOURCE_TO_CLOUD_ID.get(dsname, dsname)
4859
4960
61@apply_config_settings_override("cloud_type")
50def get_cloud_type() -> "Optional[str]":62def get_cloud_type() -> "Optional[str]":
51 if util.which("cloud-id"):63 if util.which("cloud-id"):
52 # Present in cloud-init on >= Xenial64 # Present in cloud-init on >= Xenial
diff --git a/uaclient/clouds/tests/test_identity.py b/uaclient/clouds/tests/test_identity.py
index af320e1..3b8e494 100644
--- a/uaclient/clouds/tests/test_identity.py
+++ b/uaclient/clouds/tests/test_identity.py
@@ -97,6 +97,37 @@ class TestGetCloudType:
97 ):97 ):
98 assert get_cloud_type() is None98 assert get_cloud_type() is None
9999
100 @pytest.mark.parametrize(
101 "settings_overrides",
102 (
103 (
104 """
105 settings_overrides:
106 cloud_type: "azure"
107 """
108 ),
109 (
110 """
111 settings_overrides:
112 other_setting: "blah"
113 """
114 ),
115 ),
116 )
117 @mock.patch("uaclient.util.load_file")
118 @mock.patch(M_PATH + "util.which", return_value="/usr/bin/cloud-id")
119 @mock.patch(M_PATH + "util.subp", return_value=("test", ""))
120 def test_cloud_type_when_using_settings_override(
121 self, m_subp, m_which, m_load_file, settings_overrides
122 ):
123 if "azure" in settings_overrides:
124 expected_value = "azure"
125 else:
126 expected_value = "test"
127
128 m_load_file.return_value = settings_overrides
129 assert get_cloud_type() == expected_value
130
100131
101@mock.patch(M_PATH + "get_cloud_type")132@mock.patch(M_PATH + "get_cloud_type")
102class TestCloudInstanceFactory:133class TestCloudInstanceFactory:
diff --git a/uaclient/config.py b/uaclient/config.py
index 5b2de5e..e4b2f0c 100644
--- a/uaclient/config.py
+++ b/uaclient/config.py
@@ -1,9 +1,11 @@
1import copy1import copy
2from datetime import datetime2from datetime import datetime
3from functools import wraps
3import json4import json
4import logging5import logging
5import os6import os
6import re7import re
8import sys
7import yaml9import yaml
8from collections import namedtuple, OrderedDict10from collections import namedtuple, OrderedDict
911
@@ -47,6 +49,7 @@ MERGE_ID_KEY_MAP = {
47 "availableResources": "name",49 "availableResources": "name",
48 "resourceEntitlements": "type",50 "resourceEntitlements": "type",
49}51}
52UNSET_SETTINGS_OVERRIDE_KEY = "_unset"
5053
5154
52# A data path is a filename, and an attribute ("private") indicating whether it55# A data path is a filename, and an attribute ("private") indicating whether it
@@ -69,6 +72,7 @@ class UAConfig:
6972
70 _entitlements = None # caching to avoid repetitive file reads73 _entitlements = None # caching to avoid repetitive file reads
71 _machine_token = None # caching to avoid repetitive file reading74 _machine_token = None # caching to avoid repetitive file reading
75 _contract_expiry_datetime = None
7276
73 def __init__(77 def __init__(
74 self, cfg: "Dict[str, Any]" = None, series: str = None78 self, cfg: "Dict[str, Any]" = None, series: str = None
@@ -93,6 +97,10 @@ class UAConfig:
93 def contract_url(self):97 def contract_url(self):
94 return self.cfg.get("contract_url", "https://contracts.canonical.com")98 return self.cfg.get("contract_url", "https://contracts.canonical.com")
9599
100 @property
101 def security_url(self):
102 return self.cfg.get("security_url", "https://ubuntu.com/security")
103
96 def check_lock_info(self) -> "Tuple[int, str]":104 def check_lock_info(self) -> "Tuple[int, str]":
97 """Return lock info if config lock file is present the lock is active.105 """Return lock info if config lock file is present the lock is active.
98106
@@ -209,14 +217,48 @@ class UAConfig:
209 return self._entitlements217 return self._entitlements
210218
211 @property219 @property
220 def contract_expiry_datetime(self) -> "datetime":
221 """Return a datetime of the attached contract expiration."""
222 if not self._contract_expiry_datetime:
223 contractInfo = self.machine_token["machineTokenInfo"][
224 "contractInfo"
225 ]
226 self._contract_expiry_datetime = datetime.strptime(
227 contractInfo["effectiveTo"], "%Y-%m-%dT%H:%M:%SZ"
228 )
229
230 return self._contract_expiry_datetime
231
232 @property
212 def is_attached(self):233 def is_attached(self):
213 """Report whether this machine configuration is attached to UA."""234 """Report whether this machine configuration is attached to UA."""
214 return bool(self.machine_token) # machine_token is removed on detach235 return bool(self.machine_token) # machine_token is removed on detach
215236
216 @property237 @property
238 def contract_remaining_days(self) -> int:
239 """Report num days until contract expiration based on effectiveTo
240
241 :return: A positive int representing the number of days the attached
242 contract remains in effect. Return a negative int for the number
243 of days beyond contract's effectiveTo date.
244 """
245 delta = self.contract_expiry_datetime.date() - datetime.utcnow().date()
246 return delta.days
247
248 @property
217 def features(self):249 def features(self):
218 """Return a dictionary of any features provided in uaclient.conf."""250 """Return a dictionary of any features provided in uaclient.conf."""
219 return self.cfg.get("features", {})251 features = self.cfg.get("features")
252 if features:
253 if isinstance(features, dict):
254 return features
255 else:
256 logging.warning(
257 "Unexpected uaclient.conf features value."
258 " Expected dict, but found %s",
259 features,
260 )
261 return {}
220262
221 @property263 @property
222 def machine_token(self):264 def machine_token(self):
@@ -364,7 +406,11 @@ class UAConfig:
364 released_resources.append(resource)406 released_resources.append(resource)
365 continue407 continue
366408
367 if not ent_cls.is_beta:409 enabled_status = status.UserFacingStatus.ACTIVE.value
410 if (
411 not ent_cls.is_beta
412 or resource.get("status", "") == enabled_status
413 ):
368 released_resources.append(resource)414 released_resources.append(resource)
369415
370 if released_resources:416 if released_resources:
@@ -518,6 +564,7 @@ class UAConfig:
518564
519 Write the status-cache when called by root.565 Write the status-cache when called by root.
520 """566 """
567
521 if os.getuid() != 0:568 if os.getuid() != 0:
522 response = cast("Dict[str, Any]", self.read_cache("status-cache"))569 response = cast("Dict[str, Any]", self.read_cache("status-cache"))
523 if not response:570 if not response:
@@ -530,6 +577,15 @@ class UAConfig:
530 if os.getuid() == 0:577 if os.getuid() == 0:
531 self.write_cache("status-cache", response)578 self.write_cache("status-cache", response)
532579
580 # Try to remove fix reboot notices if not applicable
581 if not util.should_reboot():
582 self.remove_notice(
583 "",
584 status.MESSAGE_ENABLE_REBOOT_REQUIRED_TMPL.format(
585 operation="fix operation"
586 ),
587 )
588
533 config_allow_beta = util.is_config_value_true(589 config_allow_beta = util.is_config_value_true(
534 config=self.cfg, path_to_value="features.allow_beta"590 config=self.cfg, path_to_value="features.allow_beta"
535 )591 )
@@ -620,19 +676,69 @@ def parse_config(config_path=None):
620 for key, value in os.environ.items():676 for key, value in os.environ.items():
621 key = key.lower()677 key = key.lower()
622 if key.startswith("ua_"):678 if key.startswith("ua_"):
623 env_keys[key[3:]] = value # Strip leading UA_679 if "ua_features_" in key:
680 key = key[12:] # String leading UA_FEATURES_
681
682 # Users can provide a yaml file to override
683 # config behavor. If they do, we are going
684 # to load that yaml and update the config
685 # with it
686 if value.endswith("yaml"):
687 if os.path.exists(value):
688 value = yaml.safe_load(util.load_file(value))
689 else:
690 raise exceptions.UserFacingError(
691 "Could not find yaml file: {}".format(value)
692 )
693
694 if "features" not in cfg:
695 cfg["features"] = {key: value}
696 else:
697 cfg["features"][key] = value
698 else:
699 env_keys[key[3:]] = value # Strip leading UA_
624 cfg.update(env_keys)700 cfg.update(env_keys)
625 cfg["log_level"] = cfg["log_level"].upper()701 cfg["log_level"] = cfg["log_level"].upper()
626 cfg["data_dir"] = os.path.expanduser(cfg["data_dir"])702 cfg["data_dir"] = os.path.expanduser(cfg["data_dir"])
627 if not util.is_service_url(cfg["contract_url"]):703 for key in ("contract_url", "security_url"):
628 raise exceptions.UserFacingError(704 if not util.is_service_url(cfg[key]):
629 "Invalid url in config. contract_url: {}".format(705 raise exceptions.UserFacingError(
630 cfg["contract_url"]706 "Invalid url in config. {}: {}".format(key, cfg[key])
631 )707 )
632 )
633 return cfg708 return cfg
634709
635710
711def apply_config_settings_override(override_key: str):
712 """Decorator used to override function return by config settings.
713
714 To identify if we should override the function return, we check
715 if the config object has the expected override key, we use it
716 has, we will use the key value as the function return. Otherwise
717 we will call the function normally.
718
719 @param override_key: key to be looked for in the settings_override
720 entry in the config dict. If that key is present, we will return
721 its value as the function return.
722 """
723
724 def wrapper(f):
725 @wraps(f)
726 def new_f():
727 cfg = parse_config()
728 value_override = cfg.get("settings_overrides", {}).get(
729 override_key, UNSET_SETTINGS_OVERRIDE_KEY
730 )
731
732 if value_override != UNSET_SETTINGS_OVERRIDE_KEY:
733 return value_override
734
735 return f()
736
737 return new_f
738
739 return wrapper
740
741
636def depth_first_merge_overlay_dict(base_dict, overlay_dict):742def depth_first_merge_overlay_dict(base_dict, overlay_dict):
637 """Merge the contents of overlay dict into base_dict not only on top-level743 """Merge the contents of overlay dict into base_dict not only on top-level
638 keys, but on all on the depths of the overlay_dict object. For example,744 keys, but on all on the depths of the overlay_dict object. For example,
@@ -678,3 +784,25 @@ def depth_first_merge_overlay_dict(base_dict, overlay_dict):
678 base_dict[key] = value784 base_dict[key] = value
679 else:785 else:
680 base_dict[key] = value786 base_dict[key] = value
787
788
789def update_ua_messages(cfg: UAConfig):
790 """Helper to load and run ua_update_messaging.
791
792 This is needed because we don't have /usr/lib/ubuntu-advantage
793 python scripts in our path and we don't want to shell out with
794 subp to call python3 /path/to/ua_update_messaging.py.
795 """
796 sys.path.append("/usr/lib/ubuntu-advantage")
797 try:
798 __import__("ua_update_messaging")
799 update_msgs = getattr(
800 sys.modules["ua_update_messaging"], "update_apt_and_motd_messages"
801 )
802 update_msgs(cfg)
803 except ImportError:
804 logging.debug(
805 "Unable to update UA messages. Cannot import ua_update_messaging."
806 )
807 finally:
808 sys.path.pop()
diff --git a/uaclient/contract.py b/uaclient/contract.py
index 32c4347..a616b7a 100644
--- a/uaclient/contract.py
+++ b/uaclient/contract.py
@@ -1,5 +1,4 @@
1import logging1import logging
2import urllib
32
4from uaclient import clouds3from uaclient import clouds
5from uaclient import exceptions4from uaclient import exceptions
@@ -97,7 +96,7 @@ class UAContractClient(serviceclient.UAServiceClient):
97 "kernel": platform["kernel"],96 "kernel": platform["kernel"],
98 }97 }
99 resource_response, headers = self.request_url(98 resource_response, headers = self.request_url(
100 API_V1_RESOURCES + "?" + urllib.parse.urlencode(query_params)99 API_V1_RESOURCES, query_params=query_params
101 )100 )
102 return resource_response101 return resource_response
103102
diff --git a/uaclient/defaults.py b/uaclient/defaults.py
index 129bc14..1eef1fd 100644
--- a/uaclient/defaults.py
+++ b/uaclient/defaults.py
@@ -6,14 +6,23 @@ any of our dependencies installed.
6"""6"""
77
8UAC_ETC_PATH = "/etc/ubuntu-advantage/"8UAC_ETC_PATH = "/etc/ubuntu-advantage/"
9DEFAULT_DATA_DIR = "/var/lib/ubuntu-advantage"
10DEFAULT_MACHINE_TOKEN_PATH = DEFAULT_DATA_DIR + "/private/machine-token.json"
9DEFAULT_CONFIG_FILE = UAC_ETC_PATH + "uaclient.conf"11DEFAULT_CONFIG_FILE = UAC_ETC_PATH + "uaclient.conf"
10DEFAULT_HELP_FILE = UAC_ETC_PATH + "help_data.yaml"12DEFAULT_HELP_FILE = UAC_ETC_PATH + "help_data.yaml"
11DEFAULT_UPGRADE_CONTRACT_FLAG_FILE = UAC_ETC_PATH + "request-update-contract"13DEFAULT_UPGRADE_CONTRACT_FLAG_FILE = UAC_ETC_PATH + "request-update-contract"
12BASE_CONTRACT_URL = "https://contracts.canonical.com"14BASE_CONTRACT_URL = "https://contracts.canonical.com"
15BASE_SECURITY_URL = "https://ubuntu.com/security"
16BASE_UA_URL = "https://ubuntu.com/advantage"
17BASE_ESM_URL = "https://ubuntu.com/esm"
18PRINT_WRAP_WIDTH = 80
19CONTRACT_EXPIRY_GRACE_PERIOD_DAYS = 14
20CONTRACT_EXPIRY_PENDING_DAYS = 20
1321
14CONFIG_DEFAULTS = {22CONFIG_DEFAULTS = {
15 "contract_url": BASE_CONTRACT_URL,23 "contract_url": BASE_CONTRACT_URL,
16 "data_dir": "/var/lib/ubuntu-advantage",24 "security_url": BASE_SECURITY_URL,
25 "data_dir": DEFAULT_DATA_DIR,
17 "log_level": "INFO",26 "log_level": "INFO",
18 "log_file": "/var/log/ubuntu-advantage.log",27 "log_file": "/var/log/ubuntu-advantage.log",
19}28}
diff --git a/uaclient/entitlements/esm.py b/uaclient/entitlements/esm.py
index 866967f..2decf4b 100644
--- a/uaclient/entitlements/esm.py
+++ b/uaclient/entitlements/esm.py
@@ -1,5 +1,6 @@
1from uaclient.entitlements import repo1from uaclient.entitlements import repo
2from uaclient import util2from uaclient import util
3from uaclient.config import update_ua_messages
34
4try:5try:
5 from typing import Optional # noqa: F4016 from typing import Optional # noqa: F401
@@ -11,31 +12,76 @@ except ImportError:
11class ESMBaseEntitlement(repo.RepoEntitlement):12class ESMBaseEntitlement(repo.RepoEntitlement):
12 help_doc_url = "https://ubuntu.com/security/esm"13 help_doc_url = "https://ubuntu.com/security/esm"
1314
15 def enable(self, *, silent_if_inapplicable: bool = False) -> bool:
16 enable_performed = super().enable(
17 silent_if_inapplicable=silent_if_inapplicable
18 )
19 if enable_performed:
20 update_ua_messages(self.cfg)
21 return enable_performed
22
23 def disable(self, silent=False) -> bool:
24 disable_performed = super().disable(silent=silent)
25 if disable_performed:
26 update_ua_messages(self.cfg)
27 return disable_performed
28
1429
15class ESMAppsEntitlement(ESMBaseEntitlement):30class ESMAppsEntitlement(ESMBaseEntitlement):
16 origin = "UbuntuESMApps"31 origin = "UbuntuESMApps"
17 name = "esm-apps"32 name = "esm-apps"
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to status/vote changes: