Skip to content

Commit 2cc48df

Browse files
committed
Added support for LKE
1 parent 4011af2 commit 2cc48df

File tree

3 files changed

+270
-2
lines changed

3 files changed

+270
-2
lines changed

linode_api4/linode_client.py

Lines changed: 105 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,8 @@ def instance_create(self, ltype, region, image=None,
129129
130130
ltype = client.linode.types().first()
131131
region = client.regions().first()
132-
image = client.images().first()
132+
image = client.images().first(:param kwargs: Any other arguments to pass along to the API. See the API
133+
docs for possible values.
133134
134135
another_linode, password = client.linode.instance_create(
135136
ltype,
@@ -428,6 +429,105 @@ def ssh_key_upload(self, key, label):
428429
return ssh_key
429430

430431

432+
class LKEGroup(Group):
433+
"""
434+
Encapsulates LKE-related methods of the :any:`LinodeClient`. This
435+
should not be instantiated on its own, but should instead be used through
436+
an instance of :any:`LinodeClient`::
437+
438+
client = LinodeClient(token)
439+
instances = client.lke.clusters() # use the LKEGroup
440+
441+
This group contains all features beneath the `/lke` group in the API v4.
442+
"""
443+
def versions(self, *filters):
444+
"""
445+
Returns a :any:`PaginatedList` of :any:`KubeVersion` objects that can be
446+
used when creating an LKE Cluster.
447+
448+
:param filters: Any number of filters to apply to the query.
449+
450+
:returns: A Paginated List of kube versions that match the query.
451+
:rtype: PaginatedList of KubeVersion
452+
"""
453+
return self.client._get_and_filter(KubeVersion, *filters)
454+
455+
def clusters(self, *filters):
456+
"""
457+
Returns a :any:`PaginagtedList` of :any:`LKECluster` objects that belong
458+
to this account.
459+
460+
:param filters: Any number of filters to apply to the query.
461+
462+
:returns: A Paginated List of LKE clusters that match the query.
463+
:rtype: PaginatedList of LKECluster
464+
"""
465+
return self.client._get_and_filter(LKECluster, *filters)
466+
467+
def cluster_create(self, region, label, node_pools, **kwargs):
468+
"""
469+
Creates an :any:`LKECluster` on this account.
470+
471+
:param region: The Region to create this LKE Cluster in.
472+
:type region: Region of str
473+
:param label: The label for the new LKE Cluster.
474+
:type label: str
475+
:param node_pools: The Node Pools to create.
476+
:type node_pools: one or a list of dicts containing keys "type" and "count". See
477+
:any:`node_pool` for a convenient way to create correctly-
478+
formatted dicts.
479+
:param kwargs: Any other arguments to pass along to the API. See the API
480+
docs for possible values.
481+
482+
:returns: The new LKE Cluster
483+
:rtype: LKECluster
484+
"""
485+
pools = []
486+
if not isinstance(node_pools, list):
487+
node_pools = [node_pools]
488+
489+
for c in node_pools:
490+
if isinstance(c, dict):
491+
new_pool = {
492+
"type": c["type"].id if "type" in c and issubclass(c["type"], Base) else c.get("type"),
493+
"count": c.get("count"),
494+
}
495+
496+
pools += [new_pool]
497+
498+
params = {
499+
"label": label,
500+
"region": region.id if issubclass(region, Base) else region,
501+
"node_pools": pools,
502+
}
503+
params.update(kwargs)
504+
505+
result = self.client.post('/lke/clusters', data=params)
506+
507+
if not 'id' in result:
508+
raise UnexpectedResponseError('Unexpected response when creating lke cluster!', json=result)
509+
510+
return LKECluster(self.client, result['id'], result)
511+
512+
def node_pool(self, node_type, node_count):
513+
"""
514+
Returns a dict that is suitable for passing into the `node_pools` array
515+
of :any:`cluster_create`. This is a convenience method, and need not be
516+
used to create Node Pools. For proper usage, see the docs for :any:`cluster_create`.
517+
518+
:param node_type: The type of node to create in this node pool.
519+
:type node_type: Type or str
520+
:param node_count: The number of nodes to create in this node pool.
521+
:type node_count: int
522+
523+
:returns: A dict describing the desired node pool.
524+
:rtype: dict
525+
"""
526+
return {
527+
"type": node_type,
528+
"count": node_count,
529+
}
530+
431531
class LongviewGroup(Group):
432532
def clients(self, *filters):
433533
"""
@@ -690,7 +790,7 @@ def ip_allocate(self, linode, public=True):
690790
ip = IPAddress(self.client, result['address'], result)
691791
return ip
692792

693-
def shared_ips(self, linode, *ips):
793+
def ips_share(self, linode, *ips):
694794
"""
695795
Shares the given list of :any:`IPAddresses<IPAddress>` with the provided
696796
:any:`Instance`. This will enable the provided Instance to bring up the
@@ -876,6 +976,9 @@ def __init__(self, token, base_url="https://api.linode.com/v4", user_agent=None)
876976
#: for more information
877977
self.object_storage = ObjectStorageGroup(self)
878978

979+
#: Access methods related to LKE - see :any:`LKEGroup` for more information.
980+
self.lke = LKEGroup(self)
981+
879982
@property
880983
def _user_agent(self):
881984
return '{}python-linode_api4/{} {}'.format(

linode_api4/objects/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@
1414
from .longview import *
1515
from .tag import Tag
1616
from .object_storage import ObjectStorageCluster, ObjectStorageKeys
17+
from .lke import *

linode_api4/objects/lke.py

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
from linode_api4.objects import (
2+
Base, DerivedBase, Property, Region, Type, Instance, MappedObject
3+
)
4+
5+
class KubeVersion(Base):
6+
"""
7+
A KubeVersion is a version of Kubernetes that can be deployed on LKE.
8+
"""
9+
api_endpoint = "/lke/versions"
10+
11+
properties = {
12+
"id": Property(identifier=True),
13+
}
14+
15+
16+
class LKENodePoolNode():
17+
"""
18+
AN LKE Node Pool Node is a helper class that is used to populate the "nodes"
19+
array of an LKE Node Pool, and set up an automatic relationship with the
20+
Linode Instance the Node represented.
21+
"""
22+
def __init__(self, client, json):
23+
"""
24+
Creates this NodePoolNode
25+
"""
26+
#: The ID of this Node Pool Node
27+
self.id = json.get("id") # why do these have an ID if they don't have an endpoint of their own?
28+
29+
#: The ID of the Linode Instance this Node represents
30+
self.instance_id = json.get("instance_id")
31+
32+
#: The Instance object backing this Node Pool Node
33+
self.instance = Instance(client, self.instance_id)
34+
35+
#: The Status of this Node Pool Node
36+
self.status = json.get("status")
37+
38+
class LKENodePool(DerivedBase):
39+
"""
40+
An LKE Node Pool describes a pool of Linode Instances that exist within an
41+
LKE Cluster.
42+
"""
43+
api_endpoint = "/lke/clustters/{cluster_id}/pools/{id}"
44+
derived_url_path = 'pools'
45+
parent_id = "linode_id"
46+
47+
properties = {
48+
"id": Property(identifier=True),
49+
"cluster_id": Property(identifier=True),
50+
"type": Property(slug_relationship=Type),
51+
"disks": Property(),
52+
"count": Property(mutable=True),
53+
"nodes": Property(volatile=True), # this is formatted in _populate below
54+
}
55+
56+
def _populate(self, json):
57+
"""
58+
Parse Nodes into more useful LKENodePoolNode objects
59+
"""
60+
if json is not None:
61+
new_nodes = [
62+
LKENodePoolNode(self._client, c) for c in json["nodes"]
63+
]
64+
json["nodes"] = new_nodes
65+
66+
super()._populate(json)
67+
68+
def recycle(self):
69+
"""
70+
Deleted and recreates all Linodes in this Node Pool in a rolling fashion.
71+
Completing this operation may take several minutes. This operation will
72+
cause all local data on Linode Instances in this pool to be lost.
73+
"""
74+
self._client.post("{}/recycle".format(LKENodePool.api_endpoint), model=self)
75+
self.invalidate()
76+
77+
78+
class LKECluster(Base):
79+
"""
80+
An LKE Cluster is a single k8s cluster deployed via Linode Kubernetes Engine.
81+
"""
82+
api_endpoint = "/lke/clusters/{id}"
83+
84+
properties = {
85+
"id": Property(identifier=True),
86+
"created": Property(is_datetime=True),
87+
"label": Property(mutable=True),
88+
"tags": Property(mutable=True),
89+
"updated": Property(is_datetime=True),
90+
"region": Property(slug_relationship=Region),
91+
"k8s_version": Property(slug_relationship=KubeVersion),
92+
"pools": Property(derived_class=LKENodePool),
93+
}
94+
95+
@property
96+
def api_endpoints(self):
97+
"""
98+
A list of API Endpoints for this Cluster.
99+
"""
100+
# This result appears to be a PaginatedList, but objects in the list don't
101+
# have IDs and can't be retrieved on their own, and it doesn't accept normal
102+
# pagination properties, so we're converting this to a list of strings.
103+
if not hasattr(self, "_api_endpoints"):
104+
results = self._client.get("{}/api-endpoints".format(LKECluster.api_endpoint), model=self)
105+
106+
self._api_endpoints = [MappedObject(**c) for c in results["data"]]
107+
108+
return self._api_endpoints
109+
110+
@property
111+
def kubeconfig(self):
112+
"""
113+
The administrative Kubernetes Config used to access this cluster, encoded
114+
in base64. Note that this config contains sensitive credentials to your
115+
cluster.
116+
117+
To convert this config into a readable form, use python's `base64` module:
118+
119+
import base64
120+
121+
config = my_cluster.kubeconfig
122+
yaml_config = base64.b64decode(config)
123+
124+
# write this config out to disk
125+
with open("/path/to/target/kubeconfig.yaml", "w") as f:
126+
f.write(yaml_config.decode())
127+
128+
It may take a few minutes for a config to be ready when creating a new
129+
cluster; during that time this request may fail.
130+
"""
131+
if not hasattr(self, "_kubeconfig"):
132+
result = self._client.get("{}/kubeconfig".format(LKECluster.api_endpoint), model=self)
133+
134+
self._kubeconfig = result["kubeconfig"]
135+
136+
return self._kubeconfig
137+
138+
def node_pool_create(self, node_type, node_count, **kwargs):
139+
"""
140+
Creates a new :any:`LKENodePool` for this cluster.
141+
142+
:param node_type: The type of nodes to create in this pool.
143+
:type node_type: :any:`Type` or str
144+
:param node_count: The number of nodes to create in this pool.
145+
:type node_count: int
146+
:param kwargs: Any other arguments to pass to the API. See the API docs
147+
for possible values.
148+
149+
:returns: The new Node Pool
150+
:rtype: LKENodePool
151+
"""
152+
params = {
153+
"type": node_type,
154+
"count": node_count,
155+
}
156+
params.update(kwargs)
157+
158+
result = self._client.post("{}/pools".format(LKECluster.api_endpoint), model=self, data=params)
159+
self.invalidate()
160+
161+
if not 'id' in result:
162+
raise UnexpectedResponseError('Unexpected response creating node pool!', json=result)
163+
164+
return LKENodePool(self._client, result["id"], self.id, result)

0 commit comments

Comments
 (0)