DEV Community

Cover image for Day 29: A Pull Request Needs Two People, and Peering Needs Two Routes
Nnamdi Felix Ibe
Nnamdi Felix Ibe

Posted on

Day 29: A Pull Request Needs Two People, and Peering Needs Two Routes

Some things cannot be finished alone, by design. A change that only you have seen is not reviewed. A network connection that only works in one direction is not a connection. Day 29 was two versions of the same idea: both sides have to agree, and half of it is worse than none because half of it looks like it should work.

One Git task, one AWS task. Open a pull request, get it reviewed and merged, then peer a public VPC with a private one so instances on each side can talk. The tasks come from the KodeKloud Engineer platform.

Pull requests: the gate, not the merge

The first thing worth saying is that a pull request is not a Git feature. Git has no idea what one is. It is a workflow layer that hosting platforms bolt on top — Gitea here, GitHub and GitLab elsewhere — and underneath, a merged PR is git merge and nothing more.

Which raises the obvious question: if the merge is trivial, what is the PR for?

The gate. A named reviewer, a recorded approval, a discussion attached to the change, and a permanent record of who agreed to what. On this task, that meant opening the request against master from a feature branch, adding a second user as reviewer, then logging in as that user to read the diff, approve it, and merge with a merge commit.

# Everything before and after the PR is ordinary git
git log --oneline fox-grapes        # what you are proposing
git log --oneline master            # what it is going into

# ...PR happens in the web UI...

git checkout master
git pull origin master
git log --oneline --graph --all --decorate
Enter fullscreen mode Exit fullscreen mode

Two things catch people out. Base and compare get reversed constantly — base is where the code is going, compare is where it comes from. Flip them, and you propose merging master into your feature branch, which usually shows nothing and reads as a broken UI rather than a mistake.

And the reviewer has to be a genuinely different user. Approving your own pull request is the one move that empties the mechanism of all meaning, which is why most platforms refuse it.

Worth knowing the third option too: "create a merge commit" preserves the branch shape, exactly the --no-ff behaviour from Day 25. Squash flattens the branch to one commit. Rebase replays them with no merge commit at all. Same code, three different stories in the history.

One small thing that trips everyone once: merging on the server does nothing to your local clone. git pull afterwards or your master sits silently behind.

VPC peering: one connection, two route tables

The AWS task was peering a public VPC with a private one so an instance in each could reach the other. And it opened with a problem that had nothing to do with peering at all.

SSH to the public instance simply hung. No refusal, no error — just silence. The cause was in the security group:

"IpPermissions": [{
  "IpProtocol": "tcp", "FromPort": 22, "ToPort": 22,
  "IpRanges": [],
  "UserIdGroupPairs": [{ "GroupId": "sg-xxxxxxxx" }]
}]
Enter fullscreen mode Exit fullscreen mode

That rule says "allow SSH from anything already inside this security group" — and the group it references is the one attached to the instance itself. Traffic from another instance in that group would match. My client host, sitting outside AWS entirely, never could.

That is the tell worth memorising: UserIdGroupPairs populated and IpRanges empty means the rule is SG-referenced, not CIDR-based. Nothing external gets in, and because security groups drop rather than reject, you get a hang instead of a message.

Then a second problem, which I liked. The task wanted my public key in the instance's authorized_keys. But writing that file needs SSH, and SSH needs that file.

EC2 Instance Connect breaks the loop. It pushes a public key to the instance out-of-band through the AWS API, valid for sixty seconds — long enough to get in and write the key permanently:

aws ec2-instance-connect send-ssh-public-key --region us-east-1 \
  --instance-id <instance-id> --instance-os-user ec2-user \
  --availability-zone us-east-1b \
  --ssh-public-key file:///root/.ssh/id_rsa.pub \
&& ssh -o StrictHostKeyChecking=no -i /root/.ssh/id_rsa ec2-user@<public-ip> \
  "mkdir -p ~/.ssh && chmod 700 ~/.ssh && echo '$(cat /root/.ssh/id_rsa.pub)' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
Enter fullscreen mode Exit fullscreen mode

The && is not stylistic. Sixty seconds is not enough to run two commands you are typing by hand, and the second one failing on an expired key looks exactly like a permissions problem.

With access sorted, the peering itself is four checks, and this is the part that matters:

# 1. The connection must be ACTIVE, not pending-acceptance
aws ec2 accept-vpc-peering-connection --vpc-peering-connection-id <pcx-id>

# 2 and 3. A route on EACH side, pointing to the other's CIDR
aws ec2 create-route --route-table-id $DEF_RTB \
  --destination-cidr-block 10.1.0.0/16 --vpc-peering-connection-id <pcx-id>

aws ec2 create-route --route-table-id $PRIV_RTB \
  --destination-cidr-block 172.31.0.0/16 --vpc-peering-connection-id <pcx-id>

# 4. The target SG must allow the traffic type from the source CIDR
aws ec2 authorize-security-group-ingress --group-id <private-sg> \
  --protocol icmp --port -1 --cidr 172.31.0.0/16
Enter fullscreen mode Exit fullscreen mode

Peering is bidirectional by nature but not by configuration. One connection object, two route tables, and you have to edit both. A route on only one side produces traffic that arrives and cannot reply, which presents as a total failure rather than a partial one.

Three details worth carrying forward. --port -1 with ICMP means all ICMP types — ping is ICMP, not TCP, so opening TCP ports does precisely nothing for it. If all four checks pass and it still fails, look at network ACLs, which, unlike security groups, are stateless and need explicit rules in both directions. And peering is not transitive: A peered to B and B peered to C does not let A reach C.

One honest note. I opened port 22 to 0.0.0.0/0 to get moving in a throwaway lab, which directly contradicts what I wrote on Day 22 about scoping SSH to /32. In a lab, that is a shortcut. Anywhere real, it is the thing you get audited for, and I would rather flag it than leave it in a code block.

Two small mistakes that cost me time, both embarrassing and both instructive. I pasted a placeholder straight into the shell — <sg-id> makes bash attempt an input redirect and throw No such file or directory. The angle brackets are notation, not syntax. And I ran ping "$PRIVATE_IP" on the remote host, where that variable had never been set. Local shell variables do not travel over SSH.

Half a handshake is not a handshake

An approval you gave yourself is not a review. A route on one side is not a path. Both tasks fail in the same quiet way: everything you configured is correct, and the thing still does not work, because the other half was never done.

So here is the Day 29 question. Where in your setup have you built one direction of something and assumed the other side agreed?

Day 29 down. Seventy-one to go.

Top comments (0)