diff --git a/.gitignore b/.gitignore index 5e94906..b0d4637 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ node_modules *.log storybook-static .next +worker +wrangler.toml diff --git a/package.json b/package.json index 822e099..adac511 100644 --- a/package.json +++ b/package.json @@ -34,10 +34,12 @@ "react-icons": "^4.1.0", "react-is": "^17.0.1", "react-tooltip": "^4.2.11", - "styled-components": "^5.2.1" + "styled-components": "^5.2.1", + "uuid": "^8.3.1" }, "devDependencies": { "@babel/core": "^7.12.9", + "@cloudflare/workers-types": "^2.1.0", "@storybook/addon-actions": "^6.1.9", "@storybook/addon-essentials": "^6.1.9", "@storybook/addon-links": "^6.1.9", @@ -54,6 +56,7 @@ "@types/react-custom-scrollbars": "^4.0.7", "@types/react-dom": "^17.0.0", "@types/styled-components": "^5.1.4", + "@types/uuid": "^8.3.0", "@typescript-eslint/eslint-plugin": "^4.9.0", "@typescript-eslint/eslint-plugin-tslint": "^4.9.0", "@typescript-eslint/parser": "^4.9.0", @@ -86,7 +89,9 @@ "stylelint-prettier": "^1.1.2", "stylelint-processor-styled-components": "^1.10.0", "ts-jest": "^26.4.4", + "ts-loader": "^8.0.11", "tsconfig-paths-webpack-plugin": "^3.3.0", - "typescript": "^4.1.2" + "typescript": "^4.1.2", + "webpack": "^5.9.0" } -} \ No newline at end of file +} diff --git a/src/backend-worker/.gitignore b/src/backend-worker/.gitignore new file mode 100644 index 0000000..1521c8b --- /dev/null +++ b/src/backend-worker/.gitignore @@ -0,0 +1 @@ +dist diff --git a/src/backend-worker/bindings.d.ts b/src/backend-worker/bindings.d.ts new file mode 100644 index 0000000..316ab40 --- /dev/null +++ b/src/backend-worker/bindings.d.ts @@ -0,0 +1,10 @@ +export {}; + +declare global { + const BOT_CLIENT_ID: string; + const BOT_CLIENT_SECRET: string; + const UI_PUBLIC_URI: string; + const API_PUBLIC_URI: string; + const ROOT_USERS: string; + const KV_PREFIX: string; +} diff --git a/src/backend-worker/handlers/bot-join.ts b/src/backend-worker/handlers/bot-join.ts new file mode 100644 index 0000000..a9b31f4 --- /dev/null +++ b/src/backend-worker/handlers/bot-join.ts @@ -0,0 +1,35 @@ +import { Bounce } from '../utils/bounce'; + +const validGuildID = /^[0-9]+$/; + +type URLParams = { + clientID: string; + permissions: number; + guildID?: string; +}; + +const buildURL = (params: URLParams) => { + let url = `https://discord.com/api/oauth2/authorize?client_id=${params.clientID}&scope=bot&permissions=${params.permissions}`; + + if (params.guildID) { + url += `&guild_id=${params.guildID}&disable_guild_select=true`; + } + + return url; +}; + +export const BotJoin = (request: Request): Response => { + let guildID = new URL(request.url).searchParams.get('guild') || ''; + + if (guildID && !validGuildID.test(guildID)) { + guildID = ''; + } + + return Bounce( + buildURL({ + clientID: BOT_CLIENT_ID, + permissions: 268435456, + guildID, + }) + ); +}; diff --git a/src/backend-worker/handlers/login-bounce.ts b/src/backend-worker/handlers/login-bounce.ts new file mode 100644 index 0000000..33d6f28 --- /dev/null +++ b/src/backend-worker/handlers/login-bounce.ts @@ -0,0 +1,23 @@ +import { v4 as uuidv4 } from 'uuid'; +import { Bounce } from '../utils/bounce'; + +type URLParams = { + clientID: string; + redirectURI: string; + state: string; +}; + +const buildURL = (params: URLParams) => + `https://discord.com/api/oauth2/authorize?client_id=${ + params.clientID + }&response_type=code&scope=identify%20guilds&redirect_uri=${encodeURIComponent( + params.redirectURI + )}&state=${params.state}`; + +export const LoginBounce = (request: Request): Response => { + const state = uuidv4(); + const redirectURI = `${API_PUBLIC_URI}/login-callback`; + const clientID = BOT_CLIENT_ID; + + return Bounce(buildURL({ state, redirectURI, clientID })); +}; diff --git a/src/backend-worker/handlers/login-callback.ts b/src/backend-worker/handlers/login-callback.ts new file mode 100644 index 0000000..34fb4cf --- /dev/null +++ b/src/backend-worker/handlers/login-callback.ts @@ -0,0 +1,3 @@ +export const LoginCallback = (request: Request): Response => { + return new Response('login-callback!'); +}; diff --git a/src/backend-worker/index.ts b/src/backend-worker/index.ts new file mode 100644 index 0000000..83efd4d --- /dev/null +++ b/src/backend-worker/index.ts @@ -0,0 +1,14 @@ +import { BotJoin } from './handlers/bot-join'; +import { LoginBounce } from './handlers/login-bounce'; +import { LoginCallback } from './handlers/login-callback'; +import { Router } from './router'; + +const router = new Router(); + +router.add('GET', 'bot-join', BotJoin); +router.add('GET', 'login-bounce', LoginBounce); +router.add('GET', 'login-callback', LoginCallback); + +addEventListener('fetch', (event: FetchEvent) => { + event.respondWith(router.handle(event.request)); +}); diff --git a/src/backend-worker/router.ts b/src/backend-worker/router.ts new file mode 100644 index 0000000..4bfc549 --- /dev/null +++ b/src/backend-worker/router.ts @@ -0,0 +1,73 @@ +export type Handler = (request: Request) => Promise | Response; + +type RoutingTree = { + [method: string]: { + [path: string]: Handler; + }; +}; + +type Fallbacks = { + root: Handler; + 404: Handler; + 500: Handler; +}; + +export class Router { + private routingTree: RoutingTree = {}; + private fallbacks: Fallbacks = { + root: this.respondToRoot, + 404: this.notFound, + 500: this.serverError, + }; + + addFallback(which: keyof Fallbacks, handler: Handler) { + this.fallbacks[which] = handler; + } + + add(method: string, rootPath: string, handler: Handler) { + const lowerMethod = method.toLowerCase(); + + if (!this.routingTree[lowerMethod]) { + this.routingTree[lowerMethod] = {}; + } + + this.routingTree[lowerMethod][rootPath] = handler; + } + + handle(request: Request): Promise | Response { + if (request.url === '/') { + return this.fallbacks.root(request); + } + const lowerMethod = request.method.toLowerCase(); + const url = new URL(request.url); + const rootPath = url.pathname.split('/')[1]; + const handler = this.routingTree[lowerMethod]?.[rootPath]; + + if (handler) { + try { + return handler(request); + } catch (e) { + console.error(e); + return this.fallbacks[500](request); + } + } + + return this.fallbacks[404](request); + } + + private respondToRoot(): Response { + return new Response('Hi there!'); + } + + private notFound(): Response { + return new Response(JSON.stringify({ error: 'not_found' }), { + status: 404, + }); + } + + private serverError(): Response { + return new Response(JSON.stringify({ error: 'internal_server_error' }), { + status: 500, + }); + } +} diff --git a/src/backend-worker/tsconfig.json b/src/backend-worker/tsconfig.json new file mode 100644 index 0000000..77059f5 --- /dev/null +++ b/src/backend-worker/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "outDir": "./dist", + "lib": ["esnext", "webworker"], + "types": ["@cloudflare/workers-types"] + }, + "include": [ + "./*.ts", + "./**/*.ts", + "../../node_modules/@cloudflare/workers-types/index.d.ts" + ], + "exclude": ["./**/*.spec.ts"], + "extends": "../../tsconfig.json" +} diff --git a/src/backend-worker/utils/bounce.ts b/src/backend-worker/utils/bounce.ts new file mode 100644 index 0000000..3aec260 --- /dev/null +++ b/src/backend-worker/utils/bounce.ts @@ -0,0 +1,7 @@ +export const Bounce = (url: string): Response => + new Response(null, { + status: 303, + headers: { + location: url, + }, + }); diff --git a/src/backend-worker/webpack.config.js b/src/backend-worker/webpack.config.js new file mode 100644 index 0000000..f04820f --- /dev/null +++ b/src/backend-worker/webpack.config.js @@ -0,0 +1,29 @@ +const path = require('path'); + +const mode = process.env.NODE_ENV || 'production'; + +module.exports = { + target: 'webworker', + entry: path.join(__dirname, 'index.ts'), + output: { + filename: `worker.${mode}.js`, + path: path.join(__dirname, 'dist'), + }, + mode, + resolve: { + extensions: ['.ts', '.tsx', '.js'], + plugins: [], + }, + module: { + rules: [ + { + test: /\.tsx?$/, + loader: 'ts-loader', + options: { + transpileOnly: true, + configFile: path.join(__dirname, 'tsconfig.json'), + }, + }, + ], + }, +}; diff --git a/src/functions/hello-world/helloworld.ts b/src/functions/hello-world/helloworld.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/redis-breaker/redisbreaker.go b/src/redis-breaker/redisbreaker.go deleted file mode 100644 index 63e63a2..0000000 --- a/src/redis-breaker/redisbreaker.go +++ /dev/null @@ -1,156 +0,0 @@ -// Package redisbreaker provides a go-redis v8 instance designed for resilient caching via circuit breakers. -// tl;dr: If redis is lost, it can either cache objects in memory using sync.Map or dropping gracefully. -// As a side benefit, it means we don't need a redis server to develop locally, unless we want one :) -package redisbreaker - -import ( - "context" - "encoding/json" - "sync" - "time" - - "github.com/go-redis/redis/v8" - "github.com/sony/gobreaker" -) - -type RedisBreaker struct { - redisClient *redis.Client - breaker *gobreaker.CircuitBreaker - - config *RedisBreakerConfig - inMemoryCache sync.Map -} - -type RedisBreakerConfig struct { - Redis *redis.Options - Breaker gobreaker.Settings - UseInMemoryCache bool - DefaultTTL time.Duration -} - -type inmemoryCacheObject struct { - expiresAt time.Time - object []byte -} - -func NewRedisBreaker(config *RedisBreakerConfig) *RedisBreaker { - if config == nil { - config = &RedisBreakerConfig{ - UseInMemoryCache: true, - } - } - - if config.DefaultTTL == 0 { - config.DefaultTTL = 2 * time.Minute - } - - breaker := &RedisBreaker{ - config: config, - redisClient: redis.NewClient(config.Redis), - breaker: gobreaker.NewCircuitBreaker(config.Breaker), - } - - return breaker -} - -func (rb *RedisBreaker) doOr( - ctx context.Context, - func1 func(context.Context, string, interface{}) (interface{}, error), - func2 func(context.Context, string, interface{}) (interface{}, error), - key string, - object interface{}, -) (interface{}, error) { - val, err := rb.breaker.Execute(func() (interface{}, error) { - return func1(ctx, key, object) - }) - if err == gobreaker.ErrOpenState || err == gobreaker.ErrTooManyRequests { - return func2(ctx, key, object) - } - - return val, err -} - -// Set pushes an object into the cache with the specified default TTL, using SetEX -func (rb *RedisBreaker) Set(ctx context.Context, key string, object interface{}) error { - _, err := rb.doOr(ctx, rb.setRedis, rb.setInmemory, key, object) - return err -} - -func (rb *RedisBreaker) setRedis(ctx context.Context, key string, object interface{}) (interface{}, error) { - objectJSON, err := json.Marshal(object) - if err != nil { - return nil, err - } - - return rb.redisClient.SetEX(ctx, key, objectJSON, rb.config.DefaultTTL).Result() -} - -func (rb *RedisBreaker) setInmemory(ctx context.Context, key string, object interface{}) (interface{}, error) { - if rb.config.UseInMemoryCache { - objectJSON, err := json.Marshal(object) - if err != nil { - return nil, err - } - - rb.inMemoryCache.Store(key, inmemoryCacheObject{ - expiresAt: time.Now().Add(rb.config.DefaultTTL), - object: objectJSON, - }) - } - - return nil, nil -} - -// Get pulls an object from cache, returning ok = true if it succeeded. -func (rb *RedisBreaker) Get(ctx context.Context, key string, object interface{}) (bool, error) { - ok, err := rb.doOr(ctx, rb.getRedis, rb.getInmemory, key, object) - - return ok.(bool), err -} - -func (rb *RedisBreaker) getRedis(ctx context.Context, key string, object interface{}) (interface{}, error) { - result := rb.redisClient.Get(ctx, key) - if result.Err() != nil { - if result.Err() == redis.Nil { - return false, nil - } - - return false, result.Err() - } - - objectJSON, err := result.Bytes() - if err != nil { - return false, err - } - - err = json.Unmarshal(objectJSON, object) - - return true, err -} - -func (rb *RedisBreaker) getInmemory(ctx context.Context, key string, object interface{}) (interface{}, error) { - if !rb.config.UseInMemoryCache { - return false, nil - } - - cacheObjIntf, ok := rb.inMemoryCache.Load(key) - if !ok { - return false, nil - } - - cacheObj, ok := cacheObjIntf.(inmemoryCacheObject) - if !ok { - return false, nil - } - - if time.Now().After(cacheObj.expiresAt) { - return false, nil - } - - err := json.Unmarshal(cacheObj.object, object) - if err != nil { - return false, err - } - - return true, nil -} diff --git a/src/redis-breaker/redisbreaker_test.go b/src/redis-breaker/redisbreaker_test.go deleted file mode 100644 index da17065..0000000 --- a/src/redis-breaker/redisbreaker_test.go +++ /dev/null @@ -1,130 +0,0 @@ -package redisbreaker_test - -import ( - "context" - "testing" - "time" - - "github.com/alicebob/miniredis/v2" - "github.com/go-redis/redis/v8" - "github.com/onsi/gomega" - "github.com/sony/gobreaker" - - redisbreaker "github.com/roleypoly/roleypoly/src/redis-breaker" -) - -func getBreaker(breakerOpen bool) (*redisbreaker.RedisBreaker, *miniredis.Miniredis) { - redisServer, err := miniredis.Run() - if err != nil { - panic(err) - } - - config := &redisbreaker.RedisBreakerConfig{ - Redis: &redis.Options{ - Addr: redisServer.Addr(), - }, - UseInMemoryCache: true, - DefaultTTL: 1 * time.Second, - } - - if breakerOpen { - config.Breaker.ReadyToTrip = func(gobreaker.Counts) bool { - return true - } - - redisServer.Close() - } - - rb := redisbreaker.NewRedisBreaker(config) - - if breakerOpen { - // forcibly open the breaker - rb.Set(context.Background(), "@@@breaker@@@", nil) - } - - return rb, redisServer -} - -type TestData struct { - IAmAField1 string - IAmAField2 int - IAmAField3 map[string]interface{} -} - -var testData = TestData{ - IAmAField1: "hello world!", - IAmAField2: 420 * 69, - IAmAField3: map[string]interface{}{ - "foxes": "are so heckin cute", - }, -} - -func getSet(t *testing.T, openCircuit bool) { - g := gomega.NewGomegaWithT(t) - rb, rds := getBreaker(openCircuit) - defer rds.Close() - - err := rb.Set(context.Background(), "test-data", testData) - g.Expect(err).To(gomega.BeNil()) - - output := TestData{} - ok, err := rb.Get(context.Background(), "test-data", &output) - g.Expect(err).To(gomega.BeNil()) - - g.Expect(ok).To(gomega.BeTrue(), "ok should be true") - g.Expect(output).To(gomega.Equal(testData), "testData should match output data") -} - -func TestGetSet(t *testing.T) { - getSet(t, false) -} - -func TestGetSetOpenCircuit(t *testing.T) { - getSet(t, true) -} - -func getNotInCache(t *testing.T, openCircuit bool) { - g := gomega.NewGomegaWithT(t) - rb, rds := getBreaker(openCircuit) - defer rds.Close() - - output := TestData{} - ok, err := rb.Get(context.Background(), "not-test-data", &output) - g.Expect(err).To(gomega.BeNil()) - - g.Expect(ok).To(gomega.BeFalse(), "ok should be false") - g.Expect(output).To(gomega.BeZero(), "output should be 'zero'") -} - -func TestGetNotInCache(t *testing.T) { - getNotInCache(t, false) -} - -func TestGetNotInCacheOpenCircuit(t *testing.T) { - getNotInCache(t, true) -} - -func getAfterTTL(t *testing.T, openCircuit bool) { - g := gomega.NewGomegaWithT(t) - rb, rds := getBreaker(openCircuit) - defer rds.Close() - - err := rb.Set(context.Background(), "test-expired", testData) - g.Expect(err).To(gomega.BeNil()) - - rds.FastForward(1 * time.Second) - time.Sleep(1 * time.Second) - - output := TestData{} - ok, err := rb.Get(context.Background(), "test-expired", &output) - g.Expect(ok).To(gomega.BeFalse(), "ok should be false") - g.Expect(output).To(gomega.BeZero(), "output should be 'zero'") -} - -func TestGetAfterTTL(t *testing.T) { - getAfterTTL(t, false) -} - -func TestGetAfterTTLOpenCircuit(t *testing.T) { - getAfterTTL(t, true) -} diff --git a/terraform/app/discord.tf b/terraform/app/discord.tf deleted file mode 100644 index d388fd4..0000000 --- a/terraform/app/discord.tf +++ /dev/null @@ -1,56 +0,0 @@ -locals { - discord_labels = { - "app.kubernetes.io/name" = "discord" - "app.kubernetes.io/part-of" = "roleypoly" - "roleypoly/environment" = var.environment_tag - } -} - -resource "kubernetes_deployment" "discord" { - metadata { - name = "discord" - namespace = local.ns - labels = local.discord_labels - } - - spec { - replicas = 1 - - selector { - match_labels = local.discord_labels - } - - template { - metadata { - labels = local.discord_labels - } - - spec { - container { - image = "roleypoly/discord:${local.tags.discord}" - name = "discord" - - liveness_probe { - http_get { - path = "/" - port = 16777 - } - - initial_delay_seconds = 3 - period_seconds = 3 - } - - readiness_probe { - http_get { - path = "/" - port = 16777 - } - - initial_delay_seconds = 3 - period_seconds = 3 - } - } - } - } - } -} diff --git a/terraform/app/provision.tf b/terraform/app/provision.tf deleted file mode 100644 index 4098140..0000000 --- a/terraform/app/provision.tf +++ /dev/null @@ -1,26 +0,0 @@ -terraform { - required_version = ">=0.12.6" - - backend "remote" { - organization = "Roleypoly" - - workspaces { - prefix = "roleypoly-app-" - } - } -} - -variable "k8s_endpoint" { type = string } -variable "k8s_token" { type = string } -variable "k8s_cert" { type = string } -variable "k8s_namespace" { type = string } -provider "kubernetes" { - load_config_file = false - token = var.k8s_token - host = var.k8s_endpoint - cluster_ca_certificate = var.k8s_cert -} - -locals { - ns = var.k8s_namespace -} diff --git a/terraform/app/tags.auto.tfvars.json b/terraform/app/tags.auto.tfvars.json deleted file mode 100644 index 9fd6f3e..0000000 --- a/terraform/app/tags.auto.tfvars.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "deployment_env": { - "production": {}, - "staging": {} - } -} diff --git a/terraform/app/variables.tf b/terraform/app/variables.tf deleted file mode 100644 index fa4dc46..0000000 --- a/terraform/app/variables.tf +++ /dev/null @@ -1,12 +0,0 @@ -variable "deployment_env" { - type = map(map(string)) -} - -variable "environment_tag" { - type = string - description = "One of: production, staging, test" -} - -locals { - tags = var.deployment_env[var.environment_tag] -} diff --git a/terraform/functions/.terraform.lock.hcl b/terraform/functions/.terraform.lock.hcl deleted file mode 100755 index 27e67f3..0000000 --- a/terraform/functions/.terraform.lock.hcl +++ /dev/null @@ -1,20 +0,0 @@ -# This file is maintained automatically by "terraform init". -# Manual edits may be lost in future updates. - -provider "registry.terraform.io/hashicorp/google" { - version = "3.49.0" - constraints = ">= 3.49.0" - hashes = [ - "h1:MgihBNqO052m2jthWBu00wWYkz+eNrETwLqBfWmvMMY=", - "zh:00ea68b3a3b6e11ea469f47ee949c7f8f5751f935a3366152f9d3c6660c27e9b", - "zh:1ef3efc2e81fa31ceb04e39ae25acd0f061629f104827e127bdb4345e95f37d0", - "zh:6bf00943baa776adef0bbc914886359cf95c505b0494f3936cedac5cd1e01a00", - "zh:7d2cce5a9be476d8eee67435d854d094f82b5814a0e34964d10f28c1e88a2c8f", - "zh:841d074e3fb06f0df7c930bc0c4a9733ce0c5f1a19d6af98632a7931d2ca6a59", - "zh:8920ccd27c8904fcf5d701d71baee4f64d9d6f1383e66c4673909d9c53895057", - "zh:91d4479d2d461ad582d127d47aa7094bd74a1278cc8d78ad36a1c4f31301f4f0", - "zh:a97c19cdb42b5f7e4e297183d60eaa45843ee7b0adde1120e47026c4cae456c1", - "zh:cbd862cc4d21866bb832e3e7fe4e6ed959f5e5363bcf3d74e476b42fec716efe", - "zh:ec3c63ba6db74b353fafff6aedbb30e3eb1a4e5c856b4920c7ffa10d7081cbbd", - ] -} diff --git a/terraform/functions/cloud-function.tf b/terraform/functions/cloud-function.tf deleted file mode 100644 index bdc244c..0000000 --- a/terraform/functions/cloud-function.tf +++ /dev/null @@ -1,23 +0,0 @@ - -resource "google_cloudfunctions_function" "function" { - name = "roleypoly-test-hello-world" - description = "Roleypoly FaaS: /hello-world" - runtime = "go113" - - available_memory_mb = 128 - source_repository { - url = "https://source.cloud.google.com/projects/roleypoly/repos/github_roleypoly_roleypoly/moveable-alias/gcf/paths/src/functions/hello-world" - } - trigger_http = true - entry_point = "helloGET" -} - -# IAM entry for all users to invoke the function -resource "google_cloudfunctions_function_iam_member" "invoker" { - project = google_cloudfunctions_function.function.project - region = google_cloudfunctions_function.function.region - cloud_function = google_cloudfunctions_function.function.name - - role = "roles/cloudfunctions.invoker" - member = "allUsers" -} diff --git a/terraform/functions/provision.tf b/terraform/functions/provision.tf deleted file mode 100644 index c9263a2..0000000 --- a/terraform/functions/provision.tf +++ /dev/null @@ -1,26 +0,0 @@ -terraform { - required_version = ">=0.14" - required_providers { - google = { - source = "hashicorp/google" - version = ">=3.49.0" - } - } -} - -/* - Google Cloud -*/ -# variable "gcs_token" { type = string } -# variable "gcs_region" { type = string } -# variable "gcs_project" { type = string } -provider "google" { - # project = var.gcs_project - # region = var.gcs_region - # credentials = var.gcs_token - - scopes = [ - "https://www.googleapis.com/auth/devstorage.full_control", - "https://www.googleapis.com/auth/cloud-platform", - ] -} diff --git a/terraform/modules/cloudflare-cluster-dns/main.tf b/terraform/modules/cloudflare-cluster-dns/main.tf deleted file mode 100644 index 89ddb10..0000000 --- a/terraform/modules/cloudflare-cluster-dns/main.tf +++ /dev/null @@ -1,66 +0,0 @@ -# Primary cluster hostname -resource "cloudflare_record" "cluster" { - zone_id = var.cloudflare-zone-id - name = var.record-name - value = var.ingress-endpoint - type = "A" - proxied = true -} - -# PRD & STG records for direct FQDN usage -# Long term, these will also be CNAME'd to -# - prd == roleypoly.com -# - stg == beta.roleypoly.com -resource "cloudflare_record" "prd" { - zone_id = var.cloudflare-zone-id - name = "prd.${var.record-name}" - value = cloudflare_record.cluster.hostname - type = "CNAME" - proxied = true -} - -resource "cloudflare_record" "stg" { - zone_id = var.cloudflare-zone-id - name = "stg.${var.record-name}" - value = cloudflare_record.cluster.hostname - type = "CNAME" - proxied = true -} - -# Origin CA Cert -resource "tls_private_key" "origin-ca-key" { - algorithm = "ECDSA" -} - -resource "tls_cert_request" "origin-ca-csr" { - key_algorithm = tls_private_key.origin-ca-key.algorithm - private_key_pem = tls_private_key.origin-ca-key.private_key_pem - - subject { - common_name = "roleypoly.com" - organization = "Roleypoly" - } -} - -resource "cloudflare_origin_ca_certificate" "origin-ca-cert" { - csr = tls_cert_request.origin-ca-csr.cert_request_pem - hostnames = [ - cloudflare_record.cluster.hostname, - cloudflare_record.prd.hostname, - cloudflare_record.stg.hostname - ] - request_type = "origin-ecc" - requested_validity = 1095 # 3 years -} - -resource "kubernetes_secret" "cloudflare-origin" { - type = "kubernetes.io/tls" - metadata { - name = "cloudflare-origin" - namespace = "default" - } - data = { - "tls.crt" = base64encode(cloudflare_origin_ca_certificate.origin-ca-cert.certificate), - "tls.key" = base64encode(tls_private_key.origin-ca-key.private_key_pem) - } -} diff --git a/terraform/modules/cloudflare-cluster-dns/variables.tf b/terraform/modules/cloudflare-cluster-dns/variables.tf deleted file mode 100644 index 827f348..0000000 --- a/terraform/modules/cloudflare-cluster-dns/variables.tf +++ /dev/null @@ -1,19 +0,0 @@ -variable "ingress-name" { - type = string -} - -variable "ingress-namespace" { - type = string -} - -variable "ingress-endpoint" { - type = string -} - -variable "cloudflare-zone-id" { - type = string -} - -variable "record-name" { - type = string -} diff --git a/terraform/modules/cloudflare-cluster-dns/version.tf b/terraform/modules/cloudflare-cluster-dns/version.tf deleted file mode 100644 index 69d8c70..0000000 --- a/terraform/modules/cloudflare-cluster-dns/version.tf +++ /dev/null @@ -1,4 +0,0 @@ -terraform { - required_version = ">=0.12" -} - diff --git a/terraform/modules/cluster-environment/main.tf b/terraform/modules/cluster-environment/main.tf deleted file mode 100644 index 31d39b1..0000000 --- a/terraform/modules/cluster-environment/main.tf +++ /dev/null @@ -1,56 +0,0 @@ -locals { - ns = "${var.app_name}-${var.environment_tag}" - labels = { - "app.kubernetes.io/name" = var.app_name - "app.kubernetes.io/part-of" = var.app_name - "roleypoly/environment" = var.environment_tag - } -} - -resource "kubernetes_namespace" "ns" { - metadata { - name = local.ns - labels = local.labels - } -} - -resource "kubernetes_service_account" "sa" { - metadata { - name = "${local.ns}-sa-tf" - namespace = local.ns - labels = local.labels - } -} - -resource "kubernetes_secret" "sa-key" { - metadata { - name = "${local.ns}-sa-tf-key" - namespace = local.ns - labels = local.labels - annotations = { - "kubernetes.io/service-account.name" = kubernetes_service_account.sa.metadata.0.name - } - } - - type = "kubernetes.io/service-account-token" -} - -resource "kubernetes_role_binding" "sa-admin-rb" { - metadata { - name = "${local.ns}-sa-admin-binding" - namespace = local.ns - labels = local.labels - } - - subject { - kind = "ServiceAccount" - name = kubernetes_service_account.sa.metadata.0.name - namespace = local.ns - } - - role_ref { - kind = "ClusterRole" - name = "admin" - api_group = "rbac.authorization.k8s.io" - } -} diff --git a/terraform/modules/cluster-environment/output.tf b/terraform/modules/cluster-environment/output.tf deleted file mode 100644 index 08b4642..0000000 --- a/terraform/modules/cluster-environment/output.tf +++ /dev/null @@ -1,7 +0,0 @@ -output "service_account_token" { - value = lookup(kubernetes_secret.sa-key, "data.token", "") -} - -output "namespace" { - value = local.ns -} diff --git a/terraform/modules/cluster-environment/variables.tf b/terraform/modules/cluster-environment/variables.tf deleted file mode 100644 index 501799c..0000000 --- a/terraform/modules/cluster-environment/variables.tf +++ /dev/null @@ -1,9 +0,0 @@ -variable "environment_tag" { - type = string - default = "main" -} - -variable "app_name" { - type = string -} - diff --git a/terraform/modules/nginx-ingress-controller/main.tf b/terraform/modules/nginx-ingress-controller/main.tf deleted file mode 100644 index 49a9bb9..0000000 --- a/terraform/modules/nginx-ingress-controller/main.tf +++ /dev/null @@ -1,331 +0,0 @@ -locals { - ns = kubernetes_namespace.ns.metadata.0.name - labels = { - "app.kubernetes.io/name" = "ingress-nginx" - "app.kubernetes.io/part-of" = "ingress-nginx" - } -} - -resource "kubernetes_namespace" "ns" { - metadata { - name = "ingress-nginx" - labels = local.labels - } -} - -resource "kubernetes_config_map" "cm-nginx" { - metadata { - name = "nginx-configuration" - namespace = local.ns - labels = local.labels - } -} - -resource "kubernetes_config_map" "cm-tcp" { - metadata { - name = "tcp-services" - namespace = local.ns - labels = local.labels - } -} - -resource "kubernetes_config_map" "cm-udp" { - metadata { - name = "udp-services" - namespace = local.ns - labels = local.labels - } -} - -resource "kubernetes_service_account" "sa" { - metadata { - name = "nginx-ingress-serviceaccount" - namespace = local.ns - labels = local.labels - } -} - -resource "kubernetes_cluster_role" "cr" { - metadata { - name = "nginx-ingress-clusterrole" - labels = local.labels - } - rule { - api_groups = [""] - resources = ["configmaps", "endpoints", "nodes", "pods", "secrets"] - verbs = ["list", "watch"] - } - rule { - api_groups = [""] - resources = ["nodes"] - verbs = ["get"] - } - rule { - api_groups = [""] - resources = ["services"] - verbs = ["get", "list", "watch"] - } - rule { - api_groups = [""] - resources = ["events"] - verbs = ["create", "patch"] - } - rule { - api_groups = ["extensions", "networking.k8s.io"] - resources = ["ingresses"] - verbs = ["get", "list", "watch"] - } - rule { - api_groups = ["extensions", "networking.k8s.io"] - resources = ["ingresses/status"] - verbs = ["update"] - } -} - -resource "kubernetes_role" "role" { - metadata { - name = "nginx-ingress-role" - namespace = local.ns - labels = local.labels - } - - rule { - api_groups = [""] - resources = ["configmaps", "pods", "secrets", "namespaces"] - verbs = ["get"] - } - - rule { - api_groups = [""] - resources = ["configmaps"] - resource_names = ["ingress-controller-leader-nginx"] - verbs = ["get", "update"] - } - - rule { - api_groups = [""] - resources = ["configmaps"] - verbs = ["create"] - } - - rule { - api_groups = [""] - resources = ["endpoints"] - verbs = ["get"] - } -} - -resource "kubernetes_role_binding" "rb" { - metadata { - name = "nginx-ingress-role-nisa-binding" - namespace = local.ns - labels = local.labels - } - - role_ref { - api_group = "rbac.authorization.k8s.io" - kind = "Role" - name = kubernetes_role.role.metadata.0.name - } - - subject { - kind = "ServiceAccount" - name = kubernetes_service_account.sa.metadata.0.name - namespace = local.ns - } -} - -resource "kubernetes_cluster_role_binding" "crb" { - metadata { - name = "nginx-ingress-clusterrole-nisa-binding" - labels = local.labels - } - - role_ref { - api_group = "rbac.authorization.k8s.io" - kind = "ClusterRole" - name = kubernetes_cluster_role.cr.metadata.0.name - } - - subject { - kind = "ServiceAccount" - name = kubernetes_service_account.sa.metadata.0.name - namespace = local.ns - } -} - -resource "kubernetes_deployment" "deployment" { - metadata { - name = "nginx-ingress-controller" - namespace = local.ns - labels = local.labels - } - - spec { - replicas = 3 - - selector { - match_labels = local.labels - } - - template { - metadata { - labels = local.labels - annotations = { - "prometheus.io/port" = "10254" - "prometheus.io/scrape" = "true" - } - } - - spec { - automount_service_account_token = true - termination_grace_period_seconds = 300 - service_account_name = kubernetes_service_account.sa.metadata.0.name - node_selector = { - "kubernetes.io/os" = "linux" - node_type = "static" - } - - container { - name = "nginx-ingress-controller" - image = "quay.io/kubernetes-ingress-controller/nginx-ingress-controller:${var.nginx-ingress-version}" - args = [ - "/nginx-ingress-controller", - "--configmap=${local.ns}/${kubernetes_config_map.cm-nginx.metadata.0.name}", - "--tcp-services-configmap=${local.ns}/${kubernetes_config_map.cm-tcp.metadata.0.name}", - "--udp-services-configmap=${local.ns}/${kubernetes_config_map.cm-udp.metadata.0.name}", - "--publish-service=${local.ns}/ingress-nginx", - "--annotations-prefix=nginx.ingress.kubernetes.io", - ] - security_context { - allow_privilege_escalation = true - capabilities { - drop = ["ALL"] - add = ["NET_BIND_SERVICE"] - } - run_as_user = 101 - } - - env { - name = "POD_NAME" - value_from { - field_ref { - field_path = "metadata.name" - } - } - } - - env { - name = "POD_NAMESPACE" - value_from { - field_ref { - field_path = "metadata.namespace" - } - } - } - - port { - name = "http" - container_port = 80 - protocol = "TCP" - } - - port { - name = "https" - container_port = 443 - protocol = "TCP" - } - - liveness_probe { - http_get { - path = "/healthz" - port = 10254 - scheme = "HTTP" - } - failure_threshold = 3 - initial_delay_seconds = 10 - period_seconds = 10 - success_threshold = 1 - timeout_seconds = 10 - } - - readiness_probe { - http_get { - path = "/healthz" - port = 10254 - scheme = "HTTP" - } - failure_threshold = 3 - initial_delay_seconds = 10 - period_seconds = 10 - success_threshold = 1 - timeout_seconds = 10 - } - - lifecycle { - pre_stop { - exec { - command = ["/wait-shutdown"] - } - } - } - } - } - } - } -} - -resource "kubernetes_limit_range" "lr" { - metadata { - name = "ingress-nginx" - namespace = local.ns - labels = local.labels - } - - spec { - limit { - min = { - memory = "90Mi" - cpu = "100m" - } - - type = "Container" - } - } -} - -# Specific service related to Google Cloud -resource "kubernetes_service" "svc" { - metadata { - name = "ingress-nginx" - namespace = local.ns - labels = local.labels - } - - spec { - external_traffic_policy = "Local" - type = "LoadBalancer" - selector = local.labels - - port { - name = "http" - port = 80 - protocol = "TCP" - target_port = "http" - } - - port { - name = "https" - port = 443 - protocol = "TCP" - target_port = "https" - } - } - - lifecycle { - ignore_changes = [ - // We add no annotations, but DO adds some. - metadata[0].annotations, - ] - } -} diff --git a/terraform/modules/nginx-ingress-controller/outputs.tf b/terraform/modules/nginx-ingress-controller/outputs.tf deleted file mode 100644 index 06bf715..0000000 --- a/terraform/modules/nginx-ingress-controller/outputs.tf +++ /dev/null @@ -1,11 +0,0 @@ -output "service-name" { - value = kubernetes_service.svc.metadata.0.name -} - -output "service-namespace" { - value = kubernetes_service.svc.metadata.0.namespace -} - -output "service-endpoint" { - value = kubernetes_service.svc.load_balancer_ingress.0.ip -} diff --git a/terraform/modules/nginx-ingress-controller/variables.tf b/terraform/modules/nginx-ingress-controller/variables.tf deleted file mode 100644 index 48c412c..0000000 --- a/terraform/modules/nginx-ingress-controller/variables.tf +++ /dev/null @@ -1,4 +0,0 @@ -variable "nginx-ingress-version" { - type = string - default = "0.30.0" -} diff --git a/terraform/modules/nginx-ingress-controller/version.tf b/terraform/modules/nginx-ingress-controller/version.tf deleted file mode 100644 index 0ade70f..0000000 --- a/terraform/modules/nginx-ingress-controller/version.tf +++ /dev/null @@ -1,3 +0,0 @@ -terraform { - required_version = ">=0.12" -} \ No newline at end of file diff --git a/terraform/modules/tfc-workspace/main.tf b/terraform/modules/tfc-workspace/main.tf deleted file mode 100644 index d94609b..0000000 --- a/terraform/modules/tfc-workspace/main.tf +++ /dev/null @@ -1,57 +0,0 @@ -locals { - dependentModulesPathed = formatlist("terraform/modules/%s", var.dependent_modules) - variableDescription = "Terraform-owned variable" -} - -resource "tfe_workspace" "ws" { - name = var.workspace-name - organization = var.tfc_org - auto_apply = var.auto_apply - trigger_prefixes = concat([var.directory], local.dependentModulesPathed) - working_directory = var.directory - - vcs_repo { - identifier = var.repo - branch = var.branch - oauth_token_id = var.tfc_oauth_token_id - } -} - -resource "tfe_notification_configuration" "webhook" { - name = "${var.workspace-name}-webhook" - enabled = true - destination_type = "slack" - triggers = ["run:created", "run:planning", "run:needs_attention", "run:applying", "run:completed", "run:errored"] - url = var.tfc_webhook_url - workspace_id = tfe_workspace.ws.id -} - -resource "tfe_variable" "vars" { - for_each = var.vars - - key = each.key - value = each.value - category = "terraform" - workspace_id = tfe_workspace.ws.id - sensitive = false -} - -resource "tfe_variable" "sensitive" { - for_each = var.secret-vars - - key = each.key - value = each.value - category = "terraform" - workspace_id = tfe_workspace.ws.id - sensitive = true -} - -resource "tfe_variable" "env" { - for_each = var.env-vars - - key = each.key - value = each.value - category = "env" - workspace_id = tfe_workspace.ws.id - sensitive = true -} diff --git a/terraform/modules/tfc-workspace/outputs.tf b/terraform/modules/tfc-workspace/outputs.tf deleted file mode 100644 index a5d7c3d..0000000 --- a/terraform/modules/tfc-workspace/outputs.tf +++ /dev/null @@ -1,3 +0,0 @@ -output "workspace" { - value = tfe_workspace.ws[*] -} \ No newline at end of file diff --git a/terraform/modules/tfc-workspace/variables.tf b/terraform/modules/tfc-workspace/variables.tf deleted file mode 100644 index 46acfae..0000000 --- a/terraform/modules/tfc-workspace/variables.tf +++ /dev/null @@ -1,54 +0,0 @@ -variable "workspace-name" { - type = string -} - -variable "secret-vars" { - type = map(string) - default = {} -} - -variable "vars" { - type = map(string) - default = {} -} - -variable "env-vars" { - type = map(string) - default = {} -} - -variable "repo" { - type = string -} - -variable "directory" { - type = string - default = "/" -} - -variable "branch" { - type = string - default = "master" -} - -variable "auto_apply" { - type = bool - default = false -} - -variable "dependent_modules" { - type = list(string) - default = [] -} - -variable "tfc_oauth_token_id" { - type = string -} - -variable "tfc_org" { - type = string -} - -variable "tfc_webhook_url" { - type = string -} diff --git a/terraform/modules/tfc-workspace/version.tf b/terraform/modules/tfc-workspace/version.tf deleted file mode 100644 index aad74ef..0000000 --- a/terraform/modules/tfc-workspace/version.tf +++ /dev/null @@ -1,7 +0,0 @@ -terraform { - required_version = ">=0.12.6" -} - -provider "tfe" { - version = ">=0.15.0" -} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index eaa9e0a..6932979 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1081,6 +1081,11 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@cloudflare/workers-types@^2.1.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@cloudflare/workers-types/-/workers-types-2.1.0.tgz#6e4f07567f4c914ad811c2d7a6172d0ae834c7f6" + integrity sha512-VmXaHTq0lt6Xre4aK1hUK25hjZjuEUkHtdUEt0FJamv+NzQO54Gwp6Zr5Cfu6SP5EQ/tTmTMP/tK9npA8zhcCg== + "@cnakazawa/watch@^1.0.3": version "1.0.4" resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a" @@ -2280,6 +2285,27 @@ "@types/cheerio" "*" "@types/react" "*" +"@types/eslint-scope@^3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.0.tgz#4792816e31119ebd506902a482caec4951fabd86" + integrity sha512-O/ql2+rrCUe2W2rs7wMR+GqPRcgB6UiqN5RhrR5xruFlY7l9YLMn0ZkDzjoHLeiFkR8MCQZVudUuuvQ2BLC9Qw== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + +"@types/eslint@*": + version "7.2.6" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.6.tgz#5e9aff555a975596c03a98b59ecd103decc70c3c" + integrity sha512-I+1sYH+NPQ3/tVqCeUSBwTE/0heyvtXqpIopUUArlBm0Kpocb8FbMa3AZ/ASKIFpN3rnEx932TTXDbt9OXsNDw== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*", "@types/estree@^0.0.45": + version "0.0.45" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.45.tgz#e9387572998e5ecdac221950dab3e8c3b16af884" + integrity sha512-jnqIUKDUqJbDIUxm0Uj7bnlMnRm1T/eZ9N+AVMqhPgzrba2GhGG5o/jCTwmdPK709nEZsGoMzXEDUjcXHa3W0g== + "@types/glob-base@^0.3.0": version "0.3.0" resolved "https://registry.yarnpkg.com/@types/glob-base/-/glob-base-0.3.0.tgz#a581d688347e10e50dd7c17d6f2880a10354319d" @@ -2365,7 +2391,7 @@ jest-diff "^26.0.0" pretty-format "^26.0.0" -"@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6": +"@types/json-schema@*", "@types/json-schema@^7.0.3", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.6": version "7.0.6" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.6.tgz#f4c7ec43e81b319a9815115031709f26987891f0" integrity sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw== @@ -2560,6 +2586,11 @@ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.3.tgz#9c088679876f374eb5983f150d4787aa6fb32d7e" integrity sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ== +"@types/uuid@^8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.0.tgz#215c231dff736d5ba92410e6d602050cce7e273f" + integrity sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ== + "@types/webpack-env@^1.15.3": version "1.16.0" resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.16.0.tgz#8c0a9435dfa7b3b1be76562f3070efb3f92637b4" @@ -2916,6 +2947,11 @@ acorn@^7.1.0, acorn@^7.1.1, acorn@^7.4.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== +acorn@^8.0.4: + version "8.0.4" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.0.4.tgz#7a3ae4191466a6984eee0fe3407a4f3aa9db8354" + integrity sha512-XNP0PqF1XD19ZlLKvB7cMmnZswW4C/03pRHgirB30uSJTaS3A3V1/P4sS3HPvFmjoriPCJQs+JDSbm4bL1TxGQ== + add-px-to-style@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/add-px-to-style/-/add-px-to-style-1.0.0.tgz#d0c135441fa8014a8137904531096f67f28f263a" @@ -5654,6 +5690,14 @@ enhanced-resolve@^4.0.0, enhanced-resolve@^4.3.0: memory-fs "^0.5.0" tapable "^1.0.0" +enhanced-resolve@^5.3.1: + version "5.4.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.4.0.tgz#a8bcf23b00affac9455cf71efd80844f4054f4dc" + integrity sha512-ZmqfWURB2lConOBM1JdCVfPyMRv5RdKWktLXO6123p97ovVm2CLBgw9t5MBj3jJWA6eHyOeIws9iJQoGFR4euQ== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.0.0" + enquirer@^2.3.5: version "2.3.6" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" @@ -6104,7 +6148,7 @@ event-target-shim@^5.0.0: resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== -events@^3.0.0: +events@^3.0.0, events@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/events/-/events-3.2.0.tgz#93b87c18f8efcd4202a461aec4dfc0556b639379" integrity sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg== @@ -8541,7 +8585,7 @@ jest-worker@24.9.0, jest-worker@^24.9.0: merge-stream "^2.0.0" supports-color "^6.1.0" -jest-worker@^26.2.1, jest-worker@^26.6.2: +jest-worker@^26.2.1, jest-worker@^26.6.1, jest-worker@^26.6.2: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== @@ -8870,6 +8914,11 @@ loader-runner@^2.4.0: resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== +loader-runner@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.1.0.tgz#f70bc0c29edbabdf2043e7ee73ccc3fe1c96b42d" + integrity sha512-oR4lB4WvwFoC70ocraKhn5nkKSs23t57h9udUgw8o0iH8hMXeEoRuUgfcvgUwAJ1ZpRqBvcou4N2SMvM1DwMrA== + loader-utils@1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" @@ -8888,7 +8937,7 @@ loader-utils@2.0.0, loader-utils@^2.0.0: emojis-list "^3.0.0" json5 "^2.1.2" -loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: +loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.3, loader-utils@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" integrity sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA== @@ -9334,7 +9383,7 @@ micromatch@^3.1.10, micromatch@^3.1.4: snapdragon "^0.8.1" to-regex "^3.0.2" -micromatch@^4.0.2: +micromatch@^4.0.0, micromatch@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259" integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q== @@ -11957,6 +12006,13 @@ serialize-javascript@^4.0.0: dependencies: randombytes "^2.1.0" +serialize-javascript@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4" + integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA== + dependencies: + randombytes "^2.1.0" + serve-favicon@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/serve-favicon/-/serve-favicon-2.5.0.tgz#935d240cdfe0f5805307fdfe967d88942a2cbcf0" @@ -12202,7 +12258,7 @@ sort-package-json@1.48.0: is-plain-obj "2.1.0" sort-object-keys "^1.1.3" -source-list-map@^2.0.0: +source-list-map@^2.0.0, source-list-map@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== @@ -12218,7 +12274,7 @@ source-map-resolve@^0.5.0, source-map-resolve@^0.5.2: source-map-url "^0.4.0" urix "^0.1.0" -source-map-support@^0.5.16, source-map-support@^0.5.6, source-map-support@~0.5.12: +source-map-support@^0.5.16, source-map-support@^0.5.6, source-map-support@~0.5.12, source-map-support@~0.5.19: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== @@ -12236,7 +12292,7 @@ source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, sourc resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== -source-map@0.7.3, source-map@^0.7.3: +source-map@0.7.3, source-map@^0.7.3, source-map@~0.7.2: version "0.7.3" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== @@ -12859,6 +12915,11 @@ tapable@^1.0.0, tapable@^1.1.3: resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA== +tapable@^2.0.0, tapable@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.1.1.tgz#b01cc1902d42a7bb30514e320ce21c456f72fd3f" + integrity sha512-Wib1S8m2wdpLbmQz0RBEVosIyvb/ykfKXf3ZIDqvWoMg/zTNm6G/tDSuUM61J1kNCDXWJrLHGSFeMhAG+gAGpQ== + tar-fs@^2.0.0, tar-fs@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" @@ -12949,6 +13010,18 @@ terser-webpack-plugin@^3.0.0: terser "^4.8.0" webpack-sources "^1.4.3" +terser-webpack-plugin@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.0.3.tgz#ec60542db2421f45735c719d2e17dabfbb2e3e42" + integrity sha512-zFdGk8Lh9ZJGPxxPE6jwysOlATWB8GMW8HcfGULWA/nPal+3VdATflQvSBSLQJRCmYZnfFJl6vkRTiwJGNgPiQ== + dependencies: + jest-worker "^26.6.1" + p-limit "^3.0.2" + schema-utils "^3.0.0" + serialize-javascript "^5.0.1" + source-map "^0.6.1" + terser "^5.3.8" + terser@5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/terser/-/terser-5.1.0.tgz#1f4ab81c8619654fdded51f3157b001e1747281d" @@ -12967,6 +13040,15 @@ terser@^4.1.2, terser@^4.6.3, terser@^4.8.0: source-map "~0.6.1" source-map-support "~0.5.12" +terser@^5.3.8: + version "5.5.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.5.1.tgz#540caa25139d6f496fdea056e414284886fb2289" + integrity sha512-6VGWZNVP2KTUcltUQJ25TtNjx/XgdDsBDKGt8nN0MpydU36LmbPPcMBd2kmtZNNGVVDLg44k7GKeHHj+4zPIBQ== + dependencies: + commander "^2.20.0" + source-map "~0.7.2" + source-map-support "~0.5.19" + test-exclude@^5.2.3: version "5.2.3" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-5.2.3.tgz#c3d3e1e311eb7ee405e092dac10aefd09091eac0" @@ -13197,6 +13279,17 @@ ts-jest@^26.4.4: semver "7.x" yargs-parser "20.x" +ts-loader@^8.0.11: + version "8.0.11" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-8.0.11.tgz#35d58a65932caacb120426eea59eca841786c899" + integrity sha512-06X+mWA2JXoXJHYAesUUL4mHFYhnmyoCdQVMXofXF552Lzd4wNwSGg7unJpttqUP7ziaruM8d7u8LUB6I1sgzA== + dependencies: + chalk "^2.3.0" + enhanced-resolve "^4.0.0" + loader-utils "^1.0.2" + micromatch "^4.0.0" + semver "^6.0.0" + ts-pnp@^1.1.6: version "1.2.0" resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92" @@ -13626,7 +13719,7 @@ uuid@^7.0.3: resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg== -uuid@^8.0.0, uuid@^8.3.0: +uuid@^8.0.0, uuid@^8.3.0, uuid@^8.3.1: version "8.3.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.1.tgz#2ba2e6ca000da60fce5a196954ab241131e05a31" integrity sha512-FOmRr+FmWEIG8uhZv6C2bTgEVXsHk08kE7mPlrBbEe+c3r9pjceVPgupIfNIhc4yx55H69OXANrUaSuu9eInKg== @@ -13750,6 +13843,14 @@ watchpack@^1.7.4: chokidar "^3.4.1" watchpack-chokidar2 "^2.0.1" +watchpack@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.0.1.tgz#2f2192c542c82a3bcde76acd3411470c120426a8" + integrity sha512-vO8AKGX22ZRo6PiOFM9dC0re8IcKh8Kd/aH2zeqUc6w4/jBGlTy2P7fTC6ekT0NjVeGjgU2dGC5rNstKkeLEQg== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + web-namespaces@^1.0.0: version "1.1.4" resolved "https://registry.yarnpkg.com/web-namespaces/-/web-namespaces-1.1.4.tgz#bc98a3de60dadd7faefc403d1076d529f5e030ec" @@ -13822,6 +13923,14 @@ webpack-sources@1.4.3, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-s source-list-map "^2.0.0" source-map "~0.6.1" +webpack-sources@^2.1.1: + version "2.2.0" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-2.2.0.tgz#058926f39e3d443193b6c31547229806ffd02bac" + integrity sha512-bQsA24JLwcnWGArOKUxYKhX3Mz/nK1Xf6hxullKERyktjNMC4x8koOeaDNTA2fEJ09BdWLbM/iTW0ithREUP0w== + dependencies: + source-list-map "^2.0.1" + source-map "^0.6.1" + webpack-virtual-modules@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.2.2.tgz#20863dc3cb6bb2104729fff951fbe14b18bd0299" @@ -13887,6 +13996,36 @@ webpack@^4.44.2: watchpack "^1.7.4" webpack-sources "^1.4.1" +webpack@^5.9.0: + version "5.9.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.9.0.tgz#af2e9cf9d6c7867cdcf214ea3bb5eb77aece6895" + integrity sha512-YnnqIV/uAS5ZrNpctSv378qV7HmbJ74DL+XfvMxzbX1bV9e7eeT6eEWU4wuUw33CNr/HspBh7R/xQlVjTEyAeA== + dependencies: + "@types/eslint-scope" "^3.7.0" + "@types/estree" "^0.0.45" + "@webassemblyjs/ast" "1.9.0" + "@webassemblyjs/helper-module-context" "1.9.0" + "@webassemblyjs/wasm-edit" "1.9.0" + "@webassemblyjs/wasm-parser" "1.9.0" + acorn "^8.0.4" + browserslist "^4.14.5" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.3.1" + eslint-scope "^5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.4" + json-parse-better-errors "^1.0.2" + loader-runner "^4.1.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + pkg-dir "^4.2.0" + schema-utils "^3.0.0" + tapable "^2.1.1" + terser-webpack-plugin "^5.0.3" + watchpack "^2.0.0" + webpack-sources "^2.1.1" + whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0"