Skip to content

Commit 71544fc

Browse files
Implemented changes for SLADE and CLEO projects
1 parent d18b54e commit 71544fc

File tree

4 files changed

+281
-45
lines changed

4 files changed

+281
-45
lines changed

linode_api4/groups/linode.py

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,9 @@ def instance_create(
162162
interface_generation: Optional[Union[InterfaceGeneration, str]] = None,
163163
network_helper: Optional[bool] = None,
164164
maintenance_policy: Optional[str] = None,
165+
root_pass: Optional[str] = None,
166+
kernel: Optional[str] = None,
167+
boot_size: Optional[int] = None,
165168
**kwargs,
166169
):
167170
"""
@@ -172,26 +175,29 @@ def instance_create(
172175
To create an Instance from an :any:`Image`, call `instance_create` with
173176
a :any:`Type`, a :any:`Region`, and an :any:`Image`. All three of
174177
these fields may be provided as either the ID or the appropriate object.
175-
In this mode, a root password will be generated and returned with the
176-
new Instance object.
178+
When an Image is provided, at least one of ``root_pass`` or
179+
``authorized_keys`` must also be given. If ``root_pass`` is provided,
180+
the Instance and the password are returned as a tuple.
177181
178182
For example::
179183
180184
new_linode, password = client.linode.instance_create(
181185
"g6-standard-2",
182186
"us-east",
183-
image="linode/debian9")
187+
image="linode/debian9",
188+
root_pass="aComplex@Password123")
184189
185190
ltype = client.linode.types().first()
186191
region = client.regions().first()
187192
image = client.images().first()
188193
189-
another_linode, password = client.linode.instance_create(
194+
another_linode = client.linode.instance_create(
190195
ltype,
191196
region,
192-
image=image)
197+
image=image,
198+
authorized_keys="ssh-rsa AAAA")
193199
194-
To output the password from the above example:
200+
To output the password from the first example above:
195201
print(password)
196202
197203
To output the first IPv4 address of the new Linode:
@@ -214,6 +220,7 @@ def instance_create(
214220
"g6-standard-2",
215221
"us-east",
216222
image="linode/debian9",
223+
root_pass="aComplex@Password123",
217224
stackscript=stackscript,
218225
stackscript_data={"gh_username": "example"})
219226
@@ -248,6 +255,7 @@ def instance_create(
248255
"g6-standard-1",
249256
"us-mia",
250257
image="linode/ubuntu24.04",
258+
root_pass="aComplex@Password123",
251259
252260
# This can be configured as an account-wide default
253261
interface_generation=InterfaceGeneration.LINODE,
@@ -280,10 +288,16 @@ def instance_create(
280288
:type ltype: str or Type
281289
:param region: The Region in which we are creating the Instance
282290
:type region: str or Region
283-
:param image: The Image to deploy to this Instance. If this is provided
284-
and no root_pass is given, a password will be generated
285-
and returned along with the new Instance.
291+
:param image: The Image to deploy to this Instance. If this is provided,
292+
at least one of root_pass or authorized_keys must also be
293+
provided.
286294
:type image: str or Image
295+
:param root_pass: The root password for the new Instance. If an image is
296+
provided and root_pass is given, the Instance and password
297+
will be returned as a tuple. If neither root_pass nor
298+
authorized_keys is provided when an image is specified,
299+
a ValueError will be raised.
300+
:type root_pass: str
287301
:param stackscript: The StackScript to deploy to the new Instance. If
288302
provided, "image" is required and must be compatible
289303
with the chosen StackScript.
@@ -336,25 +350,33 @@ def instance_create(
336350
:param maintenance_policy: The slug of the maintenance policy to apply during maintenance.
337351
If not provided, the default policy (linode/migrate) will be applied.
338352
:type maintenance_policy: str
353+
:param kernel: The kernel to boot the Instance with. If provided, this will be used as the
354+
kernel for the default configuration profile.
355+
:type kernel: str
356+
:param boot_size: The size of the boot disk in MB. If provided, this will be used to create
357+
the boot disk for the Instance.
358+
:type boot_size: int
339359
340360
:returns: A new Instance object, or a tuple containing the new Instance and
341-
the generated password.
361+
the password if root_pass was provided.
342362
:rtype: Instance or tuple(Instance, str)
343363
:raises ApiError: If contacting the API fails
344364
:raises UnexpectedResponseError: If the API response is somehow malformed.
345365
This usually indicates that you are using
346366
an outdated library.
347367
"""
348368

349-
ret_pass = None
350-
if image and not "root_pass" in kwargs:
351-
ret_pass = Instance.generate_root_password()
352-
kwargs["root_pass"] = ret_pass
369+
if image and not root_pass and not authorized_keys:
370+
raise ValueError(
371+
"When creating an Instance from an Image, at least one of "
372+
"root_pass or authorized_keys must be provided."
373+
)
353374

354375
params = {
355376
"type": ltype,
356377
"region": region,
357378
"image": image,
379+
"root_pass": root_pass,
358380
"authorized_keys": load_and_validate_keys(authorized_keys),
359381
# These will automatically be flattened below
360382
"firewall_id": firewall,
@@ -373,6 +395,8 @@ def instance_create(
373395
"interfaces": interfaces,
374396
"interface_generation": interface_generation,
375397
"network_helper": network_helper,
398+
"kernel": kernel,
399+
"boot_size": boot_size,
376400
}
377401

378402
params.update(kwargs)
@@ -388,9 +412,9 @@ def instance_create(
388412
)
389413

390414
l = Instance(self.client, result["id"], result)
391-
if not ret_pass:
415+
if not root_pass:
392416
return l
393-
return l, ret_pass
417+
return l, root_pass
394418

395419
@staticmethod
396420
def build_instance_metadata(user_data=None, encode_user_data=True):
@@ -403,6 +427,7 @@ def build_instance_metadata(user_data=None, encode_user_data=True):
403427
"g6-standard-2",
404428
"us-east",
405429
image="linode/ubuntu22.04",
430+
root_pass=""aComplex@Password123",
406431
metadata=client.linode.build_instance_metadata(user_data="myuserdata")
407432
)
408433
:param user_data: User-defined data to provide to the Linode Instance through

linode_api4/objects/linode.py

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1395,11 +1395,12 @@ def disk_create(
13951395
for the image deployed the disk will be used. Required
13961396
if creating a disk without an image.
13971397
:param read_only: If True, creates a read-only disk
1398-
:param image: The Image to deploy to the disk.
1398+
:param image: The Image to deploy to the disk. If provided, at least one of
1399+
root_pass or authorized_keys must also be given.
13991400
:param root_pass: The password to configure for the root user when deploying an
14001401
image to this disk. Not used if image is not given. If an
1401-
image is given and root_pass is not, a password will be
1402-
generated and returned alongside the new disk.
1402+
image is given and root_pass is provided, it will be returned
1403+
alongside the new disk as a tuple.
14031404
:param authorized_keys: A list of SSH keys to install as trusted for the root user.
14041405
:param authorized_users: A list of usernames whose keys should be installed
14051406
as trusted for the root user. These user's keys
@@ -1412,12 +1413,17 @@ def disk_create(
14121413
disk. Requires deploying a compatible image.
14131414
:param **stackscript_args: Any arguments to pass to the StackScript, as defined
14141415
by its User Defined Fields.
1416+
1417+
:returns: A new Disk object, or a tuple containing the new Disk and the
1418+
password if root_pass was provided.
1419+
:rtype: Disk or tuple(Disk, str)
14151420
"""
14161421

1417-
gen_pass = None
1418-
if image and not root_pass:
1419-
gen_pass = Instance.generate_root_password()
1420-
root_pass = gen_pass
1422+
if image and not root_pass and not authorized_keys:
1423+
raise ValueError(
1424+
"When creating a Disk from an Image, at least one of "
1425+
"root_pass or authorized_keys must be provided."
1426+
)
14211427

14221428
authorized_keys = load_and_validate_keys(authorized_keys)
14231429

@@ -1466,8 +1472,8 @@ def disk_create(
14661472

14671473
d = Disk(self._client, result["id"], self.id, result)
14681474

1469-
if gen_pass:
1470-
return d, gen_pass
1475+
if root_pass:
1476+
return d, root_pass
14711477
return d
14721478

14731479
def enable_backups(self):
@@ -1591,8 +1597,8 @@ def rebuild(
15911597
15921598
:param image: The Image to deploy to this Instance
15931599
:type image: str or Image
1594-
:param root_pass: The root password for the newly rebuilt Instance. If
1595-
omitted, a password will be generated and returned.
1600+
:param root_pass: The root password for the newly rebuilt Instance. At least
1601+
one of root_pass or authorized_keys must be provided.
15961602
:type root_pass: str
15971603
:param authorized_keys: The ssh public keys to install in the linode's
15981604
/root/.ssh/authorized_keys file. Each entry may
@@ -1603,14 +1609,14 @@ def rebuild(
16031609
NOTE: Disk encryption may not currently be available to all users.
16041610
:type disk_encryption: InstanceDiskEncryptionType or str
16051611
1606-
:returns: The newly generated password, if one was not provided
1607-
(otherwise True)
1612+
:returns: The root_pass if provided, otherwise True.
16081613
:rtype: str or bool
16091614
"""
1610-
ret_pass = None
1611-
if not root_pass:
1612-
ret_pass = Instance.generate_root_password()
1613-
root_pass = ret_pass
1615+
if not root_pass and not authorized_keys:
1616+
raise ValueError(
1617+
"When rebuilding an Instance, at least one of "
1618+
"root_pass or authorized_keys must be provided."
1619+
)
16141620

16151621
authorized_keys = load_and_validate_keys(authorized_keys)
16161622

@@ -1639,10 +1645,9 @@ def rebuild(
16391645
# update ourself with the newly-returned information
16401646
self._populate(result)
16411647

1642-
if not ret_pass:
1643-
return True
1644-
else:
1645-
return ret_pass
1648+
if root_pass:
1649+
return root_pass
1650+
return True
16461651

16471652
def rescue(self, *disks):
16481653
"""

test/unit/linode_client_test.py

Lines changed: 131 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -686,13 +686,140 @@ def test_instance_create(self):
686686

687687
def test_instance_create_with_image(self):
688688
"""
689-
Tests that a Linode Instance can be created with an image, and a password generated
689+
Tests that a Linode Instance can be created with an image and root_pass
690690
"""
691691
with self.mock_post("linode/instances/123") as m:
692692
l, pw = self.client.linode.instance_create(
693+
"g6-standard-1",
694+
"us-east-1a",
695+
image="linode/debian9",
696+
root_pass="aComplex@Password123",
697+
)
698+
699+
self.assertIsNotNone(l)
700+
self.assertEqual(l.id, 123)
701+
702+
self.assertEqual(m.call_url, "/linode/instances")
703+
704+
self.assertEqual(
705+
m.call_data,
706+
{
707+
"region": "us-east-1a",
708+
"type": "g6-standard-1",
709+
"image": "linode/debian9",
710+
"root_pass": "aComplex@Password123",
711+
},
712+
)
713+
714+
def test_instance_create_with_image_authorized_keys(self):
715+
"""
716+
Tests that a Linode Instance can be created with an image and authorized_keys only
717+
"""
718+
with self.mock_post("linode/instances/123") as m:
719+
l = self.client.linode.instance_create(
720+
"g6-standard-1",
721+
"us-east-1a",
722+
image="linode/debian9",
723+
authorized_keys="ssh-rsa AAAA",
724+
)
725+
726+
self.assertIsNotNone(l)
727+
self.assertEqual(l.id, 123)
728+
729+
self.assertEqual(m.call_url, "/linode/instances")
730+
731+
self.assertEqual(
732+
m.call_data,
733+
{
734+
"region": "us-east-1a",
735+
"type": "g6-standard-1",
736+
"image": "linode/debian9",
737+
"authorized_keys": ["ssh-rsa AAAA"],
738+
},
739+
)
740+
741+
def test_instance_create_with_image_requires_auth(self):
742+
"""
743+
Tests that creating an Instance from an Image without root_pass or
744+
authorized_keys raises a ValueError
745+
"""
746+
with self.assertRaises(ValueError):
747+
self.client.linode.instance_create(
693748
"g6-standard-1", "us-east-1a", image="linode/debian9"
694749
)
695750

751+
def test_instance_create_with_kernel(self):
752+
"""
753+
Tests that a Linode Instance can be created with a kernel
754+
"""
755+
with self.mock_post("linode/instances/123") as m:
756+
l, pw = self.client.linode.instance_create(
757+
"g6-standard-1",
758+
"us-east-1a",
759+
image="linode/debian9",
760+
root_pass="aComplex@Password123",
761+
kernel="linode/latest-64bit",
762+
)
763+
764+
self.assertIsNotNone(l)
765+
self.assertEqual(l.id, 123)
766+
767+
self.assertEqual(m.call_url, "/linode/instances")
768+
769+
self.assertEqual(
770+
m.call_data,
771+
{
772+
"region": "us-east-1a",
773+
"type": "g6-standard-1",
774+
"image": "linode/debian9",
775+
"root_pass": "aComplex@Password123",
776+
"kernel": "linode/latest-64bit",
777+
},
778+
)
779+
780+
def test_instance_create_with_boot_size(self):
781+
"""
782+
Tests that a Linode Instance can be created with a boot_size
783+
"""
784+
with self.mock_post("linode/instances/123") as m:
785+
l, pw = self.client.linode.instance_create(
786+
"g6-standard-1",
787+
"us-east-1a",
788+
image="linode/debian9",
789+
root_pass="aComplex@Password123",
790+
boot_size=5000,
791+
)
792+
793+
self.assertIsNotNone(l)
794+
self.assertEqual(l.id, 123)
795+
796+
self.assertEqual(m.call_url, "/linode/instances")
797+
798+
self.assertEqual(
799+
m.call_data,
800+
{
801+
"region": "us-east-1a",
802+
"type": "g6-standard-1",
803+
"image": "linode/debian9",
804+
"root_pass": "aComplex@Password123",
805+
"boot_size": 5000,
806+
},
807+
)
808+
809+
def test_instance_create_with_kernel_and_boot_size(self):
810+
"""
811+
Tests that a Linode Instance can be created with both kernel and boot_size
812+
"""
813+
with self.mock_post("linode/instances/123") as m:
814+
l, pw = self.client.linode.instance_create(
815+
"g6-standard-1",
816+
"us-east-1a",
817+
image="linode/debian9",
818+
root_pass="aComplex@Password123",
819+
kernel="linode/latest-64bit",
820+
boot_size=5000,
821+
)
822+
696823
self.assertIsNotNone(l)
697824
self.assertEqual(l.id, 123)
698825

@@ -704,7 +831,9 @@ def test_instance_create_with_image(self):
704831
"region": "us-east-1a",
705832
"type": "g6-standard-1",
706833
"image": "linode/debian9",
707-
"root_pass": pw,
834+
"root_pass": "aComplex@Password123",
835+
"kernel": "linode/latest-64bit",
836+
"boot_size": 5000,
708837
},
709838
)
710839

0 commit comments

Comments
 (0)