Skip to content

Commit

Permalink
feat: Start vaultd service (#19)
Browse files Browse the repository at this point in the history
  • Loading branch information
gruyaume authored Jan 26, 2024
1 parent f7a3368 commit e554a13
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 27 deletions.
9 changes: 3 additions & 6 deletions metadata.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

name: vault-dev

display-name: Vault Operator
display-name: Vault
summary: A tool for managing secrets
description: |
Vault secures, stores, and tightly controls access to
Expand All @@ -23,12 +23,9 @@ assumes:
- juju >= 3.1

storage:
vault-raft:
vault:
type: filesystem
location: /var/snap/vault/common/raft
config:
type: filesystem
location: /var/snap/vault/common/config
location: /var/snap/vault/common

peers:
vault-peers:
Expand Down
40 changes: 28 additions & 12 deletions src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@
CONFIG_TEMPLATE_DIR_PATH = "src/templates/"
CONFIG_TEMPLATE_NAME = "vault.hcl.j2"
PEER_RELATION_NAME = "vault-peers"
VAULT_CONFIG_PATH = "/var/snap/vault/common/config"
VAULT_CONFIG_PATH = "/var/snap/vault/common"
VAULT_CONFIG_FILE_NAME = "vault.hcl"
VAULT_PORT = 8200
VAULT_CLUSTER_PORT = 8201
VAULT_SNAP_NAME = "vault"
VAULT_SNAP_CHANNEL = "1.12/stable"
VAULT_SNAP_REVISION = 2166
VAULT_SNAP_CHANNEL = "latest/edge"
VAULT_SNAP_REVISION = 2177
VAULT_STORAGE_PATH = "/var/snap/vault/common/raft"


Expand Down Expand Up @@ -77,7 +77,11 @@ def config_file_content_matches(existing_content: str, new_content: str) -> bool
return existing_config_hcl == new_content_hcl

new_retry_joins = new_content_hcl["storage"]["raft"].pop("retry_join", [])
existing_retry_joins = existing_config_hcl["storage"]["raft"].pop("retry_join", [])

try:
existing_retry_joins = existing_config_hcl["storage"]["raft"].pop("retry_join", [])
except KeyError:
existing_retry_joins = []

# If there is only one retry join, it is a dict
if isinstance(new_retry_joins, dict):
Expand Down Expand Up @@ -105,7 +109,7 @@ def __init__(self, *args):
self,
scrape_configs=[
{
"scheme": "https",
"scheme": "http",
"tls_config": {"insecure_skip_verify": True},
"metrics_path": "/v1/sys/metrics",
"static_configs": [{"targets": [f"*:{VAULT_PORT}"]}],
Expand Down Expand Up @@ -136,7 +140,9 @@ def _configure(self, _):
return
self.unit.status = MaintenanceStatus("Installing Vault")
self._install_vault_snap()
self._create_backend_directory()
self._generate_vault_config_file()
self._start_vault_service()
self._set_peer_relation_node_api_address()
self.unit.status = ActiveStatus()

Expand All @@ -149,11 +155,21 @@ def _install_vault_snap(self) -> None:
snap.SnapState.Latest, channel=VAULT_SNAP_CHANNEL, revision=VAULT_SNAP_REVISION
)
vault_snap.hold()

logger.info("Vault snap installed")
except snap.SnapError as e:
logger.error("An exception occurred when installing Vault. Reason: %s", str(e))
raise e

def _create_backend_directory(self) -> None:
self.machine.make_dir(path=VAULT_STORAGE_PATH)

def _start_vault_service(self) -> None:
"""Start the Vault service."""
snap_cache = snap.SnapCache()
vault_snap = snap_cache[VAULT_SNAP_NAME]
vault_snap.start(services=["vaultd"])
logger.info("Vault service started")

def _generate_vault_config_file(self) -> None:
"""Create the Vault config file and push it to the Machine."""
assert self._cluster_address
Expand Down Expand Up @@ -238,23 +254,23 @@ def _bind_address(self) -> Optional[str]:

@property
def _api_address(self) -> Optional[str]:
"""Returns the IP with the https schema and vault port.
"""Returns the IP with the http schema and vault port.
Example: "https://1.2.3.4:8200"
Example: "http://1.2.3.4:8200"
"""
if not self._bind_address:
return None
return f"https://{self._bind_address}:{VAULT_PORT}"
return f"http://{self._bind_address}:{VAULT_PORT}"

@property
def _cluster_address(self) -> Optional[str]:
"""Return the IP with the https schema and vault port.
"""Return the IP with the http schema and vault port.
Example: "https://1.2.3.4:8201"
Example: "http://1.2.3.4:8201"
"""
if not self._bind_address:
return None
return f"https://{self._bind_address}:{VAULT_CLUSTER_PORT}"
return f"http://{self._bind_address}:{VAULT_CLUSTER_PORT}"

@property
def _node_id(self) -> str:
Expand Down
5 changes: 5 additions & 0 deletions src/machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import logging
import os
from pathlib import Path

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -52,3 +53,7 @@ def push(self, path: str, source: str) -> None:
with open(path, "w") as write_file:
write_file.write(source)
logger.info("Pushed file %s", path)

def make_dir(self, path: str) -> None:
"""Create a directory."""
Path(path).mkdir(parents=True, exist_ok=True)
1 change: 1 addition & 0 deletions src/templates/vault.hcl.j2
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ listener "tcp" {
unauthenticated_metrics_access = true
}
address = "{{ tcp_address }}"
tls_disable = 1
}
default_lease_ttl = "{{ default_lease_ttl }}"
max_lease_ttl = "{{ max_lease_ttl }}"
Expand Down
5 changes: 3 additions & 2 deletions tests/unit/config.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ listener "tcp" {
unauthenticated_metrics_access = true
}
address = "[::]:8200"
tls_disable = 1
}
default_lease_ttl = "168h"
max_lease_ttl = "720h"
disable_mlock = true
cluster_addr = "https://1.2.1.2:8201"
api_addr = "https://1.2.1.2:8200"
cluster_addr = "http://1.2.1.2:8201"
api_addr = "http://1.2.1.2:8200"
telemetry {
disable_hostname = true
prometheus_retention_time = "12h"
Expand Down
43 changes: 36 additions & 7 deletions tests/unit/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# See LICENSE file for licensing details.

import unittest
from typing import List, Optional
from unittest.mock import patch

import hcl
Expand All @@ -27,6 +28,10 @@ def ensure(self, state, channel, revision):
def hold(self):
self.hold_called = True

def start(self, services: Optional[List[str]] = None, enable: Optional[bool] = False) -> None:
self.start_called = True
self.start_called_with = {"services": services, "enable": enable}


class MockNetwork:
def __init__(self, bind_address: str):
Expand Down Expand Up @@ -54,6 +59,9 @@ def push(self, path: str, source: str) -> None:
def pull(self, path: str) -> str:
pass

def make_dir(self, path: str) -> None:
pass


def read_file(path: str) -> str:
"""Read a file and returns as a string.
Expand Down Expand Up @@ -107,7 +115,6 @@ def setUp(self, patch_machine):
self.mock_machine = MockMachine()
self.model_name = "whatever"
patch_machine.return_value = self.mock_machine
self.app_name = "vault"
self.harness = ops.testing.Harness(VaultOperatorCharm)
self.harness.set_model_name(self.model_name)
self.addCleanup(self.harness.cleanup)
Expand All @@ -116,7 +123,7 @@ def setUp(self, patch_machine):
def _set_peer_relation(self) -> int:
"""Set the peer relation and return the relation id."""
return self.harness.add_relation(
relation_name=PEER_RELATION_NAME, remote_app=self.app_name
relation_name=PEER_RELATION_NAME, remote_app=self.harness.charm.app.name
)

def _set_other_node_api_address_in_peer_relation(self, relation_id: int, unit_name: str):
Expand Down Expand Up @@ -144,7 +151,7 @@ def test_given_vault_snap_uninstalled_when_configure_then_vault_snap_installed(

mock_snap_cache.assert_called_with()
assert vault_snap.ensure_called
assert vault_snap.ensure_called_with == (SnapState.Latest, "1.12/stable", 2166)
assert vault_snap.ensure_called_with == (SnapState.Latest, "latest/edge", 2177)
assert vault_snap.hold_called

@patch("ops.model.Model.get_binding")
Expand All @@ -160,9 +167,7 @@ def test_given_config_file_not_exists_when_configure_then_config_file_pushed(
self.harness.charm.on.install.emit()

assert self.mock_machine.push_called
assert (
self.mock_machine.push_called_with["path"] == "/var/snap/vault/common/config/vault.hcl"
)
assert self.mock_machine.push_called_with["path"] == "/var/snap/vault/common/vault.hcl"
pushed_content_hcl = hcl.loads(self.mock_machine.push_called_with["source"])
self.assertEqual(pushed_content_hcl, expected_content_hcl)

Expand Down Expand Up @@ -194,6 +199,30 @@ def test_given_unit_not_leader_and_peer_addresses_unavailable_when_configure_the
"Waiting for other units to provide their addresses"
)

@patch("ops.model.Model.get_binding")
@patch("charms.operator_libs_linux.v1.snap.SnapCache")
def test_given_when_configure_then_service_started(self, mock_snap_cache, patch_get_binding):
patch_get_binding.return_value = MockBinding(bind_address="1.2.1.2")
vault_snap = MockSnapObject("vault")
snap_cache = {"vault": vault_snap}
mock_snap_cache.return_value = snap_cache
self.harness.set_leader(is_leader=False)
peer_relation_id = self._set_peer_relation()
other_unit_name = f"{self.harness.charm.app.name}/1"
self.harness.add_relation_unit(
relation_id=peer_relation_id, remote_unit_name=other_unit_name
)

self._set_other_node_api_address_in_peer_relation(
relation_id=peer_relation_id, unit_name=other_unit_name
)

self.harness.charm.on.install.emit()

mock_snap_cache.assert_called_with()
assert vault_snap.start_called
assert vault_snap.start_called_with == {"enable": False, "services": ["vaultd"]}

@patch("ops.model.Model.get_binding")
@patch("charms.operator_libs_linux.v1.snap.SnapCache")
def test_given_unit_not_leader_and_peer_addresses_available_when_configure_then_status_is_active(
Expand All @@ -202,7 +231,7 @@ def test_given_unit_not_leader_and_peer_addresses_available_when_configure_then_
patch_get_binding.return_value = MockBinding(bind_address="1.2.1.2")
self.harness.set_leader(is_leader=False)
peer_relation_id = self._set_peer_relation()
other_unit_name = f"{self.app_name}/1"
other_unit_name = f"{self.harness.charm.app.name}/1"
self.harness.add_relation_unit(
relation_id=peer_relation_id, remote_unit_name=other_unit_name
)
Expand Down

0 comments on commit e554a13

Please sign in to comment.