def _create_server(self): self.module.fail_on_missing_params( required_params=["name", "server_type", "image"]) params = { "name": self.module.params.get("name"), "server_type": self.client.server_types.get_by_name( self.module.params.get("server_type")), "user_data": self.module.params.get("user_data"), "labels": self.module.params.get("labels"), } if self.client.images.get_by_name( self.module.params.get("image")) is not None: # When image name is not available look for id instead params["image"] = self.client.images.get_by_name( self.module.params.get("image")) else: params["image"] = self.client.images.get_by_id( self.module.params.get("image")) if self.module.params.get("ssh_keys") is not None: params["ssh_keys"] = [ SSHKey(name=ssh_key_name) for ssh_key_name in self.module.params.get("ssh_keys") ] if self.module.params.get("volumes") is not None: params["volumes"] = [ Volume(id=volume_id) for volume_id in self.module.params.get("volumes") ] if self.module.params.get( "location") is None and self.module.params.get( "datacenter") is None: # When not given, the API will choose the location. params["location"] = None params["datacenter"] = None elif self.module.params.get( "location" ) is not None and self.module.params.get("datacenter") is None: params["location"] = self.client.locations.get_by_name( self.module.params.get("location")) elif self.module.params.get( "location") is None and self.module.params.get( "datacenter") is not None: params["datacenter"] = self.client.datacenters.get_by_name( self.module.params.get("datacenter")) if not self.module.check_mode: resp = self.client.servers.create(**params) self.result["root_password"] = resp.root_password resp.action.wait_until_finished(max_retries=1000) [action.wait_until_finished() for action in resp.next_actions] self._mark_as_changed() self._get_server()
def test_create(self, hetzner_client): response = hetzner_client.servers.create( "my-server", server_type=ServerType(name="cx11"), image=Image(name="ubuntu-16.04"), ssh_keys=[SSHKey(name="my-ssh-key")], volumes=[Volume(id=1)], user_data= "#cloud-config\\nruncmd:\\n- [touch, /root/cloud-init-worked]\\n", location=Location(name="nbg1"), automount=False) server = response.server action = response.action next_actions = response.next_actions root_password = response.root_password assert server.id == 42 assert server.volumes == [] assert server.server_type.id == 1 assert server.datacenter.id == 1 assert server.image.id == 4711 assert action.id == 1 assert action.command == "create_server" assert len(next_actions) == 1 assert next_actions[0].id == 13 assert next_actions[0].command == "start_server" assert root_password == "YItygq1v3GYjjMomLaKc"
def test_created_is_datetime(self): sshKey = SSHKey(id=1, created="2016-01-30T23:50+00:00") assert sshKey.created == datetime.datetime(2016, 1, 30, 23, 50, tzinfo=tzoffset(None, 0))
class TestSSHKeysClient(object): def test_get_by_id(self, hetzner_client): ssh_key = hetzner_client.ssh_keys.get_by_id(1) assert ssh_key.id == 2323 assert ssh_key.name == "My ssh key" assert ssh_key.fingerprint == "b7:2f:30:a0:2f:6c:58:6c:21:04:58:61:ba:06:3b:2f" assert ssh_key.public_key == "ssh-rsa AAAjjk76kgf...Xt" def test_get_by_name(self, hetzner_client): ssh_key = hetzner_client.ssh_keys.get_by_name("My ssh key") assert ssh_key.id == 2323 assert ssh_key.name == "My ssh key" assert ssh_key.fingerprint == "b7:2f:30:a0:2f:6c:58:6c:21:04:58:61:ba:06:3b:2f" assert ssh_key.public_key == "ssh-rsa AAAjjk76kgf...Xt" def test_get_list(self, hetzner_client): ssh_keys = hetzner_client.ssh_keys.get_all() assert ssh_keys[0].id == 2323 assert ssh_keys[0].name == "My ssh key" assert ssh_keys[ 0].fingerprint == "b7:2f:30:a0:2f:6c:58:6c:21:04:58:61:ba:06:3b:2f" assert ssh_keys[0].public_key == "ssh-rsa AAAjjk76kgf...Xt" def test_create(self, hetzner_client): ssh_key = hetzner_client.ssh_keys.create( name="My ssh key", public_key="ssh-rsa AAAjjk76kgf...Xt") assert ssh_key.id == 2323 assert ssh_key.name == "My ssh key" @pytest.mark.parametrize( "ssh_key", [SSHKey(id=1), BoundSSHKey(mock.MagicMock(), dict(id=1))]) def test_update(self, hetzner_client, ssh_key): ssh_key = hetzner_client.ssh_keys.update(ssh_key, name="New name") assert ssh_key.id == 2323 assert ssh_key.name == "New name" @pytest.mark.parametrize( "ssh_key", [SSHKey(id=1), BoundSSHKey(mock.MagicMock(), dict(id=1))]) def test_delete(self, hetzner_client, ssh_key): delete_success = hetzner_client.ssh_keys.delete(ssh_key) assert delete_success is True
def deploy_instance(self, name) -> None: log.info(f"hcloud: Deploying instance with name {name}") launch_config = self.launch.copy() labels = launch_config.get("labels", dict()) labels.update({"scalr": self.filter}) params = { "name": name, "labels": labels, "server_type": ServerType(launch_config["server_type"]), "image": Image(launch_config["image"]), "ssh_keys": [SSHKey(ssh_key) for ssh_key in launch_config["ssh_keys"]], "location": Location(launch_config["location"]), "user_data": launch_config["user_data"], } self.hcloud.servers.create(**params)
def create_node(self): assert self.ssh_key_id assert self.ssh_custom_key response = self.client.servers.create( name=self.get_rnd_name('node'), server_type=ServerType('cx51'), image=Image(name='debian-10'), ssh_keys=[SSHKey(name=self.key_name)] ) server = response.server ip = server.public_net.ipv4.ip logging.info('CREATED %s' % ip) time.sleep(5) # warm up for _ in range(10): ssh_conn = SSH_Connection(host=ip, user=self.config.get('remote', 'user'), connect_kwargs=self.ssh_custom_key) try: ssh_conn.run('whoami', hide=True) except: time.sleep(5) else: break return ip
class TestSSHKeysClient(object): @pytest.fixture() def ssh_keys_client(self): return SSHKeysClient(client=mock.MagicMock()) def test_get_by_id(self, ssh_keys_client, ssh_key_response): ssh_keys_client._client.request.return_value = ssh_key_response ssh_key = ssh_keys_client.get_by_id(1) ssh_keys_client._client.request.assert_called_with(url="/ssh_keys/1", method="GET") assert ssh_key._client is ssh_keys_client assert ssh_key.id == 2323 assert ssh_key.name == "My ssh key" @pytest.mark.parametrize("params", [{ 'name': "My ssh key", "fingerprint": "b7:2f:30:a0:2f:6c:58:6c:21:04:58:61:ba:06:3b:2f", "label_selector": "k==v", 'page': 1, 'per_page': 10 }, { 'name': "" }, {}]) def test_get_list(self, ssh_keys_client, two_ssh_keys_response, params): ssh_keys_client._client.request.return_value = two_ssh_keys_response result = ssh_keys_client.get_list(**params) ssh_keys_client._client.request.assert_called_with(url="/ssh_keys", method="GET", params=params) ssh_keys = result.ssh_keys assert len(ssh_keys) == 2 ssh_keys1 = ssh_keys[0] ssh_keys2 = ssh_keys[1] assert ssh_keys1._client is ssh_keys_client assert ssh_keys1.id == 2323 assert ssh_keys1.name == "SSH-Key" assert ssh_keys2._client is ssh_keys_client assert ssh_keys2.id == 2324 assert ssh_keys2.name == "SSH-Key" @pytest.mark.parametrize("params", [{ 'name': "My ssh key", 'label_selector': "label1" }, {}]) def test_get_all(self, ssh_keys_client, two_ssh_keys_response, params): ssh_keys_client._client.request.return_value = two_ssh_keys_response ssh_keys = ssh_keys_client.get_all(**params) params.update({'page': 1, 'per_page': 50}) ssh_keys_client._client.request.assert_called_with(url="/ssh_keys", method="GET", params=params) assert len(ssh_keys) == 2 ssh_keys1 = ssh_keys[0] ssh_keys2 = ssh_keys[1] assert ssh_keys1._client is ssh_keys_client assert ssh_keys1.id == 2323 assert ssh_keys1.name == "SSH-Key" assert ssh_keys2._client is ssh_keys_client assert ssh_keys2.id == 2324 assert ssh_keys2.name == "SSH-Key" def test_get_by_name(self, ssh_keys_client, one_ssh_keys_response): ssh_keys_client._client.request.return_value = one_ssh_keys_response ssh_keys = ssh_keys_client.get_by_name("SSH-Key") params = {'name': "SSH-Key"} ssh_keys_client._client.request.assert_called_with(url="/ssh_keys", method="GET", params=params) assert ssh_keys._client is ssh_keys_client assert ssh_keys.id == 2323 assert ssh_keys.name == "SSH-Key" def test_get_by_fingerprint(self, ssh_keys_client, one_ssh_keys_response): ssh_keys_client._client.request.return_value = one_ssh_keys_response ssh_keys = ssh_keys_client.get_by_fingerprint( "b7:2f:30:a0:2f:6c:58:6c:21:04:58:61:ba:06:3b:2f") params = { 'fingerprint': "b7:2f:30:a0:2f:6c:58:6c:21:04:58:61:ba:06:3b:2f" } ssh_keys_client._client.request.assert_called_with(url="/ssh_keys", method="GET", params=params) assert ssh_keys._client is ssh_keys_client assert ssh_keys.id == 2323 assert ssh_keys.name == "SSH-Key" def test_create(self, ssh_keys_client, ssh_key_response): ssh_keys_client._client.request.return_value = ssh_key_response ssh_key = ssh_keys_client.create(name="My ssh key", public_key="ssh-rsa AAAjjk76kgf...Xt") ssh_keys_client._client.request.assert_called_with( url="/ssh_keys", method="POST", json={ "name": "My ssh key", "public_key": "ssh-rsa AAAjjk76kgf...Xt" }) assert ssh_key.id == 2323 assert ssh_key.name == "My ssh key" @pytest.mark.parametrize( "ssh_key", [SSHKey(id=1), BoundSSHKey(mock.MagicMock(), dict(id=1))]) def test_update(self, ssh_keys_client, ssh_key, response_update_ssh_key): ssh_keys_client._client.request.return_value = response_update_ssh_key ssh_key = ssh_keys_client.update(ssh_key, name="New name") ssh_keys_client._client.request.assert_called_with( url="/ssh_keys/1", method="PUT", json={"name": "New name"}) assert ssh_key.id == 2323 assert ssh_key.name == "New name" @pytest.mark.parametrize( "ssh_key", [SSHKey(id=1), BoundSSHKey(mock.MagicMock(), dict(id=1))]) def test_delete(self, ssh_keys_client, ssh_key, generic_action): ssh_keys_client._client.request.return_value = generic_action delete_success = ssh_keys_client.delete(ssh_key) ssh_keys_client._client.request.assert_called_with(url="/ssh_keys/1", method="DELETE") assert delete_success is True
def create(self, defn: HcloudDefinition, check, allow_reboot, allow_recreate): assert isinstance(defn, HcloudDefinition) hetzner = defn.config.hcloud self.token = get_access_token(hetzner) if self.state not in (MachineState.RESCUE, MachineState.UP) or check: self.check() self.set_common_state(defn) self.upgrade_disk = hetzner.upgradeDisk # TODO maybe bootstrap can be automated with vncdotool image_id = self._fetch_image_id(hetzner.image, hetzner.image_selector) if self.image_id is None: self.image_id = image_id elif self.image_id != image_id: self.warn( f"image_id changed from {self.image_id} to {image_id} but can't update image of a VM." ) if self.location is None: self.location = hetzner.location elif self.location != hetzner.location: self.warn( f"location changed from {self.location} to {hetzner.location} but can't update location of a VM." ) if self.vm_id is not None and hetzner.serverType != self.server_type: # TODO Check if server can be upgraded before hitting the Hetzner API # https://docs.hetzner.cloud/#server-actions-change-the-type-of-a-server do_upgrade = True # Only confirm if upgrade_disk is True because then the upgrade can't be undone if self.upgrade_disk: do_upgrade = self.depl.logger.confirm( f"are you sure you want to change Hetzner server {self.name} type from " + f"{self.server_type} to {hetzner.serverType}?") if do_upgrade: self.log_start("Changing Hetzner server type...") self._server.shutdown().wait_until_finished() self.wait_for_down(callback=lambda: self.log_continue(".")) self._server.change_type( ServerType(name=hetzner.serverType), upgrade_disk=self.upgrade_disk).wait_until_finished() self._server.power_on() self.wait_for_up(callback=lambda: self.log_continue(".")) self.log_end("") self.server_type = hetzner.serverType ssh_keys = [ k.name if isinstance(k, ResourceEval) else k for k in hetzner.sshKeys ] if self.state != MachineState.MISSING and ssh_keys != self.ssh_keys: self.logger.warn( f"SSH keys cannot be changed after the server is created.") volume_ids = [] filesystems = {} for volumeopts in hetzner.volumes: volume = volumeopts.volume if isinstance(volume, str): volume_model = self._client.volumes.get_by_name(volume) volume_name = volume volume_id = volume_model.id volume_loc = volume_model.location.name else: volume_res = self.depl.get_typed_resource( volume._name, "hcloud-volume", HcloudVolumeState) volume_name = volume_res.name volume_id = volume_res.hcloud_id assert volume_id is not None volume_loc = volume_res.location if volume_loc != self.location: raise Exception( f"Volume {volume_name!r} is in a different location from server {self.name!r}" ) volume_ids.append(volume_id) if volumeopts.mountPoint is not None: fs = dict(volumeopts.fileSystem) fs["device"] = f"/dev/disk/by-id/scsi-0HC_Volume_{volume_id}" filesystems[volumeopts.mountPoint] = fs has_priv = self._ssh_private_key is not None has_pub = self._ssh_public_key is not None assert has_priv == has_pub if not has_priv: self.log("Generating SSH keypair...") (self._ssh_private_key, self._ssh_public_key) = create_key_pair() if self.vm_id: if self.volume_ids != volume_ids: current = set(self.volume_ids) new = set(volume_ids) volumes_client = self._client.volumes self.log_start("Updating volumes...") for v in current - new: volumes_client.detach(Volume(id=v)) self.log_continue(".") for v in new - current: volumes_client.attach( Volume(id=v), self._server, automount=False).wait_until_finished() self.log_continue(".") self.log_end("") self.volume_ids = volume_ids else: self.log_start( "Creating Hetzner Cloud VM (" + f"image '{image_id}', type '{hetzner.serverType}', location '{hetzner.location}'" + ")...") response = self._client.servers.create( name=self.name, ssh_keys=[SSHKey(name=k) for k in ssh_keys], volumes=[Volume(id=v) for v in volume_ids], server_type=ServerType(self.server_type), image=Image(id=self.image_id), # Set labels so we can find the instance if nixops crashes before writing vm_id labels=dict(self._server_labels()), user_data=None if self._ssh_public_key is None else yaml.dump( {"public-keys": [self._ssh_public_key]}), ) self.log_end("") self.public_ipv4 = response.server.public_net.ipv4.ip self.log_start("waiting for SSH...") self.wait_for_up(callback=lambda: self.log_continue(".")) self.log_end("") with self.depl._db: self.vm_id = response.server.id # TODO get state from creation response self.state = MachineState.STARTING self.ssh_keys = ssh_keys self.volume_ids = volume_ids self._detect_hardware() self._update_host_keys() self.filesystems = filesystems
def _create_server(self): self.module.fail_on_missing_params( required_params=["name", "server_type", "image"] ) params = { "name": self.module.params.get("name"), "server_type": self.client.server_types.get_by_name( self.module.params.get("server_type") ), "user_data": self.module.params.get("user_data"), "labels": self.module.params.get("labels"), } image = self.client.images.get_by_name(self.module.params.get("image")) if image is not None: # When image name is not available look for id instead params["image"] = image else: params["image"] = self.client.images.get_by_id(self.module.params.get("image")) if self.module.params.get("ssh_keys") is not None: params["ssh_keys"] = [ SSHKey(name=ssh_key_name) for ssh_key_name in self.module.params.get("ssh_keys") ] if self.module.params.get("volumes") is not None: params["volumes"] = [ Volume(id=volume_id) for volume_id in self.module.params.get("volumes") ] if self.module.params.get("firewalls") is not None: params["firewalls"] = [] for fw in self.module.params.get("firewalls"): f = self.client.firewalls.get_by_name(fw) if f is not None: # When firewall name is not available look for id instead params["firewalls"].append(f) else: params["firewalls"].append(self.client.firewalls.get_by_id(fw)) if self.module.params.get("location") is None and self.module.params.get("datacenter") is None: # When not given, the API will choose the location. params["location"] = None params["datacenter"] = None elif self.module.params.get("location") is not None and self.module.params.get("datacenter") is None: params["location"] = self.client.locations.get_by_name( self.module.params.get("location") ) elif self.module.params.get("location") is None and self.module.params.get("datacenter") is not None: params["datacenter"] = self.client.datacenters.get_by_name( self.module.params.get("datacenter") ) if not self.module.check_mode: try: resp = self.client.servers.create(**params) self.result["root_password"] = resp.root_password resp.action.wait_until_finished(max_retries=1000) [action.wait_until_finished() for action in resp.next_actions] rescue_mode = self.module.params.get("rescue_mode") if rescue_mode: self._get_server() self._set_rescue_mode(rescue_mode) backups = self.module.params.get("backups") if backups: self._get_server() self.hcloud_server.enable_backup().wait_until_finished() delete_protection = self.module.params.get("delete_protection") rebuild_protection = self.module.params.get("rebuild_protection") if delete_protection is not None and rebuild_protection is not None: self._get_server() self.hcloud_server.change_protection(delete=delete_protection, rebuild=rebuild_protection).wait_until_finished() except Exception as e: self.module.fail_json(msg=e.message) self._mark_as_changed() self._get_server()