initial port to cfworkers i guess

This commit is contained in:
41666 2020-12-03 00:32:07 -05:00
parent ab9fe30b42
commit 9eeb946389
37 changed files with 367 additions and 1098 deletions

2
.gitignore vendored
View file

@ -3,3 +3,5 @@ node_modules
*.log
storybook-static
.next
worker
wrangler.toml

View file

@ -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"
}
}
}

1
src/backend-worker/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
dist

10
src/backend-worker/bindings.d.ts vendored Normal file
View file

@ -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;
}

View file

@ -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,
})
);
};

View file

@ -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 }));
};

View file

@ -0,0 +1,3 @@
export const LoginCallback = (request: Request): Response => {
return new Response('login-callback!');
};

View file

@ -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));
});

View file

@ -0,0 +1,73 @@
export type Handler = (request: Request) => Promise<Response> | 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> | 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,
});
}
}

View file

@ -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"
}

View file

@ -0,0 +1,7 @@
export const Bounce = (url: string): Response =>
new Response(null, {
status: 303,
headers: {
location: url,
},
});

View file

@ -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'),
},
},
],
},
};

View file

View file

@ -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
}

View file

@ -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)
}

View file

@ -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
}
}
}
}
}
}

View file

@ -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
}

View file

@ -1,6 +0,0 @@
{
"deployment_env": {
"production": {},
"staging": {}
}
}

View file

@ -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]
}

View file

@ -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",
]
}

View file

@ -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"
}

View file

@ -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",
]
}

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -1,4 +0,0 @@
terraform {
required_version = ">=0.12"
}

View file

@ -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"
}
}

View file

@ -1,7 +0,0 @@
output "service_account_token" {
value = lookup(kubernetes_secret.sa-key, "data.token", "")
}
output "namespace" {
value = local.ns
}

View file

@ -1,9 +0,0 @@
variable "environment_tag" {
type = string
default = "main"
}
variable "app_name" {
type = string
}

View file

@ -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,
]
}
}

View file

@ -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
}

View file

@ -1,4 +0,0 @@
variable "nginx-ingress-version" {
type = string
default = "0.30.0"
}

View file

@ -1,3 +0,0 @@
terraform {
required_version = ">=0.12"
}

View file

@ -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
}

View file

@ -1,3 +0,0 @@
output "workspace" {
value = tfe_workspace.ws[*]
}

View file

@ -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
}

View file

@ -1,7 +0,0 @@
terraform {
required_version = ">=0.12.6"
}
provider "tfe" {
version = ">=0.15.0"
}

157
yarn.lock
View file

@ -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"