<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Arijus Gilbrantas</title>
    <description>The latest articles on DEV Community by Arijus Gilbrantas (@arijusg).</description>
    <link>https://dev.to/arijusg</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F327928%2F2743b5b7-bd35-48fc-b6cf-5ad2685a0aff.jpg</url>
      <title>DEV Community: Arijus Gilbrantas</title>
      <link>https://dev.to/arijusg</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/arijusg"/>
    <language>en</language>
    <item>
      <title>How to generate a list of compliance labels for all your gitlab repos</title>
      <dc:creator>Arijus Gilbrantas</dc:creator>
      <pubDate>Thu, 08 Feb 2024 13:29:56 +0000</pubDate>
      <link>https://dev.to/arijusg/how-to-generate-a-list-of-compliancy-labels-for-all-your-gitlab-repos-eh0</link>
      <guid>https://dev.to/arijusg/how-to-generate-a-list-of-compliancy-labels-for-all-your-gitlab-repos-eh0</guid>
      <description>&lt;p&gt;Install &lt;code&gt;pip install python-gitlab&lt;/code&gt; then run the script. It generates csv which includes full_path, http_url_to_repo and compliance_frameworks.&lt;/p&gt;

&lt;p&gt;generate.py&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from gitlab import Gitlab
import csv

gitlab_token = "&amp;lt;&amp;lt;my_token&amp;gt;&amp;gt;"
root_group_name = "&amp;lt;&amp;lt;group_name&amp;gt;&amp;gt;&amp;gt;"
output_file_name = "gitlab_compliancy_labels.csv"

gl = Gitlab(private_token=gitlab_token)
gl.auth()
# gl.enable_debug()

with open(output_file_name, "w", newline="") as csvfile:
    writer = csv.writer(csvfile, delimiter=",", quotechar="|", quoting=csv.QUOTE_MINIMAL)

    group = gl.groups.get(root_group_name)
    projects = group.projects.list(archived=0, order_by="path", include_subgroups=True, all=True)
    writer.writerow(["path", "git_url", "compliance_frameworks"])
    for project in projects:
        compliance_frameworks = project.attributes.get("compliance_frameworks")
        name = project.attributes.get("namespace")["full_path"]
        git_url = project.attributes.get("http_url_to_repo")
        writer.writerow([name, git_url, "|".join(compliance_frameworks)])
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>devops</category>
      <category>gitlab</category>
      <category>compliance</category>
    </item>
    <item>
      <title>How to run python based AWS lambda tests using pytest and localstack</title>
      <dc:creator>Arijus Gilbrantas</dc:creator>
      <pubDate>Thu, 08 Feb 2024 11:03:02 +0000</pubDate>
      <link>https://dev.to/arijusg/how-to-run-python-based-aws-lambda-tests-using-pytest-and-localstack-5f1k</link>
      <guid>https://dev.to/arijusg/how-to-run-python-based-aws-lambda-tests-using-pytest-and-localstack-5f1k</guid>
      <description>&lt;p&gt;Turns out, setting up pytest and localstack is more difficult than expected, but got it working :) &lt;/p&gt;

&lt;p&gt;I have two modes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Local mode: localstack is running in a terminal window using &lt;code&gt;localstack start&lt;/code&gt;. This is useful for TDD, as you don't spin up new containers everytime.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CICD mode: localstack is not running and it would be automatically started before all tests start.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Install packages: &lt;br&gt;
&lt;code&gt;pip install docker boto3 localstack_utils localstack-client&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Setup pytest conftest:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;conftest.py&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import boto3
import docker
import pytest
import localstack_client.session
from localstack_utils.localstack import startup_localstack, stop_localstack

# patch boto3 to automatically use localstack.
@pytest.fixture(autouse=True)
def boto3_localstack_patch(monkeypatch):
    session_ls = localstack_client.session.Session()
    monkeypatch.setattr(boto3, "client", session_ls.client)
    monkeypatch.setattr(boto3, "resource", session_ls.resource)

# check if localstack running locally using docker lib, if not running, use localstack lib to start it.
def is_localstack_running() -&amp;gt; bool:
    try:
        docker_client = docker.DockerClient(base_url='unix://var/run/docker.sock')
        container = docker_client.containers.get("localstack-main")
        return container.attrs['State']['Status'] == "running"
    except:
        return False

@pytest.fixture(autouse=True, scope="session")
def setup_localstack():
    if not is_localstack_running():
        print("Setup localstack")
        startup_localstack()
    yield
    if not is_localstack_running():
        print("Teardown localstack")
        stop_localstack()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fixture to cleanup after each test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@pytest.fixture(autouse=True)
def aws_fixture():
    # print("Setup")
    yield
    # print("Cleanup")

    # Cleanup S3
    s3 = boto3.client("s3")
    buckets = [item["Name"] for item in s3.list_buckets()["Buckets"]]
    for bucket in buckets:
        s3.delete_bucket(Bucket=bucket)

    # Cleanup DynamoDB"
    dynamodb = boto3.client("dynamodb")
    tables_names = dynamodb.list_tables()["TableNames"]
    for table_name in tables_names:
        dynamodb.delete_table(TableName=table_name)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then in the handler, add boto3 client. This funky way complies with AWS recommended way initialising global libraries and pytest can patch boto3 too.&lt;/p&gt;

&lt;p&gt;handler.py&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import functools

# DynamoDB Client
@functools.cache
def dynamodb_client() -&amp;gt; DynamoDBClient:
    return boto3.client("dynamodb")

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thats it. &lt;/p&gt;

&lt;p&gt;Full example:&lt;/p&gt;

&lt;p&gt;handler_blog.py&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from aws_lambda_powertools.utilities.data_classes.sqs_event import SQSEvent
from aws_lambda_powertools.utilities.typing import LambdaContext
import functools
from mypy_boto3_dynamodb import DynamoDBClient
import boto3


# DynamoDB Client setup
@functools.cache
def dynamodb_client() -&amp;gt; DynamoDBClient:
    return boto3.client("dynamodb")


def handler(event: SQSEvent, context: LambdaContext):
    dynamodb_client().update_item(
        TableName="super-table",
        Key={"objectId": {"N": "123456"}},
        ExpressionAttributeNames={"#name": "name"},
        ExpressionAttributeValues={":name": {"S": "batman"}},
        UpdateExpression="set #name = :name",
        ReturnValues="NONE",
    )
    return []
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;handler_blog_test.py&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import pytest
import boto3
from typing import Literal
from pydantic import BaseModel
from aws_lambda_powertools.utilities.data_classes.sqs_event import SQSEvent

from .handler_blog import handler


@pytest.fixture(autouse=True)
def aws_fixture():
    # print("Setup")
    yield
    # print("Cleanup")

    # DynamoDB"
    dynamodb = boto3.client("dynamodb")
    tables_names = dynamodb.list_tables()["TableNames"]
    for table_name in tables_names:
        dynamodb.delete_table(TableName=table_name)


class CreateDynamoDB(BaseModel):
    table_name: str
    pk_name: str
    pk_type: Literal["S", "N"]


@pytest.fixture
def create_dynamodb_table(create_dynamodb_table_config: CreateDynamoDB):
    config = create_dynamodb_table_config
    dynamodb = boto3.client("dynamodb")
    dynamodb.create_table(
        TableName=config.table_name,
        KeySchema=[
            {"AttributeName": config.pk_name, "KeyType": "HASH"},
        ],
        AttributeDefinitions=[
            {
                "AttributeName": config.pk_name,
                "AttributeType": config.pk_type,
            }
        ],
        BillingMode="PAY_PER_REQUEST",
    )


class InsertDataSQSMessage(BaseModel):
    id: int
    name: str


def get_sqs_event_stub(body: InsertDataSQSMessage) -&amp;gt; SQSEvent:
    return SQSEvent(
        {
            "Records": [
                {
                    "messageId": "4f332f15-3930-4a00-8831-1706016678846",
                    "receiptHandle": "AQEB/123456789012+bjpfjcbH0fslWvMxNSXEJWn/VNCIi0TYmuZakYNQpQhhcl2EoPseeM4ctyfd/OQ5eiMqWhta+L+iZYIuHRQiIIjmMgJrfJsl6aVHI1vYQvTTwhxaBJh2582kvuAaRvQ0gbLzT/Pe+Zp+123456789012/2Luka8cdrsLlSHEHI+21N+tN5dOaxBoGCJk1wZti6UmcrEzz3T+123456789012/O+mbqSPvJEJnbGasJRUFcKIfocbokN4sMSl8eJJKN1QkWPqxinVmk1DkEYzyY+rzSTjE8IBgcGRrxc293eYDJdfzISXo8j97h83ITP4fm1vMDA2w0/cDvvL3m4ACmZjwoZWdfoBTvJwbB8bXEa86Ykew==",
                    "body": body.model_dump_json(),
                    "attributes": {
                        "ApproximateReceiveCount": "1",
                        "SentTimestamp": "1706016678845",
                        "SenderId": "XXXXXXXXXXXXXXXXXXXXX",
                        "ApproximateFirstReceiveTimestamp": "1706016678846",
                    },
                    "messageAttributes": {},
                    "md5OfBody": "28f07e09c08aba530422dd193f991111",
                    "eventSource": "aws:sqs",
                    "eventSourceARN": "arn:aws:sqs:eu-central-1:123456789012:data",
                    "awsRegion": "eu-central-1",
                }
            ]
        }
    )


class TestHandlerBlog:

    def setup_method(self, method):
        pass

    def teardown_method(self, method):
        pass

    create_dynamodb_table_config = CreateDynamoDB(table_name="super-table", pk_name="objectId", pk_type="N")

    @pytest.mark.parametrize("create_dynamodb_table_config", [create_dynamodb_table_config])
    def test_insert_some_data_to_dynamodb(self, create_dynamodb_table):
        # Prepare
        # Act
        event = get_sqs_event_stub(InsertDataSQSMessage(id=18393972737, name="company"))
        result = handler(event=event, context={})

        # Assert function
        assert result == []

        # Assert Table content
        response = boto3.client("dynamodb").scan(TableName="super-table")
        table_records = response["Items"]
        assert len(table_records) == 1
        assert table_records == [
            {
                "objectId": {"N": "123456"},
                "name": {"S": "batman"},
            }
        ]

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ps. I also mapped vs code key "F1" to "testing.runAtCursor" and "F2" to "testing.runAll".&lt;/p&gt;

</description>
      <category>python</category>
      <category>aws</category>
      <category>pytest</category>
      <category>localstack</category>
    </item>
    <item>
      <title>Connect to EC-2 instance using SSH, SSM and VS Code. No public access or ports required</title>
      <dc:creator>Arijus Gilbrantas</dc:creator>
      <pubDate>Fri, 22 Dec 2023 09:54:42 +0000</pubDate>
      <link>https://dev.to/arijusg/connect-to-ec-2-instance-using-ssh-ssm-and-vs-code-no-public-access-or-ports-required-5bjn</link>
      <guid>https://dev.to/arijusg/connect-to-ec-2-instance-using-ssh-ssm-and-vs-code-no-public-access-or-ports-required-5bjn</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--a8yd5dlI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uha9gkfkvslk572jrnqj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--a8yd5dlI--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uha9gkfkvslk572jrnqj.png" alt="Image description" width="800" height="1277"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Install SSH extension to vs code.&lt;/li&gt;
&lt;li&gt;Import your public Yubikey cert to AWS Key pairs and name it Yubi.&lt;/li&gt;
&lt;li&gt;Deploy EC2 with Yubi key attached.&lt;/li&gt;
&lt;li&gt;Add the following to &lt;code&gt;~/.ssh/config&lt;/code&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# This is needed for VS code to know how to connect
Host i-0b377eeb467ae2f92
    HostName i-0b377eeb467ae2f92

# SSH over Session Manager implementation. Change profile as you wish.
Host i-* mi-*
    User ec2-user
    ProxyCommand sh -c "aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters 'portNumber=%p' --profile &amp;lt;&amp;lt;my_aws_profile&amp;gt;&amp;gt; --region eu-central-1"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;CMD + Shift + P → Remote-SSH: Connect Current Window to Host… &lt;/li&gt;
&lt;li&gt;Select your host and connect.&lt;/li&gt;
&lt;li&gt;You can open folder too, for file editing/transfer.&lt;/li&gt;
&lt;li&gt;Celebrate!&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>aws</category>
      <category>ec2</category>
      <category>vscode</category>
    </item>
    <item>
      <title>Make useful JS/TS errors in AWS Lambda</title>
      <dc:creator>Arijus Gilbrantas</dc:creator>
      <pubDate>Fri, 31 Jul 2020 12:10:56 +0000</pubDate>
      <link>https://dev.to/arijusg/make-useful-js-ts-errors-in-aws-lambda-313i</link>
      <guid>https://dev.to/arijusg/make-useful-js-ts-errors-in-aws-lambda-313i</guid>
      <description>&lt;p&gt;I have been playing with custom errors and how it can help with monitoring AWS lambda based APIs.&lt;/p&gt;

&lt;p&gt;The native Error of JavaScript is kinda meh. You can only set a message, which, I find, is not enough to determine an issue in production, i.e. ‘http request failed’. What I would love to know is some context, at least: url, headers, payload, response.&lt;/p&gt;

&lt;p&gt;I have tried to stringify error context and attach to an error message, but it was difficult to read the logs and therefore didn't spark joy. &lt;/p&gt;

&lt;p&gt;Ultimately, I would prefer not to look at the logs at all, the monitoring tool should tell me what's wrong and maybe even give a solution. Got better things to do, than debugging logs :)))&lt;/p&gt;

&lt;h2&gt;
  
  
  Error does not like serialisation.
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;try {
  throw new Error("Something bad happened");
} catch (err) {
  const serialised = JSON.stringify(err);
  console.log(serialised);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;outputs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Let's serialise the Error
&lt;/h2&gt;

&lt;p&gt;So what we could do is to create an BaseError, which has a property reason.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export class BaseError extends Error {
    public readonly reason: string;

    constructor(reason: string) {
        super(reason);
        this.name = BaseError.name;
        Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain

        this.reason = reason;
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;now when we run this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;try {
  throw new BaseError("Something bad happened");
} catch (err) {
  let serialised = JSON.stringify(err);
  console.log(serialised);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we get&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{"name":"BaseError","reason":"Something bad happened"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Let's make a custom Error
&lt;/h2&gt;

&lt;p&gt;So once we have a BaseError, we can create an purpose built error like that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export class SuperHeroError extends BaseError {
  public readonly superHeroName: string;

  constructor(superHeroName: string) {
    super(`superhero ${superHeroName} malfunctioned`);
    this.name = SuperHeroError.name;
    Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain

    this.superHeroName = superHeroName;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and when we run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;try {
  throw new SuperHeroError("Batman");
} catch (err) {
  let serialised = JSON.stringify(err);
  console.log(serialised);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;we get this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{"name":"SuperHeroError","reason":"superhero Batman malfunctioned","superHeroName":"Batman"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  AWS CloudWatch LogInsights report
&lt;/h2&gt;

&lt;p&gt;Once we have some failure logs, we can write a query like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fields @timestamp as timestamp, name, reason
| sort @timestamp desc
| limit 100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  and the result will be:
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;timestamp&lt;/th&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;th&gt;reason&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2020-07-23 12:59:30.716&lt;/td&gt;
&lt;td&gt;SuperHeroError&lt;/td&gt;
&lt;td&gt;superhero Batman malfunctioned&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;This is very basic error logging, in the real world you will need much more data in the logs, like sessionId, requestId etc. :)&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>logs</category>
      <category>lambda</category>
    </item>
  </channel>
</rss>
