bigeye-cli 0.3.60__tar.gz → 0.3.62__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {bigeye_cli-0.3.60 → bigeye_cli-0.3.62}/PKG-INFO +2 -2
- bigeye_cli-0.3.62/bigeye_cli/bigconfig/bigconfig_commands.py +198 -0
- {bigeye_cli-0.3.60 → bigeye_cli-0.3.62}/bigeye_cli/catalog/catalog_commands.py +229 -10
- {bigeye_cli-0.3.60 → bigeye_cli-0.3.62}/bigeye_cli/deltas/deltas_commands.py +88 -43
- {bigeye_cli-0.3.60 → bigeye_cli-0.3.62}/bigeye_cli/exceptions/exceptions.py +10 -0
- {bigeye_cli-0.3.60 → bigeye_cli-0.3.62}/bigeye_cli/lineage/lineage_commands.py +55 -11
- {bigeye_cli-0.3.60 → bigeye_cli-0.3.62}/bigeye_cli/metric/metric_commands.py +3 -3
- bigeye_cli-0.3.62/bigeye_cli/model/github_report.py +74 -0
- {bigeye_cli-0.3.60 → bigeye_cli-0.3.62}/bigeye_cli/model/vendor_report.py +17 -15
- bigeye_cli-0.3.62/bigeye_cli/workspace/workspace_commands.py +575 -0
- {bigeye_cli-0.3.60 → bigeye_cli-0.3.62}/pyproject.toml +2 -2
- bigeye_cli-0.3.60/bigeye_cli/bigconfig/bigconfig_commands.py +0 -161
- bigeye_cli-0.3.60/bigeye_cli/model/github_report.py +0 -58
- bigeye_cli-0.3.60/bigeye_cli/workspace/workspace_commands.py +0 -490
- {bigeye_cli-0.3.60 → bigeye_cli-0.3.62}/LICENSE +0 -0
- {bigeye_cli-0.3.60 → bigeye_cli-0.3.62}/bigeye_cli/CLI_DOCS.md +0 -0
- {bigeye_cli-0.3.60 → bigeye_cli-0.3.62}/bigeye_cli/__init__.py +0 -0
- {bigeye_cli-0.3.60 → bigeye_cli-0.3.62}/bigeye_cli/__main__.py +0 -0
- {bigeye_cli-0.3.60 → bigeye_cli-0.3.62}/bigeye_cli/__version__.py +0 -0
- {bigeye_cli-0.3.60 → bigeye_cli-0.3.62}/bigeye_cli/bigconfig/__init__.py +0 -0
- {bigeye_cli-0.3.60 → bigeye_cli-0.3.62}/bigeye_cli/bigconfig/bigconfig_options.py +0 -0
- {bigeye_cli-0.3.60 → bigeye_cli-0.3.62}/bigeye_cli/catalog/__init__.py +0 -0
- {bigeye_cli-0.3.60 → bigeye_cli-0.3.62}/bigeye_cli/collections/__init__.py +0 -0
- {bigeye_cli-0.3.60 → bigeye_cli-0.3.62}/bigeye_cli/collections/collection_commands.py +0 -0
- {bigeye_cli-0.3.60 → bigeye_cli-0.3.62}/bigeye_cli/configure/__init__.py +0 -0
- {bigeye_cli-0.3.60 → bigeye_cli-0.3.62}/bigeye_cli/configure/configure_commands.py +0 -0
- {bigeye_cli-0.3.60 → bigeye_cli-0.3.62}/bigeye_cli/deltas/__init__.py +0 -0
- {bigeye_cli-0.3.60 → bigeye_cli-0.3.62}/bigeye_cli/enums.py +0 -0
- {bigeye_cli-0.3.60 → bigeye_cli-0.3.62}/bigeye_cli/exceptions/__init__.py +0 -0
- {bigeye_cli-0.3.60 → bigeye_cli-0.3.62}/bigeye_cli/functions.py +0 -0
- {bigeye_cli-0.3.60 → bigeye_cli-0.3.62}/bigeye_cli/global_options.py +0 -0
- {bigeye_cli-0.3.60 → bigeye_cli-0.3.62}/bigeye_cli/issues/__init__.py +0 -0
- {bigeye_cli-0.3.60 → bigeye_cli-0.3.62}/bigeye_cli/issues/issue_commands.py +0 -0
- {bigeye_cli-0.3.60 → bigeye_cli-0.3.62}/bigeye_cli/lineage/__init__.py +0 -0
- {bigeye_cli-0.3.60 → bigeye_cli-0.3.62}/bigeye_cli/metric/__init__.py +0 -0
- {bigeye_cli-0.3.60 → bigeye_cli-0.3.62}/bigeye_cli/model/__init__.py +0 -0
- {bigeye_cli-0.3.60 → bigeye_cli-0.3.62}/bigeye_cli/model/cicd_conf.py +0 -0
- {bigeye_cli-0.3.60 → bigeye_cli-0.3.62}/bigeye_cli/workspace/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: bigeye-cli
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.62
|
|
4
4
|
Summary: Bigeye CLI offers developer tools for maintaining your developer workspace.
|
|
5
5
|
Home-page: https://docs.bigeye.com/docs
|
|
6
6
|
License: Proprietary
|
|
@@ -13,7 +13,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.10
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.11
|
|
15
15
|
Requires-Dist: PyGithub (>=1.57,<2.0)
|
|
16
|
-
Requires-Dist: bigeye-sdk (>=0.4.
|
|
16
|
+
Requires-Dist: bigeye-sdk (>=0.4.83,<0.5.0)
|
|
17
17
|
Requires-Dist: click (>=8.0.4,<9.0.0)
|
|
18
18
|
Requires-Dist: rich (>=12.4.4,<13.0.0)
|
|
19
19
|
Description-Content-Type: text/markdown
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import Optional, List
|
|
4
|
+
|
|
5
|
+
import typer
|
|
6
|
+
|
|
7
|
+
from bigeye_cli.functions import cli_client_factory
|
|
8
|
+
from bigeye_cli import global_options
|
|
9
|
+
from bigeye_cli.bigconfig import bigconfig_options
|
|
10
|
+
from bigeye_cli.model.github_report import GitHubReport
|
|
11
|
+
from bigeye_sdk.controller.metric_controller import MetricController
|
|
12
|
+
from bigeye_sdk.controller.metric_suite_controller import MetricSuiteController
|
|
13
|
+
from bigeye_sdk.generated.com.bigeye.models.generated import MetricInfoList
|
|
14
|
+
from bigeye_sdk.model.protobuf_message_facade import SimpleCollection
|
|
15
|
+
from bigeye_sdk.log import get_logger
|
|
16
|
+
|
|
17
|
+
log = get_logger(__file__)
|
|
18
|
+
|
|
19
|
+
app = typer.Typer(no_args_is_help=True, help="Bigconfig Commands for Bigeye CLI")
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
File should contain commands relating to deploying Bigconfig files.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@app.command()
|
|
27
|
+
def plan(
|
|
28
|
+
bigeye_conf: str = global_options.bigeye_conf,
|
|
29
|
+
config_file: str = global_options.config_file,
|
|
30
|
+
workspace: str = global_options.workspace,
|
|
31
|
+
input_path: Optional[List[str]] = bigconfig_options.input_path,
|
|
32
|
+
output_path: str = bigconfig_options.output_path,
|
|
33
|
+
purge_source_names: Optional[List[str]] = bigconfig_options.purge_source_names,
|
|
34
|
+
purge_all_sources: bool = bigconfig_options.purge_all_sources,
|
|
35
|
+
no_queue: bool = bigconfig_options.no_queue,
|
|
36
|
+
recursive: bool = bigconfig_options.recursive,
|
|
37
|
+
strict_mode: bool = bigconfig_options.strict_mode,
|
|
38
|
+
cicd: bool = typer.Option(
|
|
39
|
+
False,
|
|
40
|
+
"--cicd_report",
|
|
41
|
+
"-cicd",
|
|
42
|
+
help="Add the report to a pull request. (Github only)",
|
|
43
|
+
),
|
|
44
|
+
):
|
|
45
|
+
"""Executes a plan for purging sources or processing bigconfig files in the input path/current
|
|
46
|
+
working directory."""
|
|
47
|
+
|
|
48
|
+
client = cli_client_factory(bigeye_conf, config_file, workspace)
|
|
49
|
+
mc = MetricSuiteController(client=client)
|
|
50
|
+
|
|
51
|
+
# Resolve args to first true or first non None values
|
|
52
|
+
if not input_path:
|
|
53
|
+
input_path = client.config.bigconfig_input_path or [Path.cwd()]
|
|
54
|
+
if not output_path:
|
|
55
|
+
output_path = client.config.bigconfig_output_path or Path.cwd()
|
|
56
|
+
strict_mode = strict_mode or client.config.bigconfig_strict_mode
|
|
57
|
+
|
|
58
|
+
if purge_source_names or purge_all_sources:
|
|
59
|
+
report = mc.execute_purge(
|
|
60
|
+
purge_source_names=purge_source_names,
|
|
61
|
+
purge_all_sources=purge_all_sources,
|
|
62
|
+
output_path=output_path,
|
|
63
|
+
apply=False,
|
|
64
|
+
)
|
|
65
|
+
else:
|
|
66
|
+
report = mc.execute_bigconfig(
|
|
67
|
+
input_path=input_path,
|
|
68
|
+
output_path=output_path,
|
|
69
|
+
apply=False,
|
|
70
|
+
recursive=recursive,
|
|
71
|
+
no_queue=no_queue,
|
|
72
|
+
strict_mode=strict_mode,
|
|
73
|
+
)
|
|
74
|
+
if cicd:
|
|
75
|
+
gh_report = GitHubReport(github_token=os.environ["GITHUB_TOKEN"])
|
|
76
|
+
gh_report.publish_bigconfig_plan(plan=report)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@app.command()
|
|
80
|
+
def apply(
|
|
81
|
+
bigeye_conf: str = global_options.bigeye_conf,
|
|
82
|
+
config_file: str = global_options.config_file,
|
|
83
|
+
workspace: str = global_options.workspace,
|
|
84
|
+
input_path: Optional[List[str]] = bigconfig_options.input_path,
|
|
85
|
+
output_path: str = bigconfig_options.output_path,
|
|
86
|
+
purge_source_names: Optional[List[str]] = bigconfig_options.purge_source_names,
|
|
87
|
+
purge_all_sources: bool = bigconfig_options.purge_all_sources,
|
|
88
|
+
no_queue: bool = bigconfig_options.no_queue,
|
|
89
|
+
recursive: bool = bigconfig_options.recursive,
|
|
90
|
+
strict_mode: bool = bigconfig_options.strict_mode,
|
|
91
|
+
auto_approve: bool = bigconfig_options.auto_approve,
|
|
92
|
+
):
|
|
93
|
+
"""Applies a purge of deployed metrics or applies Bigconfig files from the input path/current working directory to
|
|
94
|
+
the Bigeye workspace."""
|
|
95
|
+
|
|
96
|
+
client = cli_client_factory(bigeye_conf, config_file, workspace)
|
|
97
|
+
mc = MetricSuiteController(client=client)
|
|
98
|
+
|
|
99
|
+
# Resolve args to first true or first non None values
|
|
100
|
+
if not input_path:
|
|
101
|
+
input_path = client.config.bigconfig_input_path or [Path.cwd()]
|
|
102
|
+
if not output_path:
|
|
103
|
+
output_path = client.config.bigconfig_output_path or Path.cwd()
|
|
104
|
+
strict_mode = strict_mode or client.config.bigconfig_strict_mode
|
|
105
|
+
auto_approve = auto_approve or client.config.bigconfig_auto_approve
|
|
106
|
+
|
|
107
|
+
if purge_source_names or purge_all_sources:
|
|
108
|
+
mc.execute_purge(
|
|
109
|
+
purge_source_names=purge_source_names,
|
|
110
|
+
purge_all_sources=purge_all_sources,
|
|
111
|
+
output_path=output_path,
|
|
112
|
+
apply=True,
|
|
113
|
+
)
|
|
114
|
+
else:
|
|
115
|
+
mc.execute_bigconfig(
|
|
116
|
+
input_path=input_path,
|
|
117
|
+
output_path=output_path,
|
|
118
|
+
apply=True,
|
|
119
|
+
no_queue=no_queue,
|
|
120
|
+
recursive=recursive,
|
|
121
|
+
strict_mode=strict_mode,
|
|
122
|
+
auto_approve=auto_approve,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
@app.command()
|
|
127
|
+
def export(
|
|
128
|
+
bigeye_conf: str = global_options.bigeye_conf,
|
|
129
|
+
config_file: str = global_options.config_file,
|
|
130
|
+
workspace: str = global_options.workspace,
|
|
131
|
+
output_path: str = bigconfig_options.output_path,
|
|
132
|
+
collection_id: int = typer.Option(
|
|
133
|
+
None, "--collection_id", "-cid", help="Collection ID of metrics to be exported."
|
|
134
|
+
),
|
|
135
|
+
table_id: int = typer.Option(
|
|
136
|
+
None, "--table_id", "-tid", help="Table ID of metrics to be exported."
|
|
137
|
+
),
|
|
138
|
+
metric_ids: List[int] = typer.Option(
|
|
139
|
+
None, "--metric_id", "-mid", help="Metric ID of metrics to be exported."
|
|
140
|
+
),
|
|
141
|
+
):
|
|
142
|
+
"""Exports existing metrics into a valid Bigconfig file."""
|
|
143
|
+
|
|
144
|
+
client = cli_client_factory(bigeye_conf, config_file, workspace)
|
|
145
|
+
mc = MetricController(client=client)
|
|
146
|
+
collection: SimpleCollection = SimpleCollection(
|
|
147
|
+
name="Sample", description="SDK Generated"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
# Require at least 1 option to be specified
|
|
151
|
+
if not collection_id and not table_id and not metric_ids:
|
|
152
|
+
raise typer.BadParameter(
|
|
153
|
+
"At least 1 option must be provided for bigconfig export. Please provide the "
|
|
154
|
+
"collection id (-cid), table id (-tid), or list of metric ids (-mid). "
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Get existing collection if passed
|
|
158
|
+
if collection_id:
|
|
159
|
+
collection: SimpleCollection = SimpleCollection.from_datawatch_object(
|
|
160
|
+
client.get_collection(collection_id=collection_id).collection
|
|
161
|
+
)
|
|
162
|
+
metric_ids.extend(collection.metric_ids)
|
|
163
|
+
|
|
164
|
+
# Get metrics from table if passed
|
|
165
|
+
if table_id:
|
|
166
|
+
metrics_from_table = client.get_metric_info_batch_post(
|
|
167
|
+
table_ids=[table_id]
|
|
168
|
+
).metrics
|
|
169
|
+
metric_ids.extend([m.metric_configuration.id for m in metrics_from_table])
|
|
170
|
+
if not metrics_from_table:
|
|
171
|
+
raise typer.BadParameter(
|
|
172
|
+
f"No metrics found for given table id. Please check arguments and try again."
|
|
173
|
+
)
|
|
174
|
+
if not collection_id:
|
|
175
|
+
collection.name = metrics_from_table[0].metric_metadata.dataset_name
|
|
176
|
+
|
|
177
|
+
# Pull metric information
|
|
178
|
+
metrics: MetricInfoList = client.get_metric_info_batch_post(metric_ids=metric_ids)
|
|
179
|
+
|
|
180
|
+
if not output_path:
|
|
181
|
+
output_path = client.config.bigconfig_output_path or Path.cwd()
|
|
182
|
+
|
|
183
|
+
if not metrics.metrics:
|
|
184
|
+
raise typer.BadParameter(
|
|
185
|
+
"No metrics found for given arguments. Please check again."
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
bigconfig = mc.metric_info_to_bigconfig(metric_info=metrics, collection=collection)
|
|
189
|
+
|
|
190
|
+
file_name = collection.name.lower().replace(" ", "_")
|
|
191
|
+
output = bigconfig.save(
|
|
192
|
+
output_path=output_path,
|
|
193
|
+
default_file_name=f"{file_name}.bigconfig",
|
|
194
|
+
custom_formatter=bigconfig.format_bigconfig_export,
|
|
195
|
+
)
|
|
196
|
+
typer.secho(
|
|
197
|
+
f"\nSample bigconfig file generated at: {output}\n", fg="green", bold=True
|
|
198
|
+
)
|
|
@@ -1,23 +1,37 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import logging
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
from collections import defaultdict
|
|
7
|
+
from datetime import datetime
|
|
3
8
|
from pathlib import Path
|
|
4
9
|
from typing import Optional, List, Dict
|
|
5
10
|
|
|
6
11
|
import typer
|
|
12
|
+
import yaml
|
|
7
13
|
from rich.progress import track
|
|
8
14
|
|
|
15
|
+
from bigeye_cli.exceptions.exceptions import MissingArgumentException
|
|
9
16
|
from bigeye_sdk.client.enum import Method
|
|
10
17
|
|
|
11
18
|
from bigeye_cli.enums import OPS_METRICS
|
|
12
19
|
from bigeye_cli import global_options
|
|
13
20
|
from bigeye_sdk.functions.dbt import parse_dbt_manifest
|
|
14
21
|
from bigeye_sdk.functions.file_functs import read_json_file
|
|
22
|
+
from bigeye_sdk.functions.notifications import (
|
|
23
|
+
send_email,
|
|
24
|
+
post_slack_snippet,
|
|
25
|
+
post_webhook_request,
|
|
26
|
+
)
|
|
15
27
|
from bigeye_sdk.functions.search_and_match_functions import fuzzy_match
|
|
16
28
|
from bigeye_sdk.model.dbt_manifest import DbtManifest
|
|
17
29
|
from bigeye_sdk.model.protobuf_enum_facade import (
|
|
18
30
|
MetricStatus,
|
|
19
31
|
SimpleMetricCategory,
|
|
20
32
|
SimpleMetricTemplateFieldType,
|
|
33
|
+
SimpleSchemaChangeOperation,
|
|
34
|
+
SimpleCatalogEntityType,
|
|
21
35
|
)
|
|
22
36
|
from bigeye_sdk.functions.aws import get_secret
|
|
23
37
|
from bigeye_sdk.generated.com.bigeye.models.generated import (
|
|
@@ -33,6 +47,8 @@ from bigeye_sdk.generated.com.bigeye.models.generated import (
|
|
|
33
47
|
Table,
|
|
34
48
|
User,
|
|
35
49
|
OwnableType,
|
|
50
|
+
SchemaChangeOperation,
|
|
51
|
+
SchemaChange,
|
|
36
52
|
)
|
|
37
53
|
from bigeye_sdk.log import get_logger
|
|
38
54
|
from bigeye_cli.functions import (
|
|
@@ -44,6 +60,7 @@ from bigeye_cli.functions import (
|
|
|
44
60
|
)
|
|
45
61
|
from bigeye_sdk.functions.metric_functions import has_auto_threshold
|
|
46
62
|
from bigeye_sdk.functions.core_py_functs import int_enum_enum_list_joined
|
|
63
|
+
from bigeye_sdk.model.schema_change import SourceSchemaChange
|
|
47
64
|
|
|
48
65
|
# create logger
|
|
49
66
|
log = get_logger(__file__)
|
|
@@ -430,11 +447,11 @@ def add_source(
|
|
|
430
447
|
|
|
431
448
|
if secret_name:
|
|
432
449
|
secret = get_secret(secret_name, secret_region_name)
|
|
433
|
-
source_config =
|
|
450
|
+
source_config = yaml.safe_load(secret["SecretString"])
|
|
434
451
|
else:
|
|
435
452
|
log.info(f"Loading source config file: {source_catalog_config_file}")
|
|
436
|
-
with open(source_catalog_config_file) as
|
|
437
|
-
source_config =
|
|
453
|
+
with open(source_catalog_config_file) as fin:
|
|
454
|
+
source_config = yaml.safe_load(fin)
|
|
438
455
|
|
|
439
456
|
db_type = source_config["database_type"].upper()
|
|
440
457
|
source_config["database_type"] = WarehouseType[f"DATABASE_TYPE_{db_type}"]
|
|
@@ -803,26 +820,26 @@ def upsert_template(
|
|
|
803
820
|
config_file: str = global_options.config_file,
|
|
804
821
|
workspace: str = global_options.workspace,
|
|
805
822
|
source_name: str = typer.Option(
|
|
806
|
-
|
|
823
|
+
None,
|
|
807
824
|
"--source_name",
|
|
808
825
|
"-sn",
|
|
809
826
|
help="The name of the source where the template will be defined",
|
|
810
827
|
),
|
|
811
828
|
template_name: str = typer.Option(
|
|
812
|
-
|
|
829
|
+
None, "--name", "-tn", help="The user defined name of the template"
|
|
813
830
|
),
|
|
814
831
|
template_body: str = typer.Option(
|
|
815
|
-
|
|
832
|
+
None, "--body", "-tb", help="The string to define the template"
|
|
816
833
|
),
|
|
817
834
|
return_type: SimpleMetricTemplateFieldType = typer.Option(
|
|
818
|
-
|
|
835
|
+
None,
|
|
819
836
|
"--returns",
|
|
820
837
|
"-rt",
|
|
821
838
|
help="The data type returned by the template; i.e. NUMERIC, BOOLEAN",
|
|
822
839
|
),
|
|
823
840
|
# TODO: Update to List[Tuple[str, SimpleMetricTemplateParameterType]] when supported by typer.
|
|
824
841
|
parameters: List[str] = typer.Option(
|
|
825
|
-
|
|
842
|
+
None,
|
|
826
843
|
"--params",
|
|
827
844
|
"-p",
|
|
828
845
|
help="A list of key/value pairs for parameters; ex. -p column=COLUMN_REFERENCE -p table=STRING",
|
|
@@ -833,11 +850,27 @@ def upsert_template(
|
|
|
833
850
|
"-id",
|
|
834
851
|
help="The template ID (Only required when updating a template).",
|
|
835
852
|
),
|
|
853
|
+
template_file: Optional[str] = typer.Option(
|
|
854
|
+
None,
|
|
855
|
+
"--template_file",
|
|
856
|
+
"-tf",
|
|
857
|
+
help="A file containing a template definition.",
|
|
858
|
+
)
|
|
836
859
|
):
|
|
837
860
|
"""Create or update a template for a source."""
|
|
838
861
|
# TODO: Lookup template ID, once template names are unique
|
|
839
862
|
client = cli_client_factory(bigeye_conf, config_file, workspace)
|
|
840
863
|
|
|
864
|
+
if template_file:
|
|
865
|
+
with open(template_file) as fin:
|
|
866
|
+
template = yaml.safe_load(stream=fin)
|
|
867
|
+
source_name = template["source_name"]
|
|
868
|
+
template_name = template["name"]
|
|
869
|
+
template_body = template["body"]
|
|
870
|
+
return_type = template["return_type"]
|
|
871
|
+
parameters = template["parameters"]
|
|
872
|
+
|
|
873
|
+
|
|
841
874
|
source: Source = [s for s in client.get_sources().sources if s.name == source_name][
|
|
842
875
|
0
|
|
843
876
|
]
|
|
@@ -1089,7 +1122,10 @@ def ingest_dbt_core_run_info(
|
|
|
1089
1122
|
help="The path to the target directory created with dbt run, build, and test commands.",
|
|
1090
1123
|
),
|
|
1091
1124
|
project_name: Optional[str] = typer.Option(
|
|
1092
|
-
None,
|
|
1125
|
+
None,
|
|
1126
|
+
"--project_name",
|
|
1127
|
+
"-pn",
|
|
1128
|
+
help="The name of the dbt project. Will attempt to parse from metadata section of manifest.json, if not present",
|
|
1093
1129
|
),
|
|
1094
1130
|
job_name: Optional[str] = typer.Option(
|
|
1095
1131
|
None, "--job_name", "-jn", help="The name of the dbt job."
|
|
@@ -1122,9 +1158,192 @@ def ingest_dbt_core_run_info(
|
|
|
1122
1158
|
run_results_json=json.dumps(run_results_json),
|
|
1123
1159
|
project_url=project_url,
|
|
1124
1160
|
job_url=job_url,
|
|
1125
|
-
job_run_url=job_run_url
|
|
1161
|
+
job_run_url=job_run_url,
|
|
1126
1162
|
)
|
|
1127
1163
|
|
|
1128
1164
|
log.info(
|
|
1129
1165
|
f"The following response was returned:\n\n\t{response.to_json(indent=True)}"
|
|
1130
1166
|
)
|
|
1167
|
+
|
|
1168
|
+
|
|
1169
|
+
@app.command()
|
|
1170
|
+
def track_schema_changes(
|
|
1171
|
+
bigeye_conf: str = global_options.bigeye_conf,
|
|
1172
|
+
config_file: str = global_options.config_file,
|
|
1173
|
+
workspace: str = global_options.workspace,
|
|
1174
|
+
source_names: List[str] = typer.Option(
|
|
1175
|
+
[],
|
|
1176
|
+
"--source",
|
|
1177
|
+
"-src",
|
|
1178
|
+
help="A list of source names to track. (None will track changes from all sources)",
|
|
1179
|
+
),
|
|
1180
|
+
since: Optional[str] = typer.Option(
|
|
1181
|
+
"1970-01-01",
|
|
1182
|
+
"--since",
|
|
1183
|
+
"-s",
|
|
1184
|
+
help="Track schema changes from this date. (Formatted as yyyy-MM-dd)",
|
|
1185
|
+
),
|
|
1186
|
+
frequency: Optional[int] = typer.Option(
|
|
1187
|
+
0,
|
|
1188
|
+
"--frequency",
|
|
1189
|
+
"-f",
|
|
1190
|
+
help="Every n hours to check for schema changes. (None will only run the check once)",
|
|
1191
|
+
),
|
|
1192
|
+
change_types: List[SimpleSchemaChangeOperation] = typer.Option(
|
|
1193
|
+
[
|
|
1194
|
+
SimpleSchemaChangeOperation.DELETED,
|
|
1195
|
+
SimpleSchemaChangeOperation.CREATED,
|
|
1196
|
+
SimpleSchemaChangeOperation.TYPE_CHANGED,
|
|
1197
|
+
],
|
|
1198
|
+
"--change_type",
|
|
1199
|
+
"-ct",
|
|
1200
|
+
help="The types of changes to track; i.e additions, changes, deletions. (None will track all types)",
|
|
1201
|
+
),
|
|
1202
|
+
notification_channels: List[str] = typer.Option(
|
|
1203
|
+
[],
|
|
1204
|
+
"--channel",
|
|
1205
|
+
"-nc",
|
|
1206
|
+
help="A list of notification channels; e.g. -nc email@mydomain.com -nc #slack_channel -nc https://www.webhook.com",
|
|
1207
|
+
),
|
|
1208
|
+
smtp_server: Optional[str] = typer.Option(
|
|
1209
|
+
None, "--server", "-smtp", help="The server to send emails."
|
|
1210
|
+
),
|
|
1211
|
+
smtp_port: Optional[int] = typer.Option(
|
|
1212
|
+
587, "--port", "-p", help="The server port to send emails."
|
|
1213
|
+
),
|
|
1214
|
+
slack_token: Optional[str] = typer.Option(
|
|
1215
|
+
None,
|
|
1216
|
+
"--token",
|
|
1217
|
+
"-t",
|
|
1218
|
+
help="The token to utilize the Slack web client. (Will attempt to use environment variable SLACK_BOT_TOKEN, if not set)",
|
|
1219
|
+
),
|
|
1220
|
+
):
|
|
1221
|
+
"""Track schema changes for sources on demand or every n hour(s) and notify specified channels.
|
|
1222
|
+
Environment variables SMTP_USER and SMTP_PASSWORD MUST be set if using email notification.
|
|
1223
|
+
"""
|
|
1224
|
+
|
|
1225
|
+
hour_as_secs = 3600
|
|
1226
|
+
no_reply_at_bigeye = "noreply@bigeye.com"
|
|
1227
|
+
|
|
1228
|
+
try:
|
|
1229
|
+
date_epoch = datetime.strptime(since, "%Y-%m-%d").timestamp()
|
|
1230
|
+
except ValueError as e:
|
|
1231
|
+
log.error(
|
|
1232
|
+
f"Error converting date {since}. Please ensure it formatted as yyyy-MM-dd."
|
|
1233
|
+
)
|
|
1234
|
+
log.info("The process will exit.")
|
|
1235
|
+
raise e
|
|
1236
|
+
|
|
1237
|
+
emails = []
|
|
1238
|
+
slack_channels = []
|
|
1239
|
+
webhooks = []
|
|
1240
|
+
for nc in notification_channels:
|
|
1241
|
+
if "@" in nc:
|
|
1242
|
+
emails.append(nc)
|
|
1243
|
+
elif nc.startswith("#"):
|
|
1244
|
+
slack_channels.append(nc)
|
|
1245
|
+
elif nc.startswith("http"):
|
|
1246
|
+
webhooks.append(nc)
|
|
1247
|
+
else:
|
|
1248
|
+
log.error(f"Unrecognized notification channel: {nc}")
|
|
1249
|
+
log.warning(f"Schema changes will not be sent to {nc}")
|
|
1250
|
+
|
|
1251
|
+
if emails:
|
|
1252
|
+
if not smtp_server:
|
|
1253
|
+
raise MissingArgumentException(
|
|
1254
|
+
"Email listed as notification channel but no email server specified to send one."
|
|
1255
|
+
)
|
|
1256
|
+
if not os.environ.get("SMTP_USER", None):
|
|
1257
|
+
raise MissingArgumentException(
|
|
1258
|
+
"Email listed as notification channel but no value set for environment variable SMTP_USER."
|
|
1259
|
+
)
|
|
1260
|
+
if not os.environ.get("SMTP_PASSWORD", None):
|
|
1261
|
+
raise MissingArgumentException(
|
|
1262
|
+
"Email listed as notification channel but no value set for environment variable SMTP_PASSWORD."
|
|
1263
|
+
)
|
|
1264
|
+
|
|
1265
|
+
if slack_channels:
|
|
1266
|
+
if not slack_token and not os.environ.get("SLACK_BOT_TOKEN", None):
|
|
1267
|
+
raise MissingArgumentException(
|
|
1268
|
+
"Slack listed as notification channel but no value set for slack token or environment variable SLACK_BOT_TOKEN."
|
|
1269
|
+
)
|
|
1270
|
+
if not slack_token:
|
|
1271
|
+
slack_token = os.environ.get("SLACK_BOT_TOKEN")
|
|
1272
|
+
|
|
1273
|
+
change_types = [ct.to_datawatch_object() for ct in change_types]
|
|
1274
|
+
|
|
1275
|
+
client = cli_client_factory(bigeye_conf, config_file, workspace)
|
|
1276
|
+
|
|
1277
|
+
sources = client.get_sources_by_name(source_names=source_names)
|
|
1278
|
+
while True:
|
|
1279
|
+
changes_by_source: Dict[str, List[SchemaChange]] = {}
|
|
1280
|
+
for s in sources.values():
|
|
1281
|
+
|
|
1282
|
+
changes = []
|
|
1283
|
+
since_time = datetime.fromtimestamp(timestamp=date_epoch).strftime(
|
|
1284
|
+
"%m/%d/%Y, %H:%M"
|
|
1285
|
+
)
|
|
1286
|
+
log.info(f"Retrieving changes for source {s.name} since {since_time}")
|
|
1287
|
+
changes.extend(
|
|
1288
|
+
[
|
|
1289
|
+
c
|
|
1290
|
+
for c in client.fetch_schema_changes(source_id=s.id, page_size=1000)
|
|
1291
|
+
if c.change_type in change_types and c.detected_at >= date_epoch
|
|
1292
|
+
]
|
|
1293
|
+
)
|
|
1294
|
+
if changes:
|
|
1295
|
+
changes_by_source[s.name] = changes
|
|
1296
|
+
else:
|
|
1297
|
+
continue
|
|
1298
|
+
|
|
1299
|
+
source_changes = [
|
|
1300
|
+
SourceSchemaChange(name=k, changes=v) for k, v in changes_by_source.items()
|
|
1301
|
+
]
|
|
1302
|
+
|
|
1303
|
+
all_alerts = ""
|
|
1304
|
+
alert_webhook = {}
|
|
1305
|
+
|
|
1306
|
+
if source_changes:
|
|
1307
|
+
|
|
1308
|
+
for change in source_changes:
|
|
1309
|
+
all_alerts += f"<br><br>{change.format_message()}</br></br>"
|
|
1310
|
+
alert_webhook.update(change.format_webhook())
|
|
1311
|
+
|
|
1312
|
+
|
|
1313
|
+
if emails:
|
|
1314
|
+
send_email(
|
|
1315
|
+
server_name=smtp_server,
|
|
1316
|
+
port=smtp_port,
|
|
1317
|
+
user_name=os.environ.get("SMTP_USER"),
|
|
1318
|
+
password=os.environ.get("SMTP_PASSWORD"),
|
|
1319
|
+
sender=no_reply_at_bigeye,
|
|
1320
|
+
subject=f"Schema changes detected in Bigeye since {since_time}",
|
|
1321
|
+
recipient=emails,
|
|
1322
|
+
body=all_alerts,
|
|
1323
|
+
)
|
|
1324
|
+
|
|
1325
|
+
if slack_channels:
|
|
1326
|
+
post_slack_snippet(
|
|
1327
|
+
slack_token=slack_token,
|
|
1328
|
+
slack_channel=slack_channels,
|
|
1329
|
+
body=alert_webhook,
|
|
1330
|
+
text=f"*Bigeye schema changes since {since_time}*",
|
|
1331
|
+
title="Bigeye Schema Changes",
|
|
1332
|
+
)
|
|
1333
|
+
|
|
1334
|
+
if webhooks:
|
|
1335
|
+
data = {
|
|
1336
|
+
"name": "Bigeye Schema Changes",
|
|
1337
|
+
"since": since_time,
|
|
1338
|
+
"alerts": alert_webhook,
|
|
1339
|
+
}
|
|
1340
|
+
post_webhook_request(webhook_url=webhooks, data=data)
|
|
1341
|
+
|
|
1342
|
+
if frequency:
|
|
1343
|
+
log.info(f"Continuing to track schema changes.")
|
|
1344
|
+
log.info(f"The process will run again in {frequency} hours.")
|
|
1345
|
+
date_epoch = datetime.now().timestamp()
|
|
1346
|
+
time.sleep(frequency * hour_as_secs)
|
|
1347
|
+
else:
|
|
1348
|
+
log.info("No frequency specified. The process will exit.")
|
|
1349
|
+
sys.exit(0)
|