Fix real-deployment blockers and scope down to router-only VMs

Confirmed working against a real VK Cloud PROD deployment (3 routers,
19 resources, apply succeeded end to end). Fixes found along the way:

- provider "vkcs" was never configured (versions.tf) - username/password/
  project_id/region were declared but wired to nothing; added auth_url and
  user_domain_name to complete it.
- Nova keypairs are per-user, not per-project - added an optional
  vkcs_compute_keypair resource (var.ssh_public_key) so Terraform can
  register a keypair under the deploying service account itself.
- router_priv_port used a hand-computed fixed_ip offset that collided with
  VKCS's own auto-created service ports on each network (observed: a
  "network:dns" port) - now left unset so Neutron's IPAM auto-assigns,
  which is collision-free by construction.
- vkcs_compute_instance set image_id at the top level while also booting
  from a volume via block_device - the provider docs say not to do this;
  Nova echoes back a sentinel string for image_id on a volume-booted
  server, which Terraform read as drift on a ForceNew attribute and
  wanted to destroy+recreate every already-created instance on every
  subsequent plan.
- private_network_cidrs bumped from /29 to /28 - too tight once the
  platform's own reserved ports are accounted for.

Also removed the priv_srv_01/02/03 demo instances and the LAN network/
security group only they used - this deployment provisions router VMs
only, confirmed with the user.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011hXR2ftXZZhJ4Y3XuSoR8r
This commit is contained in:
ayurishchevandClaude Sonnet 5 committed 2026-09-07 08:55:15 +03:00
1 parent b5d6367fd8
commit 8886c1baad
13 files changed
+506 -171

No files matched your search

+35 -136
View File
@@ -28,25 +28,18 @@ locals {
)
}
# LAN Network
resource "vkcs_networking_network" "lan_net" {
name = "router-lan-net"
sdn = "sprut"
admin_state_up = true
# Nova keypairs are per-user, not per-project - a keypair uploaded under a
# different account is invisible to whichever account Terraform deploys as.
# Register it here from var.ssh_public_key when supplied; otherwise assume
# var.ssh_key_name already exists under the deploying account.
resource "vkcs_compute_keypair" "router" {
count = var.ssh_public_key != null ? 1 : 0
name = var.ssh_key_name
public_key = var.ssh_public_key
}
resource "vkcs_networking_subnet" "lan_subnet" {
network_id = vkcs_networking_network.lan_net.id
name = "router-lan-subnet"
cidr = "10.200.10.0/24"
gateway_ip = "10.200.10.1"
dns_nameservers = ["8.8.8.8", "1.1.1.1"]
sdn = "sprut"
allocation_pool {
start = "10.200.10.100"
end = "10.200.10.200"
}
locals {
ssh_key_name = var.ssh_public_key != null ? vkcs_compute_keypair.router[0].name : var.ssh_key_name
}
# Security Groups
@@ -55,33 +48,6 @@ resource "vkcs_networking_secgroup" "router_sg" {
sdn = "sprut"
}
resource "vkcs_networking_secgroup" "private_sg" {
name = "private-sg"
sdn = "sprut"
}
# Private SG rules
resource "vkcs_networking_secgroup_rule" "from_rfc_net192_in" {
direction = "ingress"
remote_ip_prefix = "192.168.0.0/16"
security_group_id = vkcs_networking_secgroup.private_sg.id
sdn = "sprut"
}
resource "vkcs_networking_secgroup_rule" "from_rfc_net172_in" {
direction = "ingress"
remote_ip_prefix = "172.16.0.0/12"
security_group_id = vkcs_networking_secgroup.private_sg.id
sdn = "sprut"
}
resource "vkcs_networking_secgroup_rule" "from_rfc_net10_in" {
direction = "ingress"
remote_ip_prefix = "10.0.0.0/8"
security_group_id = vkcs_networking_secgroup.private_sg.id
sdn = "sprut"
}
# Router SG rules
resource "vkcs_networking_secgroup_rule" "router_ssh" {
direction = "ingress"
@@ -122,8 +88,8 @@ resource "vkcs_networking_secgroup_rule" "router_ipsec_nat_t" {
}
# Per-router private interfaces: one shared private network per admin-supplied
# CIDR in private_network_cidrs (no shared LAN with the priv_srv_* segment,
# no VRRP), each router getting its own port/IP inside every such network.
# CIDR in private_network_cidrs (no VRRP), each router getting its own
# port/IP inside every such network.
locals {
private_roles = [for idx in range(length(var.private_network_cidrs)) : "priv${idx + 1}"]
@@ -147,36 +113,50 @@ resource "vkcs_networking_subnet" "router_priv_subnet" {
cidr = each.value
gateway_ip = cidrhost(each.value, 1)
sdn = "sprut"
# No in-guest DHCP client is needed for these interfaces: with
# config_drive = true (set on every router instance), cloud-init learns
# each interface's assigned address from the config-drive metadata and
# renders it directly, then network-init.sh.tpl re-pins it deterministically
# to ethN by MAC - no actual DHCP protocol exchange with this subnet ever
# happens. (Note: this does NOT avoid IP collisions with VKCS's own
# auto-created service ports on the network, e.g. a "network:dns" port -
# see router_priv_port below, which leaves ip_address unset instead.)
enable_dhcp = false
}
resource "vkcs_networking_port" "router_priv_port" {
for_each = {
for pair in setproduct(range(var.router_count), local.private_roles) :
"router${pair[0] + 1}-${pair[1]}" => {
router_index = pair[0]
role = pair[1]
}
"router${pair[0] + 1}-${pair[1]}" => pair[1]
}
name = "router-${each.key}-port"
network_id = vkcs_networking_network.router_priv_net[each.value.role].id
network_id = vkcs_networking_network.router_priv_net[each.value].id
admin_state_up = true
port_security_enabled = false
full_security_groups_control = true
security_group_ids = []
sdn = "sprut"
# No explicit ip_address: let Neutron's IPAM auto-assign one from the
# subnet's pool. VKCS auto-creates its own service ports on this network
# (observed: a "network:dns" port) that can silently consume whichever
# low address a hand-computed offset would have picked, causing
# IpAddressAlreadyAllocated - IPAM guarantees no double-booking, our own
# arithmetic doesn't. network-init.sh.tpl matches interfaces by which
# declared CIDR their live IP falls into, not by an exact expected IP, so
# the assigned address doesn't need to be known in advance.
fixed_ip {
subnet_id = vkcs_networking_subnet.router_priv_subnet[each.value.role].id
ip_address = cidrhost(local.private_network_cidr[each.value.role], each.value.router_index + 2)
subnet_id = vkcs_networking_subnet.router_priv_subnet[each.value].id
}
}
resource "vkcs_compute_instance" "router" {
count = var.router_count
name = "router${count.index + 1}"
image_id = data.vkcs_images_image.ubuntu24.id
flavor_name = "STD3-4-4"
availability_zone = element(var.router_availability_zones, count.index % length(var.router_availability_zones))
key_pair = var.ssh_key_name
key_pair = local.ssh_key_name
security_group_ids = [
vkcs_networking_secgroup.router_sg.id,
@@ -220,84 +200,3 @@ resource "vkcs_compute_instance" "router" {
}
}
resource "vkcs_compute_instance" "priv_srv_01" {
name = "Priv-SRV-01"
image_id = data.vkcs_images_image.ubuntu24.id
flavor_name = "STD3-4-4"
availability_zone = "ME1"
key_pair = var.ssh_key_name
security_group_ids = [
vkcs_networking_secgroup.private_sg.id,
local.default_security_group_id
]
network {
uuid = vkcs_networking_network.lan_net.id
}
block_device {
uuid = data.vkcs_images_image.ubuntu24.id
source_type = "image"
volume_size = 20
boot_index = 0
destination_type = "volume"
volume_type = "ceph-ssd"
delete_on_termination = true
}
}
resource "vkcs_compute_instance" "priv_srv_02" {
name = "Priv-SRV-02"
image_id = data.vkcs_images_image.ubuntu24.id
flavor_name = "STD3-4-4"
availability_zone = "ME1"
key_pair = var.ssh_key_name
security_group_ids = [
vkcs_networking_secgroup.private_sg.id,
local.default_security_group_id
]
network {
uuid = vkcs_networking_network.lan_net.id
}
block_device {
uuid = data.vkcs_images_image.ubuntu24.id
source_type = "image"
volume_size = 20
boot_index = 0
destination_type = "volume"
volume_type = "ceph-ssd"
delete_on_termination = true
}
}
resource "vkcs_compute_instance" "priv_srv_03" {
name = "Priv-SRV-03"
image_id = data.vkcs_images_image.ubuntu24.id
flavor_name = "STD3-4-4"
availability_zone = "MS1"
key_pair = var.ssh_key_name
security_group_ids = [
vkcs_networking_secgroup.private_sg.id,
local.default_security_group_id
]
network {
uuid = vkcs_networking_network.lan_net.id
}
block_device {
uuid = data.vkcs_images_image.ubuntu24.id
source_type = "image"
volume_size = 20
boot_index = 0
destination_type = "volume"
volume_type = "ceph-ssd"
delete_on_termination = true
}
}
+29
View File
@@ -0,0 +1,29 @@
# Example of the *.auto.tfvars overlay pattern for real credentials.
#
# terraform.tfvars stays a committed, anonymized template. To deploy with
# real credentials (e.g. from an openrc.sh for a service account), copy this
# file to prod.auto.tfvars (gitignored - see .gitignore) and fill in real
# values. Terraform auto-loads *.auto.tfvars in addition to terraform.tfvars,
# so this layers on top of the template without ever modifying/committing it.
#
# Mapping from an OpenStack-style openrc.sh:
# OS_AUTH_URL -> auth_url
# OS_USERNAME -> username
# OS_PASSWORD -> password
# OS_PROJECT_ID -> project_id
# OS_REGION_NAME -> region
# OS_USER_DOMAIN_NAME -> user_domain_name
auth_url = "https://infra.mail.ru:35357/v3/"
username = "svc-<project_id>-svc-deployer"
password = "<real password - never commit this>"
project_id = "<project_id>"
region = "RegionOne"
user_domain_name = "service-users"
# Optional: Nova keypairs are per-user, not per-project - a keypair uploaded
# under a different account (e.g. your personal VK Cloud login) is invisible
# to a service account. Set this to have Terraform register var.ssh_key_name
# under the deploying account from this public key. Leave unset if a keypair
# with that name already exists under the deploying account.
ssh_public_key = "ssh-ed25519 AAAA... or ssh-rsa AAAA... your-public-key-content"
+7 -4
View File
@@ -1,15 +1,18 @@
username = "user@domain.local"
password = "DemoUserPassw"
project_id = "XXXXXd424998422XXXXXf13ac9XXXXX"
ssh_key_name = "AdminSSH"
ssh_key_name = "mcs_ru"
# Adaptive router VM count (has a default - see variables.tf - uncomment to override).
# router_count = 3
router_count = 3
# router_availability_zones = ["ME1"]
# One CIDR per private network that router VMs get an interface into.
# No default - must be supplied explicitly. List order determines eth1..ethN.
# /28 (not /29): VKCS auto-creates its own service ports on each network
# (observed: a "network:dns" port) that consume addresses from the pool
# too, and a /29 (5 usable) proved too tight in practice.
private_network_cidrs = [
"10.90.0.0/29",
"10.90.0.8/29",
"10.90.0.0/28",
"10.90.0.16/28",
]
+235 -14
View File
@@ -97,7 +97,9 @@ def find_data_sources(doc, dtype):
REQUIRED_FILES = [
"main.tf",
"variables.tf",
"versions.tf",
"terraform.tfvars",
"prod.auto.tfvars.example",
"scripts/network-init.sh.tpl",
]
@@ -286,7 +288,7 @@ def test_default_security_group_id_override_accepted(override, should_pass):
# ---------------------------------------------------------------------------
@pytest.mark.parametrize("relpath", ["main.tf", "variables.tf", "images.tf"])
@pytest.mark.parametrize("relpath", ["main.tf", "variables.tf", "versions.tf", "images.tf"])
def test_hcl_file_parses(relpath):
# images.tf is intentionally fully commented out (disabled helper data
# source) - it must still parse cleanly, just possibly to an empty doc.
@@ -294,6 +296,86 @@ def test_hcl_file_parses(relpath):
assert isinstance(doc, dict)
# ---------------------------------------------------------------------------
# versions.tf: the provider is actually configured (auth variables used to
# be declared in variables.tf but never wired to anything - regression
# guard against that gap reappearing)
# ---------------------------------------------------------------------------
def test_provider_vkcs_wires_all_auth_variables():
versions = load_tf("versions.tf")
provider_blocks = versions.get("provider", [])
vkcs_provider = None
for block in provider_blocks:
if "vkcs" in block:
vkcs_provider = block["vkcs"]
assert vkcs_provider is not None, "expected a provider \"vkcs\" block in versions.tf"
expected = {
"auth_url": "${var.auth_url}",
"username": "${var.username}",
"password": "${var.password}",
"project_id": "${var.project_id}",
"region": "${var.region}",
"user_domain_name": "${var.user_domain_name}",
}
for attr, expr in expected.items():
assert vkcs_provider.get(attr) == [expr], (
f"provider \"vkcs\" must set {attr} = var.{attr} - "
f"auth variables must not be left orphaned/unwired"
)
@pytest.mark.parametrize("name", ["auth_url", "user_domain_name", "region"])
def test_auth_variable_has_a_default(name):
v = find_variable(load_tf("variables.tf"), name)
assert v is not None, f"variable {name} is missing"
assert "default" in v, f"{name} should have a sane default so it doesn't have to be set explicitly for the common case"
# ---------------------------------------------------------------------------
# Real secrets must never land in the git-tracked terraform.tfvars template -
# they belong in a gitignored *.auto.tfvars overlay instead (see
# prod.auto.tfvars.example)
# ---------------------------------------------------------------------------
def test_gitignore_excludes_auto_tfvars_overlay():
gitignore_text = (REPO_ROOT / ".gitignore").read_text()
assert "*.auto.tfvars" in gitignore_text, (
"*.auto.tfvars must be gitignored - real credentials are meant to be "
"layered on top of terraform.tfvars via such a file, never committed"
)
def test_prod_auto_tfvars_example_is_not_gitignored():
"""The *.example file documents the overlay pattern and must ship in the
repo (unlike the real *.auto.tfvars it documents)."""
result = subprocess.run(
["git", "check-ignore", "terraform/prod.auto.tfvars.example"],
cwd=REPO_ROOT,
capture_output=True,
text=True,
)
assert result.returncode != 0, "prod.auto.tfvars.example must NOT be gitignored"
def test_tfvars_template_has_no_auth_credentials():
"""terraform.tfvars is a committed, anonymized template - auth_url/
user_domain_name/real credentials belong in a gitignored *.auto.tfvars
overlay, not here."""
tfvars_text = (TERRAFORM_DIR / "terraform.tfvars").read_text()
active_lines = [
line for line in tfvars_text.splitlines() if not line.strip().startswith("#")
]
for forbidden in ("auth_url", "user_domain_name"):
assert not any(re.match(rf"^\s*{forbidden}\s*=", line) for line in active_lines), (
f"{forbidden} should not be set in the committed terraform.tfvars "
f"template - use a gitignored *.auto.tfvars overlay instead"
)
# ---------------------------------------------------------------------------
# variables.tf: the scaling knobs exist as expected
# ---------------------------------------------------------------------------
@@ -319,17 +401,21 @@ def test_private_network_cidrs_variable():
)
def test_router_count_not_pinned_in_tfvars():
"""TF_VAR_router_count only takes effect if terraform.tfvars doesn't set
an active (non-comment) value for it."""
def test_router_count_pinned_in_tfvars_is_a_valid_number():
"""terraform.tfvars now pins a real router_count for this project's
actual PROD deployment (a deliberate choice, confirmed with the user) -
a tfvars-file value always beats a TF_VAR_ environment variable in
Terraform's precedence order, so TF_VAR_router_count no longer has any
effect while this stays set. Just sanity-check it's a positive integer,
not that it's absent."""
tfvars_text = (TERRAFORM_DIR / "terraform.tfvars").read_text()
active_lines = [
line for line in tfvars_text.splitlines() if not line.strip().startswith("#")
]
assert not any(re.match(r"^\s*router_count\s*=", line) for line in active_lines), (
"router_count must not be set in terraform.tfvars, or the "
"TF_VAR_router_count environment variable would be shadowed"
)
matches = [line for line in active_lines if re.match(r"^\s*router_count\s*=", line)]
assert matches, "expected router_count to be pinned in terraform.tfvars for this deployment"
value = int(matches[0].split("=", 1)[1].strip())
assert value >= 1
def test_private_network_cidrs_is_set_in_tfvars():
@@ -357,6 +443,24 @@ def test_router_resource_uses_count_variable():
assert instances["router"]["count"] == ["${var.router_count}"]
def test_compute_instances_do_not_set_top_level_image_id():
"""Regression guard: the provider docs say 'Do not specify [image_id]
if booting from a volume' - doing so anyway caused every already-created
instance to be flagged for destroy+recreate on the next plan, because
Nova reports back a sentinel string ("Attempt to boot from volume - no
image supplied") instead of echoing the image UUID for a volume-booted
server, which Terraform then sees as configuration drift on a
ForceNew attribute. The image only belongs inside block_device."""
main = load_tf("main.tf")
for name, attrs in find_resources(main, "vkcs_compute_instance"):
assert "image_id" not in attrs, (
f"vkcs_compute_instance.{name} must not set top-level image_id "
f"when booting from a volume via block_device"
)
assert attrs["block_device"][0]["source_type"] == ["image"]
assert "uuid" in attrs["block_device"][0]
def test_no_legacy_hardcoded_router_resources():
main = load_tf("main.tf")
instance_names = {name for name, _ in find_resources(main, "vkcs_compute_instance")}
@@ -371,6 +475,22 @@ def test_no_legacy_hardcoded_router_resources():
)
def test_deployment_is_router_only():
"""This deployment provisions only the router VMs - the demo's
priv_srv_01/02/03 instances and the shared LAN network/security group
that only they used were removed as out of scope (confirmed with the
user)."""
main = load_tf("main.tf")
instance_names = {name for name, _ in find_resources(main, "vkcs_compute_instance")}
assert instance_names == {"router"}, f"expected only the router instance, found {instance_names}"
network_names = {name for name, _ in find_resources(main, "vkcs_networking_network")}
assert "lan_net" not in network_names
secgroup_names = {name for name, _ in find_resources(main, "vkcs_networking_secgroup")}
assert secgroup_names == {"router_sg"}, f"expected only router_sg, found {secgroup_names}"
def test_private_roles_local_driven_by_variable():
main = load_tf("main.tf")
private_roles = find_local(main, "private_roles")
@@ -437,12 +557,109 @@ def test_all_instances_use_default_security_group_local():
)
# ---------------------------------------------------------------------------
# main.tf: SSH keypair - Nova keypairs are per-user, not per-project, so a
# keypair uploaded under a different account is invisible to the deploying
# one. var.ssh_public_key lets Terraform register it itself.
# ---------------------------------------------------------------------------
def test_ssh_public_key_variable_defaults_to_null():
v = find_variable(load_tf("variables.tf"), "ssh_public_key")
assert v is not None, "variable ssh_public_key is missing"
assert v["default"] == [None], "ssh_public_key should default to null (register nothing unless supplied)"
def test_keypair_resource_conditionally_registers_public_key():
main = load_tf("main.tf")
keypairs = dict(find_resources(main, "vkcs_compute_keypair"))
assert "router" in keypairs, "expected a vkcs_compute_keypair.router resource"
kp = keypairs["router"]
assert kp["count"] == ["${var.ssh_public_key != None ? 1 : 0}"], (
"the keypair should only be created when ssh_public_key is actually supplied"
)
assert kp["name"] == ["${var.ssh_key_name}"]
assert kp["public_key"] == ["${var.ssh_public_key}"]
def test_ssh_key_name_local_prefers_registered_keypair():
main = load_tf("main.tf")
value = find_local(main, "ssh_key_name")
assert value == [
"${var.ssh_public_key != None ? vkcs_compute_keypair.router[0].name : var.ssh_key_name}"
], (
"locals.ssh_key_name must use the keypair Terraform registers itself "
"when ssh_public_key is supplied, falling back to var.ssh_key_name "
"(an already-existing keypair) otherwise"
)
def test_all_instances_use_ssh_key_name_local():
main = load_tf("main.tf")
for name, attrs in find_resources(main, "vkcs_compute_instance"):
assert attrs["key_pair"] == ["${local.ssh_key_name}"], (
f"vkcs_compute_instance.{name} must reference local.ssh_key_name, "
f"not var.ssh_key_name directly, so it depends on the keypair "
f"resource when one is registered"
)
# ---------------------------------------------------------------------------
# main.tf: router private subnets don't need Neutron's DHCP service - with
# config_drive = true, cloud-init learns each interface's address from
# config-drive metadata rather than an actual DHCP exchange, and
# network-init.sh.tpl re-pins it deterministically to ethN afterwards
# ---------------------------------------------------------------------------
def test_router_priv_subnet_has_dhcp_disabled():
main = load_tf("main.tf")
subnets = dict(find_resources(main, "vkcs_networking_subnet"))
assert "router_priv_subnet" in subnets
assert subnets["router_priv_subnet"]["enable_dhcp"] == [False], (
"router_priv_subnet must have enable_dhcp = false - no in-guest "
"DHCP client is ever used on these interfaces"
)
def test_router_priv_port_leaves_ip_address_unset():
"""Regression guard: a hand-computed fixed_ip.ip_address collided with
VKCS's own auto-created service ports on the network (observed: a
"network:dns" port silently consuming an address) during a real PROD
deployment. Leaving ip_address unset lets Neutron's IPAM auto-assign
one, which is guaranteed collision-free; network-init.sh.tpl matches
interfaces by which declared CIDR their live IP falls into, not by an
exact expected IP, so this doesn't need to be known in advance."""
main = load_tf("main.tf")
ports = dict(find_resources(main, "vkcs_networking_port"))
assert "router_priv_port" in ports
fixed_ip = ports["router_priv_port"]["fixed_ip"][0]
assert "ip_address" not in fixed_ip, (
"router_priv_port.fixed_ip must not set ip_address - let Neutron's "
"IPAM auto-assign to avoid colliding with platform-reserved ports"
)
assert "subnet_id" in fixed_ip
# ---------------------------------------------------------------------------
# terraform.tfvars example CIDRs: sanity-check the values actually shipped
# (no auto-carving anymore - these come straight from the admin/example)
# ---------------------------------------------------------------------------
def _configured_router_count():
"""The router_count actually in effect: whatever terraform.tfvars pins,
else the variable's default (a tfvars value always beats the default)."""
tfvars_text = (TERRAFORM_DIR / "terraform.tfvars").read_text()
for line in tfvars_text.splitlines():
if line.strip().startswith("#"):
continue
m = re.match(r"^\s*router_count\s*=\s*(\d+)", line)
if m:
return int(m.group(1))
return find_variable(load_tf("variables.tf"), "router_count")["default"][0]
def test_tfvars_private_network_cidrs_do_not_overlap_and_have_room_for_routers():
tfvars = load_tf("terraform.tfvars")
raw_cidrs = tfvars["private_network_cidrs"][0]
@@ -453,13 +670,17 @@ def test_tfvars_private_network_cidrs_do_not_overlap_and_have_room_for_routers()
for b in networks[i + 1 :]:
assert not a.overlaps(b), f"{a} overlaps {b} in terraform.tfvars"
router_count_default = find_variable(load_tf("variables.tf"), "router_count")["default"][0]
router_count = _configured_router_count()
for net in networks:
# offset scheme: cidrhost(cidr, router_index + 2) for router_index in
# 0..router_count-1, so we need at least router_count + 2 addresses.
assert net.num_addresses >= router_count_default + 2, (
f"{net} has too few addresses for {router_count_default} routers "
f"(offset scheme needs router_count + 2)"
# ip_address is left unset on each port (Neutron IPAM auto-assigns -
# see router_priv_port in main.tf), so there's no fixed per-router
# offset to reserve room for - but VKCS auto-creates its own service
# ports on the network (observed: one "network:dns" port consuming
# an address), so there must be room for router_count routers plus
# at least one such reservation, on top of network/gateway/broadcast.
assert net.num_addresses >= router_count + 3, (
f"{net} has too few addresses for {router_count} routers plus "
f"platform-reserved ports (e.g. VKCS's network:dns service port)"
)
+20 -2
View File
@@ -15,9 +15,21 @@ variable "project_id" {
}
variable "region" {
description = "Region"
description = "OpenStack region (VK Cloud typically has a single region, \"RegionOne\" - not to be confused with availability zones like \"ME1\"/\"MS1\")"
type = string
default = "ME1"
default = "RegionOne"
}
variable "auth_url" {
description = "VK Cloud Identity (Keystone) auth URL"
type = string
default = "https://infra.mail.ru:35357/v3/"
}
variable "user_domain_name" {
description = "Keystone domain the auth user resides in ('users' for regular accounts, 'service-users' for svc-* service accounts)"
type = string
default = "users"
}
variable "ssh_key_name" {
@@ -25,6 +37,12 @@ variable "ssh_key_name" {
type = string
}
variable "ssh_public_key" {
description = "OpenSSH public key content to register as the ssh_key_name keypair under the deploying account (Nova keypairs are per-user, not per-project - a key uploaded under a different account is invisible here). Leave null if a keypair with that name already exists under the deploying account."
type = string
default = null
}
variable "router_count" {
description = "Number of IaaS Router VMs to provision"
type = number
+9
View File
@@ -6,3 +6,12 @@ terraform {
}
}
}
provider "vkcs" {
auth_url = var.auth_url
username = var.username
password = var.password
project_id = var.project_id
region = var.region
user_domain_name = var.user_domain_name
}