<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Apache DevLake - Open-Source Dev Data Platform for Productivity Blog</title>
        <link>https://devlake.apache.org/blog</link>
        <description>Apache DevLake - Open-Source Dev Data Platform for Productivity Blog</description>
        <lastBuildDate>Thu, 03 Apr 2025 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <item>
            <title><![CDATA[Setting up Devlake in AWS ECS cluster using Terraform]]></title>
            <link>https://devlake.apache.org/blog/setting-up-devlake-in-aws-ecs-cluster-using-terraform</link>
            <guid>setting-up-devlake-in-aws-ecs-cluster-using-terraform</guid>
            <pubDate>Thu, 03 Apr 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[This guide provides an alternative to Devlake's Kubernetes setup and will walk you through setting up DevLake in an AWS ECS (Elastic Container Service) cluster using Terraform, providing you with a scalable and maintainable infrastructure as code solution that is also cost effective.]]></description>
            <content:encoded><![CDATA[<p>This guide provides an alternative to Devlake's Kubernetes setup and will walk you through setting up DevLake in an AWS ECS (Elastic Container Service) cluster using Terraform, providing you with a scalable and maintainable infrastructure as code solution that is also cost effective.</p><p>Basic knowledge of AWS ECS, Terraform, and your network where your cluster will be deployed is necessary to follow this guide effectively.</p><h1>Setting up terraform structure</h1><p>To configure devlake, we will use a terraform module and pass the necessary variables. The final structure of the module would be something like this:</p><div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">terraform-root/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├── main.tf</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├── ...</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">├── modules/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   ├── devlake/</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │   ├── main.tf</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │   ├── variables.tf</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │   ├── outputs.tf</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │   ├── rds.tf</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │   ├── alb.tf</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │   ├── efs.tf</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │   ├── iam.tf</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │   ├── logs.tf</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │   ├── security.tf</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │   └── providers.tf</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│   │</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">As we go in this tutorial, we will be populating individual files.</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>The AWS provider that is being used is <code>version = "~&gt; 5.91.0"</code>.</p><p>Fist of, lets populate the <code>variables.tf</code>:</p><div class="language-terraform codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-terraform codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">variable "aws_region" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  description = "AWS region where resources will be created"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  type        = string</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  default     = "YOUR-AWS-REGION" </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">variable "tag_name" { # for cost monitoring</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  description = "Name tag for all DevLake resources"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  type        = string</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  default     = "devlake"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><h1>High level architecture</h1><p>The goal is to create an AWS ECS cluster running on fargate spot instances, with an RDS mysql database and efs for grafana as a storage layer. We also need an application load balancer on top which we can optionally configure authentication (like OKTA).</p><p><img loading="lazy" alt="Architecture Image" src="/assets/images/architecture-1587b6206b9702c0989bfb3131be8e1a.png" width="895" height="671" class="img_ev3q"></p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="storage">Storage<a class="hash-link" href="#storage" title="Direct link to heading">​</a></h2><p>Let's start with provisioning the necessary storage layer. First of, let's create a database and tis security group in the <code>rds.tf</code> file. For that we will need the subnets that you want to deploy your database, the vpc id and the database password (adding them to the <code>variable.tf</code>):</p><div class="language-terraform codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-terraform codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">module "devlake-rds" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  source  = "terraform-aws-modules/rds/aws"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  version = "~&gt; 6.10.0"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  identifier = "devlake-db"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  engine               = "mysql"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  engine_version       = "8.0"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  instance_class       = "db.t3.micro"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  allocated_storage    = 20</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  major_engine_version = "8.0"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  family               = "mysql8.0"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  manage_master_user_password = false </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  db_name                     = "devlake"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  username                    = "devlake"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  password                    = var.db_password # todo</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  port                        = 3306</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  create_db_subnet_group = true</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  subnet_ids             = var.private_subnets # todo</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  vpc_security_group_ids = [aws_security_group.rds.id]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  skip_final_snapshot = true</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  enabled_cloudwatch_logs_exports = ["error", "general", "slowquery"]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  create_cloudwatch_log_group     = true</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  tags = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Name = var.tag_name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">resource "aws_security_group" "rds" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  name        = "devlake-rds"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  description = "Security group for DevLake RDS"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  vpc_id      = var.vpc_id # todo</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  tags = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Name = var.tag_name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ingress {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    from_port       = 3306</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    to_port         = 3306</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    protocol        = "tcp"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    security_groups = [aws_security_group.ecs_tasks.id]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>And also let's create the EFS for grafana and its own security group in <code>efs.tf</code>. Since we know that grafana will also need to access this efs, lets go ahead and also create the access point and mount targets:</p><div class="language-terraform codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-terraform codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">resource "aws_efs_file_system" "grafana" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  creation_token = "grafana-storage"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  encrypted      = true</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  lifecycle_policy {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    transition_to_ia = "AFTER_30_DAYS"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  tags = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Name = var.tag_name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">resource "aws_efs_access_point" "grafana" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  file_system_id = aws_efs_file_system.grafana.id</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  posix_user {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    gid = 472</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    uid = 472</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  root_directory {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    path = "/var/lib/grafana" # from devlake documentation</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    creation_info { # this is necessary to give write permissions</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      owner_gid   = 472</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      owner_uid   = 472</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      permissions = "755"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  tags = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Name = var.tag_name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">resource "aws_efs_mount_target" "grafana" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  count           = length(var.private_subnets)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  file_system_id  = aws_efs_file_system.grafana.id</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  subnet_id       = var.private_subnets[count.index]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  security_groups = [aws_security_group.efs.id]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">resource "aws_security_group" "efs" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  name        = "grafana-efs"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  description = "Security group for Grafana EFS mount targets"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  vpc_id      = var.vpc_id</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  tags = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Name = var.tag_name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ingress {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    from_port       = 2049</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    to_port         = 2049</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    protocol        = "tcp"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    security_groups = [aws_security_group.ecs_tasks.id]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">} </span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><h2 class="anchor anchorWithStickyNavbar_LWe7" id="cluster">Cluster<a class="hash-link" href="#cluster" title="Direct link to heading">​</a></h2><p>Let's start with creating a cluster in the module's <code>main.tf</code>:</p><div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">resource "aws_ecs_cluster" "devlake" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  name = "devlake-cluster"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  setting {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    name  = "containerInsights"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    value = "enabled"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  tags = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Name = var.tag_name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>We also need to make sure that the services can talk to each other, therefore we need to create a service discovery namespace. This is crucial to allow for proper host resolution (side note, I also tested this with service connect and it did not work):</p><div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">resource "aws_service_discovery_private_dns_namespace" "devlake" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  name        = "devlake-ns"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  description = "Private DNS namespace for DevLake services"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  vpc         = var.vpc_id</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  tags = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Name = var.tag_name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># one for each service</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">resource "aws_service_discovery_service" "devlake" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  name = "devlake" // "config-ui" // "grafana"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  dns_config {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    namespace_id = aws_service_discovery_private_dns_namespace.devlake.id</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    dns_records {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      ttl  = 10</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      type = "A"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    routing_policy = "MULTIVALUE"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  health_check_custom_config {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    failure_threshold = 1</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  tags = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Name = var.tag_name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><h3 class="anchor anchorWithStickyNavbar_LWe7" id="permissions">Permissions<a class="hash-link" href="#permissions" title="Direct link to heading">​</a></h3><p>We also need to create IAM roles with necessary permissions to run our cluster. And a separate task role for the grafana container for efs permissions:</p><div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">resource "aws_iam_role" "ecs_task_execution_role" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  name = "devlake-ecs-task-execution-role"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  assume_role_policy = jsonencode({</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Version = "2012-10-17"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Statement = [</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        Action = "sts:AssumeRole"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        Effect = "Allow"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        Principal = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          Service = "ecs-tasks.amazonaws.com"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  })</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  tags = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Name = var.tag_name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">resource "aws_iam_role_policy_attachment" "ecs_task_execution_role_policy" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  role       = aws_iam_role.ecs_task_execution_role.name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">resource "aws_iam_role_policy_attachment" "ecs_task_execution_role_policy_cloudwatch" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  role       = aws_iam_role.ecs_task_execution_role.name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  policy_arn = "arn:aws:iam::aws:policy/CloudWatchLogsFullAccess"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># IAM Task Role for Grafana container</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">resource "aws_iam_role" "grafana_task_role" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  name = "devlake-grafana-task-role"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  assume_role_policy = jsonencode({</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Version = "2012-10-17"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Statement = [</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        Action = "sts:AssumeRole"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        Effect = "Allow"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        Principal = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          Service = "ecs-tasks.amazonaws.com"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  })</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  tags = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Name = var.tag_name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"># Policy allowing Grafana task to access EFS</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">resource "aws_iam_role_policy" "grafana_efs_access_policy" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  name = "grafana-efs-access-policy"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  role = aws_iam_role.grafana_task_role.id</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  policy = jsonencode({</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Version = "2012-10-17"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Statement = [</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        Effect = "Allow"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        Action = [</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          "elasticfilesystem:ClientMount",</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          "elasticfilesystem:ClientWrite",</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          "elasticfilesystem:DescribeAccessPoints",</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          "elasticfilesystem:DescribeFileSystems"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        ]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        Resource = "*"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    ]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  })</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><h3 class="anchor anchorWithStickyNavbar_LWe7" id="task-definition">Task definition<a class="hash-link" href="#task-definition" title="Direct link to heading">​</a></h3><p>Since the task definition is more or less identical, I will provide an example for one and just the differences in configuration for others.</p><div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">resource "aws_ecs_task_definition" "devlake" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  family                   = "devlake"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  network_mode             = "awsvpc"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  requires_compatibilities = ["FARGATE"]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  cpu                      = 256</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  memory                   = 512</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  execution_role_arn       = aws_iam_role.ecs_task_execution_role.arn</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  container_definitions = jsonencode([</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      name  = "devlake"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      image = "devlake.docker.scarf.sh/apache/devlake:v1.0.1"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      portMappings = [</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          name          = "devlake-port" </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          containerPort = 8080</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          hostPort      = 8080</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          protocol      = "tcp"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      ]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      environment = [</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          name  = "DB_URL"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          value = "mysql://devlake:${var.db_password}@${module.devlake-rds.db_instance_endpoint}/devlake?charset=utf8mb4&amp;parseTime=True&amp;loc=UTC"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        },</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          name  = "LOGGING_DIR"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          value = "/app/logs"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        },</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          name  = "TZ"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          value = "UTC"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        },</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          name  = "ENCRYPTION_SECRET"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          value = var.encryption_secret # your encryption secret</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        },</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      ]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      logConfiguration = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        logDriver = "awslogs"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        options = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          "awslogs-group"         = aws_cloudwatch_log_group.devlake.name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          "awslogs-region"        = var.aws_region</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          "awslogs-stream-prefix" = "devlake"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ])</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  tags = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Name = var.tag_name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>for config-ui, all is same except for the definition. Take a note on how the endpoints look like, as they are using the namespace we created before:</p><div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">resource "aws_ecs_task_definition" "config_ui" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  family                   = "config-ui"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ...</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  container_definitions = jsonencode([</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      name  = "config-ui"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      image = "devlake.docker.scarf.sh/apache/devlake-config-ui:latest"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      portMappings = [</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          name          = "config-ui-port" </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          containerPort = 4000</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          hostPort      = 4000</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          protocol      = "tcp"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      ]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      environment = [</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          "name" : "DEVLAKE_ENDPOINT",</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          "value" : "devlake.devlake-ns:8080"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        },</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          name  = "GRAFANA_ENDPOINT"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          value = "grafana.devlake-ns:3000"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        },</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          name  = "TZ"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          value = "UTC"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      ]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      logConfiguration = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        logDriver = "awslogs"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        options = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          "awslogs-group"         = aws_cloudwatch_log_group.config_ui.name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          "awslogs-region"        = var.aws_region</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          "awslogs-stream-prefix" = "config-ui"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ])</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ...</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>and for grafana, we need to define the task role as well as the volume mount:</p><div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">resource "aws_ecs_task_definition" "grafana" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  family                   = "grafana"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ...</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  task_role_arn            = aws_iam_role.grafana_task_role.arn</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  container_definitions = jsonencode([</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      name  = "grafana"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      image = "devlake.docker.scarf.sh/apache/devlake-dashboard:v1.0.1"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      portMappings = [</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          name          = "grafana-port" </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          containerPort = 3000</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          hostPort      = 3000</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          protocol      = "tcp"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      ]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      environment = [</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          name  = "GF_SERVER_ROOT_URL"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          value = "https://devlake.${var.domain_name}/grafana"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        },</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          name  = "TZ"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          value = "UTC"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        },</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          name  = "MYSQL_URL"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          value = module.devlake-rds.db_instance_endpoint</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        },</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          name  = "MYSQL_DATABASE"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          value = "devlake"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        },</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          name  = "MYSQL_USER"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          value = "devlake"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        },</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          name  = "MYSQL_PASSWORD"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          value = var.db_password</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      ]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      mountPoints = [</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          sourceVolume  = "grafana-storage"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          containerPath = "/var/lib/grafana"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          readOnly      = false</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      ]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      logConfiguration = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        logDriver = "awslogs"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        options = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          "awslogs-group"         = aws_cloudwatch_log_group.grafana.name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          "awslogs-region"        = var.aws_region</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          "awslogs-stream-prefix" = "grafana"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ])</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  volume {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    name = "grafana-storage"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    efs_volume_configuration {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      file_system_id     = aws_efs_file_system.grafana.id</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      root_directory     = "/"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      transit_encryption = "ENABLED"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      authorization_config {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        access_point_id = aws_efs_access_point.grafana.id</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        iam             = "ENABLED"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ...</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><h3 class="anchor anchorWithStickyNavbar_LWe7" id="security">Security<a class="hash-link" href="#security" title="Direct link to heading">​</a></h3><p>To create the service we also need to create a task security group in <code>security.tf</code>. </p><div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">resource "aws_security_group" "ecs_tasks" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  name        = "devlake-ecs-tasks"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  description = "Security group for ECS tasks"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  vpc_id      = var.management_vpc.vpc_id</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ingress {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    from_port   = 8080</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    to_port     = 8080</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    protocol    = "tcp"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    self        = true</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    description = "Allow traffic from other ECS tasks to DevLake API"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  egress {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    from_port   = 0</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    to_port     = 0</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    protocol    = "-1"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    cidr_blocks = ["0.0.0.0/0"]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  tags = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Name = var.tag_name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>We also need to add ingress rules to the RDS and EFS security groups in <code>rds.tf</code> and <code>efs.tf</code> so that the tasks can reach these services.</p><div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">resource "aws_security_group" "rds" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ...</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ingress {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    from_port       = 3306</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    to_port         = 3306</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    protocol        = "tcp"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    security_groups = [aws_security_group.ecs_tasks.id]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ...</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">resource "aws_security_group" "efs" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ...</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ingress {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    from_port       = 2049</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    to_port         = 2049</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    protocol        = "tcp"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    security_groups = [aws_security_group.ecs_tasks.id]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ...</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">} </span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><h3 class="anchor anchorWithStickyNavbar_LWe7" id="logs">Logs<a class="hash-link" href="#logs" title="Direct link to heading">​</a></h3><p>To ensure proper monitoring and debugging capabilities, we'll set up CloudWatch log groups for each service. </p><div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">resource "aws_cloudwatch_log_group" "devlake" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  name              = "/ecs/devlake"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  retention_in_days = 30</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  tags = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Name = var.tag_name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">resource "aws_cloudwatch_log_group" "config_ui" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  name              = "/ecs/config-ui"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  retention_in_days = 30</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  tags = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Name = var.tag_name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">resource "aws_cloudwatch_log_group" "grafana" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  name              = "/ecs/grafana"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  retention_in_days = 30</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  tags = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Name = var.tag_name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><h3 class="anchor anchorWithStickyNavbar_LWe7" id="services">Services<a class="hash-link" href="#services" title="Direct link to heading">​</a></h3><p>Now that we have our infrastructure and task definitions set up, let's create the ECS services. We'll deploy three services: DevLake, Config UI, and Grafana. Each service will use Fargate Spot instances for cost optimization and will be configured with service discovery for internal communication.</p><div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">resource "aws_ecs_service" "devlake" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  name            = "devlake"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  cluster         = aws_ecs_cluster.devlake.id</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  task_definition = aws_ecs_task_definition.devlake.arn</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  desired_count   = 1</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  health_check_grace_period_seconds = 120 # 2 minutes grace period for startup</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  capacity_provider_strategy {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    base              = 1</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    weight            = 100</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    capacity_provider = "FARGATE_SPOT"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  network_configuration {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    subnets         = var.management_vpc.private_subnets</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    security_groups = [aws_security_group.ecs_tasks.id]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  # Configure Service Discovery</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  service_registries {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    registry_arn = aws_service_discovery_service.devlake.arn</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  deployment_circuit_breaker {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    enable   = true</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    rollback = true</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  # Configure deployment settings</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  deployment_maximum_percent         = 100</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  deployment_minimum_healthy_percent = 0</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  propagate_tags = "SERVICE"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  tags = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Name = var.tag_name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>Take a note of the deployment setting in devlake service configuration. This is not needed for config ui or grafana, but only for devlake as only one instance od devlake can connect to DB, therefore we need to first kill the instance completely before starting a new one.</p><div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">resource "aws_ecs_service" "config_ui" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  name            = "config-ui"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  task_definition = aws_ecs_task_definition.config_ui.arn</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ...</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  service_registries {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    registry_arn = aws_service_discovery_service.config_ui.arn</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  # Wait for DevLake to be ready</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  depends_on = [aws_ecs_service.devlake]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">resource "aws_ecs_service" "grafana" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  name            = "grafana"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  task_definition = aws_ecs_task_definition.grafana.arn</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ...</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  service_registries {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    registry_arn = aws_service_discovery_service.grafana.arn</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  # Wait for both DevLake and Config UI to be ready</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  depends_on = [aws_ecs_service.devlake, aws_ecs_service.config_ui]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>At this point your containers should be up and running, without any errors regarding DB connection or write permissions to efs. Now we need to setup the load balancer and domain name.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="load-balancer">Load balancer<a class="hash-link" href="#load-balancer" title="Direct link to heading">​</a></h2><p>Load balancer helps us route secure traffic to correct containers. We want to have a human readable url, like <a href="https://devlake.YOUR-DOMAIN.com" target="_blank" rel="noopener noreferrer">https://devlake.YOUR-DOMAIN.com</a> to access config ui and <a href="https://devlake.YOUR-DOMAIN.com/grafana" target="_blank" rel="noopener noreferrer">https://devlake.YOUR-DOMAIN.com/grafana</a> to access grafana dashboards.</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="security-1">Security<a class="hash-link" href="#security-1" title="Direct link to heading">​</a></h3><p>Firts we need to create a security group for our alb in <code>security.tf</code>:</p><div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">resource "aws_security_group" "alb" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  name        = "devlake-alb"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  description = "Security group for DevLake ALB"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  vpc_id      = var.vpc_id</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ingress {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    from_port   = 80</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    to_port     = 80</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    protocol    = "tcp"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    cidr_blocks = ["0.0.0.0/0"]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    description = "Allow HTTP traffic"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ingress {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    from_port   = 443</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    to_port     = 443</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    protocol    = "tcp"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    cidr_blocks = ["0.0.0.0/0"]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    description = "Allow HTTPS traffic"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  egress {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    from_port   = 0</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    to_port     = 0</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    protocol    = "-1"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    cidr_blocks = ["0.0.0.0/0"]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    description = "Allow all outbound traffic"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  tags = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Name = var.tag_name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">} </span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>We also need to allow traffic from alb to the task security groups. This creates a secure path for external traffic to reach our services:</p><div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">resource "aws_security_group" "ecs_tasks" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ...</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ingress {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    from_port       = 8080</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    to_port         = 8080</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    protocol        = "tcp"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    security_groups = [aws_security_group.alb.id]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    description     = "Allow traffic from ALB to DevLake API"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ingress {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    from_port       = 4000</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    to_port         = 4000</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    protocol        = "tcp"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    security_groups = [aws_security_group.alb.id]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    description     = "Allow traffic from ALB to DevLake UI"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ingress {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    from_port       = 3000</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    to_port         = 3000</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    protocol        = "tcp"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    security_groups = [aws_security_group.alb.id]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    description     = "Allow traffic from ALB to Grafana UI"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ...</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><h3 class="anchor anchorWithStickyNavbar_LWe7" id="certificate">Certificate<a class="hash-link" href="#certificate" title="Direct link to heading">​</a></h3><p>To create an alb we also need a valid certificate. The certificate must be in the same region as the alb. This are probably created outside of the devlake module.</p><div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">resource "aws_acm_certificate" "devlake_cert" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  domain_name       = "*.YOUR-DOMAIN.COM"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  validation_method = "DNS"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  lifecycle {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    create_before_destroy = true</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  tags = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Name = "devlake"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">resource "aws_route53_record" "cert_validation" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  for_each = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    for dvo in aws_acm_certificate.devlake_cert.domain_validation_options : dvo.domain_name =&gt; {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      name   = dvo.resource_record_name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      record = dvo.resource_record_value</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      type   = dvo.resource_record_type</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  allow_overwrite = true</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  zone_id         = YOUR_DOMAIN_ROUTE53.zone_id</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  name            = each.value.name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  type            = each.value.type</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  records         = [each.value.record]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  ttl             = 60</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><h3 class="anchor anchorWithStickyNavbar_LWe7" id="alb">ALB<a class="hash-link" href="#alb" title="Direct link to heading">​</a></h3><p>To create alb, we need two variables - public subnets and certificate created in the previous step. </p><p>Target groups are essential components that route traffic to our ECS tasks. We'll create two target groups:</p><ol><li>Grafana target group - routes traffic to port 3000 and checks health at <code>/api/health</code></li><li>Config UI target group - routes traffic to port 4000 and checks health at the root path <code>/</code></li></ol><p>The health checks ensure that traffic is only routed to healthy containers, maintaining service reliability.</p><p>Listeners define how the ALB routes incoming traffic to our target groups. </p><p>The HTTPS listener uses our ACM certificate and routes traffic based on host headers:</p><ul><li><code>devlake.${var.domain_name}/grafana*</code> → Grafana target group</li><li><code>devlake.${var.domain_name}</code> → Config UI target group</li></ul><p>This setup ensures secure access to our services while maintaining proper routing based on the requested paths.</p><div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">module "devlake_alb" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  source  = "terraform-aws-modules/alb/aws"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  version = "~&gt; 9.13.0"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  name = "devlake-alb"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  load_balancer_type = "application"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  internal           = false</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  security_groups    = [aws_security_group.alb.id]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  subnets            = var.public_subnets</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  vpc_id             = var.vpc_id</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  listeners = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    https = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      port            = 443</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      protocol        = "HTTPS"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      ssl_policy      = "ELBSecurityPolicy-TLS13-1-2-Res-2021-06"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      certificate_arn = var.certificate_arn</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      # default action</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      forward = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        target_group_key = "devlake-config-ui-tg"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      rules = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        grafana = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          priority = 100</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          actions = [{</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            type             = "forward"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            target_group_key = "devlake-grafana-tg"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          }]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          conditions = [{</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            host_header = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">              values = ["devlake.${var.domain_name}"]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            path_pattern = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">              values = ["/grafana", "/grafana/*"]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">            }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          }]</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  target_groups = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    "devlake-grafana-tg" = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      name        = "devlake-grafana-tg"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      protocol    = "HTTP"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      port        = 3000</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      target_type = "ip"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      health_check = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        enabled             = true</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        interval            = 30</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        path                = "/api/health"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        port                = "traffic-port"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        timeout             = 5</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        healthy_threshold   = 3</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        unhealthy_threshold = 3</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        matcher             = "200"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      create_attachment = false</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    },</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    "devlake-config-ui-tg" = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      name        = "devlake-config-ui-tg"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      protocol    = "HTTP"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      port        = 4000</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      target_type = "ip"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      health_check = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        enabled             = true</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        interval            = 30</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        path                = "/"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        port                = "traffic-port"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        timeout             = 5</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        healthy_threshold   = 3</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        unhealthy_threshold = 3</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        matcher             = "200"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">      create_attachment = false</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  tags = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    Name = var.tag_name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><h3 class="anchor anchorWithStickyNavbar_LWe7" id="dns-records">DNS Records<a class="hash-link" href="#dns-records" title="Direct link to heading">​</a></h3><p>Finally, to use a human readable url, we need to create DNS records in our route53 that points to our alb. For simplicity's sake, I am referencing directly the alb module's outputs, in reality you will probably need to ooutput these from the devlake module itself: </p><div class="codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">resource "aws_route53_record" "devlake" {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  zone_id = YOUR_DOMAIN_ROUTE53.zone_id</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  name    = "devlake.YOUR-DOMAIN.com"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  type    = "A"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  alias {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    name                   = module.devlake_alb.dns_name</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    zone_id                = module.devlake_alb.zone_id</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    evaluate_target_health = true</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>And thats it, you should be able to access devlake on <a href="https://devlake.YOUR-DOMAIN.com" target="_blank" rel="noopener noreferrer">https://devlake.YOUR-DOMAIN.com</a>!</p><h1>Cost breakdown</h1><p> If you want to monitor your costs, first of you need to provide tags with all your resources and second you need to <a href="https://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/activating-tags.html" target="_blank" rel="noopener noreferrer">activate the tag on aws</a> so it is registered. </p><p><img loading="lazy" alt="Cost breakdown Image" src="/assets/images/cost-7efbeef1e68a962824a03e24db631d27.png" width="550" height="490" class="img_ev3q"></p><p>The daily cost is stable on aroun <code>$1.51</code> a day, which gives about <code>$45</code> a month. The cost does not include the route53 domain though.</p>]]></content:encoded>
            <category>DevLake</category>
            <category>AWS</category>
            <category>ECS</category>
            <category>Terraform</category>
        </item>
        <item>
            <title><![CDATA[Quick Start Guide: Setup Your First Engineering Metrics Dashboard in 5 Minutes]]></title>
            <link>https://devlake.apache.org/blog/Quick-Start-Guide-Setup-Your-First-Engineering-Metrics-Dashboard-in-5-Minutes</link>
            <guid>Quick-Start-Guide-Setup-Your-First-Engineering-Metrics-Dashboard-in-5-Minutes</guid>
            <pubDate>Wed, 24 Apr 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[In the marathon of software development, teams can be overwhelmed by the complexity of tasks and the pressure to perform. The real challenge lies in understanding which efforts lead to success and which need refinement. That's the power of metrics. They transform endless streams of data into actionable insights. Apache DevLake provides the means to harness these insights, offering a metrics dashboard that aligns daily tasks with strategic goals. This guide is your first step towards a more informed and intentional approach to development. Let’s embark on this journey to make every commit, issue, and pull request an opportunity for improvement.]]></description>
            <content:encoded><![CDATA[<p>In the marathon of software development, teams can be overwhelmed by the complexity of tasks and the pressure to perform. The real challenge lies in understanding which efforts lead to success and which need refinement. That's the power of metrics. They transform endless streams of data into actionable insights. Apache DevLake provides the means to harness these insights, offering a metrics dashboard that aligns daily tasks with strategic goals. This guide is your first step towards a more informed and intentional approach to development. Let’s embark on this journey to make every commit, issue, and pull request an opportunity for improvement.</p><p><img loading="lazy" alt="Cover Image" src="/assets/images/Cover-Image-ee6939cf9337f57a389298ae4979bcde.png" width="1920" height="1080" class="img_ev3q"></p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="getting-started">Getting Started<a class="hash-link" href="#getting-started" title="Direct link to heading">​</a></h2><p>Before setting up your first Engineering Metrics Dashboard with Apache DevLake, ensure you have the following <strong>pre-requisites</strong> ready:</p><ul><li><strong>DevLake Version:</strong> This feature is available from 1.0.0-beta3 or higher [<a href="https://devlake.apache.org/docs/GettingStarted" target="_blank" rel="noopener noreferrer">Installation Guide</a>]</li><li><strong>Repository:</strong> Your code should be on GitHub, GitLab, BitBucket, or Azure DevOps. This requirement is just for the initial setup; you can connect other tools later.</li></ul><h2 class="anchor anchorWithStickyNavbar_LWe7" id="set-up-the-workflow">Set Up The Workflow<a class="hash-link" href="#set-up-the-workflow" title="Direct link to heading">​</a></h2><p>After installation, connect to your first repository by opening the Config UI or <code>localhost:4000/onboard</code> and you will start the onboarding session as shown in the screenshot below
<img loading="lazy" alt="Start Onboarding Session" src="/assets/images/Onboarding-Session-7829b6e2bfc3942e97b678bd3227cd15.png" width="1280" height="723" class="img_ev3q"></p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="step-1-create-project">Step-1: Create Project<a class="hash-link" href="#step-1-create-project" title="Direct link to heading">​</a></h3><p>In this step, you'll define a project that aims to enhance your software development process within your organization or team. Remember, the true value lies in the goals behind the metrics rather than the metrics themselves. Focus on setting clear, strategic objectives to ensure that the metrics you track are meaningful and directly contribute to your broader organizational goals.
<img loading="lazy" alt="Step-1: Create Project" src="/assets/images/Step-1-6f32fdecf976166c196baaa941a8fee8.png" width="1280" height="735" class="img_ev3q"></p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="step-2-configure-connection">Step-2: Configure Connection<a class="hash-link" href="#step-2-configure-connection" title="Direct link to heading">​</a></h3><ul><li>Initiate by creating a Personal Access Token (PAT) on your chosen platform—be it <a href="https://devlake.apache.org/docs/Configuration/GitHub/#github-personal-access-tokensrecommended" target="_blank" rel="noopener noreferrer">GitHub</a>, <a href="https://devlake.apache.org/docs/Configuration/GitLab/#personal-access-token" target="_blank" rel="noopener noreferrer">GitLab</a>, <a href="https://devlake.apache.org/docs/Configuration/BitBucket/#username-and-app-password" target="_blank" rel="noopener noreferrer">BitBucket</a>, or <a href="https://devlake.apache.org/docs/Configuration/AzureDevOps/#tokenhttps://devlake.apache.org/docs/Configuration/AzureDevOps#token" target="_blank" rel="noopener noreferrer">Azure DevOps</a></li><li>This token empowers DevLake to securely interact with the specific repository you're setting up for metric analysis.
<img loading="lazy" alt="Step-2: Configure Connection" src="/assets/images/Step-2-39719b4e9ab1bbaf34ddc6a842cd38f7.png" width="1280" height="735" class="img_ev3q"></li></ul><h3 class="anchor anchorWithStickyNavbar_LWe7" id="step-3-add-data-scope">Step-3: Add Data Scope<a class="hash-link" href="#step-3-add-data-scope" title="Direct link to heading">​</a></h3><p>Adding your data scope is a crucial step where you select the specific repository you want to monitor. Once selected, the dashboard aggregates data pertaining to issues, deployments, pull requests (PRs), workflow runs, GitHub actions, and more, giving you a comprehensive view of your operations.
To streamline the onboarding experience and swiftly deliver your initial metrics, the system is configured to collect only the <strong>last 14 days of data</strong> by default. This ensures a rapid setup so you can start analyzing your metrics almost immediately. However, this setting is flexible— you can adjust the data collection window at any time to suit your project’s needs and objectives.</p><p><img loading="lazy" alt="Step-3: Add Data Scope" src="/assets/images/Step-3-1-8b02dcb385301f694e9db2e1ac14bce1.png" width="4064" height="2334" class="img_ev3q"></p><p><img loading="lazy" alt="Step-3: Processing Data" src="/assets/images/Step-3-2-35b2601ff8a00efd76efe6e882291ee4.png" width="3104" height="1836" class="img_ev3q"></p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="metrics-dashboard-overview">Metrics Dashboard Overview<a class="hash-link" href="#metrics-dashboard-overview" title="Direct link to heading">​</a></h2><p>This metrics dashboard provides a clear visualization of the development processes by tracking various key performance indicators from GitHub. The dashboard covers:</p><ul><li><strong>User Requirements (Issues)</strong>: Monitors new issues and how they are resolved over time, providing insights into the demands and responsiveness of the team.</li><li><strong>Issue Resolution</strong>: Shows the number of closed issues and the average time taken for issue resolution, offering a measure of efficiency and agility in problem-solving.</li><li><strong>Pull Requests</strong>: Details the number of new pull requests, an indicator of collaboration, alongside the merge time, indicating the pace of code integration.</li><li><strong>Workflow Runs</strong>: Highlights the total number of workflow runs and their success rates, illustrating the robustness of the CI/CD pipeline.</li><li><strong>Queue and Processing Times</strong>: Analyzes the time taken from issue opening to closure and pull request opening to merge, shedding light on the throughput and process optimization.</li></ul><p>By consolidating these metrics, the dashboard serves as a crucial tool for teams to evaluate their development lifecycle and make data-driven improvements.</p><p><img loading="lazy" alt="Grafana Dashboard" src="/assets/images/Grafana-Dashboard-545f2fd119a4de366ddfd2ba2364d10d.png" width="3840" height="7594" class="img_ev3q"></p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="next-steps">Next Steps<a class="hash-link" href="#next-steps" title="Direct link to heading">​</a></h2><p>Congratulations on setting up your first engineering metrics dashboard using GitHub and Grafana in just a few minutes! As an open-source tool, DevLake <a href="https://devlake.apache.org/docs/Overview/SupportedDataSources/" target="_blank" rel="noopener noreferrer">supports various data sources and plugins</a> while ensuring that your data remains securely under your control. Learn more about how DevLake can help you by checking the <a href="https://devlake.apache.org/docs/DORA/" target="_blank" rel="noopener noreferrer">DORA guide</a>, how to <a href="https://devlake.apache.org/docs/Configuration/Dashboards/GrafanaUserGuide#customizing-a-dashboard" target="_blank" rel="noopener noreferrer">customize dashboards</a>, and how to <a href="https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema/#how-to-customize-data-models" target="_blank" rel="noopener noreferrer">customize data model</a>.</p><p>If you haven't already, star <a href="https://github.com/apache/incubator-devlake" target="_blank" rel="noopener noreferrer">DevLake GitHub repository</a> to follow the updates and feel free to post your queries on <a href="https://join.slack.com/t/devlake-io/shared_invite/zt-1lkgbdmys-AU2azidzO1u~mtjlg9my7A" target="_blank" rel="noopener noreferrer">slack</a> - our vibrant community includes engineering leaders, users, experts, and contributors from diverse backgrounds around the world.</p><p>Watch this tutorial on <a href="https://www.youtube.com/watch?v=DPp6PIUDE1k" target="_blank" rel="noopener noreferrer">YouTube</a></p>]]></content:encoded>
            <category>DevLake</category>
            <category>Engineering Metrics</category>
            <category>Grafana</category>
        </item>
        <item>
            <title><![CDATA[DevLake Playground: How to explore your data]]></title>
            <link>https://devlake.apache.org/blog/DevLake-Playground-How-to-explore-your-data</link>
            <guid>DevLake-Playground-How-to-explore-your-data</guid>
            <pubDate>Fri, 15 Mar 2024 00:00:00 GMT</pubDate>
            <description><![CDATA[DevLake is a compelling offering.]]></description>
            <content:encoded><![CDATA[<p>DevLake is a compelling offering.
It collects and normalizes data from many of our favorite development tools and visualizes it using Grafana dashboards.
Like the sleuths we are, we feel the urge to look beyond the dashboard overviews and find the golden nuggets buried deep within the data.
So, we'd like to introduce the DevLake Playground, a place where you can unleash the power of Python on your data.</p><p>In the DevLake Playground, we can explore the data using Jupyter Notebooks.
There are some predefined notebooks and you can write your own.
A Jupyter notebook combines Python code and documentation, which you can easily customize to your needs with some tweaks.
The benefits of these Jupyter notebooks as opposed to Grafana are:</p><ul><li>Grafana is limited to SQL queries for gathering and transforming data, and visualizations for data tables.</li><li>Python (code) offers more flexibility in transforming the data and can easily provide feedback on intermediate steps.</li><li>The playground also supports more visualization types; for example (see the first use case below,) when the data is structured as a graph, we can visualize it with Graphviz.</li></ul><h2 class="anchor anchorWithStickyNavbar_LWe7" id="use-cases">Use cases<a class="hash-link" href="#use-cases" title="Direct link to heading">​</a></h2><h3 class="anchor anchorWithStickyNavbar_LWe7" id="analyzing-the-development-process-through-jira-statuses">Analyzing the development process through JIRA statuses<a class="hash-link" href="#analyzing-the-development-process-through-jira-statuses" title="Direct link to heading">​</a></h3><p>The <a href="https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema#schema-diagram" target="_blank" rel="noopener noreferrer">DevLake Domain model</a> exposes the changes of issues of our issue tracker, including status changes.
If we use that to visualize how issues really flow, we get a rudimentary (automated) value stream map.
We can use this to identify bottlenecks in our process or flaws in our process design.
This is inspired by <a href="https://xebia.com/blog/insights-from-your-jira-data-to-help-improve-your-team/" target="_blank" rel="noopener noreferrer">this blog post</a>:</p><p><img loading="lazy" alt="process graph" src="/assets/images/processgraph-8ae502c7bf854c719980896c811a6f5d.png" width="1712" height="1266" class="img_ev3q"></p><p>For example, in the chart above, we see that it takes on average 15 days for Stories to go from "Ready" to "In Progress".
And, it happened 476x within the selected time frame.</p><p>And now that we have this data in our playground, we can easily change how we represent it.
If we focus on the most common status transitions, we can visualize the distribution of durations in a box plot out of the same data:</p><p><img loading="lazy" alt="box plot" src="/assets/images/boxplot-31a06458d77f65b37e3cb409e7eb93c4.png" width="1868" height="842" class="img_ev3q"></p><p>This functionality is made available through a <a href="https://github.com/apache/incubator-devlake-playground/blob/main/notebooks/process_analysis.ipynb" target="_blank" rel="noopener noreferrer">predefined notebook</a>, so you can easily run it with your own data.</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="explore-data-across-different-domains">Explore data across different domains<a class="hash-link" href="#explore-data-across-different-domains" title="Direct link to heading">​</a></h3><p>Let's say we have a hypothesis: "Defect fixes are more quickly merged than new functionality."
Before building a dashboard, we want to determine whether the data quality is good enough and whether we can test this hypothesis.
With <a href="https://pandas.pydata.org/" target="_blank" rel="noopener noreferrer">pandas</a>, we can quickly join different tables from the <a href="https://devlake.apache.org/docs/DataModels/DevLakeDomainLayerSchema#schema-diagram" target="_blank" rel="noopener noreferrer">data model</a>.
With the following code, we were able to get a preliminary view:</p><div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> pandas </span><span class="token keyword" style="color:#00009f">as</span><span class="token plain"> pd</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">from</span><span class="token plain"> playground</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">db_engine </span><span class="token keyword" style="color:#00009f">import</span><span class="token plain"> create_db_engine</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># the default notebook</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">DB_URL </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"mysql://merico:merico@127.0.0.1:3306/lake"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">engine </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> create_db_engine</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">DB_URL</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># read tables from database</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">df_pr_issues </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> pd</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">read_sql</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"select * from pull_request_issues"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> engine</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">df_prs </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> pd</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">read_sql</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"select * from pull_requests"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> engine</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">df_issues </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> pd</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">read_sql</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"select * from issues"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> engine</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># join pull requests and issues based on rows in pull_request_issues</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">df </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> pd</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">merge</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">df_pr_issues</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> df_prs</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> left_on</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"pull_request_id"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> right_on</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"id"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> suffixes</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'_pr_issues'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'_prs'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">df </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> pd</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">merge</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">df</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> df_issues</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> left_on</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"issue_id"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> right_on</span><span class="token operator" style="color:#393A34">=</span><span class="token string" style="color:#e3116c">"id"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> suffixes</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'_prs'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'_issues'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># set data types correctly</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">df</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'created_date_issues'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> pd</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">to_datetime</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">df</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'created_date_issues'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">df</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'resolution_date'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> pd</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">to_datetime</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">df</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'resolution_date'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">df</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'created_date_prs'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> pd</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">to_datetime</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">df</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'created_date_prs'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">df</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'merged_date'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> pd</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">to_datetime</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">df</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'merged_date'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># calculate lead times</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">df</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'issue_lead_time'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> df</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'resolution_date'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> df</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'created_date_issues'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">df</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'pr_lead_time'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> df</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'merged_date'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"> </span><span class="token operator" style="color:#393A34">-</span><span class="token plain"> df</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'created_date_prs'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># drop unnecessary columns</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">df </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> df</span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'type_issues'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'title_issues'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'issue_lead_time'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'title_prs'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'pr_lead_time'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># group lead times by issue_type, add count</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">df_grouped </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> df</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">groupby</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">'type_issues'</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">agg</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">'title_issues'</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'count'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">'issue_lead_time'</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'mean'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'median'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'std'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token string" style="color:#e3116c">'pr_lead_time'</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">'mean'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'median'</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'std'</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">df</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">rename</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">columns</span><span class="token operator" style="color:#393A34">=</span><span class="token punctuation" style="color:#393A34">{</span><span class="token string" style="color:#e3116c">'title_issues'</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'issue_count'</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> inplace</span><span class="token operator" style="color:#393A34">=</span><span class="token boolean" style="color:#36acaa">True</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">display</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">df_grouped</span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>If we run this example on the Devlake GitHub issues and pull requests (up to March 2024), we get the following output:</p><table><thead><tr><th></th><th>title_issues</th><th>issue_lead_time</th><th></th><th></th><th>pr_lead_time</th><th></th><th></th></tr></thead><tbody><tr><td></td><td>count</td><td>mean</td><td>median</td><td>std</td><td>mean</td><td>median</td><td>std</td></tr><tr><td>type_issues</td><td></td><td></td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td>34</td><td>66 days 14:53:28.709677419</td><td>42 days 05:59:04</td><td>67 days 14:35:49.143870568</td><td>3 days 11:42:34.857142857</td><td>0 days 12:54:43.500000</td><td>12 days 17:24:54.878541108</td></tr><tr><td>BUG</td><td>141</td><td>10 days 07:27:20.572463768</td><td>1 days 22:42:39</td><td>20 days 14:51:35.075965706</td><td>1 days 16:51:51.529411764</td><td>0 days 01:04:54</td><td>10 days 10:20:54.566875184</td></tr><tr><td>INCIDENT</td><td>2</td><td>0 days 00:50:49</td><td>0 days 00:50:49</td><td>0 days 00:59:50.688234865</td><td>0 days 00:06:39</td><td>0 days 00:06:39</td><td>0 days 00:00:42.426406871</td></tr><tr><td>REQUIREMENT</td><td>40</td><td>37 days 02:50:22.500000</td><td>16 days 03:56:45.500000</td><td>60 days 02:39:39.606995949</td><td>9 days 11:13:27.270270270</td><td>2 days 00:14:04</td><td>22 days 12:32:27.522638402</td></tr></tbody></table><h2 class="anchor anchorWithStickyNavbar_LWe7" id="getting-started">Getting started<a class="hash-link" href="#getting-started" title="Direct link to heading">​</a></h2><p>We hope you are as excited as we are.
We look forward to you joining our community to get your feedback and contributions.</p><p>Want to get started?
Have a look at the <a href="https://github.com/apache/incubator-devlake-playground" target="_blank" rel="noopener noreferrer">playground repository</a>.</p>]]></content:encoded>
            <category>devlake</category>
            <category>playground</category>
            <category>python</category>
            <category>process mining</category>
        </item>
        <item>
            <title><![CDATA[Compatibility of Apache DevLake with PostgreSQL]]></title>
            <link>https://devlake.apache.org/blog/compatibility-of-apache-devLake-with-postgreSQL</link>
            <guid>compatibility-of-apache-devLake-with-postgreSQL</guid>
            <pubDate>Thu, 23 Jun 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Apache DevLake is a dev data platform that can collect and integrate data from different dev tools including Jira, Github, GitLab and Jenkins.]]></description>
            <content:encoded><![CDATA[<p>Apache DevLake is a dev data platform that can collect and integrate data from different dev tools including Jira, Github, GitLab and Jenkins.</p><p>This blog will not aim at a comprehensive summary of the compatibility of database but a record of issues for future reference.</p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="1different--data-types">1.Different  Data Types<a class="hash-link" href="#1different--data-types" title="Direct link to heading">​</a></h2><h3 class="anchor anchorWithStickyNavbar_LWe7" id="postgresql-does-not-have-a-uint-type">PostgreSQL does not have a uint type<a class="hash-link" href="#postgresql-does-not-have-a-uint-type" title="Direct link to heading">​</a></h3><div class="language-sql= codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-sql= codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">type JenkinsBuild struct {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> common.NoPKModel</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> JobName           string  `gorm:"primaryKey;type:varchar(255)"`</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> Duration          float64 // build time</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> DisplayName       string  // "#7"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> EstimatedDuration float64</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> Number            int64 `gorm:"primaryKey;type:INT(10) UNSIGNED NOT NULL"`</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> Result            string</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> Timestamp         int64     // start time</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> StartTime         time.Time // convered by timestamp</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> CommitSha         string</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>In <code>JenkinsBuild.Number</code>, the<code>gorm</code>struct tag used <code>UNSIGNED</code>, which will lead to the failure to create table and should be removed.</p><p><img loading="lazy" src="https://i.imgur.com/N7E9Vwd.png" class="img_ev3q"></p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="mysql-does-not-have-a-bool-data-type">MySQL does not have a bool data type<a class="hash-link" href="#mysql-does-not-have-a-bool-data-type" title="Direct link to heading">​</a></h3><p>For a field defined as bool type in model, gorm will map it to MySQL's TINYINT data type, which can be queried directly with 0 or 1 in SQL, but PostgreSQL has a bool type, so gorm will map it to the BOOL type. If 0 or 1 is still used in SQL to query, there will be a report of error.</p><p>Here is an example(only relevant fields are shown in the example). The lookup statement works in MySQL, but will lead to an error in PostgreSQL.</p><div class="language-sql= codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-sql= codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">type GitlabMergeRequestNote struct {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> MergeRequestId  int    `gorm:"index"`</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> System          bool </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">db.Where("merge_request_id = ? AND `system` = 0", gitlabMr.GitlabId).</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>After changing the sentence as it follows, an error will still be reported. The reason will be shown in the part about backticks.</p><div class="language-sql= codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-sql= codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">db.Where("merge_request_id = ? AND `system` = ?", gitlabMr.GitlabId, false)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><h2 class="anchor anchorWithStickyNavbar_LWe7" id="2different-behaviors">2.Different Behaviors<a class="hash-link" href="#2different-behaviors" title="Direct link to heading">​</a></h2><h3 class="anchor anchorWithStickyNavbar_LWe7" id="bulk-insertion">Bulk insertion<a class="hash-link" href="#bulk-insertion" title="Direct link to heading">​</a></h3><p>When <code>ON CONFLIT UPDATE ALL</code> was used to achieve bulk insertion, and if there are multiple records with the same primary key, it will report errors in PostgreSQL but not in MySQL.
<img loading="lazy" src="https://i.imgur.com/zaExAUG.png" class="img_ev3q"></p><p><img loading="lazy" src="https://i.imgur.com/BpZY8dN.png" class="img_ev3q"></p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="inconsistent-definition-of-model-with-schema">Inconsistent definition of model with schema<a class="hash-link" href="#inconsistent-definition-of-model-with-schema" title="Direct link to heading">​</a></h3><p>For example, in the model definition, <code>GithubPullRequest.AuthorId</code> is of the int type, but this field in the database is of VARCHAR type. When inserting data, MySQL will accept it, but ProstgresSQL will report an error.</p><div class="language-sql= codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-sql= codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">type GithubPullRequest struct {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> GithubId        int    `gorm:"primaryKey"`</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> RepoId          int    `gorm:"index"`</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> Number          int    `gorm:"index"` </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> State           string `gorm:"type:varchar(255)"`</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> Title           string `gorm:"type:varchar(255)"`</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> GithubCreatedAt time.Time</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> GithubUpdatedAt time.Time `gorm:"index"`</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> ClosedAt        *time.Time</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> // In order to get the following fields, we need to collect PRs individually from GitHub</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> Additions      int</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> Deletions      int</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> Comments       int</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> Commits        int</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> ReviewComments int</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> Merged         bool</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> MergedAt       *time.Time</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> Body           string</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> Type           string `gorm:"type:varchar(255)"`</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> Component      string `gorm:"type:varchar(255)"`</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> MergeCommitSha string `gorm:"type:varchar(40)"`</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> HeadRef        string `gorm:"type:varchar(255)"`</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> BaseRef        string `gorm:"type:varchar(255)"`</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> BaseCommitSha  string `gorm:"type:varchar(255)"`</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> HeadCommitSha  string `gorm:"type:varchar(255)"`</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> Url            string `gorm:"type:varchar(255)"`</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> AuthorName     string `gorm:"type:varchar(100)"`</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> AuthorId       int</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> common.NoPKModel</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p><img loading="lazy" src="https://i.imgur.com/onxGG8d.png" class="img_ev3q"></p><h2 class="anchor anchorWithStickyNavbar_LWe7" id="3mysql-specific-functions">3.MySQL-Specific Functions<a class="hash-link" href="#3mysql-specific-functions" title="Direct link to heading">​</a></h2><p>We used the <code>GROUP_CONCAT</code>function in a complex query. Although there are similar functions in PostgreSQL, the function names are different and the usage is slightly different.</p><div class="language-sql= codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-sql= codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">cursor2, err := db.Table("pull_requests pr1").</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  Joins("left join pull_requests pr2 on pr1.parent_pr_id = pr2.id").Group("pr1.parent_pr_id, pr2.created_date").Where("pr1.parent_pr_id != ''").</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  Joins("left join repos on pr2.base_repo_id = repos.id").</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  Order("pr2.created_date ASC").</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  Select(`pr2.key as parent_pr_key, pr1.parent_pr_id as parent_pr_id, GROUP_CONCAT(pr1.base_ref order by pr1.base_ref ASC) as cherrypick_base_branches, </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   GROUP_CONCAT(pr1.key order by pr1.base_ref ASC) as cherrypick_pr_keys, repos.name as repo_name, </span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   concat(repos.url, '/pull/', pr2.key) as parent_pr_url`).Rows()</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>Solution:
We finally decided to use two steps to achieve the <code>GROUP_CONCAT</code> function. First we used the simplest SQL query to get multiple pieces of the sorted data, and then used the code to group them.</p><p>After modification:</p><div class="language-sql= codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-sql= codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">    cursor2, err := db.Raw(</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">  `</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   SELECT pr2.pull_request_key                 AS parent_pr_key,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          pr1.parent_pr_id                     AS parent_pr_id,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          pr1.base_ref                         AS cherrypick_base_branch,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          pr1.pull_request_key                 AS cherrypick_pr_key,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          repos.NAME                           AS repo_name,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          Concat(repos.url, '/pull/', pr2.pull_request_key) AS parent_pr_url,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">        pr2.created_date</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   FROM   pull_requests pr1</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          LEFT JOIN pull_requests pr2</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                 ON pr1.parent_pr_id = pr2.id</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">          LEFT JOIN repos</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">                 ON pr2.base_repo_id = repos.id</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   WHERE  pr1.parent_pr_id != ''</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   ORDER  BY pr1.parent_pr_id,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">             pr2.created_date,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">       pr1.base_ref ASC</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   `).Rows()</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><h2 class="anchor anchorWithStickyNavbar_LWe7" id="4different-grammar">4.Different Grammar<a class="hash-link" href="#4different-grammar" title="Direct link to heading">​</a></h2><h3 class="anchor anchorWithStickyNavbar_LWe7" id="backticks">Backticks<a class="hash-link" href="#backticks" title="Direct link to heading">​</a></h3><p>We used backticks in some SQL statements to protect field names from conflicting with MySQL reserved words, which can lead to errors in PostgreSQL. To solve this problem we revisited our code, modified all field names that conflict with reserved words, and removed the backticks in the SQL statement. In the example just mentioned:</p><div class="language-sql= codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-sql= codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">db.Where("merge_request_id = ? AND `system` = ?", gitlabMr.GitlabId, false)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><p>Solution:
We changed <code>system</code> to <code>is_system</code> to avoid the usage of backticks.</p><div class="language-sql= codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-sql= codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">db.Where("merge_request_id = ? AND is_system = ?", gitlabMr.GitlabId, false)</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><h3 class="anchor anchorWithStickyNavbar_LWe7" id="non-standard-delete-statement">Non-standard delete statement<a class="hash-link" href="#non-standard-delete-statement" title="Direct link to heading">​</a></h3><p>There were delete statements as followed in our code, which are legal in MySQL but will report an error in PostgreSQL.</p><div class="language-sql= codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-sql= codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token plain">err := db.Exec(`</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> DELETE ic</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> FROM jira_issue_commits ic</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> LEFT JOIN jira_board_issues bi ON (bi.source_id = ic.source_id AND bi.issue_id = ic.issue_id)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> WHERE ic.source_id = ? AND bi.board_id = ?</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> `, sourceId, boardId).Error</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div>]]></content:encoded>
            <category>devlake</category>
            <category>database</category>
            <category>postgresql</category>
        </item>
        <item>
            <title><![CDATA[How DevLake is Up and Running]]></title>
            <link>https://devlake.apache.org/blog/how-DevLake-is-up-and-running</link>
            <guid>how-DevLake-is-up-and-running</guid>
            <pubDate>Fri, 17 Jun 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[Apache DevLake is an integration tool with the DevOps data collection functionality, which presents a different stage of data to development teams via Grafana. which also can leverage teams to improve the development process with a data-driven model.]]></description>
            <content:encoded><![CDATA[<p><a href="https://github.com/apache/incubator-devlake" target="_blank" rel="noopener noreferrer">Apache DevLake</a> is an integration tool with the DevOps data collection functionality, which presents a different stage of data to development teams via Grafana. which also can leverage teams to improve the development process with a data-driven model.</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="apache-devlack-architecture-overview">Apache DevLack Architecture Overview<a class="hash-link" href="#apache-devlack-architecture-overview" title="Direct link to heading">​</a></h3><ul><li>The left side of the following screenshot is an <a href="https://devlake.apache.org/docs/Overview/SupportedDataSources/" target="_blank" rel="noopener noreferrer">integrative DevOps data plugin</a>, the existing plugins include Github, GitLab, JIRA, Jenkins, Tapd, Feishu, and the most featured analysis engine in the Simayi platform.</li><li>The main framework in the middle of the following screenshot, completes data collection, expansion, and conversion to the domain layer by running subtasks in the plugins. The user can trigger the tasks by config-UI or all API.</li><li>RMDBS currently supports Mysql and PostgresSQL, more databases will be supported in the future.</li><li>Grafana can generate different types of needed data by using SQL.</li></ul><p><img loading="lazy" alt="Generated" src="/assets/images/Aspose.Words.093a76ac-457b-4498-a472-7dbea580bca9.001-9fe996eee294ce1843bc3f126a1a7b89.png" width="567" height="310" class="img_ev3q"></p><blockquote><p>Then let’s move on to how to start running DevLake.</p></blockquote><h3 class="anchor anchorWithStickyNavbar_LWe7" id="start-the-system">Start the system<a class="hash-link" href="#start-the-system" title="Direct link to heading">​</a></h3><p>Before the Golang program runs, it will automatically call the init() method in the package. We need to focus on the loading of the services package. The following code has detailed comments:</p><div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">func</span><span class="token plain"> </span><span class="token function" style="color:#d73a49">init</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">var</span><span class="token plain"> err </span><span class="token builtin">error</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// get initial config information</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">cfg </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> config</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">GetConfig</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// get Database</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">db</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> runner</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">NewGormDb</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">cfg</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> logger</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Global</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Nested</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"db"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// configure time zone</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">location </span><span class="token operator" style="color:#393A34">:=</span><span class="token plain"> cron</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">WithLocation</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">time</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">UTC</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// create scheduled task manager</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">cronManager </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> cron</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">New</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">location</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">panic</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">err</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// initialize the data migration</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">migration</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Init</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">db</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// register the framework's data migration scripts</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">migrationscripts</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">RegisterAll</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// load plugin, loads all .so files in the folder cfg.GetString("PLUGIN_DIR")，in th LoadPlugins method()，specifically, LoadPlugins stores the pluginName:PluginMeta key-value pair into core.plugins by calling runner.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">err </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> runner</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">LoadPlugins</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">cfg</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">GetString</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"PLUGIN_DIR"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">cfg</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">logger</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">Global</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Nested</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"plugin"</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">db</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">panic</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">err</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// run data migration scripts to complete the initializztion work of tables in the databse framework layer.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">err </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> migration</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Execute</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">context</span><span class="token punctuation" style="color:#393A34">.</span><span class="token function" style="color:#d73a49">Background</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> err </span><span class="token operator" style="color:#393A34">!=</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">nil</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">panic</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">err</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic">// call service init</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token function" style="color:#d73a49">pipelineServiceInit</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><h3 class="anchor anchorWithStickyNavbar_LWe7" id="the-execution-principle-of-devlake">The execution principle of DevLake<a class="hash-link" href="#the-execution-principle-of-devlake" title="Direct link to heading">​</a></h3><p><strong>The running process of the pipeline</strong>
Before we go through the pipeline process, we need to know the <a href="https://devlake.apache.org/docs/Overview/KeyConcepts#blueprints" target="_blank" rel="noopener noreferrer">Blueprint</a> first.</p><p>Blueprint is a timed task that contains all the subtasks and plans that need to be executed. Each execution record of Blueprint is a historical run, AKA Pipeline. Which presents a trigger for DevLack to complete one or more data collection transformation tasks through one or more plugins.</p><p><img loading="lazy" alt="Generated" src="/assets/images/Aspose.Words.093a76ac-457b-4498-a472-7dbea580bca9.002-40065677c2b00df89eeaac1d9512f286.png" width="567" height="263" class="img_ev3q"></p><p>The following is the pipeline running flow chart.</p><p><img loading="lazy" alt="Generated" src="/assets/images/Aspose.Words.093a76ac-457b-4498-a472-7dbea580bca9.003-74567cb3674a6e2d6c980cc4c4182217.png" width="664" height="1106" class="img_ev3q"></p><p>A pipeline contains a two-dimensional array of tasks, mainly to ensure that a series of tasks are executed in a preset order. Like the following screenshot if the plugin of Stage 3 needs to rely on the other plugin to prepare the data(eg: the operation of refdiff needs to rely on gitextractor and Github, for more information on data sources and plugins, please refer to the <a href="https://devlake.apache.org/docs/Overview/SupportedDataSources/" target="_blank" rel="noopener noreferrer">documentation</a>, then when Stage 3 starts to execute, it needs to ensure that its dependencies have been executed in Stage 1 and Stage 2.</p><p><img loading="lazy" alt="Generated" src="/assets/images/Aspose.Words.093a76ac-457b-4498-a472-7dbea580bca9.004-a6a550c4f00b232abc7b28e30738be09.png" width="567" height="238" class="img_ev3q"></p><p><strong>Task running process</strong></p><p>The plugin tasks in stage1, stage2, and stage3 are executed in parallel:</p><p><img loading="lazy" alt="Generated" src="/assets/images/Aspose.Words.093a76ac-457b-4498-a472-7dbea580bca9.005-d300a6d714d498f9097b571e386d4acf.png" width="664" height="1036" class="img_ev3q"></p><p><strong>The next step is to execute the subtasks in the plugin sequentially.</strong></p><p><img loading="lazy" alt="Generated" src="/assets/images/Aspose.Words.093a76ac-457b-4498-a472-7dbea580bca9.006-7ad36df1d8f947cba6e34c8e3c54cf25.png" width="760" height="1294" class="img_ev3q"></p><ol><li>The work before the RunTask is to prepare the params for the RunTask method to call, like logger, db, context and etc.</li><li>The main method of RunTask is mainly to update the tasks in the Database, at the same time prepare to run the options of the plugins task.</li><li>RunpluginTask will obtain the corresponding  <a href="#pm">PluginMeta</a> through core.Getplugin(pluginName), then obtains the <a href="#pt">PluginTask</a> via PluginMeta, and then executes RunPluginSubTasks.</li></ol><p><strong>The running process of each plugin subtask(the relevant interface and func will be explained in the next section)</strong></p><p><img loading="lazy" alt="Generated" src="/assets/images/Aspose.Words.093a76ac-457b-4498-a472-7dbea580bca9.007-f26dbf5692430e807fb782d9233f53f3.png" width="1586" height="600" class="img_ev3q"></p><ol><li>Get all available subtasks subtaskMeta of all th4e plugins by calling SubTaskMetas().</li><li>Use options<!-- -->[‘task’]<!-- --> and subtaskMeta to form a set of subtasks to be executed subtaskMetas.</li><li>Calculate how many subtasks in total</li><li>Build taskCtx via helper.NewDefaultTaskContext.</li><li>Build taskData via call pluginTask.PrepareTaskData.</li><li>Iterate over all subtasks in subtaskMetas.<ol><li>Get subtaskCtx of subtask via call taskCtx.SubTaskContext(subtaskMeta.Name).</li><li>Run subtaskMeta.EntryPoint(subtaskCtx)</li></ol></li></ol><h2 class="anchor anchorWithStickyNavbar_LWe7" id="important-interfaces-in-devlake">Important interfaces in DevLake<a class="hash-link" href="#important-interfaces-in-devlake" title="Direct link to heading">​</a></h2><ol><li><a id="pm">PluginMeta</a>: Contains the two basic methods of plugins, which all plugins need to implement. And stored in core.plugins when the system starts. And obtained through core.GetPlugin when executing plugin tasks.</li></ol><div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> PluginMeta </span><span class="token keyword" style="color:#00009f">interface</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   </span><span class="token function" style="color:#d73a49">Description</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   </span><span class="token comment" style="color:#999988;font-style:italic">//PkgPath information will be lost when compiled as plugin(.so), this func will return that info</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   </span><span class="token function" style="color:#d73a49">RootPkgPath</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token builtin">string</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><ol start="2"><li><a id="pt">PluginTask</a>: It can be obtained by PluginMeta, after the plugin implemented this method, Framework can run the subtask directly, instead of letting the plugin itself run it, the biggest benefit of this is that the subtasks of the plugin are easier to implement, and we can more easily leverage(such as adding logs, etc.) during the operation of the plugin.</li></ol><div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">type</span><span class="token plain"> PluginTask </span><span class="token keyword" style="color:#00009f">interface</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   </span><span class="token comment" style="color:#999988;font-style:italic">// return all available subtasks, framework will run them for you in order</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   </span><span class="token function" style="color:#d73a49">SubTaskMetas</span><span class="token punctuation" style="color:#393A34">(</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain">SubTaskMeta</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   </span><span class="token comment" style="color:#999988;font-style:italic">// based on task context and user input options, return data that shared among all subtasks</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   </span><span class="token function" style="color:#d73a49">PrepareTaskData</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">taskCtx TaskContext</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> options </span><span class="token keyword" style="color:#00009f">map</span><span class="token punctuation" style="color:#393A34">[</span><span class="token builtin">string</span><span class="token punctuation" style="color:#393A34">]</span><span class="token keyword" style="color:#00009f">interface</span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">(</span><span class="token keyword" style="color:#00009f">interface</span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token builtin">error</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><ol start="3"><li>Every plugin has a <a id="td">taskData</a>, which contains configuration options, apiClient, and other properties of plugins.(like the github has repo information)</li><li><a id="stm">SubTaskMeta</a>:: the meta data of a subtask, every subtask will define a SubTaskMeta.</li></ol><div class="language-go codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_biex"><pre tabindex="0" class="prism-code language-go codeBlock_bY9V thin-scrollbar"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#393A34"><span class="token keyword" style="color:#00009f">var</span><span class="token plain"> CollectMeetingTopUserItemMeta </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> core</span><span class="token punctuation" style="color:#393A34">.</span><span class="token plain">SubTaskMeta</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   Name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"collectMeetingTopUserItem"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   EntryPoint</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> CollectMeetingTopUserItem</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   EnabledByDefault</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean" style="color:#36acaa">true</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">   Description</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"Collect top user meeting data from Feishu api"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><div class="buttonGroup__atx"><button type="button" aria-label="Copy code to clipboard" title="Copy" class="clean-btn"><span class="copyButtonIcons_eSgA" aria-hidden="true"><svg class="copyButtonIcon_y97N" viewBox="0 0 24 24"><path d="M19,21H8V7H19M19,5H8A2,2 0 0,0 6,7V21A2,2 0 0,0 8,23H19A2,2 0 0,0 21,21V7A2,2 0 0,0 19,5M16,1H4A2,2 0 0,0 2,3V17H4V3H16V1Z"></path></svg><svg class="copyButtonSuccessIcon_LjdS" viewBox="0 0 24 24"><path d="M21,7L9,19L3.5,13.5L4.91,12.09L9,16.17L19.59,5.59L21,7Z"></path></svg></span></button></div></div></div><ol start="5"><li><a id="ec">ExecContext</a>: defines all resources needed to execute (sub)tasks.</li><li><a id="stc">SubTaskContext</a>: defines all resources need to execute subtask(including ExecContext).</li><li><a id="tc">TaskContext</a>: defines all resources need to execute tasks(including ExecContext). The difference with SubTaskContext is the TaskContext() method in SubTaskContext can retire TaskContext, but SubTaskContext(subtask string) method in TaskContext can return SubTaskContext, which means the subtask belongs to the plugin task, so we use the different contexts to distinguish this.</li><li><a id="step">SubTaskEntryPoint</a>: all the subtasks in the plugin have to implement this function so that they can be coordinated and arranged by the framework layer.</li></ol><h2 class="anchor anchorWithStickyNavbar_LWe7" id="further-plan">Further Plan<a class="hash-link" href="#further-plan" title="Direct link to heading">​</a></h2><p>This blog introduced the basics of the DevLack framework and how it starts and runs, there are 3 more contexts api<!-- -->_<!-- -->collector, api<!-- -->_<!-- -->extractor, and data<!-- -->_<!-- -->convertor will be explained in the next blog.</p>]]></content:encoded>
            <category>devlake</category>
            <category>apache</category>
        </item>
        <item>
            <title><![CDATA[Apache Incubator Welcomes DevLake, A Dev-Data Platform Serving Developers]]></title>
            <link>https://devlake.apache.org/blog/apache-welcomes-devlake</link>
            <guid>apache-welcomes-devlake</guid>
            <pubDate>Wed, 18 May 2022 00:00:00 GMT</pubDate>
            <description><![CDATA[We are excited to share today that the Apache Software Foundation (ASF) voted to make DevLake an officially supported project of the Apache Incubator.]]></description>
            <content:encoded><![CDATA[<p>We are excited to share today that the Apache Software Foundation (ASF) voted to make DevLake an officially supported project of the Apache Incubator.</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="what-is-devlake">What is DevLake?<a class="hash-link" href="#what-is-devlake" title="Direct link to heading">​</a></h3><p>Launched in December of 2021, <a href="https://github.com/apache/incubator-devlake" target="_blank" rel="noopener noreferrer">Apache DevLake</a> is an open-source dev data platform that ingests, analyzes, and visualizes the fragmented data in developer tools. </p><p>Software development is complex, requiring many tools and processes, and as a result creates a storm of data scattered across tools in many formats. This makes it difficult to organize, query, and make sense of. We built Apache DevLake, to make it easy to make sense of this rich data and to translate it into actionable insights.</p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="apache-devlakes-key-features">Apache DevLake's key features:<a class="hash-link" href="#apache-devlakes-key-features" title="Direct link to heading">​</a></h3><ul><li>DevOps data collection across software development lifecycle (SDLC) to connect data islands</li><li>Standardized data models with out-of-the-box metrics and customizable dashboards</li><li>Flexible plugin system for user-defined data integration and transformation</li></ul><p>Below is the architecture of Apache DevLake:
<img loading="lazy" alt="architecture" src="/assets/images/0.11-architecture-diagram-f422e47efad0b84f72eb3bc51c7d1f1b.jpg" width="2143" height="1182" class="img_ev3q"></p><h3 class="anchor anchorWithStickyNavbar_LWe7" id="why-join-apache-incubator">Why join Apache Incubator?<a class="hash-link" href="#why-join-apache-incubator" title="Direct link to heading">​</a></h3><p>First and foremost, we firmly believe in Apache's principle of "Community over Code" and <a href="https://www.apache.org/theapacheway/index.html" target="_blank" rel="noopener noreferrer">"The Apache Way"</a>. We look forward to building a vibrant, inclusive, and diverse community under the guidance of our mentors through the incubator journey.</p><p>We also feel that the Apache community has one of the strongest ecosystems when it comes to data-oriented open-source projects. </p><p>Last and not least, the support and enthusiasm of the Apache community and mentors made it clear to us that this would be a place where we can truly evolve and nurture Apache DevLake.</p><p>In the months leading up to DevLake's acceptance, our mentors provided guidance and suggestions instrumental to making this important moment a reality. We want to take this opportunity to thank and acknowledge them: </p><ul><li><p><strong><a href="https://github.com/WillemJiang" target="_blank" rel="noopener noreferrer">Willem Ning Jiang</a></strong>: Apache DevLake Champion; ASF Member &amp; Board Director</p></li><li><p><strong><a href="https://github.com/terrymanu" target="_blank" rel="noopener noreferrer">Liang Zhang</a></strong>: Founder &amp; CEO of SphereEx; ASF Member, Founder &amp; PMC Chair of Apache ShardingSphere</p></li><li><p><strong><a href="https://github.com/dailidong" target="_blank" rel="noopener noreferrer">Lidong Dai</a></strong>: ASF Member; Apache DolphinScheduler PMC Chair</p></li><li><p><strong><a href="https://github.com/sijie" target="_blank" rel="noopener noreferrer">Sijie Guo</a></strong>: ASF Member, PMC member of Apache Pulsar; Founder &amp; CEO of StreamNative</p></li><li><p><strong><a href="https://github.com/felixcheung" target="_blank" rel="noopener noreferrer">Felix Cheung</a></strong>: ASF Member, PMC on Spark, Superset, Yunikorn, Zeppelin, Pinot, and Incubator. SVP of Engineering at SafeGraph.</p></li><li><p><strong><a href="https://github.com/jbonofre" target="_blank" rel="noopener noreferrer">Jean-Baptiste Onofré</a></strong>: ASF Member, Karaf PMC Chair, PMC on ActiveMQ, Archiva, Aries, Beam, Brooklyn, Camel, Carbondata, Felix, Incubator, and <a href="http://people.apache.org/committer-index.html" target="_blank" rel="noopener noreferrer">many more</a>.</p></li></ul><h3 class="anchor anchorWithStickyNavbar_LWe7" id="apache-devlake-future-roadmap">Apache DevLake Future Roadmap<a class="hash-link" href="#apache-devlake-future-roadmap" title="Direct link to heading">​</a></h3><ul><li>Enhance system scalability and performance in large-scale data scenarios.</li><li>Integrate more data sources and tools (JIRA, GitHub, GitLab, and Jenkins are already supported.)</li><li>Enable support for OLAP databases, providing users with more choices.</li><li>Provide more scenario-specific, out-of-the-box dashboards and templates reflecting best practices and well-known methodologies to improve usability.</li></ul><h3 class="anchor anchorWithStickyNavbar_LWe7" id="join-us">Join us!<a class="hash-link" href="#join-us" title="Direct link to heading">​</a></h3><p>We invite developers and those passionate about data-driven engineering to 'dive into the lake' with us, and welcome contributions of all kinds.
Join us on Slack and at our weekly open source community meetups🥳</p><p><strong>Apache DevLake (Incubating) Links:</strong></p><ul><li>GitHub:  <a href="https://github.com/apache/incubator-devlake" target="_blank" rel="noopener noreferrer">https://github.com/apache/incubator-devlake</a></li><li>Official Website: <a href="https://devlake.apache.org/" target="_blank" rel="noopener noreferrer">https://devlake.apache.org/</a></li><li>Slack:  <a href="https://join.slack.com/t/devlake-io/shared_invite/zt-1lkgbdmys-AU2azidzO1u~mtjlg9my7A" target="_blank" rel="noopener noreferrer">https://join.slack.com/t/devlake-io/shared_invite/zt-1lkgbdmys-AU2azidzO1u~mtjlg9my7A</a></li><li>Podling Website：<a href="https://incubator.apache.org/projects/devlake.html" target="_blank" rel="noopener noreferrer">https://incubator.apache.org/projects/devlake.html</a></li></ul>]]></content:encoded>
            <category>Devlake</category>
            <category>Apache</category>
        </item>
    </channel>
</rss>