i am trying to create multiple grafana-instances with slightly different config-files with tanka. The following works, as long as the the configmap.grafana_ini is in-place. But this becomes very unreadable with a growing config. So i am looking for a way to move the configmaps to their own file and import.
But if i move that to it's own file and use an import/str i am getting a "computed imports are not allowed" error or the instance-variable becomes unknown.
local tanka = import 'github.com/grafana/jsonnet-libs/tanka-util/main.libsonnet';
local helm = tanka.helm.new(std.thisFile);
local k = import 'github.com/grafana/jsonnet-libs/ksonnet-util/kausal.libsonnet';
(import 'config.libsonnet') +
{
local configMap = k.core.v1.configMap,
local container = k.core.v1.container,
local stateful = k.apps.v1.statefulSet,
local ingrdatasourcesess = k.networking.v1.ingress,
local port = k.core.v1.containerPort,
local service = k.core.v1.service,
local pvc = k.core.v1.persistentVolumeClaim,
local ports = [port.new('http', 3000)],
grafana: {
g(instance):: {
local this = self,
deployment:
stateful.new(
name='grafana-' + instance.handle,
replicas=1,
containers=[
container.new(
name='grafana-' + instance.handle,
image=$._config.grafana.image + instance.theme + ':' + $._config.grafana.version
)
+ container.withPorts(ports),
],
)
+ stateful.metadata.withLabels({ 'io.kompose.service': 'grafana-' + instance.handle })
+ stateful.configMapVolumeMount(this.configMaps.grafana_ini, '/etc/grafana/grafana.ini', k.core.v1.volumeMount.withSubPath('grafana.ini'))
+ stateful.spec.withServiceName('grafana-' + instance.handle)
+ stateful.spec.selector.withMatchLabels({ 'io.kompose.service': 'grafana-' + instance.handle })
+ stateful.spec.template.metadata.withLabels({ 'io.kompose.service': 'grafana-' + instance.handle })
+ stateful.spec.template.spec.withImagePullSecrets({
name: 'registry.gitlab.com',
})
+ stateful.spec.template.spec.withRestartPolicy('Always'),
service:
k.util.serviceFor(self.deployment)
+ service.mixin.spec.withType('ClusterIP'),
configMaps: {
grafana_ini:
configMap.new(
'grafana-ini-' + instance.handle, {
'grafana.ini': std.manifestIni(
{
main: {
app_mode: 'production',
instance_name: instance.handle,
},
sections: {
server: {
protocol: 'http',
http_port: '3000',
domain: 'dashboard.' + $._config.ingress.realm + '.' + $._config.ingress.tld + '/' + instance.handle + '/',
root_url: $._config.ingress.protocol + 'dashboard.' + $._config.ingress.realm + '.' + $._config.ingress.tld + '/' + instance.handle + '/',
serve_from_sub_path: true,
},
},
}
),
}
),
},
},
deploys: [self.g(instance) for instance in $._config.grafana.instances],
},
}
Here is the config-part:
{
_config+:: {
grafana+: {
image: 'registry.gitlab.com/xxx/frontend/grafana/',
version: 'v7.3.7',
client_secret: 'xyz',
adminusername: 'admin',
adminpassword: 'admin',
instances: [
{
name: "xxx's Grafana",
handle: 'xyz',
theme: 'xxx',
alerting: 'false',
volume_size: '200M',
default: true,
allow_embedding: false,
public: 'false',
secret_key: 'xxxx',
email: {
host: '',
user: '',
password: '',
from_address: '',
from_name: '',
},
datasources: [
{
name: 'xxx Showcase',
type: 'influxdb',
access: 'proxy',
url: 'http://influx:8086',
database: 'test123',
user: 'admin',
password: 'admin',
editable: false,
isDefault: false,
version: 1
},
],
dashboards: [
{
src: 'provisioning/dashboards/xxx_showcase_dashboard.json',
datasource: 'xxx Showcase',
title: 'xxx office building',
template: true,
},
],
},
],
},
},
}
EDITED, as suggested by 2nd post.
greetings,
strowi
Thanks for clarifying, find below a possible solution, note I trimmed down parts of your original files, for better readability.
I think that the main highlight here is the iniFile() function, so that we can explicitly pass (config, instance) to it.
main.jsonnet
local tanka = import 'github.com/grafana/jsonnet-libs/tanka-util/main.libsonnet';
local helm = tanka.helm.new(std.thisFile);
local k = import 'github.com/grafana/jsonnet-libs/ksonnet-util/kausal.libsonnet';
(import 'config.libsonnet') +
{
local configMap = k.core.v1.configMap,
local container = k.core.v1.container,
local stateful = k.apps.v1.statefulSet,
local ingrdatasourcesess = k.networking.v1.ingress,
local port = k.core.v1.containerPort,
local service = k.core.v1.service,
local pvc = k.core.v1.persistentVolumeClaim,
local ports = [port.new('http', 3000)],
grafana: {
g(instance):: {
local this = self,
/* <snip...> */
configMaps: {
local inilib = import 'ini.libsonnet',
grafana_ini:
configMap.new(
'grafana-ini-' + instance.handle, inilib.iniFile($._config, instance)
),
},
},
deploys: [self.g(instance) for instance in $._config.grafana.instances],
},
}
config.libsonnet
{
_config+:: {
// NB: added below dummy ingress field
ingress:: {
realm:: 'bar',
tld:: 'foo.tld',
protocol:: 'tcp',
},
grafana+: {
image: 'registry.gitlab.com/xxx/frontend/grafana/',
version: 'v7.3.7',
client_secret: 'xyz',
adminusername: 'admin',
adminpassword: 'admin',
instances: [
{
name: "xxx's Grafana",
handle: 'xyz',
/* <snip...> */
},
],
},
},
}
ini.libsonnet
{
iniFile(config, instance):: {
'grafana.ini': std.manifestIni(
{
main: {
app_mode: 'production',
instance_name: instance.handle,
},
sections: {
server: {
protocol: 'http',
http_port: '3000',
domain: 'dashboard.' + config.ingress.realm + '.' + config.ingress.tld + '/' + instance.handle + '/',
root_url: config.ingress.protocol + 'dashboard.' + config.ingress.realm + '.' + config.ingress.tld + '/' + instance.handle + '/',
serve_from_sub_path: true,
},
},
}
),
},
}
Related
1- I have a project core-infra that encapasses all the core infra related compoents (VPCs, Subnets, ECS Cluster...etc)
2- I have microservice projects with independant stacks each used for deployment
I want to deploy a FargateService from a microservice project stack A to the already existing ECS living within the core-infra stack
Affected area/feature
Pulumi Service
ECS
Deploy microservice
FargateService
Pulumi github issue link
Pulumi Stack References are the answer here:
https://www.pulumi.com/docs/intro/concepts/stack/#stackreferences
Your core-infra stack would output the ECS cluster ID and then stack B consumes that output so it can, for example, deploy an ECS service to the given cluster
(https://www.pulumi.com/registry/packages/aws/api-docs/ecs/service/).
I was able to deploy using aws classic.
PS: The setup is way more complex than with the awsx, the doc and resources aren't exhaustive.
Now I have few issues:
The Loadbalancer isn't reachable and keeps loading forever
I don't have any logs in the CloudWatch LogGoup
Not sure how to use the LB Listner with the ECS service / Not sure about the port mapping
Here is the complete code for reference (people who're husteling) and I'd appreciate if you could suggest any improvments/answers.
// Capture the EnvVars
const appName = process.env.APP_NAME;
const namespace = process.env.NAMESPACE;
const environment = process.env.ENVIRONMENT;
// Load the Deployment Environment config.
const configMapLoader = new ConfigMapLoader(namespace, environment);
const env = pulumi.getStack();
const infra = new pulumi.StackReference(`org/core-datainfra/${env}`);
// Fetch ECS Fargate cluster ID.
const ecsClusterId = infra.getOutput('ecsClusterId');
// Fetch DeVpc ID.
const deVpcId = infra.getOutput('deVpcId');
// Fetch DeVpc subnets IDS.
const subnets = ['subnet-aaaaaaaaaa', 'subnet-bbbbbbbbb'];
// Fetch DeVpc Security Group ID.
const securityGroupId = infra.getOutput('deSecurityGroupId');
// Define the Networking for our service.
const serviceLb = new aws.lb.LoadBalancer(`${appName}-lb`, {
internal: false,
loadBalancerType: 'application',
securityGroups: [securityGroupId],
subnets,
enableDeletionProtection: false,
tags: {
Environment: environment
}
});
const serviceTargetGroup = new aws.lb.TargetGroup(`${appName}-t-g`, {
port: configMapLoader.configMap.service.http.externalPort,
protocol: configMapLoader.configMap.service.http.protocol,
vpcId: deVpcId,
targetType: 'ip'
});
const http = new aws.lb.Listener(`${appName}-listener`, {
loadBalancerArn: serviceLb.arn,
port: configMapLoader.configMap.service.http.externalPort,
protocol: configMapLoader.configMap.service.http.protocol,
defaultActions: [
{
type: 'forward',
targetGroupArn: serviceTargetGroup.arn
}
]
});
// Create AmazonECSTaskExecutionRolePolicy
const taskExecutionPolicy = new aws.iam.Policy(
`${appName}-task-execution-policy`,
{
policy: JSON.stringify({
Version: '2012-10-17',
Statement: [
{
Effect: 'Allow',
Action: [
'ecr:GetAuthorizationToken',
'ecr:BatchCheckLayerAvailability',
'ecr:GetDownloadUrlForLayer',
'ecr:BatchGetImage',
'logs:CreateLogStream',
'logs:PutLogEvents',
'ec2:AuthorizeSecurityGroupIngress',
'ec2:Describe*',
'elasticloadbalancing:DeregisterInstancesFromLoadBalancer',
'elasticloadbalancing:DeregisterTargets',
'elasticloadbalancing:Describe*',
'elasticloadbalancing:RegisterInstancesWithLoadBalancer',
'elasticloadbalancing:RegisterTargets'
],
Resource: '*'
}
]
})
}
);
// IAM role that allows Amazon ECS to make calls to the load balancer
const taskExecutionRole = new aws.iam.Role(`${appName}-task-execution-role`, {
assumeRolePolicy: JSON.stringify({
Version: '2012-10-17',
Statement: [
{
Effect: 'Allow',
Principal: {
Service: ['ecs-tasks.amazonaws.com']
},
Action: 'sts:AssumeRole'
},
{
Action: 'sts:AssumeRole',
Principal: {
Service: 'ecs.amazonaws.com'
},
Effect: 'Allow',
Sid: ''
},
{
Action: 'sts:AssumeRole',
Principal: {
Service: 'ec2.amazonaws.com'
},
Effect: 'Allow',
Sid: ''
}
]
}),
tags: {
name: `${appName}-iam-role`
}
});
new aws.iam.RolePolicyAttachment(`${appName}-role-policy`, {
role: taskExecutionRole.name,
policyArn: taskExecutionPolicy.arn
});
// New image to be pulled
const image = `${configMapLoader.configMap.service.image.repository}:${process.env.IMAGE_TAG}`;
// Set up Log Group
const awsLogGroup = new aws.cloudwatch.LogGroup(`${appName}-awslogs-group`, {
name: `${appName}-awslogs-group`,
tags: {
Application: `${appName}`,
Environment: 'production'
}
});
const serviceTaskDefinition = new aws.ecs.TaskDefinition(
`${appName}-task-definition`,
{
family: `${appName}-task-definition`,
networkMode: 'awsvpc',
executionRoleArn: taskExecutionRole.arn,
requiresCompatibilities: ['FARGATE'],
cpu: configMapLoader.configMap.service.resources.limits.cpu,
memory: configMapLoader.configMap.service.resources.limits.memory,
containerDefinitions: JSON.stringify([
{
name: `${appName}-fargate`,
image,
cpu: parseInt(
configMapLoader.configMap.service.resources.limits.cpu
),
memory: parseInt(
configMapLoader.configMap.service.resources.limits.memory
),
essential: true,
portMappings: [
{
containerPort: 80,
hostPort: 80
}
],
environment: configMapLoader.getConfigAsEnvironment(),
logConfiguration: {
logDriver: 'awslogs',
options: {
'awslogs-group': `${appName}-awslogs-group`,
'awslogs-region': 'us-east-2',
'awslogs-stream-prefix': `${appName}`
}
}
}
])
}
);
// Create a Fargate service task that can scale out.
const fargateService = new aws.ecs.Service(`${appName}-fargate`, {
name: `${appName}-fargate`,
cluster: ecsClusterId,
taskDefinition: serviceTaskDefinition.arn,
desiredCount: 5,
loadBalancers: [
{
targetGroupArn: serviceTargetGroup.arn,
containerName: `${appName}-fargate`,
containerPort: configMapLoader.configMap.service.http.internalPort
}
],
networkConfiguration: {
subnets
}
});
// Export the Fargate Service Info.
export const fargateServiceName = fargateService.name;
export const fargateServiceUrl = serviceLb.dnsName;
export const fargateServiceId = fargateService.id;
export const fargateServiceImage = image;
i have tried using express-mysql-session
const MySQLStore = require('express-mysql-session')(session);
app.use(cookieParser())
const sessionStore = new MySQLStore(db);
const sessionConfig = {
name: 'session',
// store: sessionStore,
secret: '25658595',
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true,
expires: Date.now() + 1000 * 60 * 60 * 24 * 7,
maxAge: 1000 * 60 * 60 * 24 * 7
},
};
const mysql = require('mysql');
require('dotenv').config();
const connection = mysql.createConnection({
socketPath:'',
host: '',
user: '',
password: '',
database: '',
multipleStatements: true
})
connection.connect((err) => {
if (err) throw err;
console.log('Database Connected')
})
module.exports = connection;
When i able session store property the server respond with an error 502 and when i disable session store property all things work fine
Warning: connect.session() MemoryStore is not
designed for a production environment, as it will leak
memory, and will not scale past a single process.
on port 8081
Because of this message i have used express-mysql-session
I have the following AWS CDK configuration in TypeScript (abriged):
const jobProps = {
command: {
name: 'glueetl',
pythonVersion: '3',
scriptLocation: `s3://${s3bucket.bucketName}/${this.scriptName}`,
},
connections: { connections: [connectionName] },
defaultArguments: { },
description: idEnv + '-job',
executionProperty: {
maxConcurrentRuns: 1,
},
glueVersion: '2.0',
maxRetries: 0,
name: idEnv + '-job',
numberOfWorkers: 2,
role: glueServiceRole.roleArn,
timeout: 180, // minutes
workerType: 'Standard',
};
const job = new CfnJob(this, idEnv, jobProps);
const trigger = new CfnTrigger(this, idEnv + '-trigger', {
type: 'SCHEDULED',
description: 'Scheduled run for ' + job.name,
schedule: this.JOB_SCHEDULE,
actions: [
{
jobName: job.name,
},
],
});
The trigger is created, it is seen in the Console and it is linked to the Job. But it just won't run (manual Job run is OK). What am I missing?
You need to add "startOnCreation: true" to the CfnTrigger props, so the trigger status will be enabled by default.
Creating an API Gateway I get this error:
api-mapping (apimappingXXXXXXX) Only one base path mapping is allowed
if the base path is empty. (Service: AmazonApiGateway; Status Code:
409; Error Code: ConflictException;
where my code is:
// External API Gateway
const externalApi = new apigateway.RestApi(this, 'external-api-gw',
{
apiKeySourceType: apigateway.ApiKeySourceType.AUTHORIZER,
restApiName: 'external-api',
deploy: false,
endpointConfiguration: {
...
},
policy: new iam.PolicyDocument({
statements: [
..
],
}),
]
})
}
);
const domainName = externalApi.addDomainName('domain-name', {
domainName: props.apigatewayRecordName + '.' + props.hostedZone,
certificate: existingCertificate,
endpointType: apigateway.EndpointType.REGIONAL,
});
const myApiGateway = new route53targets.ApiGateway(externalApi);
// deployment
const apiDeployment = new apigateway.Deployment(this, 'deployment', {
api: externalApi
});
// stage
const apiStage = new apigateway.Stage(this, 'stage', {
stageName: 'api',
accessLogDestination: new apigateway.LogGroupLogDestination(logGroup),
accessLogFormat: apigateway.AccessLogFormat.jsonWithStandardFields(),
loggingLevel: apigateway.MethodLoggingLevel.INFO,
dataTraceEnabled: true,
deployment: apiDeployment
});
externalApi.deploymentStage = apiStage;
domainName.addBasePathMapping(externalApi, { basePath: 'api', stage: apiStage} );
It seems that an empty base path mapping is created automatically and the second one cannot be added.
Any suggestions, please?
As mentioned in comments, Below you can find the working code snippet:
Create CfnDomainName:
// import relevant data from ssm
const certificateArn = ssm.StringParameter.valueFromLookup(this, 'CertificateArn');
const domainNameURL = ssm.StringParameter.valueFromLookup(this, 'ApiCustomDomainUrl');
const certificate = cert.Certificate.fromCertificateArn(this, 'Certificate', certificateArn);
// create DomainName
const domainName = new apigateway.DomainName(this, 'DomainName', {
domainName: domainNameURL,
certificate: certificate ,
endpointType: apigateway.EndpointType.REGIONAL,
});
Add base path mapping:
// create the api
const api = new apigateway.RestApi(this, id, {
restApiName: 'API GW ' + id,
deployOptions: {
stageName: 'dev',
},
endpointTypes: [apigateway.EndpointType.REGIONAL]
});
// add new base path to domain name
new apigateway.BasePathMapping(this, 'my-mapping', {
domainName: domainName,
restApi: api,
basePath: 'my-mapping'
});
// add new base path to domain name
new apigateway.BasePathMapping(this, 'my-mapping-two', {
domainName: domainName,
restApi: api,
basePath: 'my-mapping-two'
});
More about BasePathMapping , DomainName.
Not sure if something changed over the versions, but this does not work with 1.57 of the CDK.
Will generate: "Error: API does not define a default name"
conf.js
var MailListener = require("mail-listener2");
var mailListener = new MailListener({
username: "*****#office365.com",
password: "******",
host: "outlook.office365.com",
port: 993, // imap port
tls: true,
fetchUnreadOnStart: true,
tlsOptions: {rejectUnauthorized: false},
mailbox: "INBOX",
searchFilter: "UNSEEN",
markSeen: true
});
mailListener.on("server:connected", function () {
console.log("imapConnected");
});
mailListener.on("server:disconnected", function () {
console.log("imapDisconnected");
});
(function () {
var count = 0;
mailListener.on("mail", function (mail, seqno, attributes) {
var mailuid = attributes.uid,
toMailbox = 'Inbox',
i = ++count;
if (i > 1) {
mailListener.stop(); // start listening
return;
}
console.log('email parsed', {
i: i,
subject: mail.subject,
from: mail.from,
text:mail.text,
seqno: seqno,
uid: attributes.uid,
attributes: attributes
});
expect(mail.subject).toEqual("FW: Secure One-Time-Password for Account Login");
var pattern = new RegExp(/Please use (\w+)/g);
var regCode = pattern.exec(mail.text)[1];
console.log(regCode);
console.log('attempting to mark msg read/seen');
mailListener.imap.addFlags(mailuid, '\\Seen', function (err) {
if (err) {
console.log('error marking message read/SEEN');
return;
}
//console.log('moving ' + (seqno || '?') + ' to ' + toMailbox);
//mailListener.imap.move(mailuid, toMailbox, function (err) {
if (err) {
console.log('error moving message');
return;
}
console.log('moved ' + (seqno || '?'), mail.subject);
});
});
});
})
();
mailListener.start(); // start listening
setTimeout(function () {
mailListener.stop(); // start listening
}, 60 * 1000);
I am reading all the details except text and the text is in html table format.
Instead of text i am getting undefined message.If needed i will add html code also.
If i am forwarding the same mail to gmail from office 365 and reading the mail from gmail i am able to get text.
Error:
subject: 'test mail',
from: [ { address: 'otp#gmail.com', name: 'gmail.com' } ],
body: undefined,
seqno: 2,
uid: 18,
attributes:
{ date: 2017-06-14T16:22:06.000Z,
flags: [ '\\Seen' ],
uid: 18,
modseq: '3914',
'x-gm-labels': [],
'x-gm-msgid': '1570197813730673685',
'x-gm-thrid': '1570197813730673685' } }
[21:56:13] E/launcher - Cannot read property '1' of null
[21:56:13] E/launcher - TypeError: Cannot read property '1' of null
I see that this is an old issue, but I am facing the same problem and using the nearly the same function.
I want to get the content of the e-mail with a link and the email is in HTML. So: mail.text doesn't and won't work in this case.
Solution is really simple and it works for me straight forward: mail.html