diff --git a/integrations/scaleway/manifest.yaml b/integrations/scaleway/manifest.yaml new file mode 100644 index 0000000..2bb7eff --- /dev/null +++ b/integrations/scaleway/manifest.yaml @@ -0,0 +1,67 @@ +id: scaleway +name: Scaleway +version: 1.0.0 +description: "Scaleway (Cloud API) — cloud inventory and visibility: list and read instances, list security groups, and list projects. Secret-key authentication; stdlib-only, no extra Python dependencies. (French vendor.)" +changelog: "1.0.0 — Initial release: list/get instances, list security groups, list projects." +category: cloud + +# Per-instance configuration. Auth header 'X-Auth-Token: '. +config_schema: + properties: + secret_key: + type: string + description: "Scaleway API secret key" + x-soar-sensitive: true + default_zone: + type: string + description: "Default zone (e.g. fr-par-1)" + default: "fr-par-1" + required: + - secret_key + +commands: + - id: list_instances + name: scaleway-list-instances + description: "List instances in a zone." + risk: read + inputs_schema: + properties: + zone: { type: string, description: "Zone (defaults to the configured zone)" } + required: [] + outputs_schema: { properties: {} } + - id: get_instance + name: scaleway-get-instance + description: "Get an instance by ID." + risk: read + inputs_schema: + properties: + zone: { type: string, description: "Zone (defaults to the configured zone)" } + server_id: { type: string, description: "Instance/server ID" } + required: [server_id] + outputs_schema: { properties: {} } + - id: list_security_groups + name: scaleway-list-security-groups + description: "List security groups in a zone." + risk: read + inputs_schema: + properties: + zone: { type: string, description: "Zone (defaults to the configured zone)" } + required: [] + outputs_schema: { properties: {} } + - id: list_projects + name: scaleway-list-projects + description: "List projects in the organization." + risk: read + inputs_schema: + properties: {} + required: [] + outputs_schema: { properties: {} } + + - id: test_connection + name: scaleway-test-connection + description: "Verify the secret key (used by the Test button)." + risk: read + inputs_schema: + properties: {} + required: [] + outputs_schema: { properties: {} } diff --git a/integrations/scaleway/scripts/get_instance.py b/integrations/scaleway/scripts/get_instance.py new file mode 100644 index 0000000..fa24776 --- /dev/null +++ b/integrations/scaleway/scripts/get_instance.py @@ -0,0 +1,53 @@ +import json, os, sys, urllib.parse, urllib.request, urllib.error + +BASE = "https://api.scaleway.com" + + +def _cfg(): + return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + + +def _inputs(): + return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + + +def _zone(cfg, inputs): + return (inputs.get("zone") if inputs else None) or cfg.get("default_zone") or "fr-par-1" + + +def request(method, path, cfg, params=None): + url = BASE + path + if params: + clean = {k: v for k, v in params.items() if v not in (None, "")} + if clean: + url += "?" + urllib.parse.urlencode(clean) + headers = {"X-Auth-Token": str(cfg.get("secret_key", "")), "Accept": "application/json"} + req = urllib.request.Request(url, headers=headers, method=method) + with urllib.request.urlopen(req, timeout=60) as r: + raw = r.read() + return json.loads(raw) if raw else {} + + +def _run(fn): + try: + print(json.dumps(fn(_cfg(), _inputs()))) + except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) + except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) + + +q = lambda v: urllib.parse.quote(str(v), safe="") + + +def main(cfg, inputs): + server_id = inputs.get("server_id") + if not server_id: + raise Exception("server_id is required") + zone = _zone(cfg, inputs) + return request("GET", "/instance/v1/zones/" + q(zone) + "/servers/" + q(server_id), cfg) + + +_run(main) diff --git a/integrations/scaleway/scripts/list_instances.py b/integrations/scaleway/scripts/list_instances.py new file mode 100644 index 0000000..e88e56e --- /dev/null +++ b/integrations/scaleway/scripts/list_instances.py @@ -0,0 +1,50 @@ +import json, os, sys, urllib.parse, urllib.request, urllib.error + +BASE = "https://api.scaleway.com" + + +def _cfg(): + return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + + +def _inputs(): + return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + + +def _zone(cfg, inputs): + return (inputs.get("zone") if inputs else None) or cfg.get("default_zone") or "fr-par-1" + + +def request(method, path, cfg, params=None): + url = BASE + path + if params: + clean = {k: v for k, v in params.items() if v not in (None, "")} + if clean: + url += "?" + urllib.parse.urlencode(clean) + headers = {"X-Auth-Token": str(cfg.get("secret_key", "")), "Accept": "application/json"} + req = urllib.request.Request(url, headers=headers, method=method) + with urllib.request.urlopen(req, timeout=60) as r: + raw = r.read() + return json.loads(raw) if raw else {} + + +def _run(fn): + try: + print(json.dumps(fn(_cfg(), _inputs()))) + except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) + except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) + + +q = lambda v: urllib.parse.quote(str(v), safe="") + + +def main(cfg, inputs): + zone = _zone(cfg, inputs) + return request("GET", "/instance/v1/zones/" + q(zone) + "/servers", cfg) + + +_run(main) diff --git a/integrations/scaleway/scripts/list_projects.py b/integrations/scaleway/scripts/list_projects.py new file mode 100644 index 0000000..f5d5ac5 --- /dev/null +++ b/integrations/scaleway/scripts/list_projects.py @@ -0,0 +1,49 @@ +import json, os, sys, urllib.parse, urllib.request, urllib.error + +BASE = "https://api.scaleway.com" + + +def _cfg(): + return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + + +def _inputs(): + return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + + +def _zone(cfg, inputs): + return (inputs.get("zone") if inputs else None) or cfg.get("default_zone") or "fr-par-1" + + +def request(method, path, cfg, params=None): + url = BASE + path + if params: + clean = {k: v for k, v in params.items() if v not in (None, "")} + if clean: + url += "?" + urllib.parse.urlencode(clean) + headers = {"X-Auth-Token": str(cfg.get("secret_key", "")), "Accept": "application/json"} + req = urllib.request.Request(url, headers=headers, method=method) + with urllib.request.urlopen(req, timeout=60) as r: + raw = r.read() + return json.loads(raw) if raw else {} + + +def _run(fn): + try: + print(json.dumps(fn(_cfg(), _inputs()))) + except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) + except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) + + +q = lambda v: urllib.parse.quote(str(v), safe="") + + +def main(cfg, inputs): + return request("GET", "/account/v3/projects", cfg) + + +_run(main) diff --git a/integrations/scaleway/scripts/list_security_groups.py b/integrations/scaleway/scripts/list_security_groups.py new file mode 100644 index 0000000..80f5f4a --- /dev/null +++ b/integrations/scaleway/scripts/list_security_groups.py @@ -0,0 +1,50 @@ +import json, os, sys, urllib.parse, urllib.request, urllib.error + +BASE = "https://api.scaleway.com" + + +def _cfg(): + return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + + +def _inputs(): + return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + + +def _zone(cfg, inputs): + return (inputs.get("zone") if inputs else None) or cfg.get("default_zone") or "fr-par-1" + + +def request(method, path, cfg, params=None): + url = BASE + path + if params: + clean = {k: v for k, v in params.items() if v not in (None, "")} + if clean: + url += "?" + urllib.parse.urlencode(clean) + headers = {"X-Auth-Token": str(cfg.get("secret_key", "")), "Accept": "application/json"} + req = urllib.request.Request(url, headers=headers, method=method) + with urllib.request.urlopen(req, timeout=60) as r: + raw = r.read() + return json.loads(raw) if raw else {} + + +def _run(fn): + try: + print(json.dumps(fn(_cfg(), _inputs()))) + except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) + except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) + + +q = lambda v: urllib.parse.quote(str(v), safe="") + + +def main(cfg, inputs): + zone = _zone(cfg, inputs) + return request("GET", "/instance/v1/zones/" + q(zone) + "/security_groups", cfg) + + +_run(main) diff --git a/integrations/scaleway/scripts/test_connection.py b/integrations/scaleway/scripts/test_connection.py new file mode 100644 index 0000000..d9cb8e6 --- /dev/null +++ b/integrations/scaleway/scripts/test_connection.py @@ -0,0 +1,50 @@ +import json, os, sys, urllib.parse, urllib.request, urllib.error + +BASE = "https://api.scaleway.com" + + +def _cfg(): + return json.loads(os.environ.get("INTEGRATION_SECRETS", "{}")) + + +def _inputs(): + return json.loads(os.environ.get("INTEGRATION_INPUTS", "{}")) + + +def _zone(cfg, inputs): + return (inputs.get("zone") if inputs else None) or cfg.get("default_zone") or "fr-par-1" + + +def request(method, path, cfg, params=None): + url = BASE + path + if params: + clean = {k: v for k, v in params.items() if v not in (None, "")} + if clean: + url += "?" + urllib.parse.urlencode(clean) + headers = {"X-Auth-Token": str(cfg.get("secret_key", "")), "Accept": "application/json"} + req = urllib.request.Request(url, headers=headers, method=method) + with urllib.request.urlopen(req, timeout=60) as r: + raw = r.read() + return json.loads(raw) if raw else {} + + +def _run(fn): + try: + print(json.dumps(fn(_cfg(), _inputs()))) + except urllib.error.HTTPError as e: + print(json.dumps({"error": "HTTP " + str(e.code), "detail": e.read().decode("utf-8", "replace")})) + sys.exit(1) + except Exception as e: + print(json.dumps({"error": str(e)})) + sys.exit(1) + + +q = lambda v: urllib.parse.quote(str(v), safe="") + + +def main(cfg, inputs): + request("GET", "/account/v3/projects", cfg) + return {"ok": True} + + +_run(main)