Browse Source

removed old terraform k8s examples and refered to current repo

master
Sebastian Rieger 7 months ago
parent
commit
317e8f4bb5
  1. 179
      terraform/rancher-terraform/example-templates/cluster-options-example.yml
  2. 49
      terraform/rancher-terraform/example-templates/node-template-example.json
  3. 500
      terraform/rancher-terraform/main.tf
  4. 20
      terraform/rke-terraform/main.tf
  5. 6
      terraform/rke-terraform/user-data.sh
  6. 0
      terraform/rke2-terraform/README.md
  7. 11
      terraform/rke2-terraform/main.tf
  8. 5
      terraform/rke2-terraform/outputs.tf
  9. 9
      terraform/rke2-terraform/variables.tf

179
terraform/rancher-terraform/example-templates/cluster-options-example.yml

@ -1,179 +0,0 @@
#
# Cluster Config
#
docker_root_dir: /var/lib/docker
enable_cluster_alerting: false
enable_cluster_monitoring: false
enable_network_policy: false
local_cluster_auth_endpoint:
enabled: true
name: openstack-rke
#
# Rancher Config
#
rancher_kubernetes_engine_config:
#####################################################################
#
# Config for OpenStack @ NetLab Hochschule Fulda Start
#
# Paste the following section into rancher_kubernetes_engine_config
# be sure to use correct indention, if in doubt, use YAML syntax
# checker
#
# You need to replace tenant-id with your project id, you can see
# the id, e.g., in the OpenStack Web Interface (Horizon) here:
# - https://private-cloud.informatik.hs-fulda.de/project/api_access/view_credentials/
#
# Replace floating-network-id with the id of the network "public1".
# Click in network "public1" here:
# - https://private-cloud.informatik.hs-fulda.de/project/networks/
# and use the value shown for ID.
#
# Replace subnet-id with the id of the subnet (not network!) that you
# use for your RKE node instances. If you use network
# "my-terraform-rancher-network-1" you can got to:
# - https://private-cloud.informatik.hs-fulda.de/project/networks/
# click on my-terraform-rancher-network-1, then click on its subnet
# my-terraform-rancher-subnet-1, and use the shown ID of the subnet.
#
# Replace the router-id with the id of your router. Go to:
# - https://private-cloud.informatik.hs-fulda.de/project/routers/
# click on the router you use for the network of your RKE instances
# and use the shown ID of this router.
#
# Replace password with the password of your groups' OpenStack
# account
#
# You can also see other config options in RKE docu here:
# https://rancher.com/docs/rke/latest/en/config-options/cloud-providers/openstack/
#
#####################################################################
cloud_provider:
name: openstack
openstackCloudProvider:
block_storage:
ignore-volume-az: true
trust-device-path: false
global:
auth-url: 'https://private-cloud.informatik.hs-fulda.de:5000'
domain-name: Default
tenant-id: <id of your project here>
username: IntServ19
password: <your password here>
load_balancer:
create-monitor: false
floating-network-id: <id of network public1>
lb-version: v2
manage-security-groups: true
monitor-max-retries: 0
subnet-id: <id of the subnet your use for rke instances>
use-octavia: true
metadata:
request-timeout: 0
route:
router-id: <id of the router you use for the rke instances>
#####################################################################
#
# Config for OpenStack @ NetLab Hochschule Fulda End
#
#####################################################################
addon_job_timeout: 45
authentication:
strategy: x509
dns:
nodelocal:
ip_address: ''
node_selector: null
update_strategy: {}
enable_cri_dockerd: false
ignore_docker_version: true
#
# # Currently only nginx ingress provider is supported.
# # To disable ingress controller, set `provider: none`
# # To enable ingress on specific nodes, use the node_selector, eg:
# provider: nginx
# node_selector:
# app: ingress
#
ingress:
default_backend: false
default_ingress_class: true
http_port: 0
https_port: 0
provider: nginx
kubernetes_version: v1.21.8-rancher1-1
monitoring:
provider: metrics-server
replicas: 1
#
# If you are using calico on AWS
#
# network:
# plugin: calico
# calico_network_provider:
# cloud_provider: aws
#
# # To specify flannel interface
#
# network:
# plugin: flannel
# flannel_network_provider:
# iface: eth1
#
# # To specify flannel interface for canal plugin
#
# network:
# plugin: canal
# canal_network_provider:
# iface: eth1
#
network:
mtu: 0
options:
flannel_backend_type: vxlan
plugin: canal
rotate_encryption_key: false
#
# services:
# kube-api:
# service_cluster_ip_range: 10.43.0.0/16
# kube-controller:
# cluster_cidr: 10.42.0.0/16
# service_cluster_ip_range: 10.43.0.0/16
# kubelet:
# cluster_domain: cluster.local
# cluster_dns_server: 10.43.0.10
#
services:
etcd:
backup_config:
enabled: true
interval_hours: 12
retention: 6
safe_timestamp: false
timeout: 300
creation: 12h
extra_args:
election-timeout: 5000
heartbeat-interval: 500
gid: 0
retention: 72h
snapshot: false
uid: 0
kube_api:
always_pull_images: false
pod_security_policy: false
secrets_encryption_config:
enabled: false
service_node_port_range: 30000-32767
ssh_agent_auth: false
upgrade_strategy:
max_unavailable_controlplane: '1'
max_unavailable_worker: 10%
node_drain_input:
delete_local_data: false
force: false
grace_period: -1
ignore_daemon_sets: true
timeout: 120
windows_prefered_cluster: false

49
terraform/rancher-terraform/example-templates/node-template-example.json

@ -1,49 +0,0 @@
{
"driver": "openstack",
"name": "openstack-template",
"openstackConfig": {
"activeTimeout": "200",
"applicationCredentialId": "",
"applicationCredentialName": "",
"applicationCredentialSecret": "",
"authUrl": "https://private-cloud.informatik.hs-fulda.de:5000",
"availabilityZone": "nova",
"bootFromVolume": false,
"cacert": "",
"configDrive": false,
"domainId": "",
"domainName": "Default",
"endpointType": "",
"flavorId": "",
"flavorName": "m1.medium",
"floatingipPool": "public1",
"imageId": "",
"imageName": "Ubuntu 20.04 - Focal Fossa - 64-bit - Cloud Based Image",
"insecure": false,
"ipVersion": "4",
"keypairName": "rancher-key",
"netId": "",
"netName": "my-terraform-rancher-network-1",
"novaNetwork": false,
"region": "RegionOne",
"secGroups": "my-terraform-rancher-secgroup",
"sshPort": "22",
"sshUser": "ubuntu",
"tenantDomainId": "",
"tenantDomainName": "Default",
"tenantId": "",
"tenantName": "IntServ19",
"userDataFile": "",
"userDomainId": "",
"userDomainName": "Default",
"userId": "",
"username": "IntServ19",
"password": "<your password here>",
"volumeDevicePath": "",
"volumeId": "",
"volumeName": "",
"volumeSize": "0",
"volumeType": ""
},
"type": "nodeTemplate",
}

500
terraform/rancher-terraform/main.tf

@ -1,500 +0,0 @@
# Define CloudComp group number
# TODO: change to use OS env vars etc.
variable "group_number" {
type = string
default = "22"
}
## OpenStack credentials can be used in a more secure way by using
## cloud.yaml from https://private-cloud.informatik.hs-fulda.de/project/api_access/clouds.yaml/
# Define OpenStack credentials, project config etc.
locals {
auth_url = "https://private-cloud.informatik.hs-fulda.de:5000/v3"
user_name = "CloudComp${var.group_number}"
user_password = "<password of your group here, private-cloud is only reachable via vpn>"
tenant_name = "CloudComp${var.group_number}"
#network_name = "CloudComp${var.group_number}-net"
router_name = "CloudComp${var.group_number}-router"
image_name = "Ubuntu 20.04 - Focal Fossa - 64-bit - Cloud Based Image"
flavor_name = "m1.medium"
region_name = "RegionOne"
rke_flavor_name = "m1.medium"
availability_zone = "nova"
domain_name = "Default"
# possibly set floating_ip_pool = "" to avoid assigning floating ips to
# every created node and use only load balancer as frontend, however needed
# for node port forwarding etc. using kube proxy
floating_ip_pool = "public1"
ssh_user = "ubuntu"
}
# Define OpenStack provider
terraform {
required_version = ">= 0.14.0"
required_providers {
openstack = {
source = "terraform-provider-openstack/openstack"
version = ">= 1.47.0"
}
rancher2 = {
source = "rancher/rancher2"
version = ">= 1.24.0"
}
}
}
# Configure the OpenStack Provider
provider "openstack" {
user_name = local.user_name
tenant_name = local.tenant_name
password = local.user_password
auth_url = local.auth_url
region = local.region_name
use_octavia = true
}
###########################################################################
#
# create keypair
#
###########################################################################
# import keypair, if public_key is not specified, create new keypair to use
resource "openstack_compute_keypair_v2" "terraform-rancher-keypair" {
name = "my-terraform-rancher-pubkey"
# public_key = file("~/srieger_rsa.pub")
}
###########################################################################
#
# create security group
#
###########################################################################
resource "openstack_networking_secgroup_v2" "terraform-rancher-secgroup" {
name = "my-terraform-rancher-secgroup"
description = "for terraform rancher instances"
}
# TODO: possibly cleanup unnecessary ports?
resource "openstack_networking_secgroup_rule_v2" "terraform-secgroup-rule-ssh" {
direction = "ingress"
ethertype = "IPv4"
protocol = "tcp"
port_range_min = 22
port_range_max = 22
#remote_ip_prefix = "0.0.0.0/0"
security_group_id = openstack_networking_secgroup_v2.terraform-rancher-secgroup.id
}
resource "openstack_networking_secgroup_rule_v2" "terraform-secgroup-rule-http" {
direction = "ingress"
ethertype = "IPv4"
protocol = "tcp"
port_range_min = 80
port_range_max = 80
#remote_ip_prefix = "0.0.0.0/0"
security_group_id = openstack_networking_secgroup_v2.terraform-rancher-secgroup.id
}
resource "openstack_networking_secgroup_rule_v2" "terraform-secgroup-rule-https" {
direction = "ingress"
ethertype = "IPv4"
protocol = "tcp"
port_range_min = 443
port_range_max = 443
#remote_ip_prefix = "0.0.0.0/0"
security_group_id = openstack_networking_secgroup_v2.terraform-rancher-secgroup.id
}
resource "openstack_networking_secgroup_rule_v2" "terraform-secgroup-rule-2376" {
direction = "ingress"
ethertype = "IPv4"
protocol = "tcp"
port_range_min = 2376
port_range_max = 2376
#remote_ip_prefix = "0.0.0.0/0"
security_group_id = openstack_networking_secgroup_v2.terraform-rancher-secgroup.id
}
resource "openstack_networking_secgroup_rule_v2" "terraform-secgroup-rule-2379" {
direction = "ingress"
ethertype = "IPv4"
protocol = "tcp"
port_range_min = 2379
port_range_max = 2379
#remote_ip_prefix = "0.0.0.0/0"
security_group_id = openstack_networking_secgroup_v2.terraform-rancher-secgroup.id
}
resource "openstack_networking_secgroup_rule_v2" "terraform-secgroup-rule-2380" {
direction = "ingress"
ethertype = "IPv4"
protocol = "tcp"
port_range_min = 2380
port_range_max = 2380
#remote_ip_prefix = "0.0.0.0/0"
security_group_id = openstack_networking_secgroup_v2.terraform-rancher-secgroup.id
}
resource "openstack_networking_secgroup_rule_v2" "terraform-secgroup-rule-6443" {
direction = "ingress"
ethertype = "IPv4"
protocol = "tcp"
port_range_min = 6443
port_range_max = 6443
#remote_ip_prefix = "0.0.0.0/0"
security_group_id = openstack_networking_secgroup_v2.terraform-rancher-secgroup.id
}
resource "openstack_networking_secgroup_rule_v2" "terraform-secgroup-rule-9099" {
direction = "ingress"
ethertype = "IPv4"
protocol = "tcp"
port_range_min = 9099
port_range_max = 9099
#remote_ip_prefix = "0.0.0.0/0"
security_group_id = openstack_networking_secgroup_v2.terraform-rancher-secgroup.id
}
resource "openstack_networking_secgroup_rule_v2" "terraform-secgroup-rule-10250" {
direction = "ingress"
ethertype = "IPv4"
protocol = "tcp"
port_range_min = 10250
port_range_max = 10250
#remote_ip_prefix = "0.0.0.0/0"
security_group_id = openstack_networking_secgroup_v2.terraform-rancher-secgroup.id
}
resource "openstack_networking_secgroup_rule_v2" "terraform-secgroup-rule-10254" {
direction = "ingress"
ethertype = "IPv4"
protocol = "tcp"
port_range_min = 10254
port_range_max = 10254
#remote_ip_prefix = "0.0.0.0/0"
security_group_id = openstack_networking_secgroup_v2.terraform-rancher-secgroup.id
}
resource "openstack_networking_secgroup_rule_v2" "terraform-secgroup-rule-8472" {
direction = "ingress"
ethertype = "IPv4"
protocol = "udp"
port_range_min = 8472
port_range_max = 8472
#remote_ip_prefix = "0.0.0.0/0"
security_group_id = openstack_networking_secgroup_v2.terraform-rancher-secgroup.id
}
###########################################################################
#
# create network
#
###########################################################################
resource "openstack_networking_network_v2" "terraform-rancher-network-1" {
name = "my-terraform-rancher-network-1"
admin_state_up = "true"
}
resource "openstack_networking_subnet_v2" "terraform-rancher-subnet-1" {
name = "my-terraform-rancher-subnet-1"
network_id = openstack_networking_network_v2.terraform-rancher-network-1.id
cidr = "192.168.254.0/24"
dns_nameservers = [ "192.168.76.253" ]
ip_version = 4
}
data "openstack_networking_router_v2" "router-1" {
name = local.router_name
}
resource "openstack_networking_router_interface_v2" "router_interface_1" {
router_id = data.openstack_networking_router_v2.router-1.id
subnet_id = openstack_networking_subnet_v2.terraform-rancher-subnet-1.id
}
###########################################################################
#
# create instances
#
###########################################################################
resource "openstack_compute_instance_v2" "terraform-rancher-instance-1" {
name = "my-terraform-rancher-instance-1"
image_name = local.image_name
flavor_name = local.flavor_name
key_pair = openstack_compute_keypair_v2.terraform-rancher-keypair.name
security_groups = [openstack_networking_secgroup_v2.terraform-rancher-secgroup.name]
network {
uuid = openstack_networking_network_v2.terraform-rancher-network-1.id
}
user_data = <<-EOF
#!/bin/bash
apt-get update
apt-get -y upgrade
curl https://releases.rancher.com/install-docker/20.10.sh | sh
sudo docker run --privileged -d --restart=unless-stopped -p 80:80 -p 443:443 --env CATTLE_BOOTSTRAP_PASSWORD=this-is-not-a-secure-bootstrap-pw rancher/rancher
#sudo docker ps
#sudo docker logs $(sudo docker ps | grep rancher | cut -d " " -f 1) 2>&1 | grep "Bootstrap Password:"
EOF
depends_on = [
openstack_networking_subnet_v2.terraform-rancher-subnet-1
]
}
###########################################################################
#
# assign floating ip to rancher instance
#
###########################################################################
resource "openstack_networking_floatingip_v2" "fip_1" {
pool = "public1"
}
resource "openstack_compute_floatingip_associate_v2" "fip_1" {
floating_ip = "${openstack_networking_floatingip_v2.fip_1.address}"
instance_id = "${openstack_compute_instance_v2.terraform-rancher-instance-1.id}"
}
output "floating_ip" {
value = openstack_networking_floatingip_v2.fip_1
}
###########################################################################
#
# bootstrap rancher
#
###########################################################################
# Provider bootstrap config
provider "rancher2" {
alias = "bootstrap"
api_url = "https://${openstack_networking_floatingip_v2.fip_1.address}"
bootstrap = true
insecure = true
# takes roughly ~7 minutes currently
timeout = "600s"
}
# Create a new rancher2_bootstrap for Rancher v2.6.0 and above
resource "rancher2_bootstrap" "admin" {
provider = rancher2.bootstrap
initial_password = "this-is-not-a-secure-bootstrap-pw"
password = "this-is-not-a-secure-admin-pw"
telemetry = true
token_update=true
}
# Rancher2 administration provider
provider "rancher2" {
alias = "admin"
api_url = "https://${openstack_networking_floatingip_v2.fip_1.address}"
insecure = true
# ca_certs = data.kubernetes_secret.rancher_cert.data["ca.crt"]
token_key = rancher2_bootstrap.admin.token
}
###########################################################################
#
# enable rancher node driver openstack
#
###########################################################################
#data "rancher2_node_driver" "OpenStack" {
# provider = rancher2.admin
# name = "openstack"
#}
# Create a new rancher2 Node Driver
# TODO: creates a new builtin driver, maybe better to change existing one
resource "rancher2_node_driver" "OpenStack" {
provider = rancher2.admin
name = "openstack"
active = true
builtin = true
url = "local://"
# external_id = data.rancher2_node_driver.OpenStack
}
###########################################################################
#
# create rancher node template for hsfd openstack
#
###########################################################################
resource "rancher2_node_template" "hsfd-rancher-openstack" {
provider = rancher2.admin
name = "hsfd-rancher-openstack"
driver_id = rancher2_node_driver.OpenStack.id
openstack_config {
auth_url = local.auth_url
availability_zone = local.availability_zone
region = local.region_name
username = local.user_name
# TODO: (Optional/Sensitive) OpenStack password. Mandatory on Rancher v2.0.x and v2.1.x. Use rancher2_cloud_credential from Rancher v2.2.x (string)
password = local.user_password
active_timeout = "200"
domain_name = local.domain_name
boot_from_volume = false
flavor_name = local.rke_flavor_name
floating_ip_pool = local.floating_ip_pool
image_name = local.image_name
ip_version = "4"
keypair_name = openstack_compute_keypair_v2.terraform-rancher-keypair.name
net_id = openstack_networking_network_v2.terraform-rancher-network-1.id
sec_groups = openstack_networking_secgroup_v2.terraform-rancher-secgroup.name
ssh_user = local.ssh_user
private_key_file = openstack_compute_keypair_v2.terraform-rancher-keypair.private_key
tenant_name = local.tenant_name
}
# TODO: get latest recommended string possible?
engine_install_url = "https://releases.rancher.com/install-docker/20.10.sh"
}
###########################################################################
#
# create rke template for hsfd openstack
#
###########################################################################
data "openstack_identity_project_v3" "my-project" {
name = local.tenant_name
}
data "openstack_networking_network_v2" "public1" {
name = local.floating_ip_pool
}
# Create a new rancher2 Cluster Template
resource "rancher2_cluster_template" "hsfd-rke-openstack" {
provider = rancher2.admin
name = "hsfd-rke-openstack"
template_revisions {
name = "V1"
cluster_config {
rke_config {
cloud_provider {
name = "openstack"
openstack_cloud_provider {
block_storage {
ignore_volume_az = true
trust_device_path = false
}
global {
auth_url = local.auth_url
domain_name = local.domain_name
tenant_id = data.openstack_identity_project_v3.my-project.id
username = local.user_name
password = local.user_password
}
load_balancer {
create_monitor = false
floating_network_id = data.openstack_networking_network_v2.public1.id
lb_version = "v2"
manage_security_groups = true
monitor_max_retries = 0
subnet_id = openstack_networking_subnet_v2.terraform-rancher-subnet-1.id
use_octavia = true
}
metadata {
request_timeout = 0
}
route {
router_id = data.openstack_networking_router_v2.router-1.id
}
}
}
}
}
default = true
}
description = "Terraform RKE template for HSFD OpenStack"
}
###########################################################################
#
# create rke demo cluster
#
###########################################################################
resource "rancher2_cluster" "hsfd-rke-demo" {
provider = rancher2.admin
name = "hsfd-rke-demo"
cluster_template_id = rancher2_cluster_template.hsfd-rke-openstack.id
cluster_template_revision_id = rancher2_cluster_template.hsfd-rke-openstack.template_revisions.0.id
# if instance is gone before deleting the cluster, we'll not be able to
# reach rke anymore
depends_on = [
openstack_compute_instance_v2.terraform-rancher-instance-1,
openstack_networking_secgroup_rule_v2.terraform-secgroup-rule-ssh,
openstack_networking_secgroup_rule_v2.terraform-secgroup-rule-http,
openstack_networking_secgroup_rule_v2.terraform-secgroup-rule-https,
openstack_networking_secgroup_rule_v2.terraform-secgroup-rule-2376,
openstack_networking_secgroup_rule_v2.terraform-secgroup-rule-2379,
openstack_networking_secgroup_rule_v2.terraform-secgroup-rule-2380,
openstack_networking_secgroup_rule_v2.terraform-secgroup-rule-6443,
openstack_networking_secgroup_rule_v2.terraform-secgroup-rule-9099,
openstack_networking_secgroup_rule_v2.terraform-secgroup-rule-10250,
openstack_networking_secgroup_rule_v2.terraform-secgroup-rule-10254,
openstack_networking_secgroup_rule_v2.terraform-secgroup-rule-8472,
openstack_compute_floatingip_associate_v2.fip_1
]
}
# Create a new rancher2 Node Pool
resource "rancher2_node_pool" "pool1" {
provider = rancher2.admin
cluster_id = rancher2_cluster.hsfd-rke-demo.id
name = "ctrl-etcd-work"
hostname_prefix = "ctrl-etcd-work"
node_template_id = rancher2_node_template.hsfd-rancher-openstack.id
quantity = 1
control_plane = true
etcd = true
worker = true
# if instance is gone before deleting the cluster, we'll not be able to
# reach rke anymore
depends_on = [
openstack_compute_instance_v2.terraform-rancher-instance-1,
openstack_networking_secgroup_rule_v2.terraform-secgroup-rule-ssh,
openstack_networking_secgroup_rule_v2.terraform-secgroup-rule-http,
openstack_networking_secgroup_rule_v2.terraform-secgroup-rule-https,
openstack_networking_secgroup_rule_v2.terraform-secgroup-rule-2376,
openstack_networking_secgroup_rule_v2.terraform-secgroup-rule-2379,
openstack_networking_secgroup_rule_v2.terraform-secgroup-rule-2380,
openstack_networking_secgroup_rule_v2.terraform-secgroup-rule-6443,
openstack_networking_secgroup_rule_v2.terraform-secgroup-rule-9099,
openstack_networking_secgroup_rule_v2.terraform-secgroup-rule-10250,
openstack_networking_secgroup_rule_v2.terraform-secgroup-rule-10254,
openstack_networking_secgroup_rule_v2.terraform-secgroup-rule-8472,
openstack_compute_floatingip_associate_v2.fip_1
]
}

20
terraform/rke-terraform/main.tf

@ -1,20 +0,0 @@
# Consider using 'export TF_VAR_os_auth_url=$OS_AUTH_URL'
variable "os_auth_url"{}
# Consider using 'export TF_VAR_os_password=$OS_PASSWORD'
variable "os_password"{}
module "rke" {
source = "remche/rke/openstack"
image_name = "Ubuntu 20.04 - Focal Fossa - 64-bit - Cloud Based Image"
public_net_name = "public1"
master_flavor_name = "m1.small"
worker_flavor_name = "m1.small"
os_auth_url = var.os_auth_url
os_password = var.os_password
#use_ssh_agent = false
#enable_loadbalancer = true
#use_octavia = true
wait_for_commands = ["while docker info ; [ $? -ne 0 ]; do echo wait for docker; sudo newgrp - docker ; sleep 30 ; done"]
user_data_file = "user-data.sh"
dns_servers = [ "192.168.76.253" ]
}

6
terraform/rke-terraform/user-data.sh

@ -1,6 +0,0 @@
#!/bin/bash
#apt-get update
#apt-get -y upgrade
curl https://releases.rancher.com/install-docker/20.10.sh | sh
groupadd docker
usermod -aG docker ubuntu

0
terraform/rke2-terraform/README.md

11
terraform/rke2-terraform/main.tf

@ -1,11 +0,0 @@
module "controlplane" {
source = "remche/rke2/openstack"
cluster_name = var.cluster_name
dns_servers = var.dns_servers
write_kubeconfig = true
image_name = "Ubuntu 20.04 - Focal Fossa - 64-bit - Cloud Based Image"
flavor_name = "m1.medium"
public_net_name = "public1"
use_ssh_agent = false
ssh_key_file = "/root/.ssh/id_rsa"
}

5
terraform/rke2-terraform/outputs.tf

@ -1,5 +0,0 @@
output "server_ip" {
description = "Server floating IP"
value = module.controlplane.floating_ip[0]
sensitive = true
}

9
terraform/rke2-terraform/variables.tf

@ -1,9 +0,0 @@
variable "cluster_name" {
type = string
default = "minimal"
}
variable "dns_servers" {
type = list(string)
default = ["192.168.76.253"]
}
Loading…
Cancel
Save