Disable ssh host key check

2019-09-26

When automating a task, there's nothing more annoying than an "are you sure?"-question popping up.

If you want to automate an ssh connection, chances are this is the first time you're connecting to your target. Thus, you'll be presented with the following message:

$ ssh example.com
The authenticity of host 'example.com (...)' can't be established.
RSA key fingerprint is SHA256:P2VbGDRAAaPQTaBovKynIccHxse4aXNH0ZgGLyVTzQL.
Are you sure you want to continue connecting (yes/no/[fingerprint])?

This check can be turned off with the StrictHostKeyChecking option:

$ ssh -o StrictHostKeyChecking=no example.com

Caution: This is a security check to mitigate different attacks. Only turn this feature off if you understand the risks.


PostgreSQL: Include files by relative path

2019-09-24

You can include files in psql using the relative path like so:

\ir <filename>

Or, as a longer alternative:

\include_relative <filename>

Note:

When using \ir from a file that was just included using \ir, the path will be interpreted relative to the nearest file in the include tree.


Git stage parts of a file in VS Code

2019-09-23

With VS Code, you can interactively select which parts of a file should be staged:

  1. Make changes to a file that is managed with git
  2. Go to the Working Tree view of that file
  3. Select the lines you want to stage and click the right mouse button Diff view
  4. Click Stage Selected Ranges Menu

Additionally, you can also unstage or revert the selected changes.


Using Podman in bridged network mode

2019-09-21

Podman is really great for those of us who don't want the Docker daemon running in the background all the time.

It is mostly compatible with Dockerfiles and Docker CLI syntax (as far as I've read online and noticed, while poking at both of them), but some things are handled differently due to the nature of Podman's daemonless architecture.

I was trying to create a couple of Docker containers with IP addresses so I could test some Ansible scripts. I have to make sure the scripts work for different distributions and different versions of those distributions and managing virtual machines (or even clusters of those) in VirtualBox can become a bit frustrating.

So for Ansible, I usually need hostnames or IP addresses to define against which hosts my playbooks are run. But whenever I was running containers, they didn't have IP addresses:

[[email protected] ansible]$ podman run -dit --name centos1 centos:7
faf5abe92901c4757982bbcb39f0a89800e7378358fff88908bea09161922282
[[email protected] ansible]$ podman inspect centos1 | grep -i ipaddress
            "SecondaryIPAddresses": null,
            "IPAddress": "",

Of course, I could open up ports by publishing them with the -p flag, but that won't help me to use Ansible.

Google to the rescue??

Googling turned up no helpful advice, I saw that the --net host option is dangerous and should be used with caution, but no help on bridged networking, as I know it from VirtualBox.

After reading the docs on podman run, I figured it out: if you're running Podman without root, it can only use the network mode slirp4netns, which will create an isolated network stack so that you can connect to the internet from the inside of your container and bind certain ports of your container to the user-bindable ports on you host, but nothing more.

To be able to select the network mode bridged, which does exactly what I need, you'll have to run Podman as root. It turns out that the bridged mode is the default for running Podman as root.

Now, to further streamline my Ansible testing procedure, I can even specify which IP address should be used by the container:

[[email protected] ansible]$ sudo podman run -dit --ip 10.88.0.42 --name centos3 centos:7
3b56ae068a628027c7d8815485022f6cb59c7aa5d26e6bf4137961ecb6307952
[[email protected] ansible]$ sudo podman inspect centos3 | grep -i ipaddress
            "SecondaryIPAddresses": null,
            "IPAddress": "10.88.0.42",

And of course, I can now reach any ports I open in the container via this IP address.

Note however, that these containers can only be reached from your own machine, via the bridge interface usually called cni0, which is created by Podman. To access the containers by IP from other machines in your network, you'd need to bridge them to the 10.88.0.1/16 subnet somehow... But that will have to wait for another TIL for another day...


The HTML Base element

2019-09-20

The HTML <base> element specifies attributes for all links in the document at once.
Let's take a look at this example:

<head>
  <base target="_blank">
</head>

Now all links will open in a new tab by default.

Additionally, the href attribute can be set within <base> tag. This sets the base URL to be used throughout the document for relative URL addresses.

For example:

<head>
  <base href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base">
</head>

The following link:

<body>
  ...
  <a target="_blank" href="#Usage_notes">Usage notes</a>
  ...
</body>

Will actually point to

https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base#Usage_notes

Note:

  • <base> shouldn't have a closing tag. 🤷
  • There can be only one base element in a document.

More info ℹ️


Destructuring assignment features in es6

2019-09-19

Today I learned about the helpful ES6 "destructuring" feature to unpack arrays and objects.
It is a convenient way to extract values into distinct variables.

It is possible to object-destructure arrays:

const { 0: x, 2: y, 3: z } = ['a', 'b', 'c', 'd'];
console.log(x) // 'a'
console.log(z) // 'd'

This works because array indices are properties as well!

Alternatively, array-destructuring can be applied to any value that is iterable, not just to arrays:

// Sets are iterable
const mySet = new Set().add('a').add('b').add('c');
const [first, second] = mySet;
console.log(first) // 'a'
console.log(second) // 'b'

// Strings are iterable
const [a, b] = 'xyz';
console.log(a) // 'x'
console.log(b) // 'y'

Get Pull Request Approval with the GitHub API (v4)

2019-09-18

Natively, the GitHub API does not provide a way to obtain a pull request's approval status. Here's a workaround.

It is necessary to compare the date of the newest commit and the date of last approval, because new commits automatically invalidate any approvals (default behavior, can be configured).

import { graphql } from "@octokit/graphql"
import { Repository, PullRequest } from "./types"

const query = graphql.defaults({
  headers: {
    authorization: `token ${process.env.GITHUB_TOKEN}`,
  },
})

function isApproved(pr: PullRequest): Boolean {
  if (!pr.reviews.edges.length) return false

  const latestCommit = new Date(pr.commits.edges[0].node.commit.authoredDate)
  const latestApproval = new Date(pr.reviews.edges[0].node.updatedAt)

  return latestApproval > latestCommit
}

async function getAllApprovedPullRequests(): Promise<PullRequest[] | null> {
  const queryResult: any = await query(`{
      repository(owner: "cybertec-postgresql", name: "today-i-learned-content") {
        pullRequests(last: 25, states: OPEN) {
          edges {
            node {
              title
              number
              reviews(states: APPROVED, last: 1) {
                edges {
                  node {
                    updatedAt
                  }
                }
              }
              commits(last: 1) {
                edges {
                  node {
                    commit {
                      authoredDate
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  `)

  const repo: Repository = queryResult.repository

  if (!repo.pullRequests.edges) return null

  let pullRequests: PullRequest[] = repo.pullRequests.edges
    .map(edge => edge.node)
    .filter(pr => isApproved(pr))

  return pullRequests
}

Pretty CSS Hack to debug layouts

2019-09-17

  1. Create a new bookmark
  2. Add the following code to the bookmark URL:
    javascript: (function() {
	var elements = document.body.getElementsByTagName('*');
	var items = [];
	for (var i = 0; i < elements.length; i++) {
		if (elements[i].innerHTML.indexOf('* { background:#000!important;color:#0f0!important;outline:solid #f00 1px!important; background-color: rgba(255,0,0,.2) !important; }') != -1) {
			items.push(elements[i]);
		}
	}
	if (items.length > 0) {
		for (var i = 0; i < items.length; i++) {
			items[i].innerHTML = '';
		}
	} else {
		document.body.innerHTML +=
			'<style>* { background:#000!important;color:#0f0!important;outline:solid #f00 1px!important; background-color: rgba(255,0,0,.2) !important; }\
            * * { background-color: rgba(0,255,0,.2) !important; }\
            * * * { background-color: rgba(0,0,255,.2) !important; }\
            * * * * { background-color: rgba(255,0,255,.2) !important; }\
            * * * * * { background-color: rgba(0,255,255,.2) !important; }\
            * * * * * * { background-color: rgba(255,255,0,.2) !important; }\
            * * * * * * * { background-color: rgba(255,0,0,.2) !important; }\
            * * * * * * * * { background-color: rgba(0,255,0,.2) !important; }\
            * * * * * * * * * { background-color: rgba(0,0,255,.2) !important; }</style>';
	}
})();

To use it, just navigate to a website and click on the bookmark you defined.

The image below shows this website with the bookmark activated.

Screenshot of Cybertec Layout

You can use it on any page.

Ain't that cool? 😀

P.S.: Tested on Chrome and Firefox.

Check the official post and this Gist for more information.


Typescript json validation with io-ts

2019-09-16

There's a Typescript library called io-ts that can help to strong-type json data fetched from the server and at the same time provide static typescript typing.

Problem

Imagine we have this line that fetches some data from an endpoint:

const employee = await fetchEmployee();

employee will probably have the any type. If we knew the shape of the employee object, we could create a type and cast employee:

type Employee {
  firstName: string;
  lastName: string;
}

const employee = await fetchEmployee() as Employee;

But now we are assuming the shape of employee, and if it changes in future versions of the backend, it can lead to annoying runtime errors such as accessing properties on undefined objects, which can be hard to track. We could validate it with a lib such as ajv, but we wouldn't be able to have a single source of truth.

Solution

With io-ts we can define a type like this:

import * as t from 'io-ts';

// The runtime type we will use to validate the data fetched from the server
const Employee = t.type({
  firstName: t.string,
  lastName: t.string,
});

// The static type. The above runtime type acts as the single source of truth
type Employee = t.TypeOf<typeof Employee>;

// Now we can do this (in pseudo-code)
const employee = Employee.decode(await fetchEmployee());

console.log(employee.firstName);  // works
console.log(employee.foo);        // typescript compile error

Now employee is correctly typed, and the shape of the data is validated at runtime.

Implementing the pseudo-code

The lib is great, but the documentation found in the repository's README is a little confusing. The easiest way I found to just validate data and throw if the shape is incorrect is like this:

import { getOrElse } from "fp-ts/lib/Either";  // io-ts has fp-ts as a peer dependency
import { failure } from "io-ts/lib/PathReporter";

const toError = (errors: any) => new Error(failure(errors).join('\n'));
const employee = getOrElse(toError)(Employee.decode(await fetchEmployee()));

if (employee instanceof Error) {
  throw employee;
}

console.log('the first name is', employee.firstName)

This steps can be easily extracted to a helper function.