예제 #1
0
    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()
예제 #2
0
    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"
예제 #3
0
 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))
예제 #4
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
예제 #5
0
 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)
예제 #6
0
    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
예제 #7
0
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
예제 #8
0
    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
예제 #9
0
    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()