Initial commit: VMware vSphere API simulator scaffold.
Add the FastAPI app, PostgreSQL migrations, Docker/Helm packaging, API contracts, docs, client examples, and the unit/integration/compatibility test suite for local client and tooling labs without a real vCenter.
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
**Language / Язык:** [English](README.md) | [Русский](README.ru.md)
|
||||
|
||||
# Runnable client cookbooks
|
||||
|
||||
Companion code for [docs/clients.md](../docs/clients.md) and
|
||||
[docs/examples/](../docs/examples/overview.md).
|
||||
|
||||
## Prerequisites
|
||||
|
||||
```bash
|
||||
make up
|
||||
VSPHERE_PROFILE=demo-cluster make seed # or small / large
|
||||
```
|
||||
|
||||
Default lab credentials: `administrator@vsphere.local` / `VMware1!`.
|
||||
|
||||
## Native vSphere smokes
|
||||
|
||||
```bash
|
||||
python examples/python/vsphere_rest_smoke.py https://localhost
|
||||
python examples/python/vsphere_soap_smoke.py https://localhost
|
||||
python examples/python/vsphere_lifecycle.py # REST create/power/guest FS + SOAP CreateVM
|
||||
python examples/python/requests_cookbook.py # raw requests, no SDK
|
||||
ansible-playbook -i examples/ansible/inventory.ini examples/ansible/vsphere_playbook.yml
|
||||
cd examples/terraform/vsphere && terraform init && terraform apply
|
||||
cd examples/pulumi && pulumi up
|
||||
cd examples/go && go run .
|
||||
cd examples/java && javac Cookbook.java && java Cookbook
|
||||
cd examples/perl && cpanm --installdeps . && perl cookbook.pl
|
||||
make vsphere-surface
|
||||
```
|
||||
|
||||
## Layout
|
||||
|
||||
| Path | Stack |
|
||||
|---|---|
|
||||
| `python/vsphere_*.py` | Native REST + SOAP smokes / lifecycle |
|
||||
| `python/requests_cookbook.py` | Raw `requests` REST cookbook (session, create/power/delete) |
|
||||
| `ansible/vsphere_playbook.yml` | Ansible REST lifecycle |
|
||||
| `terraform/vsphere/` | Terraform `hashicorp/vsphere` (SOAP-backed provider) |
|
||||
| `pulumi/` | Pulumi REST `ComponentResource` |
|
||||
| `go/`, `java/`, `perl/` | Minimal REST cookbooks in other languages (stdlib HTTP clients, Basic-auth session) |
|
||||
|
||||
An independent `pulumi-vsphere` lab suite lives under
|
||||
[`pulumi-tests/`](../pulumi-tests/README.md) — run `make pulumi-tests` from the
|
||||
repo root (or `make test-pulumi` inside that directory).
|
||||
|
||||
Guides: [docs/examples/](../docs/examples/overview.md). Coverage matrix:
|
||||
[docs/api-coverage.md](../docs/api-coverage.md).
|
||||
@@ -0,0 +1,49 @@
|
||||
**Language / Язык:** [English](README.md) | [Русский](README.ru.md)
|
||||
|
||||
# Исполняемые client cookbooks
|
||||
|
||||
Сопровождающий код к [docs/ru/clients.md](../docs/ru/clients.md) и
|
||||
[docs/ru/examples/](../docs/ru/examples/overview.md).
|
||||
|
||||
## Требования
|
||||
|
||||
```bash
|
||||
make up
|
||||
VSPHERE_PROFILE=demo-cluster make seed # or small / large
|
||||
```
|
||||
|
||||
Учётные данные лаборатории по умолчанию: `administrator@vsphere.local` / `VMware1!`.
|
||||
|
||||
## Нативные smoke vSphere
|
||||
|
||||
```bash
|
||||
python examples/python/vsphere_rest_smoke.py https://localhost
|
||||
python examples/python/vsphere_soap_smoke.py https://localhost
|
||||
python examples/python/vsphere_lifecycle.py # REST create/power/guest FS + SOAP CreateVM
|
||||
python examples/python/requests_cookbook.py # raw requests, no SDK
|
||||
ansible-playbook -i examples/ansible/inventory.ini examples/ansible/vsphere_playbook.yml
|
||||
cd examples/terraform/vsphere && terraform init && terraform apply
|
||||
cd examples/pulumi && pulumi up
|
||||
cd examples/go && go run .
|
||||
cd examples/java && javac Cookbook.java && java Cookbook
|
||||
cd examples/perl && cpanm --installdeps . && perl cookbook.pl
|
||||
make vsphere-surface
|
||||
```
|
||||
|
||||
## Структура
|
||||
|
||||
| Путь | Стек |
|
||||
|---|---|
|
||||
| `python/vsphere_*.py` | Нативные REST + SOAP smokes / lifecycle |
|
||||
| `python/requests_cookbook.py` | Raw `requests` REST cookbook (session, create/power/delete) |
|
||||
| `ansible/vsphere_playbook.yml` | Ansible REST lifecycle |
|
||||
| `terraform/vsphere/` | Terraform `hashicorp/vsphere` (SOAP-backed provider) |
|
||||
| `pulumi/` | Pulumi REST `ComponentResource` |
|
||||
| `go/`, `java/`, `perl/` | Минимальные REST cookbook на других языках (stdlib HTTP-клиенты, сессия Basic-auth) |
|
||||
|
||||
Независимый lab-набор на `pulumi-vsphere` живёт в
|
||||
[`pulumi-tests/`](../pulumi-tests/README.ru.md) — из корня репозитория:
|
||||
`make pulumi-tests` (или `make test-pulumi` внутри этой директории).
|
||||
|
||||
Гайды: [docs/ru/examples/](../docs/ru/examples/overview.md). Матрица покрытия:
|
||||
[docs/ru/api-coverage.md](../docs/ru/api-coverage.md).
|
||||
@@ -0,0 +1,2 @@
|
||||
[simulator]
|
||||
localhost ansible_connection=local
|
||||
@@ -0,0 +1,123 @@
|
||||
---
|
||||
# vSphere Automation REST cookbook against the API simulator.
|
||||
# ansible-playbook -i inventory.ini vsphere_playbook.yml
|
||||
#
|
||||
# Requires: gateway https://localhost (or set vsphere_base).
|
||||
|
||||
- name: vSphere API simulator lifecycle
|
||||
hosts: simulator
|
||||
gather_facts: false
|
||||
vars:
|
||||
vsphere_base: "https://localhost"
|
||||
vsphere_user: "administrator@vsphere.local"
|
||||
vsphere_password: "VMware1!"
|
||||
vm_name: "ansible-lab-01"
|
||||
tasks:
|
||||
- name: Create CIS session
|
||||
ansible.builtin.uri:
|
||||
url: "{{ vsphere_base }}/api/session"
|
||||
method: POST
|
||||
url_username: "{{ vsphere_user }}"
|
||||
url_password: "{{ vsphere_password }}"
|
||||
force_basic_auth: true
|
||||
validate_certs: false
|
||||
status_code: [200, 201]
|
||||
return_content: true
|
||||
register: session
|
||||
|
||||
- name: Set session header
|
||||
ansible.builtin.set_fact:
|
||||
vsphere_headers:
|
||||
vmware-api-session-id: "{{ session.json }}"
|
||||
|
||||
- name: List VMs
|
||||
ansible.builtin.uri:
|
||||
url: "{{ vsphere_base }}/api/vcenter/vm"
|
||||
headers: "{{ vsphere_headers }}"
|
||||
validate_certs: false
|
||||
return_content: true
|
||||
register: vms
|
||||
|
||||
- name: Show inventory size
|
||||
ansible.builtin.debug:
|
||||
msg: "VMs in inventory: {{ vms.json | length }}"
|
||||
|
||||
- name: Create VM
|
||||
ansible.builtin.uri:
|
||||
url: "{{ vsphere_base }}/api/vcenter/vm"
|
||||
method: POST
|
||||
headers: "{{ vsphere_headers }}"
|
||||
body_format: json
|
||||
body:
|
||||
name: "{{ vm_name }}"
|
||||
guest_OS: OTHER_GUEST_64
|
||||
placement:
|
||||
folder: group-v23
|
||||
host: host-11
|
||||
datastore: datastore-31
|
||||
resource_pool: resgroup-22
|
||||
cpu:
|
||||
count: 1
|
||||
memory:
|
||||
size_MiB: 512
|
||||
validate_certs: false
|
||||
status_code: [200, 201]
|
||||
return_content: true
|
||||
register: created
|
||||
|
||||
- name: Power on
|
||||
ansible.builtin.uri:
|
||||
url: "{{ vsphere_base }}/api/vcenter/vm/{{ created.json }}/power?action=start"
|
||||
method: POST
|
||||
headers: "{{ vsphere_headers }}"
|
||||
validate_certs: false
|
||||
status_code: [200, 204]
|
||||
return_content: true
|
||||
register: power_on
|
||||
|
||||
- name: Poll CIS task when returned
|
||||
ansible.builtin.uri:
|
||||
url: "{{ vsphere_base }}/api/cis/tasks/{{ power_on.json.task }}"
|
||||
headers: "{{ vsphere_headers }}"
|
||||
validate_certs: false
|
||||
return_content: true
|
||||
register: task
|
||||
when: power_on.json is mapping and power_on.json.task is defined
|
||||
until: task.json.status in ['SUCCEEDED', 'FAILED']
|
||||
retries: 30
|
||||
delay: 1
|
||||
|
||||
- name: Write guest file (lab virtual FS)
|
||||
ansible.builtin.uri:
|
||||
url: "{{ vsphere_base }}/api/vcenter/vm/{{ created.json }}/guest/filesystem?path=/tmp/ansible-marker"
|
||||
method: PUT
|
||||
headers: "{{ vsphere_headers }}"
|
||||
body_format: json
|
||||
body:
|
||||
content: "ansible-ok"
|
||||
validate_certs: false
|
||||
status_code: [200, 204]
|
||||
|
||||
- name: Power off
|
||||
ansible.builtin.uri:
|
||||
url: "{{ vsphere_base }}/api/vcenter/vm/{{ created.json }}/power?action=stop"
|
||||
method: POST
|
||||
headers: "{{ vsphere_headers }}"
|
||||
validate_certs: false
|
||||
status_code: [200, 204]
|
||||
|
||||
- name: Delete VM
|
||||
ansible.builtin.uri:
|
||||
url: "{{ vsphere_base }}/api/vcenter/vm/{{ created.json }}"
|
||||
method: DELETE
|
||||
headers: "{{ vsphere_headers }}"
|
||||
validate_certs: false
|
||||
status_code: [200, 204]
|
||||
|
||||
- name: Delete session
|
||||
ansible.builtin.uri:
|
||||
url: "{{ vsphere_base }}/api/session"
|
||||
method: DELETE
|
||||
headers: "{{ vsphere_headers }}"
|
||||
validate_certs: false
|
||||
status_code: [200, 204]
|
||||
@@ -0,0 +1,3 @@
|
||||
module example.com/vmware-api-simulator-cookbook
|
||||
|
||||
go 1.22
|
||||
@@ -0,0 +1,121 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func env(k, def string) string {
|
||||
if v := os.Getenv(k); v != "" {
|
||||
return v
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
var client = &http.Client{
|
||||
Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}, // lab self-signed cert only
|
||||
}
|
||||
|
||||
func main() {
|
||||
base := strings.TrimRight(env("VSPHERE_BASE", "https://localhost"), "/")
|
||||
user := env("VSPHERE_USER", "administrator@vsphere.local")
|
||||
password := env("VSPHERE_PASSWORD", "VMware1!")
|
||||
vmName := env("VSPHERE_VM_NAME", "go-lab-01")
|
||||
|
||||
auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(user+":"+password))
|
||||
var sessionID string
|
||||
call(base+"/api/session", "POST", map[string]string{"Authorization": auth}, nil, &sessionID)
|
||||
fmt.Println("session:", sessionID)
|
||||
|
||||
headers := map[string]string{"vmware-api-session-id": sessionID}
|
||||
|
||||
var vms []map[string]any
|
||||
call(base+"/api/vcenter/vm", "GET", headers, nil, &vms)
|
||||
fmt.Printf("vms before: %d\n", len(vms))
|
||||
|
||||
body, _ := json.Marshal(map[string]any{
|
||||
"name": vmName,
|
||||
"guest_OS": "OTHER_GUEST_64",
|
||||
"placement": map[string]string{
|
||||
"folder": "group-v23",
|
||||
"host": "host-11",
|
||||
"datastore": "datastore-31",
|
||||
"resource_pool": "resgroup-22",
|
||||
},
|
||||
"cpu": map[string]int{"count": 1},
|
||||
"memory": map[string]int{"size_MiB": 512},
|
||||
})
|
||||
var moid string
|
||||
call(base+"/api/vcenter/vm", "POST", headers, body, &moid)
|
||||
fmt.Println("created:", moid)
|
||||
|
||||
var power map[string]string
|
||||
call(base+"/api/vcenter/vm/"+moid+"/power?action=start", "POST", headers, nil, &power)
|
||||
waitTask(base, headers, power["task"])
|
||||
|
||||
var detail map[string]any
|
||||
call(base+"/api/vcenter/vm/"+moid, "GET", headers, nil, &detail)
|
||||
fmt.Println("power_state:", detail["power_state"])
|
||||
|
||||
call(base+"/api/vcenter/vm/"+moid+"/power?action=stop", "POST", headers, nil, &power)
|
||||
waitTask(base, headers, power["task"])
|
||||
|
||||
call(base+"/api/vcenter/vm/"+moid, "DELETE", headers, nil, nil)
|
||||
call(base+"/api/session", "DELETE", headers, nil, nil)
|
||||
fmt.Println("ok")
|
||||
}
|
||||
|
||||
func waitTask(base string, headers map[string]string, task string) {
|
||||
if task == "" {
|
||||
return
|
||||
}
|
||||
deadline := time.Now().Add(2 * time.Minute)
|
||||
for time.Now().Before(deadline) {
|
||||
var status map[string]any
|
||||
call(base+"/api/cis/tasks/"+task, "GET", headers, nil, &status)
|
||||
if s, _ := status["status"].(string); s == "SUCCEEDED" || s == "FAILED" {
|
||||
return
|
||||
}
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
}
|
||||
panic("timeout waiting for task " + task)
|
||||
}
|
||||
|
||||
func call(url, method string, headers map[string]string, body []byte, out any) {
|
||||
var reqBody io.Reader
|
||||
if body != nil {
|
||||
reqBody = strings.NewReader(string(body))
|
||||
}
|
||||
req, err := http.NewRequest(method, url, reqBody)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for k, v := range headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
if body != nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
raw, _ := io.ReadAll(resp.Body)
|
||||
if resp.StatusCode >= 300 {
|
||||
panic(fmt.Sprintf("%s %s: %d %s", method, url, resp.StatusCode, raw))
|
||||
}
|
||||
if out == nil || len(raw) == 0 {
|
||||
return
|
||||
}
|
||||
if err := json.Unmarshal(raw, out); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpRequest.BodyPublishers;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Duration;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* Minimal Java 11+ cookbook against the vSphere REST gateway using a Basic-auth
|
||||
* session (vmware-api-session-id).
|
||||
*
|
||||
* javac Cookbook.java && java Cookbook
|
||||
*/
|
||||
public final class Cookbook {
|
||||
private static final HttpClient CLIENT =
|
||||
HttpClient.newBuilder().sslContext(insecureSslContext()).build();
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
String base = env("VSPHERE_BASE", "https://localhost");
|
||||
String user = env("VSPHERE_USER", "administrator@vsphere.local");
|
||||
String password = env("VSPHERE_PASSWORD", "VMware1!");
|
||||
String vmName = env("VSPHERE_VM_NAME", "java-lab-01");
|
||||
|
||||
String auth =
|
||||
"Basic " + Base64.getEncoder().encodeToString((user + ":" + password).getBytes(StandardCharsets.UTF_8));
|
||||
String sessionId = unquote(send(post(base + "/api/session", auth, null)));
|
||||
System.out.println("session: " + sessionId);
|
||||
|
||||
String vms = send(get(base + "/api/vcenter/vm", sessionId));
|
||||
System.out.println("vms before: " + vms.length() + " bytes");
|
||||
|
||||
String createBody =
|
||||
"{\"name\":\""
|
||||
+ vmName
|
||||
+ "\",\"guest_OS\":\"OTHER_GUEST_64\","
|
||||
+ "\"placement\":{\"folder\":\"group-v23\",\"host\":\"host-11\","
|
||||
+ "\"datastore\":\"datastore-31\",\"resource_pool\":\"resgroup-22\"},"
|
||||
+ "\"cpu\":{\"count\":1},\"memory\":{\"size_MiB\":512}}";
|
||||
String moid = unquote(send(postJson(base + "/api/vcenter/vm", sessionId, createBody)));
|
||||
System.out.println("created: " + moid);
|
||||
|
||||
String power = send(post(base + "/api/vcenter/vm/" + moid + "/power?action=start", sessionId, null));
|
||||
waitTask(base, sessionId, extractField(power, "task"));
|
||||
|
||||
String detail = send(get(base + "/api/vcenter/vm/" + moid, sessionId));
|
||||
System.out.println("power_state: " + extractField(detail, "power_state"));
|
||||
|
||||
power = send(post(base + "/api/vcenter/vm/" + moid + "/power?action=stop", sessionId, null));
|
||||
waitTask(base, sessionId, extractField(power, "task"));
|
||||
|
||||
send(delete(base + "/api/vcenter/vm/" + moid, sessionId));
|
||||
send(delete(base + "/api/session", sessionId));
|
||||
System.out.println("ok");
|
||||
}
|
||||
|
||||
private static void waitTask(String base, String sessionId, String task) throws Exception {
|
||||
if (task == null || task.isBlank()) {
|
||||
return;
|
||||
}
|
||||
long deadline = System.currentTimeMillis() + 120_000;
|
||||
while (System.currentTimeMillis() < deadline) {
|
||||
String body = send(get(base + "/api/cis/tasks/" + task, sessionId));
|
||||
String status = extractField(body, "status");
|
||||
if ("SUCCEEDED".equals(status) || "FAILED".equals(status)) {
|
||||
return;
|
||||
}
|
||||
Thread.sleep(300);
|
||||
}
|
||||
throw new IllegalStateException("timeout waiting for task " + task);
|
||||
}
|
||||
|
||||
private static String env(String key, String def) {
|
||||
String value = System.getenv(key);
|
||||
return value == null || value.isBlank() ? def : value;
|
||||
}
|
||||
|
||||
private static HttpRequest get(String uri, String sessionId) {
|
||||
return HttpRequest.newBuilder(URI.create(uri))
|
||||
.timeout(Duration.ofSeconds(60))
|
||||
.header("vmware-api-session-id", sessionId)
|
||||
.GET()
|
||||
.build();
|
||||
}
|
||||
|
||||
private static HttpRequest post(String uri, String auth, String body) {
|
||||
HttpRequest.Builder builder =
|
||||
HttpRequest.newBuilder(URI.create(uri)).timeout(Duration.ofSeconds(60));
|
||||
if (auth.startsWith("Basic ")) {
|
||||
builder.header("Authorization", auth);
|
||||
} else {
|
||||
builder.header("vmware-api-session-id", auth);
|
||||
}
|
||||
return builder.POST(body == null ? BodyPublishers.noBody() : BodyPublishers.ofString(body)).build();
|
||||
}
|
||||
|
||||
private static HttpRequest postJson(String uri, String sessionId, String body) {
|
||||
return HttpRequest.newBuilder(URI.create(uri))
|
||||
.timeout(Duration.ofSeconds(60))
|
||||
.header("vmware-api-session-id", sessionId)
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(BodyPublishers.ofString(body))
|
||||
.build();
|
||||
}
|
||||
|
||||
private static HttpRequest delete(String uri, String sessionId) {
|
||||
return HttpRequest.newBuilder(URI.create(uri))
|
||||
.timeout(Duration.ofSeconds(60))
|
||||
.header("vmware-api-session-id", sessionId)
|
||||
.DELETE()
|
||||
.build();
|
||||
}
|
||||
|
||||
private static String send(HttpRequest request) throws Exception {
|
||||
HttpResponse<String> response = CLIENT.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
if (response.statusCode() >= 300) {
|
||||
throw new RuntimeException(response.statusCode() + ": " + response.body());
|
||||
}
|
||||
return response.body();
|
||||
}
|
||||
|
||||
private static String unquote(String value) {
|
||||
String trimmed = value.trim();
|
||||
if (trimmed.startsWith("\"") && trimmed.endsWith("\"")) {
|
||||
return trimmed.substring(1, trimmed.length() - 1);
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
/** Tiny string-based field extraction — good enough for a lab smoke, no JSON library required. */
|
||||
private static String extractField(String body, String field) {
|
||||
String marker = "\"" + field + "\":";
|
||||
int start = body.indexOf(marker);
|
||||
if (start < 0) {
|
||||
return "";
|
||||
}
|
||||
start += marker.length();
|
||||
while (start < body.length() && (body.charAt(start) == ' ' || body.charAt(start) == '"')) {
|
||||
start++;
|
||||
}
|
||||
int end = start;
|
||||
while (end < body.length() && body.charAt(end) != '"' && body.charAt(end) != ',' && body.charAt(end) != '}') {
|
||||
end++;
|
||||
}
|
||||
return body.substring(start, end);
|
||||
}
|
||||
|
||||
private static javax.net.ssl.SSLContext insecureSslContext() {
|
||||
try {
|
||||
javax.net.ssl.TrustManager[] trustAll =
|
||||
new javax.net.ssl.TrustManager[] {
|
||||
new javax.net.ssl.X509TrustManager() {
|
||||
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
|
||||
return new java.security.cert.X509Certificate[0];
|
||||
}
|
||||
|
||||
public void checkClientTrusted(java.security.cert.X509Certificate[] certs, String authType) {}
|
||||
|
||||
public void checkServerTrusted(java.security.cert.X509Certificate[] certs, String authType) {}
|
||||
}
|
||||
};
|
||||
javax.net.ssl.SSLContext context = javax.net.ssl.SSLContext.getInstance("TLS");
|
||||
context.init(null, trustAll, new java.security.SecureRandom());
|
||||
return context;
|
||||
} catch (Exception error) {
|
||||
throw new RuntimeException(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env perl
|
||||
use strict;
|
||||
use warnings;
|
||||
use HTTP::Tiny;
|
||||
use JSON qw(decode_json encode_json);
|
||||
use MIME::Base64 qw(encode_base64);
|
||||
use Time::HiRes qw(sleep);
|
||||
|
||||
my $base = $ENV{VSPHERE_BASE} // 'https://localhost';
|
||||
my $user = $ENV{VSPHERE_USER} // 'administrator@vsphere.local';
|
||||
my $password = $ENV{VSPHERE_PASSWORD} // 'VMware1!';
|
||||
my $vm_name = $ENV{VSPHERE_VM_NAME} // 'perl-lab-01';
|
||||
|
||||
# Lab self-signed certificate only — do not disable verification against a real vCenter.
|
||||
my $http = HTTP::Tiny->new(timeout => 60, verify_SSL => 0);
|
||||
|
||||
sub api {
|
||||
my ($method, $path, %opts) = @_;
|
||||
my $res = $http->request($method, "$base$path", \%opts);
|
||||
die "$method $path failed: $res->{status} $res->{content}\n" unless $res->{success};
|
||||
return $res->{content};
|
||||
}
|
||||
|
||||
sub json_field {
|
||||
my ($body, $field) = @_;
|
||||
my $data = decode_json($body);
|
||||
return $data->{$field};
|
||||
}
|
||||
|
||||
sub wait_task {
|
||||
my ($task, $headers) = @_;
|
||||
return unless $task;
|
||||
my $deadline = time + 120;
|
||||
while (time < $deadline) {
|
||||
my $body = api('GET', "/api/cis/tasks/$task", headers => $headers);
|
||||
my $status = json_field($body, 'status');
|
||||
return if $status eq 'SUCCEEDED' || $status eq 'FAILED';
|
||||
sleep(0.3);
|
||||
}
|
||||
die "timeout waiting for task $task\n";
|
||||
}
|
||||
|
||||
my $auth = 'Basic ' . encode_base64("$user:$password", '');
|
||||
my $session_body = api('POST', '/api/session', headers => { Authorization => $auth });
|
||||
(my $session_id = $session_body) =~ s/^"(.*)"$/$1/;
|
||||
print "session: $session_id\n";
|
||||
|
||||
my %headers = ('vmware-api-session-id' => $session_id);
|
||||
|
||||
my $vms_before = api('GET', '/api/vcenter/vm', headers => \%headers);
|
||||
print 'vms before: ', scalar(@{ decode_json($vms_before) }), "\n";
|
||||
|
||||
my $create_body = encode_json({
|
||||
name => $vm_name,
|
||||
guest_OS => 'OTHER_GUEST_64',
|
||||
placement => {
|
||||
folder => 'group-v23',
|
||||
host => 'host-11',
|
||||
datastore => 'datastore-31',
|
||||
resource_pool => 'resgroup-22',
|
||||
},
|
||||
cpu => { count => 1 },
|
||||
memory => { size_MiB => 512 },
|
||||
});
|
||||
my $created = api(
|
||||
'POST', '/api/vcenter/vm',
|
||||
headers => { %headers, 'Content-Type' => 'application/json' },
|
||||
content => $create_body,
|
||||
);
|
||||
(my $moid = $created) =~ s/^"(.*)"$/$1/;
|
||||
print "created: $moid\n";
|
||||
|
||||
my $power = api('POST', "/api/vcenter/vm/$moid/power?action=start", headers => \%headers);
|
||||
wait_task(json_field($power, 'task'), \%headers);
|
||||
|
||||
my $detail = api('GET', "/api/vcenter/vm/$moid", headers => \%headers);
|
||||
print 'power_state: ', json_field($detail, 'power_state'), "\n";
|
||||
|
||||
$power = api('POST', "/api/vcenter/vm/$moid/power?action=stop", headers => \%headers);
|
||||
wait_task(json_field($power, 'task'), \%headers);
|
||||
|
||||
api('DELETE', "/api/vcenter/vm/$moid", headers => \%headers);
|
||||
api('DELETE', '/api/session', headers => \%headers);
|
||||
print "ok\n";
|
||||
@@ -0,0 +1,3 @@
|
||||
requires 'HTTP::Tiny';
|
||||
requires 'JSON';
|
||||
requires 'IO::Socket::SSL';
|
||||
@@ -0,0 +1,3 @@
|
||||
name: vmware-api-simulator
|
||||
runtime: python
|
||||
description: pulumi-vsphere cookbook against vmware-api-simulator
|
||||
@@ -0,0 +1,69 @@
|
||||
"""Pulumi cookbook using official pulumi-vsphere against the simulator."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
import pulumi
|
||||
import pulumi_vsphere as vsphere
|
||||
|
||||
config = pulumi.Config()
|
||||
user = config.get("user") or os.environ.get("VSPHERE_USER", "administrator@vsphere.local")
|
||||
password = config.get_secret("password") or os.environ.get("VSPHERE_PASSWORD", "VMware1!")
|
||||
server = config.get("server") or os.environ.get("VSPHERE_SERVER", "localhost")
|
||||
datacenter_name = config.get("datacenter") or "Datacenter"
|
||||
datastore_name = config.get("datastore") or "datastore1"
|
||||
cluster_name = config.get("cluster") or "Cluster"
|
||||
network_name = config.get("network") or "VM Network"
|
||||
vm_name = config.get("vm_name") or "pulumi-lab-01"
|
||||
|
||||
provider = vsphere.Provider(
|
||||
"vsphere",
|
||||
user=user,
|
||||
password=password,
|
||||
vsphere_server=server,
|
||||
allow_unverified_ssl=True,
|
||||
)
|
||||
invoke_opts = pulumi.InvokeOptions(provider=provider)
|
||||
res_opts = pulumi.ResourceOptions(provider=provider)
|
||||
|
||||
dc = vsphere.get_datacenter_output(name=datacenter_name, opts=invoke_opts)
|
||||
ds = vsphere.get_datastore_output(
|
||||
name=datastore_name,
|
||||
datacenter_id=dc.id,
|
||||
opts=invoke_opts,
|
||||
)
|
||||
cluster = vsphere.get_compute_cluster_output(
|
||||
name=cluster_name,
|
||||
datacenter_id=dc.id,
|
||||
opts=invoke_opts,
|
||||
)
|
||||
network = vsphere.get_network_output(
|
||||
name=network_name,
|
||||
datacenter_id=dc.id,
|
||||
opts=invoke_opts,
|
||||
)
|
||||
|
||||
vm = vsphere.VirtualMachine(
|
||||
"cookbook-vm",
|
||||
name=vm_name,
|
||||
resource_pool_id=cluster.resource_pool_id,
|
||||
datastore_id=ds.id,
|
||||
num_cpus=1,
|
||||
memory=1024,
|
||||
guest_id="otherGuest64",
|
||||
wait_for_guest_net_timeout=0,
|
||||
wait_for_guest_ip_timeout=0,
|
||||
network_interfaces=[
|
||||
vsphere.VirtualMachineNetworkInterfaceArgs(network_id=network.id),
|
||||
],
|
||||
disks=[
|
||||
vsphere.VirtualMachineDiskArgs(label="disk0", size=16),
|
||||
],
|
||||
opts=res_opts,
|
||||
)
|
||||
|
||||
pulumi.export("datacenter_id", dc.id)
|
||||
pulumi.export("lab_vm_id", vm.id)
|
||||
pulumi.export("lab_vm_name", vm.name)
|
||||
pulumi.export("resource_pool_id", cluster.resource_pool_id)
|
||||
@@ -0,0 +1,2 @@
|
||||
pulumi>=3.0.0,<4
|
||||
pulumi-vsphere==4.17.0
|
||||
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Raw `requests` cookbook against the vSphere REST gateway (no vsphere-automation-sdk)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
import requests
|
||||
import urllib3
|
||||
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
BASE = os.environ.get("VSPHERE_BASE", "https://localhost").rstrip("/")
|
||||
USER = os.environ.get("VSPHERE_USER", "administrator@vsphere.local")
|
||||
PASSWORD = os.environ.get("VSPHERE_PASSWORD", "VMware1!")
|
||||
VM_NAME = os.environ.get("VSPHERE_VM_NAME", "req-lab-01")
|
||||
|
||||
|
||||
def api(
|
||||
method: str,
|
||||
path: str,
|
||||
*,
|
||||
headers: dict[str, str] | None = None,
|
||||
json_body: dict[str, Any] | None = None,
|
||||
) -> Any:
|
||||
response = requests.request(
|
||||
method,
|
||||
f"{BASE}{path}",
|
||||
headers=headers,
|
||||
json=json_body,
|
||||
verify=False, # local self-signed development certificate only
|
||||
timeout=60,
|
||||
)
|
||||
response.raise_for_status()
|
||||
if response.status_code == 204 or not response.content:
|
||||
return None
|
||||
return response.json()
|
||||
|
||||
|
||||
def wait_task(headers: dict[str, str], task: str, timeout: float = 120.0) -> None:
|
||||
deadline = time.time() + timeout
|
||||
while time.time() < deadline:
|
||||
status = api("GET", f"/api/cis/tasks/{task}", headers=headers)
|
||||
if status.get("status") in {"SUCCEEDED", "FAILED"}:
|
||||
return
|
||||
time.sleep(0.3)
|
||||
raise TimeoutError(task)
|
||||
|
||||
|
||||
def session_headers() -> dict[str, str]:
|
||||
response = requests.post(
|
||||
f"{BASE}/api/session",
|
||||
auth=(USER, PASSWORD),
|
||||
verify=False,
|
||||
timeout=60,
|
||||
)
|
||||
response.raise_for_status()
|
||||
return {"vmware-api-session-id": response.json()}
|
||||
|
||||
|
||||
def main() -> int:
|
||||
headers = session_headers()
|
||||
print("session:", headers["vmware-api-session-id"])
|
||||
|
||||
vms = api("GET", "/api/vcenter/vm", headers=headers)
|
||||
print("vms before:", len(vms))
|
||||
|
||||
moid = api(
|
||||
"POST",
|
||||
"/api/vcenter/vm",
|
||||
headers=headers,
|
||||
json_body={
|
||||
"name": VM_NAME,
|
||||
"guest_OS": "OTHER_GUEST_64",
|
||||
"placement": {
|
||||
"folder": "group-v23",
|
||||
"host": "host-11",
|
||||
"datastore": "datastore-31",
|
||||
"resource_pool": "resgroup-22",
|
||||
},
|
||||
"cpu": {"count": 1},
|
||||
"memory": {"size_MiB": 512},
|
||||
},
|
||||
)
|
||||
print("created:", moid)
|
||||
|
||||
power = api("POST", f"/api/vcenter/vm/{moid}/power?action=start", headers=headers)
|
||||
wait_task(headers, power["task"])
|
||||
|
||||
detail = api("GET", f"/api/vcenter/vm/{moid}", headers=headers)
|
||||
print("power_state:", detail["power_state"])
|
||||
|
||||
power = api("POST", f"/api/vcenter/vm/{moid}/power?action=stop", headers=headers)
|
||||
wait_task(headers, power["task"])
|
||||
|
||||
api("DELETE", f"/api/vcenter/vm/{moid}", headers=headers)
|
||||
api("DELETE", "/api/session", headers=headers)
|
||||
print("ok")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1 @@
|
||||
requests>=2.31
|
||||
@@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env python3
|
||||
"""End-to-end Python smoke: REST create/power/guest-file + SOAP CreateVM_Task."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
import requests
|
||||
import urllib3
|
||||
|
||||
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
||||
|
||||
BASE = os.environ.get("VSPHERE_BASE", "https://localhost").rstrip("/")
|
||||
USER = os.environ.get("VSPHERE_USER", "administrator@vsphere.local")
|
||||
PASSWORD = os.environ.get("VSPHERE_PASSWORD", "VMware1!")
|
||||
|
||||
|
||||
def rest_session() -> dict[str, str]:
|
||||
response = requests.post(f"{BASE}/api/session", auth=(USER, PASSWORD), verify=False, timeout=60)
|
||||
response.raise_for_status()
|
||||
return {"vmware-api-session-id": response.json()}
|
||||
|
||||
|
||||
def rest_lifecycle(headers: dict[str, str]) -> str:
|
||||
created = requests.post(
|
||||
f"{BASE}/api/vcenter/vm",
|
||||
headers=headers,
|
||||
json={
|
||||
"name": "py-rest-lab-01",
|
||||
"guest_OS": "OTHER_GUEST_64",
|
||||
"placement": {
|
||||
"folder": "group-v23",
|
||||
"host": "host-11",
|
||||
"datastore": "datastore-31",
|
||||
"resource_pool": "resgroup-22",
|
||||
},
|
||||
"cpu": {"count": 1},
|
||||
"memory": {"size_MiB": 512},
|
||||
},
|
||||
verify=False,
|
||||
timeout=60,
|
||||
)
|
||||
created.raise_for_status()
|
||||
vm = created.json()
|
||||
power = requests.post(
|
||||
f"{BASE}/api/vcenter/vm/{vm}/power",
|
||||
params={"action": "start"},
|
||||
headers=headers,
|
||||
verify=False,
|
||||
timeout=60,
|
||||
)
|
||||
power.raise_for_status()
|
||||
assert "task" in power.json()
|
||||
put = requests.put(
|
||||
f"{BASE}/api/vcenter/vm/{vm}/guest/filesystem",
|
||||
params={"path": "/tmp/python-marker"},
|
||||
headers=headers,
|
||||
json={"content": "ok"},
|
||||
verify=False,
|
||||
timeout=60,
|
||||
)
|
||||
put.raise_for_status()
|
||||
requests.post(
|
||||
f"{BASE}/api/vcenter/vm/{vm}/power",
|
||||
params={"action": "stop"},
|
||||
headers=headers,
|
||||
verify=False,
|
||||
timeout=60,
|
||||
).raise_for_status()
|
||||
requests.delete(
|
||||
f"{BASE}/api/vcenter/vm/{vm}", headers=headers, verify=False, timeout=60
|
||||
).raise_for_status()
|
||||
return vm
|
||||
|
||||
|
||||
def soap_create_vm(session_id: str) -> str:
|
||||
envelope = """<?xml version="1.0"?>
|
||||
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
|
||||
<soapenv:Body>
|
||||
<CreateVM_Task xmlns="urn:vim25">
|
||||
<_this type="Folder">group-v23</_this>
|
||||
<config>
|
||||
<name>py-soap-lab-01</name>
|
||||
<guestId>otherGuest64</guestId>
|
||||
<numCPUs>1</numCPUs>
|
||||
<memoryMB>512</memoryMB>
|
||||
<files><vmPathName>[datastore1]</vmPathName></files>
|
||||
</config>
|
||||
<pool type="ResourcePool">resgroup-22</pool>
|
||||
<host type="HostSystem">host-11</host>
|
||||
</CreateVM_Task>
|
||||
</soapenv:Body>
|
||||
</soapenv:Envelope>"""
|
||||
response = requests.post(
|
||||
f"{BASE}/sdk",
|
||||
data=envelope,
|
||||
headers={
|
||||
"Content-Type": "text/xml",
|
||||
"vmware-api-session-id": session_id,
|
||||
"Cookie": f'vmware_soap_session="{session_id}"',
|
||||
},
|
||||
verify=False,
|
||||
timeout=60,
|
||||
)
|
||||
response.raise_for_status()
|
||||
root = ET.fromstring(response.text)
|
||||
task = None
|
||||
for node in root.iter():
|
||||
if node.tag.endswith("returnval") and (node.text or "").startswith("task-"):
|
||||
task = node.text
|
||||
break
|
||||
assert task, response.text
|
||||
return task
|
||||
|
||||
|
||||
def main() -> int:
|
||||
headers = rest_session()
|
||||
rest_vm = rest_lifecycle(headers)
|
||||
soap_task = soap_create_vm(headers["vmware-api-session-id"])
|
||||
print(f"REST VM lifecycle ok: {rest_vm}")
|
||||
print(f"SOAP CreateVM_Task ok: {soap_task}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Smoke the native vSphere REST surface against a running simulator."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from base64 import b64encode
|
||||
|
||||
BASE = sys.argv[1] if len(sys.argv) > 1 else "https://localhost"
|
||||
USER = "administrator@vsphere.local"
|
||||
PASSWORD = "VMware1!"
|
||||
|
||||
|
||||
def _req(
|
||||
method: str, path: str, *, headers: dict[str, str] | None = None, data: bytes | None = None
|
||||
):
|
||||
request = urllib.request.Request(
|
||||
f"{BASE}{path}",
|
||||
data=data,
|
||||
method=method,
|
||||
headers=headers or {},
|
||||
)
|
||||
ctx = None
|
||||
if BASE.startswith("https://"):
|
||||
import ssl
|
||||
|
||||
ctx = ssl._create_unverified_context() # noqa: S323 - lab self-signed
|
||||
with urllib.request.urlopen(request, context=ctx) as response: # noqa: S310
|
||||
body = response.read()
|
||||
return response.status, dict(response.headers), body
|
||||
|
||||
|
||||
def main() -> int:
|
||||
basic = b64encode(f"{USER}:{PASSWORD}".encode()).decode()
|
||||
status, headers, body = _req(
|
||||
"POST",
|
||||
"/api/session",
|
||||
headers={"Authorization": f"Basic {basic}"},
|
||||
)
|
||||
print("session", status, body.decode())
|
||||
session = json.loads(body.decode())
|
||||
sess_headers = {"vmware-api-session-id": session}
|
||||
status, _, vms = _req("GET", "/api/vcenter/vm", headers=sess_headers)
|
||||
print("vms", status, vms.decode())
|
||||
status, _, hosts = _req("GET", "/api/vcenter/host", headers=sess_headers)
|
||||
print("hosts", status, hosts.decode())
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
raise SystemExit(main())
|
||||
except urllib.error.URLError as error:
|
||||
print(f"failed: {error}", file=sys.stderr)
|
||||
raise SystemExit(1) from error
|
||||
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Minimal SOAP /sdk smoke (RetrieveServiceContent + Login envelope)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import ssl
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
BASE = sys.argv[1] if len(sys.argv) > 1 else "https://localhost"
|
||||
|
||||
CONTENT = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
|
||||
xmlns:urn="urn:vim25">
|
||||
<soapenv:Body>
|
||||
<urn:RetrieveServiceContent>
|
||||
<urn:_this type="ServiceInstance">ServiceInstance</urn:_this>
|
||||
</urn:RetrieveServiceContent>
|
||||
</soapenv:Body>
|
||||
</soapenv:Envelope>
|
||||
"""
|
||||
|
||||
LOGIN = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
|
||||
xmlns:urn="urn:vim25">
|
||||
<soapenv:Body>
|
||||
<urn:Login>
|
||||
<urn:_this type="SessionManager">SessionManager</urn:_this>
|
||||
<urn:userName>administrator@vsphere.local</urn:userName>
|
||||
<urn:password>VMware1!</urn:password>
|
||||
</urn:Login>
|
||||
</soapenv:Body>
|
||||
</soapenv:Envelope>
|
||||
"""
|
||||
|
||||
|
||||
def _post(path: str, body: str) -> tuple[int, str]:
|
||||
req = urllib.request.Request(
|
||||
f"{BASE}{path}",
|
||||
data=body.encode(),
|
||||
method="POST",
|
||||
headers={"Content-Type": "text/xml; charset=utf-8", "SOAPAction": '""'},
|
||||
)
|
||||
ctx = ssl._create_unverified_context() if BASE.startswith("https://") else None # noqa: S323
|
||||
with urllib.request.urlopen(req, context=ctx) as resp: # noqa: S310
|
||||
return int(resp.status), resp.read().decode()
|
||||
|
||||
|
||||
def main() -> int:
|
||||
status, body = _post("/sdk", CONTENT)
|
||||
print("RetrieveServiceContent", status, "ServiceContent" in body)
|
||||
if "ServiceContent" not in body:
|
||||
return 1
|
||||
status, body = _post("/sdk", LOGIN)
|
||||
login_ok = "LoginResponse" in body or "UserSession" in body or "key>" in body
|
||||
print("Login", status, login_ok)
|
||||
if not login_ok:
|
||||
print(body[:400], file=sys.stderr)
|
||||
return 1
|
||||
wsdl = urllib.request.Request(f"{BASE}/sdk/vimService.wsdl")
|
||||
ctx = ssl._create_unverified_context() if BASE.startswith("https://") else None # noqa: S323
|
||||
with urllib.request.urlopen(wsdl, context=ctx) as resp: # noqa: S310
|
||||
print("wsdl", resp.status, len(resp.read()))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
raise SystemExit(main())
|
||||
except urllib.error.URLError as error:
|
||||
print(f"failed: {error}", file=sys.stderr)
|
||||
raise SystemExit(1) from error
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
# This file is maintained automatically by "terraform init".
|
||||
# Manual edits may be lost in future updates.
|
||||
|
||||
provider "registry.terraform.io/hashicorp/vsphere" {
|
||||
version = "2.12.0"
|
||||
constraints = "~> 2.8"
|
||||
hashes = [
|
||||
"h1:WqZLjrDFhrpS0Y0IdSrKGNE/iuJf3l38K8Wz+ZRlJLE=",
|
||||
"zh:02eee1071636834570106f70df904807e4d5cff2cf378c9e1dcfbebbeef4411b",
|
||||
"zh:0ea4ed072d6b9bd8517a1b5c1b06ec05fac6293376c94a9b4625a41ca0626117",
|
||||
"zh:0fa82a384b25a58b65523e0ea4768fa1212b1f5cfc0c9379d31162454fedcc9d",
|
||||
"zh:23cede3a4cb8080662dc0416f2ea6b43e6ca0907c3af0ddae544df923b878080",
|
||||
"zh:2fd2e293115fe4a3ca5f3df051ef852315d5eada304ffa8caaa11d95d15f2ab0",
|
||||
"zh:532577c8897109a23eb768d23479870ccc353987b9bf28fd6532c1adc2878743",
|
||||
"zh:a575a9d5e0b08866fe78da9bf50afee29a3278a0f7e0c269f0626e4a4d25bba1",
|
||||
"zh:c7461658ae2d81881e84a37b6e1c69b05fcee1110fac0b57ebf7ea1a0f5406f2",
|
||||
"zh:d9af3f73391a02d8c1d0e7a0f7da72afff8d49445da8c98bf6e63ad799c5dbc9",
|
||||
"zh:deeed7463f1f532bb3edf70c786c8e3eca0b255cb1c74bcf69a5978d4757ed2c",
|
||||
"zh:ed21ac2b742326098818876125c3a8177b76ae01ce82a88238b786c8020ef151",
|
||||
"zh:f4ff28277d68956d1adc94c5d6fbc109f4a389e8ebb707d24b99ab3113235428",
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
terraform {
|
||||
required_providers {
|
||||
vsphere = {
|
||||
source = "hashicorp/vsphere"
|
||||
version = "~> 2.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "vsphere" {
|
||||
user = var.vsphere_user
|
||||
password = var.vsphere_password
|
||||
vsphere_server = var.vsphere_server
|
||||
allow_unverified_ssl = true
|
||||
}
|
||||
|
||||
data "vsphere_datacenter" "dc" {
|
||||
name = var.datacenter
|
||||
}
|
||||
|
||||
data "vsphere_datastore" "ds" {
|
||||
name = var.datastore
|
||||
datacenter_id = data.vsphere_datacenter.dc.id
|
||||
}
|
||||
|
||||
data "vsphere_compute_cluster" "cluster" {
|
||||
name = var.cluster
|
||||
datacenter_id = data.vsphere_datacenter.dc.id
|
||||
}
|
||||
|
||||
data "vsphere_network" "network" {
|
||||
name = var.network
|
||||
datacenter_id = data.vsphere_datacenter.dc.id
|
||||
}
|
||||
|
||||
data "vsphere_virtual_machine" "web" {
|
||||
name = var.vm_name
|
||||
datacenter_id = data.vsphere_datacenter.dc.id
|
||||
}
|
||||
|
||||
# Lab VM create (SOAP CreateVM_Task). Use a unique name per apply.
|
||||
resource "vsphere_virtual_machine" "lab" {
|
||||
count = var.create_lab_vm ? 1 : 0
|
||||
name = var.lab_vm_name
|
||||
resource_pool_id = data.vsphere_compute_cluster.cluster.resource_pool_id
|
||||
datastore_id = data.vsphere_datastore.ds.id
|
||||
num_cpus = 1
|
||||
memory = 1024
|
||||
guest_id = "otherGuest64"
|
||||
wait_for_guest_net_timeout = 0
|
||||
wait_for_guest_ip_timeout = 0
|
||||
|
||||
network_interface {
|
||||
network_id = data.vsphere_network.network.id
|
||||
}
|
||||
|
||||
disk {
|
||||
label = "disk0"
|
||||
size = 16
|
||||
}
|
||||
}
|
||||
|
||||
output "vm_id" {
|
||||
value = data.vsphere_virtual_machine.web.id
|
||||
}
|
||||
|
||||
output "lab_vm_id" {
|
||||
value = try(vsphere_virtual_machine.lab[0].id, null)
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
variable "vsphere_server" {
|
||||
type = string
|
||||
default = "localhost"
|
||||
}
|
||||
|
||||
variable "vsphere_user" {
|
||||
type = string
|
||||
default = "administrator@vsphere.local"
|
||||
}
|
||||
|
||||
variable "vsphere_password" {
|
||||
type = string
|
||||
default = "VMware1!"
|
||||
sensitive = true
|
||||
}
|
||||
|
||||
variable "datacenter" {
|
||||
type = string
|
||||
default = "Datacenter"
|
||||
}
|
||||
|
||||
variable "vm_name" {
|
||||
type = string
|
||||
default = "web-01"
|
||||
}
|
||||
|
||||
variable "datastore" {
|
||||
type = string
|
||||
default = "datastore1"
|
||||
}
|
||||
|
||||
variable "cluster" {
|
||||
type = string
|
||||
default = "Cluster"
|
||||
}
|
||||
|
||||
variable "network" {
|
||||
type = string
|
||||
default = "VM Network"
|
||||
}
|
||||
|
||||
variable "create_lab_vm" {
|
||||
type = bool
|
||||
default = false
|
||||
}
|
||||
|
||||
variable "lab_vm_name" {
|
||||
type = string
|
||||
default = "tf-lab-01"
|
||||
}
|
||||
Reference in New Issue
Block a user