Skip to content

Add support for FIPS Updates images #483

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Apr 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1!10.10.1
1!10.11.0
24 changes: 24 additions & 0 deletions examples/az.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,29 @@ def demo_pro_fips():
print(instance.execute("sudo ua status --wait"))


def demo_pro_fips_updates():
"""Show example of launchig a Ubuntu PRO FIPS image through Azure."""
with pycloudlib.Azure(tag="azure") as client:
image_id = client.daily_image(release="jammy", image_type=ImageType.PRO_FIPS_UPDATES)

pub_key, priv_key = client.create_key_pair(key_name="test_pro_fips")
pub_path, priv_path = save_keys(
key_name="test_pro_fips",
pub_key=pub_key,
priv_key=priv_key,
)
client.use_key(pub_path, priv_path)

print("Launching Focal Pro FIPS Updates instance.")
with client.launch(
image_id=image_id,
instance_type="Standard_DS2_v2", # default is Standard_DS1_v2
) as instance:
instance.wait()
print(instance.ip)
print(instance.execute("sudo ua status --wait"))


if __name__ == "__main__":
# Avoid polluting the log with azure info
logging.getLogger("adal-python").setLevel(logging.WARNING)
Expand All @@ -118,3 +141,4 @@ def demo_pro_fips():
demo()
demo_pro()
demo_pro_fips()
demo_pro_fips_updates()
35 changes: 15 additions & 20 deletions examples/ec2.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,22 +92,13 @@ def launch_basic(ec2, daily):
print(instance.availability_zone)


def launch_pro(ec2, daily):
"""Show basic functionality on PRO instances."""
print("Launching Pro instance...")
with ec2.launch(daily) as instance:
instance.wait()
print(instance.execute("sudo ua status --wait"))
print("Deleting Pro instance...")


def launch_pro_fips(ec2, daily):
"""Show basic functionality on PRO instances."""
print("Launching Pro FIPS instance...")
with ec2.launch(daily) as instance:
def launch_pro(ec2, name, image):
"""Show basic functionality on Pro instances."""
print("Launching {} instance...".format(name))
with ec2.launch(image) as instance:
instance.wait()
print(instance.execute("sudo ua status --wait"))
print("Deleting Pro FIPS instance...")
print(instance.execute("sudo pro status --wait"))
print("Deleting {} instance...".format(name))


def handle_ssh_key(ec2, key_name):
Expand Down Expand Up @@ -140,13 +131,17 @@ def demo():
key_name = "test-ec2"
handle_ssh_key(ec2, key_name)

daily = ec2.daily_image(release="bionic")
daily_pro = ec2.daily_image(release="bionic", image_type=ImageType.PRO)
daily_pro_fips = ec2.daily_image(release="bionic", image_type=ImageType.PRO_FIPS)
daily = ec2.daily_image(release="focal")
daily_pro = ec2.daily_image(release="focal", image_type=ImageType.PRO)
daily_pro_fips = ec2.daily_image(release="focal", image_type=ImageType.PRO_FIPS)
daily_pro_fips_updates = ec2.daily_image(
release="focal", image_type=ImageType.PRO_FIPS_UPDATES
)

launch_basic(ec2, daily)
launch_pro(ec2, daily_pro)
launch_pro_fips(ec2, daily_pro_fips)
launch_pro(ec2, "PRO", daily_pro)
launch_pro(ec2, "PRO FIPS", daily_pro_fips)
launch_pro(ec2, "PRO FIPS UPDATES", daily_pro_fips_updates)
custom_vpc(ec2, daily)
snapshot(ec2, daily)
launch_multiple(ec2, daily)
Expand Down
19 changes: 6 additions & 13 deletions examples/gce.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,12 @@ def generic(gce):
print(inst.execute("lsb_release -a"))


def pro(gce):
def pro(gce, series, image_type):
"""Show example of running a GCE PRO machine."""
daily = gce.daily_image("bionic", image_type=ImageType.PRO)
daily = gce.daily_image(series, image_type)
with gce.launch(daily) as inst:
inst.wait()
print(inst.execute("sudo ua status --wait"))


def pro_fips(gce):
"""Show example of running a GCE PRO FIPS machine."""
daily = gce.daily_image("bionic", image_type=ImageType.PRO_FIPS)
with gce.launch(daily) as inst:
inst.wait()
print(inst.execute("sudo ua status --wait"))
print(inst.execute("sudo pro status --wait"))


def demo():
Expand All @@ -62,8 +54,9 @@ def demo():
manage_ssh_key(gce)

generic(gce)
pro(gce)
pro_fips(gce)
pro(gce, "focal", ImageType.PRO)
pro(gce, "focal", ImageType.PRO_FIPS)
pro(gce, "jammy", ImageType.PRO_FIPS_UPDATES)


if __name__ == "__main__":
Expand Down
6 changes: 6 additions & 0 deletions pycloudlib/azure/cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@
"focal": "Canonical:0001-com-ubuntu-pro-focal-fips:pro-fips-20_04:latest",
}

UBUNTU_DAILY_PRO_FIPS_UPDATES_IMAGES = {
"jammy": "Canonical:0001-com-ubuntu-pro-jammy-fips:pro-fips-22_04-gen1:latest",
}

UBUNTU_RELEASE_IMAGES = {
"xenial": "Canonical:UbuntuServer:16.04-LTS:latest",
"bionic": "Canonical:UbuntuServer:18.04-LTS:latest",
Expand Down Expand Up @@ -683,6 +687,8 @@ def _get_images_dict(self, image_type: ImageType):
return UBUNTU_DAILY_PRO_IMAGES
if image_type == ImageType.PRO_FIPS:
return UBUNTU_DAILY_PRO_FIPS_IMAGES
if image_type == ImageType.PRO_FIPS_UPDATES:
return UBUNTU_DAILY_PRO_FIPS_UPDATES_IMAGES
if image_type == ImageType.MINIMAL:
return UBUNTU_MINIMAL_DAILY_IMAGES

Expand Down
1 change: 1 addition & 0 deletions pycloudlib/cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class ImageType(enum.Enum):
MINIMAL = "minimal"
PRO = "Pro"
PRO_FIPS = "Pro FIPS"
PRO_FIPS_UPDATES = "Pro FIPS Updates"


class BaseCloud(ABC):
Expand Down
5 changes: 4 additions & 1 deletion pycloudlib/ec2/cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,10 @@ def _get_name_for_image_type(self, release: str, image_type: ImageType, daily: b
return f"ubuntu-pro-server/images/{disk_type}/ubuntu-{release}-{release_ver}-*"

if image_type == ImageType.PRO_FIPS:
return f"ubuntu-pro-fips*/images/{disk_type}/ubuntu-{release}-{release_ver}-*"
return f"ubuntu-pro-fips-server/images/{disk_type}/ubuntu-{release}-{release_ver}-*"

if image_type == ImageType.PRO_FIPS_UPDATES:
return f"ubuntu-pro-fips-updates-server/images/{disk_type}/ubuntu-{release}-{release_ver}-*"

raise ValueError("Invalid image_type")

Expand Down
5 changes: 5 additions & 0 deletions pycloudlib/gce/cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,11 @@ def _get_name_filter(self, release: str, image_type: ImageType):
UBUNTU_RELEASE_VERSION_MAP[release].replace(".", ""), release
)

if image_type == ImageType.PRO_FIPS_UPDATES:
return "ubuntu-pro-fips-updates-{}-{}-*".format(
UBUNTU_RELEASE_VERSION_MAP[release].replace(".", ""), release
)

raise ValueError("Invalid image_type: {}".format(image_type.value))

def _query_image_list(self, release: str, project: str, name_filter: str, arch: str):
Expand Down
1 change: 1 addition & 0 deletions tests/integration_tests/ec2/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""EC2 integration tests."""
51 changes: 51 additions & 0 deletions tests/integration_tests/ec2/test_images.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""EC2 integration tests testing image related functionality."""

import logging

import pytest

from pycloudlib.cloud import ImageType
from pycloudlib.ec2.cloud import EC2

logger = logging.getLogger(__name__)


@pytest.fixture
def ec2_cloud():
"""
Fixture to create an EC2 instance for testing.

Yields:
EC2: An instance of the EC2 cloud class.
"""
with EC2(tag="integration-test-images") as ec2:
yield ec2


def test_finding_all_image_types_focal(ec2_cloud: EC2):
"""
Tests that all image types are available for the focal suite and that they are all unique.

As per issue #481, focal has both `fips` and `fips-updates` image types and previous to
introducing the `PRO_FIPS_UPDATES` image type, the `PRO_FIPS` image type could return a
`PRO_FIPS_UPDATES` image if it was newer. This test asserts that PR #483 prevents this from
happening.

Test assertions:
- All image types are available for the focal suite (exception is raised if not).
- No daily images returned per image type are the same (same image ID).
"""
suite = "focal"
images: dict[ImageType, str] = {}
# iterate through all ImageType enum values
for image_type in ImageType:
images[image_type] = ec2_cloud.daily_image(release=suite, image_type=image_type)
logger.info(
"Found %s image for %s: %s",
image_type,
suite,
images[image_type],
)

# make sure that none of the images are the same
assert len(set(images.values())) == len(images), f"Not all images are unique: {images}"
73 changes: 73 additions & 0 deletions tests/integration_tests/gce/test_images.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""GCE integration tests testing image related functionality."""

import logging

import pytest

from pycloudlib.cloud import ImageType
from pycloudlib.errors import ImageNotFoundError
from pycloudlib.gce.cloud import GCE

logger = logging.getLogger(__name__)


@pytest.fixture
def gce_cloud():
"""
Fixture to create a GCE instance for testing.

Yields:
GCE: An instance of the GCE cloud class.
"""
with GCE(tag="integration-test-images") as gce:
yield gce

@pytest.mark.parametrize(
"release, unavailable_image_types",
(
pytest.param(
"focal",
[ImageType.PRO_FIPS_UPDATES],
id="focal",
),
pytest.param(
"jammy",
[ImageType.PRO_FIPS],
id="jammy",
),
),
)
def test_finding_all_image_types_focal(
gce_cloud: GCE,
release: str,
unavailable_image_types: list[ImageType],
):
"""
Tests that all image types are available for the focal suite and that they are all unique.

Test assertions:
- Certain image types are unavailable for the given release (exception is raised if not).
- No daily images returned per image type are the same (same image ID).
"""
images: dict[ImageType, str] = {}
# iterate through all ImageType enum values
for image_type in ImageType:
if image_type in unavailable_image_types:
with pytest.raises(ImageNotFoundError) as exc_info:
gce_cloud.daily_image(release=release, image_type=image_type)
logger.info(
"Confirmed that %s image for %s is unavailable.",
image_type,
release,
)
else:
images[image_type] = gce_cloud.daily_image(release=release, image_type=image_type)
logger.info(
"Found %s image for %s: %s",
image_type,
release,
images[image_type],
)

# make sure that none of the images are the same
assert len(set(images.values())) == len(images), f"Not all images are unique: {images}"
10 changes: 9 additions & 1 deletion tests/unit_tests/ec2/test_cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,16 @@ class TestEC2:
"noble",
ImageType.PRO_FIPS,
False,
"ubuntu-pro-fips*/images/hvm-ssd-gp3/ubuntu-noble-24.04-*",
"ubuntu-pro-fips-server/images/hvm-ssd-gp3/ubuntu-noble-24.04-*",
id="pro-fips-lts",
),
pytest.param(
"jammy",
ImageType.PRO_FIPS_UPDATES,
False,
"ubuntu-pro-fips-updates-server/images/hvm-ssd/ubuntu-jammy-22.04-*",
id="pro-fips-updates-lts",
),
# Test GENERIC with non-LTS release and daily = False
pytest.param(
"oracular",
Expand Down Expand Up @@ -140,6 +147,7 @@ def test_get_owner_for_all_image_types(self):
ImageType.MINIMAL: "099720109477",
ImageType.PRO: "099720109477",
ImageType.PRO_FIPS: "aws-marketplace",
ImageType.PRO_FIPS_UPDATES: "aws-marketplace",
}

ec2 = FakeEC2()
Expand Down
5 changes: 5 additions & 0 deletions tests/unit_tests/gce/test_cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,11 @@ def test_daily_image_returns_latest_from_query( # noqa: D102
ImageType.PRO_FIPS,
"ubuntu-pro-fips-2004-focal-*",
),
pytest.param(
"jammy",
ImageType.PRO_FIPS_UPDATES,
"ubuntu-pro-fips-updates-2204-jammy-*",
),
],
)
def test_get_name_filter(self, release, image_type, expected_name_filter, gce):
Expand Down
Loading