Gitea, DroneCI and Portainer
Tags: git, devops, cicd, python
Published: 2022-09-30 21:05:57

# Intro
You are tasked to create a build environment and you want something relatively easy and something that is repeatable.

On my journey to accomplish the above task i came across many great solutions. There are all in one solutions like Gitlab and Github but i wanted a solution that was open-source, free and easy. 

## Gitea
[Gitea](gitea.io)
Gitea is a open-source version of something like Github that is easy to deploy and makes a great on-prem repository. An added advantage is it has out of the box integration with Drone that is configurable to trigger web-hooks on selected events.

## Drone
[Drone](drone.io)
Drone is a open-source CI tool that will take simple commands in the form of yaml and execute on them as a pipeline. They have a concept of runners that do all the work. You install the runners you need on Docker, or Kubernetes or even on a host and they will listen for jobs.

### Runners:

Docker
Kubernetes
Exec
SSH
VM

## Portainer
[Portainer](portainer.io)
Portainer is a container management platform that allows you to manage containers across clouds and allows you to manage multiple container platforms (Kubernetes, Docker, Docker Swarm, and Nomad)

# Solution:

I am using VSCode as my IDE in which i have created a little python webserver that will be encapsulated into a docker image. The Docker image will then be created, sent to a repo and then consumed by a Kubernetes deployment. The deployment will be triggered and deployed.

Here is what it will look like:

[VSCode] -> [Gitea] -> [Drone] -> [Portainer] -> [Deploy]


## Gitea Install:
Kubernetes:

```
helm install gitea gitea-charts/gitea -n gitea --values ./gitea_values.yml
```

_gitea-values.yml_ (Changes)
```
ingress:
  enabled: true
  # className: nginx
  className:
  annotations: {}
    # kubernetes.io/ingress.class: nginx
    # kubernetes.io/tls-acme: "true"
  hosts:
    - host: git.chenetz.net
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: git-chenetz-tls
      hosts:
        - git.chenetz.net
```

## Drone Install:
Kubernetes:

```
helm upgrade my-drone drone/drone -n drone --values ./drone-values.yaml
```

_drone-values.yaml_ (changes)
```
ingress:
  enabled: true
  annotations: {}
    # kubernetes.io/ingress.class: nginx
    # kubernetes.io/tls-acme: "true"
  hosts:
    - host: drone.chenetz.net
      paths:
        - path: /
          pathType: Prefix
  tls: 
   - secretName: chenetz-drone-tls
     hosts:
       - drone.chenetz.net
nv:
  DRONE_WEBHOOK_SKIP_VERIFY: true
  ## REQUIRED: Set the user-visible Drone hostname, sans protocol.
  ## Ref: https://docs.drone.io/installation/reference/drone-server-host/
  ##
  DRONE_SERVER_HOST: drone.chenetz.net
  ## The protocol to pair with the value in DRONE_SERVER_HOST (http or https).
  ## Ref: https://docs.drone.io/installation/reference/drone-server-proto/
  ##
  DRONE_SERVER_PROTO: https

  ## REQUIRED: Set the secret secret token that the Drone server and its Runners will use
  ## to authenticate. This is commented out in order to leave you the ability to set the
  ## key via a separately provisioned secret (see existingSecretName above).
  ## Ref: https://docs.drone.io/installation/reference/drone-rpc-secret/
  ##
  DRONE_RPC_SECRET: drone-runner

  ## If you'd like to use a DB other than SQLite (the default), set a driver + DSN here.
  ## Ref: https://docs.drone.io/installation/storage/database/
  ##
  # DRONE_DATABASE_DRIVER:
  # DRONE_DATABASE_DATASOURCE:

  ## If you are going to store build secrets in the Drone database, it is suggested that
  ## you set a database encryption secret. This must be set before any secrets are stored
  ## in the database.
  ## Ref: https://docs.drone.io/installation/storage/encryption/
  ##
  # DRONE_DATABASE_SECRET:

  ## If you are using self-hosted GitHub or GitLab, you'll need to set this to true.
  ## Ref: https://docs.drone.io/installation/reference/drone-git-always-auth/
  ##
  # DRONE_GIT_ALWAYS_AUTH: false

  ## ===================================================================================
  ##                         Provider Directives (select ONE)
  ## -----------------------------------------------------------------------------------
  ## Select one provider (and only one). Refer to the corresponding documentation link
  ## before filling the values in. Also note that you can use the 'secretMounts' value
  ## if you'd rather not have secrets in Kubernetes Secret instead of a ConfigMap.
  ## ===================================================================================

  ## GitHub-specific variables. See the provider docs here:
  ## Ref: https://docs.drone.io/installation/providers/github/
  ##
  # DRONE_GITHUB_CLIENT_ID:
  # DRONE_GITHUB_CLIENT_SECRET:

  ## GitLab-specific variables. See the provider docs here:
  ## Ref: https://docs.drone.io/installation/providers/gitlab/
  ##
  # DRONE_GITLAB_CLIENT_ID:
  # DRONE_GITLAB_CLIENT_SECRET:
  # DRONE_GITLAB_SERVER:

  ## Bitbucket Cloud-specific variables. See the provider docs here:
  ## Ref: https://docs.drone.io/installation/providers/bitbucket-cloud/
  ##
  # DRONE_BITBUCKET_CLIENT_ID:
  # DRONE_BITBUCKET_CLIENT_SECRET:

  ## Bitbucket-specific variables. See the provider docs here:
  ## Ref: https://docs.drone.io/installation/providers/bitbucket-server/
  ##
  # DRONE_GIT_USERNAME:
  # DRONE_GIT_PASSWORD:
  # DRONE_STASH_CONSUMER_KEY:
  # DRONE_STASH_PRIVATE_KEY:
  # DRONE_STASH_SERVER:

  ## Gitea-specific variables. See the provider docs here:
  ## Ref: https://docs.drone.io/installation/providers/gitea/
  ##
  DRONE_GITEA_CLIENT_ID: [client id]
  DRONE_GITEA_CLIENT_SECRET: [secret]
  DRONE_GITEA_SERVER: https://git.chenetz.net
```

## Drone Docker Runner Install:
Kubernetes:

```
helm install my-drone-runner-docker drone/drone-runner-docker -n drone --values ./drone-runner-docker.yml
```

_drone-runner-docker.yml_ (changes)
```
env:
  ## The docker host to be used by the drone-runner-docker when connecting
  ## to the docker daemon, defaults to the dind(sidecar) docker daemon
  DOCKER_HOST: "tcp://localhost:2375"
  ## REQUIRED: Set the secret secret token that the Docker runner will use
  ## to authenticate. This is commented out in order to leave you the ability to set the
  ## key via a separately provisioned secret (see extraSecretNamesForEnvFrom above).
  ## Ref: https://docs.drone.io/runner/docker/configuration/reference/drone-rpc-secret/
  ##
  DRONE_RPC_SECRET: drone-runner

  ## The hostname/IP (and optionally the port) for your Kubernetes runner. Defaults to the "drone"
  ## service that the drone server Chart creates by default.
  ## Ref: https://docs.drone.io/runner/docker/configuration/reference/drone-rpc-host/
  ##
  DRONE_RPC_HOST: drone.chenetz.net

  ## The protocol to use for communication with Drone server.
  ## Ref: https://docs.drone.io/runner/docker/configuration/reference/drone-rpc-proto/
  ##
  DRONE_RPC_PROTO: https
  DRONE_NAMESPACE_DEFAULT: drone
```


## Configuration:

### Gitea:

![Gitea](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ykqk0htkt721cu17ley.png)


![Gitea 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xkt0umhxv0wnxgbavw3r.png)





### Drone


After getting everything up and communicating, i added the .values.yml file to my repo. The following drone pipeline file creates the docker image after it gets a trigger from either a push or pull in Gitea. The second step triggers a webhook that is configured in Portainer to trigger a deployment of the manifest.yml in the repo.


```
kind: pipeline
name: default
type: docker 
steps:
- name: docker  
  image: plugins/docker
  settings:
    username:
      from_secret: docker-username
    password:
      from_secret: docker-password
    repo: mchenetz/test
    tags:
    - latest
- name: send
  image: plugins/webhook
  settings:
    urls: https://192.168.10.12:9443/api/stacks/webhooks/
    content_type: application/json
    template: |
      {
        "owner": "{{ repo.owner }}",
        "repo": "{{ repo.name }}",
        "status": "{{ build.status }}",
      }
    skip_verify: true
    debug: true
```

### Dockerfile

```
FROM python:latest
WORKDIR /src

COPY . .

CMD [ "python", "./test.py"]
```

### Source (test.py)
```
from http.server import BaseHTTPRequestHandler, HTTPServer
import time

hostName = "localhost"
serverPort = 80

class MyServer(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-type", "text/html")
        self.end_headers()
        self.wfile.write(bytes("<html><head><title>https://portainer.io</title></head>", "utf-8"))
        self.wfile.write(bytes("<p>Request: %s</p>" % self.path, "utf-8"))
        self.wfile.write(bytes("<body>", "utf-8"))
        self.wfile.write(bytes("<p>This is an example web server on k8s.</p>", "utf-8"))
        self.wfile.write(bytes("</body></html>", "utf-8"))

if __name__ == "__main__":        
    webServer = HTTPServer((hostName, serverPort), MyServer)
    print("Server started http://%s:%s" % (hostName, serverPort))

    try:
        webServer.serve_forever()
    except KeyboardInterrupt:
        pass

    webServer.server_close()
    print("Server stopped.")
```

Example of automated trigger on push:


![Drone](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v0kxq6tdicpr95ph3m42.png)


Docker Repo:


![Docker](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9wti5r5hx2xk6mziadje.png)

Portainer Trigger:


![Portainer Trigger](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qj1v81kjp20elmbw7yd8.png)


## Conclusion:
At the end of the day... It may sound like a few steps to setup but i now have a complete environment for my coding pipeline and any changes i make will automatically build the image and deploy. That may not always be what you want and the pipeline can be modified to insert stops for approval and to change to pull which is more common.