Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lab3 #3

Merged
merged 15 commits into from
Mar 9, 2024
71 changes: 71 additions & 0 deletions .github/workflows/go-app.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python

name: Go application

on:
push:
paths:
- .github/workflows/**
- app_go/**

permissions:
contents: read

jobs:
lint-test-snyk:

runs-on: ubuntu-latest

strategy:
matrix:
go-version: ["1.21", "1.22"]

defaults:
run:
working-directory: ./app_go

steps:
- uses: actions/checkout@v4
- name: Set up Go ${{ matrix.go-version }}
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
#cache-dependency-path: app_go/go.sum
- name: Build
run: go build
- name: Lint with `go vet`
run: go vet
- name: Test with `go test`
run: |
go test

- name: Check for vulnerabilities with Snyk
uses: snyk/actions/golang@master
with:
args: app_go/
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}


docker-build-push:
needs: [ lint-test-snyk ]

runs-on: ubuntu-latest

steps:
- name: Cache Docker images
uses: ScribeMD/docker-cache@0.3.7
with:
key: docker-app_go
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: kolay0ne
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push docker image
uses: docker/build-push-action@v5
with:
push: true
context: "{{defaultContext}}:app_go/"
tags: kolay0ne/app_go:${{ github.ref_name }}
78 changes: 78 additions & 0 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python

name: Python application

on:
push:
paths:
- .github/workflows/**
- app_python/**

permissions:
contents: read

jobs:
lint-test-snyk:

runs-on: ubuntu-latest

strategy:
matrix:
python-version: ["3.10", "3.11", "3.12"]

defaults:
run:
working-directory: ./app_python

steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache: pip
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install flake8 pytest
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Test with pytest
run: |
pytest

- name: Check for vulnerabilities with Snyk (python 3.10)
uses: snyk/actions/python-3.10@master
with:
args: --skip-unresolved app_python/
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}


docker-build-push:
needs: [ lint-test-snyk ]

runs-on: ubuntu-latest

steps:
- name: Cache Docker images
uses: ScribeMD/docker-cache@0.3.7
with:
key: docker-app_py
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: kolay0ne
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push docker image
uses: docker/build-push-action@v5
with:
push: true
context: "{{defaultContext}}:app_python/"
tags: kolay0ne/app_py:${{ github.ref_name }}
1 change: 1 addition & 0 deletions app_go/.dockerignore
27 changes: 27 additions & 0 deletions app_go/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Created by https://www.toptal.com/developers/gitignore/api/go
# Edit at https://www.toptal.com/developers/gitignore?templates=go

### Go ###
# If you prefer the allow list template instead of the deny list, see community template:
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
#
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/

# Go workspace file
go.work

# End of https://www.toptal.com/developers/gitignore/api/go
14 changes: 14 additions & 0 deletions app_go/CI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# CI for `app_python`

## Best practices

- Runs on push only if files in related directories have changed.

- Uses matrix strategy: runs tests with different python versions,
can be extended further, e.g., to use different operating systems.

- In the lint&test job, dependencies are cached and reused. Cache is
updated when newer versions of dependent libraries are released.

- In the docker build&push job, docker layers are cached and reused.
Cache is updated when any of the layers change.
30 changes: 30 additions & 0 deletions app_go/DOCKER.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Dockerfile for `app_go`

## Best practices employed

- Explicit base image tag version.

- An alpine base image is used, so as to reduce the resulting image
size.

- .dockerignore is used and is symlinked to .gitignore. What's not to
appear in the repo is also not to be included in the image.

- Run instructions in the dockerfile are in the exec form, not shell
form.

- A multi-stage build is used. The final stage consists of just two
layers and takes up about 8MB.

- Root owns the executable file, which is not to be modified.

- A non-root user executes the app.

- The port to be exposed is explicitly mentioned in the Dockerfile.

Not sure what else to say, but just look at it: it's almost perfect!

The only thing one can argue about is the use of a hardcoded UID in the
Dockerfile. But, firstly, there is no native way to add a user in a bare
image, secondly, there is nothing to exchange with between host and container,
so there's no reason to.
19 changes: 19 additions & 0 deletions app_go/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
FROM golang:1.22.0-alpine3.19 as builder

WORKDIR /usr/src/app/

COPY go.mod *.go /usr/src/app/
# No dependencies yet
#RUN go mod download && go mod verify

RUN ["env", "CGO_ENABLED=0", "go", "build", "-o", "catfact_webapp", "."]


# Like `FROM scratch` but with SSL
FROM damdo/sscratch

EXPOSE 5000

COPY --from=builder /usr/src/app/catfact_webapp /
USER 2004:2004
ENTRYPOINT ["/catfact_webapp"]
40 changes: 40 additions & 0 deletions app_go/GO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Cat facts app

## Framework

The standard-library framework is used for the web app, as it
is capable enough for this simple app and does not require an
external installation.

## Best practices

- The code is logically distributed into files.

- The code is written in clean Go, according to the Go guidelines.

- To ensure project quality, I used the built-in `go vet` static
analysis tool and manually run the application.

- Explicit error handling is applied everywhere except for transmission
errors (both in networking and when printing to console), which are
explicitly ignored for there is nothing to do in those cases.

## Tests

For the project there are unit tests that cover key functionalities
of the web application. There are:

- Unit tests: ensure that cat facts are queried without error and
do not repeat too often.

- Integration tests: test that when whatever http request is sent,
the server responses with a non-empty catfact (which is not an
error).

Tests are implemented using best practices:

- A cannonical project structure proposed by the Go authors.

- There are both tests for API and tests that interact with the app
in a way similar to user interface.

61 changes: 61 additions & 0 deletions app_go/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Cat fact web app

![CI badge](https://github.com/kolayne-IU-assignments/S24-core-course-labs/actions/workflows/python-app.yml/badge.svg)

A web application that shows random cat facts on its main page.

## One-shot run

In the project directory run `go run .`. That's it!

Note that it requires the go module support (on by default in modern versions
of go, on an old version enable manually with the `GO111MODULE=on` environment
variable).

## Compile and run

In the project directory run `go build .`. This will create an executable file
`catfact_webapp`. Run it to start the web app.

## Docker

To build, in the project directory do:

```bash
docker build . -t IMAGE_TAG_NAME
```

where `IMAGE_TAG_NAME` is the tag you want for the image.

### Pull

You can also run a pre-built version of the image. To pull it:

```bash
docker pull kolay0ne/app_go
```

Use `kolay0ne/app_go` as the image name for future docker operations.

### Run

Run a container based on the image, select options depending on your needs. For
instance, to run it in the background, exposing the port `5000` to a randomly
selected port on the host machine and automatically remove the container ones
it's stopped, do:
```bash
docker run --rm -d -p 5000 kolay0ne/app_go
```

Replace `kolay0ne/app_go` with your image/tag name if you built it manually.

## Unit Tests

To run unit tests, navigate to the project directory and run `go test`.

## CI

On every push to the repository that changes files under `app_go/`,
the code is linted and tested, and checked for vulnerabilities. On success,
an image is built and published in DockerHub under the name `kolay0ne/app_go`
with a tag matching the branch name.
29 changes: 29 additions & 0 deletions app_go/catfact.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package main

import (
"encoding/json"
"io/ioutil"
"net/http"
)

type fact struct {
Fact string `json:"fact"`
// `length` field is ignored
}

func catFact() (string, error) {
resp, err := http.Get("https://catfact.ninja/fact")
if err != nil {
return "", err
}

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}

var fact fact
err = json.Unmarshal(body, &fact)

return fact.Fact, err
}
Loading
Loading