Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions docs/docs/infrahubctl/infrahubctl-repository.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ $ infrahubctl repository [OPTIONS] COMMAND [ARGS]...
**Commands**:

* `add`: Add a new repository.
* `init`: Initialize a new Infrahub repository.
* `list`

## `infrahubctl repository add`
Expand Down Expand Up @@ -47,6 +48,26 @@ $ infrahubctl repository add [OPTIONS] NAME LOCATION
* `--config-file TEXT`: [env var: INFRAHUBCTL_CONFIG; default: infrahubctl.toml]
* `--help`: Show this message and exit.

## `infrahubctl repository init`

Initialize a new Infrahub repository.

**Usage**:

```console
$ infrahubctl repository init [OPTIONS] DIRECTORY
```

**Arguments**:

* `DIRECTORY`: Directory path for the new project. [required]

**Options**:

* `--data PATH`: Path to YAML file containing answers to CLI prompt.
* `--config-file TEXT`: [env var: INFRAHUBCTL_CONFIG; default: infrahubctl.toml]
* `--help`: Show this message and exit.

## `infrahubctl repository list`

**Usage**:
Expand Down
3 changes: 3 additions & 0 deletions infrahub_sdk/ctl/example_repo/.infrahub.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
schemas:
- schemas
36 changes: 36 additions & 0 deletions infrahub_sdk/ctl/example_repo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Infrahub Repository

Welcome! This repository was initialized via the `infrahubctl repo init` command. That bootstraps a repository for use with some example data.

## Installation

Running `poetry install` will install all the main dependencies you need to interact with this repository.

## Starting Infrahub

Included in the repository are a set of helper commands to get Infrahub up and running using `invoke`.

```bash
Available tasks:

destroy Stop and remove containers, networks, and volumes.
download-compose-file Download docker-compose.yml from InfraHub if missing or override is True.
load-schema Load schemas into InfraHub using infrahubctl.
restart Restart all services or a specific one using docker-compose.
start Start the services using docker-compose in detached mode.
stop Stop containers and remove networks.
test Run tests using pytest.
```

To start infrahub simply use `invoke start`

## Tests

By default there are some integration tests that will spin up Infrahub and its dependencies in docker and load the repository and schema. This can be run using the following:

```bash
poetry install --with=dev
pytest tests/integration
```

To change the version of infrahub being used you can use an environment variable: `export INFRAHUB_TESTING_IMAGE_VERSION=1.2.5`.
32 changes: 32 additions & 0 deletions infrahub_sdk/ctl/example_repo/copier.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
project_name:
type: str
help: What is your project name?
generators:
type: bool
help: Would you like to use generators?
default: false
transforms:
type: bool
help: Would you like to use transforms?
default: false
scripts:
type: bool
help: Would you like to have local scripts?
default: false
queries:
type: bool
help: Would you like to store queries?
default: false
menus:
type: bool
help: Would you like to store menus?
default: false
tests:
type: bool
help: Would you like to have testing?
default: false
package_mode:
type: bool
help: Would you like to have a python library initialized?
default: false
28 changes: 28 additions & 0 deletions infrahub_sdk/ctl/example_repo/pyproject.toml.jinja
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[tool.poetry]
name = "{{ project_name }}"
version = "0.1.0"
description = "Infrahub Repository"
authors = []
readme = "README.md"
{% if package_mode %}
packages = [
{ include = "lib" }
]
{% else %}
package-mode = false
{% endif %}

[tool.poetry.dependencies]
python = ">=3.9,<=3.13.1"
infrahub-sdk = { extras = ["all"], version = "*" }
invoke = "*"

[tool.poetry.group.dev.dependencies]
ruff = "*"
pytest = "*"
infrahub-testcontainers = "*"
pytest-asyncio = "*"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
32 changes: 32 additions & 0 deletions infrahub_sdk/ctl/example_repo/schemas/example.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# yaml-language-server: $schema=https://schema.infrahub.app/infrahub/schema/latest.json
---
version: "1.0"
nodes:
- name: Device
namespace: Network
human_friendly_id: ['hostname__value']
attributes:
- name: hostname
kind: Text
unique: true
- name: model
kind: Text
relationships:
- name: interfaces
cardinality: many
peer: NetworkInterface
kind: Component
- name: Interface
namespace: Network
attributes:
- name: name
kind: Text
- name: description
kind: Text
optional: true
relationships:
- name: device
cardinality: one
peer: NetworkDevice
optional: false
kind: Parent
83 changes: 83 additions & 0 deletions infrahub_sdk/ctl/example_repo/tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import os
from pathlib import Path

import httpx
from invoke import Context, task

# If no version is indicated, we will take the latest
VERSION = os.getenv("INFRAHUB_IMAGE_VER", None)


@task
def start(context: Context) -> None:
"""
Start the services using docker-compose in detached mode.
"""
download_compose_file(context, override=False)
context.run("docker compose up -d")


@task
def destroy(context: Context) -> None:
"""
Stop and remove containers, networks, and volumes.
"""
download_compose_file(context, override=False)
context.run("docker compose down -v")


@task
def stop(context: Context) -> None:
"""
Stop containers and remove networks.
"""
download_compose_file(context, override=False)
context.run("docker compose down")


@task(help={"component": "Optional name of a specific service to restart."})
def restart(context: Context, component: str = "") -> None:
"""
Restart all services or a specific one using docker-compose.
"""
download_compose_file(context, override=False)
if component:
context.run(f"docker compose restart {component}")
return

context.run("docker compose restart")


@task
def load_schema(ctx: Context) -> None:
"""
Load schemas into InfraHub using infrahubctl.
"""
ctx.run("infrahubctl schema load schemas")


@task
def test(ctx: Context) -> None:
"""
Run tests using pytest.
"""
ctx.run("pytest tests")


@task(help={"override": "Redownload the compose file even if it already exists."})
def download_compose_file(context: Context, override: bool = False) -> Path: # noqa ARG001
"""
Download docker-compose.yml from InfraHub if missing or override is True.
"""
compose_file = Path("./docker-compose.yml")

if compose_file.exists() and not override:
return compose_file

response = httpx.get("https://infrahub.opsmill.io")
response.raise_for_status()

with compose_file.open("w") as f:
f.write(response.content.decode())

return compose_file
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import logging

from infrahub_sdk.node import InfrahubNode


def print_nodes(log: logging.Logger, nodes: list[InfrahubNode]):
for node in nodes.keys():
log.info(f"{node} present.")
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import logging

from lib.example import print_nodes

from infrahub_sdk import InfrahubClient


async def run(
client: InfrahubClient,
log: logging.Logger,
branch: str,
):
log.info(f"Running example script on {branch}...")
nodes = await client.schema.all()
print_nodes(log, nodes)
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from pathlib import Path
from typing import Any

import pytest

from infrahub_sdk.yaml import SchemaFile

CURRENT_DIRECTORY = Path(__file__).parent.resolve()


@pytest.fixture
def root_directory() -> Path:
"""
Return the path of the root directory of the repository.
"""
return CURRENT_DIRECTORY.parent.parent


@pytest.fixture
def schemas_directory(root_directory: Path) -> Path:
return root_directory / "schemas"


@pytest.fixture
def schemas(schemas_directory: Path) -> list[dict[str, Any]]:
schema_files = SchemaFile.load_from_disk(paths=[schemas_directory])
return [item.content for item in schema_files if item.content]
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from pathlib import Path

import pytest

from infrahub_sdk import InfrahubClient
from infrahub_sdk.protocols import CoreGenericRepository
from infrahub_sdk.testing.docker import TestInfrahubDockerClient
from infrahub_sdk.testing.repository import GitRepo


class TestInfrahub(TestInfrahubDockerClient):
@pytest.mark.asyncio
async def test_load_schema(self, default_branch: str, client: InfrahubClient, schemas: list[dict]):
await client.schema.wait_until_converged(branch=default_branch)

resp = await client.schema.load(schemas=schemas, branch=default_branch, wait_until_converged=True)
assert resp.errors == {}

@pytest.mark.asyncio
async def test_load_repository(
self,
client: InfrahubClient,
remote_repos_dir: Path,
root_directory: Path,
) -> None:
"""Add the local directory as a repository in Infrahub and wait for the import to be complete"""

repo = GitRepo(
name="local-repository",
src_directory=root_directory,
dst_directory=remote_repos_dir,
)
await repo.add_to_infrahub(client=client)
in_sync = await repo.wait_for_sync_to_complete(client=client)
assert in_sync

repos = await client.all(kind=CoreGenericRepository)

# A breakpoint can be added to pause the tests from running and keep the test containers active
# breakpoint()

assert repos
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Changes here will be overwritten by Copier
{{ _copier_answers|to_nice_yaml -}}
27 changes: 27 additions & 0 deletions infrahub_sdk/ctl/repository.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
from __future__ import annotations

import asyncio
from pathlib import Path
from typing import Optional

import typer
import yaml
from copier import run_copy
from pydantic import ValidationError
from rich.console import Console
from rich.table import Table
Expand Down Expand Up @@ -165,3 +167,28 @@
)

console.print(table)


@app.command()
async def init(
directory: Path = typer.Argument(help="Directory path for the new project."),
data: Optional[Path] = typer.Option(default=None, help="Path to YAML file containing answers to CLI prompt."),
_: str = CONFIG_PARAM,
) -> None:
"""Initialize a new Infrahub repository."""
example_repo = Path(__file__).parent / "example_repo"
config_data = None
if data:
try:
with Path.open(data, encoding="utf-8") as f:
config_data = yaml.safe_load(f) # Load YAML contents
typer.echo(f"Loaded config: {config_data}") # Print for debugging
except Exception as e:
typer.echo(f"Error loading YAML file: {e}", err=True)
raise typer.Exit(code=1)

Check warning on line 188 in infrahub_sdk/ctl/repository.py

View check run for this annotation

Codecov / codecov/patch

infrahub_sdk/ctl/repository.py#L186-L188

Added lines #L186 - L188 were not covered by tests

try:
await asyncio.to_thread(run_copy, str(example_repo), str(directory), data=config_data)
except Exception as e:
typer.echo(f"Error running copier: {e}", err=True)
raise typer.Exit(code=1)

Check warning on line 194 in infrahub_sdk/ctl/repository.py

View check run for this annotation

Codecov / codecov/patch

infrahub_sdk/ctl/repository.py#L192-L194

Added lines #L192 - L194 were not covered by tests
Loading