feat(discord_gcp_alerts): add discord gcp alerts package (#354)

This commit is contained in:
Felix Angelov
2023-04-24 10:36:05 -05:00
committed by GitHub
parent 1dc84429e1
commit ed22a14ffb
19 changed files with 1407 additions and 0 deletions
@@ -0,0 +1,62 @@
name: Deploy Discord GCP Alerts
on:
workflow_dispatch:
push:
branches:
- main
paths:
- .github/workflows/deploy_discord_gcp_alerts.yaml
- "packages/discord_gcp_alerts/**"
env:
PROJECT_ID: code-push-prod
SERVICE: discord-gcp-alerts
REGION: us-central1
jobs:
deploy:
runs-on: ubuntu-latest
defaults:
run:
working-directory: packages/discord_gcp_alerts
name: ☁️ Discord GCP Alerts
steps:
- name: 📚 Git Checkout
uses: actions/checkout@v3
with:
submodules: recursive
- uses: dart-lang/setup-dart@v1
- name: Setup Cloud SDK
uses: google-github-actions/setup-gcloud@v0.2.0
with:
project_id: ${{ env.PROJECT_ID }}
service_account_key: ${{ secrets.CLOUD_RUN_SA_PROD }}
export_default_credentials: true
- name: Authorize Docker Push
run: gcloud auth configure-docker
- name: Build and Push Container
run: |-
docker build -t gcr.io/${{ env.PROJECT_ID }}/${{ env.SERVICE }}:${{ github.sha }} .
docker push gcr.io/${{ env.PROJECT_ID }}/${{ env.SERVICE }}:${{ github.sha }}
- name: Deploy to Cloud Run
id: deploy
uses: google-github-actions/deploy-cloudrun@v0.4.0
with:
service: ${{ env.SERVICE }}
image: gcr.io/${{ env.PROJECT_ID }}/${{ env.SERVICE }}:${{ github.sha }}
region: ${{ env.REGION }}
- name: Show Output
run: echo ${{ steps.deploy.outputs.url }}
- name: Ping
run: curl "${{ steps.deploy.outputs.url }}"
+4
View File
@@ -35,6 +35,10 @@ jobs:
- ./.github/workflows/main.yaml
- ./.github/actions/dart_package
- packages/artifact_proxy/**
discord_gcp_alerts:
- ./.github/workflows/main.yaml
- ./.github/actions/dart_package
- packages/discord_gcp_alerts/**
shorebird_cli:
- ./.github/workflows/main.yaml
- ./.github/actions/dart_package
+1
View File
@@ -28,6 +28,7 @@ This repository is a monorepo containing the following packages:
| [shorebird_cli](packages/shorebird_cli/README.md) | Command-line which allows developers to interact with various Shorebird services |
| [shorebird_code_push_client](packages/shorebird_code_push_client/README.md) | Dart library which allows Dart applications to interact with the ShoreBird CodePush API |
| [shorebird_code_push_protocol](packages/shorebird_code_push_protocol/README.md) | Dart library which contains common interfaces used by Shorebird CodePush |
| [discord_gcp_alerts](packages/discord_gcp_alerts/README.md) | Dart server which forwards GCP alerts to Discord |
| [jwt](packages/jwt/README.md) | Dart library for verifying Json Web Tokens |
| [updater](updater/README.md) | Rust library which handles the CodePush logic and does the real update work |
+10
View File
@@ -0,0 +1,10 @@
.dockerignore
Dockerfile
coverage/
build/
.dart_tool/
.git/
.github/
.gitignore
.idea/
.packages
+6
View File
@@ -0,0 +1,6 @@
# https://dart.dev/guides/libraries/private-files
# Created by `dart pub`
.dart_tool/
# Test related files
coverage/
+21
View File
@@ -0,0 +1,21 @@
# Use latest stable channel SDK.
FROM dart:stable AS build
# Resolve app dependencies.
WORKDIR /app
COPY pubspec.* ./
RUN dart pub get
# Copy app source code (except anything in .dockerignore) and AOT compile app.
COPY . .
RUN dart compile exe bin/server.dart -o bin/server
# Build minimal serving image from AOT-compiled `/server`
# and the pre-built AOT-runtime in the `/runtime/` directory of the base image.
FROM scratch
COPY --from=build /runtime/ /
COPY --from=build /app/bin/server /app/bin/
# Start server.
EXPOSE 8080
CMD ["/app/bin/server"]
+7
View File
@@ -0,0 +1,7 @@
# Discord GCP Alerts
A simple server which proxies GCP Alert notifications to a Discord webhook.
```
WEBHOOK_URL=<DISCORD_WEBHOOK> dart bin/server.dart
```
@@ -0,0 +1 @@
include: package:very_good_analysis/analysis_options.4.0.0.yaml
@@ -0,0 +1,21 @@
// ignore_for_file: avoid_print
import 'dart:io';
import 'package:discord_gcp_alerts/discord_gcp_alerts.dart';
import 'package:shelf/shelf_io.dart';
import 'package:shelf_router/shelf_router.dart';
void main() async {
final webhookUrl = Platform.environment['WEBHOOK_URL'];
if (webhookUrl == null) {
print('WEBHOOK_URL environment variable is not set.');
exit(1);
}
final handler = gcpAlertHandler(webhookUrl: webhookUrl);
final router = Router()..post('/', handler);
final port = int.parse(Platform.environment['PORT'] ?? '8080');
final ip = InternetAddress.anyIPv4;
final server = await serve(router.call, ip, port);
print('Server listening on port ${server.port}');
}
+16
View File
@@ -0,0 +1,16 @@
targets:
$default:
builders:
source_gen|combining_builder:
options:
ignore_for_file:
- implicit_dynamic_parameter
- require_trailing_commas
- cast_nullable_to_non_nullable
- lines_longer_than_80_chars
- unnecessary_lambdas
json_serializable:
options:
field_rename: snake
checked: true
explicit_to_json: true
@@ -0,0 +1,2 @@
export 'src/gcp_alert_handler.dart';
export 'src/models/models.dart';
@@ -0,0 +1,47 @@
import 'dart:convert';
import 'dart:io';
import 'package:discord_gcp_alerts/discord_gcp_alerts.dart';
import 'package:http/http.dart' as http;
import 'package:shelf/shelf.dart';
/// A handler which notifies a Discord channel of a GCP alert.
Handler gcpAlertHandler({required String webhookUrl, http.Client? client}) {
return (Request req) async {
final body = await req.readAsString();
final notification = GCPAlert.fromJson(
json.decode(body) as Map<String, dynamic>,
);
final incident = notification.incident;
final summary = incident?.summary;
final state = incident?.state;
final url = incident?.url;
final resource = incident?.resourceName;
final conditionName = incident?.conditionName;
final discordPayload = {
'embeds': [
{
'title': 'GCP Alert',
'description': summary,
'url': url,
'color': 0xFF0000,
'fields': [
{'name': 'State', 'value': state, 'inline': true},
{'name': 'Resource', 'value': resource, 'inline': true},
{'name': 'Condition', 'value': conditionName, 'inline': true},
]
}
],
'attachments': const <dynamic>[]
};
final post = client?.post ?? http.post;
final response = await post(
Uri.parse(webhookUrl),
headers: {HttpHeaders.contentTypeHeader: ContentType.json.value},
body: json.encode(discordPayload),
);
return Response(response.statusCode, body: response.body);
};
}
@@ -0,0 +1,246 @@
import 'package:json_annotation/json_annotation.dart';
part 'gcp_alert.g.dart';
/// {@template gcp_alert}
/// A GCP alert notification.
/// https://console.cloud.google.com/monitoring/alerting/notifications
///
/// For sample alerts, see https://cloud.google.com/monitoring/alerts/policies-in-json.
/// {@endtemplate}
@JsonSerializable()
class GCPAlert {
/// {@macro gcp_alert}
const GCPAlert({this.version, this.incident});
/// Creates a [GCPAlert] from a JSON [Map].
factory GCPAlert.fromJson(Map<String, dynamic> json) =>
_$GCPAlertFromJson(json);
/// Converts a [GCPAlert] to the JSON representation.
Map<String, dynamic> toJson() => _$GCPAlertToJson(this);
/// The version of the alert.
final String? version;
/// The incident associated with the alert.
final Incident? incident;
}
/// {@template incident}
/// An incident which triggered an alert.
/// {@endtemplate}
@JsonSerializable()
class Incident {
/// {@macro incident}
const Incident({
this.incidentId,
this.scopingProjectId,
this.scopingProjectNumber,
this.url,
this.startedAt,
this.endedAt,
this.state,
this.summary,
this.observedValue,
this.resource,
this.resourceTypeDisplayName,
this.resourceId,
this.resourceDisplayName,
this.resourceName,
this.metric,
this.policyName,
this.documentation,
this.condition,
this.conditionName,
this.thresholdValue,
});
/// Creates an [Incident] from a JSON [Map].
factory Incident.fromJson(Map<String, dynamic> json) =>
_$IncidentFromJson(json);
/// Converts an [Incident] to the JSON representation.
Map<String, dynamic> toJson() => _$IncidentToJson(this);
/// The unique identifier of the incident.
final String? incidentId;
/// The project ID of the scoping project.
final String? scopingProjectId;
/// The project number of the scoping project.
final int? scopingProjectNumber;
/// The URL of the incident.
final String? url;
/// The time the incident started.
final int? startedAt;
/// The time the incident ended.
final int? endedAt;
/// The state of the incident.
final String? state;
/// A summary of the incident.
final String? summary;
/// The observed value of the incident.
final String? observedValue;
/// The resource associated with the incident.
final Resource? resource;
/// The display name of the resource type.
final String? resourceTypeDisplayName;
/// The unique identifier of the resource.
final String? resourceId;
/// The display name of the resource.
final String? resourceDisplayName;
/// The name of the resource.
final String? resourceName;
/// The metric associated with the incident.
final Metric? metric;
/// The name of the policy associated with the incident.
final String? policyName;
/// The documentation associated with the incident.
final String? documentation;
/// The condition associated with the incident.
final Condition? condition;
/// The name of the condition associated with the incident.
final String? conditionName;
/// The threshold value of the incident.
final String? thresholdValue;
}
/// {@template resource}
/// A resource associated with an incident.
/// {@endtemplate}
@JsonSerializable()
class Resource {
/// {@macro resource}
const Resource({this.type});
/// Creates a [Resource] from a JSON [Map].
factory Resource.fromJson(Map<String, dynamic> json) =>
_$ResourceFromJson(json);
/// Converts a [Resource] to the JSON representation.
Map<String, dynamic> toJson() => _$ResourceToJson(this);
/// The type of the resource.
final String? type;
}
/// {@template metric}
/// A metric associated with an incident.
/// {@endtemplate}
@JsonSerializable()
class Metric {
/// {@macro metric}
const Metric({this.type, this.displayName});
/// Creates a [Metric] from a JSON [Map].
factory Metric.fromJson(Map<String, dynamic> json) => _$MetricFromJson(json);
/// Converts a [Metric] to the JSON representation.
Map<String, dynamic> toJson() => _$MetricToJson(this);
/// The type of the metric.
final String? type;
/// The display name of the metric.
final String? displayName;
}
/// {@template condition}
/// A condition associated with an incident.
/// {@endtemplate}
@JsonSerializable()
class Condition {
/// {@macro condition}
const Condition({this.name, this.displayName, this.conditionThreshold});
/// Creates a [Condition] from a JSON [Map].
factory Condition.fromJson(Map<String, dynamic> json) =>
_$ConditionFromJson(json);
/// Converts a [Condition] to the JSON representation.
Map<String, dynamic> toJson() => _$ConditionToJson(this);
/// The name of the condition.
final String? name;
/// The display name of the condition.
final String? displayName;
/// The condition threshold.
final ConditionThreshold? conditionThreshold;
}
/// {@template condition_threshold}
/// The condition threshold associated with an incident.
/// {@endtemplate}
@JsonSerializable()
class ConditionThreshold {
/// {@macro condition_threshold}
const ConditionThreshold({
this.filter,
this.comparison,
this.thresholdValue,
this.duration,
this.trigger,
});
/// Creates a [ConditionThreshold] from a JSON [Map].
factory ConditionThreshold.fromJson(Map<String, dynamic> json) =>
_$ConditionThresholdFromJson(json);
/// Converts a [ConditionThreshold] to the JSON representation.
Map<String, dynamic> toJson() => _$ConditionThresholdToJson(this);
/// The filter associated with the condition threshold.
final String? filter;
/// The comparison associated with the condition threshold.
final String? comparison;
/// The threshold value associated with the condition threshold.
final double? thresholdValue;
/// The duration associated with the condition threshold.
final String? duration;
/// The trigger associated with the condition threshold.
final Trigger? trigger;
}
/// {@template trigger}
/// The trigger associated with an incident.
/// {@endtemplate}
@JsonSerializable()
class Trigger {
/// {@macro trigger}
const Trigger({this.count});
/// Creates a [Trigger] from a JSON [Map].
factory Trigger.fromJson(Map<String, dynamic> json) =>
_$TriggerFromJson(json);
/// Converts a [Trigger] to the JSON representation.
Map<String, dynamic> toJson() => _$TriggerToJson(this);
/// The count associated with the trigger.
final int? count;
}
@@ -0,0 +1,221 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: implicit_dynamic_parameter, require_trailing_commas, cast_nullable_to_non_nullable, lines_longer_than_80_chars, unnecessary_lambdas
part of 'gcp_alert.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
GCPAlert _$GCPAlertFromJson(Map<String, dynamic> json) => $checkedCreate(
'GCPAlert',
json,
($checkedConvert) {
final val = GCPAlert(
version: $checkedConvert('version', (v) => v as String?),
incident: $checkedConvert(
'incident',
(v) => v == null
? null
: Incident.fromJson(v as Map<String, dynamic>)),
);
return val;
},
);
Map<String, dynamic> _$GCPAlertToJson(GCPAlert instance) => <String, dynamic>{
'version': instance.version,
'incident': instance.incident?.toJson(),
};
Incident _$IncidentFromJson(Map<String, dynamic> json) => $checkedCreate(
'Incident',
json,
($checkedConvert) {
final val = Incident(
incidentId: $checkedConvert('incident_id', (v) => v as String?),
scopingProjectId:
$checkedConvert('scoping_project_id', (v) => v as String?),
scopingProjectNumber:
$checkedConvert('scoping_project_number', (v) => v as int?),
url: $checkedConvert('url', (v) => v as String?),
startedAt: $checkedConvert('started_at', (v) => v as int?),
endedAt: $checkedConvert('ended_at', (v) => v as int?),
state: $checkedConvert('state', (v) => v as String?),
summary: $checkedConvert('summary', (v) => v as String?),
observedValue: $checkedConvert('observed_value', (v) => v as String?),
resource: $checkedConvert(
'resource',
(v) => v == null
? null
: Resource.fromJson(v as Map<String, dynamic>)),
resourceTypeDisplayName: $checkedConvert(
'resource_type_display_name', (v) => v as String?),
resourceId: $checkedConvert('resource_id', (v) => v as String?),
resourceDisplayName:
$checkedConvert('resource_display_name', (v) => v as String?),
resourceName: $checkedConvert('resource_name', (v) => v as String?),
metric: $checkedConvert(
'metric',
(v) => v == null
? null
: Metric.fromJson(v as Map<String, dynamic>)),
policyName: $checkedConvert('policy_name', (v) => v as String?),
documentation: $checkedConvert('documentation', (v) => v as String?),
condition: $checkedConvert(
'condition',
(v) => v == null
? null
: Condition.fromJson(v as Map<String, dynamic>)),
conditionName: $checkedConvert('condition_name', (v) => v as String?),
thresholdValue:
$checkedConvert('threshold_value', (v) => v as String?),
);
return val;
},
fieldKeyMap: const {
'incidentId': 'incident_id',
'scopingProjectId': 'scoping_project_id',
'scopingProjectNumber': 'scoping_project_number',
'startedAt': 'started_at',
'endedAt': 'ended_at',
'observedValue': 'observed_value',
'resourceTypeDisplayName': 'resource_type_display_name',
'resourceId': 'resource_id',
'resourceDisplayName': 'resource_display_name',
'resourceName': 'resource_name',
'policyName': 'policy_name',
'conditionName': 'condition_name',
'thresholdValue': 'threshold_value'
},
);
Map<String, dynamic> _$IncidentToJson(Incident instance) => <String, dynamic>{
'incident_id': instance.incidentId,
'scoping_project_id': instance.scopingProjectId,
'scoping_project_number': instance.scopingProjectNumber,
'url': instance.url,
'started_at': instance.startedAt,
'ended_at': instance.endedAt,
'state': instance.state,
'summary': instance.summary,
'observed_value': instance.observedValue,
'resource': instance.resource?.toJson(),
'resource_type_display_name': instance.resourceTypeDisplayName,
'resource_id': instance.resourceId,
'resource_display_name': instance.resourceDisplayName,
'resource_name': instance.resourceName,
'metric': instance.metric?.toJson(),
'policy_name': instance.policyName,
'documentation': instance.documentation,
'condition': instance.condition?.toJson(),
'condition_name': instance.conditionName,
'threshold_value': instance.thresholdValue,
};
Resource _$ResourceFromJson(Map<String, dynamic> json) => $checkedCreate(
'Resource',
json,
($checkedConvert) {
final val = Resource(
type: $checkedConvert('type', (v) => v as String?),
);
return val;
},
);
Map<String, dynamic> _$ResourceToJson(Resource instance) => <String, dynamic>{
'type': instance.type,
};
Metric _$MetricFromJson(Map<String, dynamic> json) => $checkedCreate(
'Metric',
json,
($checkedConvert) {
final val = Metric(
type: $checkedConvert('type', (v) => v as String?),
displayName: $checkedConvert('display_name', (v) => v as String?),
);
return val;
},
fieldKeyMap: const {'displayName': 'display_name'},
);
Map<String, dynamic> _$MetricToJson(Metric instance) => <String, dynamic>{
'type': instance.type,
'display_name': instance.displayName,
};
Condition _$ConditionFromJson(Map<String, dynamic> json) => $checkedCreate(
'Condition',
json,
($checkedConvert) {
final val = Condition(
name: $checkedConvert('name', (v) => v as String?),
displayName: $checkedConvert('display_name', (v) => v as String?),
conditionThreshold: $checkedConvert(
'condition_threshold',
(v) => v == null
? null
: ConditionThreshold.fromJson(v as Map<String, dynamic>)),
);
return val;
},
fieldKeyMap: const {
'displayName': 'display_name',
'conditionThreshold': 'condition_threshold'
},
);
Map<String, dynamic> _$ConditionToJson(Condition instance) => <String, dynamic>{
'name': instance.name,
'display_name': instance.displayName,
'condition_threshold': instance.conditionThreshold?.toJson(),
};
ConditionThreshold _$ConditionThresholdFromJson(Map<String, dynamic> json) =>
$checkedCreate(
'ConditionThreshold',
json,
($checkedConvert) {
final val = ConditionThreshold(
filter: $checkedConvert('filter', (v) => v as String?),
comparison: $checkedConvert('comparison', (v) => v as String?),
thresholdValue: $checkedConvert(
'threshold_value', (v) => (v as num?)?.toDouble()),
duration: $checkedConvert('duration', (v) => v as String?),
trigger: $checkedConvert(
'trigger',
(v) => v == null
? null
: Trigger.fromJson(v as Map<String, dynamic>)),
);
return val;
},
fieldKeyMap: const {'thresholdValue': 'threshold_value'},
);
Map<String, dynamic> _$ConditionThresholdToJson(ConditionThreshold instance) =>
<String, dynamic>{
'filter': instance.filter,
'comparison': instance.comparison,
'threshold_value': instance.thresholdValue,
'duration': instance.duration,
'trigger': instance.trigger?.toJson(),
};
Trigger _$TriggerFromJson(Map<String, dynamic> json) => $checkedCreate(
'Trigger',
json,
($checkedConvert) {
final val = Trigger(
count: $checkedConvert('count', (v) => v as int?),
);
return val;
},
);
Map<String, dynamic> _$TriggerToJson(Trigger instance) => <String, dynamic>{
'count': instance.count,
};
@@ -0,0 +1 @@
export 'gcp_alert.dart';
+565
View File
@@ -0,0 +1,565 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_fe_analyzer_shared:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: a36ec4843dc30ea6bf652bf25e3448db6c5e8bcf4aa55f063a5d1dad216d8214
url: "https://pub.dev"
source: hosted
version: "58.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
sha256: cc4242565347e98424ce9945c819c192ec0838cb9d1f6aa4a97cc96becbc5b27
url: "https://pub.dev"
source: hosted
version: "5.10.0"
args:
dependency: transitive
description:
name: args
sha256: "4cab82a83ffef80b262ddedf47a0a8e56ee6fbf7fe21e6e768b02792034dd440"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
async:
dependency: transitive
description:
name: async
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
url: "https://pub.dev"
source: hosted
version: "2.11.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
build:
dependency: transitive
description:
name: build
sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
build_config:
dependency: transitive
description:
name: build_config
sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1
url: "https://pub.dev"
source: hosted
version: "1.1.1"
build_daemon:
dependency: transitive
description:
name: build_daemon
sha256: "757153e5d9cd88253cb13f28c2fb55a537dc31fefd98137549895b5beb7c6169"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
build_resolvers:
dependency: transitive
description:
name: build_resolvers
sha256: db49b8609ef8c81cca2b310618c3017c00f03a92af44c04d310b907b2d692d95
url: "https://pub.dev"
source: hosted
version: "2.2.0"
build_runner:
dependency: "direct dev"
description:
name: build_runner
sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727
url: "https://pub.dev"
source: hosted
version: "2.3.3"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
sha256: "14febe0f5bac5ae474117a36099b4de6f1dbc52df6c5e55534b3da9591bf4292"
url: "https://pub.dev"
source: hosted
version: "7.2.7"
built_collection:
dependency: transitive
description:
name: built_collection
sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
url: "https://pub.dev"
source: hosted
version: "5.1.1"
built_value:
dependency: transitive
description:
name: built_value
sha256: "31b7c748fd4b9adf8d25d72a4c4a59ef119f12876cf414f94f8af5131d5fa2b0"
url: "https://pub.dev"
source: hosted
version: "8.4.4"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
sha256: "3d1505d91afa809d177efd4eed5bb0eb65805097a1463abdd2add076effae311"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
code_builder:
dependency: transitive
description:
name: code_builder
sha256: "0d43dd1288fd145de1ecc9a3948ad4a6d5a82f0a14c4fdd0892260787d975cbe"
url: "https://pub.dev"
source: hosted
version: "4.4.0"
collection:
dependency: transitive
description:
name: collection
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
url: "https://pub.dev"
source: hosted
version: "1.17.1"
convert:
dependency: transitive
description:
name: convert
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
coverage:
dependency: transitive
description:
name: coverage
sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097"
url: "https://pub.dev"
source: hosted
version: "1.6.3"
crypto:
dependency: transitive
description:
name: crypto
sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67
url: "https://pub.dev"
source: hosted
version: "3.0.2"
dart_style:
dependency: transitive
description:
name: dart_style
sha256: "6d691edde054969f0e0f26abb1b30834b5138b963793e56f69d3a9a4435e6352"
url: "https://pub.dev"
source: hosted
version: "2.3.0"
file:
dependency: transitive
description:
name: file
sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
url: "https://pub.dev"
source: hosted
version: "6.1.4"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
frontend_server_client:
dependency: transitive
description:
name: frontend_server_client
sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612"
url: "https://pub.dev"
source: hosted
version: "3.2.0"
glob:
dependency: transitive
description:
name: glob
sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
graphs:
dependency: transitive
description:
name: graphs
sha256: f9e130f3259f52d26f0cfc0e964513796dafed572fa52e45d2f8d6ca14db39b2
url: "https://pub.dev"
source: hosted
version: "2.2.0"
http:
dependency: "direct main"
description:
name: http
sha256: "6aa2946395183537c8b880962d935877325d6a09a2867c3970c05c0fed6ac482"
url: "https://pub.dev"
source: hosted
version: "0.13.5"
http_methods:
dependency: transitive
description:
name: http_methods
sha256: c192bb6fb4ae99d06053f67a2c1c65350a29bc778a39d9a12b96bd2ec820e9dc
url: "https://pub.dev"
source: hosted
version: "1.1.0"
http_multi_server:
dependency: transitive
description:
name: http_multi_server
sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
url: "https://pub.dev"
source: hosted
version: "3.2.1"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
io:
dependency: transitive
description:
name: io
sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
js:
dependency: transitive
description:
name: js
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.dev"
source: hosted
version: "0.6.7"
json_annotation:
dependency: "direct main"
description:
name: json_annotation
sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317
url: "https://pub.dev"
source: hosted
version: "4.8.0"
json_serializable:
dependency: "direct dev"
description:
name: json_serializable
sha256: dadc08bd61f72559f938dd08ec20dbfec6c709bba83515085ea943d2078d187a
url: "https://pub.dev"
source: hosted
version: "6.6.1"
logging:
dependency: transitive
description:
name: logging
sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d"
url: "https://pub.dev"
source: hosted
version: "1.1.1"
matcher:
dependency: transitive
description:
name: matcher
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
url: "https://pub.dev"
source: hosted
version: "0.12.15"
meta:
dependency: transitive
description:
name: meta
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
url: "https://pub.dev"
source: hosted
version: "1.9.1"
mime:
dependency: transitive
description:
name: mime
sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
url: "https://pub.dev"
source: hosted
version: "1.0.4"
mocktail:
dependency: "direct dev"
description:
name: mocktail
sha256: "80a996cd9a69284b3dc521ce185ffe9150cde69767c2d3a0720147d93c0cef53"
url: "https://pub.dev"
source: hosted
version: "0.3.0"
node_preamble:
dependency: transitive
description:
name: node_preamble
sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
package_config:
dependency: transitive
description:
name: package_config
sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
path:
dependency: transitive
description:
name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
url: "https://pub.dev"
source: hosted
version: "1.8.3"
pool:
dependency: transitive
description:
name: pool
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
url: "https://pub.dev"
source: hosted
version: "1.5.1"
pub_semver:
dependency: transitive
description:
name: pub_semver
sha256: "307de764d305289ff24ad257ad5c5793ce56d04947599ad68b3baa124105fc17"
url: "https://pub.dev"
source: hosted
version: "2.1.3"
pubspec_parse:
dependency: transitive
description:
name: pubspec_parse
sha256: ec85d7d55339d85f44ec2b682a82fea340071e8978257e5a43e69f79e98ef50c
url: "https://pub.dev"
source: hosted
version: "1.2.2"
shelf:
dependency: "direct main"
description:
name: shelf
sha256: c24a96135a2ccd62c64b69315a14adc5c3419df63b4d7c05832a346fdb73682c
url: "https://pub.dev"
source: hosted
version: "1.4.0"
shelf_packages_handler:
dependency: transitive
description:
name: shelf_packages_handler
sha256: aef74dc9195746a384843102142ab65b6a4735bb3beea791e63527b88cc83306
url: "https://pub.dev"
source: hosted
version: "3.0.1"
shelf_router:
dependency: "direct main"
description:
name: shelf_router
sha256: "0b0bfb835e8b2bb43c5341ee689f0d2851e9cea377a4f2db4ec06a1a99beace4"
url: "https://pub.dev"
source: hosted
version: "1.1.3"
shelf_static:
dependency: transitive
description:
name: shelf_static
sha256: e792b76b96a36d4a41b819da593aff4bdd413576b3ba6150df5d8d9996d2e74c
url: "https://pub.dev"
source: hosted
version: "1.1.1"
shelf_web_socket:
dependency: transitive
description:
name: shelf_web_socket
sha256: a988c0e8d8ffbdb8a28aa7ec8e449c260f3deb808781fe1284d22c5bba7156e8
url: "https://pub.dev"
source: hosted
version: "1.0.3"
source_gen:
dependency: transitive
description:
name: source_gen
sha256: c2bea18c95cfa0276a366270afaa2850b09b4a76db95d546f3d003dcc7011298
url: "https://pub.dev"
source: hosted
version: "1.2.7"
source_helper:
dependency: transitive
description:
name: source_helper
sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f"
url: "https://pub.dev"
source: hosted
version: "1.3.3"
source_map_stack_trace:
dependency: transitive
description:
name: source_map_stack_trace
sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
source_maps:
dependency: transitive
description:
name: source_maps
sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703"
url: "https://pub.dev"
source: hosted
version: "0.10.12"
source_span:
dependency: transitive
description:
name: source_span
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
version: "1.10.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
url: "https://pub.dev"
source: hosted
version: "1.11.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
stream_transform:
dependency: transitive
description:
name: stream_transform
sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
url: "https://pub.dev"
source: hosted
version: "1.2.1"
test:
dependency: "direct dev"
description:
name: test
sha256: "4f92f103ef63b1bbac6f4bd1930624fca81b2574464482512c4f0896319be575"
url: "https://pub.dev"
source: hosted
version: "1.24.2"
test_api:
dependency: transitive
description:
name: test_api
sha256: daadc9baabec998b062c9091525aa95786508b1c48e9c30f1f891b8bf6ff2e64
url: "https://pub.dev"
source: hosted
version: "0.5.2"
test_core:
dependency: transitive
description:
name: test_core
sha256: "3642b184882f79e76ca57a9230fb971e494c3c1fd09c21ae3083ce891bcc0aa1"
url: "https://pub.dev"
source: hosted
version: "0.5.2"
timing:
dependency: transitive
description:
name: timing
sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
very_good_analysis:
dependency: "direct dev"
description:
name: very_good_analysis
sha256: ebc48c51db35beeeec8c414e32f7bd78e612bd7f5992ccb0d46e19edaeb40b08
url: "https://pub.dev"
source: hosted
version: "4.0.0+1"
vm_service:
dependency: transitive
description:
name: vm_service
sha256: "518254c0d3ee20667a1feef39eefe037df87439851e4b3cb277e5b3f37afa2f0"
url: "https://pub.dev"
source: hosted
version: "11.4.0"
watcher:
dependency: transitive
description:
name: watcher
sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
url: "https://pub.dev"
source: hosted
version: "2.4.0"
webkit_inspection_protocol:
dependency: transitive
description:
name: webkit_inspection_protocol
sha256: "67d3a8b6c79e1987d19d848b0892e582dbb0c66c57cc1fef58a177dd2aa2823d"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
yaml:
dependency: transitive
description:
name: yaml
sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
sdks:
dart: ">=2.19.6 <3.0.0"
+20
View File
@@ -0,0 +1,20 @@
name: discord_gcp_alerts
description: A server which proxies GCP Alert notifications to Discord.
version: 1.0.0
publish_to: none
environment:
sdk: '>=2.19.6 <3.0.0'
dependencies:
http: ^0.13.5
json_annotation: ^4.8.0
shelf: ^1.4.0
shelf_router: ^1.1.0
dev_dependencies:
build_runner: ^2.3.3
json_serializable: ^6.6.1
mocktail: ^0.3.0
test: ^1.21.0
very_good_analysis: ^4.0.0
@@ -0,0 +1,122 @@
import 'dart:convert';
import 'dart:io';
import 'package:discord_gcp_alerts/discord_gcp_alerts.dart';
import 'package:http/http.dart' as http;
import 'package:mocktail/mocktail.dart';
import 'package:shelf/shelf.dart';
import 'package:test/test.dart';
class _MockHttpClient extends Mock implements http.Client {}
void main() {
group('gcpAlertHandler', () {
const webHookUrl = 'http://example.com/webhook';
late http.Client client;
late Handler handler;
setUp(() {
client = _MockHttpClient();
handler = gcpAlertHandler(webhookUrl: webHookUrl, client: client);
});
test('forwards the request to the webhook', () async {
when(
() => client.post(
Uri.parse(webHookUrl),
headers: any(named: 'headers'),
body: any(named: 'body'),
),
).thenAnswer((_) async => http.Response('', 200));
await handler(
Request(
'POST',
Uri.parse('http://localhost:8080/'),
body: json.encode(
{
'version': 'test',
'incident': {
'incident_id': '12345',
'scoping_project_id': '12345',
'scoping_project_number': 12345,
'url': 'http://www.example.com',
'started_at': 0,
'ended_at': 0,
'state': 'OPEN',
'summary': 'Test Incident',
'apigee_url': 'http://www.example.com',
'observed_value': '1.0',
'resource': {
'type': 'example_resource',
'labels': {'example': 'label'}
},
'resource_type_display_name': 'Example Resource Type',
'resource_id': '12345',
'resource_display_name': 'Example Resource',
'resource_name': 'projects/12345/example_resources/12345',
'metric': {
'type': 'test.googleapis.com/metric',
'displayName': 'Test Metric',
'labels': {'example': 'label'}
},
'metadata': {
'system_labels': {'example': 'label'},
'user_labels': {'example': 'label'}
},
'policy_name': 'projects/12345/alertPolicies/12345',
'policy_user_labels': {'example': 'label'},
'documentation': 'Test documentation',
'condition': {
'name': 'projects/12345/alertPolicies/12345/conditions/12345',
'displayName': 'Example condition',
'conditionThreshold': {
'filter':
'metric.type=\"test.googleapis.com/metric\" resource.type=\"example_resource\"',
'comparison': 'COMPARISON_GT',
'thresholdValue': 0.5,
'duration': '0s',
'trigger': {'count': 1}
}
},
'condition_name': 'Example condition',
'threshold_value': '0.5'
}
},
),
),
);
verify(
() => client.post(
Uri.parse(webHookUrl),
headers: {HttpHeaders.contentTypeHeader: ContentType.json.value},
body: json.encode({
'embeds': [
{
'title': 'GCP Alert',
'description': 'Test Incident',
'url': 'http://www.example.com',
'color': 16711680,
'fields': [
{'name': 'State', 'value': 'OPEN', 'inline': true},
{
'name': 'Resource',
'value': 'projects/12345/example_resources/12345',
'inline': true
},
{
'name': 'Condition',
'value': 'Example condition',
'inline': true
}
]
}
],
'attachments': <dynamic>[]
}),
),
).called(1);
});
});
}
@@ -0,0 +1,34 @@
import 'package:discord_gcp_alerts/discord_gcp_alerts.dart';
import 'package:test/test.dart';
void main() {
group('GCPAlert', () {
test('can be (de)serialized', () {
const alert = GCPAlert(
version: '1.0.0',
incident: Incident(
condition: Condition(
name: 'test',
displayName: 'Test Condition',
conditionThreshold: ConditionThreshold(
trigger: Trigger(count: 10),
filter: 'metric.type = "test.googleapis.com/metric"',
comparison: 'ComparisonType.greaterThan',
thresholdValue: 1,
duration: '0s',
),
),
resource: Resource(type: 'test_resource'),
metric: Metric(
type: 'test.googleapis.com/metric',
displayName: 'Test Metric',
),
),
);
expect(
GCPAlert.fromJson(alert.toJson()).toJson(),
equals(alert.toJson()),
);
});
});
}