zbuilder 0.2.2__tar.gz → 0.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {zbuilder-0.2.2 → zbuilder-0.2.3}/PKG-INFO +5 -18
- {zbuilder-0.2.2 → zbuilder-0.2.3}/README.md +4 -12
- {zbuilder-0.2.2 → zbuilder-0.2.3}/pyproject.toml +1 -10
- {zbuilder-0.2.2 → zbuilder-0.2.3}/pyproject.toml.orig +1 -15
- {zbuilder-0.2.2 → zbuilder-0.2.3}/src/zbuilder/helpers.py +1 -1
- {zbuilder-0.2.2 → zbuilder-0.2.3}/src/zbuilder/vm/aws.py +4 -1
- zbuilder-0.2.2/src/zbuilder/dns/gcp.py +0 -90
- zbuilder-0.2.2/src/zbuilder/vm/gcp.py +0 -305
- {zbuilder-0.2.2 → zbuilder-0.2.3}/LICENSE +0 -0
- {zbuilder-0.2.2 → zbuilder-0.2.3}/src/zbuilder/__init__.py +0 -0
- {zbuilder-0.2.2 → zbuilder-0.2.3}/src/zbuilder/base.py +0 -0
- {zbuilder-0.2.2 → zbuilder-0.2.3}/src/zbuilder/cfg.py +0 -0
- {zbuilder-0.2.2 → zbuilder-0.2.3}/src/zbuilder/cli.py +0 -0
- {zbuilder-0.2.2 → zbuilder-0.2.3}/src/zbuilder/dns/__init__.py +0 -0
- {zbuilder-0.2.2 → zbuilder-0.2.3}/src/zbuilder/dns/aws.py +0 -0
- {zbuilder-0.2.2 → zbuilder-0.2.3}/src/zbuilder/dns/bind.py +0 -0
- {zbuilder-0.2.2 → zbuilder-0.2.3}/src/zbuilder/dns/powerdns.py +0 -0
- {zbuilder-0.2.2 → zbuilder-0.2.3}/src/zbuilder/ipam/__init__.py +0 -0
- {zbuilder-0.2.2 → zbuilder-0.2.3}/src/zbuilder/ipam/phpipam.py +0 -0
- {zbuilder-0.2.2 → zbuilder-0.2.3}/src/zbuilder/options.py +0 -0
- {zbuilder-0.2.2 → zbuilder-0.2.3}/src/zbuilder/plugins.py +0 -0
- {zbuilder-0.2.2 → zbuilder-0.2.3}/src/zbuilder/vm/__init__.py +0 -0
- {zbuilder-0.2.2 → zbuilder-0.2.3}/src/zbuilder/vm/kvm.py +0 -0
- {zbuilder-0.2.2 → zbuilder-0.2.3}/src/zbuilder/vm/proxmox.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: zbuilder
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Create VMs
|
|
5
5
|
Author: Nikos Chasiotis
|
|
6
6
|
Author-email: Nikos Chasiotis <hasiotis@gmail.com>
|
|
@@ -31,14 +31,9 @@ Requires-Dist: boto3>=1.43.73
|
|
|
31
31
|
Requires-Dist: tabulate>=0.10.0
|
|
32
32
|
Requires-Dist: libvirt-python>=11,<12
|
|
33
33
|
Requires-Dist: pycdlib>=1.14,<2
|
|
34
|
-
Requires-Dist: google-auth-oauthlib>=0.4,<1.5 ; extra == 'gcp'
|
|
35
|
-
Requires-Dist: oauthlib>=3.2.1,<4 ; extra == 'gcp'
|
|
36
|
-
Requires-Dist: google-api-python-client>=2.198.0,<3 ; extra == 'gcp'
|
|
37
|
-
Requires-Dist: google-cloud-dns>=0.37.0,<0.38 ; extra == 'gcp'
|
|
38
34
|
Requires-Dist: proxmoxer>=1.1,<3 ; extra == 'proxmox'
|
|
39
35
|
Requires-Python: >=3.12, <4
|
|
40
36
|
Project-URL: Repository, https://github.com/hasiotis/zbuilder
|
|
41
|
-
Provides-Extra: gcp
|
|
42
37
|
Provides-Extra: proxmox
|
|
43
38
|
Description-Content-Type: text/markdown
|
|
44
39
|
|
|
@@ -47,7 +42,6 @@ Description-Content-Type: text/markdown
|
|
|
47
42
|
[](https://pypi.org/project/zbuilder/)
|
|
48
43
|
[](https://pypistats.org/packages/zbuilder)
|
|
49
44
|
[](https://github.com/hasiotis/zbuilder/actions?query=workflow%3A%22master%22)
|
|
50
|
-
[](https://zbuilder.readthedocs.io/en/stable/?badge=stable)
|
|
51
45
|
[](https://github.com/hasiotis/zbuilder/blob/master/LICENSE)
|
|
52
46
|
|
|
53
47
|
ZBuilder is a tool to help you build VMs ready to be transferred to ansible.
|
|
@@ -73,11 +67,6 @@ or
|
|
|
73
67
|
pip3 install --user --upgrade zbuilder
|
|
74
68
|
```
|
|
75
69
|
|
|
76
|
-
The `gcp` provider needs the google client libraries, which ship as an extra:
|
|
77
|
-
```
|
|
78
|
-
pipx install --force 'zbuilder[gcp]'
|
|
79
|
-
```
|
|
80
|
-
|
|
81
70
|
The `proxmox` provider needs `proxmoxer`, which ships as an extra:
|
|
82
71
|
```
|
|
83
72
|
pipx install --force 'zbuilder[proxmox]'
|
|
@@ -87,12 +76,11 @@ pipx install --force 'zbuilder[proxmox]'
|
|
|
87
76
|
|
|
88
77
|
| Family | Providers |
|
|
89
78
|
|---|---|
|
|
90
|
-
| VM | `aws`, `
|
|
91
|
-
| DNS | `aws`, `bind`, `
|
|
79
|
+
| VM | `aws`, `kvm`, `proxmox` |
|
|
80
|
+
| DNS | `aws`, `bind`, `powerdns` |
|
|
92
81
|
| IPAM | `phpipam` |
|
|
93
82
|
|
|
94
|
-
|
|
95
|
-
settings each one accepts, or run `zbuilder providers`.
|
|
83
|
+
Run `zbuilder providers` to see the settings each one accepts.
|
|
96
84
|
|
|
97
85
|
## Development
|
|
98
86
|
During development you can:
|
|
@@ -106,6 +94,5 @@ prek run --all-files # lint and format
|
|
|
106
94
|
|
|
107
95
|
## Links
|
|
108
96
|
|
|
109
|
-
[
|
|
110
|
-
| [Releases](https://pypi.org/project/zbuilder/)
|
|
97
|
+
[Releases](https://pypi.org/project/zbuilder/)
|
|
111
98
|
| [Code](https://github.com/hasiotis/zbuilder)
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
[](https://pypi.org/project/zbuilder/)
|
|
4
4
|
[](https://pypistats.org/packages/zbuilder)
|
|
5
5
|
[](https://github.com/hasiotis/zbuilder/actions?query=workflow%3A%22master%22)
|
|
6
|
-
[](https://zbuilder.readthedocs.io/en/stable/?badge=stable)
|
|
7
6
|
[](https://github.com/hasiotis/zbuilder/blob/master/LICENSE)
|
|
8
7
|
|
|
9
8
|
ZBuilder is a tool to help you build VMs ready to be transferred to ansible.
|
|
@@ -29,11 +28,6 @@ or
|
|
|
29
28
|
pip3 install --user --upgrade zbuilder
|
|
30
29
|
```
|
|
31
30
|
|
|
32
|
-
The `gcp` provider needs the google client libraries, which ship as an extra:
|
|
33
|
-
```
|
|
34
|
-
pipx install --force 'zbuilder[gcp]'
|
|
35
|
-
```
|
|
36
|
-
|
|
37
31
|
The `proxmox` provider needs `proxmoxer`, which ships as an extra:
|
|
38
32
|
```
|
|
39
33
|
pipx install --force 'zbuilder[proxmox]'
|
|
@@ -43,12 +37,11 @@ pipx install --force 'zbuilder[proxmox]'
|
|
|
43
37
|
|
|
44
38
|
| Family | Providers |
|
|
45
39
|
|---|---|
|
|
46
|
-
| VM | `aws`, `
|
|
47
|
-
| DNS | `aws`, `bind`, `
|
|
40
|
+
| VM | `aws`, `kvm`, `proxmox` |
|
|
41
|
+
| DNS | `aws`, `bind`, `powerdns` |
|
|
48
42
|
| IPAM | `phpipam` |
|
|
49
43
|
|
|
50
|
-
|
|
51
|
-
settings each one accepts, or run `zbuilder providers`.
|
|
44
|
+
Run `zbuilder providers` to see the settings each one accepts.
|
|
52
45
|
|
|
53
46
|
## Development
|
|
54
47
|
During development you can:
|
|
@@ -62,6 +55,5 @@ prek run --all-files # lint and format
|
|
|
62
55
|
|
|
63
56
|
## Links
|
|
64
57
|
|
|
65
|
-
[
|
|
66
|
-
| [Releases](https://pypi.org/project/zbuilder/)
|
|
58
|
+
[Releases](https://pypi.org/project/zbuilder/)
|
|
67
59
|
| [Code](https://github.com/hasiotis/zbuilder)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "zbuilder"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.3"
|
|
4
4
|
description = "Create VMs"
|
|
5
5
|
license = "GPL-3.0-or-later"
|
|
6
6
|
license-files = ["LICENSE"]
|
|
@@ -41,12 +41,6 @@ name = "Nikos Chasiotis"
|
|
|
41
41
|
email = "hasiotis@gmail.com"
|
|
42
42
|
|
|
43
43
|
[project.optional-dependencies]
|
|
44
|
-
gcp = [
|
|
45
|
-
"google-auth-oauthlib>=0.4,<1.5",
|
|
46
|
-
"oauthlib>=3.2.1,<4",
|
|
47
|
-
"google-api-python-client>=2.198.0,<3",
|
|
48
|
-
"google-cloud-dns>=0.37.0,<0.38",
|
|
49
|
-
]
|
|
50
44
|
proxmox = ["proxmoxer>=1.1,<3"]
|
|
51
45
|
|
|
52
46
|
[project.urls]
|
|
@@ -57,14 +51,12 @@ zbuilder = "zbuilder.cli:cli"
|
|
|
57
51
|
|
|
58
52
|
[project.entry-points."zbuilder.vm"]
|
|
59
53
|
aws = "zbuilder.vm.aws:vmProvider"
|
|
60
|
-
gcp = "zbuilder.vm.gcp:vmProvider"
|
|
61
54
|
kvm = "zbuilder.vm.kvm:vmProvider"
|
|
62
55
|
proxmox = "zbuilder.vm.proxmox:vmProvider"
|
|
63
56
|
|
|
64
57
|
[project.entry-points."zbuilder.dns"]
|
|
65
58
|
aws = "zbuilder.dns.aws:dnsProvider"
|
|
66
59
|
bind = "zbuilder.dns.bind:dnsProvider"
|
|
67
|
-
gcp = "zbuilder.dns.gcp:dnsProvider"
|
|
68
60
|
powerdns = "zbuilder.dns.powerdns:dnsProvider"
|
|
69
61
|
|
|
70
62
|
[project.entry-points."zbuilder.ipam"]
|
|
@@ -75,7 +67,6 @@ dev = [
|
|
|
75
67
|
"pytest>=9.1.1,<10",
|
|
76
68
|
"pytest-md-report>=0.8.0",
|
|
77
69
|
]
|
|
78
|
-
docs = ["sphinx>=7.2.6,<8"]
|
|
79
70
|
|
|
80
71
|
[tool.uv]
|
|
81
72
|
default-groups = ["dev"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "zbuilder"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.3"
|
|
4
4
|
description = "Create VMs"
|
|
5
5
|
authors = [{ name = "Nikos Chasiotis", email = "hasiotis@gmail.com" }]
|
|
6
6
|
license = "GPL-3.0-or-later"
|
|
@@ -40,15 +40,6 @@ dependencies = [
|
|
|
40
40
|
]
|
|
41
41
|
|
|
42
42
|
[project.optional-dependencies]
|
|
43
|
-
# The gcp providers (vm and dns) pull in the whole google client stack, which
|
|
44
|
-
# is a large dependency tree nobody outside GCP needs. zbuilder/vm/gcp.py and
|
|
45
|
-
# zbuilder/dns/gcp.py import it lazily and say what to install when missing.
|
|
46
|
-
gcp = [
|
|
47
|
-
"google-auth-oauthlib>=0.4,<1.5",
|
|
48
|
-
"oauthlib>=3.2.1,<4",
|
|
49
|
-
"google-api-python-client>=2.198.0,<3",
|
|
50
|
-
"google-cloud-dns>=0.37.0,<0.38",
|
|
51
|
-
]
|
|
52
43
|
# The proxmox provider talks to PVE through proxmoxer, which nobody outside a
|
|
53
44
|
# proxmox cluster needs. zbuilder/vm/proxmox.py imports it lazily and says what
|
|
54
45
|
# to install when it is missing.
|
|
@@ -66,14 +57,12 @@ zbuilder = "zbuilder.cli:cli"
|
|
|
66
57
|
# distribution can register providers under the same groups.
|
|
67
58
|
[project.entry-points."zbuilder.vm"]
|
|
68
59
|
aws = "zbuilder.vm.aws:vmProvider"
|
|
69
|
-
gcp = "zbuilder.vm.gcp:vmProvider"
|
|
70
60
|
kvm = "zbuilder.vm.kvm:vmProvider"
|
|
71
61
|
proxmox = "zbuilder.vm.proxmox:vmProvider"
|
|
72
62
|
|
|
73
63
|
[project.entry-points."zbuilder.dns"]
|
|
74
64
|
aws = "zbuilder.dns.aws:dnsProvider"
|
|
75
65
|
bind = "zbuilder.dns.bind:dnsProvider"
|
|
76
|
-
gcp = "zbuilder.dns.gcp:dnsProvider"
|
|
77
66
|
powerdns = "zbuilder.dns.powerdns:dnsProvider"
|
|
78
67
|
|
|
79
68
|
[project.entry-points."zbuilder.ipam"]
|
|
@@ -84,9 +73,6 @@ dev = [
|
|
|
84
73
|
"pytest>=9.1.1,<10",
|
|
85
74
|
"pytest-md-report>=0.8.0",
|
|
86
75
|
]
|
|
87
|
-
docs = [
|
|
88
|
-
"sphinx>=7.2.6,<8",
|
|
89
|
-
]
|
|
90
76
|
|
|
91
77
|
[tool.uv]
|
|
92
78
|
default-groups = ["dev"]
|
|
@@ -183,7 +183,7 @@ def getProviders(cfg, state):
|
|
|
183
183
|
try:
|
|
184
184
|
cp = cfg["providers"][p]
|
|
185
185
|
cp["state"] = state
|
|
186
|
-
# A name can be registered in more than one family (
|
|
186
|
+
# A name can be registered in more than one family (aws is both a
|
|
187
187
|
# vm and a dns provider), vm wins as it is the richer one.
|
|
188
188
|
if cp["type"] in zbuilder.plugins.names(zbuilder.plugins.VM):
|
|
189
189
|
curProvider = zbuilder.vm.vmProvider(cp["type"], cp)
|
|
@@ -36,10 +36,13 @@ class vmProvider(VMProvider):
|
|
|
36
36
|
for h, v in self._getVMs(hosts).items():
|
|
37
37
|
if v["status"] is None:
|
|
38
38
|
click.echo(" - Creating host: {} ".format(h))
|
|
39
|
+
# Debian AMIs boot from /dev/xvda, ubuntu ones from /dev/sda1. A mapping for any other device
|
|
40
|
+
# name adds a second volume instead of resizing the root one.
|
|
41
|
+
rootDevice = self.ec2.Image(v["values"]["ami"]).root_device_name
|
|
39
42
|
self.ec2.create_instances(
|
|
40
43
|
BlockDeviceMappings=[
|
|
41
44
|
{
|
|
42
|
-
"DeviceName":
|
|
45
|
+
"DeviceName": rootDevice,
|
|
43
46
|
"Ebs": {
|
|
44
47
|
"DeleteOnTermination": True,
|
|
45
48
|
"VolumeSize": v["values"].get("disksize", 20),
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
"""GCP DNS provider.
|
|
2
|
-
|
|
3
|
-
Like the gcp vm provider, the google client stack is an optional extra
|
|
4
|
-
(`zbuilder[gcp]`) imported lazily, so this module imports without it.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import time
|
|
8
|
-
import click
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
from zbuilder.base import DNSProvider
|
|
12
|
-
from zbuilder.vm.gcp import auth, require
|
|
13
|
-
|
|
14
|
-
try:
|
|
15
|
-
from google.cloud import dns
|
|
16
|
-
except ImportError: # installed without the gcp extra
|
|
17
|
-
dns = None
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
DNS_TTL = 60 * 10 # 10 mins
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class dnsProvider(DNSProvider):
|
|
24
|
-
def __init__(self, cfg):
|
|
25
|
-
super().__init__(cfg)
|
|
26
|
-
if cfg:
|
|
27
|
-
require(dns, "google-cloud-dns")
|
|
28
|
-
creds = auth(self.cfg)
|
|
29
|
-
|
|
30
|
-
try:
|
|
31
|
-
if "project" in cfg["dns"]:
|
|
32
|
-
self.dns = dns.Client(project=self.cfg["dns"]["project"], credentials=creds)
|
|
33
|
-
else:
|
|
34
|
-
self.dns = dns.Client(credentials=creds)
|
|
35
|
-
except Exception as e:
|
|
36
|
-
click.echo("Login failed: [{}]".format(e))
|
|
37
|
-
raise click.Abort()
|
|
38
|
-
|
|
39
|
-
def _getZoneInfo(self, host, zone):
|
|
40
|
-
zones = self.dns.list_zones()
|
|
41
|
-
mzone = None
|
|
42
|
-
for z in zones:
|
|
43
|
-
if z.dns_name == zone + ".":
|
|
44
|
-
mzone = z
|
|
45
|
-
break
|
|
46
|
-
|
|
47
|
-
return mzone
|
|
48
|
-
|
|
49
|
-
def update(self, host, zone, ip):
|
|
50
|
-
mzone = self._getZoneInfo(host, zone)
|
|
51
|
-
try:
|
|
52
|
-
changes = mzone.changes()
|
|
53
|
-
changes.delete_record_set(mzone.resource_record_set("{}.{}.".format(host, zone), "A", DNS_TTL, [ip]))
|
|
54
|
-
changes.create()
|
|
55
|
-
while changes.status != "done":
|
|
56
|
-
time.sleep(5)
|
|
57
|
-
changes.reload()
|
|
58
|
-
click.echo(" - Updating record [{}.{}] with ip [{}]".format(host, zone, ip))
|
|
59
|
-
except Exception:
|
|
60
|
-
click.echo(" - Creating record [{}.{}] with ip [{}]".format(host, zone, ip))
|
|
61
|
-
|
|
62
|
-
changes = mzone.changes()
|
|
63
|
-
changes.add_record_set(mzone.resource_record_set("{}.{}.".format(host, zone), "A", DNS_TTL, [ip]))
|
|
64
|
-
changes.create()
|
|
65
|
-
while changes.status != "done":
|
|
66
|
-
time.sleep(5)
|
|
67
|
-
changes.reload()
|
|
68
|
-
time.sleep(5)
|
|
69
|
-
|
|
70
|
-
def remove(self, host, zone):
|
|
71
|
-
mzone = self._getZoneInfo(host, zone)
|
|
72
|
-
records = mzone.list_resource_record_sets()
|
|
73
|
-
found = False
|
|
74
|
-
for r in records:
|
|
75
|
-
if r.name == "{}.{}.".format(host, zone) and r.record_type in [
|
|
76
|
-
"A",
|
|
77
|
-
"CNAME",
|
|
78
|
-
]:
|
|
79
|
-
found = True
|
|
80
|
-
click.echo(" - Removing record [{}.{}]".format(host, zone))
|
|
81
|
-
changes = mzone.changes()
|
|
82
|
-
changes.delete_record_set(mzone.resource_record_set(r.name, r.record_type, r.ttl, r.rrdatas))
|
|
83
|
-
changes.create()
|
|
84
|
-
while changes.status != "done":
|
|
85
|
-
time.sleep(5)
|
|
86
|
-
changes.reload()
|
|
87
|
-
break
|
|
88
|
-
|
|
89
|
-
if not found:
|
|
90
|
-
click.echo(" - No such record [{}.{}]".format(host, zone))
|
|
@@ -1,305 +0,0 @@
|
|
|
1
|
-
"""GCP provider, driving the google api client.
|
|
2
|
-
|
|
3
|
-
The google client stack is an optional extra (`zbuilder[gcp]`) and is imported
|
|
4
|
-
lazily: this module has to import on a machine without it, or provider
|
|
5
|
-
discovery would break for everybody.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
import os
|
|
9
|
-
import click
|
|
10
|
-
import pickle
|
|
11
|
-
|
|
12
|
-
from zbuilder.base import VMProvider
|
|
13
|
-
from zbuilder.dns import dnsUpdate, dnsRemove
|
|
14
|
-
|
|
15
|
-
try:
|
|
16
|
-
import googleapiclient.discovery
|
|
17
|
-
from google.oauth2 import service_account
|
|
18
|
-
from google_auth_oauthlib.flow import InstalledAppFlow
|
|
19
|
-
from google.auth.transport.requests import Request
|
|
20
|
-
except ImportError: # installed without the gcp extra
|
|
21
|
-
googleapiclient = None
|
|
22
|
-
|
|
23
|
-
CONFIG_PATH = "~/.config/zbuilder/"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
def require(module, name):
|
|
27
|
-
"""The gcp extra is optional, say so instead of raising ImportError"""
|
|
28
|
-
if module is None:
|
|
29
|
-
raise click.ClickException(
|
|
30
|
-
"The gcp provider needs [{}], install it with: pip install 'zbuilder[gcp]'".format(name)
|
|
31
|
-
)
|
|
32
|
-
return module
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def auth(cfg):
|
|
36
|
-
require(googleapiclient, "google-api-python-client")
|
|
37
|
-
|
|
38
|
-
SCOPES = [
|
|
39
|
-
"https://www.googleapis.com/auth/compute",
|
|
40
|
-
"https://www.googleapis.com/auth/ndev.clouddns.readwrite",
|
|
41
|
-
]
|
|
42
|
-
|
|
43
|
-
creds = None
|
|
44
|
-
# First we check for client-secret and creds-file
|
|
45
|
-
if "client-secret" in cfg and "creds-file" in cfg:
|
|
46
|
-
secretFilename = os.path.expanduser("{}/{}".format(CONFIG_PATH, cfg["client-secret"]))
|
|
47
|
-
tokenFilename = os.path.expanduser("{}/{}".format(CONFIG_PATH, cfg.get("creds-file", "")))
|
|
48
|
-
if os.path.exists(secretFilename) and os.path.exists(tokenFilename):
|
|
49
|
-
with open(tokenFilename, "rb") as token:
|
|
50
|
-
creds = pickle.load(token)
|
|
51
|
-
if not creds or not creds.valid:
|
|
52
|
-
if creds and creds.expired and creds.refresh_token:
|
|
53
|
-
creds.refresh(Request())
|
|
54
|
-
else:
|
|
55
|
-
flow = InstalledAppFlow.from_client_secrets_file(secretFilename, SCOPES)
|
|
56
|
-
creds = flow.run_local_server(port=0)
|
|
57
|
-
with open(tokenFilename, "wb") as token:
|
|
58
|
-
pickle.dump(creds, token)
|
|
59
|
-
|
|
60
|
-
# In case of missing client-secret or creds-file fallback to service-key
|
|
61
|
-
if creds is None and "service-key" in cfg:
|
|
62
|
-
secretFilename = os.path.expanduser("{}/{}".format(CONFIG_PATH, cfg["service-key"]))
|
|
63
|
-
if os.path.exists(secretFilename) and os.path.exists(secretFilename):
|
|
64
|
-
creds = service_account.Credentials.from_service_account_file(secretFilename, scopes=SCOPES)
|
|
65
|
-
|
|
66
|
-
# Fail if no auth keys are available
|
|
67
|
-
if creds is None:
|
|
68
|
-
click.echo("Unable to authenticate due to missing secrets")
|
|
69
|
-
raise click.Abort()
|
|
70
|
-
|
|
71
|
-
return creds
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
class vmProvider(VMProvider):
|
|
75
|
-
def __init__(self, cfg):
|
|
76
|
-
super().__init__(cfg)
|
|
77
|
-
if cfg:
|
|
78
|
-
creds = auth(self.cfg)
|
|
79
|
-
|
|
80
|
-
try:
|
|
81
|
-
self.compute = googleapiclient.discovery.build("compute", "v1", credentials=creds)
|
|
82
|
-
except Exception as e:
|
|
83
|
-
click.echo("Login failed: [{}]".format(e))
|
|
84
|
-
raise click.Abort()
|
|
85
|
-
|
|
86
|
-
def _waitDone(self, ops, msg=None):
|
|
87
|
-
for h, op in ops.items():
|
|
88
|
-
while True:
|
|
89
|
-
request = self.compute.zoneOperations().get(
|
|
90
|
-
project=op["project"], zone=op["zone"], operation=op["name"]
|
|
91
|
-
)
|
|
92
|
-
response = request.execute()
|
|
93
|
-
if response["status"] == "DONE":
|
|
94
|
-
if msg:
|
|
95
|
-
click.echo(msg.format(h))
|
|
96
|
-
break
|
|
97
|
-
|
|
98
|
-
def _getVMs(self, hosts):
|
|
99
|
-
retValue = {}
|
|
100
|
-
for h, v in hosts.items():
|
|
101
|
-
if hosts[h]["enabled"]:
|
|
102
|
-
shortname = h.partition(".")[0]
|
|
103
|
-
try:
|
|
104
|
-
result = (
|
|
105
|
-
self.compute.instances()
|
|
106
|
-
.list(
|
|
107
|
-
project=v["project"],
|
|
108
|
-
zone=v["zone"],
|
|
109
|
-
filter='name="{}"'.format(shortname),
|
|
110
|
-
)
|
|
111
|
-
.execute()
|
|
112
|
-
)
|
|
113
|
-
except Exception as e:
|
|
114
|
-
click.echo("Error [{}]".format(e))
|
|
115
|
-
exit()
|
|
116
|
-
items = result.get("items", [])
|
|
117
|
-
if items:
|
|
118
|
-
retValue[h] = items[0]
|
|
119
|
-
retValue[h]["values"] = v
|
|
120
|
-
else:
|
|
121
|
-
retValue[h] = {"status": None}
|
|
122
|
-
retValue[h]["values"] = v
|
|
123
|
-
|
|
124
|
-
image_response = (
|
|
125
|
-
self.compute.images()
|
|
126
|
-
.getFromFamily(project=v["image"]["project"], family=v["image"]["family"])
|
|
127
|
-
.execute()
|
|
128
|
-
)
|
|
129
|
-
source_disk_image = image_response["selfLink"]
|
|
130
|
-
|
|
131
|
-
fname = os.path.expanduser(v["ZBUILDER_PUBKEY"])
|
|
132
|
-
sshkey = ""
|
|
133
|
-
if os.path.isfile(fname):
|
|
134
|
-
with open(fname, "r") as f:
|
|
135
|
-
sshkey = f.read().rstrip("\n")
|
|
136
|
-
|
|
137
|
-
network = "global/networks/{v[network]}".format(v=v)
|
|
138
|
-
if "/" in v["network"]:
|
|
139
|
-
network = v["network"]
|
|
140
|
-
|
|
141
|
-
subnetwork = "regions/{v[region]}/subnetworks/{v[subnet]}".format(v=v)
|
|
142
|
-
if "/" in v["subnet"]:
|
|
143
|
-
subnetwork = v["subnet"]
|
|
144
|
-
|
|
145
|
-
accessConfigs = []
|
|
146
|
-
if v.get("net_external", False):
|
|
147
|
-
accessConfigs = [{"type": "ONE_TO_ONE_NAT", "name": "External NAT"}]
|
|
148
|
-
|
|
149
|
-
retValue[h]["insert"] = self.compute.instances().insert(
|
|
150
|
-
project=v["project"],
|
|
151
|
-
zone=v["zone"],
|
|
152
|
-
body={
|
|
153
|
-
"name": shortname,
|
|
154
|
-
"machineType": "zones/{v[zone]}/machineTypes/{v[size]}".format(v=v),
|
|
155
|
-
"disks": [
|
|
156
|
-
{
|
|
157
|
-
"boot": True,
|
|
158
|
-
"autoDelete": True,
|
|
159
|
-
"initializeParams": {
|
|
160
|
-
"sourceImage": source_disk_image,
|
|
161
|
-
},
|
|
162
|
-
}
|
|
163
|
-
],
|
|
164
|
-
"networkInterfaces": [
|
|
165
|
-
{
|
|
166
|
-
"network": network,
|
|
167
|
-
"subnetwork": subnetwork,
|
|
168
|
-
"accessConfigs": accessConfigs,
|
|
169
|
-
}
|
|
170
|
-
],
|
|
171
|
-
"metadata": {"items": [{"key": "ssh-keys", "value": "gcpadmin:" + sshkey}]},
|
|
172
|
-
},
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
return retValue
|
|
176
|
-
|
|
177
|
-
def build(self, hosts):
|
|
178
|
-
ops = {}
|
|
179
|
-
ips = {}
|
|
180
|
-
for h, v in self._getVMs(hosts).items():
|
|
181
|
-
if v["status"] is None:
|
|
182
|
-
click.echo(" - Creating host: {} ".format(h))
|
|
183
|
-
r = v["insert"].execute()
|
|
184
|
-
ops[h] = {
|
|
185
|
-
"name": r["name"],
|
|
186
|
-
"project": hosts[h]["project"],
|
|
187
|
-
"zone": hosts[h]["zone"],
|
|
188
|
-
}
|
|
189
|
-
ips[h] = None
|
|
190
|
-
elif v["status"] == "TERMINATED":
|
|
191
|
-
click.echo(" - Booting host: {} ".format(h))
|
|
192
|
-
self.compute.instances().start(
|
|
193
|
-
project=hosts[h]["project"],
|
|
194
|
-
zone=hosts[h]["zone"],
|
|
195
|
-
instance=v["name"],
|
|
196
|
-
).execute()
|
|
197
|
-
elif v["status"] == "RUNNING":
|
|
198
|
-
click.echo(" - Already up host: {} ".format(h))
|
|
199
|
-
else:
|
|
200
|
-
click.echo(" - Status of host: {} is {}".format(h, v["status"]))
|
|
201
|
-
|
|
202
|
-
self._waitDone(ops, " - Host {} created")
|
|
203
|
-
for h, v in self._getVMs(hosts).items():
|
|
204
|
-
if v["values"].get("dns_external", False):
|
|
205
|
-
ips[h] = v["networkInterfaces"][0]["accessConfigs"][0]["natIP"]
|
|
206
|
-
else:
|
|
207
|
-
ips[h] = v["networkInterfaces"][0]["networkIP"]
|
|
208
|
-
|
|
209
|
-
dnsUpdate(ips)
|
|
210
|
-
|
|
211
|
-
def up(self, hosts):
|
|
212
|
-
for h, v in self._getVMs(hosts).items():
|
|
213
|
-
if v["status"] is None:
|
|
214
|
-
click.echo(" - No such host: {} ".format(h))
|
|
215
|
-
elif v["status"] == "TERMINATED":
|
|
216
|
-
click.echo(" - Booting host: {} ".format(h))
|
|
217
|
-
self.compute.instances().start(
|
|
218
|
-
project=hosts[h]["project"],
|
|
219
|
-
zone=hosts[h]["zone"],
|
|
220
|
-
instance=v["name"],
|
|
221
|
-
).execute()
|
|
222
|
-
elif v["status"] == "RUNNING":
|
|
223
|
-
click.echo(" - Already up host: {} ".format(h))
|
|
224
|
-
else:
|
|
225
|
-
click.echo(" - Status of host: {} is {}".format(h, v["status"]))
|
|
226
|
-
|
|
227
|
-
def halt(self, hosts):
|
|
228
|
-
for h, v in self._getVMs(hosts).items():
|
|
229
|
-
if v["status"] is None:
|
|
230
|
-
click.echo(" - No such host: {} ".format(h))
|
|
231
|
-
if v["status"] == "RUNNING":
|
|
232
|
-
click.echo(" - Halting host: {} ".format(h))
|
|
233
|
-
self.compute.instances().stop(
|
|
234
|
-
project=hosts[h]["project"],
|
|
235
|
-
zone=hosts[h]["zone"],
|
|
236
|
-
instance=v["name"],
|
|
237
|
-
).execute()
|
|
238
|
-
else:
|
|
239
|
-
click.echo(" - Status of host: {} is {}".format(h, v["status"]))
|
|
240
|
-
|
|
241
|
-
def destroy(self, hosts):
|
|
242
|
-
updateHosts = {}
|
|
243
|
-
ops = {}
|
|
244
|
-
for h, v in self._getVMs(hosts).items():
|
|
245
|
-
if v["status"] is not None:
|
|
246
|
-
click.echo(" - Destroying host: {} ".format(h))
|
|
247
|
-
r = (
|
|
248
|
-
self.compute.instances()
|
|
249
|
-
.delete(
|
|
250
|
-
project=hosts[h]["project"],
|
|
251
|
-
zone=hosts[h]["zone"],
|
|
252
|
-
instance=v["name"],
|
|
253
|
-
)
|
|
254
|
-
.execute()
|
|
255
|
-
)
|
|
256
|
-
ops[h] = {
|
|
257
|
-
"name": r["name"],
|
|
258
|
-
"project": hosts[h]["project"],
|
|
259
|
-
"zone": hosts[h]["zone"],
|
|
260
|
-
}
|
|
261
|
-
updateHosts[h] = {}
|
|
262
|
-
else:
|
|
263
|
-
click.echo(" - Host does not exists : {}".format(h))
|
|
264
|
-
|
|
265
|
-
self._waitDone(ops, " - Host {} destroyed")
|
|
266
|
-
dnsRemove(updateHosts)
|
|
267
|
-
|
|
268
|
-
def dnsupdate(self, hosts):
|
|
269
|
-
ips = {}
|
|
270
|
-
for h, v in self._getVMs(hosts).items():
|
|
271
|
-
if v["values"].get("dns_external", False):
|
|
272
|
-
ips[h] = v["networkInterfaces"][0]["accessConfigs"][0]["natIP"]
|
|
273
|
-
else:
|
|
274
|
-
ips[h] = v["networkInterfaces"][0]["networkIP"]
|
|
275
|
-
|
|
276
|
-
dnsUpdate(ips)
|
|
277
|
-
|
|
278
|
-
def dnsremove(self, hosts):
|
|
279
|
-
ips = {}
|
|
280
|
-
for h in hosts:
|
|
281
|
-
if hosts[h]["enabled"]:
|
|
282
|
-
ips[h] = None
|
|
283
|
-
|
|
284
|
-
dnsRemove(hosts)
|
|
285
|
-
|
|
286
|
-
def snapCreate(self, hosts):
|
|
287
|
-
pass
|
|
288
|
-
|
|
289
|
-
def snapRestore(self, hosts):
|
|
290
|
-
pass
|
|
291
|
-
|
|
292
|
-
def snapDelete(self, hosts):
|
|
293
|
-
pass
|
|
294
|
-
|
|
295
|
-
def config(self):
|
|
296
|
-
if "service-key" in self.cfg:
|
|
297
|
-
return "service-key: {}{}".format(CONFIG_PATH, self.cfg["service-key"])
|
|
298
|
-
else:
|
|
299
|
-
return "client-secret: {}{}".format(CONFIG_PATH, self.cfg["client-secret"])
|
|
300
|
-
|
|
301
|
-
def status(self):
|
|
302
|
-
return "PASS"
|
|
303
|
-
|
|
304
|
-
def params(self, params):
|
|
305
|
-
return {k: params[k] for k in ["size", "image"]}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|