Skip to content
Merged
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
120 changes: 120 additions & 0 deletions CHEATSHEET.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# CFEngine CLI cheatsheet

Quick reference for `cfengine COMMANDS`
For the full command list, run `cfengine help` or `cfengine COMMAND --help`.

## Managing hosts

```bash
# Save one or more hosts under a group name, for reuse in other commands
cfengine save --hosts 192.168.56.90 --role hub --name myhub

# List all saved host-groups
cfengine show

# Show details about a specific saved host (or group)
cfengine show --host myhub

# Open an interactive SSH session to a saved host
cfengine connect --hosts myhub
```

## Managing cfbs modules

These are thin wrappers around the equivalent `cfbs` subcommands, usable from anywhere inside a cfbs project.

```bash
# Search the build-index for a module
cfengine search promise-type-git

# Add module(s) to the current cfbs project
cfengine add promise-type-git

# Set/update input.json for a module that takes input
cfengine input promise-type-git

# Remove module(s) from the current cfbs project
cfengine remove promise-type-git

# Update the current cfbs project (or specific modules)
cfengine update
cfengine update promise-type-git

# Show status of the current cfbs project
cfengine moduleinfo

# Show info about specific module(s) (does not require being inside a project)
cfengine moduleinfo promise-type-git
```

## Building and deploying policy sets

```bash
# Build the policy set from a cfbs project (equivalent to `cfbs build`)
cfengine build

# build -> deploy -> run on the given hub without extra prompts
cfengine build --hub myhub --non-interactive

# Deploy an already-built policy set to a hub
cfengine deploy --hub myhub
```

## Running the CFEngine agent

```bash
# Default: cf-agent -KIf update.cf && cf-agent -KIf
# If there is a local installation, this will be used
cfengine run

# Run a specific policy file (resolved locally, or uploaded if not found remotely)
cfengine run /tmp/some_policy.cf

# Run against a specific saved host, instead of being prompted
cfengine run --host myhub

# Chain multiple commands, executed in sequence
cfengine run /tmp/some_policy.cf /tmp/other_policy.cf
```

## Initializing new projects

```bash
# Build project on top of the default masterfiles (default, same as `cfbs init`)
cfengine init --policy-set

# Initialize an example build project for working on a custom promise type (python)
cfengine init --promise-type

# Initialize an exapmlte module project for build.cfengine.com (or internal use)
cfengine init --policy-module

# Initialize an example policy module that takes input data
cfengine init --policy-module --with-input

# Skip interactive prompts
cfengine init --promise-module --non-interactive
```

## Formatting and linting

```bash
cfengine format
cfengine lint
cfengine lint main.cf
```

## Reporting

```bash
# Refresh reporting data on all known hubs/clients (capped at 25 hosts), optional flag to run agent first.
cfengine report [--run-agent]

# Only refresh a specific hub (and its clients, capped at 25), running the agent first
cfengine report --hub myhub --run-agent
```

---

See [README.md](./README.md) for installation and general usage, and [HACKING.md](./HACKING.md) for
contributing/development info.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ To ignore the previously saved configuration and apply the current configuration
cfengine up --reset config.yaml
```

See [CHEATSHEET.md](./CHEATSHEET.md) for a fuller command reference.

## Supported platforms and versions

This tool will only support a limited number of platforms, it is not intended to run everywhere CFEngine runs.
Expand Down
108 changes: 101 additions & 7 deletions src/cfengine_cli/cfengine_wrapper/arg_parse.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,93 @@
add_uninstall_args,
add_spawn_args,
add_destroy_args,
add_connect_args,
)


def parse_wrapper_args(subp: argparse._SubParsersAction):
update_parser = subp.add_parser(
"update",
help="Updates the current cfbs project",
description="A wrapper around the cfbs `update` function",
)
update_parser.add_argument(
"to_update",
nargs="*",
help="Directory of cfbs-project to update",
)
remove_parser = subp.add_parser(
"remove",
help="Removes the specified module(s) from cfbs project",
description="A wrapper around the cfbs `remove` function",
)
remove_parser.add_argument(
"module",
nargs="+",
help="Module(s) for which to remove",
)

add_parser = subp.add_parser(
"add",
help="Adds the specified module(s) to cfbs project",
description="A wrapper around the cfbs `add` function",
)
add_parser.add_argument(
"module",
nargs="+",
help="Module(s) for which to add",
)
search_parser = subp.add_parser(
"search",
help="Searches the build-index for specified module(s)",
description="A wrapper around the cfbs `search` function",
)
search_parser.add_argument(
"module",
nargs="+",
help="Module(s) for which to lookup",
)

input_parser = subp.add_parser(
"input",
help="Sets/updates input.json for selected module(s)",
description="A wrapper around the cfbs `input` function",
)
input_parser.add_argument(
"module",
nargs="+",
help="Module(s) for which to set input",
)

add_connect_args(
subp.add_parser(
"connect",
help="Opens interactive ssh shell",
description="A wrapper around cf-remote `connect` function",
)
)

moduleinfo_parser = subp.add_parser(
"moduleinfo",
help="Shows information about your cfbs-project or a specific module",
description="A wrapper around the cfbs `status` function",
)

moduleinfo_parser.add_argument(
"modules",
nargs="*",
help="Module(s) for which you would like more info, utilizes cfbs `info` function",
)

show_parser = subp.add_parser(
"show", help="Shows your saved host-groups or info about a specified host"
)
show_parser.add_argument(
"--hosts",
"--host",
"-H",
help="Shows more specific information about specific host(s)",
)

add_save_args(
subp.add_parser(
Expand All @@ -29,18 +112,29 @@ def parse_wrapper_args(subp: argparse._SubParsersAction):
default=None,
)

subp.add_parser(
sp = subp.add_parser(
"build",
help="""Build a policy set from a CFEngine Build project.
A wrapper around the cfbs `build`-function.""",
help="Build a policy set from a CFEngine Build project",
description="A wrapper around the cf-remote `build`-function with some added niceties",
)
sp.add_argument(
"--non-interactive",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't the whole point with CFEngine CLI that it should not be used in scripts? I feel options like --non-interactive kind of works against that. Do we already have these options for other sub commands?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not for most commands, only init and for build/deploy. I added them as niceties when testing a module, that way I could just do cfengine build --host HOST --non-interactive and wait for output rather than pressing enter every 5 seconds.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could consider

parser = argparse.ArgumentParser(prog='frobble')
parser.add_argument('--foo', help=argparse.SUPPRESS)
parser.print_help()

for arguments that are only for our tests.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant testing as in using. They way it would work without is that you would build, prompt if you want to deploy, then prompt if you want to run. But when working on a module/policy-set you would probably want to just fast-forward this and read the output after the full pipeline.

help="Non-interactive mode (picks the default for all prompts)",
action="store_true",
)
sp.add_argument("--hub", help="Hub(s) to deploy to after building", type=str)

deploy_parser = subp.add_parser(
"deploy",
help="""Deploy policy-set (masterfiles) to hub.
A wrapper around the cf-remote `deploy`-function with some added niceties.""",
help="Deploy policy-set (masterfiles) to hub.",
description="A wrapper around the cf-remote `deploy`-function with some added niceties.",
)
add_deploy_args(deploy_parser)
deploy_parser.add_argument(
"--non-interactive",
help="Non-interactive mode (picks the default for all prompts)",
action="store_true",
)

install_parser = subp.add_parser(
"install",
Expand Down Expand Up @@ -81,8 +175,8 @@ def parse_wrapper_args(subp: argparse._SubParsersAction):

run_parser = subp.add_parser(
"run",
description="Run the CFEngine agent, fetching, evaluating, and enforcing policy.\n\
A wrapper around the cf-remote `run`-function with some added niceties",
help="Run the CFEngine agent, fetching, evaluating, and enforcing policy.",
description="A wrapper around the cf-remote `run`-function with some added niceties",
epilog="""Examples:
`cfengine run` defaults to use `cf-agent -KIf update.cf && cf-agent -KI`

Expand Down
100 changes: 92 additions & 8 deletions src/cfengine_cli/cfengine_wrapper/cfengine_commands.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
import os

from cfbs.commands import build_command
from cfbs.utils import is_cfbs_repo
from cfbs.commands import (
build_command,
info_command,
status_command,
)
from cf_remote import log
from cf_remote.commands import deploy as deploy_command
from cf_remote.commands import deploy as deploy_command, info
from cf_remote.commands import destroy as destroy_command
from cf_remote.commands import save as save_command
from cf_remote.commands import show as show_command
from cf_remote.remote import run_command, transfer_file
from cf_remote.commands import connect_cmd
from cfbs.commands import (
input_command,
add_command,
remove_command,
update_command,
search_command,
)

from cfengine_cli.utils import UserError
from cfengine_cli.cfengine_wrapper.cfengine_objects import (
Expand Down Expand Up @@ -228,17 +242,87 @@ def destroy(groupname, del_all=False) -> int:
return destroy_command(groupname)


def build() -> int:
def build(hub=None, non_interactive=False) -> int:
rc = build_command()
if rc != 0:
return rc
if prompt_yes_no("Deploy the built policy set now?", default=True):
return deploy(None, None)
if prompt_yes_no(
"Deploy the built policy set now?",
default=True,
non_interactive=non_interactive,
):
return deploy(hub, None, non_interactive)
return 0


def deploy(target: str | list[str] | None, masterfiles: str | None = None) -> int:
def deploy(
target: str | list[str] | None,
masterfiles: str | None = None,
non_interactive: bool = False,
) -> int:
error = 0
if isinstance(target, str):
target = [target]
hubs = [require_executable("cf-agent", h).location for h in (target or [])] or None
return deploy_command(hubs, masterfiles)
hubs = {
x.location: x
for h in (target or [])
for x in [require_executable("cf-agent", h)]
} or None

# TODO/WOULD be nice: Deploy without run (CFE-4704: https://northerntech.atlassian.net/browse/CFE-4704)
if hubs:
# cf-remote functions use "localhost" (not "local" as it is here)
deploy_targets = [
"localhost" if location == "local" else location for location in hubs
]
error = deploy_command(deploy_targets, masterfiles)
else:
return deploy_command(hubs, masterfiles)

if prompt_yes_no(
"Run policy set now?", default=True, non_interactive=non_interactive
):
for hub in hubs:
hubs[hub].run("-KIf update.cf", "-KI")
Comment thread
SimonThalvorsen marked this conversation as resolved.
return error


def show(target: list[str] | None = None) -> int:
if target == [] or target is None:
return show_command(False)
if isinstance(target, str):
target = [target]
return info(target)


def moduleinfo(modules: list[str]) -> int:
if modules != []:
return info_command(modules)
if not is_cfbs_repo():
log.error("This is not a cfbs repo, to get started, type: cfengine init")
return 1
return status_command()


def connect(host) -> int:
return connect_cmd(host)


def cfbs_input(modules: list[str]) -> int:
return input_command(modules, "cfengine input")


def cfbs_add(modules: list[str]) -> int:
return add_command(modules, "cfengine input")


def cfbs_remove(modules: list[str] | None = None) -> int:
return remove_command(modules, "cfengine input")


def cfbs_update(to_update) -> int:
return update_command(to_update)


def cfbs_search(modules: list[str]) -> int:
return search_command(modules)
Loading