diff --git a/roles/cloud-scaleway/tasks/main.yml b/roles/cloud-scaleway/tasks/main.yml index 1239a949d..646bd48d6 100644 --- a/roles/cloud-scaleway/tasks/main.yml +++ b/roles/cloud-scaleway/tasks/main.yml @@ -3,25 +3,21 @@ import_tasks: prompts.yml - block: - - name: Gather Scaleway organizations facts - scaleway_organization_info: - register: scaleway_org - - - name: Get images - scaleway_image_info: - region: "{{ algo_region }}" - register: scaleway_image + - name: Get Ubuntu 22.04 image ID from Scaleway Marketplace API + uri: + url: "https://api-marketplace.scaleway.com/images?arch={{ cloud_providers.scaleway.arch }}&include_eol=false" + method: GET + return_content: true + register: marketplace_images - - name: Set cloud specific facts + - name: Find Ubuntu 22.04 Jammy image set_fact: - organization_id: "{{ scaleway_org.scaleway_organization_info[0]['id'] }}" - images: >- - [{% for i in scaleway_image.scaleway_image_info -%} - {% if i.name == cloud_providers.scaleway.image and - i.arch == cloud_providers.scaleway.arch -%} - '{{ i.id }}'{% if not loop.last %},{% endif %} - {%- endif -%} - {%- endfor -%}] + scaleway_image_id: >- + {{ (marketplace_images.json.images | + selectattr('name', 'match', '.*Ubuntu.*22\\.04.*Jammy.*') | + first).versions[0].local_images | + selectattr('zone', 'equalto', algo_region) | + map(attribute='id') | first }} - name: Create a server scaleway_compute: @@ -30,8 +26,8 @@ public_ip: dynamic boot_type: local state: present - image: "{{ images[0] }}" - organization: "{{ organization_id }}" + image: "{{ scaleway_image_id }}" + project: "{{ algo_scaleway_org_id }}" region: "{{ algo_region }}" commercial_type: "{{ cloud_providers.scaleway.size }}" wait: true @@ -57,8 +53,8 @@ public_ip: dynamic boot_type: local state: running - image: "{{ images[0] }}" - organization: "{{ organization_id }}" + image: "{{ scaleway_image_id }}" + project: "{{ algo_scaleway_org_id }}" region: "{{ algo_region }}" commercial_type: "{{ cloud_providers.scaleway.size }}" wait: true diff --git a/roles/cloud-scaleway/tasks/prompts.yml b/roles/cloud-scaleway/tasks/prompts.yml index 015ab60cf..f5f45efdc 100644 --- a/roles/cloud-scaleway/tasks/prompts.yml +++ b/roles/cloud-scaleway/tasks/prompts.yml @@ -21,6 +21,20 @@ register: _algo_region when: region is undefined +- pause: + prompt: | + Enter your Scaleway Organization ID (also serves as your default Project ID) + You can find this in your Scaleway console: + 1. Go to https://console.scaleway.com/organization/settings + 2. Copy the Organization ID from the Organization Settings page + + Note: For the default project, the Project ID is the same as the Organization ID. + (https://trailofbits.github.io/algo/cloud-scaleway.html) + register: _scaleway_org_id + when: + - scaleway_org_id is undefined + - lookup('env', 'SCW_DEFAULT_ORGANIZATION_ID')|length <= 0 + - name: Set scaleway facts set_fact: algo_scaleway_token: "{{ scaleway_token | default(_scaleway_token.user_input) | default(lookup('env', 'SCW_TOKEN'), true) }}" @@ -28,4 +42,5 @@ {% if region is defined %}{{ region }} {%- elif _algo_region.user_input %}{{ scaleway_regions[_algo_region.user_input | int - 1]['alias'] }} {%- else %}{{ scaleway_regions.0.alias }}{% endif %} + algo_scaleway_org_id: "{{ scaleway_org_id | default(_scaleway_org_id.user_input) | default(lookup('env', 'SCW_DEFAULT_ORGANIZATION_ID'), true) }}" no_log: true diff --git a/tests/unit/test_scaleway_fix.py b/tests/unit/test_scaleway_fix.py new file mode 100644 index 000000000..41cf63e89 --- /dev/null +++ b/tests/unit/test_scaleway_fix.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +""" +Test Scaleway role fixes for issue #14846 + +This test validates that: +1. The Scaleway role uses the modern 'project' parameter instead of deprecated 'organization' +2. The Marketplace API is used for image lookup instead of the broken scaleway_image_info module +3. The prompts include organization/project ID collection +""" + +import sys +from pathlib import Path + +import yaml + + +def load_yaml_file(file_path): + """Load and parse a YAML file""" + with open(file_path) as f: + return yaml.safe_load(f) + + +def test_scaleway_main_uses_project_parameter(): + """Test that main.yml uses 'project' instead of deprecated 'organization' parameter""" + main_yml = Path("roles/cloud-scaleway/tasks/main.yml") + assert main_yml.exists(), "Scaleway main.yml not found" + + with open(main_yml) as f: + content = f.read() + + # Should NOT use the broken scaleway_organization_info module + assert ( + "scaleway_organization_info" not in content + ), "Still using broken scaleway_organization_info module (issue #14846)" + + # Should NOT use the broken scaleway_image_info module + assert "scaleway_image_info" not in content, "Still using broken scaleway_image_info module" + + # Should use project parameter (modern approach) + assert "project:" in content, "Missing 'project:' parameter in scaleway_compute calls" + assert "algo_scaleway_org_id" in content, "Missing algo_scaleway_org_id variable reference" + + # Should NOT use deprecated organization parameter + assert 'organization: "{{' not in content, "Still using deprecated 'organization' parameter" + + # Should use Marketplace API for image lookup + assert "api-marketplace.scaleway.com" in content, "Not using Scaleway Marketplace API for image lookup" + + print("✓ Scaleway main.yml uses modern 'project' parameter") + + +def test_scaleway_prompts_collect_org_id(): + """Test that prompts.yml collects organization/project ID from user""" + prompts_yml = Path("roles/cloud-scaleway/tasks/prompts.yml") + assert prompts_yml.exists(), "Scaleway prompts.yml not found" + + with open(prompts_yml) as f: + content = f.read() + + # Should prompt for organization ID + assert "Organization ID" in content, "Missing prompt for Scaleway Organization ID" + + # Should set algo_scaleway_org_id fact + assert "algo_scaleway_org_id:" in content, "Missing algo_scaleway_org_id fact definition" + + # Should support SCW_DEFAULT_ORGANIZATION_ID env var + assert ( + "SCW_DEFAULT_ORGANIZATION_ID" in content + ), "Missing support for SCW_DEFAULT_ORGANIZATION_ID environment variable" + + # Should mention console.scaleway.com for finding the ID + assert "console.scaleway.com" in content, "Missing instructions on where to find Organization ID" + + print("✓ Scaleway prompts.yml collects organization/project ID") + + +def test_scaleway_config_has_valid_settings(): + """Test that config.cfg has valid Scaleway settings""" + config_file = Path("config.cfg") + assert config_file.exists(), "config.cfg not found" + + with open(config_file) as f: + content = f.read() + + # Should have scaleway section + assert "scaleway:" in content, "Missing Scaleway configuration section" + + # Should specify Ubuntu 22.04 + assert "Ubuntu 22.04" in content or "ubuntu" in content.lower(), "Missing Ubuntu image specification" + + print("✓ config.cfg has valid Scaleway settings") + + +def test_scaleway_marketplace_api_usage(): + """Test that the role correctly uses Scaleway Marketplace API""" + main_yml = Path("roles/cloud-scaleway/tasks/main.yml") + + with open(main_yml) as f: + content = f.read() + + # Should use uri module to fetch from Marketplace API + assert "uri:" in content, "Not using uri module for API calls" + + # Should filter for Ubuntu 22.04 Jammy + assert "Ubuntu" in content and "22" in content, "Not filtering for Ubuntu 22.04 image" + + # Should set scaleway_image_id variable + assert "scaleway_image_id" in content, "Missing scaleway_image_id variable for image UUID" + + print("✓ Scaleway role uses Marketplace API correctly") + + +if __name__ == "__main__": + tests = [ + test_scaleway_main_uses_project_parameter, + test_scaleway_prompts_collect_org_id, + test_scaleway_config_has_valid_settings, + test_scaleway_marketplace_api_usage, + ] + + failed = 0 + for test in tests: + try: + test() + except AssertionError as e: + print(f"✗ {test.__name__} failed: {e}") + failed += 1 + except Exception as e: + print(f"✗ {test.__name__} error: {e}") + failed += 1 + + if failed > 0: + print(f"\n{failed} tests failed") + sys.exit(1) + else: + print(f"\nAll {len(tests)} tests passed!")