diff --git a/.env b/.env index 36c3296cb..852ad0e90 100644 --- a/.env +++ b/.env @@ -33,6 +33,9 @@ CQ_GUARDIAN_SNYK=0.1.1 # See https://github.com/guardian/cq-source-github-languages CQ_GITHUB_LANGUAGES=0.0.4 +# See https://github.com/guardian/cq-source-ns1 +CQ_NS1=0.0.3 + # --- FOR LOCAL DEVELOPMENT ONLY --- STAGE=DEV DATABASE_USER=postgres diff --git a/packages/cdk/lib/__snapshots__/service-catalogue.test.ts.snap b/packages/cdk/lib/__snapshots__/service-catalogue.test.ts.snap index 67afc0272..d7b0cb678 100644 --- a/packages/cdk/lib/__snapshots__/service-catalogue.test.ts.snap +++ b/packages/cdk/lib/__snapshots__/service-catalogue.test.ts.snap @@ -6243,6 +6243,637 @@ spec: }, "Type": "AWS::IAM::Policy", }, + "CloudquerySourceNS1ScheduledEventRule1D618E3C": { + "Properties": { + "ScheduleExpression": "cron(0 0 * * ? *)", + "State": "ENABLED", + "Targets": [ + { + "Arn": { + "Fn::GetAtt": [ + "servicecatalogueCluster5FC34DC5", + "Arn", + ], + }, + "EcsParameters": { + "LaunchType": "FARGATE", + "NetworkConfiguration": { + "AwsVpcConfiguration": { + "AssignPublicIp": "DISABLED", + "SecurityGroups": [ + { + "Fn::GetAtt": [ + "PostgresAccessSecurityGroupServicecatalogue03C78F14", + "GroupId", + ], + }, + ], + "Subnets": { + "Ref": "PrivateSubnets", + }, + }, + }, + "TaskCount": 1, + "TaskDefinitionArn": { + "Ref": "CloudquerySourceNS1TaskDefinition56258A70", + }, + }, + "Id": "Target0", + "Input": "{}", + "RoleArn": { + "Fn::GetAtt": [ + "CloudquerySourceNS1TaskDefinitionEventsRole2D5A948C", + "Arn", + ], + }, + }, + ], + }, + "Type": "AWS::Events::Rule", + }, + "CloudquerySourceNS1TaskDefinition56258A70": { + "Properties": { + "ContainerDefinitions": [ + { + "Command": [ + "/bin/sh", + "-c", + "wget -O /usr/local/share/ca-certificates/global-bundle.crt -q https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem && update-ca-certificates;printf 'kind: source +spec: + name: ns1 + registry: grpc + path: localhost:7777 + version: v0.0.0 + tables: + - ns1_* + destinations: + - postgresql + spec: + apiKey: \${NS1_API_KEY} +' > /source.yaml;printf 'kind: destination +spec: + name: postgresql + registry: github + path: cloudquery/postgresql + version: v7.0.1 + migrate_mode: forced + spec: + connection_string: >- + user=\${DB_USERNAME} password=\${DB_PASSWORD} host=\${DB_HOST} port=5432 + dbname=postgres sslmode=verify-full +' > /destination.yaml;/app/cloudquery sync /source.yaml /destination.yaml --log-format json --log-console", + ], + "DependsOn": [ + { + "Condition": "START", + "ContainerName": "CloudquerySource-NS1PluginContainer", + }, + ], + "DockerLabels": { + "App": "service-catalogue", + "Name": "NS1", + "Stack": "deploy", + "Stage": "TEST", + }, + "EntryPoint": [ + "", + ], + "Environment": [ + { + "Name": "GOMEMLIMIT", + "Value": "409MiB", + }, + ], + "Essential": true, + "Image": "ghcr.io/cloudquery/cloudquery:4.3.5", + "LogConfiguration": { + "LogDriver": "awsfirelens", + "Options": { + "Name": "kinesis_streams", + "region": { + "Ref": "AWS::Region", + }, + "retry_limit": "2", + "stream": { + "Ref": "LoggingStreamName", + }, + }, + }, + "Name": "CloudquerySource-NS1Container", + "Secrets": [ + { + "Name": "NS1_API_KEY", + "ValueFrom": { + "Fn::Join": [ + "", + [ + { + "Ref": "ns1credentialsA8DD3B2D", + }, + ":api-key::", + ], + ], + }, + }, + { + "Name": "DB_USERNAME", + "ValueFrom": { + "Fn::Join": [ + "", + [ + { + "Ref": "PostgresInstance1SecretAttachmentBA0D257D", + }, + ":username::", + ], + ], + }, + }, + { + "Name": "DB_HOST", + "ValueFrom": { + "Fn::Join": [ + "", + [ + { + "Ref": "PostgresInstance1SecretAttachmentBA0D257D", + }, + ":host::", + ], + ], + }, + }, + { + "Name": "DB_PASSWORD", + "ValueFrom": { + "Fn::Join": [ + "", + [ + { + "Ref": "PostgresInstance1SecretAttachmentBA0D257D", + }, + ":password::", + ], + ], + }, + }, + { + "Name": "CLOUDQUERY_API_KEY", + "ValueFrom": { + "Fn::Join": [ + "", + [ + { + "Ref": "cloudqueryapikeyCCF82F53", + }, + ":api-key::", + ], + ], + }, + }, + ], + }, + { + "Essential": false, + "Image": "ghcr.io/guardian/cq-source-ns1:0.0.3", + "LogConfiguration": { + "LogDriver": "awsfirelens", + "Options": { + "Name": "kinesis_streams", + "region": { + "Ref": "AWS::Region", + }, + "retry_limit": "2", + "stream": { + "Ref": "LoggingStreamName", + }, + }, + }, + "Name": "CloudquerySource-NS1PluginContainer", + }, + { + "Environment": [ + { + "Name": "STACK", + "Value": "deploy", + }, + { + "Name": "STAGE", + "Value": "TEST", + }, + { + "Name": "APP", + "Value": "service-catalogue", + }, + { + "Name": "GU_REPO", + "Value": "guardian/service-catalogue", + }, + ], + "Essential": true, + "FirelensConfiguration": { + "Type": "fluentbit", + }, + "Image": "ghcr.io/guardian/devx-logs:2", + "LogConfiguration": { + "LogDriver": "awslogs", + "Options": { + "awslogs-group": { + "Ref": "CloudquerySourceNS1TaskDefinitionCloudquerySourceNS1FirelensLogGroup3A327514", + }, + "awslogs-region": { + "Ref": "AWS::Region", + }, + "awslogs-stream-prefix": "deploy/TEST/service-catalogue", + }, + }, + "Name": "CloudquerySource-NS1Firelens", + }, + ], + "Cpu": "256", + "ExecutionRoleArn": { + "Fn::GetAtt": [ + "CloudquerySourceNS1TaskDefinitionExecutionRoleECC80718", + "Arn", + ], + }, + "Family": "ServiceCatalogueCloudquerySourceNS1TaskDefinitionE32F0EBB", + "Memory": "512", + "NetworkMode": "awsvpc", + "RequiresCompatibilities": [ + "FARGATE", + ], + "Tags": [ + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/service-catalogue", + }, + { + "Key": "Name", + "Value": "NS1", + }, + { + "Key": "Stack", + "Value": "deploy", + }, + { + "Key": "Stage", + "Value": "TEST", + }, + ], + "TaskRoleArn": { + "Fn::GetAtt": [ + "CloudquerySourceNS1TaskDefinitionTaskRole654B2CE7", + "Arn", + ], + }, + }, + "Type": "AWS::ECS::TaskDefinition", + }, + "CloudquerySourceNS1TaskDefinitionCloudquerySourceNS1FirelensLogGroup3A327514": { + "DeletionPolicy": "Retain", + "Properties": { + "RetentionInDays": 1, + "Tags": [ + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/service-catalogue", + }, + { + "Key": "Name", + "Value": "NS1", + }, + { + "Key": "Stack", + "Value": "deploy", + }, + { + "Key": "Stage", + "Value": "TEST", + }, + ], + }, + "Type": "AWS::Logs::LogGroup", + "UpdateReplacePolicy": "Retain", + }, + "CloudquerySourceNS1TaskDefinitionEventsRole2D5A948C": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "events.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Tags": [ + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/service-catalogue", + }, + { + "Key": "Name", + "Value": "NS1", + }, + { + "Key": "Stack", + "Value": "deploy", + }, + { + "Key": "Stage", + "Value": "TEST", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "CloudquerySourceNS1TaskDefinitionEventsRoleDefaultPolicyE40A5A68": { + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": "ecs:RunTask", + "Condition": { + "ArnEquals": { + "ecs:cluster": { + "Fn::GetAtt": [ + "servicecatalogueCluster5FC34DC5", + "Arn", + ], + }, + }, + }, + "Effect": "Allow", + "Resource": { + "Ref": "CloudquerySourceNS1TaskDefinition56258A70", + }, + }, + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "CloudquerySourceNS1TaskDefinitionExecutionRoleECC80718", + "Arn", + ], + }, + }, + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "CloudquerySourceNS1TaskDefinitionTaskRole654B2CE7", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "CloudquerySourceNS1TaskDefinitionEventsRoleDefaultPolicyE40A5A68", + "Roles": [ + { + "Ref": "CloudquerySourceNS1TaskDefinitionEventsRole2D5A948C", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "CloudquerySourceNS1TaskDefinitionExecutionRoleDefaultPolicyA99171A1": { + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "secretsmanager:GetSecretValue", + "secretsmanager:DescribeSecret", + ], + "Effect": "Allow", + "Resource": { + "Ref": "ns1credentialsA8DD3B2D", + }, + }, + { + "Action": [ + "secretsmanager:GetSecretValue", + "secretsmanager:DescribeSecret", + ], + "Effect": "Allow", + "Resource": { + "Ref": "PostgresInstance1SecretAttachmentBA0D257D", + }, + }, + { + "Action": [ + "secretsmanager:GetSecretValue", + "secretsmanager:DescribeSecret", + ], + "Effect": "Allow", + "Resource": { + "Ref": "cloudqueryapikeyCCF82F53", + }, + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + "Effect": "Allow", + "Resource": { + "Fn::GetAtt": [ + "CloudquerySourceNS1TaskDefinitionCloudquerySourceNS1FirelensLogGroup3A327514", + "Arn", + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "CloudquerySourceNS1TaskDefinitionExecutionRoleDefaultPolicyA99171A1", + "Roles": [ + { + "Ref": "CloudquerySourceNS1TaskDefinitionExecutionRoleECC80718", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, + "CloudquerySourceNS1TaskDefinitionExecutionRoleECC80718": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Tags": [ + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/service-catalogue", + }, + { + "Key": "Name", + "Value": "NS1", + }, + { + "Key": "Stack", + "Value": "deploy", + }, + { + "Key": "Stage", + "Value": "TEST", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "CloudquerySourceNS1TaskDefinitionTaskRole654B2CE7": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "ecs-tasks.amazonaws.com", + }, + }, + ], + "Version": "2012-10-17", + }, + "Tags": [ + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/service-catalogue", + }, + { + "Key": "Name", + "Value": "NS1", + }, + { + "Key": "Stack", + "Value": "deploy", + }, + { + "Key": "Stage", + "Value": "TEST", + }, + ], + }, + "Type": "AWS::IAM::Role", + }, + "CloudquerySourceNS1TaskDefinitionTaskRoleDefaultPolicy7B696A72": { + "Properties": { + "PolicyDocument": { + "Statement": [ + { + "Action": [ + "kinesis:Describe*", + "kinesis:Put*", + ], + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":kinesis:", + { + "Ref": "AWS::Region", + }, + ":", + { + "Ref": "AWS::AccountId", + }, + ":stream/", + { + "Ref": "LoggingStreamName", + }, + ], + ], + }, + }, + { + "Action": "rds-db:connect", + "Effect": "Allow", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:", + { + "Ref": "AWS::Partition", + }, + ":rds-db:", + { + "Ref": "AWS::Region", + }, + ":", + { + "Ref": "AWS::AccountId", + }, + ":dbuser:", + { + "Fn::GetAtt": [ + "PostgresInstance16DE4286E", + "DbiResourceId", + ], + }, + "/{{resolve:secretsmanager:", + { + "Ref": "PostgresInstance1SecretAttachmentBA0D257D", + }, + ":SecretString:username::}}", + ], + ], + }, + }, + ], + "Version": "2012-10-17", + }, + "PolicyName": "CloudquerySourceNS1TaskDefinitionTaskRoleDefaultPolicy7B696A72", + "Roles": [ + { + "Ref": "CloudquerySourceNS1TaskDefinitionTaskRole654B2CE7", + }, + ], + }, + "Type": "AWS::IAM::Policy", + }, "CloudquerySourceOrgWideAutoScalingGroupsScheduledEventRuleC637A0C6": { "Properties": { "ScheduleExpression": "cron(0 0 * * ? *)", @@ -16064,6 +16695,33 @@ spec: "Type": "AWS::SecretsManager::Secret", "UpdateReplacePolicy": "Delete", }, + "ns1credentialsA8DD3B2D": { + "DeletionPolicy": "Delete", + "Properties": { + "GenerateSecretString": {}, + "Name": "/TEST/deploy/service-catalogue/ns1-credentials", + "Tags": [ + { + "Key": "gu:cdk:version", + "Value": "TEST", + }, + { + "Key": "gu:repo", + "Value": "guardian/service-catalogue", + }, + { + "Key": "Stack", + "Value": "deploy", + }, + { + "Key": "Stage", + "Value": "TEST", + }, + ], + }, + "Type": "AWS::SecretsManager::Secret", + "UpdateReplacePolicy": "Delete", + }, "repocop20553EB8": { "DependsOn": [ "repocopServiceRoleDefaultPolicyF20BF625", diff --git a/packages/cdk/lib/cloudquery/cluster.ts b/packages/cdk/lib/cloudquery/cluster.ts index 4eb440fb7..b61a50164 100644 --- a/packages/cdk/lib/cloudquery/cluster.ts +++ b/packages/cdk/lib/cloudquery/cluster.ts @@ -2,7 +2,7 @@ import { GuLoggingStreamNameParameter } from '@guardian/cdk/lib/constructs/core' import type { AppIdentity, GuStack } from '@guardian/cdk/lib/constructs/core'; import type { GuSecurityGroup } from '@guardian/cdk/lib/constructs/ec2'; import type { ISecurityGroup, IVpc } from 'aws-cdk-lib/aws-ec2'; -import { Cluster, Secret } from 'aws-cdk-lib/aws-ecs'; +import { Cluster, type RepositoryImage, Secret } from 'aws-cdk-lib/aws-ecs'; import type { Schedule } from 'aws-cdk-lib/aws-events'; import type { IManagedPolicy } from 'aws-cdk-lib/aws-iam'; import { Effect, PolicyStatement } from 'aws-cdk-lib/aws-iam'; @@ -83,6 +83,17 @@ export interface CloudquerySource { * @default false */ runAsSingleton?: boolean; + + /** + * The image of a CloudQuery plugin that is distributed via Docker, + * i.e. plugins not written in Go. + * + * This image will be run on its own, exposing the GRPC server on localhost:7777. + * The CloudQuery source config should be configured with a registry of grpc, and path of localhost:7777. + * + * @see https://docs.cloudquery.io/docs/reference/source-spec + */ + dockerDistributedPluginImage?: RepositoryImage; } interface CloudqueryClusterProps extends AppIdentity { @@ -160,6 +171,7 @@ export class CloudqueryCluster extends Cluster { cpu, additionalSecurityGroups, runAsSingleton = false, + dockerDistributedPluginImage, }) => { new ScheduledCloudqueryTask(scope, `CloudquerySource-${name}`, { ...taskProps, @@ -178,6 +190,7 @@ export class CloudqueryCluster extends Cluster { cloudqueryApiKey, 'api-key', ), + dockerDistributedPluginImage, }); }, ); diff --git a/packages/cdk/lib/cloudquery/config.ts b/packages/cdk/lib/cloudquery/config.ts index d078b2d14..8525de2aa 100644 --- a/packages/cdk/lib/cloudquery/config.ts +++ b/packages/cdk/lib/cloudquery/config.ts @@ -204,6 +204,27 @@ export function galaxiesSourceConfig(bucketName: string): CloudqueryConfig { }; } +export function ns1SourceConfig(): CloudqueryConfig { + return { + kind: 'source', + spec: { + name: 'ns1', + registry: 'grpc', + path: 'localhost:7777', + + // This property is required, but only relevant for GitHub hosted plugins. + // Use a fake value to satisfy the config parser. + // See https://docs.cloudquery.io/docs/reference/source-spec#version + version: 'v0.0.0', + tables: ['ns1_*'], + destinations: ['postgresql'], + spec: { + apiKey: '${NS1_API_KEY}', + }, + }, + }; +} + export function riffraffSourcesConfig(): CloudqueryConfig { return { kind: 'source', diff --git a/packages/cdk/lib/cloudquery/images.ts b/packages/cdk/lib/cloudquery/images.ts index 8fdfe080e..e8089e09e 100644 --- a/packages/cdk/lib/cloudquery/images.ts +++ b/packages/cdk/lib/cloudquery/images.ts @@ -9,4 +9,9 @@ export const Images = { amazonLinux: ContainerImage.fromRegistry( 'public.ecr.aws/amazonlinux/amazonlinux:latest', ), + + // https://github.com/guardian/cq-source-ns1 + ns1Source: ContainerImage.fromRegistry( + `ghcr.io/guardian/cq-source-ns1:${Versions.CloudqueryNs1}`, + ), }; diff --git a/packages/cdk/lib/cloudquery/index.ts b/packages/cdk/lib/cloudquery/index.ts index 0d6bbd435..b7ad9775a 100644 --- a/packages/cdk/lib/cloudquery/index.ts +++ b/packages/cdk/lib/cloudquery/index.ts @@ -20,10 +20,12 @@ import { githubLanguagesConfig, githubSourceConfig, guardianSnykSourceConfig, + ns1SourceConfig, riffraffSourcesConfig, skipTables, snykSourceConfig, } from './config'; +import { Images } from './images'; import { cloudqueryAccess, listOrgsPolicy, readBucketPolicy } from './policies'; interface CloudqueryEcsClusterProps { @@ -503,6 +505,21 @@ export function addCloudqueryEcsCluster( // additionalCommands: additionalGithubCommands, }; + const ns1ApiKey = new SecretsManager(scope, 'ns1-credentials', { + secretName: `/${stage}/${stack}/${app}/ns1-credentials`, + }); + + const ns1Source: CloudquerySource = { + name: 'NS1', + description: 'DNS records from NS1', + schedule: nonProdSchedule ?? Schedule.cron({ minute: '0', hour: '0' }), + dockerDistributedPluginImage: Images.ns1Source, + secrets: { + NS1_API_KEY: Secret.fromSecretsManager(ns1ApiKey, 'api-key'), + }, + config: ns1SourceConfig(), + }; + new CloudqueryCluster(scope, `${app}Cluster`, { app, vpc, @@ -517,6 +534,7 @@ export function addCloudqueryEcsCluster( ...snykSources, riffRaffSources, githubLanguagesSource, + ns1Source, ], }); } diff --git a/packages/cdk/lib/cloudquery/task.ts b/packages/cdk/lib/cloudquery/task.ts index c28b28f1d..3bf01a3b2 100644 --- a/packages/cdk/lib/cloudquery/task.ts +++ b/packages/cdk/lib/cloudquery/task.ts @@ -2,7 +2,6 @@ import type { AppIdentity, GuStack } from '@guardian/cdk/lib/constructs/core'; import type { GuSecurityGroup } from '@guardian/cdk/lib/constructs/ec2'; import { Tags } from 'aws-cdk-lib'; import type { ISecurityGroup } from 'aws-cdk-lib/aws-ec2'; -import type { Cluster } from 'aws-cdk-lib/aws-ecs'; import { ContainerDependencyCondition, FargateTaskDefinition, @@ -11,6 +10,7 @@ import { LogDrivers, Secret, } from 'aws-cdk-lib/aws-ecs'; +import type { Cluster, RepositoryImage } from 'aws-cdk-lib/aws-ecs'; import type { ScheduledFargateTaskProps } from 'aws-cdk-lib/aws-ecs-patterns'; import { ScheduledFargateTask } from 'aws-cdk-lib/aws-ecs-patterns'; import type { IManagedPolicy, PolicyStatement } from 'aws-cdk-lib/aws-iam'; @@ -101,6 +101,17 @@ export interface ScheduledCloudqueryTaskProps * @see https://cloud.cloudquery.io/teams/the-guardian/api-keys */ cloudQueryApiKey: Secret; + + /** + * The image of a CloudQuery plugin that is distributed via Docker, + * i.e. plugins not written in Go. + * + * This image will be run on its own, exposing the GRPC server on localhost:7777. + * The CloudQuery source config should be configured with a registry of grpc, and path of localhost:7777. + * + * @see https://docs.cloudquery.io/docs/reference/source-spec + */ + dockerDistributedPluginImage?: RepositoryImage; } export class ScheduledCloudqueryTask extends ScheduledFargateTask { @@ -125,6 +136,7 @@ export class ScheduledCloudqueryTask extends ScheduledFargateTask { additionalSecurityGroups = [], runAsSingleton, cloudQueryApiKey, + dockerDistributedPluginImage, } = props; const { region, stack, stage } = scope; const thisRepo = 'guardian/service-catalogue'; // TODO get this from GuStack @@ -201,6 +213,22 @@ export class ScheduledCloudqueryTask extends ScheduledFargateTask { logging: fireLensLogDriver, }); + if (dockerDistributedPluginImage) { + const additionalCloudQueryContainer = task.addContainer( + `${id}PluginContainer`, + { + image: dockerDistributedPluginImage, + logging: fireLensLogDriver, + essential: false, + }, + ); + + cloudqueryTask.addContainerDependencies({ + container: additionalCloudQueryContainer, + condition: ContainerDependencyCondition.START, + }); + } + if (runAsSingleton) { const operationInProgress = 114; const success = 0; diff --git a/packages/cdk/lib/cloudquery/versions.ts b/packages/cdk/lib/cloudquery/versions.ts index 30b4a5d1f..4f7e6a00a 100644 --- a/packages/cdk/lib/cloudquery/versions.ts +++ b/packages/cdk/lib/cloudquery/versions.ts @@ -27,4 +27,5 @@ export const Versions = { CloudquerySnyk: envOrError('CQ_SNYK'), CloudquerySnykGuardian: envOrError('CQ_GUARDIAN_SNYK'), CloudqueryGithubLanguages: envOrError('CQ_GITHUB_LANGUAGES'), + CloudqueryNs1: envOrError('CQ_NS1'), };