Merge branch 'work' of https://github.com/Lendico/kargo into work
This commit is contained in:
commit
658db71c4b
15 changed files with 48 additions and 3 deletions
5
roles/docker/meta/main.yml
Normal file
5
roles/docker/meta/main.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
- role: bootstrap-os
|
||||||
|
|
|
@ -61,11 +61,12 @@
|
||||||
force: "{{item.force|default(omit)}}"
|
force: "{{item.force|default(omit)}}"
|
||||||
state: present
|
state: present
|
||||||
register: docker_task_result
|
register: docker_task_result
|
||||||
until: docker_task_result|succeeded
|
until: docker_task_result|succeeded or ansible_check_mode
|
||||||
retries: 4
|
retries: 4
|
||||||
delay: "{{ retry_stagger | random + 3 }}"
|
delay: "{{ retry_stagger | random + 3 }}"
|
||||||
with_items: "{{ docker_package_info.pkgs }}"
|
with_items: "{{ docker_package_info.pkgs }}"
|
||||||
notify: restart docker
|
notify: restart docker
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
when: not (ansible_os_family in ["CoreOS", "Container Linux by CoreOS"] or is_atomic) and (docker_package_info.pkgs|length > 0)
|
when: not (ansible_os_family in ["CoreOS", "Container Linux by CoreOS"] or is_atomic) and (docker_package_info.pkgs|length > 0)
|
||||||
|
|
||||||
- name: check minimum docker version for docker_dns mode. You need at least docker version >= 1.12 for resolvconf_mode=docker_dns
|
- name: check minimum docker version for docker_dns mode. You need at least docker version >= 1.12 for resolvconf_mode=docker_dns
|
||||||
|
@ -85,3 +86,4 @@
|
||||||
state: started
|
state: started
|
||||||
with_items:
|
with_items:
|
||||||
- docker
|
- docker
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
|
|
5
roles/download/meta/main.yml
Normal file
5
roles/download/meta/main.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
- role: docker
|
||||||
|
|
|
@ -127,6 +127,7 @@
|
||||||
dest: "{{cert_tempfile.stdout}}"
|
dest: "{{cert_tempfile.stdout}}"
|
||||||
owner: root
|
owner: root
|
||||||
mode: "0600"
|
mode: "0600"
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
when: inventory_hostname in groups['etcd'] and sync_certs|default(false) and
|
when: inventory_hostname in groups['etcd'] and sync_certs|default(false) and
|
||||||
inventory_hostname != groups['etcd'][0]
|
inventory_hostname != groups['etcd'][0]
|
||||||
|
|
||||||
|
@ -135,6 +136,7 @@
|
||||||
no_log: true
|
no_log: true
|
||||||
changed_when: false
|
changed_when: false
|
||||||
check_mode: no
|
check_mode: no
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
when: inventory_hostname in groups['etcd'] and sync_certs|default(false) and
|
when: inventory_hostname in groups['etcd'] and sync_certs|default(false) and
|
||||||
inventory_hostname != groups['etcd'][0]
|
inventory_hostname != groups['etcd'][0]
|
||||||
notify: set secret_changed
|
notify: set secret_changed
|
||||||
|
@ -143,6 +145,7 @@
|
||||||
file:
|
file:
|
||||||
path: "{{cert_tempfile.stdout}}"
|
path: "{{cert_tempfile.stdout}}"
|
||||||
state: absent
|
state: absent
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
when: inventory_hostname in groups['etcd'] and sync_certs|default(false) and
|
when: inventory_hostname in groups['etcd'] and sync_certs|default(false) and
|
||||||
inventory_hostname != groups['etcd'][0]
|
inventory_hostname != groups['etcd'][0]
|
||||||
|
|
||||||
|
@ -181,6 +184,7 @@
|
||||||
dest: "{{ ca_cert_path }}"
|
dest: "{{ ca_cert_path }}"
|
||||||
remote_src: true
|
remote_src: true
|
||||||
register: etcd_ca_cert
|
register: etcd_ca_cert
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
|
|
||||||
- name: Gen_certs | update ca-certificates (Debian/Ubuntu/Container Linux by CoreOS)
|
- name: Gen_certs | update ca-certificates (Debian/Ubuntu/Container Linux by CoreOS)
|
||||||
command: update-ca-certificates
|
command: update-ca-certificates
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
retries: 4
|
retries: 4
|
||||||
delay: "{{ retry_stagger | random + 3 }}"
|
delay: "{{ retry_stagger | random + 3 }}"
|
||||||
changed_when: false
|
changed_when: false
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
|
|
||||||
#Plan B: looks nicer, but requires docker-py on all hosts:
|
#Plan B: looks nicer, but requires docker-py on all hosts:
|
||||||
#- name: Install | Set up etcd-binarycopy container
|
#- name: Install | Set up etcd-binarycopy container
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
name: etcd
|
name: etcd
|
||||||
state: started
|
state: started
|
||||||
enabled: yes
|
enabled: yes
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
when: is_etcd_master and etcd_cluster_setup
|
when: is_etcd_master and etcd_cluster_setup
|
||||||
|
|
||||||
# After etcd cluster is assembled, make sure that
|
# After etcd cluster is assembled, make sure that
|
||||||
|
|
|
@ -34,6 +34,7 @@
|
||||||
- name: "Pre-upgrade | remove etcd-proxy if it exists"
|
- name: "Pre-upgrade | remove etcd-proxy if it exists"
|
||||||
command: "{{ docker_bin_dir }}/docker rm -f {{item}}"
|
command: "{{ docker_bin_dir }}/docker rm -f {{item}}"
|
||||||
with_items: "{{etcd_proxy_container.stdout_lines}}"
|
with_items: "{{etcd_proxy_container.stdout_lines}}"
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
|
|
||||||
- name: "Pre-upgrade | see if etcdctl is installed"
|
- name: "Pre-upgrade | see if etcdctl is installed"
|
||||||
stat:
|
stat:
|
||||||
|
@ -45,11 +46,12 @@
|
||||||
register: etcd_member_list
|
register: etcd_member_list
|
||||||
retries: 10
|
retries: 10
|
||||||
delay: 3
|
delay: 3
|
||||||
until: etcd_member_list.rc != 2
|
until: etcd_member_list.rc != 2 or ansible_check_mode
|
||||||
run_once: true
|
run_once: true
|
||||||
when: etcdctl_installed.stat.exists
|
when: etcdctl_installed.stat.exists
|
||||||
changed_when: false
|
changed_when: false
|
||||||
failed_when: false
|
failed_when: false
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
|
|
||||||
- name: "Pre-upgrade | change peer names to SSL"
|
- name: "Pre-upgrade | change peer names to SSL"
|
||||||
shell: >-
|
shell: >-
|
||||||
|
@ -57,3 +59,4 @@
|
||||||
awk -F"[: =]" '{print "{{ bin_dir }}/etcdctl --peers={{ etcd_access_addresses | regex_replace('https','http') }} member update "$1" https:"$7":"$8}' | bash
|
awk -F"[: =]" '{print "{{ bin_dir }}/etcdctl --peers={{ etcd_access_addresses | regex_replace('https','http') }} member update "$1" https:"$7":"$8}' | bash
|
||||||
run_once: true
|
run_once: true
|
||||||
when: 'etcdctl_installed.stat.exists and etcd_member_list.rc == 0 and "http://" in etcd_member_list.stdout'
|
when: 'etcdctl_installed.stat.exists and etcd_member_list.rc == 0 and "http://" in etcd_member_list.stdout'
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
url: http://localhost:8080/healthz
|
url: http://localhost:8080/healthz
|
||||||
register: result
|
register: result
|
||||||
until: result.status == 200
|
until: result.status == 200
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
retries: 10
|
retries: 10
|
||||||
delay: 6
|
delay: 6
|
||||||
when: inventory_hostname == groups['kube-master'][0]
|
when: inventory_hostname == groups['kube-master'][0]
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
until: scheduler_result.status == 200
|
until: scheduler_result.status == 200
|
||||||
retries: 60
|
retries: 60
|
||||||
delay: 5
|
delay: 5
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
|
|
||||||
- name: Master | wait for kube-controller-manager
|
- name: Master | wait for kube-controller-manager
|
||||||
uri:
|
uri:
|
||||||
|
@ -36,6 +37,7 @@
|
||||||
until: controller_manager_result.status == 200
|
until: controller_manager_result.status == 200
|
||||||
retries: 15
|
retries: 15
|
||||||
delay: 5
|
delay: 5
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
|
|
||||||
- name: Master | wait for the apiserver to be running
|
- name: Master | wait for the apiserver to be running
|
||||||
uri:
|
uri:
|
||||||
|
@ -44,3 +46,4 @@
|
||||||
until: result.status == 200
|
until: result.status == 200
|
||||||
retries: 20
|
retries: 20
|
||||||
delay: 6
|
delay: 6
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
- name: Copy kubectl from hyperkube container
|
- name: Copy kubectl from hyperkube container
|
||||||
command: "{{ docker_bin_dir }}/docker run --rm -v {{ bin_dir }}:/systembindir {{ hyperkube_image_repo }}:{{ hyperkube_image_tag }} /bin/cp /hyperkube /systembindir/kubectl"
|
command: "{{ docker_bin_dir }}/docker run --rm -v {{ bin_dir }}:/systembindir {{ hyperkube_image_repo }}:{{ hyperkube_image_tag }} /bin/cp /hyperkube /systembindir/kubectl"
|
||||||
register: kube_task_result
|
register: kube_task_result
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
until: kube_task_result.rc == 0
|
until: kube_task_result.rc == 0
|
||||||
retries: 4
|
retries: 4
|
||||||
delay: "{{ retry_stagger | random + 3 }}"
|
delay: "{{ retry_stagger | random + 3 }}"
|
||||||
|
@ -23,6 +24,7 @@
|
||||||
group: root
|
group: root
|
||||||
mode: 0755
|
mode: 0755
|
||||||
when: ansible_os_family in ["Debian","RedHat"]
|
when: ansible_os_family in ["Debian","RedHat"]
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
tags: [kubectl, upgrade]
|
tags: [kubectl, upgrade]
|
||||||
|
|
||||||
- name: Write kube-apiserver manifest
|
- name: Write kube-apiserver manifest
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
- name: "Pre-upgrade | etcd3 upgrade | use etcd2 unless forced to etc3"
|
- name: "Pre-upgrade | etcd3 upgrade | use etcd2 unless forced to etc3"
|
||||||
set_fact:
|
set_fact:
|
||||||
kube_apiserver_storage_backend: "etcd2"
|
kube_apiserver_storage_backend: "etcd2"
|
||||||
when: old_data_exists.rc == 0 and not force_etcd3|bool
|
when: not old_data_exists|skipped and old_data_exists.rc == 0 and not force_etcd3|bool
|
||||||
|
|
||||||
- name: "Pre-upgrade | etcd3 upgrade | see if data was already migrated"
|
- name: "Pre-upgrade | etcd3 upgrade | see if data was already migrated"
|
||||||
command: "{{ bin_dir }}/etcdctl --endpoints={{ etcd_access_addresses }} get --limit=1 --prefix=true /registry/minions"
|
command: "{{ bin_dir }}/etcdctl --endpoints={{ etcd_access_addresses }} get --limit=1 --prefix=true /registry/minions"
|
||||||
|
|
|
@ -60,4 +60,5 @@
|
||||||
name: kubelet
|
name: kubelet
|
||||||
enabled: yes
|
enabled: yes
|
||||||
state: started
|
state: started
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
tags: kubelet
|
tags: kubelet
|
||||||
|
|
|
@ -83,6 +83,7 @@
|
||||||
no_log: true
|
no_log: true
|
||||||
register: master_cert_data
|
register: master_cert_data
|
||||||
check_mode: no
|
check_mode: no
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
delegate_to: "{{groups['kube-master'][0]}}"
|
delegate_to: "{{groups['kube-master'][0]}}"
|
||||||
when: inventory_hostname in groups['kube-master'] and sync_certs|default(false) and
|
when: inventory_hostname in groups['kube-master'] and sync_certs|default(false) and
|
||||||
inventory_hostname != groups['kube-master'][0]
|
inventory_hostname != groups['kube-master'][0]
|
||||||
|
@ -115,6 +116,7 @@
|
||||||
dest: "{{cert_tempfile.stdout}}"
|
dest: "{{cert_tempfile.stdout}}"
|
||||||
owner: root
|
owner: root
|
||||||
mode: "0600"
|
mode: "0600"
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
when: inventory_hostname in groups['kube-master'] and sync_certs|default(false) and
|
when: inventory_hostname in groups['kube-master'] and sync_certs|default(false) and
|
||||||
inventory_hostname != groups['kube-master'][0]
|
inventory_hostname != groups['kube-master'][0]
|
||||||
|
|
||||||
|
@ -123,6 +125,7 @@
|
||||||
no_log: true
|
no_log: true
|
||||||
changed_when: false
|
changed_when: false
|
||||||
check_mode: no
|
check_mode: no
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
when: inventory_hostname in groups['kube-master'] and sync_certs|default(false) and
|
when: inventory_hostname in groups['kube-master'] and sync_certs|default(false) and
|
||||||
inventory_hostname != groups['kube-master'][0]
|
inventory_hostname != groups['kube-master'][0]
|
||||||
notify: set secret_changed
|
notify: set secret_changed
|
||||||
|
@ -131,6 +134,7 @@
|
||||||
file:
|
file:
|
||||||
path: "{{cert_tempfile.stdout}}"
|
path: "{{cert_tempfile.stdout}}"
|
||||||
state: absent
|
state: absent
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
when: inventory_hostname in groups['kube-master'] and sync_certs|default(false) and
|
when: inventory_hostname in groups['kube-master'] and sync_certs|default(false) and
|
||||||
inventory_hostname != groups['kube-master'][0]
|
inventory_hostname != groups['kube-master'][0]
|
||||||
|
|
||||||
|
@ -141,6 +145,7 @@
|
||||||
no_log: true
|
no_log: true
|
||||||
changed_when: false
|
changed_when: false
|
||||||
check_mode: no
|
check_mode: no
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
when: inventory_hostname in groups['kube-node'] and
|
when: inventory_hostname in groups['kube-node'] and
|
||||||
sync_certs|default(false) and
|
sync_certs|default(false) and
|
||||||
inventory_hostname != groups['kube-master'][0]
|
inventory_hostname != groups['kube-master'][0]
|
||||||
|
@ -153,6 +158,7 @@
|
||||||
owner: kube
|
owner: kube
|
||||||
mode: "u=rwX,g-rwx,o-rwx"
|
mode: "u=rwX,g-rwx,o-rwx"
|
||||||
recurse: yes
|
recurse: yes
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
|
|
||||||
- name: Gen_certs | target ca-certificates path
|
- name: Gen_certs | target ca-certificates path
|
||||||
set_fact:
|
set_fact:
|
||||||
|
@ -172,6 +178,7 @@
|
||||||
dest: "{{ ca_cert_path }}"
|
dest: "{{ ca_cert_path }}"
|
||||||
remote_src: true
|
remote_src: true
|
||||||
register: kube_ca_cert
|
register: kube_ca_cert
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
|
|
||||||
- name: Gen_certs | update ca-certificates (Debian/Ubuntu/Container Linux by CoreOS)
|
- name: Gen_certs | update ca-certificates (Debian/Ubuntu/Container Linux by CoreOS)
|
||||||
command: update-ca-certificates
|
command: update-ca-certificates
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
shell: "(find {{ kube_token_dir }} -maxdepth 1 -type f)"
|
shell: "(find {{ kube_token_dir }} -maxdepth 1 -type f)"
|
||||||
register: tokens_list
|
register: tokens_list
|
||||||
check_mode: no
|
check_mode: no
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
delegate_to: "{{groups['kube-master'][0]}}"
|
delegate_to: "{{groups['kube-master'][0]}}"
|
||||||
run_once: true
|
run_once: true
|
||||||
when: sync_tokens|default(false)
|
when: sync_tokens|default(false)
|
||||||
|
@ -48,11 +49,13 @@
|
||||||
shell: "tar cfz - {{ tokens_list.stdout_lines | join(' ') }} | base64 --wrap=0"
|
shell: "tar cfz - {{ tokens_list.stdout_lines | join(' ') }} | base64 --wrap=0"
|
||||||
register: tokens_data
|
register: tokens_data
|
||||||
check_mode: no
|
check_mode: no
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
delegate_to: "{{groups['kube-master'][0]}}"
|
delegate_to: "{{groups['kube-master'][0]}}"
|
||||||
run_once: true
|
run_once: true
|
||||||
when: sync_tokens|default(false)
|
when: sync_tokens|default(false)
|
||||||
|
|
||||||
- name: Gen_tokens | Copy tokens on masters
|
- name: Gen_tokens | Copy tokens on masters
|
||||||
shell: "echo '{{ tokens_data.stdout|quote }}' | base64 -d | tar xz -C /"
|
shell: "echo '{{ tokens_data.stdout|quote }}' | base64 -d | tar xz -C /"
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
when: inventory_hostname in groups['kube-master'] and sync_tokens|default(false) and
|
when: inventory_hostname in groups['kube-master'] and sync_tokens|default(false) and
|
||||||
inventory_hostname != groups['kube-master'][0]
|
inventory_hostname != groups['kube-master'][0]
|
||||||
|
|
|
@ -47,6 +47,7 @@
|
||||||
retries: 4
|
retries: 4
|
||||||
delay: "{{ retry_stagger | random + 3 }}"
|
delay: "{{ retry_stagger | random + 3 }}"
|
||||||
changed_when: false
|
changed_when: false
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
tags: [hyperkube, upgrade]
|
tags: [hyperkube, upgrade]
|
||||||
|
|
||||||
- name: Calico | Copy cni plugins from calico/cni container
|
- name: Calico | Copy cni plugins from calico/cni container
|
||||||
|
@ -56,6 +57,7 @@
|
||||||
retries: 4
|
retries: 4
|
||||||
delay: "{{ retry_stagger | random + 3 }}"
|
delay: "{{ retry_stagger | random + 3 }}"
|
||||||
changed_when: false
|
changed_when: false
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
when: overwrite_hyperkube_cni|bool
|
when: overwrite_hyperkube_cni|bool
|
||||||
tags: [hyperkube, upgrade]
|
tags: [hyperkube, upgrade]
|
||||||
|
|
||||||
|
@ -77,6 +79,7 @@
|
||||||
delay: 5
|
delay: 5
|
||||||
delegate_to: "{{groups['etcd'][0]}}"
|
delegate_to: "{{groups['etcd'][0]}}"
|
||||||
run_once: true
|
run_once: true
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
|
|
||||||
- name: Calico | Check if calico network pool has already been configured
|
- name: Calico | Check if calico network pool has already been configured
|
||||||
command: |-
|
command: |-
|
||||||
|
@ -103,6 +106,7 @@
|
||||||
environment:
|
environment:
|
||||||
NO_DEFAULT_POOLS: true
|
NO_DEFAULT_POOLS: true
|
||||||
run_once: true
|
run_once: true
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
when: not legacy_calicoctl and
|
when: not legacy_calicoctl and
|
||||||
("Key not found" in calico_conf.stdout or "nodes" not in calico_conf.stdout)
|
("Key not found" in calico_conf.stdout or "nodes" not in calico_conf.stdout)
|
||||||
|
|
||||||
|
@ -151,11 +155,13 @@
|
||||||
- set_fact:
|
- set_fact:
|
||||||
calico_pools: "{{ calico_pools_raw.stdout | from_json }}"
|
calico_pools: "{{ calico_pools_raw.stdout | from_json }}"
|
||||||
run_once: true
|
run_once: true
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
|
|
||||||
- name: Calico | Check if calico pool is properly configured
|
- name: Calico | Check if calico pool is properly configured
|
||||||
fail:
|
fail:
|
||||||
msg: 'Only one network pool must be configured and it must be the subnet {{ kube_pods_subnet }}.
|
msg: 'Only one network pool must be configured and it must be the subnet {{ kube_pods_subnet }}.
|
||||||
Please erase calico configuration and run the playbook again ("etcdctl rm --recursive /calico/v1/ipam/v4/pool")'
|
Please erase calico configuration and run the playbook again ("etcdctl rm --recursive /calico/v1/ipam/v4/pool")'
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
when: ( calico_pools['node']['nodes'] | length > 1 ) or
|
when: ( calico_pools['node']['nodes'] | length > 1 ) or
|
||||||
( not calico_pools['node']['nodes'][0]['key'] | search(".*{{ kube_pods_subnet | ipaddr('network') }}.*") )
|
( not calico_pools['node']['nodes'][0]['key'] | search(".*{{ kube_pods_subnet | ipaddr('network') }}.*") )
|
||||||
run_once: true
|
run_once: true
|
||||||
|
@ -203,6 +209,7 @@
|
||||||
name: calico-node
|
name: calico-node
|
||||||
state: started
|
state: started
|
||||||
enabled: yes
|
enabled: yes
|
||||||
|
ignore_errors: "{{ ansible_check_mode }}"
|
||||||
|
|
||||||
- name: Calico | Disable node mesh
|
- name: Calico | Disable node mesh
|
||||||
shell: "{{ bin_dir }}/calicoctl config set nodeToNodeMesh off"
|
shell: "{{ bin_dir }}/calicoctl config set nodeToNodeMesh off"
|
||||||
|
|
Loading…
Reference in a new issue