-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add command to create new integrations #2037
Changes from 6 commits
6743846
7a80cc1
3f839d6
681372d
d1d40e6
8e2b936
a262d7e
5ec23d9
3ccdadc
c2eff60
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# (C) Datadog, Inc. 2018 | ||
# All rights reserved | ||
# Licensed under a 3-clause BSD style license (see LICENSE) | ||
import sys | ||
|
||
from .tooling.cli import ddev | ||
|
||
|
||
sys.exit(ddev()) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
# (C) Datadog, Inc. 2018 | ||
# All rights reserved | ||
# Licensed under a 3-clause BSD style license (see LICENSE) | ||
import os | ||
import uuid | ||
from collections import defaultdict | ||
from datetime import datetime | ||
|
||
import click | ||
|
||
from .utils import CONTEXT_SETTINGS, abort, echo_info, echo_success | ||
from ..constants import get_root | ||
from ..create import create_template_files, get_valid_templates | ||
from ..utils import normalize_package_name | ||
from ...utils import resolve_path | ||
|
||
HYPHEN = b'\xe2\x94\x80\xe2\x94\x80'.decode('utf-8') | ||
PIPE = b'\xe2\x94\x82'.decode('utf-8') | ||
PIPE_MIDDLE = b'\xe2\x94\x9c'.decode('utf-8') | ||
PIPE_END = b'\xe2\x94\x94'.decode('utf-8') | ||
|
||
|
||
def tree(): | ||
return defaultdict(tree) | ||
|
||
|
||
def construct_output_info(path, depth, last, is_dir=False): | ||
if depth == 0: | ||
return u'', path, is_dir | ||
else: | ||
if depth == 1: | ||
return ( | ||
u'{}{} '.format( | ||
PIPE_END if last else PIPE_MIDDLE, HYPHEN | ||
), | ||
path, | ||
is_dir | ||
) | ||
else: | ||
return ( | ||
u'{} {}{}'.format( | ||
PIPE, | ||
u' ' * 4 * (depth - 2), | ||
u'{}{} '.format(PIPE_END if last or is_dir else PIPE_MIDDLE, HYPHEN) | ||
), | ||
path, | ||
is_dir | ||
) | ||
|
||
|
||
def path_tree_output(path_tree, depth=0): | ||
# Avoid possible imposed recursion limits by using a generator. | ||
# See https://en.wikipedia.org/wiki/Trampoline_(computing) | ||
dirs = [] | ||
files = [] | ||
|
||
for path in path_tree: | ||
if len(path_tree[path]) > 0: | ||
dirs.append(path) | ||
else: | ||
files.append(path) | ||
|
||
dirs.sort() | ||
length = len(dirs) | ||
|
||
for i, path in enumerate(dirs, 1): | ||
yield construct_output_info(path, depth, last=i == length and not files, is_dir=True) | ||
|
||
for info in path_tree_output(path_tree[path], depth + 1): | ||
yield info | ||
|
||
files.sort() | ||
length = len(files) | ||
|
||
for i, path in enumerate(files, 1): | ||
yield construct_output_info(path, depth, last=i == length) | ||
|
||
|
||
def display_path_tree(path_tree): | ||
for indent, path, is_dir in path_tree_output(path_tree): | ||
if indent: | ||
echo_info(indent, nl=False) | ||
|
||
if is_dir: | ||
echo_success(path) | ||
else: | ||
echo_info(path) | ||
|
||
|
||
@click.command(context_settings=CONTEXT_SETTINGS, short_help='Create a new integration') | ||
@click.argument('name') | ||
@click.option( | ||
'--type', '-t', 'integration_type', | ||
type=click.Choice(get_valid_templates()), | ||
default='check', | ||
help='The type of integration to create' | ||
) | ||
@click.option('--location', '-l', help='Where to create the integration') | ||
@click.option('--quiet', '-q', is_flag=True, help='Show less output') | ||
@click.option('--dry-run', '-n', is_flag=True, help='Only show what would be created') | ||
@click.pass_context | ||
def create(ctx, name, integration_type, location, quiet, dry_run): | ||
"""Create a new integration.""" | ||
integration_name = normalize_package_name(name) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should also keep a reference to |
||
root = resolve_path(location) if location else get_root() | ||
path_sep = os.path.sep | ||
|
||
integration_dir = os.path.join(root, integration_name) | ||
if os.path.exists(integration_dir): | ||
abort('Path `{}` already exists!'.format(integration_dir)) | ||
|
||
repo_choice = ctx.obj['repo_choice'] | ||
if repo_choice == 'core': | ||
author = 'Datadog' | ||
email = 'help@datadoghq.com' | ||
email_packages = 'packages@datadoghq.com' | ||
support_type = 'core' | ||
tox_base_dep = '../datadog_checks_base[deps]' | ||
else: | ||
author = 'U.N. Owen' | ||
email = email_packages = 'friend@datadog.community' | ||
support_type = 'contrib' | ||
tox_base_dep = 'datadog-checks-base[deps]' | ||
|
||
config = { | ||
'author': author, | ||
'check_class': '{}Check'.format(''.join(part.capitalize() for part in integration_name.split('_'))), | ||
'check_name': integration_name, | ||
'check_name_cap': integration_name.capitalize(), | ||
'email': email, | ||
'email_packages': email_packages, | ||
'guid': uuid.uuid4(), | ||
'repo_choice': repo_choice, | ||
'support_type': support_type, | ||
'tox_base_dep': tox_base_dep, | ||
'year': str(datetime.now().year), | ||
} | ||
|
||
files = create_template_files(integration_type, root, config, read=not dry_run) | ||
file_paths = [file.file_path.replace('{}{}'.format(root, path_sep), '', 1) for file in files] | ||
|
||
path_tree = tree() | ||
for file_path in file_paths: | ||
branch = path_tree | ||
|
||
for part in file_path.split(path_sep): | ||
branch = branch[part] | ||
|
||
if dry_run: | ||
if quiet: | ||
echo_info('Will create `{}`'.format(integration_dir)) | ||
else: | ||
echo_info('Will create in `{}`:'.format(root)) | ||
display_path_tree(path_tree) | ||
return | ||
|
||
for file in files: | ||
file.write() | ||
|
||
if quiet: | ||
echo_info('Created `{}`'.format(integration_dir)) | ||
else: | ||
echo_info('Created in `{}`:'.format(root)) | ||
display_path_tree(path_tree) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
# (C) Datadog, Inc. 2018 | ||
# All rights reserved | ||
# Licensed under a 3-clause BSD style license (see LICENSE) | ||
import os | ||
|
||
from ..utils import ( | ||
create_file, | ||
dir_exists, | ||
ensure_parent_dir_exists, | ||
path_join, | ||
read_file, | ||
read_file_binary, | ||
write_file, | ||
write_file_binary | ||
) | ||
|
||
TEMPLATES_DIR = path_join(os.path.dirname(os.path.abspath(__file__)), 'templates') | ||
BINARY_EXTENSIONS = ('.png', ) | ||
|
||
|
||
def get_valid_templates(): | ||
return sorted(os.listdir(TEMPLATES_DIR)) | ||
|
||
|
||
def create_template_files(template_name, new_root, config, read=False): | ||
files = [] | ||
|
||
template_root = path_join(TEMPLATES_DIR, template_name) | ||
if not dir_exists(template_root): | ||
return files | ||
|
||
for root, _, template_files in os.walk(template_root): | ||
for template_file in template_files: | ||
template_path = path_join(root, template_file) | ||
|
||
file_path = template_path.replace(template_root, '') | ||
file_path = '{}{}'.format(new_root, file_path.format(**config)) | ||
|
||
files.append( | ||
File( | ||
file_path, | ||
template_path, | ||
config, | ||
read=read | ||
) | ||
) | ||
|
||
return files | ||
|
||
|
||
class File(object): | ||
def __init__(self, file_path, template_path, config, read=False): | ||
self.file_path = file_path | ||
self.template_path = template_path | ||
self.config = config | ||
self.binary = template_path.endswith(BINARY_EXTENSIONS) | ||
self._read = read_file_binary if self.binary else read_file | ||
self._write = write_file_binary if self.binary else write_file | ||
self.contents = None | ||
|
||
if read: | ||
self.read() | ||
|
||
def read(self): | ||
contents = self._read(self.template_path) | ||
|
||
if self.binary: | ||
self.contents = contents | ||
else: | ||
self.contents = contents.format(**self.config) | ||
|
||
def write(self): | ||
if self.contents is None: | ||
create_file(self.file_path) | ||
else: | ||
ensure_parent_dir_exists(self.file_path) | ||
self._write(self.file_path, self.contents) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# CHANGELOG - {check_name_cap} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
graft datadog_checks | ||
graft tests | ||
|
||
include MANIFEST.in | ||
include README.md | ||
include requirements.in | ||
include requirements.txt | ||
include requirements-dev.txt | ||
include manifest.json | ||
|
||
global-exclude *.py[cod] __pycache__ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
# Agent Check: {check_name_cap} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add a line break between these headers. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! |
||
## Overview | ||
|
||
This check monitors [{check_name_cap}][1]. | ||
|
||
## Setup | ||
|
||
### Installation | ||
|
||
The {check_name_cap} check is included in the [Datadog Agent][2] package, so you don't | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is true of core Integrations, but not of extras; suggest hedging our default language here. |
||
need to install anything else on your server. | ||
|
||
### Configuration | ||
|
||
1. Edit the `{check_name}.d/conf.yaml` file, in the `conf.d/` folder at the root of your | ||
Agent's configuration directory to start collecting your {check_name} performance data. | ||
See the [sample {check_name}.d/conf.yaml][3] for all available configuration options. | ||
|
||
2. [Restart the Agent][4] | ||
|
||
### Validation | ||
|
||
[Run the Agent's `status` subcommand][5] and look for `{check_name}` under the Checks section. | ||
|
||
## Data Collected | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Line break please :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! |
||
### Metrics | ||
|
||
The {check_name_cap} check does not include any metrics at this time. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't need to say "at this time" here. The documentation represents now, not a hypothetical future.
Imho, we could even shorten this to:
|
||
|
||
### Service Checks | ||
|
||
The {check_name_cap} check does not include any service checks at this time. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As above. |
||
|
||
### Events | ||
|
||
The {check_name_cap} check does not include any events at this time. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As above. |
||
|
||
## Troubleshooting | ||
|
||
Need help? Contact [Datadog Support][6]. | ||
|
||
[1]: link to integration's site | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should this be a templatable item as well? If that's problematic, we should surface this more obviously, like |
||
[2]: https://app.datadoghq.com/account/settings#agent | ||
[3]: https://github.com/DataDog/integrations-core/blob/master/{check_name}/datadog_checks/{check_name}/data/conf.yaml.example | ||
[4]: https://docs.datadoghq.com/agent/faq/agent-commands/#start-stop-restart-the-agent | ||
[5]: https://docs.datadoghq.com/agent/faq/agent-commands/#agent-status-and-information | ||
[6]: https://docs.datadoghq.com/help/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# (C) Datadog, Inc. {year} | ||
# All rights reserved | ||
# Licensed under a 3-clause BSD style license (see LICENSE) | ||
__path__ = __import__('pkgutil').extend_path(__path__, __name__) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
# (C) Datadog, Inc. {year} | ||
# All rights reserved | ||
# Licensed under a 3-clause BSD style license (see LICENSE) | ||
__version__ = '0.0.1' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# (C) Datadog, Inc. {year} | ||
# All rights reserved | ||
# Licensed under a 3-clause BSD style license (see LICENSE) | ||
from .__about__ import __version__ | ||
from .{check_name} import {check_class} | ||
|
||
__all__ = [ | ||
'__version__', | ||
'{check_class}' | ||
] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
init_config: | ||
|
||
instances: | ||
- {{}} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# (C) Datadog, Inc. {year} | ||
# All rights reserved | ||
# Licensed under a 3-clause BSD style license (see LICENSE) | ||
from datadog_checks.checks import AgentCheck | ||
|
||
|
||
class {check_class}(AgentCheck): | ||
def check(self, instance): | ||
pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't actually create a new Integration - it sets up the framework for a new Integration. We should be clear about that here and in the
Options:
below.