Skip to content
This repository was archived by the owner on Feb 22, 2024. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 128 additions & 45 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: API Lint, Build, Test, Deploy
name: EventAPI CI

on:
push:
Expand All @@ -14,26 +14,27 @@ on:
workflow_dispatch:
inputs:
deploy:
description: "Deploy location"
description: "Which environment to deploy to"
required: true
default: "none"
type: choice
options:
- production
- staging
- prod
- test
- none

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
DEPLOY: ${{ (inputs.deploy != 'none' && inputs.deploy) || ((github.event_name == 'workflow_dispatch' && github.event.inputs.deploy == 'prod') || (github.event_name == 'push' && github.ref_type == 'branch' && github.ref_name == 'master') && 'prod') || ((github.event_name == 'workflow_dispatch' && github.event.inputs.deploy == 'test') || (github.event_name == 'push' && github.ref_type == 'branch' && github.ref_name == 'dev') || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'staged')) && 'test') || 'none' }}

jobs:
ci:
name: EventAPI Lint, Build, Test, Deploy
name: EventAPI Lint & Build
runs-on: aws-runner
env:
DEPLOY_PROD: ${{ (github.event_name == 'workflow_dispatch' && github.event.inputs.deploy == 'production') || (github.event_name == 'push' && github.ref_type == 'branch' && github.ref_name == 'master') }}
DEPLOY_STAGE: ${{ (github.event_name == 'workflow_dispatch' && github.event.inputs.deploy == 'staging') || (github.event_name == 'push' && github.ref_type == 'branch' && github.ref_name == 'dev') || (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'staged')) }}
GOLANGCI_LINT_CACHE: /home/runner/.cache/golangci-lint
concurrency:
group: ${{ github.workflow }}-ci-${{ github.ref }}
Expand Down Expand Up @@ -98,64 +99,146 @@ jobs:
- name: Build App
run: make build

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
if: ${{ env.DEPLOY_PROD == 'true' || env.DEPLOY_STAGE == 'true' }}
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}

- name: Login to Amazon ECR
id: login-ecr
if: ${{ env.DEPLOY_PROD == 'true' || env.DEPLOY_STAGE == 'true' }}
uses: aws-actions/amazon-ecr-login@v1

- name: Make build context
if: ${{ env.DEPLOY_PROD == 'true' || env.DEPLOY_STAGE == 'true' }}
if: env.DEPLOY != 'none'
run: |
docker context create builders

- name: Setup buildx
uses: docker/setup-buildx-action@v2
if: ${{ env.DEPLOY_PROD == 'true' || env.DEPLOY_STAGE == 'true' }}
if: env.DEPLOY != 'none'
with:
install: true
endpoint: builders

- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}

- name: Build docker image
uses: docker/build-push-action@v3
if: ${{ env.DEPLOY_PROD == 'true' || env.DEPLOY_STAGE == 'true' }}
if: env.DEPLOY != 'none'
with:
context: .
file: docker/partial.Dockerfile
# cache-from: |
# type=registry,ref=gha
# cache-to: |
# type=registry,ref=gha,mode=max
tags: |
${{ steps.login-ecr.outputs.registry }}/${{ (env.DEPLOY_PROD == 'true' && '7tv') || '7tv-stage' }}/eventapi:latest
${{ steps.login-ecr.outputs.registry }}/${{ (env.DEPLOY_PROD == 'true' && '7tv') || '7tv-stage' }}/eventapi:${{ github.sha }}
ghcr.io/seventv/eventapi:${{ env.DEPLOY }}-${{ github.sha }}
ghcr.io/seventv/eventapi:${{ env.DEPLOY }}-latest
push: true

- name: Update deployment template
uses: danielr1996/envsubst-action@1.1.0
if: ${{ env.DEPLOY_PROD == 'true' || env.DEPLOY_STAGE == 'true' }}
env:
IMAGE: ${{ steps.login-ecr.outputs.registry }}/${{ (env.DEPLOY_PROD == 'true' && '7tv') || '7tv-stage' }}/eventapi:${{ github.sha }}
with:
input: k8s/${{ (env.DEPLOY_PROD == 'true' && 'production') || 'staging' }}.template.yaml
output: k8s/deploy.yaml
validate:
name: EventAPI Deploy Validation
needs: ci
runs-on: ubuntu-latest
permissions:
pull-requests: write
defaults:
run:
working-directory: ./terraform

- name: Setup Kubectl
uses: azure/setup-kubectl@v3.0
steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Deploy to k8s
if: ${{ env.DEPLOY_PROD == 'true' || env.DEPLOY_STAGE == 'true' }}
- name: "Setup Terraform"
uses: hashicorp/setup-terraform@v1
with:
cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}

- name: "Terraform Init"
id: init
env:
KUBE_CONFIG_DATA: ${{ (env.DEPLOY_PROD == 'true' && secrets.KUBECONFIG) || secrets.KUBECONFIG_STAGE }}
TF_WORKSPACE: ${{ env.DEPLOY }}
run: terraform init
continue-on-error: true

- name: "Terraform Workspace"
run: terraform workspace select -or-create=true ${{ env.DEPLOY }}

- name: Terraform fmt
id: fmt
run: terraform fmt -check
continue-on-error: true

- name: Terraform Validate
id: validate
run: terraform validate -no-color

- name: Terraform Variables
run: |
mkdir -p ~/.kube
(echo $KUBE_CONFIG_DATA | base64 -d) >> ~/.kube/config
cat <<EOF > *.auto.tfvars
image_url="ghcr.io/seventv/eventapi:${{ env.DEPLOY }}-${{ github.sha }}"
image_pull_policy="IfNotPresent"

EOF

- name: "Terraform Plan"
id: plan
run: terraform plan -no-color

kubectl apply -f k8s/deploy.yaml
- uses: actions/github-script@v6
if: github.event_name == 'pull_request'
env:
PLAN: "terraform\n${{ steps.plan.outputs.stdout }}"
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
// 1. Retrieve existing bot comments for the PR
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
})
const botComment = comments.find(comment => {
return comment.user.type === 'Bot' && comment.body.includes('Terraform Format and Style')
})

// 2. Prepare format of the comment
const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
#### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
#### Terraform Validation 🤖\`${{ steps.validate.outcome }}\`
<details><summary>Validation Output</summary>

\`\`\`\n
${{ steps.validate.outputs.stdout }}
\`\`\`

</details>

#### Terraform Plan 📖\`${{ steps.plan.outcome }}\`

<details><summary>Show Plan</summary>

\`\`\`\n
${process.env.PLAN}
\`\`\`

</details>

*Actor: @${{ github.actor }}, Action: \`${{ github.event_name }}\`, Workflow: \`${{ github.workflow }}\`*`;

// 3. If we have a comment, update it, otherwise create a new one
if (botComment) {
github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: botComment.id,
body: output
})
} else {
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: output
})
}

- name: "Terraform Apply"
if: env.deploy != 'none'
id: apply
run: terraform apply -no-color -auto-approve
continue-on-error: true
16 changes: 15 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,18 @@ config.yaml

go.work
go.work.sum
shutdown
shutdown

# Terraform local state files
**/.terraform/*
*.tfstate
*.tfstate.*
*.tfplan
crash.log
*.tfvars
override.tf
override.tf.json
*_override.tf
*_override.tf.json
.terraformrc
terraform.rc
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,9 @@ work:

dev:
go run cmd/main.go

terraform:
terraform -chdir=./terraform init

deploy:
terraform -chdir=./terraform apply -auto-approve
11 changes: 7 additions & 4 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,16 +86,19 @@ func main() {

dones := []<-chan struct{}{}

var srv *app.Server

if gctx.Config().API.Enabled {
_, done := app.New(gctx)
var done <-chan struct{}

srv, done = app.New(gctx)
dones = append(dones, done)
}
if gctx.Config().PProf.Enabled {
done := pprof.New(gctx)
dones = append(dones, done)
dones = append(dones, pprof.New(gctx))
}
if gctx.Config().Health.Enabled {
dones = append(dones, health.New(gctx))
dones = append(dones, health.New(gctx, srv))
}
if gctx.Config().Monitoring.Enabled {
dones = append(dones, monitoring.New(gctx))
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ require (
github.com/hashicorp/go-multierror v1.1.1
github.com/json-iterator/go v1.1.12
github.com/prometheus/client_golang v1.14.0
github.com/seventv/api v0.0.0-20230328043606-720366e31af1
github.com/seventv/common v0.0.0-20230402095344-527b0414019e
github.com/seventv/api v0.0.0-20230728083849-9b49a7905591
github.com/seventv/common v0.0.0-20230528214454-1a842fd909aa
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.15.0
github.com/valyala/fasthttp v1.44.0
Expand Down
10 changes: 4 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -233,12 +233,10 @@ github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBO
github.com/savsgio/gotils v0.0.0-20220530130905-52f3993e8d6d/go.mod h1:Gy+0tqhJvgGlqnTF8CVGP0AaGRjwBtXs/a5PA0Y3+A4=
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee h1:8Iv5m6xEo1NR1AvpV+7XmhI4r39LGNzwUL4YpMuL5vk=
github.com/savsgio/gotils v0.0.0-20230208104028-c358bd845dee/go.mod h1:qwtSXrKuJh/zsFQ12yEE89xfCrGKK63Rr7ctU/uCo4g=
github.com/seventv/api v0.0.0-20230328043606-720366e31af1 h1:UaHUcEeALbiBXAHwv5lKPwImQShXTq4kX2Q0o2awGWI=
github.com/seventv/api v0.0.0-20230328043606-720366e31af1/go.mod h1:o1EMa6sXZH5SxgrVno0pTOaMU8G5Q2LLa2TASm7e5cs=
github.com/seventv/common v0.0.0-20230321183435-350a6acc3175 h1:D0Op+jkNGUYCptieoHuiVROVupx+IQERHbyceLtjepI=
github.com/seventv/common v0.0.0-20230321183435-350a6acc3175/go.mod h1:jHHFe3uNMyzb/ReDqMvaI/A1euvV/PW/G+2PhcWQqWw=
github.com/seventv/common v0.0.0-20230402095344-527b0414019e h1:8mChwg4qO8yi+XmSp7848Jmskz+7mPhSOExmgruiwSA=
github.com/seventv/common v0.0.0-20230402095344-527b0414019e/go.mod h1:jHHFe3uNMyzb/ReDqMvaI/A1euvV/PW/G+2PhcWQqWw=
github.com/seventv/api v0.0.0-20230728083849-9b49a7905591 h1:/uUa/gPQkGj7xFUA7VBNFuEUGifk3S3yZApwQp8Erf8=
github.com/seventv/api v0.0.0-20230728083849-9b49a7905591/go.mod h1:P9sY6WXnMOgGwTxNj4m9pWrFCyMfTyTa2JFw+HwG8Pg=
github.com/seventv/common v0.0.0-20230528214454-1a842fd909aa h1:kIYvE8Ii1VmbTNQzI22QMiufnQZeG3vWURrCHd4ZH2I=
github.com/seventv/common v0.0.0-20230528214454-1a842fd909aa/go.mod h1:jHHFe3uNMyzb/ReDqMvaI/A1euvV/PW/G+2PhcWQqWw=
github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
Expand Down
2 changes: 1 addition & 1 deletion internal/app/connection/connection.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ type Connection interface {
// Retrieve the hex-encoded ID of this session
SessionID() string
// Greet sends an Hello message to the client
Greet() error
Greet(gctx global.Context) error
// Listen for incoming and outgoing events
Read(gctx global.Context)
// SendHeartbeat lets the client know that the connection is healthy
Expand Down
6 changes: 5 additions & 1 deletion internal/app/connection/eventstream/eventstream.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,15 @@ func (es *EventStream) Buffer() client.EventBuffer {
return es.evbuf
}

func (es *EventStream) Greet() error {
func (es *EventStream) Greet(gctx global.Context) error {
msg := events.NewMessage(events.OpcodeHello, events.HelloPayload{
HeartbeatInterval: uint32(es.heartbeatInterval),
SessionID: hex.EncodeToString(es.sessionID),
SubscriptionLimit: es.subscriptionLimit,
Instance: events.HelloPayloadInstanceInfo{
Name: gctx.Config().Pod.Name,
Population: gctx.Inst().ConcurrencyValue,
},
})

return es.Write(msg.ToRaw())
Expand Down
6 changes: 5 additions & 1 deletion internal/app/connection/eventstream/eventstream_read.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@ func (es *EventStream) Read(gctx global.Context) {

heartbeat := time.NewTicker(time.Duration(es.heartbeatInterval) * time.Millisecond)

liveness := time.NewTicker(time.Second * 1)

defer func() {
heartbeat.Stop()
es.Destroy()
liveness.Stop()
}()

if err := es.Greet(); err != nil {
if err := es.Greet(gctx); err != nil {
return
}

Expand All @@ -49,6 +52,7 @@ func (es *EventStream) Read(gctx global.Context) {
if err := es.SendHeartbeat(); err != nil {
return
}
case <-liveness.C: // Connection liveness check
case s = <-es.evm.DispatchChannel():
if s == nil { // channel closed
return
Expand Down
2 changes: 1 addition & 1 deletion internal/app/connection/websocket/websocket.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func (w *WebSocket) SessionID() string {
return hex.EncodeToString(w.sessionID)
}

func (w *WebSocket) Greet() error {
func (w *WebSocket) Greet(gctx global.Context) error {
msg := events.NewMessage(events.OpcodeHello, events.HelloPayload{
HeartbeatInterval: uint32(w.heartbeatInterval),
SessionID: hex.EncodeToString(w.sessionID),
Expand Down
5 changes: 1 addition & 4 deletions internal/app/connection/websocket/websocket_read.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,6 @@ func (w *WebSocket) Read(gctx global.Context) {
defer func() {
deferred = true

// Ignore panics, they're caused by fasthttp
_ = recover()

buf := w.Buffer()
if buf != nil {
<-buf.Context().Done()
Expand Down Expand Up @@ -126,7 +123,7 @@ func (w *WebSocket) Read(gctx global.Context) {
}
}()

if err := w.Greet(); err != nil {
if err := w.Greet(gctx); err != nil {
return
}

Expand Down
Loading