diff --git a/tasks/__init__.py b/tasks/__init__.py index 940570d754855..19f7de38f7899 100644 --- a/tasks/__init__.py +++ b/tasks/__init__.py @@ -13,6 +13,7 @@ cluster_agent_cloudfoundry, components, cws_instrumentation, + devcontainer, diff, docker_tasks, docs, @@ -71,7 +72,7 @@ send_unit_tests_stats, test, ) -from tasks.install_tasks import download_tools, install_shellcheck, install_tools +from tasks.install_tasks import download_tools, install_devcontainer_cli, install_shellcheck, install_tools from tasks.junit_tasks import junit_upload from tasks.libs.common.go_workspaces import handle_go_work from tasks.pr_checks import lint_releasenote @@ -105,6 +106,7 @@ ns.add_task(print_default_build_tags) ns.add_task(e2e_tests) ns.add_task(install_shellcheck) +ns.add_task(install_devcontainer_cli) ns.add_task(download_tools) ns.add_task(install_tools) ns.add_task(invoke_unit_tests) @@ -160,6 +162,7 @@ ns.add_collection(owners) ns.add_collection(modules) ns.add_collection(pre_commit) +ns.add_collection(devcontainer) ns.configure( { 'run': { diff --git a/tasks/devcontainer.py b/tasks/devcontainer.py new file mode 100644 index 0000000000000..e81ccbaf089cc --- /dev/null +++ b/tasks/devcontainer.py @@ -0,0 +1,168 @@ +""" +vscode namespaced tags + +Helpers for getting vscode set up nicely +""" +import json +import os +from collections import OrderedDict +from pathlib import Path + +from invoke import task +from invoke.exceptions import Exit +from libs.common.color import color_message + +from tasks.build_tags import build_tags, filter_incompatible_tags, get_build_tags, get_default_build_tags +from tasks.flavor import AgentFlavor + +DEVCONTAINER_DIR = ".devcontainer" +DEVCONTAINER_FILE = "devcontainer.json" +DEVCONTAINER_NAME = "datadog_agent_devcontainer" + + +@task +def setup( + _, + target="agent", + build_include=None, + build_exclude=None, + flavor=AgentFlavor.base.name, + arch='x64', + image='', +): + """ + Generate or Modify devcontainer settings file for this project. + """ + flavor = AgentFlavor[flavor] + if target not in build_tags[flavor]: + print("Must choose a valid target. Valid targets are: \n") + print(f'{", ".join(build_tags[flavor].keys())} \n') + return + + build_include = ( + get_default_build_tags(build=target, arch=arch, flavor=flavor) + if build_include is None + else filter_incompatible_tags(build_include.split(","), arch=arch) + ) + build_exclude = [] if build_exclude is None else build_exclude.split(",") + use_tags = get_build_tags(build_include, build_exclude) + + if not os.path.exists(DEVCONTAINER_DIR): + os.makedirs(DEVCONTAINER_DIR) + + devcontainer = {} + fullpath = os.path.join(DEVCONTAINER_DIR, DEVCONTAINER_FILE) + if os.path.exists(fullpath): + with open(fullpath, "r") as sf: + devcontainer = json.load(sf, object_pairs_hook=OrderedDict) + + local_build_tags = ",".join(use_tags) + + devcontainer["name"] = "Datadog-Agent-DevEnv" + if image: + devcontainer["image"] = image + if devcontainer.get("build"): + del devcontainer["build"] + else: + devcontainer["build"] = { + "dockerfile": "Dockerfile", + "args": {}, + } + if devcontainer.get("image"): + del devcontainer["image"] + devcontainer["runArgs"] = [ + "--cap-add=SYS_PTRACE", + "--security-opt", + "seccomp=unconfined", + "--name", + "datadog_agent_devcontainer", + ] + devcontainer["remoteUser"] = "datadog" + devcontainer["mounts"] = ["source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind,consistency=cached"] + devcontainer["customizations"] = { + "vscode": { + "settings": { + "go.toolsManagement.checkForUpdates": "local", + "go.useLanguageServer": True, + "go.gopath": "/home/datadog/go", + "go.goroot": "/usr/local/go", + "go.buildTags": local_build_tags, + "go.testTags": local_build_tags, + "go.lintTool": "golangci-lint", + "go.lintOnSave": "file", + "go.lintFlags": [ + "--build-tags", + local_build_tags, + "--config", + "/workspaces/datadog-agent/.golangci.yml", + ], + "[go]": { + "editor.formatOnSave": True, + }, + "gopls": {"formatting.local": "github.com/DataDog/datadog-agent"}, + }, + "extensions": ["golang.Go"], + } + } + devcontainer[ + "postStartCommand" + ] = "git config --global --add safe.directory /workspaces/datadog-agent && invoke install-tools && invoke deps" + + with open(fullpath, "w") as sf: + json.dump(devcontainer, sf, indent=4, sort_keys=False, separators=(',', ': ')) + + +@task +def start(ctx, path="."): + """ + Start the devcontainer + """ + if not file().exists(): + print(color_message("No devcontainer settings found. Run `invoke devcontainer.setup` first.", "red")) + raise Exit(code=1) + + if not is_installed(ctx): + print(color_message("Devcontainer CLI is not installed. Run `invoke install-devcontainer-cli` first.", "red")) + raise Exit(code=1) + + ctx.run(f"devcontainer up --workspace-folder {path}") + + +@task +def stop(ctx): + """ + Stop the running devcontainer + """ + if not file().exists(): + print(color_message("No devcontainer settings found. Run `inv devcontainer.setup` first and start it.", "red")) + raise Exit(code=1) + + if not is_up(ctx): + print(color_message("Devcontainer is not running.", "red")) + raise Exit(code=1) + + ctx.run(f"docker kill {DEVCONTAINER_NAME}") + + +@task +def restart(ctx, path="."): + """ + Restart the devcontainer + """ + ctx.run(f"docker rm -f {DEVCONTAINER_NAME}") + start(ctx, path) + + +def file() -> Path: + return Path(DEVCONTAINER_DIR) / DEVCONTAINER_FILE + + +def is_up(ctx) -> bool: + res = ctx.run("docker ps", hide=True, warn=True) + # TODO: it's fragile to just check for the container name, but it's the best we can do for now + return DEVCONTAINER_NAME in res.stdout + + +def is_installed(ctx) -> bool: + res = ctx.run("which devcontainer", hide=True, warn=True) + return res.ok diff --git a/tasks/install_tasks.py b/tasks/install_tasks.py index 4fcaf2c4d4efc..3f4ee9c797c77 100644 --- a/tasks/install_tasks.py +++ b/tasks/install_tasks.py @@ -85,3 +85,11 @@ def install_shellcheck(ctx, version="0.8.0", destination="/usr/local/bin"): ) ctx.run(f"cp \"/tmp/shellcheck-v{version}/shellcheck\" {destination}") ctx.run(f"rm -rf \"/tmp/shellcheck-v{version}\"") + + +@task +def install_devcontainer_cli(ctx): + """ + Install the devcontainer CLI + """ + ctx.run("npm install -g @devcontainers/cli") diff --git a/tasks/vscode.py b/tasks/vscode.py index 5ec716dc62223..b9984c1e73f6b 100644 --- a/tasks/vscode.py +++ b/tasks/vscode.py @@ -5,17 +5,16 @@ """ import json import os -from collections import OrderedDict +from typing import OrderedDict from invoke import task +from libs.common.color import color_message from tasks.build_tags import build_tags, filter_incompatible_tags, get_build_tags, get_default_build_tags from tasks.flavor import AgentFlavor VSCODE_DIR = ".vscode" VSCODE_FILE = "settings.json" -VSCODE_DEVCONTAINER_DIR = ".devcontainer" -VSCODE_DEVCONTAINER_FILE = "devcontainer.json" @task @@ -73,80 +72,16 @@ def setup_devcontainer( """ Generate or Modify devcontainer settings file for this project. """ - flavor = AgentFlavor[flavor] - if target not in build_tags[flavor]: - print("Must choose a valid target. Valid targets are: \n") - print(f'{", ".join(build_tags[flavor].keys())} \n') - return - - build_include = ( - get_default_build_tags(build=target, arch=arch, flavor=flavor) - if build_include is None - else filter_incompatible_tags(build_include.split(","), arch=arch) + from tasks.devcontainer import setup + + print(color_message('This command is deprecated, please use `devcontainer.setup` instead', "orange")) + print("Running `devcontainer.setup`...") + setup( + _, + target=target, + build_include=build_include, + build_exclude=build_exclude, + flavor=flavor, + arch=arch, + image=image, ) - build_exclude = [] if build_exclude is None else build_exclude.split(",") - use_tags = get_build_tags(build_include, build_exclude) - - if not os.path.exists(VSCODE_DEVCONTAINER_DIR): - os.makedirs(VSCODE_DEVCONTAINER_DIR) - - devcontainer = {} - fullpath = os.path.join(VSCODE_DEVCONTAINER_DIR, VSCODE_DEVCONTAINER_FILE) - if os.path.exists(fullpath): - with open(fullpath, "r") as sf: - devcontainer = json.load(sf, object_pairs_hook=OrderedDict) - - local_build_tags = ",".join(use_tags) - - devcontainer["name"] = "Datadog-Agent-DevEnv" - if image: - devcontainer["image"] = image - if devcontainer.get("build"): - del devcontainer["build"] - else: - devcontainer["build"] = { - "dockerfile": "Dockerfile", - "args": {}, - } - if devcontainer.get("image"): - del devcontainer["image"] - devcontainer["runArgs"] = [ - "--cap-add=SYS_PTRACE", - "--security-opt", - "seccomp=unconfined", - "--name", - "datadog_agent_devcontainer", - ] - devcontainer["remoteUser"] = "datadog" - devcontainer["mounts"] = ["source=/var/run/docker.sock,target=/var/run/docker.sock,type=bind,consistency=cached"] - devcontainer["customizations"] = { - "vscode": { - "settings": { - "go.toolsManagement.checkForUpdates": "local", - "go.useLanguageServer": True, - "go.gopath": "/home/datadog/go", - "go.goroot": "/usr/local/go", - "go.buildTags": local_build_tags, - "go.testTags": local_build_tags, - "go.lintTool": "golangci-lint", - "go.lintOnSave": "file", - "go.lintFlags": [ - "--build-tags", - local_build_tags, - "--config", - "/workspaces/datadog-agent/.golangci.yml", - ], - "[go]": { - "editor.formatOnSave": True, - }, - "gopls": {"formatting.local": "github.com/DataDog/datadog-agent"}, - }, - "extensions": ["golang.Go"], - } - } - devcontainer[ - "postStartCommand" - ] = "git config --global --add safe.directory /workspaces/datadog-agent && invoke install-tools && invoke deps" - - with open(fullpath, "w") as sf: - json.dump(devcontainer, sf, indent=4, sort_keys=False, separators=(',', ': '))