Skip to content

Commit

Permalink
task: initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
lukaszlach-arch authored and lukaszlach committed Sep 18, 2019
0 parents commit a21474e
Show file tree
Hide file tree
Showing 11 changed files with 338 additions and 0 deletions.
1 change: 1 addition & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#builder/*.go
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.idea/
htpasswd*
23 changes: 23 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
FROM golang:1.13-alpine AS builder
WORKDIR /builder
ENV GOPATH=/builder
COPY ./builder /builder
RUN go build .

FROM alpine:3.10 AS docker
ARG DOCKER_CLI_VERSION=19.03.1
RUN apk --update add curl && \
mkdir /docker && \
curl -sSfL "https://download.docker.com/linux/static/stable/x86_64/docker-$DOCKER_CLI_VERSION.tgz" | tar -xz -C /docker && \
mv /docker/docker/docker /usr/local/bin/ && \
rm -rf /docker && \
docker --help

FROM alpine:3.10 AS release
EXPOSE 5050/tcp
ENV BUILDER_BASE_IMAGE=alpine
CMD ["builder"]
RUN apk --no-cache add curl bash
COPY --from=builder /builder/builder /usr/local/bin/
COPY --from=docker /usr/local/bin/docker /usr/local/bin/
COPY ./builder/* /builder/
24 changes: 24 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
MIT License

Copyright (c) 2019 Łukasz Lach <llach@llach.pl>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Copyright (c) 2019 Coder Technologies Inc.
https://github.com/cdr/code-server/blob/master/LICENSE
26 changes: 26 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
.PHONY: build start clean-start stop restart clean logs
export DOCKER_BUILDKIT=1
export BUILDER_REGISTRY_USERNAME=
export BUILDER_REGISTRY_PASSWORD=

build:
docker-compose build

auth:
docker run --entrypoint htpasswd registry:2 -Bbn ${BUILDER_REGISTRY_USERNAME} ${BUILDER_REGISTRY_PASSWORD} > htpasswd.registry

start:
docker-compose up --force-recreate --build -d

clean-start: clean start

stop:
docker-compose down --remove-orphans

restart: stop start

clean:
docker-compose rm -f -s -v

logs:
docker-compose logs -f
105 changes: 105 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# Commando

![Version](https://img.shields.io/badge/version-0.1.0-lightgrey.svg?style=flat)

**Commando** generates Docker images on-demand with all the commands you need and simply point them by name in the `docker run` command. **Commando** is SysOps and DevOps best friend.

![](https://user-images.githubusercontent.com/5011490/65174414-25080700-da51-11e9-8f88-d3a69728c0a6.gif)

## Features

When running a Docker image you will enter the Bash shell by default and have the requested commands available.

Commando is deployed under `cmd.cat` and publicly available, you can freely use it but do not depend on it's stability in your projects as it is hosted on my private server with limited resources.

The image is based on Alpine and the builder does its best to reuse the existing layers when using multiple commands. This way both `cmd.cat/envsubst/curl` and `cmd.cat/curl/envsubst` are the same images, also `cmd.cat/envsubst/tcpdump/curl` adds only one extra layer.

[![](http://www.brendangregg.com/Perf/linuxperftools.png)](http://www.brendangregg.com/blog/2014-11-22/linux-perf-tools-2014.html)

> Source: [Linux PerfTools](http://www.brendangregg.com/linuxperf.html)
```bash
# One command.
docker run -it cmd.cat/strace
docker run -it cmd.cat/ab

# Two...
docker run -it cmd.cat/curl/wget
docker run -it cmd.cat/htop/iostat

# ... or a lot of commands, how many you need.
docker run -it cmd.cat/ping/nmap/whois
docker run -it cmd.cat/ngrep/tcpdump/ip/ifconfig/netstat
```

Use the generated image with host/container pid/network modes to debug and monitor your containers or the host system.

![](https://user-images.githubusercontent.com/5011490/65175421-25090680-da53-11e9-80db-37c111d5a640.gif)

```bash
docker run -d --name nginx nginx

# Enter the shell with all network tools available
docker run -it --net container:nginx cmd.cat/curl/ab/ngrep
# Monitor all network interfaces of the nginx container
docker run -it --net container:nginx cmd.cat/ngrep ngrep -d any
```

```bash
docker run -d --name redis redis

# Monitor the processes running inside the redis container
docker run -it --pid container:redis cmd.cat/htop htop
```

```bash
# Monitor network and processes on the host system
docker run -it --net host --pid host cmd.cat/htop/ngrep
```

## Running

Run the project locally or deploy it internally inside your company with a single command that will pull all the required images and build the registry proxy:

```bash
git clone https://github.com/lukaszlach/commando.git
cd commando
docker-compose up -d
```

Run any command built locally the same way:

```bash
docker run -it localhost:5050/tcpdump
docker run -it localhost:5050/strace/php
```

> The first run needs to build the base image so it takes longer than all further calls.
## License

MIT License

Copyright (c) 2019 Łukasz Lach <llach@llach.pl>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

---

Google [Nixery](https://github.com/google/nixery) :heart:
16 changes: 16 additions & 0 deletions builder/alpine/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# This stage is built once on the first hit and cached afterwards
FROM alpine:3.10 AS dependencies
ARG APK_FILE_VERSION=0.3.5
RUN apk --no-cache add curl && \
curl -sSfL https://github.com/genuinetools/apk-file/releases/download/v${APK_FILE_VERSION}/apk-file-linux-amd64 -o /apk-file && \
export APK_FILE_SHA256="6cbd92aea6448b0f526d76e1a910b97799bbcba9bed99a6049d0b80a03e2295c" && \
echo "$APK_FILE_SHA256 /apk-file" | sha256sum -c - && \
chmod a+x /apk-file
COPY ./install.sh /

# Pick the newest Alpine version
FROM alpine AS release
CMD ["/bin/bash"]
RUN apk --no-cache add bash ca-certificates
COPY --from=dependencies /install.sh /apk-file /
#RUN bash /install.sh "$COMMANDS"
28 changes: 28 additions & 0 deletions builder/alpine/install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
COMMANDS=$1
PACKAGES=
# @todo why can I not make the IFS work here?!
while read COMMAND; do
APK_FILE=$(/apk-file "bin/$COMMAND" | grep " x86")
if [ -z "$APK_FILE" ]; then
echo "Error: Cannot find package for the $COMMAND command"
exit 1
fi
BINARY=$(echo "$APK_FILE" | awk '{print $1}' | sort | uniq | grep -E "/s?bin/${COMMAND}$" | head -n 1)
if [ ! -z "$BINARY" ]; then
PACKAGE=$(echo "$APK_FILE" | grep -E "^$BINARY " | awk '{print $2}')
fi
if apk info | grep -qE "^${PACKAGE}$" >/dev/null; then
echo "Notice: Package already installed"
exit 0
fi
if [ ! -z "$PACKAGE" ]; then
PACKAGES="${PACKAGES}${PACKAGE} "
fi
done < <(echo "$COMMANDS" | tr ' ' $'\n')
if [ -z "$PACKAGES" ]; then
echo "Error: Nothing to install"
exit 1
fi
apk --no-cache add ${PACKAGES}
exit 0
43 changes: 43 additions & 0 deletions builder/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package main

import(
"log"
"net/url"
"net/http"
"net/http/httputil"
"regexp"
"os/exec"
)

func main() {
remote, err := url.Parse("http://registry:5000")
if err != nil {
panic(err)
}

proxy := httputil.NewSingleHostReverseProxy(remote)
http.HandleFunc("/", handler(proxy))
err = http.ListenAndServe(":5050", nil)
if err != nil {
panic(err)
}
}

func handler(p *httputil.ReverseProxy) func(http.ResponseWriter, *http.Request) {
var manifestExpr = regexp.MustCompile(`^/v2/[a-zA-Z0-9._/-]+/manifests/latest$`)
return func(w http.ResponseWriter, r *http.Request) {
log.Println(r.URL)
if r.Method == http.MethodGet && manifestExpr.MatchString(r.URL.String()) {
log.Println("Executing shell command")
cmd := exec.Command("bash", "/shell.sh", r.URL.String());
out, err := cmd.CombinedOutput()
if err != nil {
log.Println("Error executing shell command")
w.WriteHeader(http.StatusBadRequest)
w.Write(out)
return
}
}
p.ServeHTTP(w, r)
}
}
43 changes: 43 additions & 0 deletions builder/shell.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
URL="$1"
if curl -sSf -m 1 -I "registry:5000$URL"; then
# Image already exists in the registry
exit 0
fi
set -e
TARGET_IMAGE=$(echo "$URL" | sed 's|^/v2/||; s|/manifests/latest$||')
PARAMS=$(echo "$TARGET_IMAGE" | sed 's|^/v2/||; s|/manifests/latest$||' | tr '/' ' ')
set -- $PARAMS
COMMANDS=$(printf '%s\n' "$@" | sort)
if [ -z "$COMMANDS" ]; then
echo "Error: No commands specified"
exit 1
fi
image_tag () {
echo "$BUILDER_BASE_IMAGE $@" | xargs | md5sum | awk '{print $1}'
}
IMAGE_TAG=$(image_tag "$COMMANDS")
TEMP_DOCKERFILE="/tmp/Dockerfile.$IMAGE_TAG"
cp -f "/builder/$BUILDER_BASE_IMAGE/Dockerfile" "$TEMP_DOCKERFILE"
while read COMMAND; do
SANITIZED_COMMAND=$(echo "$COMMAND" | sed 's/[^a-zA-Z0-9._~-]//g')
if [ "$SANITIZED_COMMAND" != "$COMMAND" ]; then
echo "Error: $COMMAND contains invalid characters (sanitized to $SANITIZED_COMMAND)"
exit 1
fi
echo "RUN bash /install.sh $COMMAND" >> "$TEMP_DOCKERFILE"
done <<< $COMMANDS
docker build -q \
--force-rm \
--build-arg "COMMANDS=$COMMANDS" \
-t "$IMAGE_TAG" \
-f "$TEMP_DOCKERFILE" \
"/builder/$BUILDER_BASE_IMAGE"
rm -f "$TEMP_DOCKERFILE"
docker tag "$IMAGE_TAG" "registry:5000/$TARGET_IMAGE"
if [ ! -z "$BUILDER_REGISTRY_USERNAME" ] && [ ! -z "$BUILDER_REGISTRY_PASSWORD" ]; then
echo "$BUILDER_REGISTRY_PASSWORD" | \
docker login --username "$BUILDER_REGISTRY_USERNAME" --password-stdin "registry:5000"
fi
docker push "registry:5000/$TARGET_IMAGE"
exit 0
27 changes: 27 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
version: '3'

services:
registry:
container_name: builder_registry
image: registry:2
# environment: ["REGISTRY_AUTH=htpasswd", "REGISTRY_AUTH_HTPASSWD_REALM=cmd.cat", "REGISTRY_AUTH_HTPASSWD_PATH=/htpasswd"]
# volumes: ["./htpasswd.registry:/htpasswd:ro"]
docker:
container_name: builder_docker
image: docker:stable-dind
privileged: true
environment:
DOCKER_TLS_CERTDIR:
command: ["--insecure-registry=registry:5000"]
builder:
container_name: builder
build: .
volumes:
- ./builder/shell.sh:/shell.sh:ro
- ./builder/:/builder:ro
ports: ["5050:5050"]
depends_on: ["docker", "registry"]
environment:
DOCKER_HOST: tcp://docker:2375
BUILDER_REGISTRY_USERNAME:
BUILDER_REGISTRY_PASSWORD:

0 comments on commit a21474e

Please sign in to comment.